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_card

Full-frame typewriter reveal of a prompt or quote with optional byline. Configurable font, speed, ambient background video.

Workflow Inputs (1)

  • Optional ambient background video
    ambientVideo
    VIDEORequired

Config Fields (12)

  • byline
  • cursor
  • prompt
  • videoTint
  • promptFont
  • bylineColor
  • cursorColor
  • promptColor
  • bylineFontSize
  • charsPerSecond
  • promptFontSize
  • backgroundColor

Meta

Updated
7/10/2026