1/* Motion layer v2 â CSS-first (robust): IO reveals, CSS entrances, var-driven parallax. 2 GSAP only for the pinned walkthrough (scrub:true = event-driven, no ticker dependency). */ 3window.initMotion = function () { 4 /* Re-entrant: the single-page shell re-mounts sections on route change, so any 5 ScrollTrigger built against the previous DOM is killed before rebuilding. */ 6 if (window.ScrollTrigger && window.ScrollTrigger.getAll) window.ScrollTrigger.getAll().forEach(function (t) { t.kill(); }); 7 window.__motionInit = true; 8 const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; 9 document.body.classList.add('has-motion'); 10 11 /* Scroll reveals retired (craft pass): section headings and body copy now render at 12 their final state on load. The only motion left on scroll belongs to the two places 13 where it carries meaning â the MCP answer lines and the mobile notification cards â 14 and both are owned by their own components. */ 15 if (reduce) return; 16 17 /* Split-text headline: word-level rise, 50ms stagger (CSS keyframes) */ 18 document.querySelectorAll('[data-split]').forEach((el) => { 19 if (el.querySelector('.sw')) return; 20 el.innerHTML = el.textContent.trim().split(/\s+/).map((w) => '<span class="sw"><span class="swi">' + w + '</span></span>').join(' '); 21 }); 22 document.querySelectorAll('.hero-h1 .swi').forEach((w, i) => { w.style.animationDelay = (.08 + i * .05) + 's'; w.classList.add('swi-a'); }); 23 24 /* Lenis smooth scroll â self-driven rAF (independent of GSAP ticker) */ 25 if (window.Lenis && !window.__lenis) { 26 const lenis = new Lenis({ duration: 1.05, smoothWheel: true }); 27 window.__lenis = lenis; 28 const raf = (t) => { lenis.raf(t); requestAnimationFrame(raf); }; 29 requestAnimationFrame(raf); 30 if (window.ScrollTrigger) lenis.on('scroll', ScrollTrigger.update); 31 } 32 33 /* Custom cursor: disabled â native pointer only */ 34 if (false) { 35 const dot = document.createElement('span'), ring = document.createElement('span'); 36 dot.className = 'cur-dot'; ring.className = 'cur-ring'; 37 document.body.append(dot, ring); 38 let mx = -100, my = -100, rx = -100, ry = -100, live = false; 39 addEventListener('pointermove', (e) => { 40 mx = e.clientX; my = e.clientY; 41 if (!live) { live = true; document.body.classList.add('cur-live'); rx = mx; ry = my; } 42 dot.style.transform = 'translate(' + (mx - 3.5) + 'px,' + (my - 3.5) + 'px)'; 43 }, { passive: true }); 44 const loop = () => { rx += (mx - rx) * .16; ry += (my - ry) * .16; ring.style.transform = 'translate(' + (rx - 17) + 'px,' + (ry - 17) + 'px)'; requestAnimationFrame(loop); }; 45 requestAnimationFrame(loop); 46 addEventListener('pointerover', (e) => { ring.classList.toggle('on', !!(e.target.closest && e.target.closest('a,button,[role="button"],input,textarea,select,[data-cursor]'))); }, { passive: true }); 47 } 48 49 /* Section spotlight (cursor-tracking glow) + scroll drift (gentle counter-parallax) */ 50 const fxSecs = [...document.querySelectorAll('main [data-screen-label] > section')].filter((s) => s.id !== 'walkthrough'); 51 fxSecs.forEach((s) => { 52 s.classList.add('sec-fx'); 53 s.addEventListener('pointermove', (e) => { const r = s.getBoundingClientRect(); s.style.setProperty('--mx', (e.clientX - r.left) + 'px'); s.style.setProperty('--my', (e.clientY - r.top) + 'px'); }, { passive: true }); 54 }); 55 /* Hero parallax: mouse-move + scroll drift via CSS vars, speed â data-depth */ 56 const hero = document.getElementById('hero'); 57 const layers = hero ? [...hero.querySelectorAll('[data-depth]')] : []; 58 if (hero && layers.length) { 59 if (matchMedia('(min-width: 900px) and (hover: hover)').matches) { 60 hero.addEventListener('pointermove', (e) => { 61 const r = hero.getBoundingClientRect(); 62 const dx = (e.clientX - r.left) / r.width - .5, dy = (e.clientY - r.top) / r.height - .5; 63 layers.forEach((l) => { const d = +l.dataset.depth; l.style.setProperty('--px', dx * d * 26 + 'px'); l.style.setProperty('--py', dy * d * 16 + 'px'); }); 64 const card = document.getElementById('hv-card'); 65 if (card) { card.style.setProperty('--ry', dx * 5 + 'deg'); card.style.setProperty('--rx', -dy * 4 + 'deg'); } 66 }); 67 hero.addEventListener('pointerleave', () => {
68 layers.forEach((l) => { l.style.setProperty('--px', '0px'); l.style.setProperty('--py', '0px'); }); 69 const card = document.getElementById('hv-card'); 70 if (card) { card.style.setProperty('--ry', '0deg'); card.style.setProperty('--rx', '0deg'); } 71 }); 72 } 73 const drift = () => { const y = Math.min(window.scrollY, 900); layers.forEach((l) => l.style.setProperty('--sy', -(y * .05 * +l.dataset.depth) + 'px')); }; 74 if (window.__driftHandler) removeEventListener('scroll', window.__driftHandler); 75 window.__driftHandler = drift; 76 addEventListener('scroll', drift, { passive: true }); 77 } 78 79 /* Walkthrough: pinned scrub timeline on desktop (scrub:true â driven by scroll events) */ 80 if (window.gsap && window.ScrollTrigger && matchMedia('(min-width: 900px)').matches) { 81 gsap.registerPlugin(ScrollTrigger); 82 const wt = document.getElementById('walkthrough'); 83 if (wt) { 84 const pctEl = document.getElementById('wt-pct'), pct = { v: 0 }; 85 gsap.set('#wt-row', { opacity: 0, x: -26 }); 86 gsap.set('#wt-done', { opacity: 0, scale: .8 }); 87 gsap.set('#wt-bar', { width: '0%' }); 88 gsap.set(['#wt-step-1', '#wt-step-2'], { opacity: .38 }); 89 gsap.set('#wt-rail-fill', { height: '0%' }); 90 if (pctEl) pctEl.textContent = '0%'; 91 const dealX = () => document.getElementById('wt-col-2').getBoundingClientRect().left - document.getElementById('wt-col-0').getBoundingClientRect().left; 92 const tl = gsap.timeline({ scrollTrigger: { trigger: wt, start: 'top top', end: '+=1600', pin: true, scrub: true } }); 93 tl.to('#wt-row', { opacity: 1, x: 0, duration: .6, ease: 'power2.out' }) 94 .to('#wt-step-0', { opacity: .38, duration: .3 }, '+=.3') 95 .to('#wt-step-1', { opacity: 1, duration: .3 }, '<') 96 .to('#wt-rail-fill', { height: '50%', duration: .3, ease: 'none' }, '<') 97 .to('#wt-deal', { x: dealX, duration: 1, ease: 'power2.inOut' }, '<') 98 .to('#wt-step-1', { opacity: .38, duration: .3 }, '+=.4') 99 .to('#wt-step-2', { opacity: 1, duration: .3 }, '<') 100 .to('#wt-rail-fill', { height: '100%', duration: .3, ease: 'none' }, '<') 101 .to('#wt-bar', { width: '96%', duration: 1, ease: 'power2.out' }, '<') 102 .to(pct, { v: 96, duration: 1, onUpdate: () => { if (pctEl) pctEl.textContent = Math.round(pct.v) + '%'; } }, '<') 103 .to('#wt-done', { opacity: 1, scale: 1, duration: .5, ease: 'back.out(2)' }, '-=.3') 104 .to({}, { duration: .3 }); 105 } 106 } 107};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.