PageSourceSearch

https://www.cruzestudio.com.ar/java/trabajos.js?v=16

js cruzestudio.com.ar collected 2026-10-06 13:35:01 UTC 5,668 bytes, 147 lines download raw bytes

1/**
2 * ============================================================
3 *  CRUZ ESTUDIO ® — Trabajos archive
4 *
5 *  - View switcher Grid / List (legacy).
6 *  - Menú hamburguesa mobile (match index).
7 *  - HOVER CAROUSEL en cada card: al pasar el cursor sobre un
8 *    .project-item, sus imágenes adicionales (data-images JSON
9 *    array) se ciclan con crossfade suave + barra de progreso.
10 * ============================================================
11 */
12document.addEventListener('DOMContentLoaded', () => {
13
14    /* ---------- 1. VIEW SWITCHER (Explorar / Grid / List) ----------
15       La vista EXPLORAR (canvas arrastrable) es la default en TODOS los dispositivos
16       — también en mobile (arrastre táctil). El canvas lo controla
17       window.CruzCanvas (java/trabajos-canvas.js). */
18    const viewBtns  = document.querySelectorAll('.cg-viewswitch .view-btn');
19    const container = document.getElementById('archive-main');
20
21    function setView(view) {
22        viewBtns.forEach(b => b.classList.toggle('active', b.getAttribute('data-view') === view));
23        if (view === 'canvas') {
24            if (window.CruzCanvas) window.CruzCanvas.activate();
25        } else {
26            if (window.CruzCanvas) window.CruzCanvas.deactivate();
27            container.classList.toggle('list-mode', view === 'list');
28            container.classList.toggle('grid-mode', view !== 'list');
29        }
30    }
31
32    viewBtns.forEach(btn => {
33        btn.addEventListener('click', () => setView(btn.getAttribute('data-view')));
34    });
35
36    // Vista por defecto: EXPLORAR (inmersiva) en TODOS los dispositivos.
37    if (window.CruzCanvas) setView('canvas');
38    else setView('grid');
39
40    /* ---------- 2. MENÚ HAMBURGUESA (match index) ---------- */
41    const menuToggle = document.getElementById('mobile-menu');
42    const navLinks   = document.getElementById('nav-links');
43    if (menuToggle) {
44        menuToggle.addEventListener('click', () => {
45            navLinks.classList.toggle('active');
46            menuToggle.classList.toggle('open');
47            document.body.classList.toggle('menu-open');
48        });
49    }
50
51    /* ---------- 3. HOVER CAROUSEL EN LAS CARDS ---------- */
52    initHoverCarousel();
53});
54
55/**
56 * Activa el carousel de imágenes adicionales al hover en cada .project-item.
57 * Espera que cada item tenga:
58 *   data-images='["url1", "url2", "url3", ...]'
59 * (JSON array de URLs). Si el atributo no existe o tiene <2 imágenes,
60 * el item se ignora (queda con su comportamiento normal).
61 */
62function initHoverCarousel() {
63    const items = document.querySelectorAll('.project-item[data-images]');
64    if (!items.length) return;
65
66    const CYCLE_MS = 800;       // tiempo entre cambios de imagen
67    // Touch / sin hover real: skip carousel (no aporta nada)
68    const isTouch = window.matchMedia('(hover: none)').matches;
69    if (isTouch) return;
70
71    items.forEach((item) => {
72        const visualBox = item.querySelector('.visual-box');
73        if (!visualBox) return;
74
75        // Parse de las imágenes del carousel
76        let extras = [];
77        try {
78            extras = JSON.parse(item.getAttribute('data-images') || '[]');
79        } catch (_) {
80            extras = [];
81        }
82        if (!Array.isArray(extras) || extras.length < 2) return;
83
84        // Inyectamos cada URL como un <img class="pi-cycle-img"> stackeado.
85        // La primera arranca con .is-active. El <img> estático original queda
86        // como fallback debajo del stack.
87        const cycleImgs = extras.map((src, i) => {
88            const img = document.createElement('img');
89            img.className = 'pi-cycle-img' + (i === 0 ? ' is-active' : '');
90            img.alt = '';
91            img.setAttribute('aria-hidden', 'true');
92            img.loading = 'eager';
93            img.decoding = 'async';
94            img.src = src;
95            return img;
96        });
97
98        // Indicador de progreso (línea inferior amarilla)
99        const indicator = document.createElement('div');
100        indicator.className = 'pi-cycle-indicator';
101        const indicatorFill = document.createElement('div');
102        indicatorFill.className = 'pi-cycle-indicator-fill';
103        indicator.appendChild(indicatorFill);
104
105        cycleImgs.forEach(img => visualBox.appendChild(img));
106        visualBox.appendChild(indicator);
107
108        let currentIdx = 0;
109        let cycleTimer = null;
110
111        function showIndex(idx) {
112            cycleImgs.forEach((img, i) => {
113                img.classList.toggle('is-active', i === idx);
114            });
115            // Reset + animar progress bar
116            indicatorFill.style.transition = 'none';
117            indicatorFill.style.width = '0%';
118            void indicatorFill.offsetWidth; // force reflow
119            indicatorFill.style.transition = 'width ' + CYCLE_MS + 'ms linear';
120            indicatorFill.style.width = '100%';
121        }
122
123        function startCycle() {
124            currentIdx = 0;
125            showIndex(currentIdx);
126            cycleTimer = setInterval(() => {
127                currentIdx = (currentIdx + 1) % cycleImgs.length;
128                showIndex(currentIdx);
129            }, CYCLE_MS);
130        }
131
132        function stopCycle() {
133            if (cycleTimer) {
134                clearInterval(cycleTimer);
135                cycleTimer = null;
136            }
137            cycleImgs.forEach((img, i) => {
138                img.classList.toggle('is-active', i === 0);
139            });
140            indicatorFill.style.transition = 'width 0.3s ease';
141            indicatorFill.style.width = '0%';
142        }
143
144        item.addEventListener('mouseenter', startCycle);
145        item.addEventListener('mouseleave', stopCycle);
146    });
147}

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.