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}
{index}
{label}
>
);
}
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}
);
}