// Hooks compartidos por todos los componentes del sitio const { useState, useEffect, useRef } = React; function useScrolled(threshold = 80) { const [scrolled, setScrolled] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > threshold); window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); return () => window.removeEventListener('scroll', onScroll); }, [threshold]); return scrolled; } function useReveal() { const ref = useRef(null); useEffect(() => { if (!ref.current) return; const io = new IntersectionObserver( (entries) => entries.forEach(e => e.isIntersecting && e.target.classList.add('in')), { threshold: 0.15 } ); io.observe(ref.current); return () => io.disconnect(); }, []); return ref; } function useCounter(target, duration = 1600, trigger = true) { const [value, setValue] = useState(0); useEffect(() => { if (!trigger) return; let raf; const start = performance.now(); const tick = (now) => { const t = Math.min(1, (now - start) / duration); const eased = 1 - Math.pow(1 - t, 3); setValue(Math.floor(eased * target)); if (t < 1) raf = requestAnimationFrame(tick); else setValue(target); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [target, trigger, duration]); return value; } function formatNumber(n) { return n.toLocaleString('es-CO'); } Object.assign(window, { useScrolled, useReveal, useCounter, formatNumber });