/* ═══════════════════════════════════════════════════════════ LISTIFY v2 — CORE.JS One file. One system. No duplication. Nav · Scroll reveal · Cursor · Counters · Utils ═══════════════════════════════════════════════════════════ */ 'use strict'; (function () { /* ── STATE ── */ var isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0; var isDesk = !isTouch && window.matchMedia('(hover:hover)').matches; var pRM = window.matchMedia('(prefers-reduced-motion:reduce)').matches; var W = window.innerWidth, H = window.innerHeight; window.addEventListener('resize', function () { W = window.innerWidth; H = window.innerHeight; }, { passive: true }); /* ── SAFE DOM ── */ function $(sel, ctx) { return (ctx || document).querySelector(sel); } function $$(sel, ctx) { return Array.from((ctx || document).querySelectorAll(sel)); } function on(el, ev, fn, opts) { if (el) el.addEventListener(ev, fn, opts || false); } /* ── NAV ── */ function initNav() { var nav = $('#nav'); var btn = $('#nav-mob-btn'); var drawer = $('#mob-drawer'); if (!nav) return; /* Scroll state */ on(window, 'scroll', function () { nav.classList.toggle('scrolled', window.scrollY > 50); }, { passive: true }); /* Mobile toggle */ if (btn && drawer) { on(btn, 'click', function () { var isOpen = drawer.classList.toggle('open'); btn.classList.toggle('open', isOpen); btn.setAttribute('aria-expanded', isOpen); document.body.style.overflow = isOpen ? 'hidden' : ''; }); /* Close on link click */ $$('a', drawer).forEach(function (a) { on(a, 'click', function () { drawer.classList.remove('open'); btn.classList.remove('open'); btn.setAttribute('aria-expanded', 'false'); document.body.style.overflow = ''; }); }); /* ESC */ on(document, 'keydown', function (e) { if (e.key === 'Escape' && drawer.classList.contains('open')) { drawer.classList.remove('open'); btn.classList.remove('open'); btn.setAttribute('aria-expanded', 'false'); document.body.style.overflow = ''; btn.focus(); } }); } /* Active link */ var path = window.location.pathname.split('/').pop() || 'index.html'; $$('.nav-links a, .mob-drawer a').forEach(function (a) { var href = a.getAttribute('href') || ''; if (href === path || (path === '' && href === 'index.html')) { a.classList.add('active'); } }); } /* ── SCROLL REVEAL ── */ function initScrollReveal() { if (pRM) { $$('.rv').forEach(function (el) { el.classList.add('visible'); }); return; } var obs = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('visible'); obs.unobserve(e.target); } }); }, { threshold: 0.01, rootMargin: '0px 0px 100px 0px' }); $$('.rv').forEach(function (el) { obs.observe(el); }); } /* ── BACK/FORWARD CACHE (bfcache) FIX ── */ function initBFCache() { window.addEventListener('pageshow', function (e) { if (e.persisted) { /* Page restored from bfcache — clear curtain if stuck */ var curtain = $('#page-curtain'); if (curtain) { curtain.style.transition = 'none'; curtain.style.transform = 'scaleY(0)'; } /* Re-run scroll reveal for any missed elements */ $$('.rv:not(.visible)').forEach(function (el) { el.classList.add('visible'); }); } }); } /* ── CUSTOM CURSOR ── */ function initCursor() { if (!isDesk || pRM) return; var dot = $('#cursor'); var ring = $('#cursor-ring'); if (!dot || !ring) return; var mx = 0, my = 0, rx = 0, ry = 0; on(document, 'mousemove', function (e) { mx = e.clientX; my = e.clientY; dot.style.left = mx + 'px'; dot.style.top = my + 'px'; }); on(document, 'mousedown', function () { dot.style.transform = 'translate(-50%,-50%) scale(.5)'; }); on(document, 'mouseup', function () { dot.style.transform = 'translate(-50%,-50%) scale(1)'; }); /* Lag ring */ (function raf() { rx += (mx - rx) * .11; ry += (my - ry) * .11; ring.style.left = rx + 'px'; ring.style.top = ry + 'px'; requestAnimationFrame(raf); })(); /* Hover states */ $$('a,button,.card,.price-card,.testimonial-card').forEach(function (el) { on(el, 'mouseenter', function () { ring.style.width = '52px'; ring.style.height = '52px'; ring.style.borderColor = 'rgba(60,45,30,.6)'; ring.style.borderRadius = '6px'; }); on(el, 'mouseleave', function () { ring.style.width = '36px'; ring.style.height = '36px'; ring.style.borderColor = 'rgba(60,45,30,.35)'; ring.style.borderRadius = '50%'; }); }); } /* ── MAGNETIC BUTTONS ── */ function initMagnetic() { if (!isDesk || pRM) return; $$('.btn-primary,.btn-gold,.btn-wa').forEach(function (btn) { on(btn, 'mousemove', function (e) { var r = btn.getBoundingClientRect(); var dx = e.clientX - (r.left + r.width / 2); var dy = e.clientY - (r.top + r.height / 2); btn.style.transform = 'translate(' + dx * .2 + 'px,' + dy * .2 + 'px)'; }); on(btn, 'mouseleave', function () { btn.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1)'; btn.style.transform = ''; setTimeout(function () { btn.style.transition = ''; }, 500); }); }); } /* ── ANIMATED COUNTERS ── */ function animateCounter(el) { var target = parseFloat(el.dataset.target || el.textContent) || 0; var suffix = el.dataset.suffix || ''; var prefix = el.dataset.prefix || ''; var decimals = el.dataset.decimals ? parseInt(el.dataset.decimals) : 0; var duration = 1400; var start = performance.now(); function tick(now) { var elapsed = Math.min(now - start, duration); var progress = elapsed / duration; /* Ease out cubic */ var eased = 1 - Math.pow(1 - progress, 3); var val = target * eased; el.textContent = prefix + (decimals ? val.toFixed(decimals) : Math.floor(val).toLocaleString('en-IN')) + suffix; if (elapsed < duration) requestAnimationFrame(tick); else el.textContent = prefix + (decimals ? target.toFixed(decimals) : Math.floor(target).toLocaleString('en-IN')) + suffix; } requestAnimationFrame(tick); } function initCounters() { if (pRM) return; var obs = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { animateCounter(e.target); obs.unobserve(e.target); } }); }, { threshold: 0.5 }); $$('[data-counter]').forEach(function (el) { obs.observe(el); }); } /* ── BENTO BAR FILLS ── */ function initBentoBars() { var obs = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { var w = e.target.dataset.w || '0%'; e.target.style.width = w; obs.unobserve(e.target); } }); }, { threshold: 0.3 }); $$('.bento-bar-fill').forEach(function (el) { obs.observe(el); }); } /* ── FAQ ACCORDION ── */ function initFAQ() { $$('.faq-item').forEach(function (item) { var btn = $('.faq-q', item); if (!btn) return; on(btn, 'click', function () { var isOpen = item.classList.contains('open'); $$('.faq-item.open').forEach(function (open) { open.classList.remove('open'); }); if (!isOpen) item.classList.add('open'); }); }); } /* ── MARQUEE (duplicate items for seamless loop) ── */ function initMarquee() { $$('.marquee-track').forEach(function (track) { /* Already duplicated in HTML — just ensure GPU layer */ track.style.willChange = 'transform'; }); } /* ── PAGE TRANSITIONS (curtain) ── */ function initPageTransitions() { var curtain = $('#page-curtain'); if (!curtain || pRM) return; /* Reveal on load */ curtain.style.transform = 'scaleY(0)'; /* Intercept outgoing links */ $$('a[href]').forEach(function (link) { var href = link.getAttribute('href'); if (!href || href.startsWith('#') || href.startsWith('http') || href.startsWith('mailto') || href.startsWith('tel') || href.includes('wa.me')) return; on(link, 'click', function (e) { e.preventDefault(); curtain.style.transition = 'transform .5s cubic-bezier(.76,0,.24,1)'; curtain.style.transformOrigin = 'bottom'; curtain.style.transform = 'scaleY(1)'; setTimeout(function () { window.location.href = href; }, 520); }); }); } /* ── STICKY CTA ── */ function initStickyCTA() { var cta = $('#sticky-cta'); if (!cta) return; var closeBtn = $('.scta-close', cta); var shown = false; on(window, 'scroll', function () { var pct = window.scrollY / (document.documentElement.scrollHeight - H); if (pct > 0.25 && !shown) { cta.classList.add('show'); } else if (pct <= 0.25) { cta.classList.remove('show'); } }, { passive: true }); if (closeBtn) { on(closeBtn, 'click', function () { cta.style.display = 'none'; shown = true; }); } } /* ── GSAP SCROLL ANIMATIONS (loads after DOM) ── */ function initGSAP() { if (pRM || !window.gsap || !window.ScrollTrigger) return; gsap.registerPlugin(ScrollTrigger); /* Stagger reveal on section headings */ $$('.gsap-stagger').forEach(function (el) { var children = el.children; if (!children.length) return; gsap.from(children, { opacity: 0, y: 28, duration: .7, stagger: .1, ease: 'power3.out', scrollTrigger: { trigger: el, start: 'top 85%', once: true } }); }); /* Hero headline line reveal */ $$('.hero-line').forEach(function (line, i) { gsap.from(line, { y: '105%', duration: .9, delay: .15 + i * .15, ease: 'power4.out' }); }); /* Services cards stagger */ $$('.gsap-cards').forEach(function (wrap) { gsap.from($$('.card,.price-card,.testimonial-card', wrap), { opacity: 0, y: 36, duration: .65, stagger: .1, ease: 'power3.out', scrollTrigger: { trigger: wrap, start: 'top 82%', once: true } }); }); } /* ── LENIS SMOOTH SCROLL ── */ function initLenis() { if (pRM || isTouch || !window.Lenis) return; try { var lenis = new Lenis({ lerp: .1, smoothWheel: true }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); if (window.ScrollTrigger) { lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add(function (t) { lenis.raf(t * 1000); }); gsap.ticker.lagSmoothing(0); } } catch (e) { console.warn('[Listify] Lenis:', e); } } /* ── CARD 3D TILT ── */ function initTilt() { if (!isDesk || pRM) return; $$('.tilt').forEach(function (el) { on(el, 'mousemove', function (e) { var r = el.getBoundingClientRect(); var dx = (e.clientX - r.left - r.width / 2) / r.width; var dy = (e.clientY - r.top - r.height / 2) / r.height; el.style.transform = 'perspective(800px) rotateX(' + (-dy * 6) + 'deg) rotateY(' + (dx * 6) + 'deg) translateY(-4px)'; }); on(el, 'mouseleave', function () { el.style.transition = 'transform .5s cubic-bezier(.4,0,.2,1)'; el.style.transform = ''; setTimeout(function () { el.style.transition = ''; }, 500); }); }); } /* ── DYNAMIC YEAR ── */ function setYear() { $$('[data-year]').forEach(function (el) { el.textContent = new Date().getFullYear(); }); } /* ── GLOBAL ERROR SAFETY ── */ window.onerror = function (msg, src, line) { console.warn('[Listify] Error:', msg, src, line); return false; }; /* ── INIT ALL ── */ function boot() { initNav(); initScrollReveal(); initCounters(); initBentoBars(); initFAQ(); initMarquee(); initPageTransitions(); initStickyCTA(); initTilt(); setYear(); initBFCache(); if (isDesk) { initCursor(); initMagnetic(); } /* GSAP + Lenis — deferred until scripts load */ if (window.gsap) { initGSAP(); initLenis(); } else { document.addEventListener('gsap-ready', function () { initGSAP(); initLenis(); }); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } /* Expose for page-specific use */ window.Listify = { animateCounter: animateCounter, $: $, $$: $$, on: on, isTouch: isTouch, isDesk: isDesk, pRM: pRM }; })();