// KarlaPay — Scenes
// Brand: #FF6A1A orange, #0D0F14 bg, Satoshi + Caveat
// Arc (30s): Logo build → Problem → Carla (orb/waveform) → Result → Tagline/CTA
const BG = '#0D0F14';
const ORANGE = '#FF6A1A';
const INK = '#FFFFFF';
const MUTED = '#6B7280';
const PANEL = '#14171F';
const LINE = '#2A2F3A';
const SATOSHI = '"Satoshi", "Inter", system-ui, sans-serif';
const CAVEAT = '"Caveat", "Brush Script MT", cursive';
const MONO = '"JetBrains Mono", ui-monospace, monospace';
// ─────────────────────────────────────────────────────────────────────────────
// Ambient background — subtle radial orange glow + grid
function AmbientBG() {
const t = useTime();
const pulse = 0.7 + 0.3 * Math.sin(t * 0.8);
return (
{/* Orange glow */}
{/* Faint grid */}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// SCENE 1 — Logo build (0.0 → 5.0s)
// "Karla" writes on in Caveat, then "pay" joins with a fade, tagline follows.
function SceneLogo() {
const { localTime, progress } = useSprite();
// Handwriting reveal: clip-path sweeps left→right over Caveat "Karla"
const writeDur = 1.9;
const writeT = clamp(localTime / writeDur, 0, 1);
const writeEased = Easing.easeInOutCubic(writeT);
// "pay" appears at 1.8s
const payStart = 1.9;
const payT = clamp((localTime - payStart) / 0.6, 0, 1);
const payEased = Easing.easeOutCubic(payT);
// Tagline at 2.7s
const tagStart = 2.7;
const tagT = clamp((localTime - tagStart) / 0.7, 0, 1);
const tagEased = Easing.easeOutCubic(tagT);
// Exit at end
const exitStart = 4.3;
const exitT = clamp((localTime - exitStart) / 0.7, 0, 1);
const exitEased = Easing.easeInCubic(exitT);
const exitOpacity = 1 - exitT;
const exitScale = 1 - exitEased * 0.04;
return (
{/* Wordmark */}
{/* "Karla" in Caveat — clipped reveal */}
Karla
{/* "pay" in Satoshi */}
pay
{/* Tagline */}
Tu agente de IA que llama por ti para cobrar.
);
}
// ─────────────────────────────────────────────────────────────────────────────
// SCENE 2 — Problem (5.0 → 10.5s)
// Pile of "IMPAGO" (unpaid) invoice cards fall onto screen, red warning accent.
function InvoiceCard({ delay, x, y, rot, amount, client, status = 'IMPAGO' }) {
const { localTime } = useSprite();
const t = clamp((localTime - delay) / 0.5, 0, 1);
const eased = Easing.easeOutCubic(t);
const opacity = t;
const dy = (1 - eased) * -40;
const scale = 0.9 + eased * 0.1;
const isOverdue = status === 'IMPAGO';
return (
FAC-{Math.floor(1000 + Math.random() * 9000)}
{status}
);
}
function SceneProblem() {
const { localTime, duration } = useSprite();
// Title in first
const titleT = clamp(localTime / 0.6, 0, 1);
const titleEased = Easing.easeOutCubic(titleT);
// Exit
const exitStart = duration - 0.7;
const exitT = clamp((localTime - exitStart) / 0.7, 0, 1);
const exitOpacity = 1 - Easing.easeInCubic(exitT);
const cards = [
{ delay: 0.6, x: 180, y: 220, rot: -8, amount: '1.250', client: 'Clínica Dental Sonríe' },
{ delay: 0.85, x: 500, y: 180, rot: 3, amount: '840', client: 'Autoescuela Velocidad' },
{ delay: 1.1, x: 850, y: 240, rot: -4, amount: '2.100', client: 'Gimnasio Fuerza' },
{ delay: 1.35, x: 1200, y: 200, rot: 6, amount: '560', client: 'Peluquería Bella' },
{ delay: 1.6, x: 320, y: 450, rot: 5, amount: '1.780', client: 'Academia Idiomas' },
{ delay: 1.85, x: 680, y: 490, rot: -6, amount: '3.400', client: 'Consultoría Nova' },
{ delay: 2.1, x: 1040, y: 470, rot: 2, amount: '920', client: 'Restaurante Mar' },
];
return (
{/* Top label */}
EL PROBLEMA
Las facturas no se pagan solas.
{cards.map((c, i) =>
)}
{/* Counter at bottom */}
);
}
function SceneProblemCounter({ localTime }) {
const t = clamp((localTime - 2.3) / 1.4, 0, 1);
const eased = Easing.easeOutCubic(t);
const value = Math.floor(eased * 10850);
const formatted = value.toLocaleString('es-ES');
const appearT = clamp((localTime - 2.3) / 0.5, 0, 1);
return (
IMPAGADO ESTE MES
€ {formatted}
);
}
Object.assign(window, {
BG, ORANGE, INK, MUTED, PANEL, LINE,
SATOSHI, CAVEAT, MONO,
AmbientBG, SceneLogo, SceneProblem,
});