import { useEffect, useState } from 'react'; import type { Variants } from 'framer-motion'; // EaseOutExpo — smooth editorial deceleration export const easeOutExpo = [0.22, 1, 0.36, 1] as const; export const fadeInUp: Variants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: easeOutExpo } }, }; export const fadeIn: Variants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { duration: 0.4, ease: easeOutExpo } }, }; export const staggerContainer: Variants = { hidden: {}, visible: { transition: { staggerChildren: 0.1 } }, }; export const viewportOnce = { once: true, amount: 0.3 } as const; // Hook: returns true if user prefers reduced motion. Components use this to // disable Framer Motion animations (per spec, a11y requirement). export function usePrefersReducedMotion() { const [reduced, setReduced] = useState(false); useEffect(() => { const mq = window.matchMedia('(prefers-reduced-motion: reduce)'); setReduced(mq.matches); const handler = () => setReduced(mq.matches); mq.addEventListener('change', handler); return () => mq.removeEventListener('change', handler); }, []); return reduced; } // Pass to as motion-reduced guard. When reduced, variants resolve to visible immediately. export const motionProps = (reduced: boolean) => reduced ? { initial: 'visible' as const, animate: 'visible' as const, whileInView: undefined, viewport: undefined } : {};