// 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 (
<>