/* =========================================================== Halvorsen — Desktop App shell (composes all sections) =========================================================== */ const App = () => { const [theme, setTheme] = React.useState("light"); const [bookOpen, setBookOpen] = React.useState(false); React.useEffect(() => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); // Reveal-on-scroll React.useEffect(() => { document.documentElement.classList.add("js-anim"); const els = document.querySelectorAll(".reveal, .mask-line"); const io = new IntersectionObserver( (entries) => { entries.forEach((e) => { if (e.isIntersecting) e.target.classList.add("in"); }); }, { threshold: 0.05, rootMargin: "0px 0px -4% 0px" } ); els.forEach((el) => io.observe(el)); // Force trigger items already in view on initial mount requestAnimationFrame(() => { els.forEach((el) => { const r = el.getBoundingClientRect(); if (r.top < window.innerHeight && r.bottom > 0) el.classList.add("in"); }); }); return () => io.disconnect(); }, []); return (