'use client'; import React, { CSSProperties, PropsWithChildren } from 'react'; import { useInViewOnce } from './useInViewOnce'; type RevealProps = PropsWithChildren<{ as?: React.ElementType; /** px to translate on Y axis initially */ offsetY?: number; /** px to translate on X axis initially (rarely needed) */ offsetX?: number; /** ms */ durationMs?: number; /** ms */ delayMs?: number; /** Optional className passed to the wrapper */ className?: string; /** Optional style merged into wrapper */ style?: CSSProperties; /** Intersection thresholds */ threshold?: number | number[]; /** Root margin for preloading */ rootMargin?: string; /** When true, disable animations and render normally */ headless?: boolean; }>; const Reveal: React.FC = ({ as = 'div', offsetY = 12, offsetX = 0, durationMs = 500, delayMs = 0, className, style, threshold, rootMargin, headless, children, }) => { const Tag = as as any; const { ref, hasIntersected } = useInViewOnce({ threshold, rootMargin }); // In headless mode, render without any animation or transition. if (headless) { return ( {children} ); } // Respect reduced motion is handled in the hook by triggering immediately. const baseTransform = `translate(${offsetX}px, ${offsetY}px)`; const eased = 'cubic-bezier(0.22, 1, 0.36, 1)'; // subtle spring-ish ease const inlineStyles: CSSProperties = { opacity: hasIntersected ? 1 : 0, transform: hasIntersected ? 'none' : baseTransform, transition: `opacity ${durationMs}ms ${eased} ${delayMs}ms, transform ${durationMs}ms ${eased} ${delayMs}ms`, willChange: hasIntersected ? undefined : 'opacity, transform', ...style, }; return ( {children} ); }; export default Reveal;