1/** 2 * hero.js â Carrossel Hero 3 * Funcionalidades: 4 * - Auto-play 6s 5 * - Crossfade via classes .is-active / .is-prev 6 * - Navegação por setas (prev/next) 7 * - Dots sincronizados 8 * - Swipe mobile (touchstart/touchend) 9 * - Teclado (ArrowLeft / ArrowRight) 10 * - Pause em hover e focus 11 * - prefers-reduced-motion: desabilita auto-play 12 */ 13 14document.addEventListener('DOMContentLoaded', () => { 15 16 const hero = document.querySelector('.hero'); 17 if (!hero) return; 18 19 // âââ Elementos ââââââââââââââââââââââââââââââââââââââââââââââââ 20 const slides = [...hero.querySelectorAll('.hero__slide')]; 21 const dots = [...hero.querySelectorAll('.hero__dot')]; 22 const btnPrev = hero.querySelector('.hero__btn--prev'); 23 const btnNext = hero.querySelector('.hero__btn--next'); 24 25 if (!slides.length) return; 26 27 // âââ Estado âââââââââââââââââââââââââââââââââââââââââââââââââââ 28 const AUTOPLAY_DELAY = 6000; 29 const prefersReducedMotion = window.matchMedia( 30 '(prefers-reduced-motion: reduce)' 31 ).matches; 32 33 let currentIndex = 0; 34 let autoPlayTimer = null; 35 let isAnimating = false; 36 const ANIM_DURATION = 800; // ms â deve ser igual à transition do CSS 37 38 // âââ Ir para slide ââââââââââââââââââââââââââââââââââââââââââââ 39 function goToSlide(index) { 40 if (isAnimating || index === currentIndex) return; 41 42 isAnimating = true; 43 44 // Remove classes do slide atual 45 slides[currentIndex].classList.remove('is-active'); 46 slides[currentIndex].classList.add('is-prev'); 47 slides[currentIndex].setAttribute('aria-hidden', 'true'); 48 49 // Atualiza dots 50 dots[currentIndex]?.classList.remove('is-active'); 51 dots[currentIndex]?.setAttribute('aria-selected', 'false'); 52 53 currentIndex = (index + slides.length) % slides.length; 54 55 // Ativa novo slide 56 slides[currentIndex].classList.add('is-active'); 57 slides[currentIndex].classList.remove('is-prev'); 58 slides[currentIndex].setAttribute('aria-hidden', 'false'); 59 60 dots[currentIndex]?.classList.add('is-active'); 61 dots[currentIndex]?.setAttribute('aria-selected', 'true'); 62 63 // Remove .is-prev após a transição completar 64 setTimeout(() => { 65 slides.forEach((slide) => slide.classList.remove('is-prev')); 66 isAnimating = false; 67 }, ANIM_DURATION); 68 } 69 70 // âââ Navegação ââââââââââââââââââââââââââââââââââââââââââââââââ 71 function next() { goToSlide(currentIndex + 1); } 72 function prev() { goToSlide(currentIndex - 1); } 73 74 // âââ Auto-play ââââââââââââââââââââââââââââââââââââââââââââââââ 75 function startAutoPlay() { 76 if (prefersReducedMotion || autoPlayTimer !== null) return; 77 autoPlayTimer = setInterval(next, AUTOPLAY_DELAY); 78 } 79 80 function stopAutoPlay() { 81 clearInterval(autoPlayTimer); 82 autoPlayTimer = null; 83 } 84 85 function resetAutoPlay() { 86 stopAutoPlay(); 87 startAutoPlay(); 88 } 89 90 // âââ Setas ââââââââââââââââââââââââââââââââââââââââââââââââââââ 91 btnPrev?.addEventListener('click', () => { prev(); resetAutoPlay(); }); 92 btnNext?.addEventListener('click', () => { next(); resetAutoPlay(); }); 93 94 // âââ Dots âââââââââââââââââââââââââââââââââââââââââââââââââââââ 95 dots.forEach((dot, i) => { 96 dot.addEventListener('click', () => { goToSlide(i); resetAutoPlay(); }); 97 }); 98 99 // âââ Pause em hover / focus ââââââââââââââââââââââââââââââââââââ 100 hero.addEventListener('mouseenter', stopAutoPlay); 101 hero.addEventListener('mouseleave', startAutoPlay); 102 hero.addEventListener('focusin', stopAutoPlay); 103 hero.addEventListener('focusout', (e) => {
104 if (!hero.contains(e.relatedTarget)) startAutoPlay(); 105 }); 106 107 // âââ Teclado (ArrowLeft / ArrowRight) âââââââââââââââââââââââââ 108 hero.addEventListener('keydown', (e) => { 109 if (e.key === 'ArrowRight') { next(); resetAutoPlay(); } 110 if (e.key === 'ArrowLeft') { prev(); resetAutoPlay(); } 111 }); 112 113 // Tornar a seção focável para capturar eventos de teclado 114 if (!hero.hasAttribute('tabindex')) { 115 hero.setAttribute('tabindex', '0'); 116 } 117 118 // âââ Swipe mobile âââââââââââââââââââââââââââââââââââââââââââââ 119 let touchStartX = 0; 120 const SWIPE_THRESHOLD = 50; 121 122 hero.addEventListener('touchstart', (e) => { 123 touchStartX = e.touches[0].clientX; 124 }, { passive: true }); 125 126 hero.addEventListener('touchend', (e) => { 127 const diff = touchStartX - e.changedTouches[0].clientX; 128 if (Math.abs(diff) > SWIPE_THRESHOLD) { 129 diff > 0 ? next() : prev(); 130 resetAutoPlay(); 131 } 132 }, { passive: true }); 133 134 // âââ Inicialização ââââââââââââââââââââââââââââââââââââââââââââ 135 // Garantir estado correto no slide 0 136 slides.forEach((slide, i) => { 137 if (i === 0) { 138 slide.classList.add('is-active'); 139 slide.setAttribute('aria-hidden', 'false'); 140 } else { 141 slide.classList.remove('is-active'); 142 slide.setAttribute('aria-hidden', 'true'); 143 } 144 }); 145 146 dots.forEach((dot, i) => { 147 dot.classList.toggle('is-active', i === 0); 148 dot.setAttribute('aria-selected', i === 0 ? 'true' : 'false'); 149 }); 150 151 startAutoPlay(); 152 153});
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.