/* ============================================================ CLOSING CAPITAL — Componentes compartidos Nav, Footer, Logo, hooks de animación, partículas ============================================================ */ const { useEffect, useRef, useState, createContext, useContext } = React; // ============================================================ // LOGO SVG (recreación inspirada en el logo aportado) // ============================================================ function LogoMark({ size = 42, className = '' }) { return ( {/* C exterior dorada */} {/* C interior plateada (espejo) */} {/* Barras crecientes */} {/* Flecha */} ); } // ============================================================ // NAVIGATION // ============================================================ function Nav({ active }) { const [scrolled, setScrolled] = useState(false); const [lang, setLang] = useState('ES'); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 30); window.addEventListener('scroll', onScroll); return () => window.removeEventListener('scroll', onScroll); }, []); const links = [ { id: 'inicio', label: 'Inicio', href: 'index.html' }, { id: 'agencia', label: 'Agencia Closing', href: 'agencia-closing.html' }, { id: 'academia', label: 'Academia Closing', href: 'academia-closing.html' }, { id: 'agents', label: 'Closing Agents', href: 'closing-agents.html' }, ]; return ( ); } // ============================================================ // FOOTER // ============================================================ function Footer() { return ( ); } // ============================================================ // HOOK: scroll reveal // ============================================================ function useReveal() { useEffect(() => { const els = document.querySelectorAll('.reveal'); const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -80px 0px' }); els.forEach(el => io.observe(el)); return () => io.disconnect(); }, []); } // ============================================================ // PARTICULAS DORADAS // ============================================================ function GoldParticles({ count = 25 }) { const particles = Array.from({ length: count }, (_, i) => { const left = Math.random() * 100; const dur = 8 + Math.random() * 14; const delay = Math.random() * 12; const size = 1 + Math.random() * 2.5; return ( ); }); return ; } // ============================================================ // COUNTER ANIMADO // ============================================================ function Counter({ to, duration = 2200, suffix = '', prefix = '', decimals = 0 }) { const [n, setN] = useState(0); const ref = useRef(null); const started = useRef(false); useEffect(() => { const io = new IntersectionObserver((entries) => { if (entries[0].isIntersecting && !started.current) { started.current = true; const start = performance.now(); const tick = (now) => { const t = Math.min(1, (now - start) / duration); // easeOutCubic const eased = 1 - Math.pow(1 - t, 3); setN(to * eased); if (t < 1) requestAnimationFrame(tick); else setN(to); }; requestAnimationFrame(tick); } }, { threshold: 0.4 }); if (ref.current) io.observe(ref.current); return () => io.disconnect(); }, [to, duration]); return {prefix}{n.toFixed(decimals)}{suffix}; } // ============================================================ // EXPORT GLOBAL // ============================================================ Object.assign(window, { LogoMark, Nav, Footer, useReveal, GoldParticles, Counter, });