/* Art.n Architects — shared site JS (quiet edition). GSAP + ScrollTrigger + Lenis, all vendored locally. Every feature is guarded by element existence so one file serves every page. */ (function () { 'use strict'; document.documentElement.classList.add('js'); var reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; var mobile = matchMedia('(max-width: 860px)').matches; /* ---------- header ---------- */ var hdr = document.querySelector('header.site'); if (hdr) { var onScroll = function () { hdr.classList.toggle('scrolled', window.scrollY > 40); }; addEventListener('scroll', onScroll, { passive: true }); onScroll(); } /* ---------- mobile menu ---------- */ var menu = document.getElementById('site-menu'); var menuBtn = document.querySelector('.menu-label'); var closeBtn = document.querySelector('.menu-close'); if (menu && menuBtn && closeBtn) { var setMenu = function (open) { menu.hidden = !open; menu.classList.toggle('open', open); menuBtn.setAttribute('aria-expanded', String(open)); if (open) closeBtn.focus(); else menuBtn.focus(); }; menuBtn.addEventListener('click', function () { setMenu(true); }); closeBtn.addEventListener('click', function () { setMenu(false); }); menu.addEventListener('click', function (e) { if (e.target.tagName === 'A') setMenu(false); }); addEventListener('keydown', function (e) { if (e.key === 'Escape' && !menu.hidden) setMenu(false); }); } /* ---------- reveals (CSS does the easing; IO adds the class) ---------- */ var revealEls = document.querySelectorAll('.reveal'); if (revealEls.length) { if (reduce || !('IntersectionObserver' in window)) { revealEls.forEach(function (el) { el.classList.add('is-in'); }); } else { var ro = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('is-in'); ro.unobserve(e.target); } }); }, { rootMargin: '0px 0px -8% 0px' }); revealEls.forEach(function (el) { ro.observe(el); }); } } /* ---------- page-transition veil ---------- */ var veil = document.querySelector('.veil-page'); if (veil && !reduce && window.gsap) { if (sessionStorage.getItem('an-veil') === '1') { sessionStorage.removeItem('an-veil'); gsap.fromTo(veil, { scaleY: 1, transformOrigin: 'bottom' }, { scaleY: 0, duration: .6, ease: 'power3.inOut', delay: .05 }); } document.addEventListener('click', function (e) { if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; var a = e.target.closest('a'); if (!a || a.target === '_blank' || a.hasAttribute('download')) return; var href = a.getAttribute('href') || ''; if (!href || href[0] === '#' || /^(https?:)?\/\//.test(href) && a.host !== location.host) return; if (/^(mailto|tel|javascript):/.test(href)) return; if (a.pathname === location.pathname && a.hash) return; e.preventDefault(); sessionStorage.setItem('an-veil', '1'); gsap.fromTo(veil, { scaleY: 0, transformOrigin: 'top' }, { scaleY: 1, duration: .5, ease: 'power3.in', onComplete: function () { location.href = a.href; } }); }); addEventListener('pageshow', function (e) { if (e.persisted) gsap.set(veil, { scaleY: 0 }); }); } if (!window.gsap || reduce) return; // fully usable without motion below this line gsap.registerPlugin(ScrollTrigger); /* ---------- Lenis inertial scroll ---------- */ if (window.Lenis && !mobile) { var lenis = new Lenis({ lerp: 0.085 }); lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add(function (t) { lenis.raf(t * 1000); }); gsap.ticker.lagSmoothing(0); } /* ---------- hero statement: lines rise once ---------- */ var heroLines = document.querySelectorAll('.hero-quiet h1 .line > span'); if (heroLines.length) { gsap.from(heroLines, { yPercent: 112, duration: 1.2, ease: 'power4.out', stagger: .12, delay: .2 }); gsap.from('.hero-quiet .meta', { opacity: 0, y: 14, duration: 1, delay: 1, ease: 'power2.out' }); } /* ---------- slow parallax on full-bleed panels & tagged imagery ---------- */ document.querySelectorAll('.panel-full .photo').forEach(function (ph) { gsap.fromTo(ph, { yPercent: -7 }, { yPercent: 7, ease: 'none', scrollTrigger: { trigger: ph.parentNode, start: 'top bottom', end: 'bottom top', scrub: true } }); }); document.querySelectorAll('[data-inner]').forEach(function (img) { gsap.fromTo(img, { yPercent: -7 }, { yPercent: 0, ease: 'none', scrollTrigger: { trigger: img, start: 'top bottom', end: 'bottom top', scrub: true } }); }); /* ---------- gallery items drift in ---------- */ var gItems = document.querySelectorAll('.gallery a'); if (gItems.length) { gsap.set(gItems, { opacity: 0, y: 30 }); ScrollTrigger.batch(gItems, { start: 'top 92%', onEnter: function (batch) { gsap.to(batch, { opacity: 1, y: 0, duration: 1, ease: 'power2.out', stagger: .09 }); } }); } /* ---------- scroll-scrubbed model orbit (loads model-viewer on approach) ---------- */ var mv = document.querySelector('model-viewer[data-orbit-scrub]'); if (mv) { var mvLoaded = false; var loadMV = function () { if (mvLoaded) return; mvLoaded = true; var s = document.createElement('script'); s.type = 'module'; s.src = '/assets/vendor/model-viewer.min.js'; document.head.appendChild(s); }; if ('IntersectionObserver' in window) { var io = new IntersectionObserver(function (entries) { if (entries.some(function (e) { return e.isIntersecting; })) { loadMV(); io.disconnect(); } }, { rootMargin: '800px 0px' }); io.observe(mv); } else loadMV(); var section = mv.closest('section') || mv; var state = { theta: -30 }; gsap.to(state, { theta: 330, ease: 'none', scrollTrigger: { trigger: section, start: 'top 85%', end: 'bottom 15%', scrub: 1 }, onUpdate: function () { mv.setAttribute('camera-orbit', state.theta.toFixed(1) + 'deg 76deg auto'); } }); ScrollTrigger.create({ trigger: section, start: 'top 85%', end: 'bottom 15%', onToggle: function (self) { if (self.isActive) mv.removeAttribute('auto-rotate'); else mv.setAttribute('auto-rotate', ''); } }); } /* ---------- GLightbox (project galleries) ---------- */ if (window.GLightbox && document.querySelector('.glightbox')) { GLightbox({ selector: '.glightbox', touchNavigation: true, loop: true }); } })();