/** * motion.ts * --------------------------------------------------------------------------- * Shared animation helpers. Most scroll reveals are handled in CSS via the * `reveal` utility (IntersectionObserver in Reveal.astro, zero JS framework * cost). These are for the few React islands that genuinely need Framer Motion * (animated hero background, mobile menu, contact form states). * * Every helper here respects `prefers-reduced-motion`: when set, animations are * collapsed to instant final states. */ /** True when the user has asked the OS to minimize motion. SSR-safe. */ export function prefersReducedMotion(): boolean { if (typeof window === 'undefined' || !window.matchMedia) return false; return window.matchMedia('(prefers-reduced-motion: reduce)').matches; } /** Standard ease curves reused across islands. */ export const easings = { out: [0.22, 1, 0.36, 1] as const, inOut: [0.65, 0, 0.35, 1] as const, }; /** Fade + rise entrance. Used for staggered card/hero children. */ export const fadeUp = { hidden: { opacity: 0, y: 24 }, visible: { opacity: 1, y: 0 }, }; /** A simple, accessible menu/framer transition. */ export const mobileMenuTransition = { hidden: { opacity: 0, height: 0 }, visible: { opacity: 1, height: 'auto' }, } as const;