"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.06 } ); 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}
); } /* ------------------------------------------------------------- hardware */ export function Led({ kind = "on", pulse = false, slow = false, className = "", style, }: { kind?: "on" | "amber" | "off"; pulse?: boolean; slow?: boolean; className?: string; style?: CSSProperties; }) { return (