diff --git a/website/app/components/terminal-demo.tsx b/website/app/components/terminal-demo.tsx
new file mode 100644
index 0000000..11b7be4
--- /dev/null
+++ b/website/app/components/terminal-demo.tsx
@@ -0,0 +1,559 @@
+"use client";
+
+import { useCallback, useEffect, useRef, useState } from "react";
+
+// ── Types ────────────────────────────────────────────────────────────────────
+
+type TabId = "terminal" | "screen" | "cursor";
+
+type Tab = {
+ id: TabId;
+ label: string;
+ icon: string;
+ color: string;
+ colorClass: string;
+ phases: Phase[];
+};
+
+type Phase = {
+ name: string;
+ duration: number;
+};
+
+// ── Demo data ────────────────────────────────────────────────────────────────
+
+const MACHINES = [
+ { name: "gpu-server", os: "linux" },
+ { name: "staging-01", os: "linux" },
+ { name: "dev-macbook", os: "macos" },
+ { name: "prod-api", os: "linux" },
+];
+
+const RECENT_DIRS = [
+ "~/projects/api-server",
+ "~/projects/ml-pipeline",
+ "~/dotfiles",
+];
+
+const TABS: Tab[] = [
+ {
+ id: "terminal",
+ label: "~ terminal",
+ icon: "~",
+ color: "#22d3ee",
+ colorClass: "text-cyan-400",
+ phases: [
+ { name: "typing", duration: 800 },
+ { name: "picker", duration: 400 },
+ { name: "navigating", duration: 800 },
+ { name: "selecting", duration: 300 },
+ { name: "transition", duration: 1200 },
+ { name: "result", duration: 2500 },
+ ],
+ },
+ {
+ id: "screen",
+ label: "▶ screen",
+ icon: "▶",
+ color: "#e879f9",
+ colorClass: "text-fuchsia-400",
+ phases: [
+ { name: "typing", duration: 800 },
+ { name: "picker", duration: 400 },
+ { name: "navigating", duration: 1200 },
+ { name: "selecting", duration: 300 },
+ { name: "transition", duration: 1800 },
+ ],
+ },
+ {
+ id: "cursor",
+ label: "▣ cursor",
+ icon: "▣",
+ color: "#facc15",
+ colorClass: "text-yellow-400",
+ phases: [
+ { name: "typing", duration: 800 },
+ { name: "picker", duration: 400 },
+ { name: "selecting", duration: 300 },
+ { name: "directory", duration: 600 },
+ { name: "dirSelect", duration: 1200 },
+ { name: "transition", duration: 1800 },
+ ],
+ },
+];
+
+function totalDuration(tab: Tab): number {
+ return tab.phases.reduce((sum, p) => sum + p.duration, 0);
+}
+
+// ── Subcomponents ────────────────────────────────────────────────────────────
+
+function HeaderBox({ subtitle }: { subtitle: string }) {
+ return (
+
+
+ remote control
+ v0.1.0
+
+
{subtitle}
+
+ );
+}
+
+function MachineList({
+ selected,
+ highlight,
+}: {
+ selected: number;
+ highlight: boolean;
+}) {
+ return (
+
+ {MACHINES.map((m, i) => {
+ const isSel = i === selected;
+ const flash = isSel && highlight;
+ return (
+
+ {isSel ? ">" : " "}
+ {m.name}
+ {m.os}
+
+ );
+ })}
+
+ );
+}
+
+function ModeBar({
+ tab,
+ modeLabel,
+}: {
+ tab: Tab;
+ modeLabel: string;
+}) {
+ return (
+
+
+ {modeLabel}
+ (shift+tab to cycle)
+
+
+ tab next machine · / to search
+
+
+ );
+}
+
+function DirectoryPicker({ selectedDir }: { selectedDir: number }) {
+ return (
+
+
+ d
+ irectory (enter for ~)
+
+
+
recent paths
+ {RECENT_DIRS.map((dir, i) => {
+ const isSel = i === selectedDir;
+ return (
+
+ {isSel ? ">" : " "}
+ {dir}
+
+ );
+ })}
+
+
+ );
+}
+
+// ── Typewriter hook ──────────────────────────────────────────────────────────
+
+function useTypewriter(
+ text: string,
+ active: boolean,
+ signal: AbortSignal,
+ onDone: () => void,
+) {
+ const [typed, setTyped] = useState("");
+
+ useEffect(() => {
+ if (!active) {
+ setTyped("");
+ return;
+ }
+
+ let i = 0;
+ setTyped("");
+
+ function next() {
+ if (signal.aborted) return;
+ if (i < text.length) {
+ i++;
+ setTyped(text.slice(0, i));
+ const delay = 40 + Math.random() * 80;
+ setTimeout(next, delay);
+ } else {
+ onDone();
+ }
+ }
+
+ const start = setTimeout(next, 200);
+ return () => clearTimeout(start);
+ }, [active, text, signal, onDone]);
+
+ return typed;
+}
+
+// ── Main component ───────────────────────────────────────────────────────────
+
+export function TerminalDemo() {
+ const [activeTab, setActiveTab] = useState(0);
+ const [phaseIndex, setPhaseIndex] = useState(-1); // -1 = not started
+ const [progress, setProgress] = useState(0);
+ const [isVisible, setIsVisible] = useState(false);
+ const [reducedMotion, setReducedMotion] = useState(false);
+
+ const containerRef = useRef(null);
+ const abortRef = useRef(null);
+ const startTimeRef = useRef(0);
+ const rafRef = useRef(0);
+
+ const tab = TABS[activeTab];
+ const phaseName = phaseIndex >= 0 ? tab.phases[phaseIndex]?.name ?? "done" : "idle";
+ const tabTotal = totalDuration(tab);
+
+ // ── Reduced motion check ───────────────────────────────────────────────
+
+ useEffect(() => {
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
+ setReducedMotion(mq.matches);
+ const handler = (e: MediaQueryListEvent) => setReducedMotion(e.matches);
+ mq.addEventListener("change", handler);
+ return () => mq.removeEventListener("change", handler);
+ }, []);
+
+ // ── Intersection observer ──────────────────────────────────────────────
+
+ useEffect(() => {
+ const el = containerRef.current;
+ if (!el) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => setIsVisible(entry.isIntersecting),
+ { threshold: 0.3 },
+ );
+ observer.observe(el);
+ return () => observer.disconnect();
+ }, []);
+
+ // ── Phase sequencer ────────────────────────────────────────────────────
+
+ const advancePhase = useCallback(() => {
+ setPhaseIndex((prev) => {
+ const next = prev + 1;
+ if (next >= TABS[activeTab].phases.length) {
+ // Auto-advance to next tab
+ setTimeout(() => {
+ setActiveTab((t) => (t + 1) % TABS.length);
+ }, 0);
+ return prev;
+ }
+ return next;
+ });
+ }, [activeTab]);
+
+ const runAnimation = useCallback(
+ (tabIdx: number) => {
+ // Cancel previous
+ abortRef.current?.abort();
+ const ac = new AbortController();
+ abortRef.current = ac;
+
+ setPhaseIndex(0);
+ setProgress(0);
+ startTimeRef.current = performance.now();
+
+ // Progress bar RAF
+ const tabDur = totalDuration(TABS[tabIdx]);
+ function tick() {
+ if (ac.signal.aborted) return;
+ const elapsed = performance.now() - startTimeRef.current;
+ const p = Math.min(elapsed / tabDur, 1);
+ setProgress(p);
+ if (p < 1) {
+ rafRef.current = requestAnimationFrame(tick);
+ }
+ }
+ rafRef.current = requestAnimationFrame(tick);
+
+ // Phase timer
+ let phaseIdx = 0;
+ function scheduleNext() {
+ if (ac.signal.aborted) return;
+ const phases = TABS[tabIdx].phases;
+ if (phaseIdx >= phases.length) return;
+
+ const dur = phases[phaseIdx].duration;
+ setTimeout(() => {
+ if (ac.signal.aborted) return;
+ phaseIdx++;
+ if (phaseIdx < phases.length) {
+ setPhaseIndex(phaseIdx);
+ scheduleNext();
+ } else {
+ // Tab done, auto-advance
+ setTimeout(() => {
+ if (ac.signal.aborted) return;
+ const nextTab = (tabIdx + 1) % TABS.length;
+ setActiveTab(nextTab);
+ }, 300);
+ }
+ }, dur);
+ }
+ scheduleNext();
+
+ return () => {
+ ac.abort();
+ cancelAnimationFrame(rafRef.current);
+ };
+ },
+ [],
+ );
+
+ // Trigger animation when tab changes and visible
+ useEffect(() => {
+ if (!isVisible || reducedMotion) return;
+ const cleanup = runAnimation(activeTab);
+ return cleanup;
+ }, [activeTab, isVisible, reducedMotion, runAnimation]);
+
+ // ── Typewriter for prompt ──────────────────────────────────────────────
+
+ const signal = abortRef.current?.signal ?? new AbortController().signal;
+ const isTyping = phaseName === "typing";
+ const typedText = useTypewriter("rc", isTyping, signal, advancePhase);
+
+ // ── Compute derived state ──────────────────────────────────────────────
+
+ // Selected machine index based on phase
+ let selectedMachine = 0;
+ let highlightFlash = false;
+
+ if (tab.id === "terminal") {
+ if (phaseName === "navigating" || phaseName === "selecting" || phaseName === "transition" || phaseName === "result") {
+ selectedMachine = 1; // staging-01
+ }
+ if (phaseName === "selecting") highlightFlash = true;
+ } else if (tab.id === "screen") {
+ if (phaseName === "navigating") selectedMachine = 1; // animating
+ if (phaseName === "selecting" || phaseName === "transition") {
+ selectedMachine = 2; // dev-macbook
+ }
+ if (phaseName === "selecting") highlightFlash = true;
+ } else if (tab.id === "cursor") {
+ if (phaseName === "selecting") highlightFlash = true;
+ }
+
+ // Navigating animation for screen tab - move through machines
+ const [navIndex, setNavIndex] = useState(0);
+ useEffect(() => {
+ if (tab.id === "screen" && phaseName === "navigating") {
+ setNavIndex(0);
+ const t1 = setTimeout(() => setNavIndex(1), 400);
+ const t2 = setTimeout(() => setNavIndex(2), 800);
+ return () => { clearTimeout(t1); clearTimeout(t2); };
+ }
+ if (tab.id === "terminal" && phaseName === "navigating") {
+ setNavIndex(0);
+ const t1 = setTimeout(() => setNavIndex(1), 400);
+ return () => clearTimeout(t1);
+ }
+ }, [tab.id, phaseName]);
+
+ // Directory selection animation
+ const [dirIndex, setDirIndex] = useState(0);
+ useEffect(() => {
+ if (phaseName === "dirSelect") {
+ setDirIndex(0);
+ const t1 = setTimeout(() => setDirIndex(1), 400);
+ const t2 = setTimeout(() => setDirIndex(2), 800);
+ return () => { clearTimeout(t1); clearTimeout(t2); };
+ }
+ }, [phaseName]);
+
+ // Effective selected for navigating phases
+ let effectiveSelected = selectedMachine;
+ if (tab.id === "screen" && phaseName === "navigating") effectiveSelected = navIndex;
+ if (tab.id === "terminal" && phaseName === "navigating") effectiveSelected = navIndex;
+
+ // ── Render helpers ─────────────────────────────────────────────────────
+
+ function renderContent() {
+ // Reduced motion: show static final state
+ if (reducedMotion) {
+ return renderPickerScreen(tab, 1, false);
+ }
+
+ // Idle / typing: show prompt
+ if (phaseName === "idle" || phaseName === "typing") {
+ return (
+
+
+ $
+ {typedText}
+ ▋
+
+
+ );
+ }
+
+ // Transition messages
+ if (phaseName === "transition") {
+ const messages: Record = {
+ terminal: "Copying SSH key to staging-01...",
+ screen: "Opening Screen Sharing to dev-macbook...",
+ cursor: "Opening Cursor to gpu-server...",
+ };
+ return (
+
+ );
+ }
+
+ // Result (terminal only)
+ if (phaseName === "result") {
+ return (
+
+
+ user@staging-01
+ :
+ ~
+ $
+ ▋
+
+
+ );
+ }
+
+ // Directory phases (cursor tab)
+ if (phaseName === "directory" || phaseName === "dirSelect") {
+ return renderDirectoryScreen();
+ }
+
+ // Picker phases
+ return renderPickerScreen(tab, effectiveSelected, highlightFlash);
+ }
+
+ function renderPickerScreen(t: Tab, sel: number, flash: boolean) {
+ const subtitles: Record = {
+ terminal: "quickly access remote machines",
+ screen: "quickly access remote machines",
+ cursor: "quickly access remote machines",
+ };
+
+ return (
+
+
+
+
+
+ );
+ }
+
+ function renderDirectoryScreen() {
+ return (
+
+
+
+
+
+ esc to go back · tab to cycle · enter to continue
+
+
+
+ );
+ }
+
+ // ── Tab click handler ──────────────────────────────────────────────────
+
+ function handleTabClick(idx: number) {
+ setActiveTab(idx);
+ }
+
+ // ── Render ─────────────────────────────────────────────────────────────
+
+ return (
+
+
+
+ {/* Window chrome */}
+
+ {/* macOS dots */}
+
+
+ {/* Tabs */}
+
+ {TABS.map((t, i) => {
+ const isActive = i === activeTab;
+ return (
+
+ );
+ })}
+
+
+
+ {/* Terminal content */}
+
+ {renderContent()}
+
+
+
+
+ );
+}
diff --git a/website/app/globals.css b/website/app/globals.css
index 74f9ac0..68cdcb2 100644
--- a/website/app/globals.css
+++ b/website/app/globals.css
@@ -49,6 +49,15 @@ html {
}
/* Emerald gradient text utility */
+@keyframes blink-cursor {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0; }
+}
+
+.blink-cursor {
+ animation: blink-cursor 1s step-end infinite;
+}
+
.text-emerald-gradient {
background: linear-gradient(135deg, #10b981 0%, #34d399 50%, #6ee7b7 100%);
-webkit-background-clip: text;
diff --git a/website/app/page.tsx b/website/app/page.tsx
index e02af1a..876b2ea 100644
--- a/website/app/page.tsx
+++ b/website/app/page.tsx
@@ -2,6 +2,7 @@
import { useState } from "react";
import { Terminal, Monitor, Code, Sparkle, Copy, Check } from "lucide-react";
+import { TerminalDemo } from "./components/terminal-demo";
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
return (
@@ -102,6 +103,8 @@ export default function Home() {
+ {/* Terminal Demo */}
+
{/* Features */}