TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use client";2import * as React from "react";3import { cn } from "@/lib/utils";45/**6 * Subtle fade-up on scroll. Content is rendered visible on the server (SEO, no-JS, screenshots);7 * once mounted, elements still below the fold are hidden and revealed when they enter the viewport.8 * Respects prefers-reduced-motion via the global CSS rule (transitions collapse to ~0 ms).9 */10export function Reveal({ children, className, delay = 0, as = "div" }: { children: React.ReactNode; className?: string; delay?: number; as?: "div" | "section" | "li" }) {11 const ref = React.useRef<HTMLElement | null>(null);12 const [state, setState] = React.useState<"ssr" | "hidden" | "shown">("ssr");1314 React.useEffect(() => {15 const el = ref.current;16 if (!el || typeof IntersectionObserver === "undefined") return;17 const rect = el.getBoundingClientRect();18 if (rect.top < window.innerHeight * 0.95) return; // already on screen: never hide19 setState("hidden");20 const io = new IntersectionObserver(21 (entries) => {22 if (entries.some((e) => e.isIntersecting)) {23 setState("shown");24 io.disconnect();25 }26 },27 { rootMargin: "0px 0px -60px 0px", threshold: 0.05 },28 );29 io.observe(el);30 return () => io.disconnect();31 }, []);3233 const Tag = as as "div";34 return (35 <Tag36 ref={ref as React.RefObject<HTMLDivElement>}37 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")}38 style={state !== "ssr" ? { transitionDelay: `${delay}s` } : undefined}39 >40 {children}41 </Tag>42 );43}44