1/** 2 * Apple Stories Portal Mode Script 3 * Maneja la navegación basada en JS con Scroll-Snap. 4 * Usamos Vanilla JS moderno (ES6 compatible con navegadores de 2026). 5 */ 6document.addEventListener('DOMContentLoaded', () => { 7 8 // Inicializar cada módulo en caso de haber varios publicados en la página 9 document.querySelectorAll('.gkNspPM-AppleStories').forEach(module => { 10 if (module.dataset.initialized) return; 11 module.dataset.initialized = 'true'; 12 13 const scrollContainer = module.querySelector('.applestories-cards-scroll'); 14 const cards = Array.from(module.querySelectorAll('.applestories-card')); 15 const btnPrev = module.querySelector('.applestories-btn-prev'); 16 const btnNext = module.querySelector('.applestories-btn-next'); 17 const dots = Array.from(module.querySelectorAll('.applestories-dot')); 18 19 if (!scrollContainer || cards.length === 0) return; 20 21 let activeIdx = 0; 22 23 // --- 2. Sincronización visual (Active States) --- 24 const updateControls = () => { 25 let minDiff = Infinity; 26 27 // Detectar cuál es la tarjeta más visible según el scroll map 28 cards.forEach((card, idx) => { 29 const diff = Math.abs((card.offsetLeft - scrollContainer.offsetLeft) - scrollContainer.scrollLeft); 30 if (diff < minDiff) { 31 minDiff = diff; 32 activeIdx = idx; 33 } 34 }); 35 36 // Habilitar/deshabilitar botones de dirección 37 if (btnPrev && btnNext) { 38 btnPrev.disabled = activeIdx === 0; 39 40 // Si llegamos al final, el botón NEXT NO se deshabilita si tiene link 41 const hitEnd = (scrollContainer.scrollLeft + scrollContainer.clientWidth) >= (scrollContainer.scrollWidth - 10); 42 const hasLink = btnNext.dataset.link && btnNext.dataset.link !== ''; 43 44 if (hitEnd && hasLink) { 45 btnNext.disabled = false; 46 btnNext.classList.add('is-cta'); 47 } else { 48 btnNext.disabled = hitEnd; 49 btnNext.classList.remove('is-cta'); 50 } 51 } 52 53 // Sincronizar classes de los dots 54 dots.forEach((dot, idx) => { 55 if(idx === activeIdx) { 56 dot.classList.add('is-active'); 57 } else { 58 dot.classList.remove('is-active'); 59 } 60 }); 61 }; 62 63 // Escuchar el evento scroll usando rAF para óptimo rendimiento sin afectar baterÃa 64 let isScrolling; 65 scrollContainer.addEventListener('scroll', () => { 66 if (isScrolling) cancelAnimationFrame(isScrolling); 67 isScrolling = requestAnimationFrame(updateControls); 68 }, { passive: true }); 69 70 // --- 3. Arrow Navigation --- 71 if (btnPrev && btnNext) { 72 btnPrev.addEventListener('click', () => { 73 // Cálculo dinámico para mayor precisión 74 const cardWidth = cards[0].offsetWidth + parseInt(getComputedStyle(scrollContainer).gap || 0); 75 scrollContainer.scrollBy({ left: -cardWidth, behavior: 'smooth' }); 76 }); 77 78 btnNext.addEventListener('click', () => { 79 const cardWidth = cards[0].offsetWidth + parseInt(getComputedStyle(scrollContainer).gap || 0); 80 const hitEnd = (scrollContainer.scrollLeft + scrollContainer.clientWidth) >= (scrollContainer.scrollWidth - 10); 81 const link = btnNext.dataset.link; 82 83 if (hitEnd && link) { 84 // Si estamos al final y hay link, navegamos 85 window.location.href = link; 86 } else { 87 // Si no, scrolleamos exactamente una tarjeta 88 scrollContainer.scrollBy({ left: cardWidth, behavior: 'smooth' }); 89 } 90 }); 91 } 92 93 // --- 4. Dot Navigation ---
94 dots.forEach((dot, idx) => { 95 dot.addEventListener('click', () => { 96 const targetCard = cards[idx]; 97 scrollContainer.scrollTo({ 98 left: targetCard.offsetLeft - scrollContainer.offsetLeft, 99 behavior: 'smooth' 100 }); 101 }); 102 }); 103 104 // Ejecución Inicial para activar el state del dot número 1 105 updateControls(); 106 }); 107});
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.