import React from "react";
import { loadFont } from "@remotion/google-fonts/SpaceGrotesk";
import {
AbsoluteFill,
Audio,
CalculateMetadataFunction,
OffthreadVideo,
Sequence,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
import { CinematicRendererProps, CinematicTone, CinematicVideoScene } from "./cinematic/types";
const FPS = 30;
const { fontFamily } = loadFont("normal", {
weights: ["400", "500", "700"],
subsets: ["latin"],
});
const toneGradient = (tone: CinematicTone) => {
switch (tone) {
case "steel":
return "linear-gradient(180deg, rgba(6,12,18,0.18) 0%, rgba(2,4,8,0.48) 100%)";
case "void":
return "linear-gradient(180deg, rgba(2,4,8,0.14) 0%, rgba(0,0,0,0.56) 100%)";
case "neutral":
return "linear-gradient(180deg, rgba(10,10,12,0.16) 0%, rgba(0,0,0,0.42) 100%)";
case "cold":
default:
return "linear-gradient(180deg, rgba(8,16,24,0.18) 0%, rgba(2,4,8,0.42) 100%)";
}
};
const SceneVideo: React.FC<{ scene: CinematicVideoScene }> = ({ scene }) => {
const frame = useCurrentFrame();
const { durationInFrames, fps } = useVideoConfig();
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",
extrapolateRight: "clamp",
});
const trimBefore =
scene.trimBeforeSeconds !== undefined
? Math.round(scene.trimBeforeSeconds * fps)
: undefined;
const trimAfter =
scene.trimAfterSeconds !== undefined
? Math.round(scene.trimAfterSeconds * fps)
: undefined;
return (
);
};
const SignalTexture: React.FC<{
accent: string;
intensity: number;
lineCount: number;
}> = ({ accent, intensity, lineCount }) => {
const frame = useCurrentFrame();
return (
{new Array(lineCount).fill(true).map((_, index) => {
const pulse = Math.max(0, Math.sin(frame * 0.06 + index * 0.85));
const opacity = (0.025 + pulse * 0.07) * intensity;
const width = 18 + ((index * 37) % 56);
const top = 140 + index * 42;
const left = index % 2 === 0 ? 0 : 1920 - width;
return (
);
})}
);
};
const TitleCard: React.FC<{
text: string;
accent: string;
intensity: number;
titleFontSize: number;
titleWidth: number;
signalLineCount: number;
}> = ({
text,
accent,
intensity,
titleFontSize,
titleWidth,
signalLineCount,
}) => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
const reveal = spring({
fps,
frame,
config: { damping: 18, stiffness: 90 },
});
const exit = interpolate(
frame,
[durationInFrames - 12, durationInFrames],
[1, 0],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
},
);
const y = interpolate(reveal, [0, 1], [18, 0]);
const letterSpacing = interpolate(reveal, [0, 1], [0.3, 0.18]);
const flareOpacity =
0.18 + Math.max(0, Math.sin(frame * 0.08)) * 0.14 * intensity;
return (
{text}
);
};
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 (