/* ========================================================================== Emerald Isle Electric — script.js Deferred, non-blocking. Handles carousels, smooth scroll, menu, reveals. ========================================================================== */ (function () { 'use strict'; /* ---------------------------------------------------------------- 1. SMOOTH SCROLL for anchor links ---------------------------------------------------------------- */ document.querySelectorAll('a[href^="#"]').forEach(function (anchor) { anchor.addEventListener('click', function (e) { var target = document.querySelector(this.getAttribute('href')); if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Close mobile menu if open closeMobileMenu(); } }); }); /* ---------------------------------------------------------------- 2. STICKY HEADER — scroll class + shrink behavior ---------------------------------------------------------------- */ var header = document.querySelector('.site-header'); var lastScroll = 0; function onScroll() { var scrollY = window.pageYOffset || document.documentElement.scrollTop; if (scrollY > 40) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } lastScroll = scrollY; } window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); // run on load /* ---------------------------------------------------------------- 3. MOBILE MENU TOGGLE ---------------------------------------------------------------- */ var hamburger = document.querySelector('.hamburger'); var mobileMenu = document.querySelector('.mobile-menu'); function closeMobileMenu() { if (hamburger && mobileMenu) { hamburger.classList.remove('active'); hamburger.setAttribute('aria-expanded', 'false'); mobileMenu.classList.remove('active'); mobileMenu.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; } } if (hamburger && mobileMenu) { hamburger.addEventListener('click', function () { var isOpen = hamburger.classList.toggle('active'); hamburger.setAttribute('aria-expanded', String(isOpen)); mobileMenu.classList.toggle('active'); mobileMenu.setAttribute('aria-hidden', String(!isOpen)); document.body.style.overflow = isOpen ? 'hidden' : ''; }); // Close menu on link click mobileMenu.querySelectorAll('a').forEach(function (link) { link.addEventListener('click', closeMobileMenu); }); } /* ---------------------------------------------------------------- 4. CAROUSEL — Wheel Effect Center card is large; flanking cards are smaller (tucked behind) ---------------------------------------------------------------- */ document.querySelectorAll('[data-carousel]').forEach(function (wrapper) { var track = wrapper.querySelector('.carousel-track'); var cards = Array.from(track.children); var prevBtn = wrapper.querySelector('.carousel-btn.prev'); var nextBtn = wrapper.querySelector('.carousel-btn.next'); var dotsContainer = wrapper.querySelector('.carousel-dots'); var currentIndex = 0; var totalCards = cards.length; var autoplayInterval = null; var isDragging = false; var startX = 0; var dragThreshold = 50; // Build dots if (dotsContainer) { for (var i = 0; i < totalCards; i++) { var dot = document.createElement('div'); dot.className = 'dot' + (i === 0 ? ' active' : ''); dot.dataset.index = i; dot.addEventListener('click', function () { goTo(parseInt(this.dataset.index)); }); dotsContainer.appendChild(dot); } } function updateClasses() { cards.forEach(function (card, idx) { card.classList.remove('active', 'adjacent'); if (idx === currentIndex) { card.classList.add('active'); } else if (idx === currentIndex - 1 || idx === currentIndex + 1) { card.classList.add('adjacent'); } }); // Update dots if (dotsContainer) { var dots = dotsContainer.querySelectorAll('.dot'); dots.forEach(function (dot, idx) { dot.classList.toggle('active', idx === currentIndex); }); } } function centerCard() { if (!cards[currentIndex]) return; var card = cards[currentIndex]; var trackRect = track.getBoundingClientRect(); var wrapperRect = wrapper.getBoundingClientRect(); var cardCenter = card.offsetLeft + card.offsetWidth / 2; var wrapperCenter = wrapperRect.width / 2; var translateX = wrapperCenter - cardCenter; track.style.transform = 'translateX(' + translateX + 'px)'; } function goTo(index) { if (index < 0) index = totalCards - 1; if (index >= totalCards) index = 0; currentIndex = index; updateClasses(); centerCard(); resetAutoplay(); } function goNext() { goTo(currentIndex + 1); } function goPrev() { goTo(currentIndex - 1); } if (prevBtn) prevBtn.addEventListener('click', goPrev); if (nextBtn) nextBtn.addEventListener('click', goNext); // Touch / drag support track.addEventListener('mousedown', function (e) { isDragging = true; startX = e.clientX; track.style.transition = 'none'; }); track.addEventListener('touchstart', function (e) { isDragging = true; startX = e.touches[0].clientX; }, { passive: true }); function onDragEnd(endX) { if (!isDragging) return; isDragging = false; track.style.transition = ''; var diff = startX - endX; if (Math.abs(diff) > dragThreshold) { if (diff > 0) goNext(); else goPrev(); } else { centerCard(); } } track.addEventListener('mouseup', function (e) { onDragEnd(e.clientX); }); track.addEventListener('mouseleave', function (e) { if (isDragging) onDragEnd(e.clientX); }); track.addEventListener('touchend', function (e) { if (e.changedTouches && e.changedTouches[0]) { onDragEnd(e.changedTouches[0].clientX); } }); // Keyboard wrapper.setAttribute('tabindex', '0'); wrapper.addEventListener('keydown', function (e) { if (e.key === 'ArrowLeft') { e.preventDefault(); goPrev(); } if (e.key === 'ArrowRight') { e.preventDefault(); goNext(); } }); // Autoplay function startAutoplay() { autoplayInterval = setInterval(goNext, 5000); } function resetAutoplay() { clearInterval(autoplayInterval); startAutoplay(); } // Pause on hover/focus wrapper.addEventListener('mouseenter', function () { clearInterval(autoplayInterval); }); wrapper.addEventListener('mouseleave', startAutoplay); wrapper.addEventListener('focusin', function () { clearInterval(autoplayInterval); }); wrapper.addEventListener('focusout', startAutoplay); // Init updateClasses(); centerCard(); startAutoplay(); // Recenter on resize var resizeTimer; window.addEventListener('resize', function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(centerCard, 150); }); }); /* ---------------------------------------------------------------- 5. INTERSECTION OBSERVER — Reveal animations ---------------------------------------------------------------- */ var revealSections = document.querySelectorAll( '.services-section, .how-section, .generators-section, .offers-section, ' + '.commercial-section, .about-section, .reviews-section, .areas-section, .contact-section' ); if ('IntersectionObserver' in window) { var revealObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { entry.target.classList.add('visible'); // Also trigger child stagger entry.target.querySelectorAll('.how-grid, .offers-grid, .about-stats, .areas-grid').forEach(function (el) { el.classList.add('visible'); }); } }); }, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' }); revealSections.forEach(function (section) { section.classList.add('reveal'); revealObserver.observe(section); }); // Stagger grids document.querySelectorAll('.how-grid, .offers-grid, .about-stats, .areas-grid').forEach(function (el) { el.classList.add('reveal-stagger'); }); } /* ---------------------------------------------------------------- 6. LAZY LOAD IMAGES (already using loading="lazy" in HTML, this is a progressive enhancement for older browsers) ---------------------------------------------------------------- */ if ('IntersectionObserver' in window) { var imgObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { var img = entry.target; if (img.dataset.src) { img.src = img.dataset.src; img.removeAttribute('data-src'); } imgObserver.unobserve(img); } }); }, { rootMargin: '200px' }); document.querySelectorAll('img[data-src]').forEach(function (img) { imgObserver.observe(img); }); } })();