// final-sections.jsx — secciones de la landing final con animaciones intensificadas // Paleta de marca: se lee desde brand.css vía window.BRAND (ver brand.js). // Editá los colores en brand.css, no acá. const F = Object.assign({ bg: '#060813', bgAlt: '#0a0e1c', surface: '#0f1424', surfaceHi: '#141a30', line: 'rgba(120,140,210,0.10)', lineStrong: 'rgba(120,140,210,0.22)', text: '#e7ecff', textDim: 'rgba(231,236,255,0.62)', textMute: 'rgba(231,236,255,0.40)', accent: '#2199ea', accent2: '#63b7f0', accent3: '#59a4d6', accentGlow: 'rgba(33,153,234,0.45)', ok: '#5cf0b8', warn: '#ffb86b' }, window.BRAND || {}); // Rutas relativas para páginas en subcarpetas (/legal/, /blog/) const EK_SUB = typeof location !== 'undefined' ? (/\/(legal|blog)\//.exec(decodeURIComponent(location.pathname)) || [])[1] : undefined; const ekRel = (p) => (EK_SUB && !/^(https?:|mailto:|#)/.test(p)) ? '../' + p : p; // ───────────── NAV ───────────── function FNav({ content, lang, setLang, scrollY }) { const compact = scrollY > 80; const [open, setOpen] = React.useState(false); return ( ); } // ───────────── LANDING NAV (soluciones: MVP/Empresas/Growth/Partnership) ───────────── function LandingNav({ lang, setLang, scrollY, hideLang }) { const compact = scrollY > 80; return ( ); } // ───────────── HERO NUMBER (#1) ───────────── function HeroNumber({ accent }) { const containerRef = React.useRef(null); // Inject @property + keyframes once React.useEffect(() => { if (document.getElementById('hero-num-styles')) return; const s = document.createElement('style'); s.id = 'hero-num-styles'; s.textContent = ` @property --heroAngle { syntax: ''; inherits: false; initial-value: 0deg; } @keyframes heroGradRotate { to { --heroAngle: 360deg; } } @keyframes heroShimmer { 0%, 10% { background-position: 200% center; opacity: 0; } 15% { opacity: 1; } 55% { background-position: -100% center; opacity: 1; } 65%, 100% { background-position: -100% center; opacity: 0; } } .hero-num-main { position: relative; } .hero-num-main::after { content: '#1'; position: absolute; inset: 0; color: transparent; -webkit-text-stroke: 0px transparent; background: linear-gradient(90deg, transparent 25%, rgba(255,255,255,0.28) 50%, transparent 75%) -100% center / 200% 100% no-repeat; -webkit-background-clip: text; background-clip: text; animation: heroShimmer 7s ease-in-out infinite 1.5s backwards; pointer-events: none; } @keyframes heroFlare { 0% { transform: scale(1); opacity: 1; box-shadow: 0 0 0 0 rgba(255,255,255,0.9), 0 0 12px 4px rgba(33,153,234,0.7); } 40% { transform: scale(2.4); opacity: 0.9; box-shadow: 0 0 0 6px rgba(255,255,255,0.0), 0 0 32px 12px rgba(33,153,234,0.4); } 100% { transform: scale(0.6); opacity: 0; box-shadow: none; } } .hero-num-main { font-family: var(--font-mono); font-size: 280px; font-weight: 200; line-height: 0.9; letter-spacing: -0.04em; color: transparent; -webkit-text-stroke: 1px rgba(120,140,210,0.18); background: conic-gradient(from var(--heroAngle), transparent 0%, rgba(33,153,234,0.22) 18%, rgba(140,200,255,0.14) 36%, transparent 55%, rgba(33,153,234,0.08) 78%, transparent 100%); -webkit-background-clip: text; background-clip: text; animation: heroGradRotate 22s linear infinite; position: relative; display: block; user-select: none; } `; document.head.appendChild(s); }, []); // Star flare — fires after 2s then every 10s React.useEffect(() => { const fire = () => { if (!containerRef.current) return; const f = document.createElement('div'); f.style.cssText = `position:absolute;top:48px;right:52px;width:5px;height:5px;border-radius:50%;background:white;pointer-events:none;z-index:10;animation:heroFlare 900ms cubic-bezier(.2,.8,.3,1) forwards;`; containerRef.current.appendChild(f); setTimeout(() => f.remove(), 950); }; const t = setTimeout(() => {fire();}, 2000); const iv = setInterval(fire, 10000); return () => {clearTimeout(t);clearInterval(iv);}; }, []); const echoLayers = [ { blur: 10, opacity: 0.07, dx: 8, dy: 5, stroke: 'rgba(33,153,234,0.5)' }, { blur: 5, opacity: 0.11, dx: 4, dy: 3, stroke: 'rgba(120,180,255,0.35)' }, { blur: 2, opacity: 0.16, dx: 1.5, dy: 1, stroke: 'rgba(180,210,255,0.22)' }]; return (
{/* Depth echo layers */} {echoLayers.map((e, i) =>
#1
)} {/* Main layer — rotating gradient fill */}
#1
{/* Shimmer via ::after — perfectly aligned */}
); } // ───────────── HERO ───────────── function FHero({ content }) { const tweaks = React.useContext(window.TweaksCtx) || {}; const heroRef = React.useRef(null); const canvasRef = React.useRef(null); const progress = useScrollProgress(heroRef); const [mouse, setMouse] = React.useState({ x: 0.5, y: 0.5 }); // Particle network canvas React.useEffect(() => { const c = canvasRef.current; if (!c) return; const ctx = c.getContext('2d'); let w, h, raf; const dpr = Math.min(2, window.devicePixelRatio || 1); const resize = () => { const r = c.getBoundingClientRect(); w = r.width;h = r.height; c.width = w * dpr;c.height = h * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); }; resize(); const ro = new ResizeObserver(resize); ro.observe(c); const N = 48; const pts = Array.from({ length: N }, () => ({ x: Math.random() * w, y: Math.random() * h, vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3, r: 0.8 + Math.random() * 1.6 })); const loop = () => { ctx.clearRect(0, 0, w, h); // grid layer ctx.strokeStyle = 'rgba(120,140,210,0.06)'; ctx.lineWidth = 1; for (let x = 0; x < w; x += 60) {ctx.beginPath();ctx.moveTo(x, 0);ctx.lineTo(x, h);ctx.stroke();} for (let y = 0; y < h; y += 60) {ctx.beginPath();ctx.moveTo(0, y);ctx.lineTo(w, y);ctx.stroke();} for (const p of pts) { p.x += p.vx;p.y += p.vy; if (p.x < 0 || p.x > w) p.vx *= -1; if (p.y < 0 || p.y > h) p.vy *= -1; } for (let i = 0; i < pts.length; i++) { for (let j = i + 1; j < pts.length; j++) { const dx = pts[i].x - pts[j].x,dy = pts[i].y - pts[j].y; const d = Math.hypot(dx, dy); if (d < 170) { ctx.strokeStyle = `rgba(33,153,234,${(1 - d / 170) * 0.4})`; ctx.lineWidth = 0.7; ctx.beginPath();ctx.moveTo(pts[i].x, pts[i].y);ctx.lineTo(pts[j].x, pts[j].y);ctx.stroke(); } } } for (const p of pts) { ctx.fillStyle = F.accent; ctx.shadowColor = F.accent;ctx.shadowBlur = 14; ctx.beginPath();ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);ctx.fill(); } ctx.shadowBlur = 0; raf = requestAnimationFrame(loop); }; loop(); return () => {cancelAnimationFrame(raf);ro.disconnect();}; }, []); // Inject hero button styles once React.useEffect(() => { if (document.getElementById('hero-btn-styles')) return; const s = document.createElement('style'); s.id = 'hero-btn-styles'; s.textContent = ` @property --heroBorderAngle { syntax: ''; inherits: false; initial-value: 0deg; } @keyframes heroBorderRotate { to { --heroBorderAngle: 360deg; } } @keyframes heroBtnShimmer { 0%, 75% { background-position: 200% center; opacity: 0; } 80% { opacity: 1; } 95%, 100%{ background-position: -100% center; opacity: 0; } } @keyframes heroBtnArrow { 0%, 60%, 100% { transform: translateX(0); } 80% { transform: translateX(5px); } } .hero-cta-wrap { position: relative; display: inline-block; border-radius: 14px; padding: 2px; background: conic-gradient(from var(--heroBorderAngle), transparent 0%, #2199ea 12%, #63b7f0 22%, #137dc5 32%, transparent 44%, transparent 100%); animation: heroBorderRotate 2.5s linear infinite; transition: transform 0.3s cubic-bezier(.2,.8,.3,1); } .hero-cta-wrap:hover { transform: scale(1.04); } .hero-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; } .hero-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: heroBtnShimmer 9s ease-in-out infinite; pointer-events: none; } .hero-cta-arrow { display: inline-block; animation: heroBtnArrow 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) * 24; const py = (mouse.y - 0.5) * 24; return (
{/* Floating decorative grid layers — parallax depth */}
{/* Big floating #1 (parallax) */}
EUREKANT.SOFTWARE.FACTORY.MMXXII

{content.hero.sub}

{/* Terminal-like bar with typewriter */} {tweaks.showTerminal !== false && }
{/* Bottom scroll indicator */}
SCROLL
); } function FTerminalBar({ text }) { const [show, setShow] = React.useState(false); React.useEffect(() => {const t = setTimeout(() => setShow(true), 2400);return () => clearTimeout(t);}, []); const typed = useTypewriter(text, 22, show); return (
$ {typed}
); } // ───────────── MARQUEE ───────────── function FMarquee({ items, dir = 1 }) { const doubled = [...items, ...items, ...items, ...items]; return (
0 ? 32 : 32}s linear infinite ${dir < 0 ? 'reverse' : ''}`, gap: 56 }}> {doubled.map((it, i) => ✦ {it} )}
); } // ───────────── STATS ───────────── function FStats({ stats }) { const ref = React.useRef(null); const inView = useInView(ref); return (
{stats.map((s, i) => { const n = useCounter(s.n, inView, 1800 + i * 250); return (
0{i + 1} / 0{stats.length}
{n.toLocaleString()}{s.suf}
{s.l}
); })}
); } // ───────────── SOLUCIONES ───────────── function FSoluciones({ content }) { const ref = React.useRef(null); const progress = useScrollProgress(ref); return (
02
// SOLUCIONES — DE LA IDEA AL MERCADO

{content.soluciones.title}

{content.soluciones.sub}

{content.soluciones.items.map((it, i) =>
)}
); } function FSolutionCard({ item, index, featuredLabel }) { const cardRef = React.useRef(null); const [tilt, setTilt] = React.useState({ x: 0, y: 0 }); const [hover, setHover] = React.useState(false); React.useEffect(() => { if (document.getElementById('sol-featured-styles')) return; const s = document.createElement('style'); s.id = 'sol-featured-styles'; s.textContent = ` @property --solAngle { syntax: ''; inherits: false; initial-value: 0deg; } @keyframes solBorderRotate { to { --solAngle: 360deg; } } .sol-featured-wrap { position: relative; border-radius: 27px; padding: 2px; height: 100%; background: conic-gradient(from var(--solAngle), transparent 0%, #2199ea 12%, #63b7f0 22%, #137dc5 32%, transparent 44%, transparent 100%), rgba(120,140,210,0.22); animation: solBorderRotate 6s linear infinite; box-shadow: 0 0 46px rgba(33,153,234,0.28); } .sol-featured-badge { position: absolute; top: -13px; left: 26px; z-index: 4; padding: 5px 14px; border-radius: 999px; background: linear-gradient(135deg, #2199ea 20%, #137dc5); color: #fff; font-family: var(--font-body); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; box-shadow: 0 6px 18px rgba(33,153,234,0.4); } @media (prefers-reduced-motion: reduce) { .sol-featured-wrap { animation: none; } } `; document.head.appendChild(s); }, []); const onMove = (e) => { const r = cardRef.current.getBoundingClientRect(); const x = ((e.clientX - r.left) / r.width - 0.5) * 12; const y = ((e.clientY - r.top) / r.height - 0.5) * -12; setTilt({ x, y }); }; const lift = `perspective(1000px) rotateY(${tilt.x}deg) rotateX(${tilt.y}deg) translateY(${hover ? -8 : 0}px)`; const inner =
setHover(true)} onMouseLeave={() => {setHover(false);setTilt({ x: 0, y: 0 });}} onMouseMove={onMove} style={{ background: item.featured ? F.surfaceHi : F.surface, border: `1px solid ${item.featured ? 'transparent' : hover ? F.accent : F.line}`, borderRadius: item.featured ? 25 : 24, height: item.featured ? 'auto' : '100%', minHeight: item.featured ? 320 : undefined, padding: item.featured ? '48px 56px' : 40, position: 'relative', overflow: 'hidden', transform: item.featured ? 'none' : lift, transition: 'transform .4s cubic-bezier(.2,.7,.2,1), border-color .3s, background .3s', cursor: 'pointer' }}>
{item.n} {item.tag}
{item.featured ?

{item.title}

{item.desc}

{item.cta} →
    {item.bullets.map((b, j) =>
  • ▸ {b}
  • )}
:

{item.title}

{item.desc}

    {item.bullets.map((b, j) =>
  • ▸{b}
  • )}
{item.cta} →
}
; const linkProps = item.href ? { href: item.href, style: { textDecoration: 'none', display: 'block', height: '100%' } } : null; if (item.featured) { const wrap =
{featuredLabel}
{inner}
; return linkProps ? {wrap} : wrap; } return linkProps ? {inner} : inner; } // ───────────── PROCESO ───────────── function FProceso({ content }) { const [active, setActive] = React.useState(0); const ref = React.useRef(null); const progress = useScrollProgress(ref); // Auto-advance based on scroll progress React.useEffect(() => { if (progress > 0.1 && progress < 0.9) { const idx = Math.min(3, Math.floor(progress * 4)); if (idx !== active) setActive(idx); } }, [progress]); return (
03
// PROCESO — METODOLOGÍA PROBADA

{content.proceso.title}

{content.proceso.sub}

{content.proceso.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}
)}
{/* scan line */}
{content.proceso.steps[active].n}
STEP {content.proceso.steps[active].n} / 04 · {content.proceso.steps[active].dur}

{content.proceso.steps[active].title}

{content.proceso.steps[active].desc}

{content.proceso.steps.map((_, i) =>
)}
); } // ───────────── CASO TASTY ───────────── function FCaso({ content }) { const { caso } = content; const ref = React.useRef(null); const inView = useInView(ref, { threshold: 0.2 }); return (
04
// {caso.eyebrow}

{caso.title}

{caso.desc}

{caso.tags.map((t, i) => {t} )}
{caso.metrics.map((m, i) =>
{m.k}
{m.v}
)}
● Semifinalista
); } // ───────────── TESTIMONIOS ───────────── function FTestimonios({ content }) { const [i, setI] = React.useState(0); const t = content.testimonios; React.useEffect(() => { const iv = setInterval(() => setI((x) => (x + 1) % t.length), 7000); return () => clearInterval(iv); }, [t.length]); return (
// CLIENTES — VOCES REALES
{t.map((tm, j) =>
“{tm.quote}”
{tm.author} · {tm.role}
)}
{t.map((_, j) =>
); } // ───────────── EQUIPO ───────────── function FEquipo({ content }) { return (
05
{content.equipo.franco.name
// {content.equipo.eyebrow}

{content.equipo.title}

{content.equipo.franco.name}
{content.equipo.franco.role}

{content.equipo.franco.bio}

); } // ───────────── BLOG ───────────── function FBlog({ content }) { const [filter, setFilter] = React.useState('Todos'); const tags = ['Todos', ...Array.from(new Set(content.blog.posts.map((p) => p.tag)))]; const filtered = filter === 'Todos' ? content.blog.posts : content.blog.posts.filter((p) => p.tag === filter); return (
// BLOG — INSIGHTS TÉCNICOS

{content.blog.title}

{tags.map((t) => )}
); } // ───────────── FAQ ───────────── function FFaq({ content }) { const [open, setOpen] = React.useState(0); return (
// FAQ

Preguntas frecuentes

{content.faq.map((f, i) =>
setOpen(open === i ? -1 : i)} style={{ borderTop: `1px solid ${F.line}`, padding: '32px 0', cursor: 'pointer' }}>

{f.q}

+

{f.a}

)}
); } // ───────────── CTA FINAL ───────────── function FCtaFinal({ content }) { const ref = React.useRef(null); const canvasRef = React.useRef(null); const inView = useInView(ref, { threshold: 0.2 }); const progress = useScrollProgress(ref); // Noise canvas background React.useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); let frame = 0; let raf; const W = canvas.width = canvas.offsetWidth; const H = canvas.height = canvas.offsetHeight; function noise(x, y, t) { return Math.sin(x * 0.015 + t * 0.4) * Math.cos(y * 0.012 + t * 0.3) * 0.5 + 0.5; } function draw() { frame += 0.008; ctx.clearRect(0, 0, W, H); const step = 28; for (let x = 0; x < W; x += step) { for (let y = 0; y < H; y += step) { const n = noise(x, y, frame); const alpha = n * 0.055; ctx.fillStyle = `rgba(33,153,234,${alpha})`; ctx.fillRect(x, y, step - 1, step - 1); } } raf = requestAnimationFrame(draw); } draw(); return () => cancelAnimationFrame(raf); }, []); // Counter animation (solo si la landing define content.cta.counter) — slower, starts after bubble const counterCfg = content.cta.counter; const [count, setCount] = React.useState(0); React.useEffect(() => { if (!inView || !counterCfg) return; const dur = 3200; const target = counterCfg.target; const delay = 500; let raf; const t = setTimeout(() => { const start = Date.now(); const tick = () => { const elapsed = Date.now() - start; const p = Math.min(elapsed / dur, 1); const ease = 1 - Math.pow(1 - p, 4); setCount(Math.round(ease * target)); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); }, delay); return () => {clearTimeout(t);cancelAnimationFrame(raf);}; }, [inView, counterCfg]); // Inject CTA keyframes once React.useEffect(() => { if (document.getElementById('cta-final-styles')) return; const s = document.createElement('style'); s.id = 'cta-final-styles'; s.textContent = ` @keyframes ctaAuroraA { 0% { transform: translate(0px, 0px) scale(1); } 33% { transform: translate(60px, -40px) scale(1.15); } 66% { transform: translate(-30px, 50px) scale(0.92); } 100% { transform: translate(0px, 0px) scale(1); } } @keyframes ctaAuroraB { 0% { transform: translate(0px, 0px) scale(1); } 40% { transform: translate(-70px, 30px) scale(1.1); } 70% { transform: translate(40px, -60px) scale(0.95); } 100% { transform: translate(0px, 0px) scale(1); } } @keyframes ctaAuroraC { 0% { transform: translate(0px, 0px) scale(1.05); } 50% { transform: translate(50px, 50px) scale(0.9); } 100% { transform: translate(0px, 0px) scale(1.05); } } @keyframes ctaDotWaveOrbit { 0% { transform: translate(0px, 0px) scale(1); opacity: 0.45; } 8% { transform: translate(2px, -8px) scale(1.18); opacity: 1; } 16% { transform: translate(0px, 0px) scale(1); opacity: 0.45; } 24% { transform: translate(-2px, 6px) scale(0.86); opacity: 0.75; } 32% { transform: translate(0px, 0px) scale(1); opacity: 0.45; } /* pause 32%→100% */ 100% { transform: translate(0px, 0px) scale(1); opacity: 0.45; } } @keyframes ctaBubbleIn { from { opacity: 0; transform: translateY(10px) scale(0.92); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes ctaBubbleOut { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(-8px) scale(0.94); } } @keyframes ctaPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(33,153,234,0.7), 0 0 24px 4px rgba(33,153,234,0.35); } 50% { box-shadow: 0 0 0 10px rgba(33,153,234,0), 0 0 40px 12px rgba(33,153,234,0.18); } } @keyframes ctaGridLine { from { opacity: 0; } to { opacity: 1; } } `; document.head.appendChild(s); }, []); const parallaxTitle = { transform: `translateY(${progress * -30}px)` }; const parallaxSub = { transform: `translateY(${progress * -18}px)` }; return (
{/* Layer 1 — Noise canvas */} {/* Layer 1.5 — Shader animation (nuevo, no reemplaza nada) */} {window.ShaderAnimation && inView && } {/* Layer 2 — Aurora blobs */}
{/* Layer 3 — Grid overlay */}
{/* Content */}
{/* Chat bubble — siempre visible, wave+orbit CSS puro */}
{[0, 1, 2].map((i) =>
)}
{/* Eyebrow (opcional, distinta por landing) */} {content.cta.eyebrow &&
{content.cta.eyebrow}
} {/* Title with parallax */}

{inView && }

{/* Subtitle with parallax — con contador animado si la landing lo define */}

{counterCfg ? <> {counterCfg.prefix} {count}{counterCfg.unit} {counterCfg.suffix} : content.cta.sub}

{/* Buttons */}
); } // ───────────── FOOTER ───────────── const FOOT_ICONS = { mail: '', whatsapp: '', linkedin: '', instagram: '', facebook: '' }; const FOOT_FILLED = { whatsapp: true }; function FootIcon({ n, s = 16 }) { const filled = FOOT_FILLED[n]; return ; } function FFooter({ content }) { const inLegal = EK_SUB === 'legal'; const rel = ekRel; const legalHref = (name) => inLegal ? name : rel('legal/' + name); return ( ); } Object.assign(window, { FNav, LandingNav, FHero, FMarquee, FStats, FSoluciones, FProceso, FCaso, FTestimonios, FEquipo, FBlog, FFaq, FCtaFinal, FFooter, F, ekRel });