// PLAIN TEXT JS — iam/marketing v5 home
//
// Banner ensures finfo classifies as text/plain.
// D4-prod · Home page
// Operator Console direction · production hi-fi
// ── Editable copy loader ─────────────────────────────────────────
// Strings come from copy.js (window.HOME_COPY). If a key is missing,
// the `??` fallback uses the literal default so the page never breaks.
const __C = (typeof window !== 'undefined' && window.HOME_COPY) || {};
const HERO = __C.hero || {};
const PROBLEM = __C.problem || {};
const DIVIDER = __C.divider || {};
const GLOBAL = __C.global || {};
// ── useIsMobile · CSS-viewport detection via matchMedia (works in real browsers + preview emulation) ─
function useIsMobile(breakpoint = 900) {
const query = `(max-width: ${breakpoint - 1}px)`;
const [isMobile, setIsMobile] = React.useState(() => {
if (typeof window === 'undefined' || !window.matchMedia) return false;
return window.matchMedia(query).matches;
});
React.useEffect(() => {
if (!window.matchMedia) return;
const mql = window.matchMedia(query);
const onChange = (e) => setIsMobile(e.matches);
if (mql.addEventListener) mql.addEventListener('change', onChange);
else mql.addListener(onChange);
setIsMobile(mql.matches);
return () => {
if (mql.removeEventListener) mql.removeEventListener('change', onChange);
else mql.removeListener(onChange);
};
}, [query]);
return isMobile;
}
// ── ScrollRail · fixed right-edge progress tracker ────────────
// Per spec — 10 sections. Filtered at mount to only those actually present in DOM.
const SCROLL_RAIL_SECTIONS = [
['problem', '01 / PROBLEMA'],
['backend', '01.5 / BACKEND'],
['mechanism', '02 / MECANISMO'],
['engine', '03 · MOTOR'],
['who', '04 / EQUIPO'],
['receipts', '05 / RECIBOS'],
['diff', '06 / DIFERENCIA'],
['process', '08 / PROCESO'],
['command-center', '09 / CENTRO_DE_MANDO'],
['room-01', '10 / ROOM_01'],
];
const ScrollRail = () => {
const [activeIdx, setActiveIdx] = React.useState(0);
const [pct, setPct] = React.useState(0);
const [items, setItems] = React.useState([]);
const rafRef = React.useRef(null);
// Resolve only sections that are actually mounted in the DOM
React.useEffect(() => {
if (typeof window === 'undefined') return;
const present = SCROLL_RAIL_SECTIONS.filter(([id]) => document.getElementById(id));
setItems(present);
}, []);
React.useEffect(() => {
if (typeof window === 'undefined' || !items.length) return;
let ticking = false;
const onScroll = () => {
if (ticking) return;
ticking = true;
rafRef.current = requestAnimationFrame(() => {
const max = document.documentElement.scrollHeight - window.innerHeight;
const ratio = max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0;
setPct(Math.round(ratio * 100));
ticking = false;
});
};
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll, { passive: true });
const observed = items
.map(([id]) => document.getElementById(id))
.filter(Boolean);
const visibility = new Map();
const obs = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
visibility.set(entry.target.id, entry.intersectionRatio);
});
let bestId = null;
let bestRatio = 0;
visibility.forEach((ratio, id) => {
if (ratio > bestRatio) { bestRatio = ratio; bestId = id; }
});
if (bestId) {
const idx = items.findIndex(([id]) => id === bestId);
if (idx >= 0) setActiveIdx(idx);
}
}, { threshold: [0, 0.25, 0.5, 0.75, 1] });
observed.forEach((el) => obs.observe(el));
return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
if (rafRef.current) cancelAnimationFrame(rafRef.current);
obs.disconnect();
};
}, [items]);
const handleJump = (id) => {
const el = document.getElementById(id);
if (!el) return;
const top = el.getBoundingClientRect().top + window.scrollY - 60;
window.scrollTo({ top, behavior: 'smooth' });
};
if (!items.length) return null;
return (
<>
// SECCIÓN
{items.map(([id, label], i) => {
const top = items.length > 1 ? (i / (items.length - 1)) * 100 : 0;
const active = i === activeIdx;
return (
handleJump(id)}
className={`scroll-rail-row ${active ? 'is-active' : ''}`}
aria-label={`Saltar a ${label}`}
style={{
position: 'absolute', left: 0, top: `${top}%`,
transform: 'translateY(-50%)',
display: 'flex', alignItems: 'center', gap: 10, flexDirection: 'row',
background: 'transparent', border: 'none', padding: 0,
cursor: 'pointer', pointerEvents: 'auto',
}}
>
{label}
);
})}
// SCROLL · {String(pct).padStart(2, '0')}%
>
);
};
const HomePage = ({ lang = 'ES' }) => {
const mobile = useIsMobile();
return (
{/* Mobile-only press feedback for magnetic CTAs (no cursor → :active fallback) */}
{!mobile && }
);
};
// ── HERO ── VSL-first vertical-axis · centered focus ───────────
const HomeHero = ({ mobile }) => {
const dashboardRef = React.useRef(null);
const heroLang = 'ES';
const metrics = [
['CONVERSIONES', '12,842', '+32.4%', 'metric-a'],
['ROAS', '6.72x', '+18.7%', 'metric-b'],
['COSTO POR ADQUISICIÓN', '$23.18', '-14.3%', 'metric-c'],
];
const campaigns = [
['Prospecting | US', '7.34x', '$1,246,231'],
['Retargeting | All', '6.21x', '$982,115'],
['Lookalike | 1%', '5.18x', '$752,841'],
['Email Promotion', '8.11x', '$612,503'],
];
React.useEffect(() => {
const root = dashboardRef.current;
if (!root) return;
const cards = Array.from(root.querySelectorAll('.iam-tilt-card'));
let raf = 0;
let active = null;
const resetCard = (card) => {
card.style.setProperty('--rx', '0deg');
card.style.setProperty('--ry', '0deg');
};
const onMove = (event) => {
const card = event.currentTarget;
active = { card, x: event.clientX, y: event.clientY };
if (raf) return;
raf = requestAnimationFrame(() => {
if (!active) { raf = 0; return; }
const rect = active.card.getBoundingClientRect();
const px = (active.x - rect.left) / rect.width - 0.5;
const py = (active.y - rect.top) / rect.height - 0.5;
active.card.style.setProperty('--ry', `${px * 8}deg`);
active.card.style.setProperty('--rx', `${py * -7}deg`);
raf = 0;
});
};
const onScroll = () => {
const rect = root.getBoundingClientRect();
const viewport = window.innerHeight || 1;
const progress = Math.max(-1, Math.min(1, (viewport * 0.5 - rect.top) / viewport));
cards.forEach((card, i) => {
const speed = Number(card.dataset.speed || 12);
card.style.setProperty('--py', `${progress * speed}px`);
});
};
cards.forEach((card) => {
card.addEventListener('pointermove', onMove);
card.addEventListener('pointerleave', () => resetCard(card));
});
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll, { passive: true });
return () => {
if (raf) cancelAnimationFrame(raf);
cards.forEach((card) => {
card.removeEventListener('pointermove', onMove);
resetCard(card);
});
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
};
}, []);
return (
{Array.from({ length: 18 }).map((_, i) => )}
Performance Marketing. Sistemas de IA. Crecimiento.
Escala Con Marketing Que Realmente Mueve Ingresos
Construimos campañas de alto rendimiento, sistemas de conversión y motores de crecimiento automatizados para marcas listas para escalar.
{['Ads', 'CRO', 'Email', 'Automatización'].map((x) => // {x} )}
RENDIMIENTO DE INGRESOS
$4,392,712 ▲ 28.6%
{[0,1,2,3,4].map(i => )}
{metrics.map(([label, value, delta, cls], i) => (
))}
CAMPAÑAS TOP
{campaigns.map(([name, roas, revenue]) =>
{name} {roas} {revenue}
)}
{heroLang === 'ES' ? 'EMBUDO DE CONVERSIÓN' : 'CONVERSION FUNNEL'}
{heroLang === 'ES' ? 'Visitantes' : 'Visitors'} 342,142 {heroLang === 'ES' ? 'Vistas de Landing' : 'Landing Page Views'} 78,931 {heroLang === 'ES' ? 'Compras' : 'Purchases'} 12,842 3.75%
);
};
const HomeMarquee = () => (
);
// ── PROBLEM ────────────────────────────────────────────────────
const HomeProblem = ({ mobile }) => (
Pagas por anuncios.
El tráfico llega aquí.
Y ahí es donde muchas marcas pierden la venta.
Compites contra MercadoLibre, Amazon y Shein. Tu cliente ya está acostumbrado a páginas rápidas, confianza inmediata, pruebas claras y una experiencia de compra sin fricción.
No necesitas más herramientas sueltas. Necesitas que tu tienda convierta.
La mayoría de las agencias de "crecimiento" solo compran más clics y mandan tráfico a un checkout lleno de fugas:
{['Pop ups que estorban.', 'Poca confianza.', 'Precios confusos.', 'Botones lentos.', 'Cero pruebas.', 'Una experiencia que hace dudar al cliente justo antes de comprar.'].map((item, i) => (
×
{item}
))}
El CAC sube. El LTV baja. Y tu cliente nunca llega al botón de compra.
{[
['→', 'CAC ↑ 38% YoY · Porque la página tiene fugas.'],
['→', 'LTV ↓ 22% · Porque no hay backend que convierta compradores en clientes recurrentes.'],
['→', 'ROAS 3.2x → 1.4x · Misma inversión. Menos ingresos.'],
['→', 'El equipo se va · Y no queda ningún sistema que heredar.'],
].map(([a, t], i) => (
{a}
{t}
))}
Reservar llamada para corregir fugas
{/* Beginner dropshipping mobile store — believable but flawed · order 2 on mobile */}
{/* Premium angled-floating product-shot phone treatment */}
{/* Ambient chartreuse halo behind the phone */}
{/* Soft floor reflection — mirrored fade beneath the phone */}
{/* Titanium frame — outer ring */}
{/* Inner bezel — black */}
{/* Screen */}
{/* Status bar */}
9:41
{/* Signal */}
5G
{/* Battery */}
{/* Dynamic Island */}
{/* Safari URL bar */}
🔒
thebestgadget.myshopify.com
↻
{/* Site nav */}
☰
BESTGADGET
🛒1
{/* Hero */}
-70% OFF
FREE SHIPPING*
only 3 left!
{/* Product info */}
HOT TRENDING ★ NEW
2024 NEW Premium Smart Multi-Function Gadget Pro™ (Original)
$29.99
$99.99
SAVE 70%
★★★★★ (2 reviews)
{[
{ l: '✓ SECURE', c: '#22c55e' },
{ l: '🚚 FAST', c: '#3b82f6' },
{ l: '↩ 30-DAY', c: '#a855f7' },
{ l: '⭐ #1 USA', c: '#f59e0b' },
].map((b, i) => (
{b.l}
))}
ADD TO CART →
🔥 17 people viewing this · order soon!
{/* Home indicator */}
{/* Screen reflection — subtle highlight */}
{/* Side buttons — left silence + volume */}
{/* Right side: power */}
{/* Floating objection popups — overlap the browser */}
{[
{ txt: '✕ "¿esto es real?"', top: '8%', left: '-4%', rot: -3, c: D4P.danger },
{ txt: '✕ "¿qué hace esto realmente?"', top: '24%', right: '-6%', rot: 4, c: D4P.warm },
{ txt: '✕ "¿por qué el envío cuesta más que el producto?"', top: '46%', left: '-8%', rot: -2, c: D4P.danger },
{ txt: '✕ "sin reseñas · sin pruebas"', top: '62%', right: '-4%', rot: 3, c: D4P.warm },
{ txt: '✕ "el sitio parece del 2009"', top: '78%', left: '4%', rot: -4, c: D4P.danger },
{ txt: '✕ "lo voy a pensar" → nunca regresa', top: '92%', right: '8%', rot: 2, c: D4P.danger },
].map((p, i) => (
{p.txt}
))}
{/* Mouse cursor leaving */}
→ pestaña cerrada
👋
{/* Caption */}
// 1 de cada 100 compra · 99 rebotaron
// arreglamos la página · después compramos los anuncios
);
// ── BACKEND RECOVERY ───────────────────────────────────────────
const HomeBackend = ({ mobile }) => {
const proSystems = [
{ name: 'Klaviyo · Flujos de Ciclo de Vida', value: '+$8,400/mes', on: true },
{ name: 'Upsell Post-Compra', value: '+$5,200/mes', on: true },
{ name: 'Downsell Recuperación de Carrito', value: '+$3,600/mes', on: true },
{ name: 'Agente de IA · Recuperación CX', value: '+$4,100/mes', on: true },
{ name: 'Abandono de Navegación', value: '+$2,800/mes', on: true },
{ name: 'RFM Win-Back · 60d', value: '+$3,200/mes', on: true },
{ name: 'Replenishment Subscribe-Save', value: '+$2,700/mes', on: true },
];
const noneSystems = [
'Klaviyo · Flujos de Ciclo de Vida',
'Upsell Post-Compra',
'Downsell Recuperación de Carrito',
'Agente de IA · Recuperación CX',
'Abandono de Navegación',
'RFM Win-Back · 60d',
'Replenishment Subscribe-Save',
];
return (
{/* Centered headline + sub */}
Mismo negocio.
Mismo tráfico.
{/* TWO VISUAL DASHBOARDS — pro vs none — center overlay text */}
{/* ── LEFT · PRO BUSINESS ────────────────────────────────── */}
// NEGOCIO PRO
Stack completo.
// 7 sistemas · sincronizados · 24/7
EN VIVO
{/* monthly net */}
// EXTRA/MES GENERADO
▲ +12.4% MoM
+$30,000
// por cada $100K en ingresos · sin gastar más en anuncios
{/* system rows */}
{proSystems.map((s, i) => (
{s.name}
{s.value}
))}
{/* ── RIGHT · NO SYSTEM ──────────────────────────────────── */}
// SOLO ANUNCIOS
Un canal.
// 0 sistemas · desconectado · con fugas
OFFLINE
{/* monthly leak */}
// NETO/MES · FUGÁNDOSE
▼ −12.4% MoM
−$30,000
// por cada $100K en ingresos · se fueron · nunca regresaron
{/* missing rows */}
{noneSystems.map((s, i) => (
{s}
APAGADO
))}
{/* CENTER OVERLAY — "WITH OUR SYSTEM" / "NO SYSTEM" */}
{!mobile && (
// CON NUESTRO SISTEMA
+$30K/mes
// SIN SISTEMA
−$30K/mes
)}
{/* footer line — same store same traffic */}
$60K/mes de diferencia
— sin gastar un dólar más en anuncios.
);
};
// ── INCOME FOUNTAIN · interactive Canvas 2D particle system ────
// Each stream is a labeled nozzle at the top. Particles fall under gravity
// into a pool at the bottom. Hover a stream to highlight, click to toggle.
// IntersectionObserver pauses rAF when off-screen. Reduced-motion renders
// a single static frame. Falls back to nothing on canvas-failed browsers.
const IncomeFountain = ({ streams, mobile }) => {
const canvasRef = React.useRef(null);
const containerRef = React.useRef(null);
const stateRef = React.useRef({
particles: [],
nozzles: [],
active: streams.map(() => true),
hovered: -1,
poolFill: 0, // visual fill height target
paused: false,
rafId: 0,
lastEmit: 0,
});
const [hoveredIdx, setHoveredIdx] = React.useState(-1);
const [activeMap, setActiveMap] = React.useState(streams.map(() => true));
React.useEffect(() => {
const canvas = canvasRef.current;
const container = containerRef.current;
if (!canvas || !container) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const H = mobile ? 360 : 460;
const resize = () => {
const w = container.clientWidth;
canvas.width = w * dpr;
canvas.height = H * dpr;
canvas.style.width = w + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
// recompute nozzle positions
const n = streams.length;
stateRef.current.nozzles = streams.map((s, i) => ({
x: (w * (i + 0.5)) / n,
y: 70,
idx: i,
}));
};
resize();
window.addEventListener('resize', resize);
// Pause when off-screen
const obs = new IntersectionObserver(
([entry]) => { stateRef.current.paused = !entry.isIntersecting; },
{ threshold: 0 }
);
obs.observe(container);
// Hover + click
const hitTest = (mx, my) => {
for (let i = 0; i < stateRef.current.nozzles.length; i++) {
const n = stateRef.current.nozzles[i];
if (Math.abs(mx - n.x) < 28 && Math.abs(my - n.y) < 22) return i;
}
return -1;
};
const onMove = (e) => {
const r = canvas.getBoundingClientRect();
const hit = hitTest(e.clientX - r.left, e.clientY - r.top);
if (hit !== stateRef.current.hovered) {
stateRef.current.hovered = hit;
setHoveredIdx(hit);
canvas.style.cursor = hit >= 0 ? 'pointer' : 'default';
}
};
const onLeave = () => {
stateRef.current.hovered = -1;
setHoveredIdx(-1);
canvas.style.cursor = 'default';
};
const onClick = () => {
const i = stateRef.current.hovered;
if (i < 0) return;
const next = [...stateRef.current.active];
next[i] = !next[i];
stateRef.current.active = next;
setActiveMap(next);
};
canvas.addEventListener('mousemove', onMove);
canvas.addEventListener('mouseleave', onLeave);
canvas.addEventListener('click', onClick);
// Static render for reduced-motion users
const drawFrame = (animate) => {
const w = canvas.clientWidth;
const poolY = H - 90;
ctx.clearRect(0, 0, w, H);
// Pool basin border
ctx.strokeStyle = 'rgba(197,216,109,0.28)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(40, poolY);
ctx.lineTo(w - 40, poolY);
ctx.stroke();
// Pool fill — height proportional to count of active streams
const activeCount = stateRef.current.active.filter(Boolean).length;
const targetFill = (activeCount / streams.length) * 78;
stateRef.current.poolFill += (targetFill - stateRef.current.poolFill) * 0.06;
const fillH = stateRef.current.poolFill;
const grad = ctx.createLinearGradient(0, poolY, 0, poolY + 80);
grad.addColorStop(0, 'rgba(197,216,109,0.32)');
grad.addColorStop(1, 'rgba(197,216,109,0.08)');
ctx.fillStyle = grad;
ctx.fillRect(40, poolY + (80 - fillH), w - 80, fillH);
ctx.strokeStyle = 'rgba(197,216,109,0.18)';
ctx.strokeRect(40, poolY, w - 80, 80);
// Nozzles + labels
const ns = stateRef.current.nozzles;
for (let i = 0; i < ns.length; i++) {
const n = ns[i];
const isActive = stateRef.current.active[i];
const isHovered = stateRef.current.hovered === i;
// Nozzle box
ctx.fillStyle = isActive
? (isHovered ? '#D9EE7A' : '#C5D86D')
: 'rgba(197,216,109,0.18)';
ctx.fillRect(n.x - 16, n.y - 6, 32, 12);
if (isActive) {
ctx.fillStyle = 'rgba(197,216,109,0.25)';
ctx.fillRect(n.x - 18, n.y + 6, 36, 4); // drip lip
}
// Connector line down to nozzle
ctx.strokeStyle = isActive ? 'rgba(197,216,109,0.4)' : 'rgba(197,216,109,0.1)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(n.x, 0);
ctx.lineTo(n.x, n.y - 6);
ctx.stroke();
// Label below
ctx.fillStyle = isActive
? (isHovered ? '#F2EFE6' : 'rgba(242,239,230,0.78)')
: 'rgba(242,239,230,0.35)';
ctx.font = `${mobile ? 8 : 9}px JetBrains Mono, monospace`;
ctx.textAlign = 'center';
const short = streams[i].name.split(' ')[0].replace('·', '');
ctx.fillText(short, n.x, n.y + 26);
ctx.fillStyle = isActive ? '#C5D86D' : 'rgba(197,216,109,0.3)';
ctx.font = `${mobile ? 8 : 9}px JetBrains Mono, monospace`;
ctx.fillText(`+${(streams[i].recover / 1000).toFixed(1)}K`, n.x, n.y + 40);
}
// Particles
const ps = stateRef.current.particles;
for (let p of ps) {
const opacity = Math.min(1, p.life / 80);
ctx.fillStyle = `rgba(197,216,109,${opacity * 0.85})`;
ctx.beginPath();
ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2);
ctx.fill();
}
// Pool surface ripple line
ctx.strokeStyle = 'rgba(197,216,109,0.6)';
ctx.lineWidth = 1;
ctx.beginPath();
const rippleY = poolY + (80 - fillH);
ctx.moveTo(40, rippleY);
ctx.lineTo(w - 40, rippleY);
ctx.stroke();
};
if (reduced) {
drawFrame(false);
return () => {
obs.disconnect();
canvas.removeEventListener('mousemove', onMove);
canvas.removeEventListener('mouseleave', onLeave);
canvas.removeEventListener('click', onClick);
window.removeEventListener('resize', resize);
};
}
// Animation loop
const tick = () => {
if (!stateRef.current.paused) {
const w = canvas.clientWidth;
const poolY = H - 90;
const now = performance.now();
// Emit one particle per active stream — desktop ~55ms · mobile ~110ms (50% density for 60fps)
if (now - stateRef.current.lastEmit > (mobile ? 110 : 55)) {
stateRef.current.lastEmit = now;
const ns = stateRef.current.nozzles;
for (let i = 0; i < ns.length; i++) {
if (!stateRef.current.active[i]) continue;
stateRef.current.particles.push({
x: ns[i].x + (Math.random() - 0.5) * 4,
y: ns[i].y + 8,
vx: (Math.random() - 0.5) * 0.8,
vy: 1.4 + Math.random() * 0.6,
life: 220,
});
}
}
// Update particles
const survivors = [];
for (let p of stateRef.current.particles) {
p.vy += 0.15;
p.x += p.vx;
p.y += p.vy;
p.life--;
// Drip into pool — slight bounce stop
if (p.y > poolY + (80 - stateRef.current.poolFill) - 2) continue;
if (p.life > 0) survivors.push(p);
}
stateRef.current.particles = survivors;
drawFrame(true);
}
stateRef.current.rafId = requestAnimationFrame(tick);
};
stateRef.current.rafId = requestAnimationFrame(tick);
return () => {
cancelAnimationFrame(stateRef.current.rafId);
obs.disconnect();
canvas.removeEventListener('mousemove', onMove);
canvas.removeEventListener('mouseleave', onLeave);
canvas.removeEventListener('click', onClick);
window.removeEventListener('resize', resize);
};
}, [mobile, streams]);
// Total = sum of active stream values
const totalActive = streams.reduce((sum, s, i) => sum + (activeMap[i] ? s.recover : 0), 0);
const activeCount = activeMap.filter(Boolean).length;
return (
{/* Header strip — top of canvas */}
// {activeCount} FLUJO{activeCount === 1 ? '' : 'S'} · EN VIVO
// haz clic en una boquilla para activar/desactivar
{/* Total counter — bottom right */}
// EXTRA / MES
+${totalActive.toLocaleString()}
{/* Hover tooltip — bottom left */}
{hoveredIdx >= 0 && (
// {streams[hoveredIdx].name}
+${streams[hoveredIdx].recover.toLocaleString()}/mes
// {activeMap[hoveredIdx] ? 'fluyendo — clic para apagar' : 'apagado — clic para reactivar'}
)}
);
};
// ── DIVIDER · MULTI-INCOME ──────────────────────────────────────
const HomeMultiBanner = ({ mobile }) => {
// Each "string" = a backend system that catches lost revenue
const strings = [
{ name: 'KLAVIYO · CICLO DE VIDA', recover: 8400, pct: 8.4 },
{ name: 'UPSELL POST-COMPRA', recover: 5200, pct: 5.2 },
{ name: 'DOWNSELL RECUPERACIÓN DE CARRITO', recover: 3600, pct: 3.6 },
{ name: 'AGENTE DE IA · RECUPERACIÓN CX', recover: 4100, pct: 4.1 },
{ name: 'ABANDONO DE NAVEGACIÓN', recover: 2800, pct: 2.8 },
{ name: 'RFM WIN-BACK · 60d', recover: 3200, pct: 3.2 },
{ name: 'REPLENISHMENT SUBSCRIBE-SAVE', recover: 2700, pct: 2.7 },
];
const total = strings.reduce((a, s) => a + s.recover, 0);
return (
{/* Header */}
{DIVIDER.eyebrow || '// AXIOMA · 002 · DIVERSIFICA O MUERE'}
{DIVIDER.line1 || 'Múltiples flujos'} {DIVIDER.line1Italic || 'de ingreso'}
{DIVIDER.line2 || 'es'} {DIVIDER.line2Italic || 'así se gana.'}
{DIVIDER.body || 'Un canal de flujo no es rentable — pero siete te enriquecen.'}
{/* ──── REVENUE TOWERS · ONE STREAM vs. SEVEN ──── */}
{/* Shared input rail — same traffic feeds both towers */}
{DIVIDER.inputBarLabel || '// MISMO INPUT · MISMA TIENDA · MISMA INVERSIÓN PUBLICITARIA'}
{DIVIDER.inputBarTraffic || '$100,000'}{DIVIDER.inputBarSub || '/MES TRÁFICO'}
{/* ──── INCOME FOUNTAIN · Canvas particle system · interactive ──── */}
{/* Verdict strip — locks the message */}
{DIVIDER.verdictTag || '// VEREDICTO'}
{DIVIDER.verdictLead || 'Un flujo es una apuesta.'}{' '}
{DIVIDER.verdictItalic || 'Siete apilados son un motor.'}
// ${(strings.reduce((a, s) => a + s.recover, 0) * 12).toLocaleString()}/año extra
);
};
// ── ENGINE — two pipelines ──────────────────────────────────────
const HomeEngine = ({ mobile }) => (
Un negocio es rico.
El otro está seco.
// mismo producto · mismo mercado · misma inversión publicitaria · cableado distinto · resultado distinto
{/* Dramatic centerpiece — letters fall in, point to each pipe */}
{/* LEFT — OUR WAY → green/rich */}
// NUESTRA FORMA
{Array.from('rico.').map((ch, i) => (
{ch}
))}
→ muchos afluentes · una base
{/* CENTER — divider arrows · desktop only (mobile arrows pointed at whitespace) */}
{!mobile && (
VS
)}
{mobile && (
VS
)}
{/* RIGHT — YOUR AGENCY → red/dry */}
// TU AGENCIA
{Array.from('seco.').map((ch, i) => (
{ch}
))}
→ un solo tubo · hub roto · con fugas
{/* RICH */}
// HOME_BASE_A · NUESTROS CLIENTES
{[
{ x: 30, y: 30, label: 'ANUNCIOS PAGADOS' },
{ x: 30, y: 90, label: 'EMAIL · 5 FLUJOS' },
{ x: 30, y: 150, label: 'SMS' },
{ x: 30, y: 210, label: 'ORGÁNICO' },
{ x: 30, y: 270, label: 'REFERIDOS' },
{ x: 460, y: 30, label: 'POST-COMPRA', right: true },
{ x: 460, y: 110, label: 'WIN-BACK', right: true },
{ x: 460, y: 220, label: 'IMPULSO LTV', right: true },
].map((s, i) => (
{s.label}
))}
BASE
$$$
{[['8', 'fuentes'], ['5', 'flujos backend'], ['+38%', 'LTV']].map(([v, l]) => (
))}
{/* DRY */}
// HOME_BASE_B · LA MAYORÍA DE AGENCIAS
ANUNCIOS PAGADOS
// única fuente
{[{ x: 30, y: 30, l: 'EMAIL · ✕' }, { x: 30, y: 90, l: 'SMS · ✕' }, { x: 30, y: 230, l: 'BACKEND · ✕' }, { x: 30, y: 290, l: 'POST-COMPRA · ✕' }].map((s, i) => (
{s.l}
))}
BASE
$
{[{ x: 290, y: 110 }, { x: 305, y: 160 }, { x: 290, y: 210 }].map((p, i) => (
fuga →
))}
{[['1', 'fuente'], ['0', 'flujos backend'], ['−40%', 'LTV']].map(([v, l]) => (
))}
);
// ── EDITORIAL BREAK — holographic $120M counter ───────────────
// Pinterest-derived move: oversized animated counter with holographic shimmer + scan lines.
const EditorialBreak = ({ mobile }) => {
const counterRef = React.useRef(null);
const sectionRef = React.useRef(null);
React.useEffect(() => {
const target = 120000000;
const duration = 2800;
let raf = null;
const easeOutExpo = (t) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t));
const fmt = (n) => '$' + Math.floor(n).toLocaleString('en-US');
const run = () => {
if (!counterRef.current) return;
if (raf) cancelAnimationFrame(raf);
const start = performance.now();
const tick = (now) => {
const p = Math.min((now - start) / duration, 1);
counterRef.current.textContent = fmt(easeOutExpo(p) * target);
if (p < 1) raf = requestAnimationFrame(tick);
else counterRef.current.textContent = fmt(target);
};
raf = requestAnimationFrame(tick);
};
const obs = new IntersectionObserver((entries) => {
entries.forEach((e) => { if (e.isIntersecting && e.intersectionRatio > 0.5) run(); });
}, { threshold: 0.5 });
if (counterRef.current) obs.observe(counterRef.current);
return () => { if (raf) cancelAnimationFrame(raf); obs.disconnect(); };
}, []);
return (
{/* corner terminal labels */}
[ ENGINE_V1 · EN VIVO ]
● ROOM_01 EN VIVO_
// REVENUE.SH
// COUNTER.JS · ACTIVO
$0
[
GENERADO PARA NUESTROS CLIENTES
·
$40M EN INVERSIÓN PUBLICITARIA
·
10 AÑOS
]
// INGRESOS REALES · MARCAS REALES · RESULTADOS REALES
);
};
// ── MECHANISM — V12 engine ─────────────────────────────────────
const HomeMechanism = ({ mobile }) => (
12 cilindros.
Un motor.
// 12 cilindros disparando · ingresos en cada tiempo · poder v12
{/* ENGINE — center on desktop spanning all 3 rows; top spanning both cols on mobile */}
{/* CSS fallback — sits behind the real image so loading state isn't black */}
{/* Real V12 photo — WP Media absolute URL */}
{/* Brand-grade overlay — chartreuse cast + vignette */}
{/* Animated smoke wisps */}
{[80, 180, 280, 380, 500].map((cx, i) => (
{[0, 0.9, 1.8].map(d => (
))}
))}
{/* HUD corner brackets */}
{[
{ top: 8, left: 8, borders: { borderTop: `1px solid ${D4P.accent}`, borderLeft: `1px solid ${D4P.accent}` } },
{ top: 8, right: 8, borders: { borderTop: `1px solid ${D4P.accent}`, borderRight: `1px solid ${D4P.accent}` } },
{ bottom: 8, left: 8, borders: { borderBottom: `1px solid ${D4P.accent}`, borderLeft: `1px solid ${D4P.accent}` } },
{ bottom: 8, right: 8, borders: { borderBottom: `1px solid ${D4P.accent}`, borderRight: `1px solid ${D4P.accent}` } },
].map((c, i) => (
))}
{/* Mono labels */}
+ V12 · ENGINE_v1
RPM 8,400
TORQUE 94%
ESTADO · NOMINAL
{/* center reticle */}
{/* firing-order strip */}
// ORDEN DE ENCENDIDO · 1-12-5-8-3-10-6-7-2-11-4-9
{/* FOUR FRAMEWORK BOXES · ONE IN EACH CORNER OF THE ENGINE */}
{[
{ n: '01', title: 'WEBSITE · CRO', color: D4P.bone, items: ['Conversion Rate Optimization', 'Landing Pages', 'Confianza + Velocidad'], dCol: 1, dRow: 1, mIdx: 0, align: 'left' },
{ n: '02', title: 'EMAIL · SMS', color: D4P.accent, items: ['Automatizaciones', 'Flujos', 'Campañas'], dCol: 3, dRow: 1, mIdx: 1, align: 'right' },
{ n: '03', title: 'UPSELLS · IA', color: D4P.warm, items: ['Upsells Post-Compra', 'Agentes de IA', 'Workflows de IA'], dCol: 1, dRow: 3, mIdx: 2, align: 'left' },
{ n: '04', title: 'PAID · ORGÁNICO', color: D4P.cool, items: ['Paid Social', 'Organic Social', 'Contenido + Anuncios'], dCol: 3, dRow: 3, mIdx: 3, align: 'right' },
].map(b => (
{/* number + status pip */}
{b.n}
{/* title */}
{b.title}.
{/* 3 framework items */}
{b.items.map((item, i) => (
+
{item}
))}
))}
);
// ── WHO ─────────────────────────────────────────────────────────
const HomeWho = ({ mobile }) => (
The team in
Room_01.
We're not a deck-and-deliver agency. Every operator on this team has built and broken their own businesses. We've been in your seat.
14 yrs $1.5B moved 65k operators
{[
['M.F.', 'Founder · Operator', '8 exits', '$420M moved'],
['A.S.', 'CRO · Backend', '5 flows', '+38% LTV avg'],
['J.R.', 'Media Buyer', '$80M ad spend', '3.4× ROAS'],
['L.K.', 'Systems · AI', '14 stacks', '10× cadence'],
].map(([n, role, m1, m2]) => (
{/* Avatar placeholder */}
))}
);
// ── RECEIPTS ────────────────────────────────────────────────────
const HomeReceipts = ({ mobile }) => (
Numbers,
not adjectives.
{[
{ client: 'Stayfull', cat: 'DTC · Wellness', from: '$1.8K/mo', to: '$100K/mo', mult: '55×', days: '90 days', href: './case-stayfull.html' },
{ client: 'Mortero', cat: 'CPG · Beverage', from: '$0/mo', to: '$800K/mo', mult: '∞', days: '70 days', href: './case-mortero.html' },
{ client: 'Promoted to Mom', cat: 'Course · Coaching', from: '$8K/mo', to: '$80K/mo', mult: '10×', days: '12 mo', href: './case-promoted.html' },
].map((c) => (
{ e.currentTarget.style.borderColor = D4P.accent; e.currentTarget.style.transform = 'translateY(-4px)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = D4P.hair018; e.currentTarget.style.transform = 'translateY(0)'; }}>
// {c.client.toUpperCase()}
{c.cat}
{c.from}
→
{c.to}
{c.mult}
· {c.days}
./read-case ▸
))}
);
// ── MobileDiffStack · IO-driven side-entry for §6 on mobile ───────
// Alternating left/right translateX(±60%) + opacity 0 → in-view.
// 80ms stagger · 600ms ease-out · respects prefers-reduced-motion.
const MobileDiffStack = ({ blades, usLabel = '// US' }) => {
const wrapRef = React.useRef(null);
React.useEffect(() => {
const el = wrapRef.current;
if (!el) return;
const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduced) { el.classList.add('is-in'); return; }
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
el.classList.add('is-in');
io.disconnect();
}
});
}, { threshold: 0.15, rootMargin: '0px 0px -10% 0px' });
io.observe(el);
return () => io.disconnect();
}, []);
return (
{blades.map((b, i) => {
const fromLeft = (i % 2 === 0);
return (
{b.axis}
{usLabel}
{b.us}
{b.usProof}
);
})}
);
};
// ── DIFF — SCROLL ROLODEX · WHY US → WHY NOT THEM ───────────────
// Each card has ONE rotation axis (X) driven by its own flip value.
// No nested 3D, no transform-style chains fighting backface-visibility.
// Bulletproof in Chrome / Safari / Firefox.
const HomeDiff = ({ mobile }) => {
const sectionRef = React.useRef(null);
const pinRef = React.useRef(null);
const [progress, setProgress] = React.useState(0);
React.useEffect(() => {
if (mobile) return;
const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduced) { setProgress(1.0); return; }
if (!window.gsap || !window.ScrollTrigger) {
// GSAP not loaded yet — fall back to no-op (section will still render statically)
console.warn('[HomeDiff] GSAP/ScrollTrigger not loaded');
return;
}
const gsap = window.gsap;
const ScrollTrigger = window.ScrollTrigger;
gsap.registerPlugin(ScrollTrigger);
const trigger = ScrollTrigger.create({
trigger: sectionRef.current,
start: 'top top',
end: '+=180%', // 180vh of scroll runway — unfurl + brief hold, no flip
pin: pinRef.current, // GSAP transforms the inner stage to keep it fixed
pinSpacing: true,
scrub: 1, // smooth-scrub: 1s catch-up
onUpdate: (self) => setProgress(self.progress),
// markers: true, // uncomment for dev debug
});
return () => {
trigger.kill();
ScrollTrigger.refresh();
};
}, [mobile]);
// Six parallel axes — US verbatim from Manny, THEM verbatim from Manny's comment 7
const blades = [
{ axis: 'ESCALA',
us: 'Escala más allá de tu techo actual.',
usProof: 'Crecimiento predecible diseñado para escalar.',
them: 'Solo han manejado cuentas de $50/día.',
themProof: 'Campañas de $50/día · para siempre' },
{ axis: 'CONVERSIÓN',
us: 'Convierte el tráfico en una máquina de dinero.',
usProof: 'Más conversión. Más retención. Más profit.',
them: 'Creen que los anuncios son el negocio.',
themProof: 'Gastar más · perder más · repetir' },
{ axis: 'PLATEAU',
us: 'Escapa del plateau de los $100K.',
usProof: 'Sistemas construidos para crecimiento serio.',
them: 'Aprenden eCom en YouTube.',
themProof: 'Ven cursos · coleccionan plantillas' },
{ axis: 'MARCA',
us: 'Construye una marca por la que la gente se obsesione.',
usProof: 'La autoridad genera demanda imparable.',
them: 'Nunca han construido una marca real.',
themProof: 'Pared de logos · cero equity' },
{ axis: 'INFRA',
us: 'Escala sin caos.',
usProof: 'Infraestructura real detrás del crecimiento.',
them: 'Corren con tabs y buena vibra.',
themProof: 'Hojas de cálculo · rezos · pánico' },
{ axis: 'CATEGORÍA',
us: 'De marca en crecimiento a líder de categoría.',
usProof: 'Construido para dominar, no para competir.',
them: 'Persiguen al algoritmo, no al mercado.',
themProof: 'Trends hoy · olvidadas mañana' },
];
// PHASE MAP across the 180vh scroll
// 0.00–0.70 unfurl · cards fly in from alternating sides, chartreuse only
// 0.70–1.00 hold · full chartreuse stack legible · ambient scan
const unfurl = Math.min(1, progress / 0.70);
// ease-out-quart for entrance, ease-out-back for rotation overshoot
const easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
const easeOutBack = (t) => { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); };
return (
{/* Headline — single static WHY US block */}
// 06 · POR QUÉ NOSOTROS
Los recibos de nuestra
excelencia.
{/* Milestone slab — editorial · asymmetric · hero + 2 supporting */}
{/* eyebrow bar */}
// MILESTONE_06 · DESDE 2015
// YO + MIS SOCIOS · OPERADORES, NO CONSULTORES
{/* asymmetric 3-up — hero $120M+ on the left, supporting stats stacked-equal on the right */}
{/* HERO · $120M+ generated for clients */}
// INGRESOS DE CLIENTES
$120M+
generados para clientes
{/* live pulse dot */}
{/* SUPPORTING · 10 yrs */}
// TIEMPO EN LA SALA
10 años
en eCom · hands-on
{/* SUPPORTING · $40M+ ad spend */}
// INVERSIÓN PUBLICITARIA
$40M+
manejados en portafolios
{/* Mobile: clean US-only stack with IO-driven side-entry · alternating ±60% translateX · 80ms stagger */}
{mobile && (
)}
{/* Desktop: GSAP ScrollTrigger pin · 400vh scroll runway · scrub 1 */}
{!mobile && (
{/* Ambient layer · subtle continuous motion to fill scroll holds */}
{/* big phase chip — WHY US */}
POR QUÉ NOSOTROS
// scroll · {Math.round(progress * 100)}%
// EJES
{String(Math.min(blades.length, Math.ceil(unfurl * blades.length))).padStart(2, '0')} / {String(blades.length).padStart(2, '0')}
{/* Card stack — Z-axis cascade · each card has its own rotateX flip */}
{blades.map((b, i) => {
const total = blades.length;
// SIDE-ENTRY FLOW · cards fly in from alternating sides and converge to center
// each card enters at progress (i * 0.06) and finishes at (0.55 + i * 0.06) within the unfurl phase (0-0.70)
const startProg = i * 0.06;
const endProg = 0.55 + i * 0.06;
const enterRaw = Math.min(1, Math.max(0, (progress - startProg) / Math.max(0.0001, (endProg - startProg))));
const enter = easeOutQuart(enterRaw);
const enterRot = easeOutBack(enterRaw);
// vertical fan — center anchor, even spacing (resting position)
const yRest = (i - (total - 1) / 2) * 110;
// horizontal entry: even-indexed cards from LEFT, odd from RIGHT
const fromLeft = (i % 2 === 0);
const sideSign = fromLeft ? -1 : 1;
const xStart = sideSign * 1200; // far off-stage
const x = xStart * (1 - enter);
// rotateY: -25deg from left, +25deg from right, easing to 0 with overshoot
const ryStart = sideSign * -25;
const rotY = ryStart * (1 - enterRot);
// depth · slight z-stagger so cards feel layered
const z = (i - (total - 1) / 2) * -8;
const scale = 0.88 + 0.12 * enter;
const opacity = enter;
const cardW = 560;
const cardH = 132;
return (
{/* US face (chartreuse) */}
{b.axis} · NOSOTROS
{String(i + 1).padStart(2, '0')} / {String(total).padStart(2, '0')}
{b.us}
{b.usProof}
);
})}
{/* Floor reflection */}
{/* Bottom legend */}
// SEIS EJES · POR QUÉ NOSOTROS
Seis ejes. Seis recibos. Un motor, en el registro.
)}
);
};
// ── PROCESS ─────────────────────────────────────────────────────
// 4-layer service stack visualised as a puzzle assembly: stage 0 (raw) → stage 4 (polished)
const HomeProcess = ({ mobile }) => {
// A single stage panel — a stylised storefront card whose fidelity grows left→right.
const StagePanel = ({ stage, mobile }) => {
// border, glow & opacity scale with stage
const isRaw = stage === 0;
const isFinal = stage === 4;
const borderColor =
isRaw ? 'rgba(242,239,230,0.18)' :
isFinal ? D4P.accent :
`rgba(197,216,109,${0.18 + stage * 0.14})`;
const bg =
isRaw ? 'rgba(15,15,17,0.45)' :
stage === 1 ? D4P.surface :
stage === 2 ? D4P.surface :
stage === 3 ? D4P.surface :
D4P.surface;
const glow =
isRaw ? 'none' :
isFinal ? `0 0 0 1px ${D4P.accent}, 0 18px 40px -18px rgba(197,216,109,0.55), 0 0 60px -8px rgba(197,216,109,0.35)` :
`0 ${4 + stage * 4}px ${16 + stage * 6}px -10px rgba(0,0,0,0.55), 0 0 ${20 + stage * 10}px -16px rgba(197,216,109,${0.10 + stage * 0.05})`;
return (
{/* mock browser chrome */}
{isRaw ? 'untitled.store/v0' : isFinal ? 'tumarca.com' : 'tumarca.com'}
{/* hero / headline area */}
= 1 ? D4P.bone : 'rgba(242,239,230,0.16)'),
marginBottom: 5,
borderRadius: 1,
}} />
{/* CTA button — appears at stage 1 */}
{stage >= 1 && (
COMPRAR YA
)}
{/* heatmap overlay — stage 3+ */}
{stage >= 3 && (
)}
{/* tiny climbing chart — stage 2+ */}
{stage >= 2 && (
= 2 && stage < 4 ? 26 : 30, left: 10, width: 60, height: 14, opacity: 0.85 }}>
)}
{/* upsell chip — stage 3+ */}
{stage >= 3 && (
+UPSELL
)}
{/* backend pills — stage 2+ */}
{stage >= 2 && stage < 4 && (
{['KLAVIYO', 'SMS', 'FLUJOS'].map(p => (
{p}
))}
)}
{/* ad campaign badges — stage 4 */}
{stage >= 4 && (
CAMPAÑAS
{['M', 'G', 'T'].map(c => (
{c}
))}
)}
{/* stage label */}
ETAPA {stage}
);
};
const stageCaptions = ['crudo', '+ CRO', '+ backend', '+ apps', '+ anuncios'];
// ── Puzzle piece path generator ─────────────────────────────
// Returns SVG path d-string for a single piece of size s, with
// each side flagged: +1 = tab (out), -1 = blank (in), 0 = flat edge.
const puzzlePath = (s, top, right, bottom, left) => {
const k = s * 0.18; // knob protrusion
const w = s * 0.18; // half-width of knob neck
const c = s * 0.10; // bezier control offset for round knob
// top edge: left → right
const topEdge = top === 0
? `L ${s} 0`
: `L ${s/2 - w} 0
C ${s/2 - w + c} ${-top * c}, ${s/2 - w - c} ${-top * (k - c)}, ${s/2} ${-top * k}
C ${s/2 + w + c} ${-top * (k - c)}, ${s/2 + w - c} ${-top * c}, ${s/2 + w} 0
L ${s} 0`;
// right edge: top → bottom
const rightEdge = right === 0
? `L ${s} ${s}`
: `L ${s} ${s/2 - w}
C ${s + right * c} ${s/2 - w + c}, ${s + right * (k - c)} ${s/2 - w - c}, ${s + right * k} ${s/2}
C ${s + right * (k - c)} ${s/2 + w + c}, ${s + right * c} ${s/2 + w - c}, ${s} ${s/2 + w}
L ${s} ${s}`;
// bottom edge: right → left
const bottomEdge = bottom === 0
? `L 0 ${s}`
: `L ${s/2 + w} ${s}
C ${s/2 + w - c} ${s + bottom * c}, ${s/2 + w + c} ${s + bottom * (k - c)}, ${s/2} ${s + bottom * k}
C ${s/2 - w - c} ${s + bottom * (k - c)}, ${s/2 - w + c} ${s + bottom * c}, ${s/2 - w} ${s}
L 0 ${s}`;
// left edge: bottom → top
const leftEdge = left === 0
? `Z`
: `L 0 ${s/2 + w}
C ${-left * c} ${s/2 + w - c}, ${-left * (k - c)} ${s/2 + w + c}, ${-left * k} ${s/2}
C ${-left * (k - c)} ${s/2 - w - c}, ${-left * c} ${s/2 - w + c}, 0 ${s/2 - w}
Z`;
return `M 0 0 ${topEdge} ${rightEdge} ${bottomEdge} ${leftEdge}`;
};
// Deterministic tab/blank map for 4x4 grid.
// Each interior edge has one tab, one blank — chosen by parity of (c,r).
// edges[c][r] = { top, right, bottom, left }
const buildEdges = () => {
const grid = [];
for (let c = 0; c < 4; c++) {
grid[c] = [];
for (let r = 0; r < 4; r++) {
// outer edges flat
const top = r === 0 ? 0 : -grid[c][r - 1].bottom; // mirror of above piece's bottom
const left = c === 0 ? 0 : -grid[c - 1][r].right; // mirror of left piece's right
// pick this piece's right & bottom: alternate tab/blank by parity
const right = c === 3 ? 0 : ((c + r) % 2 === 0 ? 1 : -1);
const bottom = r === 3 ? 0 : ((c * 3 + r) % 2 === 0 ? -1 : 1);
grid[c][r] = { top, right, bottom, left };
}
}
return grid;
};
const puzzleEdges = buildEdges();
// Group classification — which quadrant a piece belongs to.
// top-right (A · raw): cols 2-3, rows 0-1
// bottom-right (B · CRO): cols 2-3, rows 2-3
// bottom-left (C · BACKEND): cols 0-1, rows 2-3
// top-left (D · APPS+ADS): cols 0-1, rows 0-1
const groupOf = (c, r) => {
if (c >= 2 && r <= 1) return 'A';
if (c >= 2 && r >= 2) return 'B';
if (c <= 1 && r >= 2) return 'C';
return 'D';
};
const groupStyles = {
A: { stroke: 'rgba(242,239,230,0.32)', strokeDasharray: '3 3', fill: 'rgba(242,239,230,0.08)', label: '', labelColor: 'rgba(242,239,230,0.32)' },
B: { stroke: `${D4P.accent}4D`, strokeDasharray: '0', fill: D4P.surface, label: 'CRO', labelColor: `${D4P.accent}88` },
C: { stroke: `${D4P.accent}80`, strokeDasharray: '0', fill: 'rgba(197,216,109,0.06)', label: 'FLUJOS',labelColor: `${D4P.accent}AA` },
D: { stroke: D4P.accent, strokeDasharray: '0', fill: 'rgba(197,216,109,0.12)', label: 'ANUNCIOS', labelColor: D4P.accent },
};
const groupDelay = { A: 0, B: 220, C: 440, D: 660 };
// Per-piece words. Indexed by [c][r] (4×4). Each piece "says something".
// Quadrant A (top-right c=2-3, r=0-1): raw store failures
// Quadrant B (bottom-right c=2-3, r=2-3): + CRO
// Quadrant C (bottom-left c=0-1, r=2-3): + BACKEND
// Quadrant D (top-left c=0-1, r=0-1): + APPS + ADS
const pieceWords = {
// Quadrant D — top-left
'0-0': 'META', '1-0': 'IA',
'0-1': 'MAPAS-CALOR', '1-1': 'UPSELL',
// Quadrant A — top-right
'2-0': 'REBOTE', '3-0': 'FUGA',
'2-1': 'ABANDONO', '3-1': 'ESTANCAMIENTO',
// Quadrant C — bottom-left
'0-2': 'KLAVIYO', '1-2': 'SMS',
'0-3': 'FLUJOS', '1-3': 'EMAIL',
// Quadrant B — bottom-right
'2-2': 'PÁGINAS', '3-2': 'VELOCIDAD',
'2-3': 'PAGO', '3-3': 'COPY',
};
// Terminal-log order: A (failures) first → B → C → D (shipped). Matches snap stagger.
const terminalSequence = React.useMemo(() => {
const order = [];
const groupsOrder = ['A', 'B', 'C', 'D'];
groupsOrder.forEach(g => {
for (let c = 0; c < 4; c++) {
for (let r = 0; r < 4; r++) {
if (groupOf(c, r) === g) {
const within = (c % 2) * 60 + (r % 2) * 100;
order.push({
key: `${c}-${r}`,
word: pieceWords[`${c}-${r}`],
group: g,
t: groupDelay[g] + within,
});
}
}
}
});
return order.sort((a, b) => a.t - b.t);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// IntersectionObserver to trigger snap → connect → morph chain
const puzzleRef = React.useRef(null);
const wrapperRef = React.useRef(null);
const [logCount, setLogCount] = React.useState(0);
React.useEffect(() => {
if (!puzzleRef.current) return;
const node = puzzleRef.current;
const wrap = wrapperRef.current;
const reduced = typeof window !== 'undefined' && window.matchMedia
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
: false;
const timers = [];
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
node.classList.add('hp-pz-snap');
if (reduced) {
// Skip the show — go straight to morphed end-state
node.classList.add('is-connected');
node.classList.add('is-morphed');
if (wrap) { wrap.classList.add('is-connected'); wrap.classList.add('is-morphed'); }
setLogCount(terminalSequence.length);
} else {
// Stream terminal log lines as pieces snap in
terminalSequence.forEach((s, i) => {
timers.push(setTimeout(() => setLogCount(i + 1), s.t + 520));
});
// After all pieces have snapped (last delay ≈ 860ms + 620ms transition), connect
const lastSnap = terminalSequence[terminalSequence.length - 1].t + 620;
timers.push(setTimeout(() => {
node.classList.add('is-connected');
if (wrap) wrap.classList.add('is-connected');
}, lastSnap + 200));
// Hold the connected state, then morph into SUCCESS
timers.push(setTimeout(() => {
node.classList.add('is-morphed');
if (wrap) wrap.classList.add('is-morphed');
}, lastSnap + 200 + 1500));
}
io.disconnect();
}
});
}, { threshold: 0.25 });
io.observe(node);
return () => { io.disconnect(); timers.forEach(t => clearTimeout(t)); };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const layers = [
{
n: '01', title: 'Optimización de Conversión (CRO)',
desc: 'Tu tienda tiene que convertir de verdad. Reconstruimos lo que está matando tu CVR — velocidad de página, diseño, copy y proceso de pago.',
proof: '+1.8% LIFT PROMEDIO DE CVR',
tags: ['páginas', 'velocidad', 'pago'],
},
{
n: '02', title: 'Sistemas de Backend',
desc: 'Email, SMS, Klaviyo — cada automatización que recupera ingresos del tráfico por el que ya pagaste.',
proof: 'SUPERA AL 40% DE LAS AGENCIAS',
tags: ['klaviyo', 'sms', 'flujos'],
},
{
n: '03', title: 'Apps + Optimización',
desc: 'Upsells, ventas adicionales, mapas de calor y herramientas de decisión que componen cada visita.',
proof: '+22% AOV PROMEDIO',
tags: ['upsells', 'mapas-calor', 'apps'],
},
{
n: '04', title: 'Anuncios + Orgánico + IA',
desc: 'Meta, Google, TikTok, orgánico — y los agentes de IA que leen los datos y deciden el siguiente movimiento.',
proof: '$40M EN INVERSIÓN PUBLICITARIA RASTREADA',
tags: ['meta', 'google', 'tiktok'],
},
];
return (
{/* PART 1 — Header strip */}
// EL STACK DE 4 CAPAS · LO QUE INTEGRAMOS EN TU NEGOCIO
Todas las áreas de tu empresa
maximizadas y plenamente funcionando.
// tienda cruda → conversión → backend → expansión → demanda · el mismo playbook en cada cuenta
{/* PART 1b — Puzzle hero · the visual argument */}
{(() => {
const pieceSize = mobile ? 56 : 80;
const pad = pieceSize * 0.35; // padding for tabs
const gridPx = pieceSize * 4;
const svgSize = gridPx + pad * 2;
return (
{/* radial chartreuse glow */}
{/* overline label */}
// EL NEGOCIO DE 16 PIEZAS · ENSAMBLADO · SIEMPRE ENSAMBLANDO
{/* assembled wrap — puzzle + terminal log share state via .is-connected / .is-morphed */}
{/* puzzle stage */}
{puzzleEdges.map((col, c) =>
col.map((edges, r) => {
const g = groupOf(c, r);
const gs = groupStyles[g];
// stagger inside each group: A=0..200, B=220..420, C=440..640, D=660..860
const within = (c % 2) * 60 + (r % 2) * 100;
const delay = groupDelay[g] + within;
// entrance offset varies by group quadrant
const ox = c >= 2 ? 60 : -60;
const oy = r >= 2 ? 40 : -40;
const or = ((c + r) % 2 === 0) ? 8 : -8;
const d = puzzlePath(pieceSize, edges.top, edges.right, edges.bottom, edges.left);
const word = pieceWords[`${c}-${r}`];
const wordColor = g === 'D' ? D4P.accent : D4P.bone;
return (
{word}
);
})
)}
{/* SUCCESS overlay — fades in over the morphed puzzle */}
ÉXITO
{/* Terminal log — desktop only, streams as pieces snap */}
{!mobile && (
// system.log
{terminalSequence.map((s, i) => {
const shipped = s.group === 'D';
const on = i < logCount;
return (
[OK]
{s.word}
{shipped
? {'✓'} desplegado
: {'✗'} resuelto }
);
})}
SISTEMA ENSAMBLADO.
ÉXITO.
)}
{/* loose floating pieces — still being assembled */}
);
})()}
{/* PART 1c — The argument */}
// EL ARGUMENTO
La única forma de ganar es{' '}
construir un negocio real.
// no es una campaña. no es un hack. no es un solo canal. es la máquina completa.
{/* PART 2b — 4 layer cards · 2-col zig-zag */}
{layers.map((l, i) => {
const isOdd = i % 2 === 0; // i=0 (01) odd row → copy left, visual right
const copyEl = (
{l.n}
// CAPA {l.n}
{l.title}
{l.desc}
● {l.proof}
{l.tags.map(t => (
{t}
))}
);
// Visual per layer
let visualEl = null;
if (i === 0) {
// 01 CRO — 3 stat tiles with sparklines, staggered
const tiles = [
{ v: '+1.8%', l: 'CVR PROMEDIO', off: 0, pts: '0,18 12,15 24,16 36,11 48,9 60,5 64,3' },
{ v: '−47%', l: 'REBOTE', off: 12, pts: '0,4 12,7 24,6 36,11 48,14 60,18 64,20' },
{ v: '+22s', l: 'TIEMPO EN PÁGINA', off: 0, pts: '0,18 12,16 24,12 36,11 48,7 60,4 64,2' },
];
visualEl = (
{tiles.map((t, ti) => (
{t.v}
{t.l}
{!mobile && (
)}
))}
);
} else if (i === 1) {
// 02 Backend — 3 horizontal pill nodes with traveling dots
const nodes = ['KLAVIYO', 'SMS', 'AUTOMATIZACIONES'];
visualEl = (
{nodes.map((n, ni) => (
{n}
{ni < nodes.length - 1 && (
)}
))}
);
} else if (i === 2) {
// 03 Apps — 3x3 mini grid, 3 activated
const labels = ['', '', '', 'HOTJAR', '', 'REBUY', '', 'KLAVIYO\nRESEÑAS', ''];
const active = new Set([3, 5, 7]);
visualEl = (
{labels.map((label, gi) => {
const on = active.has(gi);
return (
{label}
);
})}
);
} else {
// 04 Paid+AI — radial spoke graphic
const outer = [
{ l: 'META', x: 200, y: 30 },
{ l: 'GOOGLE', x: 290, y: 110 },
{ l: 'TIKTOK', x: 200, y: 190 },
{ l: 'ORGÁNICO', x: 110, y: 110 },
];
visualEl = (
{/* spoke lines */}
{outer.map(o => (
))}
{/* outer nodes */}
{outer.map(o => (
{o.l}
))}
{/* center rotating ring */}
MOTOR DE
ESTRATEGIA
);
}
return (
{isOdd ? (
<>
{copyEl}
{visualEl}
>
) : (
<>
{visualEl}
{copyEl}
>
)}
);
})}
{/* PART 3 — Bottom summary */}
// no tienes que contratar 4 agencias. contratas una.
De tienda cruda a operación funcionando — en una sola pasada.
);
};
// ── AI OPERATION CENTER ─────────────────────────────────────────
// Command-center layout · 8 specialist agents flanking a pulsing core
const HomeAIControl = ({ mobile }) => {
const agentsLeft = [
{ id: '01', name: 'Constructor de Tiendas', task: 'Despliega tiendas Shopify, páginas de producto, páginas de aterrizaje y paquetes.' },
{ id: '02', name: 'Motor Creativo', task: 'Genera anuncios, copy, guiones UGC, ganchos, ángulos y creatividades de marca.' },
{ id: '03', name: 'Operador de Anuncios', task: 'Lanza campañas en Meta y TikTok, escala las ganadoras y pausa las perdedoras.' },
{ id: '04', name: 'Optimizador de Ingresos', task: 'Sube el AOV con upsells, paquetes, ofertas post-compra y tests de precio.' },
];
const agentsRight = [
{ id: '05', name: 'Sistema de Retención', task: 'Crea flujos de email, SMS, recuperación de carrito, recuperación de clientes y lealtad.' },
{ id: '06', name: 'Analista de Rentabilidad', task: 'Mide MER, ROAS, CAC, LTV, margen de contribución y ganancia diaria.' },
{ id: '07', name: 'Pronóstico de Productos', task: 'Predice los SKUs más vendidos, faltantes de inventario y productos a impulsar.' },
{ id: '08', name: 'Escáner de Mercado', task: 'Analiza competidores, tendencias, creadores, reseñas y datos de clientes.' },
];
const bottomBoxes = [
{ title: 'Construye más rápido.', body: 'Lanza tiendas, páginas de producto, páginas de aterrizaje, ofertas, paquetes, campañas, emails y SMS sin esperar a un equipo lento.' },
{ title: 'Gasta con inteligencia.', body: 'Detecta anuncios perdedores antes de que desperdicien presupuesto, escala campañas ganadoras automáticamente y monitorea los números que realmente importan.' },
{ title: 'Opera con precisión.', body: 'Recibe reportes ejecutivos diarios, planes de acción de crecimiento, datos de producto, tendencias de clientes y pronósticos de inventario sin escarbar en paneles.' },
];
const renderAgent = (a, i, side) => (
AGENTE {a.id}
Activo
{a.name}
{a.task}
);
return (
{/* TOP · centered hero · eyebrow + headline + subhead + CTA */}
// INFRAESTRUCTURA DE CRECIMIENTO CON IA · SCALE UP MARKETING
Desarrollamos agentes de IA personalizados para tu negocio —y tú conservas la propiedad de ellos.
Creamos sistemas de IA diseñados específicamente para apoyar tu tienda, anuncios, creatividades, email, SMS, reportes y análisis de datos de clientes.
Cada sistema se construye una vez, se adapta a tu operación y queda como un activo permanente para tu empresa.
Un equipo digital que trabaja de forma continua para ayudarte a operar con más claridad, velocidad y eficiencia.
Reserva una llamada
{/* GRID · 4 left agents · center panel · 4 right agents */}
{agentsLeft.map((a, i) => renderAgent(a, i, 'left'))}
// CENTRO DE MANDO EN VIVO
8 OPERADORES EN LÍNEA
Un centro de mando para el crecimiento.
Tu tienda, anuncios, email, SMS, creadores, reportes, productos y comportamiento de clientes — conectados en un solo sistema inteligente que sabe qué construir, qué probar, qué escalar y qué matar.
Monitoreo de ROAS
MER en vivo
Control de CAC
{agentsRight.map((a, i) => renderAgent(a, i, 'right'))}
{/* STAT STRIP */}
Fuentes Conectadas
Shopify · Meta · Klaviyo · TikTok
{/* BOTTOM · 3 outcome boxes */}
{bottomBoxes.map((b, i) => (
))}
{/* FINAL CTA */}
);
};
// ── CALENDAR ────────────────────────────────────────────────────
const HomeCalendar = ({ mobile }) => {
// §10 ROOM_01 · Calendly automation booking
return (
Reserva
tu reunión.
// mapeamos tu motor en la pizarra
30 minutos. Operador en la llamada. Diagnosticamos, reformulamos, mapeamos tu motor en la pizarra, después te decimos sí o no.
{[['00:00 — 07:30', 'Diagnóstico'], ['07:30 — 17:00', 'Reformular'], ['17:00 — 25:00', 'Mapear'], ['25:00 — 30:00', 'Contratar / no-contratar']].map(([t, p]) => (
{t}
{p}.
))}
{/* GHL · live calendar iframe */}
);
};
// ── CTA · v5 kinetic mask reveal ────────────────────────────────
// Words enter via clip-mask + Y-translate, line-by-line. Triggered once on
// viewport entry. Reduced-motion fallback: full-opacity static.
const HomeCTA = ({ mobile }) => {
const ref = React.useRef(null);
const [played, setPlayed] = React.useState(false);
React.useEffect(() => {
const el = ref.current;
if (!el) return;
const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduced) { setPlayed(true); return; }
const obs = new IntersectionObserver(([e]) => {
if (e.isIntersecting) { setPlayed(true); obs.disconnect(); }
}, { threshold: 0.35 });
obs.observe(el);
return () => obs.disconnect();
}, []);
return (
// FIN · ROOM_01 LA ÚLTIMA PÁGINA ANTES DE LA LLAMADA
Deja de conformarte.
Empieza a sobresalir.
// 30 minutos. Un operador. Diagnosticamos tu motor, después te decimos sí o no.
);
};
window.HomePage = HomePage;