// portal-app.jsx — assembles the portal + toast + scroll reveal function App() { const [toastMsg, setToastMsg] = React.useState(""); const toastTimer = React.useRef(null); const toast = React.useCallback((msg) => { setToastMsg(msg); clearTimeout(toastTimer.current); toastTimer.current = setTimeout(() => setToastMsg(""), 2600); }, []); // scroll reveal React.useEffect(() => { const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add("on"); io.unobserve(e.target); } }); }, { threshold: 0.12 }); const run = () => document.querySelectorAll(".reveal:not(.on)").forEach(el => io.observe(el)); run(); const mo = new MutationObserver(run); mo.observe(document.body, { childList: true, subtree: true }); return () => { io.disconnect(); mo.disconnect(); }; }, []); return ( <>