// 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 / MECANISME'],
['engine', '03 · MOTOR'],
['who', '04 / EQUIP'],
['receipts', '05 / REBUTS'],
['diff', '06 / DIFERÈNCIA'],
['process', '08 / PROCÉS'],
['command-center', '09 / CENTRE_DE_COMANDAMENT'],
['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Ó
{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={`Salta 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 = 'CA' }) => {
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 [playing, setPlaying] = React.useState(false);
const [progress, setProgress] = React.useState(0); // 0..1 — drives the bottom rail
const videoRef = React.useRef(null);
const videoSrc = null; // Drop a real path like './vsl.mp4' to enable the player
return (
{/* Top corner labels */}
{!mobile && (
// FIELD_REPORT_001
· {new Date().toISOString().slice(0,10)}
)}
{!mobile && (
)}
{/* CENTERED VERTICAL STACK · clean axial composition */}
{/* THESIS LINE · added above eyebrow */}
// MÚLTIPLES FLUXOS D'INGRÉS — AIXÍ S'ESCALA
{/* Secondary eyebrow */}
// $40M EN INVERSIÓ PUBLICITÀRIA · 10 ANYS · LIDERAT PER OPERADORS
{/* Headline · centered cascade */}
L'equip de creixement 1-a-1 per a
marques de consum de 8 xifres.
{/* Sub-headline */}
// No passem anuncis. Operem tota la màquina — pàgines, backend, automatitzacions, anuncis, agents d'IA — i deixem el sistema dins del teu negoci.
{/* VSL eyebrow */}
▶ MIRA EL PLAYBOOK · 12 MIN
{/* VSL player · centered focal point */}
{videoSrc ? (
setPlaying(false)}
onPlay={() => setPlaying(true)}
onPause={() => setPlaying(false)}
onEnded={() => { setPlaying(false); setProgress(1); }}
onTimeUpdate={(e) => {
const v = e.currentTarget;
if (v.duration && Number.isFinite(v.duration)) setProgress(v.currentTime / v.duration);
}}
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', background: 'transparent', display: 'block' }}
/>
) : null}
{!playing && (
<>
{ if (!videoSrc) return; videoRef.current?.play().catch(() => setPlaying(true)); }}
aria-label="Reprodueix el vídeo del playbook"
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'transparent', border: 'none', cursor: 'pointer', padding: 0 }}
>
>
)}
12:47
{/* Single primary CTA */}
{/* Trust line */}
// sense presentació · sense upsell · operador a la trucada · 30 minuts
);
};
// ── MARQUEE ────────────────────────────────────────────────────
const HomeMarquee = () => (
);
// ── PROBLEM ────────────────────────────────────────────────────
const HomeProblem = ({ mobile }) => (
Pagues per anuncis.
Aterren aquí.
La majoria de les agències de "creixement" només compren més clics. Després llencen trànsit cap a un checkout que trenca cada objecció del llibre — pop-ups, falta de confiança, preus confusos, botons lents, sense proves.
El CAC puja. L'LTV baixa. El teu client mai arriba al botó de comprar.
{[
['→', 'CAC ↑ +38% YoY · perquè la pàgina té fuites'],
['→', 'LTV ↓ −22% · sense backend, compradors d\'una sola vegada'],
['→', 'ROAS 3.2× → 1.4× · mateixa inversió, menys ingressos'],
['→', 'L\'equip se\'n va · sense sistema per heretar'],
].map(([a, t], i) => (
{a}
{t}
))}
{/* 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: '✕ "això és real?"', top: '8%', left: '-4%', rot: -3, c: D4P.danger },
{ txt: '✕ "què fa això realment?"', top: '24%', right: '-6%', rot: 4, c: D4P.warm },
{ txt: '✕ "per què l\'enviament costa més que el producte?"', top: '46%', left: '-8%', rot: -2, c: D4P.danger },
{ txt: '✕ "sense ressenyes · sense proves"', top: '62%', right: '-4%', rot: 3, c: D4P.warm },
{ txt: '✕ "el lloc sembla del 2009"', top: '78%', left: '4%', rot: -4, c: D4P.danger },
{ txt: '✕ "m\'ho pensaré" → no torna mai', top: '92%', right: '8%', rot: 2, c: D4P.danger },
].map((p, i) => (
{p.txt}
))}
{/* Mouse cursor leaving */}
→ pestanya tancada
👋
{/* Caption */}
// 1 de cada 100 compra · 99 van rebotar
// arreglem la pàgina · després comprem els anuncis
);
// ── BACKEND RECOVERY ───────────────────────────────────────────
const HomeBackend = ({ mobile }) => {
const proSystems = [
{ name: 'Klaviyo · Fluxos de Cicle de Vida', value: '+$8,400/mes', on: true },
{ name: 'Upsell Post-Compra', value: '+$5,200/mes', on: true },
{ name: 'Downsell Recuperació de Carret', value: '+$3,600/mes', on: true },
{ name: 'Agent d\'IA · Recuperació CX', value: '+$4,100/mes', on: true },
{ name: 'Abandonament de Navegació', 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 · Fluxos de Cicle de Vida',
'Upsell Post-Compra',
'Downsell Recuperació de Carret',
'Agent d\'IA · Recuperació CX',
'Abandonament de Navegació',
'RFM Win-Back · 60d',
'Replenishment Subscribe-Save',
];
return (
{/* Centered headline + sub */}
La majoria del trànsit se'n va amb la cartera.
Nosaltres ho arreglem.
{/* TWO VISUAL DASHBOARDS — pro vs none — center overlay text */}
{/* ── LEFT · PRO BUSINESS ────────────────────────────────── */}
// NEGOCI PRO
Stack complet.
// 7 sistemes · sincronitzats · 24/7
EN DIRECTE
{/* monthly net */}
// EXTRA/MES GENERAT
▲ +12.4% MoM
+$30,000
// per cada $100K en ingressos · sense gastar més en anuncis
{/* system rows */}
{proSystems.map((s, i) => (
{s.name}
{s.value}
))}
{/* ── RIGHT · NO SYSTEM ──────────────────────────────────── */}
// NOMÉS ANUNCIS
Un canal.
// 0 sistemes · desconnectat · amb fuites
OFFLINE
{/* monthly leak */}
// NET/MES · FUGINT
▼ −12.4% MoM
−$30,000
// per cada $100K en ingressos · se'n van anar · no van tornar mai
{/* missing rows */}
{noneSystems.map((s, i) => (
{s}
APAGAT
))}
{/* CENTER OVERLAY — "WITH OUR SYSTEM" / "NO SYSTEM" */}
{!mobile && (
// AMB EL NOSTRE SISTEMA
+$30K/mes
// SENSE SISTEMA
−$30K/mes
)}
{/* footer line — same store same traffic */}
Mateixa botiga. Mateix trànsit.
$60K/mes de diferència.
);
};
// ── 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} FLUX{activeCount === 1 ? '' : 'OS'} · EN DIRECTE
// fes clic en una boquilla per 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] ? 'fluint — clic per apagar' : 'apagat — clic per reactivar'}
)}
);
};
// ── DIVIDER · MULTI-INCOME ──────────────────────────────────────
const HomeMultiBanner = ({ mobile }) => {
// Each "string" = a backend system that catches lost revenue
const strings = [
{ name: 'KLAVIYO · CICLE DE VIDA', recover: 8400, pct: 8.4 },
{ name: 'UPSELL POST-COMPRA', recover: 5200, pct: 5.2 },
{ name: 'DOWNSELL RECUPERACIÓ DE CARRET', recover: 3600, pct: 3.6 },
{ name: 'AGENT D\'IA · RECUPERACIÓ CX', recover: 4100, pct: 4.1 },
{ name: 'ABANDONAMENT DE NAVEGACIÓ', 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 || '// AXIOM · 002 · DIVERSIFY OR DIE'}
{DIVIDER.line1 || 'Multiple streams'} {DIVIDER.line1Italic || 'of income'}
{DIVIDER.line2 || 'is'} {DIVIDER.line2Italic || 'how you win.'}
{DIVIDER.body || '// every system catches revenue the next one missed · stacked, they compound'}
{/* ──── REVENUE TOWERS · ONE STREAM vs. SEVEN ──── */}
{/* Shared input rail — same traffic feeds both towers */}
{DIVIDER.inputBarLabel || '// SAME INPUT · SAME STORE · SAME AD SPEND'}
{DIVIDER.inputBarTraffic || '$100,000'}{DIVIDER.inputBarSub || '/MO TRAFFIC'}
{/* ──── INCOME FOUNTAIN · Canvas particle system · interactive ──── */}
{/* Verdict strip — locks the message */}
{DIVIDER.verdictTag || '// VERDICT'}
{DIVIDER.verdictLead || 'One stream is a gamble.'}{' '}
{DIVIDER.verdictItalic || 'Seven stacked is an engine.'}
// ${(strings.reduce((a, s) => a + s.recover, 0) * 12).toLocaleString()}/any extra
);
};
// ── ENGINE — two pipelines ──────────────────────────────────────
const HomeEngine = ({ mobile }) => (
Un negoci és ric.
L'altre està sec.
// mateix producte · mateix mercat · mateixa inversió publicitària · cablejat diferent · resultat diferent
{/* Dramatic centerpiece — letters fall in, point to each pipe */}
{/* LEFT — OUR WAY → green/rich */}
// LA NOSTRA MANERA
{Array.from('ric.').map((ch, i) => (
{ch}
))}
→ molts afluents · una base
{/* CENTER — divider arrows · desktop only (mobile arrows pointed at whitespace) */}
{!mobile && (
VS
)}
{mobile && (
VS
)}
{/* RIGHT — YOUR AGENCY → red/dry */}
// LA TEVA AGÈNCIA
{Array.from('sec.').map((ch, i) => (
{ch}
))}
→ un sol tub · hub trencat · amb fuites
{/* RICH */}
// HOME_BASE_A · ELS NOSTRES CLIENTS
{[
{ x: 30, y: 30, label: 'ANUNCIS PAGATS' },
{ x: 30, y: 90, label: 'EMAIL · 5 FLUXOS' },
{ x: 30, y: 150, label: 'SMS' },
{ x: 30, y: 210, label: 'ORGÀNIC' },
{ x: 30, y: 270, label: 'REFERITS' },
{ x: 460, y: 30, label: 'POST-COMPRA', right: true },
{ x: 460, y: 110, label: 'WIN-BACK', right: true },
{ x: 460, y: 220, label: 'IMPULS LTV', right: true },
].map((s, i) => (
{s.label}
))}
BASE
$$$
{[['8', 'fonts'], ['5', 'fluxos backend'], ['+38%', 'LTV']].map(([v, l]) => (
))}
{/* DRY */}
// HOME_BASE_B · LA MAJORIA D'AGÈNCIES
ANUNCIS PAGATS
// única font
{[{ 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) => (
fuita →
))}
{[['1', 'font'], ['0', 'fluxos 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 DIRECTE ]
● ROOM_01 EN DIRECTE_
// REVENUE.SH
// COUNTER.JS · ACTIU
$0
[
GENERAT PER ALS NOSTRES CLIENTS
·
$40M EN INVERSIÓ PUBLICITÀRIA
·
10 ANYS
]
// INGRESSOS REALS · MARQUES REALS · RESULTATS REALS
);
};
// ── MECHANISM — V12 engine ─────────────────────────────────────
const HomeMechanism = ({ mobile }) => (
12 cilindres.
Un motor.
// 12 cilindres disparant · ingressos a cada temps · 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
PARELL 94%
ESTAT · NOMINAL
{/* center reticle */}
{/* firing-order strip */}
// ORDRE D'ENCESA · 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', 'Confiança + Velocitat'], dCol: 1, dRow: 1, mIdx: 0, align: 'left' },
{ n: '02', title: 'EMAIL · SMS', color: D4P.accent, items: ['Automatitzacions', 'Fluxos', 'Campanyes'], dCol: 3, dRow: 1, mIdx: 1, align: 'right' },
{ n: '03', title: 'UPSELLS · IA', color: D4P.warm, items: ['Upsells Post-Compra', 'Agents d\'IA', 'Workflows d\'IA'], dCol: 1, dRow: 3, mIdx: 2, align: 'left' },
{ n: '04', title: 'PAID · ORGÀNIC', color: D4P.cool, items: ['Paid Social', 'Organic Social', 'Contingut + Anuncis'], 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 enllà del teu sostre actual.',
usProof: 'Creixement predictible dissenyat per escalar.',
them: 'Només han gestionat comptes de $50/dia.',
themProof: 'Campanyes de $50/dia · per sempre' },
{ axis: 'CONVERSIÓ',
us: 'Converteix el trànsit en una màquina de diners.',
usProof: 'Més conversió. Més retenció. Més profit.',
them: 'Creuen que els anuncis són el negoci.',
themProof: 'Gastar més · perdre més · repetir' },
{ axis: 'PLATEAU',
us: 'Escapa del plateau dels $100K.',
usProof: 'Sistemes construïts per a creixement seriós.',
them: 'Aprenen eCom a YouTube.',
themProof: 'Veuen cursos · col·leccionen plantilles' },
{ axis: 'MARCA',
us: 'Construeix una marca per la qual la gent s\'obsessioni.',
usProof: 'L\'autoritat genera demanda imparable.',
them: 'No han construït mai una marca real.',
themProof: 'Paret de logos · zero equity' },
{ axis: 'INFRA',
us: 'Escala sense caos.',
usProof: 'Infraestructura real darrere del creixement.',
them: 'Corren amb tabs i bon rotllo.',
themProof: 'Fulls de càlcul · pregàries · pànic' },
{ axis: 'CATEGORIA',
us: 'De marca en creixement a líder de categoria.',
usProof: 'Construït per dominar, no per competir.',
them: 'Persegueixen l\'algoritme, no el mercat.',
themProof: 'Trends avui · oblidades demà' },
];
// 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 · PER QUÈ NOSALTRES
Els rebuts
que la sala exigeix.
{/* Milestone slab — editorial · asymmetric · hero + 2 supporting */}
{/* eyebrow bar */}
// MILESTONE_06 · DES DEL 2015
// JO + ELS MEUS SOCIS · OPERADORS, NO CONSULTORS
{/* asymmetric 3-up — hero $120M+ on the left, supporting stats stacked-equal on the right */}
{/* HERO · $120M+ generated for clients */}
// INGRESSOS DE CLIENTS
$120M+
generats per a clients
{/* live pulse dot */}
{/* SUPPORTING · 10 yrs */}
// TEMPS A LA SALA
10 anys
en eCom · hands-on
{/* SUPPORTING · $40M+ ad spend */}
// INVERSIÓ PUBLICITÀRIA
$40M+
gestionats en portafolis
{/* 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 */}
PER QUÈ NOSALTRES
// scroll · {Math.round(progress * 100)}%
// EIXOS
{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} · NOSALTRES
{String(i + 1).padStart(2, '0')} / {String(total).padStart(2, '0')}
{b.us}
{b.usProof}
);
})}
{/* Floor reflection */}
{/* Bottom legend */}
// SIS EIXOS · PER QUÈ NOSALTRES
Sis eixos. Sis rebuts. Un motor, al registre.
)}
);
};
// ── 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 && (
COMPRA JA
)}
{/* 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', 'FLOWS'].map(p => (
{p}
))}
)}
{/* ad campaign badges — stage 4 */}
{stage >= 4 && (
CAMPANYES
{['M', 'G', 'T'].map(c => (
{c}
))}
)}
{/* stage label */}
ETAPA {stage}
);
};
const stageCaptions = ['cru', '+ CRO', '+ backend', '+ apps', '+ anuncis'];
// ── 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: 'FLOWS',labelColor: `${D4P.accent}AA` },
D: { stroke: D4P.accent, strokeDasharray: '0', fill: 'rgba(197,216,109,0.12)', label: 'ADS', 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': 'HEATMAPS', '1-1': 'UPSELL',
// Quadrant A — top-right
'2-0': 'REBOT', '3-0': 'FUITA',
'2-1': 'CHURN', '3-1': 'STALL',
// Quadrant C — bottom-left
'0-2': 'KLAVIYO', '1-2': 'SMS',
'0-3': 'FLUXOS', '1-3': 'EMAIL',
// Quadrant B — bottom-right
'2-2': 'PÀGINES', '3-2': 'VELOCITAT',
'2-3': 'CHECKOUT', '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: 'Conversion Rate Optimization',
desc: 'La teva botiga ha de convertir de veritat. Reconstruïm el que està matant el teu CVR — velocitat de pàgina, layout, copy, checkout.',
proof: '+1.8% INCREMENT CVR MITJÀ',
tags: ['pàgines', 'velocitat', 'checkout'],
},
{
n: '02', title: 'Sistemes de Backend',
desc: 'Email, SMS, Klaviyo, cada automatització que extreu ingressos del trànsit pel qual ja vas pagar.',
proof: 'SUPERA EL 40% DE LES AGÈNCIES',
tags: ['klaviyo', 'sms', 'fluxos'],
},
{
n: '03', title: 'Apps + Optimització',
desc: 'Upsells, downsells, mapes de calor, les eines de decisió que componen cada visita.',
proof: '+22% AOV MITJÀ',
tags: ['upsells', 'heatmaps', 'apps'],
},
{
n: '04', title: 'Paid + Orgànic + IA',
desc: 'Meta, Google, TikTok, orgànic, i els agents d\'IA que llegeixen les dades i decideixen el següent moviment.',
proof: '$40M EN INVERSIÓ PUBLICITÀRIA RASTREJADA',
tags: ['meta', 'google', 'tiktok'],
},
];
return (
{/* PART 1 — Header strip */}
// L'STACK DE 4 CAPES · EL QUE INTEGREM AL TEU NEGOCI
Totes les àrees del negoci
maximitzades i totalment funcionant.
// botiga crua → conversió → backend → expansió → demanda · el mateix playbook a cada compte
{/* 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 NEGOCI DE 16 PECES · ACOBLAT · SEMPRE ACOBLANT
{/* 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 */}
ÈXIT
{/* 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
? {'✓'} desplegat
: {'✗'} resolt }
);
})}
SISTEMA ACOBLAT.
ÈXIT.
)}
{/* loose floating pieces — still being assembled */}
);
})()}
{/* PART 1c — The argument */}
// L'ARGUMENT
L'única manera de guanyar és{' '}
construir un negoci real.
// no és una campanya. no és un hack. no és un sol canal. és 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 MITJÀ', off: 0, pts: '0,18 12,15 24,16 36,11 48,9 60,5 64,3' },
{ v: '−47%', l: 'REBOT', off: 12, pts: '0,4 12,7 24,6 36,11 48,14 60,18 64,20' },
{ v: '+22s', l: 'TEMPS A LA 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', 'AUTOMATITZACIONS'];
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\nRESSENYES', ''];
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ÀNIC', x: 110, y: 110 },
];
visualEl = (
{/* spoke lines */}
{outer.map(o => (
))}
{/* outer nodes */}
{outer.map(o => (
{o.l}
))}
{/* center rotating ring */}
MOTOR
D'ESTRATÈGIA
);
}
return (
{isOdd ? (
<>
{copyEl}
{visualEl}
>
) : (
<>
{visualEl}
{copyEl}
>
)}
);
})}
{/* PART 3 — Bottom summary */}
// no has de contractar 4 agències. en contractes una.
De botiga crua a operació funcionant — en una sola passada.
);
};
// ── AI OPERATION CENTER ─────────────────────────────────────────
// Command-center layout · 8 specialist agents flanking a pulsing core
const HomeAIControl = ({ mobile }) => {
const agentsLeft = [
{ id: '01', name: 'Constructor de Stores', task: 'Desplega botigues Shopify, pàgines de producte, landing pages i bundles.' },
{ id: '02', name: 'Motor Creatiu', task: 'Genera anuncis, copy, guions UGC, hooks, angles i creativitats de marca.' },
{ id: '03', name: 'Operador d\'Anuncis', task: 'Llança campanyes a Meta i TikTok, escala les guanyadores, pausa les perdedores.' },
{ id: '04', name: 'Optimitzador d\'Ingressos', task: 'Millora l\'AOV amb upsells, bundles, ofertes post-compra i tests de preu.' },
];
const agentsRight = [
{ id: '05', name: 'Sistema de Retenció', task: 'Crea fluxos d\'email, SMS, recuperació de carret, winback, churn i fidelitat.' },
{ id: '06', name: 'Analista de Rendibilitat', task: 'Mesura MER, ROAS, CAC, LTV, marge de contribució i profit diari.' },
{ id: '07', name: 'Pronòstic de Productes', task: 'Prediu els SKUs més venuts, manca d\'inventari i productes a impulsar.' },
{ id: '08', name: 'Escàner de Mercat', task: 'Analitza competidors, tendències, creadors, ressenyes i insights de clients.' },
];
const bottomBoxes = [
{ title: 'Construeix més ràpid.', body: 'Llança botigues, pàgines de producte, landing pages, ofertes, bundles, campanyes, emails i SMS sense esperar un equip lent.' },
{ title: 'Gasta amb intel·ligència.', body: 'Detecta anuncis perdedors abans que malgastin pressupost, escala campanyes guanyadores automàticament i monitora els números que realment importen.' },
{ title: 'Opera amb precisió.', body: 'Rep reports executius diaris, plans d\'acció de creixement, insights de producte, tendències de clients i pronòstics d\'inventari sense regirar dashboards.' },
];
const renderAgent = (a, i, side) => (
AGENT {a.id}
Actiu
{a.name}
{a.task}
);
return (
{/* TOP · centered hero · eyebrow + headline + subhead + CTA */}
// Infraestructura de Creixement amb IA · Scale Up Marketing
Desplegem agents d'IA reals per a tu —i els deixem amb tu.
Sistemes d'IA a mida per a la teva botiga, anuncis, creativitats, email, SMS, reports i dades de clients — construïts una vegada, teus per sempre. Un equip complet que es torna més afilat cada dia.
Reserva una Trucada ▸
{/* GRID · 4 left agents · center panel · 4 right agents */}
{agentsLeft.map((a, i) => renderAgent(a, i, 'left'))}
// CENTRE DE COMANDAMENT EN DIRECTE
8 OPERADORS EN LÍNIA
Un centre de comandament per al creixement.
La teva botiga, anuncis, email, SMS, creadors, reports, productes i comportament de clients connectats en un sistema intel·ligent que sap què construir, què provar, què escalar i què matar.
Monitoratge de ROAS
MER en Directe
Control de CAC
{agentsRight.map((a, i) => renderAgent(a, i, 'right'))}
{/* STAT STRIP */}
Fonts Connectades
Shopify · Meta · Klaviyo · TikTok
{/* BOTTOM · 3 outcome boxes */}
{bottomBoxes.map((b, i) => (
))}
{/* FINAL CTA */}
);
};
// ── CALENDAR ────────────────────────────────────────────────────
const HomeCalendar = ({ mobile }) => {
// §10 ROOM_01 · live GHL Sales Call calendar (KJr0zawfZO9vDNibCQO2)
return (
Reserva
la teva reunió.
// mapegem el teu motor a la pissarra
30 minuts. Operador a la trucada. Diagnostiquem, reformulem, mapegem el teu motor a la pissarra, després et diem sí o no.
{[['00:00 — 07:30', 'Diagnòstic'], ['07:30 — 17:00', 'Reformular'], ['17:00 — 25:00', 'Mapejar'], ['25:00 — 30:00', 'Contractar / no-contractar']].map(([t, p]) => (
{t}
{p}.
))}
{/* GHL · live calendar iframe */}
// ROOM_01.CAL · EN DIRECTE
);
};
// ── 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 (
// FI · ROOM_01 L'ÚLTIMA PÀGINA ABANS DE LA TRUCADA
Deixa de conformar-te.
Comença a destacar.
// 30 minuts. Un operador. Diagnostiquem el teu motor, després et diem sí o no.
);
};
window.HomePage = HomePage;