Zero-key video formula: fix rendering bugs, add 8 demo compositions, update skills

Fix ComparisonCard and CalloutBox dark-theme passthrough in SceneRenderer.
Add 8 demo-props JSON files (3 fixed originals + 5 new compositions).
Update scene-director and compose-director skills with all 10 Remotion
scene types and zero-key rendering guidance. Document the proven
all-dark-background formula in remotion.md. Add cinematic renderer,
onboarding skill, prompt gallery, and demo render script.
This commit is contained in:
calesthio
2026-03-30 13:23:28 -07:00
parent 32c8d78d24
commit 5223eec21c
24 changed files with 3189 additions and 204 deletions
+83 -10
View File
@@ -2,6 +2,7 @@ import React from "react";
import { loadFont } from "@remotion/google-fonts/SpaceGrotesk";
import {
AbsoluteFill,
Audio,
CalculateMetadataFunction,
OffthreadVideo,
Sequence,
@@ -36,16 +37,24 @@ const toneGradient = (tone: CinematicTone) => {
const SceneVideo: React.FC<{ scene: CinematicVideoScene }> = ({ scene }) => {
const frame = useCurrentFrame();
const { durationInFrames, fps } = useVideoConfig();
const opacity = interpolate(
frame,
[0, 10, durationInFrames - 10, durationInFrames],
[0, 1, 1, 0],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
},
);
const fadeInFrames = scene.fadeInFrames ?? 10;
const fadeOutFrames = scene.fadeOutFrames ?? 10;
const fadeOutStart = Math.max(fadeInFrames, durationInFrames - fadeOutFrames);
const fadeInOpacity =
fadeInFrames === 0
? 1
: interpolate(frame, [0, fadeInFrames], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fadeOutOpacity =
fadeOutFrames === 0
? 1
: interpolate(frame, [fadeOutStart, durationInFrames], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const opacity = Math.min(fadeInOpacity, fadeOutOpacity);
const scale = interpolate(frame, [0, durationInFrames], [1.015, 1], {
extrapolateLeft: "clamp",
@@ -242,6 +251,59 @@ const TitleCard: React.FC<{
);
};
const Soundtrack: React.FC<{
src: string;
volume: number;
trimBeforeSeconds?: number;
trimAfterSeconds?: number;
fadeInSeconds: number;
fadeOutSeconds: number;
}> = ({
src,
volume,
trimBeforeSeconds,
trimAfterSeconds,
fadeInSeconds,
fadeOutSeconds,
}) => {
const frame = useCurrentFrame();
const { durationInFrames, fps } = useVideoConfig();
const fadeInFrames = Math.max(1, Math.round(fadeInSeconds * fps));
const fadeOutFrames = Math.max(1, Math.round(fadeOutSeconds * fps));
const trimBefore =
trimBeforeSeconds !== undefined
? Math.round(trimBeforeSeconds * fps)
: undefined;
const trimAfter =
trimAfterSeconds !== undefined
? Math.round(trimAfterSeconds * fps)
: undefined;
const fadeIn = interpolate(frame, [0, fadeInFrames], [0, volume], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fadeOut = interpolate(
frame,
[durationInFrames - fadeOutFrames, durationInFrames],
[volume, 0],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
},
);
return (
<Audio
src={src}
trimBefore={trimBefore}
trimAfter={trimAfter}
volume={() => Math.min(fadeIn, fadeOut)}
/>
);
};
export const calculateCinematicMetadata: CalculateMetadataFunction<CinematicRendererProps> =
async ({ props }) => {
const totalSeconds =
@@ -264,9 +326,20 @@ export const CinematicRenderer: React.FC<CinematicRendererProps> = ({
titleFontSize = 78,
titleWidth = 1320,
signalLineCount = 18,
soundtrack,
}) => {
return (
<AbsoluteFill style={{ backgroundColor: "#000000" }}>
{soundtrack ? (
<Soundtrack
src={soundtrack.src}
volume={soundtrack.volume ?? 0.45}
trimBeforeSeconds={soundtrack.trimBeforeSeconds}
trimAfterSeconds={soundtrack.trimAfterSeconds}
fadeInSeconds={soundtrack.fadeInSeconds ?? 1.5}
fadeOutSeconds={soundtrack.fadeOutSeconds ?? 2}
/>
) : null}
{scenes.map((scene) => (
<Sequence
key={scene.id}
+168 -6
View File
@@ -25,6 +25,11 @@ import { TextCard } from "./components/TextCard";
import { StatCard } from "./components/StatCard";
import { CalloutBox } from "./components/CalloutBox";
import { ComparisonCard } from "./components/ComparisonCard";
import { BarChart } from "./components/charts/BarChart";
import { LineChart } from "./components/charts/LineChart";
import { PieChart } from "./components/charts/PieChart";
import { KPIGrid } from "./components/charts/KPIGrid";
import { ProgressBar } from "./components/ProgressBar";
import { CaptionOverlay, WordCaption } from "./components/CaptionOverlay";
import { SectionTitle } from "./components/SectionTitle";
import { StatReveal } from "./components/StatReveal";
@@ -58,6 +63,34 @@ interface Cut {
rightLabel?: string;
leftValue?: string;
rightValue?: string;
// Chart props
chartData?: any[];
chartSeries?: any[];
chartColors?: string[];
chartAnimation?: string;
donut?: boolean;
centerLabel?: string;
centerValue?: string;
showGrid?: boolean;
showValues?: boolean;
showLegend?: boolean;
showMarkers?: boolean;
xLabel?: string;
yLabel?: string;
columns?: 2 | 3 | 4;
// Progress bar props
progress?: number;
progressLabel?: string;
progressColor?: string;
progressAnimation?: string;
progressSegments?: any[];
// Hero title props (when used as scene, not overlay)
heroSubtitle?: string;
// Styling overrides
backgroundColor?: string;
color?: string;
accentColor?: string;
fontSize?: number;
// Animation & transitions
animation?: string;
transition_in?: string;
@@ -246,14 +279,36 @@ const VideoScene: React.FC<{ src: string; startFrom?: number }> = ({
const SceneRenderer: React.FC<{ cut: Cut }> = ({ cut }) => {
// Explicit component types
if (cut.type === "text_card" && cut.text) {
return <TextCard text={cut.text} />;
return (
<TextCard
text={cut.text}
fontSize={cut.fontSize}
color={cut.color}
backgroundColor={cut.backgroundColor}
/>
);
}
if (cut.type === "stat_card" && cut.stat) {
return <StatCard stat={cut.stat} subtitle={cut.subtitle} />;
return (
<StatCard
stat={cut.stat}
subtitle={cut.subtitle}
accentColor={cut.accentColor}
backgroundColor={cut.backgroundColor}
/>
);
}
if (cut.type === "callout" && cut.text) {
return (
<CalloutBox text={cut.text} type={cut.callout_type} title={cut.title} />
<CalloutBox
text={cut.text}
type={cut.callout_type}
title={cut.title}
borderColor={cut.accentColor}
backgroundColor={cut.backgroundColor}
textColor={cut.color}
containerBackgroundColor={cut.backgroundColor}
/>
);
}
if (
@@ -270,21 +325,128 @@ const SceneRenderer: React.FC<{ cut: Cut }> = ({ cut }) => {
leftValue={cut.leftValue}
rightValue={cut.rightValue}
title={cut.title}
backgroundColor={cut.backgroundColor}
textColor={cut.color}
/>
);
}
if (cut.type === "hero_title" && cut.text) {
return <HeroTitle title={cut.text} subtitle={cut.heroSubtitle || cut.subtitle} />;
}
// --- Chart types ---
if (cut.type === "bar_chart" && cut.chartData) {
return (
<BarChart
data={cut.chartData}
title={cut.title}
colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "grow-up"}
showGrid={cut.showGrid}
showValues={cut.showValues}
backgroundColor={cut.backgroundColor}
/>
);
}
if (cut.type === "line_chart" && cut.chartSeries) {
return (
<LineChart
series={cut.chartSeries}
title={cut.title}
colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "draw"}
showGrid={cut.showGrid}
showMarkers={cut.showMarkers}
showLegend={cut.showLegend}
xLabel={cut.xLabel}
yLabel={cut.yLabel}
backgroundColor={cut.backgroundColor}
/>
);
}
if (cut.type === "pie_chart" && cut.chartData) {
return (
<PieChart
data={cut.chartData}
title={cut.title}
colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "expand"}
donut={cut.donut}
centerLabel={cut.centerLabel}
centerValue={cut.centerValue}
showLegend={cut.showLegend}
backgroundColor={cut.backgroundColor}
/>
);
}
if (cut.type === "kpi_grid" && cut.chartData) {
return (
<KPIGrid
metrics={cut.chartData}
title={cut.title}
columns={cut.columns}
colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "count-up"}
backgroundColor={cut.backgroundColor}
/>
);
}
if (cut.type === "progress_bar" && cut.progress !== undefined) {
return (
<AbsoluteFill
style={{
backgroundColor: cut.backgroundColor || "#FFFFFF",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "80px 120px",
}}
>
{cut.title && (
<div
style={{
position: "absolute",
top: 120,
fontSize: 48,
fontWeight: 700,
color: "#1F2937",
textAlign: "center",
width: "100%",
}}
>
{cut.title}
</div>
)}
<ProgressBar
progress={cut.progress}
label={cut.progressLabel}
color={cut.progressColor || cut.accentColor}
animationStyle={(cut.progressAnimation as any) || "fill"}
segments={cut.progressSegments}
backgroundColor={cut.backgroundColor}
/>
</AbsoluteFill>
);
}
// --- Media types (image / video fallback) ---
const animation = cut.animation || cut.transform?.animation;
if (isImage(cut.source)) {
if (cut.source && isImage(cut.source)) {
return <ImageScene src={cut.source} animation={animation} />;
}
if (isVideo(cut.source)) {
if (cut.source && isVideo(cut.source)) {
return <VideoScene src={cut.source} startFrom={cut.in_seconds} />;
}
return <ImageScene src={cut.source} animation={animation} />;
// Final fallback — try as image if source exists, otherwise show text_card
if (cut.source) {
return <ImageScene src={cut.source} animation={animation} />;
}
// No source, no type — render as text card with cut id as fallback
return <TextCard text={cut.text || cut.id} />;
};
// ---------------------------------------------------------------------------
+11
View File
@@ -4,6 +4,7 @@ import {
CinematicRenderer,
calculateCinematicMetadata,
} from "./CinematicRenderer";
import { signalFromTomorrowWithMusicFixture } from "./cinematic/fixtures";
const calculateMetadata: CalculateMetadataFunction<ExplainerProps> = async ({
props,
@@ -50,6 +51,16 @@ export const Root: React.FC = () => {
}}
calculateMetadata={calculateCinematicMetadata}
/>
<Composition
id="SignalFromTomorrowWithMusic"
component={CinematicRenderer}
durationInFrames={30 * 30}
fps={30}
width={1920}
height={1080}
defaultProps={signalFromTomorrowWithMusicFixture}
calculateMetadata={calculateCinematicMetadata}
/>
</>
);
};
@@ -0,0 +1,83 @@
import { staticFile } from "remotion";
import { CinematicRendererProps } from "./types";
export const signalFromTomorrowWithMusicFixture: CinematicRendererProps = {
titleFontSize: 78,
titleWidth: 1320,
signalLineCount: 18,
soundtrack: {
src: staticFile(
"music/signal-from-tomorrow/cinematic_time_hans_zimmer_style.mp3",
),
volume: 0.42,
fadeInSeconds: 1.5,
fadeOutSeconds: 2.5,
},
scenes: [
{
id: "sc1",
kind: "video",
startSeconds: 0,
durationSeconds: 4,
src: staticFile("video/signal-from-tomorrow/sample_observatory_veo31_ref.mp4"),
tone: "cold",
trimBeforeSeconds: 1,
fadeInFrames: 0,
},
{
id: "sc2",
kind: "video",
startSeconds: 4,
durationSeconds: 4,
src: staticFile(
"video/signal-from-tomorrow/sc2_mission_control_veo31_ref_8s.mp4",
),
tone: "steel",
},
{
id: "sc3",
kind: "title",
startSeconds: 8,
durationSeconds: 3,
text: "YESTERDAY, THEY LAUNCHED.",
accent: "#89d7ff",
intensity: 1,
},
{
id: "sc4",
kind: "video",
startSeconds: 11,
durationSeconds: 7,
src: staticFile("video/signal-from-tomorrow/sc4_launch_departure_veo31_ref.mp4"),
tone: "cold",
},
{
id: "sc5",
kind: "title",
startSeconds: 18,
durationSeconds: 3,
text: "THE SIGNAL CAME FROM EARTH.",
accent: "#a6e6ff",
intensity: 1.15,
},
{
id: "sc6",
kind: "video",
startSeconds: 21,
durationSeconds: 6,
src: staticFile(
"video/signal-from-tomorrow/sc6_orbital_paradox_veo31_ref_8s.mp4",
),
tone: "void",
},
{
id: "sc7",
kind: "title",
startSeconds: 27,
durationSeconds: 3,
text: "SIGNAL FROM TOMORROW",
accent: "#d6f1ff",
intensity: 0.9,
},
],
};
+12
View File
@@ -13,6 +13,8 @@ export interface CinematicVideoScene extends CinematicBaseScene {
trimBeforeSeconds?: number;
trimAfterSeconds?: number;
filter?: string;
fadeInFrames?: number;
fadeOutFrames?: number;
}
export interface CinematicTitleScene extends CinematicBaseScene {
@@ -24,10 +26,20 @@ export interface CinematicTitleScene extends CinematicBaseScene {
export type CinematicScene = CinematicVideoScene | CinematicTitleScene;
export interface CinematicSoundtrack {
src: string;
volume?: number;
trimBeforeSeconds?: number;
trimAfterSeconds?: number;
fadeInSeconds?: number;
fadeOutSeconds?: number;
}
export interface CinematicRendererProps {
[key: string]: unknown;
scenes: CinematicScene[];
titleFontSize?: number;
titleWidth?: number;
signalLineCount?: number;
soundtrack?: CinematicSoundtrack;
}