import { motion, useReducedMotion, type Variants } from "motion/react"; import type { ReactNode } from "react"; const EASE = [0.2, 0.7, 0, 1] as const; /** Content enters once, when it scrolls into view: 12 px rise and a short fade. */ export function Reveal({ children, delay = 0, className, as = "div" }: { children: ReactNode; delay?: number; className?: string; as?: "div" | "li" | "section" | "figure"; }) { const reduce = useReducedMotion(); const Tag = motion[as]; return ( {children} ); } const wipe: Variants = { hidden: { clipPath: "inset(0 100% 0 0)" }, show: { clipPath: "inset(0 0% 0 0)", transition: { duration: 1.1, ease: EASE } }, }; /** Image wipe: clip-path opens left to right, like a scanner pass. The observer watches the unclipped outer box: * Chrome's IntersectionObserver honours clip-path, so a fully clipped element would never count as visible. */ export function Wipe({ children, className }: { children: ReactNode; className?: string }) { const reduce = useReducedMotion(); return ( {children} ); } export const stagger: Variants = { hidden: {}, show: { transition: { staggerChildren: 0.08 } }, }; export const rise: Variants = { hidden: { opacity: 0, y: 14 }, show: { opacity: 1, y: 0, transition: { duration: 0.64, ease: EASE } }, }; export function SectionHead({ index, label }: { index: string; label: string }) { return ( <>
{index}
); } const lineUp: Variants = { hidden: { y: "108%" }, show: { y: "0%", transition: { duration: 0.9, ease: EASE } }, }; /** A headline line that slides up from behind a mask (editorial reveal); static with reduced motion. */ export function MaskLine({ children, className }: { children: ReactNode; className?: string }) { return ( {children} ); } /** Viewfinder corners over a photo (decorative). */ export function Viewfinder({ inset = 18, size = 28, color = "var(--signal)" }: { inset?: number; size?: number; color?: string }) { const s = { position: "absolute" as const, width: size, height: size, borderColor: color, borderStyle: "solid" }; return ( ); } /** Engineering dimension line; draws itself when it scrolls into view. */ export function DimensionLine({ label }: { label: string }) { const reduce = useReducedMotion(); return (
{label}
); }