import { useCurrentFrame } from "remotion"; // ─── Design tokens (from DESIGN.md dark mode) ─── const C = { bg: "#09090B", surface: "#18181B", border: "#27272A", text: "#FAFAFA", dim: "#71717A", secondary: "#A1A1AA", accent: "#22D3EE", success: "#22C55E", warning: "#EAB308", error: "#EF4444", orange: "#F97316", } as const; const FONT = "'Geist Mono', 'SF Mono', 'Cascadia Code', monospace"; // ─── Event types ─── export type TerminalEvent = | { type: "type"; text: string; at: number; duration: number } | { type: "print"; text: string; at: number; color?: string } | { type: "blank"; at: number }; // ─── Auto-scroll container ─── const LINE_H = 26; // matches lineHeight function AutoScroll({ lineCount, children }: { lineCount: number; children: React.ReactNode }) { // Estimate visible lines from a reference height (1080 - 60*2 padding - 40 title bar - 48 body pad) // For split-screen the container is smaller, but overflow: hidden on parent clips it anyway. // We use a generous max visible count; the translateY just pins the bottom. const MAX_VISIBLE = 30; const overflow = lineCount - MAX_VISIBLE; const scrollY = overflow > 0 ? overflow * LINE_H : 0; return
{children}
; } // ─── Helpers ─── function Prompt() { return $ ; } function Cursor({ visible }: { visible: boolean }) { return ( ); } function colorize(text: string, color?: string): React.ReactNode { if (color) return {text}; // Auto-colorize patterns return text.split(/(\s+)/).map((token, i) => { // Checkmark if (token === "\u2713") return ( {token} ); // Priority tags if (token === "[urgent]") return ( {token} ); if (token === "[high]") return ( {token} ); if (token === "[medium]") return ( {token} ); if (token === "[low]") return ( {token} ); // Arrow for agent assignment if (token.startsWith("\u2192")) return ( {token} ); // Log level tags if (token === "[info]") return ( {token} ); // IDs and special values if (/^[bmatrs]_/.test(token)) return ( {token} ); return {token}; }); } // ─── Terminal ─── interface TerminalProps { events: TerminalEvent[]; title?: string; } export function Terminal({ events, title }: TerminalProps) { const frame = useCurrentFrame(); const lines: React.ReactNode[] = []; for (const ev of events) { if (ev.at > frame) continue; if (ev.type === "blank") { lines.push(
); continue; } if (ev.type === "print") { lines.push(
{colorize(ev.text, ev.color)}
, ); continue; } if (ev.type === "type") { const elapsed = frame - ev.at; const progress = Math.min(elapsed / ev.duration, 1); const chars = Math.floor(progress * ev.text.length); const visible = ev.text.slice(0, chars); const isDone = progress >= 1; lines.push(
{visible} {!isDone && }
, ); } } // If no active typing, show blinking cursor on last line const lastEvent = events.filter((e) => e.at <= frame).at(-1); const shouldShowIdleCursor = lastEvent && lastEvent.type !== "type" && frame - lastEvent.at > 5; return (
{/* Title bar */}
{title && {title}}
{/* Terminal body — auto-scrolls when content exceeds viewport */}
{lines} {shouldShowIdleCursor && (
)}
); }