Catalog

Edit Props (live)

TEXT
TEXT
config
config
config
config
config
config

Source

163 lines
// Kinetic Text Intro — staggered word-by-word reveal of a headline with a
// growing accent bar and a fading subhead. Native Remotion (interpolate +
// spring, driven entirely by useCurrentFrame so it renders identically on
// Lambda). The single biggest "this isn't a slideshow" upgrade for the first
// 1.5–3s of a reel.

import {
  AbsoluteFill,
  interpolate,
  spring,
  useCurrentFrame,
  useVideoConfig,
} from 'remotion';
import { loadFont as loadInter } from '@remotion/google-fonts/Inter';
import { loadFont as loadRoboto } from '@remotion/google-fonts/Roboto';
import { loadFont as loadGrotesk } from '@remotion/google-fonts/SpaceGrotesk';

const { fontFamily: GROTESK } = loadGrotesk();
const { fontFamily: INTER } = loadInter();
const { fontFamily: ROBOTO } = loadRoboto();
const FONT_MAP: Record<string, string> = {
  SpaceGrotesk: GROTESK,
  Inter: INTER,
  Roboto: ROBOTO,
};

export type KineticTextIntroProps = {
  headline: string;
  subhead: string;
  accentColor: string;
  textColor: string;
  backgroundColor: string;
  font: 'SpaceGrotesk' | 'Inter' | 'Roboto';
  fontSize: number;
  style: 'word-reveal' | 'rise' | 'scale-pop';
};

export default function KineticTextIntro({
  headline = 'YOUR HEADLINE HERE',
  subhead = '',
  accentColor = '#5b3df5',
  textColor = '#ffffff',
  backgroundColor = '#0a0a14',
  font = 'SpaceGrotesk',
  fontSize = 108,
  style = 'word-reveal',
}: Partial<KineticTextIntroProps>) {
  const frame = useCurrentFrame();
  const { fps, durationInFrames, width } = useVideoConfig();
  // Resolution-independent: all px are authored against a 1080-wide
  // reference, then scaled by k. At 1080 k=1 (identical output); at any
  // other width (540 thumbnail, square, etc.) the layout scales cleanly.
  const k = width / 1080;
  const family = FONT_MAP[font] ?? GROTESK;
  const fpx = fontSize * k;
  const words = String(headline).trim().split(/\s+/).filter(Boolean);
  const stagger = style === 'scale-pop' ? 0 : 3;

  const exit = interpolate(
    frame,
    [durationInFrames - 12, durationInFrames - 1],
    [1, 0],
    { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
  );

  const subStart = words.length * stagger + 14;
  const subOpacity = interpolate(frame, [subStart, subStart + 12], [0, 1], {
    extrapolateLeft: 'clamp',
    extrapolateRight: 'clamp',
  });
  const accentGrow = spring({
    frame: frame - (words.length * stagger + 6),
    fps,
    config: { damping: 16 },
    durationInFrames: 18,
  });

  return (
    <AbsoluteFill
      style={{
        backgroundColor,
        justifyContent: 'center',
        alignItems: 'center',
        opacity: exit,
        fontFamily: family,
      }}
    >
      <div style={{ maxWidth: '86%', textAlign: 'center' }}>
        <div
          style={{
            display: 'flex',
            flexWrap: 'wrap',
            justifyContent: 'center',
            lineHeight: 1.04,
          }}
        >
          {words.map((w, i) => {
            const s = spring({
              frame: frame - i * stagger,
              fps,
              config: { damping: 14, stiffness: 120 },
              durationInFrames: 22,
            });
            const y = interpolate(
              s,
              [0, 1],
              [(style === 'rise' ? 64 : 26) * k, 0]
            );
            const sc =
              style === 'scale-pop'
                ? interpolate(s, [0, 1], [0.6, 1])
                : interpolate(s, [0, 1], [0.92, 1]);
            return (
              <span
                key={i}
                style={{
                  display: 'inline-block',
                  // Spacing via margin, NOT flex `gap` — flex gap does not
                  // render on the Remotion Lambda Chromium (works in the
                  // editor preview, paints zero on Lambda → "Yourcalorieappis").
                  margin: '0.06em 0.21em',
                  transform: `translateY(${y}px) scale(${sc})`,
                  opacity: s,
                  color: textColor,
                  fontSize: fpx,
                  fontWeight: 800,
                  letterSpacing: '-0.02em',
                }}
              >
                {w}
              </span>
            );
          })}
        </div>

        <div
          style={{
            height: 8 * k,
            width: `${accentGrow * 42}%`,
            margin: `${30 * k}px auto 0`,
            background: accentColor,
            borderRadius: 4 * k,
          }}
        />

        {subhead ? (
          <div
            style={{
              marginTop: 26 * k,
              color: textColor,
              opacity: subOpacity * 0.85,
              fontSize: Math.round(fpx * 0.32),
              fontWeight: 500,
            }}
          >
            {subhead}
          </div>
        ) : null}
      </div>
    </AbsoluteFill>
  );
}

Schema (Zod)

import { z } from 'zod';

export const Schema = z.object({
  headline: z
    .string()
    .default('YOUR HEADLINE HERE')
    .describe('$port:text Headline (animates word by word)'),
  subhead: z
    .string()
    .default('')
    .describe('$port:text Subhead, fades in after the headline (optional)'),
  accentColor: z
    .string()
    .default('#5b3df5')
    .describe('$style:color Accent bar color'),
  textColor: z.string().default('#ffffff').describe('$style:color Text color'),
  backgroundColor: z
    .string()
    .default('#0a0a14')
    .describe('$style:color Background color'),
  font: z
    .enum(['SpaceGrotesk', 'Inter', 'Roboto'])
    .default('SpaceGrotesk')
    .describe('$style:font Headline font'),
  fontSize: z.number().default(108).describe('$style:size Headline font size'),
  style: z
    .enum(['word-reveal', 'rise', 'scale-pop'])
    .default('word-reveal')
    .describe('$style:select Reveal animation style'),
});

Kinetic Text Intro

kinetic_text_intro

Headline that reveals word by word (spring) with a growing accent bar and a fading subhead. The biggest perceived-quality jump for an opener — use it instead of a plain text scene.

Workflow Inputs (2)

  • Headline (animates word by word)
    headline
    TEXT
  • Subhead, fades in after the headline (optional)
    subhead
    TEXT

Config Fields (6)

  • font
  • style
  • fontSize
  • textColor
  • accentColor
  • backgroundColor

Meta

Updated
7/10/2026