"use client"; import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react"; /* --------------------------------------------------------------- reveal */ export function Reveal({ children, delay = 0, className = "", }: { children: ReactNode; delay?: number; className?: string; }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { el.classList.add("is-in"); return; } const io = new IntersectionObserver( (entries) => { for (const e of entries) { if (e.isIntersecting) { el.classList.add("is-in"); io.disconnect(); } } }, { rootMargin: "0px 0px -12% 0px", threshold: 0.08 } ); const rect = el.getBoundingClientRect(); const onScreen = rect.top < window.innerHeight && rect.bottom > 0; if (onScreen) { // First screenful: stagger in document order so it fills top to bottom. const all = Array.from(document.querySelectorAll(".reveal")); const order = Math.min(Math.max(0, all.indexOf(el)), 8); el.style.setProperty("--delay", `${delay + order * 90}ms`); requestAnimationFrame(() => el.classList.add("is-in")); } else { io.observe(el); } return () => io.disconnect(); }, [delay]); return (
{children}
); } /* ------------------------------------------------------------ primitives */ export function Section({ id, children, className = "", }: { id?: string; children: ReactNode; className?: string; }) { return (
{children}
); } export function SectionHeading({ index, eyebrow, title, lead, }: { index?: string; eyebrow?: string; title: ReactNode; lead?: ReactNode; }) { return (
{index ? {index} : null} {index && eyebrow ? / : null} {eyebrow}

{title}

{lead ?

{lead}

: null}
); } export function Stat({ value, label, sub }: { value: string; label: string; sub?: string }) { return (
{value}
{label}
{sub ?
{sub}
: null}
); } /* -------------------------------------------------------------- socials */ /** Minimal inline LinkedIn mark (the bundled linkedin.svg is a filled badge, * which masks to a plain square). Everything else masks its SVG. */ function LinkedInGlyph({ className = "size-4" }: { className?: string }) { return ( ); } export function SocialGlyph({ name, icon }: { name: string; icon: string }) { if (name === "LinkedIn") return ; return (