// growth-sections.jsx — secciones premium de la landing "Growth" // Reusa F, Magnetic, Parallax, RevealText, RevealWords, GlitchWord, useScrollProgress, // useInView de shared.jsx / final-hooks.jsx / final-sections.jsx. // Pieza nueva de esta landing: GrowthChart — un gráfico de crecimiento animado con // contadores en vivo, distinto de la terminal de MVP y la red de nodos de Empresas. function useCountUp(target, active, duration = 1600) { const [val, setVal] = React.useState(0); React.useEffect(() => { if (!active) return; let raf, start; const step = (t) => { if (!start) start = t; const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(target * eased); if (p < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [active, target, duration]); return val; } function GrowthChart() { const ref = React.useRef(null); const inView = useInView(ref, { threshold: 0.35 }); const svgRef = React.useRef(null); const [dash, setDash] = React.useState(1000); const pathD = 'M0,150 C40,148 60,140 90,132 C130,122 150,110 190,100 C230,90 250,60 290,52 C320,46 340,20 380,8'; const points = [ { x: 0, y: 150 }, { x: 90, y: 132 }, { x: 190, y: 100 }, { x: 290, y: 52 }, { x: 380, y: 8 } ]; React.useEffect(() => { const p = svgRef.current; if (p) setDash(p.getTotalLength()); }, []); const clientes = useCountUp(312, inView, 1800); const roas = useCountUp(4.2, inView, 1800); const cac = useCountUp(38, inView, 1800); return (
ADQUISICIÓN · ÚLTIMOS 90 DÍAS EN VIVO
{points.map((p, i) => ( ))}
{Math.round(clientes)}
CLIENTES NUEVOS
{roas.toFixed(1)}x
RETORNO SOBRE PAUTA
-{Math.round(cac)}%
COSTO POR CLIENTE
); } // ───────────── HERO ───────────── function GHero({ content }) { const heroRef = React.useRef(null); const progress = useScrollProgress(heroRef); const [mouse, setMouse] = React.useState({ x: 0.5, y: 0.5 }); React.useEffect(() => { if (document.getElementById('growth-hero-styles')) return; const s = document.createElement('style'); s.id = 'growth-hero-styles'; s.textContent = ` @property --grBorderAngle { syntax: ''; inherits: false; initial-value: 0deg; } @keyframes grBorderRotate { to { --grBorderAngle: 360deg; } } @keyframes grBtnShimmer { 0%, 75% { background-position: 200% center; opacity: 0; } 80% { opacity: 1; } 95%, 100% { background-position: -100% center; opacity: 0; } } @keyframes grArrow { 0%, 60%, 100% { transform: translateX(0); } 80% { transform: translateX(5px); } } .gr-cta-wrap { position: relative; display: inline-block; border-radius: 14px; padding: 2px; background: conic-gradient(from var(--grBorderAngle), transparent 0%, #2199ea 12%, #63b7f0 22%, #137dc5 32%, transparent 44%, transparent 100%); animation: grBorderRotate 2.5s linear infinite; transition: transform 0.3s cubic-bezier(.2,.8,.3,1); } .gr-cta-wrap:hover { transform: scale(1.04); } .gr-cta-inner { position: relative; overflow: hidden; padding: 20px 36px; background: linear-gradient(135deg, #2199ea 20%, #137dc5); color: #fff; border: none; border-radius: 12px; font-family: var(--font-heading); font-size: 15px; font-weight: 500; letter-spacing: 0.02em; cursor: pointer; display: flex; align-items: center; gap: 8px; } .gr-cta-inner::after { content: ''; position: absolute; inset: 0; background: linear-gradient(105deg, transparent 30%, rgba(255,255,255,0.55) 50%, transparent 70%); background-size: 200% 100%; animation: grBtnShimmer 9s ease-in-out infinite; pointer-events: none; } .gr-arrow { display: inline-block; animation: grArrow 3s ease-in-out infinite; } `; document.head.appendChild(s); }, []); React.useEffect(() => { const onMove = (e) => { const el = heroRef.current; if (!el) return; const r = el.getBoundingClientRect(); setMouse({ x: (e.clientX - r.left) / r.width, y: (e.clientY - r.top) / r.height }); }; const el = heroRef.current; if (!el) return; el.addEventListener('mousemove', onMove); return () => el.removeEventListener('mousemove', onMove); }, []); const px = (mouse.x - 0.5) * 20; const py = (mouse.y - 0.5) * 20; return (
EUREKANT.GROWTH.MARKETING

{content.hero.sub}

●{content.hero.meta}
); } function SectionEyebrowHead({ eyebrow, title, sub, align = 'left', maxTitle = 900 }) { return (
{eyebrow}

{title}

{sub &&

{sub}

}
); } // ───────────── COSTO DE SER INVISIBLE ───────────── function GCost({ content }) { const ref = React.useRef(null); const inView = useInView(ref, { threshold: 0.2 }); return (
01
{content.cost.items.map((c, i) => (
{c.stat}
{c.label}

{c.desc}

))}
); } // ───────────── DOLORES ───────────── function GPains({ content }) { const ref = React.useRef(null); const inView = useInView(ref, { threshold: 0.15 }); return (
{content.pains.items.map((p, i) => (
e.currentTarget.style.borderColor = F.accent} onMouseLeave={(e) => e.currentTarget.style.borderColor = F.line} style={{ background: F.surface, border: `1px solid ${F.line}`, borderRadius: 24, padding: 40, position: 'relative', overflow: 'hidden', opacity: inView ? 1 : 0, transform: inView ? 'none' : 'translateY(28px)', transitionProperty: 'border-color, opacity, transform', transitionDuration: `.35s, .8s, .8s`, transitionDelay: `0s, ${i * 0.12}s, ${i * 0.12}s`, transitionTimingFunction: 'ease, cubic-bezier(.16,.84,.3,1), cubic-bezier(.16,.84,.3,1)' }}>
{p.n}

{p.title}

{p.desc}

))}
); } // ───────────── QUÉ INCLUYE (sticky scroll) ───────────── function GModules({ content }) { const [active, setActive] = React.useState(0); const ref = React.useRef(null); const progress = useScrollProgress(ref); React.useEffect(() => { if (progress > 0.1 && progress < 0.9) { const idx = Math.min(3, Math.floor(progress * 4)); if (idx !== active) setActive(idx); } }, [progress]); const step = content.modules.steps[active]; return (
02
{content.modules.steps.map((s, i) => (
setActive(i)} onClick={() => setActive(i)} style={{ padding: '32px 0', cursor: 'pointer', borderTop: `1px solid ${active === i ? F.accent : F.line}`, opacity: active === i ? 1 : 0.4, transform: active === i ? 'translateX(8px)' : 'translateX(0)', transition: 'opacity .4s, border-color .4s, transform .4s' }}>
{s.n}

{s.title}

{s.dur}
))}
{step.n}
CANAL {step.n} / 04 · {step.dur.toUpperCase()}

{step.title}

{step.desc}

{content.modules.steps.map((_, i) => (
))}
); } // ───────────── CÓMO TRABAJAMOS ───────────── function GProcess({ content }) { const ref = React.useRef(null); const inView = useInView(ref, { threshold: 0.15 }); return (
03
{content.process.items.map((st, i) => (
0{i + 1}
{st.dur.toUpperCase()}

{st.title}

{st.desc}

))}
); } Object.assign(window, { GrowthChart, GHero, GCost, GPains, GModules, GProcess });