// desidium-helpers.jsx — shared primitives, exported to window const { useState, useEffect, useRef } = React; // pick a localized string: L({en,es}, lang) function L(obj, lang) { return obj ? (obj[lang] ?? obj.en) : ""; } // Scroll-reveal wrapper. Adds .is-in when scrolled into view. // `motion=false` -> always visible (reduced motion / tweak off). function Reveal({ children, motion = true, delay = 0, y = 26, as = "div", className = "", style = {} }) { const ref = useRef(null); const [seen, setSeen] = useState(false); useEffect(() => { if (!motion) { setSeen(true); return; } const el = ref.current; if (!el) return; const io = new IntersectionObserver((es) => { es.forEach((e) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } }); }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }); io.observe(el); return () => io.disconnect(); }, [motion]); const Tag = as; const baseT = motion ? `opacity .9s cubic-bezier(.22,.61,.36,1) ${delay}ms, transform 1.05s cubic-bezier(.22,.61,.36,1) ${delay}ms` : "none"; return ( {children} ); } // Cinematic film placeholder (the hero video stand-in). function Film({ label = "Hero film · placeholder", showPlay = true, showScrub = true, time = "00:00 / 01:20", className = "", style = {} }) { return (
{showPlay && }
{label}
{showScrub &&
{time}
}
); } // Portrait placeholder for roster / divisions. Striped, with mono caption. function Portrait({ caption = "Portrait", tall = false, className = "", style = {} }) { return (
{caption}
); } Object.assign(window, { L, Reveal, Film, Portrait });