← Catalog
No preview yet
Source
159 lines// Prompt Card Block — full-frame typewriter reveal of a prompt or
// quote with an optional byline. Letters tick in at a configurable
// chars-per-second rate. Background can be a flat color or an
// optional ambient video (muted, slow zoom). Useful for "here's
// what I asked" / quote / lower-third intro shots.
import {
AbsoluteFill,
OffthreadVideo,
interpolate,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
import { loadFont as loadInter } from '@remotion/google-fonts/Inter';
import { loadFont as loadJetBrains } from '@remotion/google-fonts/JetBrainsMono';
import { loadFont as loadPlayfair } from '@remotion/google-fonts/PlayfairDisplay';
const { fontFamily: INTER } = loadInter();
const { fontFamily: JETBRAINS } = loadJetBrains();
const { fontFamily: PLAYFAIR } = loadPlayfair();
const FONT_MAP: Record<string, string> = {
Inter: INTER,
JetBrainsMono: JETBRAINS,
PlayfairDisplay: PLAYFAIR,
};
export type PromptCardProps = {
ambientVideo: string;
prompt: string;
byline: string;
promptColor: string;
promptFontSize: number;
promptFont: 'Inter' | 'JetBrainsMono' | 'PlayfairDisplay';
bylineColor: string;
bylineFontSize: number;
charsPerSecond: number;
cursor: boolean;
cursorColor: string;
backgroundColor: string;
videoTint: number;
};
export default function PromptCard({
ambientVideo,
prompt = 'What did the agent see at frame 0?',
byline = '',
promptColor = '#FFFFFF',
promptFontSize = 64,
promptFont = 'Inter',
bylineColor = '#FFD84D',
bylineFontSize = 28,
charsPerSecond = 28,
cursor = true,
cursorColor = '#00F0FF',
backgroundColor = '#0a0b0f',
videoTint = 0.55,
}: Partial<PromptCardProps>) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const fontFamily = FONT_MAP[promptFont] ?? INTER;
const totalChars = prompt.length;
const revealedChars = Math.min(
totalChars,
Math.round((frame / fps) * charsPerSecond)
);
const visiblePrompt = prompt.slice(0, revealedChars);
const cursorVisible = cursor && Math.floor(frame / 15) % 2 === 0;
const hasVideo =
typeof ambientVideo === 'string' && /^https?:\/\//i.test(ambientVideo);
// Subtle background drift on the ambient video so it doesn't read
// as a still — slow zoom from 1.0 → 1.05 over the whole composition.
const slowZoom = interpolate(frame, [0, 240], [1, 1.05], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const bylineOpacity = interpolate(
revealedChars,
[totalChars - 6, totalChars],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
return (
<AbsoluteFill style={{ backgroundColor, overflow: 'hidden' }}>
{hasVideo ? (
<OffthreadVideo
src={ambientVideo}
muted
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
transform: `scale(${slowZoom})`,
transformOrigin: 'center',
}}
/>
) : null}
{hasVideo && videoTint > 0 ? (
<div
style={{
position: 'absolute',
inset: 0,
backgroundColor: `rgba(0,0,0,${Math.min(0.95, videoTint)})`,
}}
/>
) : null}
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '0 64px',
}}
>
<div
style={{
color: promptColor,
fontFamily,
fontSize: promptFontSize,
fontWeight: promptFont === 'PlayfairDisplay' ? 600 : 600,
lineHeight: 1.18,
letterSpacing: '-0.01em',
textAlign: 'center',
maxWidth: '100%',
textShadow: '0 4px 24px rgba(0,0,0,0.6)',
}}
>
{visiblePrompt}
{cursorVisible && revealedChars < totalChars ? (
<span style={{ color: cursorColor }}>▍</span>
) : null}
</div>
{byline ? (
<div
style={{
marginTop: 32,
color: bylineColor,
fontFamily: INTER,
fontWeight: 600,
fontSize: bylineFontSize,
letterSpacing: '0.18em',
textTransform: 'uppercase',
opacity: bylineOpacity,
}}
>
{byline}
</div>
) : null}
</div>
</AbsoluteFill>
);
}
Schema (Zod)
import { z } from 'zod';
export const Schema = z.object({
ambientVideo: z
.string()
.describe('$port:video Optional ambient background video'),
prompt: z
.string()
.default('What did the agent see at frame 0?')
.describe('Prompt or quote text'),
byline: z.string().default('').describe('Byline / citation (optional)'),
promptColor: z
.string()
.default('#FFFFFF')
.describe('$style:color Prompt text color'),
promptFontSize: z
.number()
.default(64)
.describe('$style:size Prompt font size'),
promptFont: z
.enum(['Inter', 'JetBrainsMono', 'PlayfairDisplay'])
.default('Inter')
.describe('$style:font Prompt font'),
bylineColor: z
.string()
.default('#FFD84D')
.describe('$style:color Byline color'),
bylineFontSize: z
.number()
.default(28)
.describe('$style:size Byline font size'),
charsPerSecond: z
.number()
.default(28)
.describe('$style:scale Typewriter speed (chars/sec)'),
cursor: z.boolean().default(true).describe('Show blinking cursor'),
cursorColor: z
.string()
.default('#00F0FF')
.describe('$style:color Cursor color'),
backgroundColor: z
.string()
.default('#0a0b0f')
.describe('$style:color Background color (when no video)'),
videoTint: z
.number()
.default(0.55)
.describe('$style:scale Video darken overlay (0–1)'),
});
Prompt Card
prompt_cardFull-frame typewriter reveal of a prompt or quote with optional byline. Configurable font, speed, ambient background video.
Workflow Inputs (1)
- Optional ambient background video
ambientVideoVIDEORequired
Config Fields (12)
bylinecursorpromptvideoTintpromptFontbylineColorcursorColorpromptColorbylineFontSizecharsPerSecondpromptFontSizebackgroundColor
Meta
- Updated
- 7/10/2026