// KarlaPay — Scenes part 2: Carla call + Result + Tagline
// ─────────────────────────────────────────────────────────────────────────────
// SCENE 3 — Carla calls (10.5 → 19.0s)
// Abstract orange orb pulses while a voice waveform animates; live transcript.
function Waveform({ localTime, width = 520, height = 120, bars = 48, active = true }) {
const barW = 4;
const gap = (width - bars * barW) / (bars - 1);
return (
{Array.from({ length: bars }).map((_, i) => {
// Organic waveform using layered sines
const phase = i * 0.42;
const envelope = Math.sin((i / bars) * Math.PI); // peak in middle
const base = 0.15;
const variance = active
? (0.5 + 0.5 * Math.sin(localTime * 6 + phase)) *
(0.6 + 0.4 * Math.sin(localTime * 2.3 + phase * 0.5))
: 0.1;
const h = (base + variance * envelope * 0.85) * height;
const color = ORANGE;
return (
);
})}
);
}
function CarlaOrb({ localTime, size = 280 }) {
const pulse1 = 1 + 0.08 * Math.sin(localTime * 2.2);
const pulse2 = 1 + 0.12 * Math.sin(localTime * 3.1 + 1.2);
const pulse3 = 1 + 0.06 * Math.sin(localTime * 1.5 + 2.4);
return (
{/* Outer rings */}
{/* Glow */}
{/* Core */}
{/* Headset suggestion - thin arc */}
);
}
function TranscriptLine({ speaker, text, visible, enter, color }) {
const opacity = clamp(enter, 0, 1);
const dy = (1 - opacity) * 12;
return (
);
}
function SceneCarla() {
const { localTime, duration } = useSprite();
// Phone ring establishing (0 → 1.2s)
const ringT = clamp(localTime / 1.0, 0, 1);
const ringEased = Easing.easeOutCubic(ringT);
// Orb zooms in (1.0s)
const orbAppearT = clamp((localTime - 0.6) / 0.9, 0, 1);
const orbEased = Easing.easeOutBack(orbAppearT);
// Connection label
const connectStart = 0.3;
const connectT = clamp((localTime - connectStart) / 0.5, 0, 1);
// Transcript timings — after connection, lines appear
const transcriptStart = 1.8;
const lines = [
{ at: 0.0, speaker: 'CARLA', color: ORANGE, text: '"Hola Mario, soy Carla de idme SL."' },
{ at: 1.8, speaker: 'MARIO', color: MUTED, text: '"Sí, dígame."' },
{ at: 3.0, speaker: 'CARLA', color: ORANGE, text: '"Le llamo por su factura de € 1.250, con vencimiento el 3 de mayo."' },
{ at: 5.0, speaker: 'MARIO', color: MUTED, text: '"Ah, sí… ¿puedo pagar ahora por Bizum?"' },
];
// Which line is "active" (driving waveform)
const activeSpeaker = (() => {
const t = localTime - transcriptStart;
let current = 'CARLA';
for (const l of lines) {
if (t >= l.at) current = l.speaker;
}
return current;
})();
// Exit
const exitStart = duration - 0.7;
const exitT = clamp((localTime - exitStart) / 0.7, 0, 1);
const exitOpacity = 1 - Easing.easeInCubic(exitT);
return (
{/* Top bar: call status */}
LLAMADA EN CURSO
·
{formatCallTime(Math.max(0, localTime - 0.3))}
{/* Left: Orb */}
{/* Center-right: Transcript + waveform */}
{/* Waveform at top of transcript area */}
{/* Lines */}
{lines.map((l, i) => {
const lineLocal = localTime - transcriptStart - l.at;
const enter = clamp(lineLocal / 0.5, 0, 1);
const visible = lineLocal >= 0;
return (
);
})}
);
}
function formatCallTime(t) {
const m = Math.floor(t / 60);
const s = Math.floor(t % 60);
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
}
Object.assign(window, {
Waveform, CarlaOrb, TranscriptLine, SceneCarla, formatCallTime,
});