// ============================================================ // Tribeca Lisboa — ABOUT app (responsive, bilingual) // Uses shared SiteHeader / SiteMenuDrawer + global lang. // ============================================================ const { useEffect: useEffectAb, useRef: useRefAb } = React; function useAbReveal(rootRef) { useEffectAb(() => { const root = rootRef.current; if (!root) return; const els = root.querySelectorAll('.ab-rev'); if (window.PREFERS_REDUCED) { els.forEach(e => e.classList.add('in')); return; } const vh = () => window.innerHeight || document.documentElement.clientHeight; const check = () => els.forEach(el => { if (!el.classList.contains('in')) { const r = el.getBoundingClientRect(); if (r.top < vh() - 40 && r.bottom > 0) el.classList.add('in'); } }); let frames = 0, raf, alive = true; const burst = () => { if (!alive) return; check(); if (frames++ < 16) raf = requestAnimationFrame(burst); }; const onScroll = () => check(); window.addEventListener('scroll', onScroll, { passive: true }); window.addEventListener('resize', onScroll); burst(); const t1 = setTimeout(check, 300), t2 = setTimeout(check, 800); return () => { alive = false; cancelAnimationFrame(raf); clearTimeout(t1); clearTimeout(t2); window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); }; }, []); } function AboutInner() { const rootRef = useRefAb(null); const [menu, setMenu] = React.useState(false); useAbReveal(rootRef); return (
setMenu(true)} /> setMenu(false)} />
); } function About() { const [lang] = useLang(); return ( ); } ReactDOM.createRoot(document.getElementById('root')).render();