/** Decorative geometry uses the PNG's exact 2172×724 pixel coordinate system. */ export const heroMotionMarkup = (imageUrl: string): string => `
`; export function createHeroMotion(root: HTMLElement): void { const hero = root.querySelector('.landing-hero'); if (!hero) return; const reduced = matchMedia('(prefers-reduced-motion: reduce)'); const finePointer = matchMedia('(hover: hover) and (pointer: fine)'); let frame = 0; let currentX = 0; let currentY = 0; let targetX = 0; let targetY = 0; const update = (): void => { currentX += (targetX-currentX)*.075; currentY += (targetY-currentY)*.075; hero.style.setProperty('--landing-parallax-x', `${currentX.toFixed(2)}px`); hero.style.setProperty('--landing-parallax-y', `${currentY.toFixed(2)}px`); frame = Math.abs(targetX-currentX)+Math.abs(targetY-currentY)>.08 ? requestAnimationFrame(update) : 0; }; const reset = (): void => { targetX = targetY = currentX = currentY = 0; cancelAnimationFrame(frame);frame=0; hero.style.setProperty('--landing-parallax-x','0px');hero.style.setProperty('--landing-parallax-y','0px'); }; hero.addEventListener('pointermove', (event) => { if (reduced.matches || !finePointer.matches || root.hidden || root.classList.contains('guide-open')) return; const bounds=hero.getBoundingClientRect(); targetX=((event.clientX-bounds.left)/bounds.width-.5)*18; targetY=((event.clientY-bounds.top)/bounds.height-.5)*12; if (!frame) frame=requestAnimationFrame(update); },{passive:true}); hero.addEventListener('pointerleave',reset,{passive:true}); reduced.addEventListener('change',()=>{if(reduced.matches)reset();}); document.addEventListener('visibilitychange',()=>{root.classList.toggle('landing-paused',document.hidden);if(document.hidden)reset();}); // Native import dialogs are outside the overlay. Pause all ambient work while // an upload/guide panel is active or when the main application is showing. const synchronize = (): void => { const blocked=!!root.hidden||!!document.querySelector('#import-dialog[open]')||!!root.querySelector('.landing-panel.visible')||root.classList.contains('guide-open'); if (root.classList.contains('landing-inactive')!==blocked) root.classList.toggle('landing-inactive',blocked); if (blocked)reset(); }; const observer=new MutationObserver(synchronize); observer.observe(root,{attributes:true,subtree:true,attributeFilter:['hidden','class']}); const importDialog=document.querySelector('#import-dialog'); if(importDialog)observer.observe(importDialog,{attributes:true,attributeFilter:['open']}); synchronize(); }