"use client"; import * as React from "react"; import { cn } from "@/lib/utils"; /** * Subtle fade-up on scroll. Content is rendered visible on the server (SEO, no-JS, screenshots); * once mounted, elements still below the fold are hidden and revealed when they enter the viewport. * Respects prefers-reduced-motion via the global CSS rule (transitions collapse to ~0 ms). */ export function Reveal({ children, className, delay = 0, as = "div" }: { children: React.ReactNode; className?: string; delay?: number; as?: "div" | "section" | "li" }) { const ref = React.useRef(null); const [state, setState] = React.useState<"ssr" | "hidden" | "shown">("ssr"); React.useEffect(() => { const el = ref.current; if (!el || typeof IntersectionObserver === "undefined") return; const rect = el.getBoundingClientRect(); if (rect.top < window.innerHeight * 0.95) return; // already on screen: never hide setState("hidden"); const io = new IntersectionObserver( (entries) => { if (entries.some((e) => e.isIntersecting)) { setState("shown"); io.disconnect(); } }, { rootMargin: "0px 0px -60px 0px", threshold: 0.05 }, ); io.observe(el); return () => io.disconnect(); }, []); const Tag = as as "div"; return ( } className={cn(className, "transition-[opacity,transform] duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] will-change-[opacity,transform]", state === "hidden" && "opacity-0 translate-y-3")} style={state !== "ssr" ? { transitionDelay: `${delay}s` } : undefined} > {children} ); }