← Catalog
Edit Props (live)
IMAGE
IMAGE
IMAGE
AUDIO
config
config
config
Source
155 linesimport React from "react";
import {
AbsoluteFill,
Audio,
Img,
OffthreadVideo,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
getInputProps,
} from "remotion";
import { KineticCaption } from "./KineticCaption";
interface WordTimestamp {
word: string;
start: number;
end: number;
emphasis?: boolean;
}
export interface EspnDebriefProps {
avatarSrc: string;
screenshotSrc: string;
screenshotHeight: number; // actual pixel height of the full-page screenshot
audioSrc: string | null;
wordTimestamps: WordTimestamp[];
fps: number;
durationSec: number;
}
export const EspnDebrief: React.FC<EspnDebriefProps> = ({
avatarSrc,
screenshotSrc,
screenshotHeight,
audioSrc,
wordTimestamps,
fps,
durationSec,
}) => {
const frame = useCurrentFrame();
const { width, height, durationInFrames } = useVideoConfig();
// Layout: top half = scrolling article, bottom half = avatar
const splitY = Math.round(height * 0.48); // article gets top 48%
const avatarH = height - splitY; // avatar gets bottom 52%
// Scrolling article — move the tall screenshot upward over the duration.
// The visible window is splitY pixels tall, the image is screenshotHeight tall.
// Scroll from top to (screenshotHeight - splitY) over the video duration.
const maxScroll = Math.max(0, (screenshotHeight || 4000) - splitY);
const scrollY = interpolate(
frame,
[0, durationInFrames],
[0, -maxScroll],
{ extrapolateRight: "clamp" },
);
return (
<AbsoluteFill style={{ background: "#0a0a0a" }}>
{/* Top half: scrolling article screenshot */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
width,
height: splitY,
overflow: "hidden",
}}
>
{screenshotSrc && (
<Img
src={staticFile(screenshotSrc)}
style={{
position: "absolute",
top: scrollY,
left: 0,
width: "100%",
}}
/>
)}
{/* Bottom fade so article blends into the avatar section */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 60,
background: "linear-gradient(transparent, #0a0a0a)",
}}
/>
</div>
{/* Bottom half: avatar video (HeyGen 16:9 output, cover-fit) */}
<div
style={{
position: "absolute",
top: splitY,
left: 0,
width,
height: avatarH,
overflow: "hidden",
}}
>
{avatarSrc && (
<OffthreadVideo
src={staticFile(avatarSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
objectPosition: "center top",
}}
/>
)}
</div>
{/* Thin divider line between sections */}
<div
style={{
position: "absolute",
top: splitY - 1,
left: 0,
right: 0,
height: 2,
background: "#CC0000",
}}
/>
{/* Kinetic captions — over the avatar section */}
{wordTimestamps.length > 0 && (
<div
style={{
position: "absolute",
bottom: 60,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
}}
>
<KineticCaption words={wordTimestamps} />
</div>
)}
{/* Separate audio track if provided */}
{audioSrc && (
<Audio src={staticFile(audioSrc)} volume={1} />
)}
</AbsoluteFill>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: EspnDebrief
// 7 props detected · 4 port(s)
Espn Debrief
espn_debriefEspn Debrief — Andy's hand-crafted Remotion scene. 7 props. avatarSrc:IMAGE, screenshotSrc:IMAGE, screenshotHeight:IMAGE, +1 more.
Workflow Inputs (4)
- Avatar Src
avatarSrcIMAGERequired - Screenshot Src
screenshotSrcIMAGERequired - Screenshot Height
screenshotHeightIMAGERequired - Audio Src
audioSrcAUDIORequired
Config Fields (3)
fpsdurationSecwordTimestamps
Meta
- Updated
- 4/21/2026