// 9:16 portrait composition — 1080x1920, all 5 acts redesigned for vertical // Reuses existing Sprite/CueLayer infra. Width 1080, height 1920. // ── Act 1 — Problema ──────────────────────────────────────────────────────── function Act1ProblemV() { return ( ); } function Act1VInner() { const { localTime: t } = useSprite(); const bars = Array.from({ length: 32 }, (_, i) => 8 + Math.abs(Math.sin(i * 0.4 + t * 4)) * 80 + Math.abs(Math.sin(i * 1.7 + t * 3)) * 50); const pulse = 0.7 + Math.sin(t * 6) * 0.3; const chatLines = [ { u: 'gabriela_91', m: '🔥🔥🔥', c: '#F59E0B' }, { u: 'el_madmax', m: 'NO J*DAS', c: '#EA580C' }, { u: 'streamfan', m: 'guarda eso pa shorts', c: '#9061f9' }, { u: 'kevin_g', m: 'clip clip clip', c: '#EA580C' }, { u: 'lia_88', m: 'a tiktok ya', c: '#F59E0B' }, ]; const visibleChat = Math.min(5, Math.floor(t * 2.4)); // Captions alineadas con Act1 (3 captions, 5s total) const captions = [ { t: 0.0, end: 1.8, text: '3 horas de stream.' }, { t: 1.9, end: 3.4, text: '0 clips publicados.' }, { t: 3.5, end: 5.0, text: 'La competencia publica a diario.' }, ]; return (
{/* Stream panel — dentro de safe-zone TikTok/Reels (top 220 reservado) */}
EN VIVO
2.847 espectadores
{bars.map((h, i) => { const fade = Math.max(0.2, 1 - Math.abs(i - 16) / 16); return
; })}
{t > 3 && t < 6 && (
● momento detectado
)}
Título del directo
Probando el nuevo update · gameplay caótico
{/* Chat panel — below stream, fuera de bottom safe-zone (400 reservados) */}
Chat en directo
{chatLines.slice(0, visibleChat).map((c, i) => (
{c.u} {c.m}
))}
{/* Kinetic captions — bottom */} {captions.map((c, i) => { if (t < c.t || t > c.end) return null; const local = t - c.t, dur = c.end - c.t; let opacity = 1, ty = 0, scale = 1; if (local < 0.3) { const k = local/0.3; opacity = k; ty = (1-k)*40; scale = 0.9 + k*0.1; } else if (local > dur - 0.3) { const k = (dur-local)/0.3; opacity = k; } return (
{c.text}
); })}
); } // ── Act 2 — Solución ──────────────────────────────────────────────────────── function Act2SolutionV() { return ; } function Act2VInner() { const { localTime: t } = useSprite(); // 15s total, idéntico mapeo a Act2 desktop return (
{t < 1.5 && } {t >= 1.5 && t < 4.5 && } {t >= 4.5 && t < 7.5 && } {t >= 7.5 && t < 11.0 && } {t >= 11.0 && t < 15.0 && }
); } function BoltSvgV({ size }) { return ; } function LogoRevealV({ t }) { // 1.5s logo reveal, slogan reservado para Act 5 if (t >= 1.5) return null; const fadeIn = Easing.easeOutCubic(clamp(t / 0.4, 0, 1)); const fadeOut = t > 1.1 ? Easing.easeInCubic(clamp((1.5 - t) / 0.4, 0, 1)) : 1; const opacity = Math.min(fadeIn, fadeOut); const scaleK = Easing.easeOutBack(clamp(t / 0.55, 0, 1)); const scale = 0.6 + scaleK * 0.4; return (
Chispa
); } function PhaseURLV({ t }) { // 3s phase, 1 URL typed + 1 instant (paridad con desktop) const opacity = t < 0.3 ? Easing.easeOutCubic(t / 0.3) : t > 2.6 ? Easing.easeInCubic(clamp((3.0 - t) / 0.4, 0, 1)) : 1; const url1 = 'twitch.tv/streamer/v/2847391'; const url2 = 'youtube.com/watch?v=Hk9oG7tT4nQ'; const typed1 = t < 1.4 ? url1.slice(0, Math.min(url1.length, Math.floor(t * 26))) : url1; const url1Active = t < 1.4; const url1Done = t >= 1.4; const url2In = clamp((t - 1.4) / 0.35, 0, 1); const url2Eased = Easing.easeOutBack(url2In); const url2Done = t >= 1.6; const showCursor = Math.floor(t * 4) % 2 === 0; const buttonProgress = Easing.easeOutCubic(clamp((t - 1.8) * 4, 0, 1)); let pressed = t >= 2.35; return (
paso 1 · directo o vídeo
{/* In-product CTA — neutral, no compite con Act 5 */}
Detectar la chispa →
); } function URLRowV({ label, labelColor, typed, active, done, cursor, kind }) { const borderColor = active ? '#EA580C' : done ? 'rgba(34,197,94,0.5)' : 'rgba(255,255,255,0.08)'; const glow = active ? '0 0 40px rgba(234,88,12,0.3)' : 'none'; const icon = kind === 'twitch' ? : ; return (
{label}
{icon}
{typed}{active && cursor && } {done && ( )}
); } function PhaseAnalysisV({ t }) { // 3s phase const opacity = t < 0.3 ? Easing.easeOutCubic(t / 0.3) : t > 2.5 ? Easing.easeInCubic(clamp((3.0 - t) / 0.5, 0, 1)) : 1; const bars = Array.from({ length: 36 }, (_, i) => 10 + Math.abs(Math.sin(i*0.4 + t*3))*60 + Math.abs(Math.sin(i*1.3 + t*2))*40); const markers = [ { pos: 8, label: 'grito', appear: 0.6 }, { pos: 16, label: 'reacción', appear: 1.2 }, { pos: 24, label: 'giro', appear: 1.8 }, { pos: 30, label: 'climax', appear: 2.4 }, ]; return (
paso 2 · IA analiza
{bars.map((h, i) => { const isNear = markers.some(m => Math.abs(m.pos - i) < 2 && t > m.appear); return
; })}
{markers.map((m, i) => { if (t < m.appear) return null; return (
{m.label}
); })}
analizando · 03:24:18
● {markers.filter(m => t > m.appear).length} momentos
); } function PhaseClipV({ t }) { // 3.5s phase const opacity = t < 0.3 ? Easing.easeOutCubic(t / 0.3) : t > 3.0 ? Easing.easeInCubic(clamp((3.5 - t) / 0.5, 0, 1)) : 1; const subProgress = Math.min(1, Math.max(0, (t - 0.5) / 0.5)); const cap = Math.min(2, Math.floor(Math.max(0, t - 0.6) / 0.55)); const captions = ['¡NO J*DAS!', 'NO ME LO CREO', 'ESTO ES BRUTAL']; return (
paso 3 · clip vertical listo
9:16 · 28s
renderizado
{subProgress > 0 && (
{captions[cap]}
)}
{[0, 1, 2].map(i => (
))}
); } function ClipFeatureV({ delay, t, icon, label }) { const local = t - delay; const slide = Math.max(0, Math.min(1, local * 4)); const ease = 1 - Math.pow(1-slide, 3); return (
{icon}
{label}
); } function PhasePublishV({ t }) { // 4s phase (Adapt+Publish fusionado para paridad con desktop) const opacity = t < 0.3 ? Easing.easeOutCubic(t / 0.3) : t > 3.5 ? Easing.easeInCubic(clamp((4.0 - t) / 0.5, 0, 1)) : 1; const headlineProgress = Math.max(0, Math.min(1, t / 0.5)); const logos = [ { name: 'YouTube', color: '#FF0033', delay: 0.0 }, { name: 'TikTok', color: '#fff', delay: 0.18 }, { name: 'Instagram', color: '#E1306C', delay: 0.36 }, ]; const showTs = t > 1.0; return (
paso 4 · adapta + publica
publica
mientras
duermes.
{showTs && (
· 03:47 a.m. ·
)}
{/* Subtítulo: cada red, su versión */}
Cada clip optimizado para su red.
{logos.map((l, i) => { const local = t - l.delay; const ease = Easing.easeOutBack(clamp(local * 3, 0, 1)); const done = t > l.delay + 0.6; let icon; if (l.name === 'YouTube') { icon = ; } else if (l.name === 'TikTok') { icon = ; } else { icon = ( ); } return (
{icon} {done && (
)}
{l.name}
); })}
); } // ── Act 3 — IA aprende ────────────────────────────────────────────────────── function Act3AIV() { return ; } function Act3VInner() { const { localTime: t } = useSprite(); return (
{/* Multi-idioma chip — 2s breve, no persistente */} {t < 2.0 && (
1.7 ? Easing.easeInCubic(clamp((2.0 - t) / 0.3, 0, 1)) : 1 }}> 🌐 español · català · english
)} {t < 1.5 &&
1.0 ? Easing.easeInCubic(clamp((1.4-t)/0.4, 0, 1)) : 1 }}>
diferenciador · IA personal
} {t >= 1.0 && t < 4.5 && } {t >= 4.5 && t < 7.0 && } {t >= 7.0 && t < 12.0 && }
); } function LearningCurveV({ t }) { const opacity = t < 0.3 ? t/0.3 : t > 4.0 ? Math.max(0, (4.5-t)/0.5) : 1; const points = 20; // Faster: complete by t=2.0 const drawProgress = Math.min(1, t / 2.0); const xs = Array.from({ length: points }, (_, i) => 60 + (i/(points-1))*840); const ys = Array.from({ length: points }, (_, i) => { const x = i/(points-1); return 320 - Math.pow(x,0.5)*240 + Math.sin(i*1.7)*10; }); // Smooth continuous draw with partial last segment const totalSeg = points - 1; const segF = drawProgress * totalSeg; const fullSeg = Math.floor(segF); const frac = segF - fullSeg; let path = ''; let headX = xs[0], headY = ys[0]; if (fullSeg < 1 && frac > 0) { headX = xs[0] + (xs[1]-xs[0])*frac; headY = ys[0] + (ys[1]-ys[0])*frac; path = `M${xs[0]} ${ys[0]} L${headX} ${headY}`; } else { for (let i = 0; i <= fullSeg; i++) { path += (i===0?'M':'L') + xs[i] + ' ' + ys[i] + ' '; headX = xs[i]; headY = ys[i]; } if (fullSeg < totalSeg && frac > 0) { const nx = xs[fullSeg] + (xs[fullSeg+1]-xs[fullSeg])*frac; const ny = ys[fullSeg] + (ys[fullSeg+1]-ys[fullSeg])*frac; path += `L${nx} ${ny}`; headX = nx; headY = ny; } } // Sin claims numéricos: solo curva visual relativa return (
Curva de aprendizaje
Cada clip que publicas,
la IA mejora.
{[0,1,2,3,4].map(i => )} {drawProgress > 0.02 && } {drawProgress > 0.02 && } engagement primer clip tras meses
); } function ABSubsV({ t }) { const opacity = t < 0.3 ? t/0.3 : t > 5.0 ? Math.max(0, (5.5-t)/0.5) : 1; // Barras relativas (sin cifras absolutas). const b1 = Easing.easeOutCubic(clamp(t/1.5, 0, 1)) * 0.32; const b2 = Easing.easeOutCubic(clamp(t/2.0, 0, 1)) * 0.92; const winnerVisible = t > 2.5; return (
🔥 Estrategia dual YouTube + TikTok
); } function ABCardV({ label, sub, barW, winner, bg, cleanClip }) { return (
{Array.from({ length: 14 }).map((_, i) =>
)}
{!cleanClip ? (
¿QUÉ PASÓ?!
) : (
que pasó después...
)}
{label}
{winner &&
WINNER
}
{/* Barra relativa de engagement — sin claims numéricos */}
{sub}
); } function HookABV({ t }) { const opacity = t < 0.3 ? t/0.3 : t > 4.0 ? Math.max(0, (4.5-t)/0.5) : 1; const showV2 = t > 1.0; const showResult = t > 2.5; return (
hooks · qué titular gana
{showV2 && } {showResult &&
💡 Y lo replica automáticamente.
}
); } function HookRowV({ version, text, barW, winner }) { return (
{version}
"{text}"
{winner ? '✅' : '❌'}
); } // ── Act 4 — Resultados ────────────────────────────────────────────────────── function Act4ResultsV() { return ; } function Act4VInner() { const { localTime: t } = useSprite(); return (
9 ? Easing.easeInCubic(clamp((9.5-t)/0.5, 0, 1)) : 1 }}>
tres formas de usar Chispa
Un mismo motor IA. Tres formatos.
{t >= 1.4 && t < 9.5 && } {/* Act 4: no cue layer — visuals carry the message */}
); } function BigStatsV({ t }) { const opacity = t < 0.3 ? t/0.3 : t > 4.5 ? Math.max(0, (5.0-t)/0.5) : 1; const easeT = Math.min(1, t/2.5); const easeO = 1 - Math.pow(1-easeT, 3); const clips = (23.4*easeO).toFixed(1); const views = (1.2*easeO).toFixed(1); const tikTok = (4.7*easeO).toFixed(1); return (
clips · 7 días
{clips}M
generados desde directos
); } function TickerCardV({ label, value, suffix, delta, highlight }) { return (
{label}
{value}{suffix}
↑ {delta}
); } function UseCasesV({ t }) { const opacity = t < 0.3 ? Easing.easeOutCubic(t / 0.3) : t > 8.5 ? Easing.easeInCubic(clamp((9.0 - t) / 0.5, 0, 1)) : 1; const cases = [ { tag: '01', label: 'Clips personalizados', meta: 'el día a día', desc: 'IA entrenada en TU canal. Gaming, deportes, podcast, comedy, lifestyle.', delay: 0.2 }, { tag: '02', label: 'Recortar bruto', meta: 'montaje · sin silencios', desc: 'Elimina silencios, voz de producción y tomas falladas. Pieza lista para entregar.', delay: 0.7 }, { tag: '03', label: 'Highlights del directo', meta: '3 h → 5 min', desc: 'Detecta los picos de un stream o podcast y los condensa sin perder el hilo.', delay: 1.2, highlight: true }, ]; return (
{cases.map((c, i) => { const local = t - c.delay; const slide = Easing.easeOutCubic(clamp(local * 2.5, 0, 1)); return (
{c.tag}
{c.meta}
{c.label}
{c.desc}
); })}
); } // ── Act 5 — CTA ───────────────────────────────────────────────────────────── function Act5CTAV() { return ; } function Act5VInner() { const { localTime: t } = useSprite(); const pulseScale = 1 + Math.sin(t*2)*0.02; return (
{/* Logo top=260 (fuera de top safe-zone 220px) */} {t < 11 && (
Chispa
)} {/* Headline */} {t > 0.4 && t < 11 && (
Genera tu primera chispa.
5 clips gratis. Sin tarjeta.
)} {/* Pricing (PricingColV usa top=800) */} {t >= 1.5 && t < 11 && } {/* Closing tagline + compliance, fuera de bottom safe-zone 400px */} {t >= 5.5 && t < 11 && (
Tu IA, entrenada con tu contenido.
RGPD · ENS-BÁSICA · OWASP ASVS L2
)} {/* CTA button — bottom=460 (justo por encima del bottom safe-zone) */} {t >= 3.0 && t < 11 && } {/* Domain — bottom=240 (dentro de bottom safe-zone donde TikTok superpone description: lo subimos arriba) */} {/* Eliminado: el wordmark Chispa de top ya transmite la marca. */}
); } function PricingColV({ t }) { const tiers = [ { name: 'Free', price: '€0', clips: '5 clips/mes', delay: 0.0, accent: 'rgba(255,255,255,0.5)' }, { name: 'Creator', price: '€29', clips: '50 clips · YT + TT + IG', delay: 0.2, accent: '#F59E0B' }, { name: 'Pro', price: '€79', clips: '200 clips · IA entrenada', delay: 0.4, accent: '#9061f9', highlight: true }, { name: 'Studio', price: '€199', clips: 'ilimitado · editorial', delay: 0.6, accent: '#fff' }, ]; const proPulse = 0.5 + Math.sin(t*3)*0.5; return (
{tiers.map((tier, i) => { const local = t - tier.delay; if (local < 0) return null; const slide = Math.min(1, local*3); const ease = 1 - Math.pow(1-slide, 3); return (
{tier.name}
{tier.clips}
{tier.price}
/mes
); })}
); } function CTAButtonV({ t }) { const opacity = Math.min(1, t/0.4); const pulse = 1 + Math.sin(t*4)*0.015; const glow = 0.5 + Math.sin(t*4)*0.5; return (
Empezar gratis →
); } window.Act1ProblemV = Act1ProblemV; window.Act2SolutionV = Act2SolutionV; window.Act3AIV = Act3AIV; window.Act4ResultsV = Act4ResultsV; // ── Act 3 PRO+ phase (vertical) ───────────────────────────────────────────── function TrainYourAIV({ t }) { // 5s phase (paridad con desktop) const opacity = t < 0.4 ? Easing.easeOutCubic(t / 0.4) : t > 4.3 ? Easing.easeInCubic(clamp((4.8 - t) / 0.5, 0, 1)) : 1; const proPulse = 0.85 + Easing.easeInOutSine((Math.sin(t * 3) + 1) / 2) * 0.15; const traits = [ { icon: '🎙️', label: 'Tu voz', desc: 'humor, ritmo, muletillas' }, { icon: '👥', label: 'Tu audiencia', desc: 'qué responden tus fans' }, { icon: '🚀', label: 'Lo que triunfa', desc: 'replica los hooks que ganan' }, ]; return (
exclusivo plan PRO+
Una IA que conoce tu canal.
Entrenada con tus vídeos, tus clips ganadores y la reacción real de tu audiencia.
{traits.map((tr, i) => { const delay = 0.3 + i*0.16; const local = t - delay; const slide = Math.max(0, Math.min(1, local*3)); const ease = 1 - Math.pow(1-slide, 3); return (
{tr.icon}
{tr.label}
{tr.desc}
); })}
); } window.Act5CTAV = Act5CTAV;