// app.jsx — main app + Tweaks wiring + on-scroll animations const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "gradient", "type": "jakarta", "hero": "asymmetric" }/*EDITMODE-END*/; function useOnScrollReveal() { React.useEffect(() => { const reveal = (el) => el.classList.add("in"); const isAboveOrInView = (el) => { const r = el.getBoundingClientRect(); // Element is in viewport OR has already been scrolled past return r.top < window.innerHeight - 40; }; const io = new IntersectionObserver( (entries) => { entries.forEach((e) => { if (e.isIntersecting) { reveal(e.target); io.unobserve(e.target); } }); }, { rootMargin: "0px 0px -40px 0px", threshold: 0.01 } ); const init = () => { document.querySelectorAll(".k-anim:not(.in)").forEach((el) => { if (isAboveOrInView(el)) { reveal(el); } else { io.observe(el); } }); }; init(); // Safety: any unrevealed element after 2.5s gets revealed const safety = setTimeout(() => { document.querySelectorAll(".k-anim:not(.in)").forEach((el) => { const r = el.getBoundingClientRect(); if (r.top < window.innerHeight) reveal(el); }); }, 2500); return () => { io.disconnect(); clearTimeout(safety); }; }, []); } function applyTweaks(t) { const html = document.documentElement; html.setAttribute("data-accent", t.accent); html.setAttribute("data-type", t.type); html.setAttribute("data-hero", t.hero); } function KApp() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); React.useLayoutEffect(() => { applyTweaks(t); }, [t]); useOnScrollReveal(); return (