/* 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;