Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

One Response

Leave a Reply

Your email address will not be published. Required fields are marked *

============================================================ */ (function () { 'use strict'; /* ── 1. WAIT FOR DOM ──────────────────────────────────── */ document.addEventListener('DOMContentLoaded', function () { initLenis(); initAOS(); initNavbar(); initCustomCursor(); initCounters(); initPortfolioFilter(); initMarquee(); initPageTransition(); }); /* ── 2. LENIS SMOOTH SCROLL ────────────────────────────── */ function initLenis() { if (typeof Lenis === 'undefined') return; const lenis = new Lenis({ duration: 1.2, easing: function (t) { return Math.min(1, 1.001 - Math.pow(2, -10 * t)); }, orientation: 'vertical', smoothWheel: true, wheelMultiplier: 0.9, touchMultiplier: 1.5, infinite: false, }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); // Expose globally for other scripts window.lenisInstance = lenis; // Sync with AOS lenis.on('scroll', function () { if (typeof AOS !== 'undefined') AOS.refresh(); }); // Smooth anchor links document.querySelectorAll('a[href^="#"]').forEach(function (anchor) { anchor.addEventListener('click', function (e) { const target = document.querySelector(this.getAttribute('href')); if (target) { e.preventDefault(); lenis.scrollTo(target, { offset: -80, duration: 1.4 }); } }); }); } /* ── 3. AOS SCROLL ANIMATIONS ──────────────────────────── */ function initAOS() { if (typeof AOS === 'undefined') return; AOS.init({ duration: 800, easing: 'cubic-bezier(0.16, 1, 0.3, 1)', once: true, offset: 80, delay: 0, }); } /* ── 4. NAVBAR HIDE ON SCROLL DOWN ─────────────────────── */ function initNavbar() { const header = document.querySelector( '.elementor-location-header, #masthead, .site-header' ); if (!header) return; let lastScrollY = 0; let ticking = false; function updateNav() { const currentY = window.scrollY; if (currentY > lastScrollY && currentY > 120) { header.classList.add('pw-nav-hidden'); } else { header.classList.remove('pw-nav-hidden'); } lastScrollY = currentY; ticking = false; } window.addEventListener('scroll', function () { if (!ticking) { requestAnimationFrame(updateNav); ticking = true; } }); } /* ── 5. CUSTOM CURSOR ───────────────────────────────────── */ function initCustomCursor() { // Only on desktop if (window.innerWidth < 1024) return; const cursor = document.createElement('div'); cursor.className = 'pw-cursor'; document.body.appendChild(cursor); const ring = document.createElement('div'); ring.className = 'pw-cursor-ring'; document.body.appendChild(ring); let mouseX = 0, mouseY = 0; let ringX = 0, ringY = 0; document.addEventListener('mousemove', function (e) { mouseX = e.clientX; mouseY = e.clientY; cursor.style.left = mouseX + 'px'; cursor.style.top = mouseY + 'px'; }); // Ring follows with lag function animateRing() { ringX += (mouseX - ringX) * 0.12; ringY += (mouseY - ringY) * 0.12; ring.style.left = ringX + 'px'; ring.style.top = ringY + 'px'; requestAnimationFrame(animateRing); } animateRing(); // Hover states const hoverTargets = 'a, button, .pw-portfolio-item, .elementor-button'; document.querySelectorAll(hoverTargets).forEach(function (el) { el.addEventListener('mouseenter', function () { cursor.style.transform = 'translate(-50%, -50%) scale(0.3)'; ring.style.width = '64px'; ring.style.height = '64px'; ring.style.borderColor = 'var(--pw-gold)'; }); el.addEventListener('mouseleave', function () { cursor.style.transform = 'translate(-50%, -50%) scale(1)'; ring.style.width = '40px'; ring.style.height = '40px'; ring.style.borderColor = 'var(--pw-black)'; }); }); } /* ── 6. COUNTER ANIMATION ───────────────────────────────── */ /* Animates any element with class .pw-counter and data-target="NUMBER" */ function initCounters() { const counters = document.querySelectorAll('.pw-counter'); if (!counters.length) return; const observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { animateCounter(entry.target); observer.unobserve(entry.target); } }); }, { threshold: 0.4 }); counters.forEach(function (counter) { observer.observe(counter); }); function animateCounter(el) { const target = parseInt(el.dataset.target, 10); const suffix = el.dataset.suffix || ''; const duration = 2000; const start = performance.now(); function update(currentTime) { const elapsed = currentTime - start; const progress = Math.min(elapsed / duration, 1); // Ease-out cubic const eased = 1 - Math.pow(1 - progress, 3); const current = Math.round(eased * target); el.textContent = current + suffix; if (progress < 1) { requestAnimationFrame(update); } else { el.textContent = target + suffix; } } requestAnimationFrame(update); } } /* ── 7. PORTFOLIO FILTER ───────────────────────────────── */ /* For .pw-portfolio-grid with filter buttons */ function initPortfolioFilter() { const filterBtns = document.querySelectorAll('.pw-filter-btn'); const portfolioItems = document.querySelectorAll('.pw-portfolio-item'); if (!filterBtns.length || !portfolioItems.length) return; filterBtns.forEach(function (btn) { btn.addEventListener('click', function () { const filter = this.dataset.filter; // Active state filterBtns.forEach(function (b) { b.classList.remove('pw-filter-active'); }); this.classList.add('pw-filter-active'); // Filter items portfolioItems.forEach(function (item) { if (filter === 'all' || item.dataset.category === filter) { item.style.display = 'block'; item.style.animation = 'filterFadeIn 0.4s ease forwards'; } else { item.style.display = 'none'; } }); }); }); // Add keyframe dynamically const style = document.createElement('style'); style.textContent = ` @keyframes filterFadeIn { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } } .pw-filter-btn { background: transparent; border: 1px solid var(--pw-border); font-family: var(--font-body, 'DM Sans', sans-serif); font-size: 12px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; padding: 10px 22px; cursor: pointer; transition: all 0.25s; color: var(--pw-grey, #6B6B6B); } .pw-filter-btn:hover, .pw-filter-btn.pw-filter-active { background: var(--pw-black, #141414); border-color: var(--pw-black, #141414); color: var(--pw-white, #FAFAF8); } `; document.head.appendChild(style); } /* ── 8. MARQUEE SCROLL TEXT ─────────────────────────────── */ function initMarquee() { const track = document.querySelector('.pw-marquee-track'); if (!track) return; // Duplicate content for seamless loop const clone = track.innerHTML; track.innerHTML = clone + clone; } /* ── 9. PAGE TRANSITION ─────────────────────────────────── */ function initPageTransition() { const overlay = document.createElement('div'); overlay.className = 'pw-page-transition'; document.body.prepend(overlay); // On internal link click — animate out document.querySelectorAll('a').forEach(function (link) { const href = link.getAttribute('href'); if (!href || href.startsWith('#') || href.startsWith('mailto') || href.startsWith('tel') || href.startsWith('http') || link.target === '_blank') return; link.addEventListener('click', function (e) { e.preventDefault(); overlay.style.transform = 'translateY(0)'; overlay.style.transition = 'transform 0.4s cubic-bezier(0.65, 0, 0.35, 1)'; setTimeout(function () { window.location.href = href; }, 400); }); }); // Reveal on load window.addEventListener('load', function () { overlay.style.transition = 'transform 0.6s cubic-bezier(0.16, 1, 0.3, 1)'; overlay.style.transform = 'translateY(-100%)'; }); } /* ── 10. STAGGERED ANIMATION FOR CARD GRIDS ─────────────── */ /* Apply to any grid with .pw-stagger-grid — children animate in sequence */ document.addEventListener('DOMContentLoaded', function () { const staggerGrids = document.querySelectorAll('.pw-stagger-grid'); staggerGrids.forEach(function (grid) { const children = grid.children; Array.from(children).forEach(function (child, i) { child.style.opacity = '0'; child.style.transform = 'translateY(32px)'; child.style.transition = 'opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), ' + 'transform 0.7s cubic-bezier(0.16, 1, 0.3, 1)'; child.style.transitionDelay = i * 0.1 + 's'; }); const observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { Array.from(entry.target.children).forEach(function (child) { child.style.opacity = '1'; child.style.transform = 'translateY(0)'; }); observer.unobserve(entry.target); } }); }, { threshold: 0.15 }); observer.observe(grid); }); }); })();