@@ -517,7 +603,7 @@ function ChatMessages({ items }: { items: ChatItem[] }) {
}
return (
{item.text}
@@ -537,21 +623,12 @@ function ChatArea() {
);
}
-function ImplementPane() {
- return (
-
- {TERMINAL_LINES.map((line, i) => (
-
+ {TERMINAL_LINES.map((line) => (
+
{line.text}
))}
@@ -609,37 +686,9 @@ function ExplorerSidebar() {
{/* Diff lines */}
- {DIFF_LINES.map((line, i) => {
- const isAdd = line.type === "add";
- const isRemove = line.type === "remove";
- const lineBg = isAdd
- ? "bg-mock-diff-add"
- : isRemove
- ? "bg-mock-diff-remove"
- : "bg-mock-surface1";
- const lineNumCls = isAdd
- ? "text-mock-green-400"
- : isRemove
- ? "text-mock-red"
- : "text-mock-fg-muted";
-
- return (
-
-
-
- {line.ln ?? ""}
-
-
-
- {line.tokens?.map((tok, j) => (
-
- {tok.text}
-
- ))}
-
-
- );
- })}
+ {DIFF_LINES.map((line) => (
+
+ ))}
{/* Collapsed file rows */}
@@ -684,6 +733,7 @@ function ExplorerSidebar() {
const DOT_SEQUENCE = [0, 1, 3, 5, 4, 2] as const;
const SYNCED_LOADER_DURATION_MS = 950;
const DOT_COUNT = 6;
+const DOT_INDICES = Array.from({ length: DOT_COUNT }, (_, i) => i);
const STEP_MS = SYNCED_LOADER_DURATION_MS / DOT_COUNT; // 158.333…ms per step
interface SyncedLoaderDotProps {
@@ -743,8 +793,13 @@ function SyncedLoader({ size = 11 }: { size?: number }) {
return (
- {Array.from({ length: DOT_COUNT }).map((_, dotIndex) => (
-
+ {DOT_INDICES.map((dotIndex) => (
+
))}
@@ -792,57 +847,7 @@ function Sidebar() {
{/* Workspace rows — indented one level */}
{project.workspaces.map((workspace) => (
-
-
-
- {workspace.status === "syncing" ? (
-
- ) : (
- <>
- {workspace.kind === "worktree" ? (
-
- ) : (
-
- )}
- {workspace.status !== "idle" && (
-
-
-
- )}
- >
- )}
-
-
- {workspace.name}
-
- {workspace.diffStat && (
-
-
- +{workspace.diffStat.additions}
-
-
- -{workspace.diffStat.deletions}
-
-
- )}
-
- {workspace.pr && (
-
-
-
- #{workspace.pr.number} ·{" "}
- {workspace.pr.state === "open"
- ? "Open"
- : workspace.pr.state === "merged"
- ? "Merged"
- : "Closed"}
-
-
- )}
-
+
))}
))}
@@ -867,6 +872,75 @@ function Sidebar() {
);
}
+const TERMINAL_TABS: TabDef[] = [
+ { name: "npm run dev", provider: "terminal", done: false, active: true },
+];
+
+function TitleBarLeft() {
+ return (
+
+
+
+
main
+
+ acme/returns-app
+
+
+
+
+
+
+ );
+}
+
+function TitleBarRight() {
+ return (
+
+ );
+}
+
+function DesktopSplitPanes() {
+ return (
+
+ {/* Left pane: all agent tabs + chat */}
+
+
+ {/* Resize handle */}
+
+
+ {/* Right pane: terminal only */}
+
+
+
+
+
+ );
+}
+
// ── Desktop Mockup ──────────────────────────────────────
function DesktopMockup() {
@@ -908,64 +982,11 @@ function DesktopMockup() {
{...fade(D.titleBar)}
className="flex items-center h-10 px-2 bg-mock-surface0 border-b border-mock-border flex-shrink-0"
>
-
-
-
-
- main
-
-
- acme/returns-app
-
-
-
-
-
-
-
-
+
+
- {/* Split panes */}
-
- {/* Left pane: all agent tabs + chat */}
-
-
- {/* Resize handle */}
-
-
- {/* Right pane: terminal only */}
-
-
-
-
-
+
{/* Explorer sidebar — full height, pushes center column */}
diff --git a/packages/website/src/components/landing-page.tsx b/packages/website/src/components/landing-page.tsx
index d35f9a795..b1468916c 100644
--- a/packages/website/src/components/landing-page.tsx
+++ b/packages/website/src/components/landing-page.tsx
@@ -12,7 +12,6 @@ import {
// reference and doesn't trigger jsx-no-new-object-as-prop.
const FADE_IN_UP = { opacity: 0, y: 20 };
const FADE_IN = { opacity: 1, y: 0 };
-const FADE_IN_UP_SMALL = { opacity: 0, y: 16 };
const FADE_IN_UP_TINY = { opacity: 0, y: -10 };
const FADE_IN_UP_XL = { opacity: 0, y: 30 };
const FADE_IN_UP_40 = { opacity: 0, y: 40 };
@@ -29,7 +28,6 @@ const EASE_OUT_015: Transition = { duration: 0.15, ease: "easeOut" };
const DURATION_05: Transition = { duration: 0.5 };
const VIEWPORT_60 = { once: true, margin: "-60px" };
-const VIEWPORT_40 = { once: true, margin: "-40px" };
const SVG_OVERFLOW_VISIBLE_STYLE = { overflow: "visible" as const };
const PHONE_PERSPECTIVE_STYLE = { minHeight: 480, perspective: 1200 };
@@ -255,14 +253,22 @@ function Hero({ title, subtitle }: { title: React.ReactNode; subtitle: string })
);
}
+const CLAUDE_CODE_BADGE_ICON =
;
+const CODEX_BADGE_ICON =
;
+const OPENCODE_BADGE_ICON =
;
+const COPILOT_BADGE_ICON =
;
+const PI_BADGE_ICON =
;
+
function AgentBadge({ name, icon }: { name: string; icon: React.ReactNode }) {
const [hovered, setHovered] = React.useState(false);
+ const handleMouseEnter = React.useCallback(() => setHovered(true), []);
+ const handleMouseLeave = React.useCallback(() => setHovered(false), []);
return (
setHovered(true)}
- onMouseLeave={() => setHovered(false)}
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
>
{icon}
@@ -308,22 +314,6 @@ function FeatureSection({
);
}
-function PrinciplesSection() {
- return (
-
-
- Here's what's under the hood.
-
-
- );
-}
-
function MultiProviderSection() {
const providers = [
{ name: "Claude Code", icon: },
@@ -444,6 +434,19 @@ function SelfHostedDiagram() {
const clientRefs = React.useRef<(HTMLDivElement | null)[]>([]);
const hostRefs = React.useRef<(HTMLDivElement | null)[]>([]);
const centerRef = React.useRef(null);
+
+ const setClientRef = React.useCallback(
+ (index: number) => (el: HTMLDivElement | null) => {
+ clientRefs.current[index] = el;
+ },
+ [],
+ );
+ const setHostRef = React.useCallback(
+ (index: number) => (el: HTMLDivElement | null) => {
+ hostRefs.current[index] = el;
+ },
+ [],
+ );
const [paths, setPaths] = React.useState<{ left: string[]; right: string[] }>({
left: [],
right: [],
@@ -546,10 +549,10 @@ function SelfHostedDiagram() {
style={SVG_OVERFLOW_VISIBLE_STYLE}
>
{[...paths.left, ...paths.right].map(
- (d, i) =>
+ (d) =>
d && (
(
{
- clientRefs.current[i] = el;
- }}
+ ref={setClientRef(i)}
className="flex items-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-5 py-4 backdrop-blur-sm"
>
{c.icon}
@@ -597,9 +598,7 @@ function SelfHostedDiagram() {
{hosts.map((h, i) => (
{
- hostRefs.current[i] = el;
- }}
+ ref={setHostRef(i)}
className="flex items-center gap-3 rounded-xl border border-white/10 bg-white/[0.03] px-5 py-4 backdrop-blur-sm"
>
@@ -757,12 +756,14 @@ function VoiceBar({ index, barCount }: VoiceBarProps) {
return ;
}
+const VOICE_BAR_COUNT = 48;
+const VOICE_BAR_INDICES = Array.from({ length: VOICE_BAR_COUNT }, (_, i) => i);
+
function VoiceWaveform() {
- const barCount = 48;
return (
- {Array.from({ length: barCount }).map((_, i) => (
-
+ {VOICE_BAR_INDICES.map((i) => (
+
))}
);
@@ -874,6 +875,25 @@ function useVoiceConversation() {
return { dictationWordIndex, responseWordIndex, showResponse };
}
+function makeWordKey(words: string[], i: number): string {
+ const word = words[i];
+ let occurrence = 0;
+ for (let j = 0; j < i; j++) {
+ if (words[j] === word) occurrence++;
+ }
+ return `${word}#${occurrence}`;
+}
+
+function WordSpan({ word, confirmed }: { word: string; confirmed: boolean }) {
+ return (
+
+ {word}{" "}
+
+ );
+}
+
function StreamingWords({
words,
wordIndex,
@@ -894,14 +914,7 @@ function StreamingWords({
{words.map((word, i) => {
if (i >= wordIndex) return null;
const confirmed = i < wordIndex - confirmLag;
- return (
-
- {word}{" "}
-
- );
+ return ;
})}
@@ -1005,11 +1018,11 @@ function GetStarted() {
Supports
-
} />
-
} />
-
} />
-
} />
-
} />
+
+
+
+
+
@@ -1035,23 +1048,27 @@ function DownloadButton() {
);
}
+const SERVER_INSTALL_TRIGGER = (
+
+
+
+);
+
+const SERVER_INSTALL_FOOTNOTE = (
+ <>
+ Requires Node.js 18+. Run
paseo to start the
+ daemon.
+ >
+);
+
function ServerInstallButton() {
return (
-
-
- }
+ trigger={SERVER_INSTALL_TRIGGER}
title="Run agents on a remote machine"
description="For headless machines you want to connect to from the Paseo apps. The desktop app already includes a built-in daemon."
command="npm install -g @getpaseo/cli && paseo"
- footnote={
- <>
- Requires Node.js 18+. Run paseo to start
- the daemon.
- >
- }
+ footnote={SERVER_INSTALL_FOOTNOTE}
/>
);
}
@@ -1141,61 +1158,6 @@ function PiIcon(props: React.SVGProps) {
);
}
-function AppStoreIcon(props: React.SVGProps) {
- return (
-
- );
-}
-
-function ChevronDownIcon(props: React.SVGProps) {
- return (
-
- );
-}
-
-function Step({ number, children }: { number: number; children: React.ReactNode }) {
- return (
-
-
- {number}
-
-
{children}
-
- );
-}
-
const bashKeywords = new Set([
"while",
"do",
@@ -1210,167 +1172,134 @@ const bashKeywords = new Set([
]);
const bashCommands = new Set(["paseo", "echo", "jq"]);
+function tokenizeBashComment(code: string, i: number): { node: React.ReactNode; len: number } {
+ const end = code.indexOf("\n", i);
+ const comment = end === -1 ? code.slice(i) : code.slice(i, end);
+ return {
+ node: {comment},
+ len: comment.length,
+ };
+}
+
+function tokenizeBashDoubleQuoted(code: string, i: number): { node: React.ReactNode; len: number } {
+ let j = i + 1;
+ while (j < code.length && code[j] !== '"') {
+ if (code[j] === "\\") j++;
+ j++;
+ }
+ const str = code.slice(i, j + 1);
+ return { node: {str}, len: str.length };
+}
+
+function tokenizeBashSingleQuoted(code: string, i: number): { node: React.ReactNode; len: number } {
+ let j = i + 1;
+ while (j < code.length && code[j] !== "'") j++;
+ const str = code.slice(i, j + 1);
+ return { node: {str}, len: str.length };
+}
+
+function tokenizeBashDollar(code: string, i: number): { node: React.ReactNode; len: number } {
+ if (code[i + 1] === "(") {
+ return { node: $(, len: 2 };
+ }
+ let j = i + 1;
+ while (j < code.length && /\w/.test(code[j])) j++;
+ return {
+ node: {code.slice(i, j)},
+ len: j - i,
+ };
+}
+
+function tokenizeBashFlag(code: string, i: number): { node: React.ReactNode; len: number } {
+ let j = i;
+ if (code[j + 1] === "-") j++;
+ j++;
+ while (j < code.length && /[\w-]/.test(code[j])) j++;
+ return {
+ node: {code.slice(i, j)},
+ len: j - i,
+ };
+}
+
+function tokenizeBashWord(code: string, i: number): { node: React.ReactNode; len: number } {
+ let j = i;
+ while (j < code.length && /\w/.test(code[j])) j++;
+ const word = code.slice(i, j);
+ const len = j - i;
+ if (bashKeywords.has(word)) {
+ return { node: {word}, len };
+ }
+ if (bashCommands.has(word)) {
+ return { node: {word}, len };
+ }
+ return { node: word, len };
+}
+
+function isBashFlagStart(code: string, i: number): boolean {
+ return (
+ code[i] === "-" &&
+ (i === 0 || /\s/.test(code[i - 1])) &&
+ i + 1 < code.length &&
+ /[\w-]/.test(code[i + 1])
+ );
+}
+
+function isBashCommentStart(code: string, i: number): boolean {
+ return code[i] === "#" && (i === 0 || /[\s(]/.test(code[i - 1]));
+}
+
+function tokenizeBashChar(code: string, i: number): { node: React.ReactNode; len: number } {
+ const c = code[i];
+ if (c === "|" || (c === "&" && code[i + 1] === "&")) {
+ const op = c === "|" ? "|" : "&&";
+ return { node: {op}, len: op.length };
+ }
+ if (c === "\\") return { node: \, len: 1 };
+ if (c === ")") return { node: ), len: 1 };
+ return { node: c, len: 1 };
+}
+
+function nextBashToken(code: string, i: number): { node: React.ReactNode; len: number } {
+ if (isBashCommentStart(code, i)) return tokenizeBashComment(code, i);
+ if (code[i] === '"') return tokenizeBashDoubleQuoted(code, i);
+ if (code[i] === "'") return tokenizeBashSingleQuoted(code, i);
+ if (code[i] === "$") return tokenizeBashDollar(code, i);
+ if (isBashFlagStart(code, i)) return tokenizeBashFlag(code, i);
+ if (/[a-zA-Z_]/.test(code[i])) return tokenizeBashWord(code, i);
+ return tokenizeBashChar(code, i);
+}
+
function highlightBash(code: string): React.ReactNode {
const tokens: React.ReactNode[] = [];
let i = 0;
let key = 0;
while (i < code.length) {
- if (code[i] === "#" && (i === 0 || /[\s(]/.test(code[i - 1]))) {
- const end = code.indexOf("\n", i);
- const comment = end === -1 ? code.slice(i) : code.slice(i, end);
- tokens.push(
-
- {comment}
- ,
- );
- i += comment.length;
- continue;
+ const { node, len } = nextBashToken(code, i);
+ if (React.isValidElement(node)) {
+ tokens.push(React.cloneElement(node, { key: key++ }));
+ } else {
+ tokens.push(node);
}
-
- if (code[i] === '"') {
- let j = i + 1;
- while (j < code.length && code[j] !== '"') {
- if (code[j] === "\\") j++;
- j++;
- }
- const str = code.slice(i, j + 1);
- tokens.push(
-
- {str}
- ,
- );
- i = j + 1;
- continue;
- }
-
- if (code[i] === "'") {
- let j = i + 1;
- while (j < code.length && code[j] !== "'") j++;
- const str = code.slice(i, j + 1);
- tokens.push(
-
- {str}
- ,
- );
- i = j + 1;
- continue;
- }
-
- if (code[i] === "$") {
- if (code[i + 1] === "(") {
- tokens.push(
-
- $(
- ,
- );
- i += 2;
- continue;
- }
- let j = i + 1;
- while (j < code.length && /\w/.test(code[j])) j++;
- tokens.push(
-
- {code.slice(i, j)}
- ,
- );
- i = j;
- continue;
- }
-
- if (
- code[i] === "-" &&
- (i === 0 || /\s/.test(code[i - 1])) &&
- i + 1 < code.length &&
- /[\w-]/.test(code[i + 1])
- ) {
- let j = i;
- if (code[j + 1] === "-") j++;
- j++;
- while (j < code.length && /[\w-]/.test(code[j])) j++;
- tokens.push(
-
- {code.slice(i, j)}
- ,
- );
- i = j;
- continue;
- }
-
- if (/[a-zA-Z_]/.test(code[i])) {
- let j = i;
- while (j < code.length && /\w/.test(code[j])) j++;
- const word = code.slice(i, j);
- if (bashKeywords.has(word)) {
- tokens.push(
-
- {word}
- ,
- );
- } else if (bashCommands.has(word)) {
- tokens.push(
-
- {word}
- ,
- );
- } else {
- tokens.push(word);
- key++;
- }
- i = j;
- continue;
- }
-
- if (code[i] === "|" || (code[i] === "&" && code[i + 1] === "&")) {
- const op = code[i] === "|" ? "|" : "&&";
- tokens.push(
-
- {op}
- ,
- );
- i += op.length;
- continue;
- }
-
- if (code[i] === "\\") {
- tokens.push(
-
- \
- ,
- );
- i++;
- continue;
- }
-
- if (code[i] === ")") {
- tokens.push(
-
- )
- ,
- );
- i++;
- continue;
- }
-
- tokens.push(code[i]);
- i++;
+ i += len;
}
- return <>{tokens}>;
+ return tokens;
}
function CLICodeBlock({ children }: { children: string }) {
const [copied, setCopied] = React.useState(false);
- function handleCopy() {
+ const handleCopy = React.useCallback(() => {
navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
- }
+ }, [children]);
return (