← 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_introHeadline 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)
headlineTEXT - Subhead, fades in after the headline (optional)
subheadTEXT
Config Fields (6)
fontstylefontSizetextColoraccentColorbackgroundColor
Meta
- Updated
- 7/10/2026