import React from 'react'; import { AbsoluteFill, Img, Sequence, interpolate, staticFile, useCurrentFrame, } from 'remotion'; import manifest from '../capture-manifest.json'; import { FPS, HEIGHT, WIDTH, timeline, type Scene } from './scenes'; /** * Presentation only. Every pixel of evidence in here is a promoted Playwright * capture; this file controls pacing, framing, and captions and nothing else. * It must never composite, crop, or annotate in a way that changes what the * underlying screen said. */ const COLORS = { bg: '#0a0d13', panel: '#121821', line: '#243044', text: '#e3e9f2', dim: '#8797ad', accent: '#4da3ff', good: '#56d364', dream: '#d29922', } as const; const SANS = 'ui-sans-serif, -apple-system, "Segoe UI", system-ui, "Helvetica Neue", sans-serif'; const MONO = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace'; type CaptureEntry = { id: string; file: string; width: number; height: number; audienceVerdict: string; auditStatus: string; }; const captures: Record = Object.fromEntries( (manifest.captures as CaptureEntry[]).map((c) => [c.id, c]), ); /** Reject at render time rather than shipping a scene with no evidence. */ function requireCapture(captureId: string): CaptureEntry { const entry = captures[captureId]; if (!entry) throw new Error(`capture ${captureId} is not in the manifest`); if (entry.auditStatus !== 'pass' || entry.audienceVerdict !== 'perfect') { throw new Error( `capture ${captureId} is ${entry.auditStatus}/${entry.audienceVerdict}; only a ` + `passing, perfect capture may be rendered`, ); } return entry; } /** * Frames of cross-dissolve between scenes — 10 at 30fps is 333ms, inside the * 200-400ms micro-transition range. */ export const OVERLAP = 10; /** * Fade IN only, never out. * * The first cut of this composition faded each scene out over its last 8 frames * while the next faded in from its own frame 0. Because Remotion Sequences do * not overlap by default, both sat at opacity 0 on the boundary frame and the * video blinked to bare background between every scene. Scenes now start * OVERLAP frames early and rise on top of the outgoing one, which is a real * cross-dissolve with no empty frame. */ function useEntrance() { const frame = useCurrentFrame(); const opacity = interpolate(frame, [0, OVERLAP], [0, 1], { extrapolateRight: 'clamp', }); const lift = interpolate(frame, [0, OVERLAP + 4], [12, 0], { extrapolateRight: 'clamp', }); return { opacity, lift }; } const RungBadge: React.FC<{ rung: Scene['rung'] }> = ({ rung }) => ( {rung} ); const TitleScene: React.FC<{ scene: Extract }> = ({ scene, }) => { const { opacity, lift } = useEntrance(); return (

{scene.heading}

{scene.lines.map((line) => (

{line}

))}
{scene.footer ? (

{scene.footer}

) : null} ); }; const ProofScene: React.FC<{ scene: Extract }> = ({ scene }) => { const { opacity, lift } = useEntrance(); const capture = requireCapture(scene.captureId); // Reserve fixed bands for the heading and caption, then fit the capture into // whatever is left, preserving its aspect ratio. Tall captures (the 1600x1800 // dashboard) letterbox instead of being squashed or cropped. const headerBand = 76; const captionBand = 104; const availableWidth = WIDTH - 160; const availableHeight = HEIGHT - headerBand - captionBand; const scale = Math.min(availableWidth / capture.width, availableHeight / capture.height); const renderWidth = Math.round(capture.width * scale); const renderHeight = Math.round(capture.height * scale); return (
{scene.capabilityId}

{scene.caption}

); }; export const Walkthrough: React.FC = () => ( {timeline.map(({ scene, from, frames }) => ( // Start early and run long by OVERLAP so the incoming scene dissolves // over the outgoing one. End times are unchanged, so total duration and // every storyboard hold stay exactly as specified. 0 ? OVERLAP : 0))} durationInFrames={frames + (from > 0 ? OVERLAP : 0)} name={scene.id} > {scene.kind === 'proof' ? ( ) : ( )} ))} ); export const config = { FPS, WIDTH, HEIGHT };