/* Torre Auro — shared components */ const { useState, useEffect, useRef, useMemo, useCallback, Fragment } = React; function Icon({ n, ...rest }) { return ; } function useScrolled(threshold = 80) { const [scrolled, setScrolled] = useState(false); useEffect(() => { const onS = () => setScrolled(window.scrollY > threshold); onS(); window.addEventListener('scroll', onS, { passive:true }); return () => window.removeEventListener('scroll', onS); }, [threshold]); return scrolled; } function CountUp({ to, duration = 1400 }) { const [val, setVal] = useState(to); useEffect(() => { let raf, start, cancelled = false; setVal(0); const step = (t) => { if (cancelled) return; if (!start) start = t; const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(Math.round(to * eased)); if (p < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); // Fallback: if rAF doesn't advance (throttled iframe), force-set final value const fb = setTimeout(() => { if (!cancelled) setVal(to); }, duration + 200); return () => { cancelled = true; cancelAnimationFrame(raf); clearTimeout(fb); }; }, [to, duration]); return {val}; } /* routing */ function navigate(route) { if (window.location.pathname === route) return; window.history.pushState({}, '', route); window.dispatchEvent(new PopStateEvent('popstate')); window.scrollTo({ top: 0, behavior: 'instant' }); } function useRoute() { const [route, setRoute] = useState(window.location.pathname || '/'); useEffect(() => { const onPop = () => setRoute(window.location.pathname || '/'); window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); }, []); return route; } function openChat(context) { window.dispatchEvent(new CustomEvent('ta:open-chat', { detail: { context } })); } /* number to roman or padded number */ function pad2(n) { return String(n).padStart(2, '0'); } /* ────────────────── NAV ────────────────── */ function Nav({ route }) { const scrolled = useScrolled(60); const isHome = route === '/'; const navCls = `nav ${scrolled || !isHome ? 'is-solid' : ''}`; const activeId = (() => { if (route === '/') return 'home'; if (route.startsWith('/eventos')) return 'eventos'; if (route.startsWith('/perspectiva')) return 'perspectiva'; if (route === '/contacto') return 'contacto'; if (route === '/planta-baja' || route.startsWith('/nivel-')) return 'ecosistemas'; return ''; })(); return ( navigate('/')} aria-label="Torre Auro · Inicio"> {NAV_LINKS.map(l => ( l.mega ? ( {l.label} Cuatro ecosistemas · siete niveles {ECOSYSTEMS.map(e => ( navigate('/' + e.primaryFloor)}>{e.name} {e.descriptor} {e.floors.map(fid => { const f = FLOOR_BY_ID[fid]; if (!f) return null; return ( navigate(f.route)}> {f.sh} {f.short.split('·')[0].trim().split('—')[0].trim()} {f.av}/{f.tot} ); })} ))} ) : ( navigate(l.route)} >{l.label} ) ))} openChat('general')}> Hablar con un asesor ); } /* ────────────────── FOOTER ────────────────── */ function Footer() { return ( ); } /* ────────────────── CTA BANNER (editorial) ────────────────── */ function CtaBanner({ no, eyebrow, title, sub, primary, secondary, onPrimary, onSecondary }) { return ( {no || '07'} {eyebrow} {sub} {primary} {secondary && ( {secondary} )} ); } Object.assign(window, { Icon, useScrolled, CountUp, pad2, navigate, useRoute, openChat, Nav, Footer, CtaBanner, });
{sub}