/* helpers.jsx — shared primitives: Placeholder, line Icons, useReveal */ const { useState, useEffect, useRef, useCallback } = React; /* Reveal-on-scroll: adds .in when element enters viewport. Uses a scroll/resize check (robust across iframes & offscreen previews). */ function useReveal() { useEffect(() => { document.documentElement.classList.add("js-reveal"); let raf = 0; const check = () => { raf = 0; const vh = window.innerHeight || document.documentElement.clientHeight; document.querySelectorAll(".reveal:not(.in)").forEach((el) => { const r = el.getBoundingClientRect(); // reveal once the element's top crosses ~88% of the viewport if (r.top < vh * 0.88 && r.bottom > 0) el.classList.add("in"); }); }; const onScroll = () => { if (!raf) raf = requestAnimationFrame(check); }; // initial passes (let layout + fonts settle) check(); requestAnimationFrame(check); const t1 = setTimeout(check, 250); window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", onScroll, { passive: true }); return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); clearTimeout(t1); if (raf) cancelAnimationFrame(raf); }; }, []); } /* Marked image placeholder */ function Placeholder({ label, sub, dark, style, className = "" }) { return (