Codepen: Hero Slider
// start auto rotation (with fresh progress bar) function startAutoRotation() if (autoInterval) stopAutoRotation(); resetProgressBar(); // start progress bar from 0% autoInterval = setInterval(() => if (isTransitioning) return; goToNextSlide(); , autoDelay);
/* navigation arrows */ .slider-arrow position: absolute; top: 50%; transform: translateY(-50%); width: 48px; height: 48px; background: rgba(20, 20, 30, 0.7); backdrop-filter: blur(8px); border-radius: 60px; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 20; transition: all 0.2s; border: 1px solid rgba(255,255,255,0.25); color: white; font-size: 1.5rem;
/* slides track */ .slides-track display: flex; transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94); will-change: transform; hero slider codepen
/* overlay for better text contrast */ .slide::before content: ""; position: absolute; inset: 0; background: linear-gradient(105deg, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.4) 70%, rgba(0,0,0,0.2) 100%); z-index: 1;
.btn-primary, .btn-outline display: inline-flex; align-items: center; gap: 0.6rem; padding: 0.85rem 1.8rem; border-radius: 50px; font-weight: 600; font-size: 0.95rem; transition: all 0.25s ease; cursor: pointer; text-decoration: none; backdrop-filter: blur(4px); border: none; // start auto rotation (with fresh progress bar)
/* Responsive touches */ @media (max-width: 780px) .hero-content padding: 1.5rem 1.8rem; margin-left: 4%; margin-right: 1rem; backdrop-filter: blur(6px); .hero-content p max-width: 100%; font-size: 0.95rem; .slider-arrow width: 40px; height: 40px; font-size: 1.2rem; .arrow-left left: 0.8rem; .arrow-right right: 0.8rem; .slide min-height: 500px; height: 60vh; .btn-primary, .btn-outline padding: 0.7rem 1.4rem; font-size: 0.85rem;
// ---------- helper: update slider position & active states ---------- function updateSlider(instant = false) if (isTransitioning && !instant) return; if (instant) track.style.transition = 'none'; else track.style.transition = 'transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; const offset = -currentIndex * 100; track.style.transform = `translateX($offset%)`; // update active dot const dots = document.querySelectorAll('.dot'); dots.forEach((dot, idx) => if (idx === currentIndex) dot.classList.add('active'); else dot.classList.remove('active'); ); // small callback after transition ends if needed if (instant) // force reflow then restore transition setTimeout(() => track.style.transition = ''; , 20); if (isTransitioning) return
// navigate to specific index function goToSlide(index, fromAuto = false) if (isTransitioning) return; if (index < 0) index = totalSlides - 1; if (index >= totalSlides) index = 0; if (index === currentIndex && !fromAuto) return; isTransitioning = true; currentIndex = index; // update slider with animation updateSlider(false); // after transition, re-enable flag setTimeout(() => isTransitioning = false; // if auto rotation is active, restart progress bar (sync with fresh timer) if (autoInterval) // only reset progress if we are still in auto mode restartAutoRotation(); , 650); // slightly more than transition duration (0.6s) // If auto rotation active, also reset progress immediately (but careful: restartAutoRotation will call reset) if (autoInterval && !fromAuto) restartAutoRotation(); else if (autoInterval && fromAuto) // Already within the interval call, but we should reset progress to avoid mismatch // However restartAutoRotation inside the setTimeout will happen anyway, but we reset now to keep sync. if (progressInterval) clearInterval(progressInterval); resetProgressBar(); else resetProgressBar(); function goToNextSlide() if (isTransitioning) return; goToSlide(currentIndex + 1, true); function goToPrevSlide() if (isTransitioning) return; goToSlide(currentIndex - 1, true); // generate dots function buildDots() dotsContainer.innerHTML = ''; for (let i = 0; i < totalSlides; i++) const dot = document.createElement('button'); dot.classList.add('dot'); if (i === currentIndex) dot.classList.add('active'); dot.setAttribute('data-index', i); dot.setAttribute('aria-label', `Go to slide $i+1`); dot.addEventListener('click', (e) => e.stopPropagation(); if (isTransitioning) return; const idx = parseInt(dot.getAttribute('data-index'), 10); if (idx !== currentIndex) goToSlide(idx); else // if same slide, just restart auto timer for better UX if (autoInterval) restartAutoRotation(); ); dotsContainer.appendChild(dot); // handle keyboard navigation (optional) function handleKeydown(e) if (e.key === 'ArrowLeft') e.preventDefault(); goToPrevSlide(); if (autoInterval) restartAutoRotation(); else if (e.key === 'ArrowRight') e.preventDefault(); goToNextSlide(); if (autoInterval) restartAutoRotation(); // pause auto rotation on hover / touch let pauseTimeout = null; function pauseAutoRotation() if (autoInterval) stopAutoRotation(); if (progressInterval) clearInterval(progressInterval); progressInterval = null; function resumeAutoRotation() if (!autoInterval) startAutoRotation(); // attach hover & touch events to slider container for pause/resume const sliderElement = document.querySelector('.hero-slider'); if (sliderElement) sliderElement.addEventListener('mouseenter', () => pauseAutoRotation(); ); sliderElement.addEventListener('mouseleave', () => if (!autoInterval) startAutoRotation(); ); // for touch devices sliderElement.addEventListener('touchstart', () => pauseAutoRotation(); ); sliderElement.addEventListener('touchend', () => // small delay before resuming to avoid accidental taps if (pauseTimeout) clearTimeout(pauseTimeout); pauseTimeout = setTimeout(() => if (!autoInterval) startAutoRotation(); , 2000); ); // attach arrow listeners prevBtn.addEventListener('click', (e) => e.preventDefault(); goToPrevSlide(); if (autoInterval) restartAutoRotation(); ); nextBtn.addEventListener('click', (e) => e.preventDefault(); goToNextSlide(); if (autoInterval) restartAutoRotation(); ); // disable transitions during initial load & set first slide properly function initSlider() buildDots(); // make sure track starts at first slide without animation track.style.transition = 'none'; currentIndex = 0; updateSlider(true); // start auto rotation startAutoRotation(); // add keyboard listener window.addEventListener('keydown', handleKeydown); // edge: ensure progress bar fully reset after first load setTimeout(() => if (progressFill) progressFill.style.width = '0%'; resetProgressBar(); , 100); // optional: handle window resize (no reflow issues) let resizeTimeout; window.addEventListener('resize', () => if (resizeTimeout) clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => if (!isTransitioning) updateSlider(true); , 150); ); // preload images? not necessary, but great effect: prevent accidental clicks during transition // all good, initialise initSlider(); // add tiny safety to ensure dots also reflect after manual slide update // also handles if someone clicks multiple times const originalGoToSlide = goToSlide; window.__sliderDebug = false; // just for fun )(); </script> </body> </html>

