← Catalog
Edit Props (live)
TEXT
IMAGE
config
config
config
config
config
Source
393 linesimport React from "react";
import {
AbsoluteFill,
Img,
OffthreadVideo,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
spring,
staticFile,
} from "remotion";
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
import { loadFont as loadMono } from "@remotion/google-fonts/JetBrainsMono";
loadInter("normal", { weights: ["400", "700", "900"] });
loadMono("normal", { weights: ["400", "500"] });
const MONO = "'JetBrains Mono', monospace";
const ACCENT = "#D97757";
const CURSOR_COLORS = ["#5EEAD4", "#FACC15", "#60A5FA", "#F472B6"];
const isVideo = (s: string) => /\.(mp4|mov|webm)(\?|$)/i.test(s);
const resolveStatic = (s: string) => (s.startsWith("http") ? s : staticFile(s));
interface CursorDef {
color: string;
from: [number, number];
to: [number, number];
startF: number;
}
interface ClaudeCodeLogoSplitProps {
promptText?: string;
typingCps?: number;
splitHeadSrc?: string;
topBackground?: string;
accentShapesColor?: string;
cursors?: CursorDef[];
// When set, replaces the pixel-drawn wordmark with this image (wiped in L→R).
logoImageSrc?: string;
}
type Letter = string[];
const ALPHABET: Record<string, Letter> = {
C: [".XXX.", "X...X", "X....", "X....", "X....", "X...X", ".XXX."],
L: ["X....", "X....", "X....", "X....", "X....", "X....", "XXXXX"],
A: [".XXX.", "X...X", "X...X", "XXXXX", "X...X", "X...X", "X...X"],
U: ["X...X", "X...X", "X...X", "X...X", "X...X", "X...X", ".XXX."],
D: ["XXXX.", "X...X", "X...X", "X...X", "X...X", "X...X", "XXXX."],
E: ["XXXXX", "X....", "X....", "XXX..", "X....", "X....", "XXXXX"],
O: [".XXX.", "X...X", "X...X", "X...X", "X...X", "X...X", ".XXX."],
" ": [".....", ".....", ".....", ".....", ".....", ".....", "....."],
};
const MouseCursor: React.FC<{ color: string; size?: number }> = ({ color, size = 64 }) => (
<svg width={size} height={size * (32 / 28)} viewBox="0 0 28 32" style={{ display: "block", overflow: "visible" }}>
<path
d="M 5 3 L 5 23 L 10 18 L 13 26 L 17 24 L 14 16 L 21 15 Z"
fill={color}
stroke="#0d0d0f"
strokeWidth={1.6}
strokeLinejoin="round"
strokeLinecap="round"
/>
</svg>
);
const PixelLetter: React.FC<{
letter: string;
cell: number;
color: string;
}> = ({ letter, cell, color }) => {
const glyph = ALPHABET[letter.toUpperCase()] || ALPHABET[" "];
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{glyph.map((row, r) => (
<div key={r} style={{ display: "flex" }}>
{row.split("").map((c, x) => (
<div
key={x}
style={{
width: cell,
height: cell,
background: c === "X" ? color : "transparent",
boxShadow: c === "X" ? `2px 2px 0 ${color}66` : undefined,
}}
/>
))}
</div>
))}
</div>
);
};
// Word cell-widths: each letter is 5 cells wide, gap between letters = 1 cell.
const wordCellWidth = (text: string) => text.length * 5 + Math.max(0, text.length - 1);
const PixelWord: React.FC<{
text: string;
cell: number;
color: string;
revealCells: number; // total cells revealed by the shared sweep
}> = ({ text, cell, color, revealCells }) => {
const totalCells = wordCellWidth(text);
const cellsShown = Math.max(0, Math.min(totalCells, revealCells));
const clipRightPct = ((totalCells - cellsShown) / totalCells) * 100;
// scanline: thin bright edge at the current reveal column, while still sweeping
const sweeping = cellsShown > 0 && cellsShown < totalCells;
const scanX = cellsShown * cell;
return (
<div style={{ position: "relative" }}>
<div
style={{
display: "flex",
gap: cell,
clipPath: `inset(0 ${clipRightPct}% 0 0)`,
WebkitClipPath: `inset(0 ${clipRightPct}% 0 0)`,
}}
>
{text.split("").map((ch, i) => (
<PixelLetter key={i} letter={ch} cell={cell} color={color} />
))}
</div>
{sweeping && (
<div
style={{
position: "absolute",
left: scanX - 2,
top: -4,
width: 4,
height: 7 * cell + 8,
background: "rgba(255, 255, 255, 0.8)",
boxShadow: "0 0 16px rgba(255, 212, 178, 0.9)",
pointerEvents: "none",
}}
/>
)}
</div>
);
};
export const ClaudeCodeLogoSplit: React.FC<ClaudeCodeLogoSplitProps> = ({
promptText = "Build an MVP for my food delivery app",
typingCps = 28,
splitHeadSrc,
topBackground = "#0d0d0f",
cursors,
logoImageSrc,
}) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const hasHead = !!splitHeadSrc;
const splitY = Math.round(height * 0.5);
const contentBottom = hasHead ? splitY : height;
const logoCenterY = hasHead ? Math.round(splitY * 0.42) : Math.round(height * 0.42);
const inputY = hasHead ? Math.round(splitY * 0.82) : Math.round(height * 0.68);
const cell = Math.floor(width / 50);
// Shared L→R sweep across both words.
// Longest word = CLAUDE (35 cells). Progress runs cells 0..35 over ~22 frames.
const maxCells = wordCellWidth("CLAUDE");
const sweepT = interpolate(frame, [3, 25], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
const revealCells = sweepT * maxCells;
const defaultCursors: CursorDef[] = [
{ color: CURSOR_COLORS[0], from: [-0.15, 0.25], to: [0.78, 0.24], startF: 14 },
{ color: CURSOR_COLORS[1], from: [1.15, 0.10], to: [0.85, 0.46], startF: 18 },
{ color: CURSOR_COLORS[2], from: [1.18, 0.85], to: [0.30, 0.62], startF: 22 },
{ color: CURSOR_COLORS[3], from: [-0.18, 0.95], to: [0.16, 0.54], startF: 26 },
];
const cursorList = cursors && cursors.length ? cursors : defaultCursors;
const promptFadeIn = interpolate(frame, [30, 42], [0, 1], { extrapolateRight: "clamp" });
const typeStart = 34;
const typeChars = Math.max(0, Math.floor(((frame - typeStart) / fps) * typingCps));
const typedPrompt = promptText.slice(0, typeChars);
const cursorBlink = typeChars < promptText.length && Math.floor(frame / 9) % 2 === 0;
return (
<AbsoluteFill style={{ backgroundColor: topBackground, overflow: "hidden" }}>
{/* Top region: logo + input (full-frame when no head, else top half) */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: contentBottom,
background: topBackground,
overflow: "hidden",
}}
>
{logoImageSrc ? (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: contentBottom,
display: "flex",
alignItems: "center",
justifyContent: "center",
clipPath: `inset(0 ${(1 - sweepT) * 100}% 0 0)`,
WebkitClipPath: `inset(0 ${(1 - sweepT) * 100}% 0 0)`,
}}
>
<Img
src={resolveStatic(logoImageSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "contain",
display: "block",
}}
/>
</div>
) : (
<div
style={{
position: "absolute",
top: logoCenterY,
left: 0,
right: 0,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 14,
transform: "translateY(-50%)",
}}
>
<PixelWord text="CLAUDE" cell={cell} color={ACCENT} revealCells={revealCells} />
<PixelWord text="CODE" cell={cell} color={ACCENT} revealCells={revealCells} />
</div>
)}
{logoImageSrc && sweepT > 0 && sweepT < 1 && (
<div
style={{
position: "absolute",
top: 0,
left: `${sweepT * 100}%`,
width: 6,
height: contentBottom,
transform: "translateX(-3px)",
background: "rgba(255,255,255,0.85)",
boxShadow: "0 0 22px rgba(255,212,178,0.95), 0 0 40px rgba(217,119,87,0.6)",
pointerEvents: "none",
zIndex: 5,
}}
/>
)}
{cursorList.map((cur, i) => {
const t = spring({
frame: frame - cur.startF,
fps,
config: { mass: 0.9, damping: 15, stiffness: 160 },
});
const x = interpolate(t, [0, 1], [cur.from[0], cur.to[0]]) * width;
const yBase = interpolate(t, [0, 1], [cur.from[1], cur.to[1]]) * contentBottom;
const wobble = t > 0.9 ? Math.sin((frame - cur.startF) * 0.15) * 3 : 0;
const y = yBase + wobble;
const op = interpolate(frame - cur.startF, [0, 4], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const pop = spring({
frame: frame - (cur.startF + 18),
fps,
config: { mass: 0.6, damping: 11, stiffness: 220 },
});
const popScale = interpolate(pop, [0, 1], [1.25, 1]);
return (
<div
key={i}
style={{
position: "absolute",
left: x,
top: y,
opacity: op,
transform: `scale(${popScale})`,
pointerEvents: "none",
filter: "drop-shadow(0 4px 8px rgba(0,0,0,0.45))",
}}
>
<MouseCursor color={cur.color} size={64} />
</div>
);
})}
{!logoImageSrc && (
<div
style={{
position: "absolute",
top: inputY,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
opacity: promptFadeIn,
}}
>
<div
style={{
background: "#000000",
border: "1px solid rgba(255,255,255,0.18)",
borderRadius: 10,
padding: "16px 22px",
color: "#ffffff",
fontFamily: MONO,
fontSize: 28,
width: "82%",
}}
>
<span style={{ color: ACCENT, marginRight: 12 }}>{">"}</span>
{typedPrompt}
{cursorBlink && (
<span
style={{
display: "inline-block",
width: 12,
height: 28,
background: "#ffffff",
verticalAlign: "text-bottom",
marginLeft: 2,
}}
/>
)}
</div>
</div>
)}
</div>
{hasHead && (
<>
<div
style={{
position: "absolute",
top: splitY,
left: 0,
right: 0,
bottom: 0,
background: "#1a1a1c",
overflow: "hidden",
}}
>
{isVideo(splitHeadSrc!) ? (
<OffthreadVideo
src={resolveStatic(splitHeadSrc!)}
muted
style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
/>
) : (
<Img
src={resolveStatic(splitHeadSrc!)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
objectPosition: "center -25px",
display: "block",
}}
/>
)}
</div>
<div
style={{
position: "absolute",
top: splitY,
left: 0,
right: 0,
height: 3,
transform: "translateY(-1.5px)",
background: "rgba(255,255,255,0.15)",
boxShadow: "0 0 14px rgba(255,255,255,0.22)",
pointerEvents: "none",
zIndex: 8,
}}
/>
</>
)}
</AbsoluteFill>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: ClaudeCodeLogoSplit
// 7 props detected · 2 port(s)
Claude Code Logo Split
claude_code_logo_splitClaude Code Logo Split — Andy's hand-crafted Remotion scene. 7 props. promptText:TEXT, logoImageSrc:IMAGE.
Workflow Inputs (2)
- Prompt Text
promptTextTEXT - Logo Image Src
logoImageSrcIMAGE
Config Fields (5)
cursorstypingCpssplitHeadSrctopBackgroundaccentShapesColor
Meta
- Updated
- 4/21/2026