import { AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig } from "remotion"; import { FPS, type Scene } from "../schema"; import { theme } from "../theme"; type Word = { text: string; startMs: number; endMs: number }; /** Kinetic captions: the active word glows in the accent color. Frame 0 = start of the narration. */ export function Captions({ words, accent, fontSize = 60, bottom = 260, }: { words: Word[]; accent: string; fontSize?: number; bottom?: number; }) { const frame = useCurrentFrame(); const ms = (frame / FPS) * 1000; const idx = words.findIndex((w) => ms >= w.startMs && ms < w.endMs); if (idx < 0) return null; const start = Math.max(0, idx - 2); const visible = words.slice(start, start + 5); return (

{visible.map((w, i) => ( {w.text} ))}

); } export function ProgressBar({ accent, height = 10 }: { accent: string; height?: number }) { const frame = useCurrentFrame(); const { durationInFrames } = useVideoConfig(); return (
); } export function SceneCard({ scene, accent, scale = 1 }: { scene: Scene; accent: string; scale?: number }) { const frame = useCurrentFrame(); const opacity = interpolate(frame, [0, 8], [0, 1], { extrapolateRight: "clamp" }); const y = interpolate(frame, [0, 12], [40, 0], { extrapolateRight: "clamp" }); const isQuote = scene.type === "pull_quote"; const isNumber = scene.type === "number"; return (

{scene.onScreenText}

{scene.citation && (

Source: {scene.citation}

)}
); } export function Logo({ url, size = 72, inset = 60 }: { url?: string; size?: number; inset?: number }) { if (!url) return null; return ; }