← Catalog
Edit Props (live)
TEXT
TEXT
VIDEO
config
config
config
config
config
config
config
config
Source
353 linesimport React from "react";
import {
AbsoluteFill,
Img,
OffthreadVideo,
Sequence,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
} from "remotion";
import { loadFont as loadZilla } from "@remotion/google-fonts/ZillaSlab";
import { loadFont as loadLora } from "@remotion/google-fonts/Lora";
// Fonts:
// - Zilla Slab 900 for the scramble caps (closer to the reference slab than
// Roboto Slab — narrower shoulders, flatter serifs).
// - Lora 500 for the Claude wordmark (refined serif, matches Tiempos /
// Copernicus family the real Anthropic mark uses).
loadZilla("normal", { weights: ["700"] });
loadLora("normal", { weights: ["500", "600"] });
// Scramble-to-reveal two-line text hook. Letters in each line tumble through
// random glyphs (cipher-style) before locking on the final character. Inspired
// by the "MANAGED / AGENTS" opener in the reference reel.
//
// Optional Claude sparkle outro: after the scramble settles, the frame fades
// to cream and the real claude.svg sparkle fades in. Then "Claude" appears
// letter-by-letter to the right of the sparkle, each letter ramping from
// transparent through a mid-grey to full black.
const GLYPHS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#@$%&*!?><[]{}+-";
interface GlitchTextRevealProps {
topText: string;
bottomText: string;
topColor?: string; // default Claude orange
bottomColor?: string; // default white
backgroundColor?: string; // default "#000"
scrambleDurationS?: number; // how long the scramble lasts, default 1.4s
holdAfterS?: number; // hold on settled text, default 0.5s
showClaudeOutro?: boolean; // if true, fade to cream + Claude reveal clip
outroBackground?: string; // cream bg for outro, matched to Anthropic brand
sparkleColor?: string; // unused (sparkle is part of the outro clip)
outroVideoSrc?: string; // relative path under public/visuals — defaults
// to the real Anthropic Claude reveal clip
// extracted from the reference IG reel
}
const SLAB = "'Zilla Slab', serif";
const SERIF = "'Lora', 'Tiempos Text', serif";
// Pseudo-random based on seed — stable across frames for a given letter slot
function rand(seed: number): number {
const x = Math.sin(seed) * 43758.5453;
return x - Math.floor(x);
}
function scrambledChar(i: number, frame: number, finalChar: string, locked: boolean): string {
if (locked) return finalChar;
if (finalChar === " ") return " ";
// Tumble glyph every 3 frames
const tick = Math.floor(frame / 3);
const r = rand(i * 13.37 + tick * 7.77);
return GLYPHS[Math.floor(r * GLYPHS.length)];
}
const ScrambleLine: React.FC<{
text: string;
color: string;
startFrame: number;
settleDurationFrames: number;
frame: number;
fontSize: number;
letterSpacing?: number;
}> = ({ text, color, startFrame, settleDurationFrames, frame, fontSize, letterSpacing = -1 }) => {
const localFrame = frame - startFrame;
const letters = text.split("");
return (
<div
style={{
display: "flex",
justifyContent: "center",
gap: 0,
fontFamily: SLAB,
fontSize,
fontWeight: 700,
letterSpacing,
lineHeight: 1.0,
}}
>
{letters.map((ch, i) => {
const lockFrame = (i / Math.max(letters.length - 1, 1)) * settleDurationFrames;
const locked = localFrame >= lockFrame;
const displayed = scrambledChar(i, localFrame, ch, locked);
const sinceLock = locked ? localFrame - lockFrame : 0;
const flashing = locked && sinceLock < 4;
return (
<span
key={i}
style={{
color: flashing ? "#ffffff" : color,
backgroundColor: flashing ? color : "transparent",
padding: flashing ? "0 2px" : 0,
opacity: localFrame < 0 ? 0 : 1,
transition: "color 120ms, background-color 120ms",
fontVariantNumeric: "tabular-nums",
}}
>
{displayed}
</span>
);
})}
</div>
);
};
// Real Claude sparkle — uses the actual claude.svg asset. To animate the
// arms we apply a dual-axis scale pulse with 180° phase offset: scaleX and
// scaleY oscillate in opposition, so the horizontal arms grow while the
// vertical ones shrink and vice versa. The net effect reads as "arms
// morphing" even though the SVG itself is a single continuous path and
// can't be manipulated per-arm. A very slow rotation adds organic life.
const ClaudeSparkle: React.FC<{
size: number;
frame: number;
fps: number;
speedHz?: number;
}> = ({ size, frame, fps, speedHz = 0.9 }) => {
const t = (frame / fps) * speedHz * Math.PI * 2;
// scaleX / scaleY oscillate in opposition → sparkle alternates between
// "wide" (horizontal arms long) and "tall" (vertical arms long) silhouette.
const sx = 1 + Math.sin(t) * 0.08; // 0.92..1.08
const sy = 1 + Math.sin(t + Math.PI) * 0.08; // opposite phase
// Slow rotation — one full turn every ~30s so it's barely perceptible
// but adds micro-life to the shape.
const rot = (frame / fps) * 12; // deg/s
return (
<div
style={{
width: size,
height: size,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Img
src={staticFile("logos/claude.svg")}
style={{
width: size,
height: size,
objectFit: "contain",
transform: `rotate(${rot}deg) scale(${sx}, ${sy})`,
transformOrigin: "center",
willChange: "transform",
}}
/>
</div>
);
};
// Renders "Claude" as individual letters that stamp in one by one. Each
// letter opacity-fades 0→1 over 3 frames with a 4-frame stagger — matches
// the tight typewriter feel of the reference. A small translateY (6→0px)
// gives each letter a subtle settle. Letters stay at full near-black the
// whole time; only opacity + Y animate.
const ClaudeWordmark: React.FC<{
startFrame: number;
frame: number;
fontSize: number;
staggerFrames?: number;
perLetterFadeFrames?: number;
color?: string;
}> = ({
startFrame,
frame,
fontSize,
staggerFrames = 4,
perLetterFadeFrames = 3,
color = "#141414",
}) => {
const text = "Claude";
return (
<div
style={{
fontFamily: SERIF,
fontSize,
fontWeight: 500,
letterSpacing: -2,
display: "inline-flex",
lineHeight: 1.0,
color,
}}
>
{text.split("").map((ch, i) => {
const letterStart = startFrame + i * staggerFrames;
const op = interpolate(
frame,
[letterStart, letterStart + perLetterFadeFrames],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }
);
const y = interpolate(
frame,
[letterStart, letterStart + perLetterFadeFrames + 2],
[6, 0],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }
);
return (
<span
key={i}
style={{
opacity: op,
transform: `translateY(${y}px)`,
display: "inline-block",
}}
>
{ch}
</span>
);
})}
</div>
);
};
export const GlitchTextReveal: React.FC<GlitchTextRevealProps> = ({
topText,
bottomText,
topColor = "#D97757",
bottomColor = "#ffffff",
backgroundColor = "#000000",
scrambleDurationS = 1.4,
holdAfterS = 0.5,
showClaudeOutro = false,
outroBackground = "#F0E8DB", // warmer cream to match Anthropic brand bg
sparkleColor = "#D97757",
outroVideoSrc = "visuals/claude_reveal.mp4",
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scrambleFrames = Math.round(scrambleDurationS * fps);
const holdFrames = Math.round(holdAfterS * fps);
const totalPhase1 = scrambleFrames + holdFrames;
// Phase transition — hard cut + brief (8 frame) cross-fade.
const outroStartFrame = totalPhase1 + 4;
const crossFade = interpolate(frame, [outroStartFrame, outroStartFrame + 8], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.inOut(Easing.cubic),
});
const inOutro = showClaudeOutro && frame >= outroStartFrame;
// Sparkle gentle fade-in (opacity + soft scale 0.85→1.0), then holds.
// No bounce — reference has a calm fade, not a spring pop.
const sparkleStartFrame = outroStartFrame + 6;
const sparkleOpacity = interpolate(
frame,
[sparkleStartFrame, sparkleStartFrame + 12],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }
);
const sparkleScale = interpolate(
frame,
[sparkleStartFrame, sparkleStartFrame + 14],
[0.85, 1.0],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) }
);
// Wordmark letters start appearing ~14 frames after sparkle (sparkle needs
// to establish itself first).
const wordmarkStartFrame = sparkleStartFrame + 14;
const phase1Opacity = inOutro ? 1 - crossFade : 1;
// Mix bg colors during cross-fade by layering outro bg above phase1 bg.
return (
<AbsoluteFill style={{ backgroundColor, overflow: "hidden" }}>
{/* Outro background layer (cream) — fades in during transition */}
{inOutro && (
<div
style={{
position: "absolute",
inset: 0,
backgroundColor: outroBackground,
opacity: crossFade,
}}
/>
)}
{/* PHASE 1 — scramble text */}
<div
style={{
position: "absolute",
inset: 0,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 20,
opacity: phase1Opacity,
}}
>
<ScrambleLine
text={topText}
color={topColor}
startFrame={4}
settleDurationFrames={scrambleFrames}
frame={frame}
fontSize={180}
/>
<ScrambleLine
text={bottomText}
color={bottomColor}
startFrame={10}
settleDurationFrames={scrambleFrames}
frame={frame}
fontSize={180}
/>
</div>
{/* PHASE 2 — Claude reveal: plays the real Anthropic animation clip
(sparkle arms pulse + typewriter wordmark) as an OffthreadVideo so
we get the exact motion instead of a synthetic reconstruction. */}
{showClaudeOutro && outroVideoSrc && (
<Sequence from={outroStartFrame} layout="none">
<div
style={{
position: "absolute",
inset: 0,
opacity: crossFade,
}}
>
<OffthreadVideo
src={outroVideoSrc.startsWith("http") ? outroVideoSrc : staticFile(outroVideoSrc)}
muted
style={{
width: "100%",
height: "100%",
objectFit: "cover",
display: "block",
}}
/>
</div>
</Sequence>
)}
</AbsoluteFill>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: GlitchTextReveal
// 11 props detected · 3 port(s)
Glitch Text Reveal
glitch_text_revealGlitch Text Reveal — Andy's hand-crafted Remotion scene. 11 props. topText:TEXT, bottomText:TEXT, outroVideoSrc:VIDEO.
Workflow Inputs (3)
- Top Text
topTextTEXTRequired - Bottom Text
bottomTextTEXTRequired - Outro Video Src
outroVideoSrcVIDEO
Config Fields (8)
topColorholdAfterSbottomColorsparkleColorbackgroundColoroutroBackgroundshowClaudeOutroscrambleDurationS
Meta
- Updated
- 4/21/2026