PageSourceSearch

https://hjdoblekhuila.com/static/js/carousel.js

js hjdoblekhuila.com collected 2026-10-07 18:59:10 UTC 10,644 bytes, 269 lines download raw bytes

1/**
2 * INRAI · Carruseles del sitio
3 *
4 *  1) Ad carousel  → rotación crossfade de anunciantes en cada slot.
5 *  2) Hero carousel → 6 noticias destacadas con autoplay, dots con barra
6 *     de progreso, navegación por teclado (←/→) y swipe táctil.
7 *
8 * Idempotente: cada root se marca con data-*-init="1" para no doble-bind
9 * cuando el soft-nav router re-inicializa tras swap de #main-content.
10 */
11
12/* ── 1) Ad carousel ──────────────────────────────────────────── */
13/* Transición ÚNICA "vidrio roto" para toda publicidad: al avanzar de un
14 * slide al siguiente, capturamos la imagen del banner saliente, la dividimos
15 * en una grilla de fragmentos con clip-path irregular y los hacemos caer
16 * con rotación + gravedad. Mientras caen, debajo ya está visible el banner
17 * entrante. Los fragmentos se generan con un jitter aleatorio por
18 * transición para que cada rotura sea diferente. */
19(() => {
20    const FX_DURATION = 1200;
21    const COLS = 5;
22    const ROWS = 3;
23
24    const rand = (a, b) => a + Math.random() * (b - a);
25    const jitter = (v, r) => v + (Math.random() - 0.5) * r;
26
27    /**
28     * Polígono octagonal con vértices ligeramente perturbados, en
29     * coordenadas porcentuales del overlay (0-100). Genera una pieza
30     * dentro de una celda (c, r) de la grilla COLS×ROWS.
31     */
32    const shardPolygon = (c, r) => {
33        const x0 = (c / COLS) * 100;
34        const x1 = ((c + 1) / COLS) * 100;
35        const y0 = (r / ROWS) * 100;
36        const y1 = ((r + 1) / ROWS) * 100;
37        const mx = (x0 + x1) / 2;
38        const my = (y0 + y1) / 2;
39        const j = 3; // jitter porcentual sobre vértices
40        return `polygon(
41            ${jitter(x0, j)}% ${jitter(y0, j)}%,
42            ${jitter(mx, j)}% ${jitter(y0, j * 1.4)}%,
43            ${jitter(x1, j)}% ${jitter(y0, j)}%,
44            ${jitter(x1, j * 1.4)}% ${jitter(my, j)}%,
45            ${jitter(x1, j)}% ${jitter(y1, j)}%,
46            ${jitter(mx, j)}% ${jitter(y1, j * 1.4)}%,
47            ${jitter(x0, j)}% ${jitter(y1, j)}%,
48            ${jitter(x0, j * 1.4)}% ${jitter(my, j)}%
49        )`;
50    };
51
52    const shatter = (root, outgoing) => {
53        const img = outgoing.querySelector('img');
54        if (!img) return;
55        const url = img.currentSrc || img.src;
56        if (!url) return;
57
58        const overlay = document.createElement('div');
59        overlay.className = 'glass-overlay';
60        overlay.setAttribute('aria-hidden', 'true');
61
62        // Punto de impacto aleatorio cerca del centro
63        const impactX = rand(38, 62);
64        const impactY = rand(38, 62);
65
66        const crack = document.createElement('div');
67        crack.className = 'glass-crack';
68        crack.style.setProperty('--impact-x', `${impactX}%`);
69        crack.style.setProperty('--impact-y', `${impactY}%`);
70        overlay.appendChild(crack);
71
72        for (let r = 0; r < ROWS; r++) {
73            for (let c = 0; c < COLS; c++) {
74                const shard = document.createElement('span');
75                shard.className = 'glass-shard';
76
77                // Centroide aproximado de la celda
78                const cx = (c + 0.5) / COLS * 100;
79                const cy = (r + 0.5) / ROWS * 100;
80
81                // Vector de explosión desde el punto de impacto (kick inicial)
82                const dx = cx - impactX;
83                const dy = cy - impactY;
84                const len = Math.hypot(dx, dy) || 1;
85                const kick = rand(10, 22);
86                const kickX = (dx / len) * kick;
87                const kickY = (dy / len) * kick;
88
89                // Caída: empuje horizontal residual + gravedad fuerte en Y
90                const drift = (dx / len) * rand(20, 80);
91                const fall = rand(220, 380) + Math.abs(dy) * rand(1.4, 2.2);
92                const rot  = rand(-260, 260);
93                const delay = Math.random() * 180;
94
95                shard.style.setProperty('--img', `url("${url.replace(/"/g, '\\"')}")`);
96                shard.style.setProperty('--shape', shardPolygon(c, r));
97                shard.style.setProperty('--origin-x', `${cx}%`);
98                shard.style.setProperty('--origin-y', `${cy}%`);
99                shard.style.setProperty('--kick-x', `${kickX}px`);
100                shard.style.setProperty('--kick-y', `${kickY}px`);
101                shard.style.setProperty('--tx', `${drift}px`);
102                shard.style.setProperty('--ty', `${fall}px`);
103                shard.style.setProperty('--rot', `${rot}deg`);
104                shard.style.setProperty('--delay', `${delay}ms`);
105
106                overlay.appendChild(shard);
107            }
108        }
109
110        root.appendChild(overlay);
111        window.setTimeout(() => overlay.remove(), FX_DURATION);
112    };
113
114    const initAdCarousel = (root) => {
115        if (root.dataset.adCarouselInit === '1') return;
116        const slides = [...root.querySelectorAll('.ad-carousel__slide')];
117        const counter = root.querySelector('[data-ad-current]');
118        if (slides.length < 2) return;
119        root.dataset.adCarouselInit = '1';
120        let current = 0;
121        let timer;
122        let busy = false;
123
124        const advance = () => {
125            if (busy) return;
126            busy = true;
127            const outgoing = slides[current];
128            const nextIdx = (current + 1) % slides.length;
129            const incoming = slides[nextIdx];
130
131            // El entrante se hace visible primero (queda debajo del overlay).
132            incoming.classList.add('is-active');
133            shatter(root, outgoing);
134
135            // El saliente sale del flujo cuando los pedazos ya cubren el
136            // banner; así no hay flash de "cambio brusco" antes del efecto.
137            window.setTimeout(() => {
138                outgoing.classList.remove('is-active');
139            }, 60);
140
141            current = nextIdx;
142            if (counter) counter.textContent = current + 1;
143            window.setTimeout(() => { busy = false; }, FX_DURATION);
144        };
145
146        const start = () => { timer = window.setInterval(advance, 6000); };
147        const stop  = () => { window.clearInterval(timer); };
148
149        root.addEventListener('mouseenter', stop);
150        root.addEventListener('mouseleave', start);
151        start();
152    };
153
154    const initAll = () => {
155        document.querySelectorAll('[data-ad-carousel]').forEach(initAdCarousel);
156    };
157
158    if (document.readyState === 'loading') {
159        document.addEventListener('DOMContentLoaded', initAll);
160    } else {
161        initAll();
162    }
163    document.addEventListener('nav-soft:loaded', initAll);
164})();
165
166/* ── 2) Hero carousel ────────────────────────────────────────── */
167(() => {
168    const initHero = (root) => {
169        if (!root || root.dataset.heroInit === '1') return;
170        const slides = [...root.querySelectorAll('.hero__slide')];
171        const dots   = [...root.querySelectorAll('[data-hero-dot]')];
172        const prev   = root.querySelector('[data-hero-prev]');
173        const next   = root.querySelector('[data-hero-next]');
174        if (slides.length < 2) return;
175        root.dataset.heroInit = '1';
176
177        const duration = parseInt(root.dataset.autoplay, 10) || 6000;
178        root.style.setProperty('--hero-duration', `${duration}ms`);
179
180        let index = slides.findIndex(s => s.classList.contains('is-active'));
181        if (index < 0) index = 0;
182        let timer = null;
183        let paused = false;
184
185        const go = (target) => {
186            const n = (target + slides.length) % slides.length;
187            if (n === index) return;
188
189            const out = slides[index];
190            const into = slides[n];
191            out.classList.remove('is-active');
192            out.setAttribute('aria-hidden', 'true');
193            out.setAttribute('tabindex', '-1');
194            into.classList.add('is-active');
195            into.removeAttribute('aria-hidden');
196            into.removeAttribute('tabindex');
197
198            dots[index].classList.remove('is-active');
199            dots[index].setAttribute('aria-selected', 'false');
200            dots[n].classList.add('is-active');
201            dots[n].setAttribute('aria-selected', 'true');
202
203            index = n;
204        };
205
206        const stop = () => { clearTimeout(timer); timer = null; };
207        const play = () => {
208            stop();
209            if (paused) return;
210            timer = setTimeout(() => { go(index + 1); play(); }, duration);
211        };
212
213        /* Navegación manual */
214        prev && prev.addEventListener('click', () => { go(index - 1); play(); });
215        next && next.addEventListener('click', () => { go(index + 1); play(); });
216        dots.forEach((dot, i) => {
217            dot.addEventListener('click', () => { go(i); play(); });
218        });
219
220        /* Pausa al pasar el cursor (también pausa la barra de progreso vía CSS) */
221        root.addEventListener('mouseenter', () => {
222            paused = true; root.dataset.paused = 'true'; stop();
223        });
224        root.addEventListener('mouseleave', () => {
225            paused = false; delete root.dataset.paused; play();
226        });
227
228        /* Teclado: ← → cuando el carrusel tiene foco */
229        root.addEventListener('keydown', (e) => {
230            if (e.key === 'ArrowLeft')  { go(index - 1); play(); }
231            if (e.key === 'ArrowRight') { go(index + 1); play(); }
232        });
233
234        /* Swipe táctil */
235        let touchX = null;
236        root.addEventListener('touchstart', (e) => {
237            touchX = e.touches[0].clientX;
238            paused = true; stop();
239        }, { passive: true });
240        root.addEventListener('touchend', (e) => {
241            if (touchX === null) return;
242            const dx = e.changedTouches[0].clientX - touchX;
243            if (Math.abs(dx) > 40) go(dx < 0 ? index + 1 : index - 1);
244            touchX = null;
245            paused = false; play();
246        });
247
248        /* Pausa cuando la pestaña no está visible (ahorra CPU).
249         * Listener global montado una vez por root vivo. */
250        const onVis = () => {
251            if (document.hidden) { paused = true; stop(); }
252            else { paused = false; play(); }
253        };
254        document.addEventListener('visibilitychange', onVis);
255
256        play();
257    };
258
259    const initAll = () => {
260        document.querySelectorAll('[data-hero]').forEach(initHero);
261    };
262
263    if (document.readyState === 'loading') {
264        document.addEventListener('DOMContentLoaded', initAll);
265    } else {
266        initAll();
267    }
268    document.addEventListener('nav-soft:loaded', initAll);
269})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.