← Catalog
Edit Props (live)
TEXT
TEXT
config
config
config
config
config
config
config
config
config
config
config
config
Source
345 linesimport React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
spring,
} from "remotion";
import { loadFont as loadMono } from "@remotion/google-fonts/JetBrainsMono";
loadMono("normal", { weights: ["400", "500", "700"] });
const MONO = "'JetBrains Mono', monospace";
const ACCENT = "#D97757";
type State = "welcome" | "typing" | "executing";
interface ResultLine {
text: string;
// Optional inline highlight ranges, e.g. highlight a substring orange.
highlights?: { from: number; to: number }[];
}
interface ClaudeCodeTerminalProps {
// Background tone — orange-peach for "warm" intro shots, cream for product cards.
backgroundColor?: string;
// Window
windowTitle?: string; // tab text, e.g. "Claude Code" or "• Refactor PickleCoach to Ne…"
// Welcome header
welcomeName?: string; // "Nick"
modelInfo?: string; // "Sonnet 4.6 with medium effort"
planLabel?: string; // "Claude Pro"
cwd?: string; // "C:\\Users\\shubh"
// State
terminalState?: State; // "welcome" (default) | "typing" | "executing"
// typing state
command?: string; // "/model opus"
// executing state — shows command result block + spinner
resultLines?: ResultLine[]; // [{text:"/model opusplan"}, {text:"└ Set model to Opus 4.6 …"}]
toolCall?: string; // "Explore(Explore PickleCoach project structure)"
toolDetail?: string; // "└ Bash(ls -la …)"
statusText?: string; // "Sautéing… (18s · ↓ 269 tokens)"
showInterruptHint?: boolean; // "esc to interrupt"
// typing speed
typingCps?: number; // default 30
}
const isLight = (hex: string) => {
const h = hex.replace("#", "");
const r = parseInt(h.slice(0, 2), 16);
const g = parseInt(h.slice(2, 4), 16);
const b = parseInt(h.slice(4, 6), 16);
return (r * 0.299 + g * 0.587 + b * 0.114) > 180;
};
// Pixel monster mascot — 8x6 grid, drawn as colored square cells.
const MONSTER_PIXELS = [
"..XXXXXX..",
".XXXXXXXX.",
"XX.XX.XXXX",
"XXXXXXXXXX",
"X.XX.X.XXX",
"X........X",
];
const PixelMonster: React.FC<{ size?: number; color?: string }> = ({ size = 90, color = ACCENT }) => {
const cell = Math.floor(size / 10);
return (
<div style={{ display: "inline-block" }}>
{MONSTER_PIXELS.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",
}}
/>
))}
</div>
))}
</div>
);
};
export const ClaudeCodeTerminal: React.FC<ClaudeCodeTerminalProps> = ({
backgroundColor = "#E89070",
windowTitle = "Claude Code",
welcomeName = "there",
modelInfo = "Sonnet 4.6 with medium effort",
planLabel = "Claude Pro",
cwd = "C:\\Users\\shubh",
terminalState = "welcome",
command = "/model opus",
resultLines,
toolCall,
toolDetail,
statusText,
showInterruptHint,
typingCps = 30,
}) => {
const frame = useCurrentFrame();
const { fps, width, height, durationInFrames } = useVideoConfig();
// Window dims — square-ish centered panel
const winW = Math.round(width * 0.86);
const winH = Math.round(height * 0.46);
const winLeft = Math.round((width - winW) / 2);
const winTop = Math.round(height * 0.20);
const chromeOp = interpolate(frame / fps, [0, 0.25], [0, 1], { extrapolateRight: "clamp" });
const chromeScale = interpolate(frame / fps, [0, 0.25], [0.97, 1], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
// Typing animation for `command`
const typeStart = Math.round(D(durationInFrames, 0.12));
const typeChars = Math.max(0, Math.floor(((frame - typeStart) / fps) * typingCps));
const typedCmd = command.slice(0, typeChars);
const cmdCaret = typeChars < command.length && Math.floor(frame / 9) % 2 === 0;
// Spinner glyphs for executing state
const SPINNER = ["·", "•", "◦", "•"];
const spin = SPINNER[Math.floor(frame / 6) % SPINNER.length];
const headerColor = isLight(backgroundColor) ? "#1a1a1a" : "#ffffff";
return (
<AbsoluteFill style={{ backgroundColor, overflow: "hidden", fontFamily: MONO }}>
{/* Window */}
<div
style={{
position: "absolute",
left: winLeft, top: winTop,
width: winW, height: winH,
background: "#0d0d0f",
borderRadius: 12,
overflow: "hidden",
boxShadow: "0 30px 80px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.06)",
opacity: chromeOp,
transform: `scale(${chromeScale})`,
transformOrigin: "center center",
display: "flex",
flexDirection: "column",
}}
>
{/* Windows-style title bar (matches reference reels — not mac dots) */}
<div
style={{
height: 38,
background: "#1a1a1c",
borderBottom: "1px solid rgba(255,255,255,0.06)",
display: "flex",
alignItems: "center",
paddingLeft: 8,
paddingRight: 0,
flexShrink: 0,
}}
>
{/* Browser-shield icon */}
<span style={{ width: 28, height: 28, display: "inline-flex", alignItems: "center", justifyContent: "center", color: "#777", fontSize: 18 }}>◉</span>
{/* Folder icon */}
<span style={{ width: 28, height: 28, display: "inline-flex", alignItems: "center", justifyContent: "center", color: "#777", fontSize: 16 }}>▦</span>
{/* Active tab */}
<div
style={{
display: "flex", alignItems: "center", gap: 8,
background: "#0d0d0f",
borderTop: `2px solid ${ACCENT}`,
padding: "0 12px",
height: "100%",
maxWidth: 280,
}}
>
<span style={{ color: ACCENT, fontSize: 14 }}>▣</span>
<span style={{ color: "#d4d4d4", fontSize: 14, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
{windowTitle}
</span>
<span style={{ color: "#666", fontSize: 14, marginLeft: 4 }}>×</span>
</div>
{/* New tab + dropdown */}
<span style={{ marginLeft: 8, color: "#666", fontSize: 18 }}>+</span>
<span style={{ marginLeft: 6, color: "#666", fontSize: 14 }}>⌄</span>
<div style={{ flex: 1 }} />
<span style={{ color: "#666", fontSize: 18, padding: "0 12px" }}>—</span>
<span style={{ color: "#666", fontSize: 14, padding: "0 12px" }}>▢</span>
<span style={{ color: "#666", fontSize: 18, padding: "0 14px" }}>×</span>
</div>
{/* Body */}
<div
style={{
flex: 1,
padding: "16px 20px",
color: "#d4d4d4",
fontFamily: MONO,
fontSize: 18,
lineHeight: 1.5,
overflow: "hidden",
display: "flex",
flexDirection: "column",
minHeight: 0,
}}
>
{/* Welcome banner — bordered rounded box */}
<div
style={{
border: `1px solid ${ACCENT}55`,
borderRadius: 10,
padding: "14px 18px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 10,
flexShrink: 0,
}}
>
<div style={{ position: "relative", width: "100%" }}>
<div style={{ position: "absolute", left: -2, top: -22, color: ACCENT, fontSize: 14, background: "#0d0d0f", padding: "0 8px" }}>
Claude Code
</div>
</div>
<div style={{ color: "#ffffff", fontSize: 22, fontWeight: 500 }}>Welcome back {welcomeName}!</div>
<PixelMonster size={80} />
<div style={{ color: "#bdbdbd", fontSize: 16, textAlign: "center", lineHeight: 1.5 }}>
{modelInfo}<br />
{planLabel}<br />
{cwd}
</div>
</div>
{/* Body content varies by state */}
{terminalState === "welcome" && (
<div style={{ flex: 1, marginTop: 18 }}>
<PromptLine text="" caret={Math.floor(frame / 9) % 2 === 0} />
</div>
)}
{terminalState === "typing" && (
<div style={{ flex: 1, marginTop: 18 }}>
<PromptLine text={typedCmd} caret={cmdCaret} />
</div>
)}
{terminalState === "executing" && (
<div style={{ flex: 1, marginTop: 14, display: "flex", flexDirection: "column", gap: 6 }}>
{/* Result lines (e.g. /model opusplan + tree) */}
{(resultLines || []).map((rl, i) => (
<div key={i} style={{ color: i === 0 ? "#ffffff" : "#d4d4d4", whiteSpace: "pre-wrap" }}>
{renderHighlighted(rl)}
</div>
))}
{/* Tool call block */}
{toolCall && (
<div style={{ color: "#ffffff", whiteSpace: "pre-wrap" }}>{toolCall}</div>
)}
{toolDetail && (
<div style={{ color: "#bdbdbd", whiteSpace: "pre-wrap" }}>{toolDetail}</div>
)}
{/* Status / spinner */}
{statusText && (
<div style={{ marginTop: 6, color: "#d4d4d4", display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ color: ACCENT }}>{spin}</span>
<span>{statusText}</span>
</div>
)}
<div style={{ flex: 1 }} />
{/* Empty prompt at bottom + interrupt hint */}
<PromptLine text="" caret={Math.floor(frame / 9) % 2 === 0} />
{showInterruptHint && (
<div style={{ color: "#777", fontSize: 14 }}>esc to interrupt</div>
)}
</div>
)}
</div>
</div>
{/* Watermark-y caption left blank — kinetic captions ride on top globally */}
{/* (no extra label — keep the surface clean for the reel's caption band) */}
<div style={{ display: "none" }} aria-hidden>
{/* dim, mostly to silence unused-vars in headerColor for now */}
{headerColor}
</div>
</AbsoluteFill>
);
};
const PromptLine: React.FC<{ text: string; caret: boolean }> = ({ text, caret }) => (
<div
style={{
borderTop: "1px solid rgba(255,255,255,0.10)",
borderBottom: "1px solid rgba(255,255,255,0.10)",
padding: "10px 0",
display: "flex",
alignItems: "center",
gap: 12,
color: "#d4d4d4",
}}
>
<span style={{ color: ACCENT, fontWeight: 700, fontSize: 20 }}>{">"}</span>
<span style={{ minHeight: 22 }}>
{text}
{caret && (
<span
style={{
display: "inline-block",
width: 10,
height: 22,
background: "#d4d4d4",
verticalAlign: "text-bottom",
marginLeft: 2,
}}
/>
)}
</span>
</div>
);
function renderHighlighted(rl: ResultLine): React.ReactNode {
const { text, highlights } = rl;
if (!highlights || !highlights.length) return text;
// Sort + emit alternating plain/highlight spans
const sorted = [...highlights].sort((a, b) => a.from - b.from);
const out: React.ReactNode[] = [];
let cursor = 0;
sorted.forEach((h, i) => {
if (h.from > cursor) out.push(text.slice(cursor, h.from));
out.push(
<span key={i} style={{ background: ACCENT, color: "#fff", padding: "0 4px", borderRadius: 2 }}>
{text.slice(h.from, h.to)}
</span>,
);
cursor = h.to;
});
if (cursor < text.length) out.push(text.slice(cursor));
return out;
}
function D(durFrames: number, frac: number): number {
return Math.round(durFrames * frac);
}
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: ClaudeCodeTerminal
// 14 props detected · 2 port(s)
Claude Code Terminal
claude_code_terminalClaude Code Terminal — Andy's hand-crafted Remotion scene. 14 props. windowTitle:TEXT, statusText:TEXT.
Workflow Inputs (2)
- Window Title
windowTitleTEXT - Status Text
statusTextTEXT
Config Fields (12)
cwdcommandtoolCallmodelInfoplanLabeltypingCpstoolDetailresultLineswelcomeNameterminalStatebackgroundColorshowInterruptHint
Meta
- Updated
- 4/21/2026