/* eslint-disable react/prop-types */ /** * Stats · Sección 2 del Home. * Brief 1 §3.1 Sección 2. * * - 6 cifras: 3×2 desktop, 2×3 tablet, 1×6 mobile * - Fondo crema * - Cifra ExtraBold 96px desktop, label uppercase 13px navy medio * - Aparición secuencial al entrar al viewport (IntersectionObserver) * - Count-up easing 1.4s, prefijo/sufijo estáticos */ const { useState: uS_St, useEffect: uE_St, useRef: uR_St } = React; const STATS = [ { num: 18, prefix: '', suffix: '', line1: 'AÑOS DE', line2: 'EXPERIENCIA' }, { num: 1900, prefix: '+', suffix: '', line1: 'PROYECTOS', line2: 'ORIGINADOS', comma: true }, { num: 40, prefix: '+', suffix: '', line1: 'DISTRITOS', line2: 'CO-CREADOS' }, { num: 20, prefix: '+', suffix: 'M', line1: 'METROS CUADRADOS', line2: 'DESARROLLADOS' }, { num: 3, prefix: '+', suffix: 'B USD', line1: 'DE VALOR INMOBILIARIO', line2: 'ORIGINADO' }, { num: 57, prefix: '', suffix: '', line1: 'CIUDADES EN', line2: '3 REGIONES' }, ]; function useCountUp(target, run, duration = 1400) { const [value, setValue] = uS_St(0); uE_St(() => { if (!run) return; const start = performance.now(); let raf; const ease = (t) => 1 - Math.pow(1 - t, 3); const tick = (now) => { const t = Math.min(1, (now - start) / duration); setValue(target * ease(t)); if (t < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [run, target, duration]); return value; } function StatCard({ stat, run, delay }) { const v = useCountUp(stat.num, run); const formatted = stat.comma ? Math.round(v).toLocaleString('en-US') : Math.round(v).toString(); return (
{stat.prefix && {stat.prefix}} {formatted} {stat.suffix && {stat.suffix}}
{stat.line1}
{stat.line2}
); } function Stats() { const ref = uR_St(null); const [run, setRun] = uS_St(false); uE_St(() => { if (!ref.current) return; const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setRun(true); obs.disconnect(); } }, { threshold: 0.2 }); obs.observe(ref.current); return () => obs.disconnect(); }, []); return (
LOS NÚMEROS DETRÁS DE 18 AÑOS
{STATS.map((s, i) => ( ))}
); } const statStyles = { section: { background: 'var(--color-cream)', padding: 'var(--space-3xl) 0', }, eyebrowRow: { display: 'flex', alignItems: 'center', gap: 20, marginBottom: 'clamp(40px, 5vw, 64px)', }, eyebrow: { fontWeight: 700, fontSize: 'clamp(12px, 0.9vw, 14px)', letterSpacing: '0.22em', textTransform: 'uppercase', color: 'var(--color-copper-fallback)', whiteSpace: 'nowrap', }, eyebrowRule: { flex: 1, height: 1, background: 'rgba(168, 107, 45, 0.35)', maxWidth: 320, }, grid: { display: 'grid', gridTemplateColumns: 'repeat(1, 1fr)', gap: 'clamp(40px, 5vw, 64px)', }, card: { display: 'flex', flexDirection: 'column', gap: 18, paddingBottom: 28, borderBottom: '1px solid rgba(33, 52, 64, 0.12)', }, figure: { fontWeight: 800, color: 'var(--color-navy-deep)', fontSize: 'clamp(48px, 11vw, 128px)', lineHeight: 0.95, letterSpacing: '-0.02em', display: 'flex', alignItems: 'baseline', gap: 4, }, affix: { fontSize: '0.55em', fontWeight: 800, color: 'var(--color-copper-fallback)', }, label: { fontWeight: 700, fontSize: 'clamp(13px, 1vw, 15px)', letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--color-navy-medium)', lineHeight: 1.4, }, }; const statMQ = document.createElement('style'); statMQ.textContent = ` @media (min-width: 900px) { section[data-screen-label="02 Stats"] > div > div { grid-template-columns: repeat(2, 1fr) !important; gap: 32px !important; } } @media (min-width: 1024px) { section[data-screen-label="02 Stats"] > div > div { grid-template-columns: repeat(3, 1fr) !important; gap: 48px !important; } } `; document.head.appendChild(statMQ); window.Stats = Stats;