feat(web): animated mesh hero with peer constellation + comparison section
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

- New hero section with a live animated mesh background: three equal
  Claude Code peers in a triangle layout + six desaturated background
  peers, all rendered pixel-perfect from pure React/CSS using the exact
  Unicode characters and colors from Claude Code's own source.
- User prompts type into the bottom prompt-input box and "submit" to
  scrollback (matching real Claude Code behavior). Mesh sends fly as
  envelope icons with fading trails between peers; receivers pulse on
  arrival. Dynamic routing by peer displayName.
- Radial vignette overlay keeps the hero title crisp while letting the
  corner peers pulse visibly around the edges. Top/bottom linear fades
  bleed into adjacent sections.
- Responsive scaling via ResizeObserver: cover-fit in hero bg context,
  contain-fit for standalone use.
- Features section: added Skills, MCPs, and Commands as the first
  three tabs — the mesh's real differentiators. Updated subtitle copy.
- New "Where claudemesh fits" section positioned between Features and
  WhatIsClaudemesh: four-card comparison (vs MCP, vs subagents,
  vs OpenClaw, and the positive claim) framing claudemesh as a wire
  between Claude Code sessions, not a replacement for any of them.

All work is additive: 10 new files in apps/web/src/modules/marketing/
home/fake-claude-code/ plus hero-mesh-animation.tsx, hero-with-mesh.tsx,
and where-mesh-fits.tsx. Single edit each to features.tsx and
(marketing)/page.tsx to swap in the new hero and mount the new section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-10 23:39:24 +01:00
parent c8ae6462e3
commit 5e97d48cd5
15 changed files with 1867 additions and 3 deletions

View File

@@ -0,0 +1,52 @@
import { fccTheme } from "./theme";
export type ClawdPose = "default" | "arms-up" | "look-left" | "look-right";
const APPLE_EYES: Record<ClawdPose, string> = {
default: " \u2597 \u2596 ",
"look-left": " \u2598 \u2598 ",
"look-right": " \u259d \u259d ",
"arms-up": " \u2597 \u2596 ",
};
export function Clawd({ pose = "default" }: { pose?: ClawdPose }) {
const monoStyle: React.CSSProperties = {
fontFamily: fccTheme.fontMono,
color: fccTheme.clawdBody,
lineHeight: 1,
letterSpacing: 0,
fontVariantLigatures: "none",
fontFeatureSettings: '"liga" 0, "calt" 0',
whiteSpace: "pre",
};
const eyesStyle: React.CSSProperties = {
backgroundColor: fccTheme.clawdBody,
color: fccTheme.clawdBackground,
};
const bodyRowStyle: React.CSSProperties = {
backgroundColor: fccTheme.clawdBody,
color: fccTheme.clawdBody,
};
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
...monoStyle,
}}
aria-label="Claude Code mascot"
>
<div>
<span>{"\u2597"}</span>
<span style={eyesStyle}>{APPLE_EYES[pose]}</span>
<span>{"\u2596"}</span>
</div>
<div style={bodyRowStyle}>{" "}</div>
<div>{"\u2598\u2598 \u259d\u259d"}</div>
</div>
);
}

View File

@@ -0,0 +1,327 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { Session, type SessionEvent, type SessionStep } from "./session";
import { fccTheme } from "./theme";
type Point = { x: number; y: number };
type SessionConfig = {
id: string;
/** Display name used by mesh-send `to` fields to route particles */
displayName: string;
title: string;
cwd: string;
script: SessionStep[];
startDelayMs?: number;
position: {
xPct: number;
yPct: number;
scale?: number;
rotate?: number;
opacity?: number;
zIndex?: number;
/** 0..1 — 1 is full color, 0 is grayscale */
saturate?: number;
/** pixels — adds depth-of-field bokeh blur to background peers */
blurPx?: number;
};
};
type ArcConfig = {
fromId: string;
toId: string;
triggerStepKind: "mesh-send";
};
type FlyingParticle = {
id: number;
fromId: string;
toId: string;
bornAt: number;
};
type MeshHeroProps = {
sessions: SessionConfig[];
arcs?: ArcConfig[];
width?: number;
height?: number;
};
const PARTICLE_LIFE_MS = 1100;
const TRAIL_SEGMENTS = 18;
const TRAIL_SPAN = 0.34;
const ICON_W = 38;
const ICON_H = 26;
export function MeshHero({
sessions,
arcs = [],
width = 1440,
height = 720,
}: MeshHeroProps) {
const containerRef = useRef<HTMLDivElement>(null);
const anchorsRef = useRef<Record<string, Point>>({});
const [particles, setParticles] = useState<FlyingParticle[]>([]);
const particleIdRef = useRef(0);
const [, forceTick] = useState(0);
const [reactions, setReactions] = useState<
Record<string, { nonce: number; kind: "receive" | "send" | "arrive" }>
>({});
const reactionTimersRef = useRef<Record<string, ReturnType<typeof setTimeout>>>(
{},
);
const arrivedParticlesRef = useRef<Set<number>>(new Set());
const bumpReaction = (
sessionId: string,
kind: "receive" | "send" | "arrive",
) => {
setReactions((prev) => ({
...prev,
[sessionId]: { nonce: (prev[sessionId]?.nonce ?? 0) + 1, kind },
}));
};
useEffect(() => {
let raf = 0;
const loop = () => {
forceTick((n) => (n + 1) % 1_000_000);
raf = requestAnimationFrame(loop);
};
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, []);
useEffect(() => {
if (particles.length === 0) return;
const now = performance.now();
const next = particles.filter((p) => now - p.bornAt < PARTICLE_LIFE_MS);
if (next.length !== particles.length) setParticles(next);
});
const handleEvent = (e: SessionEvent) => {
if (e.kind !== "mesh-send") return;
// Resolve destination by matching the mesh-send `to` field against
// session displayNames. Fall back to the configured arcs if provided.
const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
const target = normalize(e.to);
const toSession = sessions.find(
(s) => normalize(s.displayName) === target,
);
let fromId = e.sessionId;
let toId = toSession?.id;
if (!toId) {
const arc = arcs.find((a) => a.fromId === e.sessionId);
if (!arc) return;
toId = arc.toId;
}
if (fromId === toId) return;
bumpReaction(fromId, "send");
const id = particleIdRef.current++;
setParticles((prev) => [
...prev,
{
id,
fromId,
toId,
bornAt: performance.now(),
},
]);
const timer = setTimeout(
() => bumpReaction(toId!, "arrive"),
PARTICLE_LIFE_MS - 60,
);
reactionTimersRef.current[`${id}`] = timer;
};
useEffect(() => {
return () => {
Object.values(reactionTimersRef.current).forEach(clearTimeout);
};
}, []);
const setAnchor = (id: string) => (el: HTMLDivElement | null) => {
if (!el || !containerRef.current) return;
const container = containerRef.current.getBoundingClientRect();
const rect = el.getBoundingClientRect();
anchorsRef.current[id] = {
x: rect.left - container.left + rect.width / 2,
y: rect.top - container.top + rect.height / 2,
};
};
const arcForParticle = (fromId: string, toId: string) => {
const from = anchorsRef.current[fromId];
const to = anchorsRef.current[toId];
if (!from || !to) return null;
const midX = (from.x + to.x) / 2;
const midY = (from.y + to.y) / 2 - Math.abs(to.x - from.x) * 0.08 - 30;
return { from, to, midX, midY };
};
return (
<div
ref={containerRef}
style={{
position: "relative",
width,
height,
background:
"radial-gradient(ellipse at 50% 40%, rgba(215,119,87,0.07) 0%, rgba(0,0,0,0) 55%), #0a0a0a",
overflow: "hidden",
}}
>
{sessions.map((s) => {
const left = (s.position.xPct / 100) * width;
const top = (s.position.yPct / 100) * height;
const scale = s.position.scale ?? 1;
const rotate = s.position.rotate ?? 0;
const opacity = s.position.opacity ?? 1;
const zIndex = s.position.zIndex ?? 1;
const saturate = s.position.saturate ?? 1;
const blurPx = s.position.blurPx ?? 0;
const filters = [
"drop-shadow(0 30px 50px rgba(0,0,0,0.6))",
saturate !== 1 ? `saturate(${saturate})` : "",
blurPx > 0 ? `blur(${blurPx}px)` : "",
]
.filter(Boolean)
.join(" ");
return (
<div
key={s.id}
ref={setAnchor(s.id)}
style={{
position: "absolute",
left,
top,
transform: `translate(-50%, -50%) scale(${scale}) rotate(${rotate}deg)`,
transformOrigin: "center center",
filter: filters,
opacity,
zIndex,
}}
>
<Session
sessionId={s.id}
script={s.script}
title={s.title}
cwd={s.cwd}
width={720}
height={480}
startDelayMs={s.startDelayMs}
onEvent={handleEvent}
reactionNonce={reactions[s.id]?.nonce ?? 0}
reactionKind={reactions[s.id]?.kind ?? "receive"}
/>
</div>
);
})}
<svg
width={width}
height={height}
style={{
position: "absolute",
inset: 0,
pointerEvents: "none",
mixBlendMode: "screen",
}}
>
<defs>
<filter id="meshGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<symbol id="meshMsgIcon" viewBox="0 0 38 26">
<rect
x="1.5"
y="1.5"
width="35"
height="23"
rx="3"
ry="3"
fill={fccTheme.clawdBody}
stroke={fccTheme.claudeShimmer}
strokeWidth="1"
/>
<path
d="M 4 5 L 19 15 L 34 5"
stroke={fccTheme.clawdBackground}
strokeWidth="2.2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
/>
</symbol>
</defs>
{particles.map((p) => {
const arc = arcForParticle(p.fromId, p.toId);
if (!arc) return null;
const age = (performance.now() - p.bornAt) / PARTICLE_LIFE_MS;
if (age > 1) return null;
const head = Math.min(1, Math.max(0, age));
const pointAt = (t: number) => {
const tt = Math.max(0, Math.min(1, t));
const inv = 1 - tt;
return {
x:
inv * inv * arc.from.x +
2 * inv * tt * arc.midX +
tt * tt * arc.to.x,
y:
inv * inv * arc.from.y +
2 * inv * tt * arc.midY +
tt * tt * arc.to.y,
};
};
const trailNodes = Array.from({ length: TRAIL_SEGMENTS }, (_, i) => {
const frac = i / TRAIL_SEGMENTS;
const t = head - frac * TRAIL_SPAN;
if (t < 0) return null;
const pt = pointAt(t);
const falloff = Math.pow(1 - frac, 2.2);
return {
x: pt.x,
y: pt.y,
r: 2 + falloff * 5,
opacity: 0.75 * falloff,
};
}).filter((n): n is NonNullable<typeof n> => n !== null);
const headPt = pointAt(head);
const iconOpacity = Math.min(1, Math.sin(head * Math.PI) * 1.2 + 0.15);
return (
<g key={p.id} filter="url(#meshGlow)">
{trailNodes.map((n, i) => (
<circle
key={i}
cx={n.x}
cy={n.y}
r={n.r}
fill={fccTheme.clawdBody}
opacity={n.opacity}
/>
))}
<use
href="#meshMsgIcon"
x={headPt.x - ICON_W / 2}
y={headPt.y - ICON_H / 2}
width={ICON_W}
height={ICON_H}
opacity={iconOpacity}
/>
</g>
);
})}
</svg>
</div>
);
}

View File

@@ -0,0 +1,139 @@
import type { ReactNode } from "react";
import { fccTheme } from "./theme";
type BaseProps = { children: ReactNode };
export function UserPromptRow({ children }: BaseProps) {
return (
<div style={{ display: "flex", gap: "1ch", marginTop: 6 }}>
<span style={{ color: fccTheme.dim }}>{"\u003e"}</span>
<span style={{ color: fccTheme.text }}>{children}</span>
</div>
);
}
export function BashRunRow({
command,
lines,
}: {
command: string;
lines?: string[];
}) {
return (
<div style={{ marginTop: 10, marginBottom: 6 }}>
<div style={{ display: "flex", gap: "0.7ch", alignItems: "baseline" }}>
<span style={{ color: fccTheme.success }}>{"\u25cf"}</span>
<span style={{ fontWeight: 700 }}>Bash</span>
<span style={{ color: fccTheme.dim }}>({command})</span>
</div>
{lines?.map((l, i) => (
<div
key={i}
style={{
paddingLeft: "2.2ch",
color: fccTheme.dim,
}}
>
<span style={{ color: fccTheme.subtle, marginRight: "0.7ch" }}>
{"\u2514"}
</span>
{l}
</div>
))}
</div>
);
}
export function BulletRow({
color = "success",
children,
}: {
color?: "success" | "error" | "dim";
children: ReactNode;
}) {
const c =
color === "error"
? fccTheme.error
: color === "dim"
? fccTheme.dim
: fccTheme.success;
return (
<div style={{ display: "flex", gap: "0.7ch", marginTop: 8 }}>
<span style={{ color: c }}>{"\u25cf"}</span>
<span>{children}</span>
</div>
);
}
export function ToolUseRow({
name,
args,
result,
}: {
name: string;
args?: string;
result?: string;
}) {
return (
<div style={{ marginTop: 8 }}>
<div style={{ display: "flex", gap: "0.7ch" }}>
<span style={{ color: fccTheme.clawdBody }}>{"\u25cf"}</span>
<span style={{ fontWeight: 700 }}>{name}</span>
{args && <span style={{ color: fccTheme.dim }}>({args})</span>}
</div>
{result && (
<div style={{ paddingLeft: "2.2ch", color: fccTheme.dim }}>
<span style={{ color: fccTheme.subtle, marginRight: "0.7ch" }}>
{"\u2514"}
</span>
{result}
</div>
)}
</div>
);
}
export function AssistantTextRow({ children }: BaseProps) {
return (
<div style={{ marginTop: 8, color: fccTheme.text }}>
<span style={{ color: fccTheme.clawdBody, marginRight: "0.7ch" }}>
{"\u25cf"}
</span>
{children}
</div>
);
}
export function MeshMessageRow({
direction,
peer,
message,
}: {
direction: "out" | "in";
peer: string;
message: string;
}) {
const arrow = direction === "out" ? "\u2192" : "\u2190";
return (
<div
style={{
marginTop: 8,
padding: "6px 10px",
border: `1px solid ${fccTheme.clawdBody}`,
borderRadius: 4,
color: fccTheme.text,
display: "flex",
gap: "0.7ch",
alignItems: "baseline",
}}
>
<span style={{ color: fccTheme.clawdBody }}>mesh</span>
<span style={{ color: fccTheme.dim }}>{arrow}</span>
<span style={{ color: fccTheme.clawdBody, fontWeight: 700 }}>
{peer}
</span>
<span style={{ color: fccTheme.dim }}>:</span>
<span>{message}</span>
</div>
);
}

View File

@@ -0,0 +1,39 @@
import { fccTheme } from "./theme";
type PromptInputProps = {
value?: string;
caret?: boolean;
};
export function PromptInput({ value = "", caret = true }: PromptInputProps) {
return (
<div
style={{
marginTop: 12,
border: `1px solid ${fccTheme.promptBorder}`,
borderRadius: 4,
padding: "6px 10px",
color: fccTheme.text,
display: "flex",
alignItems: "baseline",
gap: "0.7ch",
}}
>
<span style={{ color: fccTheme.dim }}>{"\u003e"}</span>
<span>{value}</span>
{caret && (
<span
aria-hidden
style={{
display: "inline-block",
width: "0.6ch",
height: "1em",
backgroundColor: fccTheme.text,
animation: "fccCaret 1s steps(1) infinite",
}}
/>
)}
<style>{`@keyframes fccCaret { 50% { opacity: 0; } }`}</style>
</div>
);
}

View File

@@ -0,0 +1,398 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
// useRef is still used for onEventRef below
import { fccTheme } from "./theme";
import { TerminalWindow } from "./terminal-window";
import { Welcome } from "./welcome";
import {
AssistantTextRow,
BulletRow,
MeshMessageRow,
ToolUseRow,
UserPromptRow,
} from "./message-row";
import { PromptInput } from "./prompt-input";
import { StatusBar } from "./status-bar";
import { ThinkingSpinner } from "./thinking-spinner";
export type SessionStep =
| { type: "user-input"; text: string; typeMs?: number }
| { type: "thinking"; durationMs: number; label?: string }
| { type: "assistant-text"; text: string; streamMs?: number }
| { type: "tool-use"; name: string; args?: string; result?: string }
| { type: "bullet"; text: string; color?: "success" | "error" | "dim" }
| {
type: "mesh-send";
to: string;
message: string;
}
| {
type: "mesh-receive";
from: string;
message: string;
}
| { type: "pause"; durationMs: number };
export type SessionEvent =
| { kind: "mesh-send"; sessionId: string; to: string; message: string; stepIndex: number }
| { kind: "mesh-receive"; sessionId: string; from: string; message: string; stepIndex: number }
| { kind: "step-start"; sessionId: string; stepIndex: number }
| { kind: "script-complete"; sessionId: string };
export type SessionReaction = "receive" | "send" | "arrive";
export type SessionProps = {
sessionId: string;
script: SessionStep[];
title?: string;
cwd?: string;
width?: number;
height?: number;
contextPct?: number;
loop?: boolean;
startDelayMs?: number;
onEvent?: (event: SessionEvent) => void;
/**
* Bumps to trigger a reaction animation. Parent increments this to fire the
* matching effect — e.g. an "arrive" pulse when a mesh particle lands.
*/
reactionNonce?: number;
reactionKind?: SessionReaction;
};
type RenderedStep =
| { kind: "user-input"; text: string; done: boolean }
| { kind: "thinking"; label: string }
| { kind: "assistant-text"; text: string; done: boolean }
| { kind: "tool-use"; name: string; args?: string; result?: string }
| { kind: "bullet"; text: string; color: "success" | "error" | "dim" }
| { kind: "mesh-send"; to: string; message: string }
| { kind: "mesh-receive"; from: string; message: string };
export function Session({
sessionId,
script,
title,
cwd = "/Users/agutierrez",
width = 760,
height = 540,
contextPct = 6,
loop = true,
startDelayMs = 0,
onEvent,
reactionNonce = 0,
reactionKind = "receive",
}: SessionProps) {
const [rendered, setRendered] = useState<RenderedStep[]>([]);
const [liveInput, setLiveInput] = useState("");
const [cycle, setCycle] = useState(0);
const onEventRef = useRef(onEvent);
onEventRef.current = onEvent;
const scriptKey = useMemo(
() => script.map((s) => s.type).join("|") + "::" + sessionId,
[script, sessionId],
);
useEffect(() => {
let cancelled = false;
setRendered([]);
setLiveInput("");
const wait = (ms: number) =>
new Promise<void>((res) => {
const id = setTimeout(() => {
if (!cancelled) res();
}, ms);
void id;
});
const emit = (e: SessionEvent) => {
if (!cancelled) onEventRef.current?.(e);
};
const appendStep = (step: RenderedStep) => {
if (cancelled) return;
setRendered((prev) => [...prev, step]);
};
const updateLast = (mut: (s: RenderedStep) => RenderedStep) => {
if (cancelled) return;
setRendered((prev) => {
if (prev.length === 0) return prev;
const next = prev.slice();
next[next.length - 1] = mut(next[next.length - 1]);
return next;
});
};
const popLast = () => {
if (cancelled) return;
setRendered((prev) => prev.slice(0, -1));
};
const run = async () => {
if (startDelayMs > 0) await wait(startDelayMs);
for (let i = 0; i < script.length; i++) {
if (cancelled) return;
const step = script[i];
emit({ kind: "step-start", sessionId, stepIndex: i });
switch (step.type) {
case "pause":
await wait(step.durationMs);
break;
case "user-input": {
const ms = step.typeMs ?? 35;
for (let c = 1; c <= step.text.length; c++) {
if (cancelled) return;
const slice = step.text.slice(0, c);
if (!cancelled) setLiveInput(slice);
await wait(ms);
}
// Submit: clear the input box and push the prompt into scrollback
await wait(260);
if (cancelled) return;
setLiveInput("");
appendStep({ kind: "user-input", text: step.text, done: true });
await wait(140);
break;
}
case "thinking": {
appendStep({ kind: "thinking", label: step.label ?? "Thinking" });
await wait(step.durationMs);
popLast();
break;
}
case "assistant-text": {
appendStep({ kind: "assistant-text", text: "", done: false });
const ms = step.streamMs ?? 18;
for (let c = 1; c <= step.text.length; c++) {
if (cancelled) return;
const slice = step.text.slice(0, c);
updateLast((s) =>
s.kind === "assistant-text" ? { ...s, text: slice } : s,
);
await wait(ms);
}
updateLast((s) =>
s.kind === "assistant-text" ? { ...s, done: true } : s,
);
await wait(250);
break;
}
case "tool-use": {
appendStep({
kind: "tool-use",
name: step.name,
args: step.args,
result: step.result,
});
await wait(400);
break;
}
case "bullet": {
appendStep({
kind: "bullet",
text: step.text,
color: step.color ?? "success",
});
await wait(200);
break;
}
case "mesh-send": {
appendStep({
kind: "mesh-send",
to: step.to,
message: step.message,
});
emit({
kind: "mesh-send",
sessionId,
to: step.to,
message: step.message,
stepIndex: i,
});
await wait(350);
break;
}
case "mesh-receive": {
appendStep({
kind: "mesh-receive",
from: step.from,
message: step.message,
});
emit({
kind: "mesh-receive",
sessionId,
from: step.from,
message: step.message,
stepIndex: i,
});
await wait(350);
break;
}
}
}
if (cancelled) return;
emit({ kind: "script-complete", sessionId });
if (loop) {
await wait(2000);
if (cancelled) return;
setCycle((c) => c + 1);
}
};
void run();
return () => {
cancelled = true;
};
}, [scriptKey, cycle, loop, script, sessionId, startDelayMs]);
const reactionClass = `fcc-react-${reactionKind}`;
const reactionKey = `${reactionKind}-${reactionNonce}`;
return (
<div
key={reactionKey}
className={reactionNonce > 0 ? reactionClass : undefined}
style={{ willChange: "transform, filter" }}
>
<style>{`
@keyframes fccPulseReceive {
0% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(215,119,87,0)); }
30% { transform: scale(1.02); filter: drop-shadow(0 0 22px rgba(215,119,87,0.55)); }
100% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(215,119,87,0)); }
}
@keyframes fccPulseArrive {
0% { transform: scale(1); filter: brightness(1) drop-shadow(0 0 0 rgba(215,119,87,0)); }
25% { transform: scale(1.015); filter: brightness(1.25) drop-shadow(0 0 30px rgba(215,119,87,0.7)); }
100% { transform: scale(1); filter: brightness(1) drop-shadow(0 0 0 rgba(215,119,87,0)); }
}
@keyframes fccPulseSend {
0% { transform: scale(1); }
35% { transform: scale(0.99); filter: drop-shadow(0 0 12px rgba(215,119,87,0.35)); }
100% { transform: scale(1); }
}
.fcc-react-receive { animation: fccPulseReceive 380ms cubic-bezier(0.22, 0.61, 0.36, 1); }
.fcc-react-arrive { animation: fccPulseArrive 520ms cubic-bezier(0.22, 0.61, 0.36, 1); }
.fcc-react-send { animation: fccPulseSend 260ms cubic-bezier(0.22, 0.61, 0.36, 1); }
`}</style>
<TerminalWindow width={width} height={height} title={title}>
<div style={{ marginBottom: 6 }}>
<span style={{ color: fccTheme.dim }}>[(base) </span>
<span style={{ color: fccTheme.text }}>agutierrez@Mac</span>
<span style={{ color: fccTheme.dim }}> ~ % </span>
<span style={{ color: fccTheme.text }}>claude</span>
</div>
<Welcome cwd={cwd} />
<div
aria-hidden
style={{
marginTop: 8,
marginBottom: 4,
height: 1,
background: `repeating-linear-gradient(90deg, ${fccTheme.subtle} 0 6px, transparent 6px 10px)`,
}}
/>
<div
style={{
minHeight: 180,
display: "flex",
flexDirection: "column",
}}
>
{rendered.map((s, i) => {
switch (s.kind) {
case "user-input":
return (
<UserPromptRow key={i}>
{s.text}
{!s.done && <BlinkCursor />}
</UserPromptRow>
);
case "thinking":
return (
<div key={i} style={{ marginTop: 8 }}>
<ThinkingSpinner label={s.label} />
</div>
);
case "assistant-text":
return (
<AssistantTextRow key={i}>
{s.text}
{!s.done && <BlinkCursor />}
</AssistantTextRow>
);
case "tool-use":
return (
<ToolUseRow
key={i}
name={s.name}
args={s.args}
result={s.result}
/>
);
case "bullet":
return (
<BulletRow key={i} color={s.color}>
{s.text}
</BulletRow>
);
case "mesh-send":
return (
<MeshMessageRow
key={i}
direction="out"
peer={s.to}
message={s.message}
/>
);
case "mesh-receive":
return (
<MeshMessageRow
key={i}
direction="in"
peer={s.from}
message={s.message}
/>
);
}
})}
</div>
<PromptInput value={liveInput} />
<StatusBar cwd="~" contextPct={contextPct} />
</TerminalWindow>
</div>
);
}
function BlinkCursor() {
return (
<span
aria-hidden
style={{
display: "inline-block",
width: "0.55ch",
height: "1em",
marginLeft: "0.15ch",
verticalAlign: "text-bottom",
backgroundColor: fccTheme.text,
animation: "fccCaret 1s steps(1) infinite",
}}
/>
);
}

View File

@@ -0,0 +1,55 @@
import { fccTheme } from "./theme";
type StatusBarProps = {
user?: string;
cwd?: string;
model?: string;
contextPct?: number;
errorNote?: string;
errorAction?: string;
};
export function StatusBar({
user = "agutierrez@Mac",
cwd = "~",
model = "Opus 4.6 (1M context)",
contextPct = 6,
errorNote,
errorAction,
}: StatusBarProps) {
return (
<div
style={{
marginTop: 16,
paddingTop: 8,
borderTop: `1px solid ${fccTheme.subtle}`,
display: "flex",
justifyContent: "space-between",
color: fccTheme.dim,
fontSize: "inherit",
}}
>
<div>
<span>{user}</span>
<span style={{ margin: "0 0.7ch" }}>{"\u007c"}</span>
<span>{cwd}</span>
<span style={{ margin: "0 0.7ch" }}>{"\u007c"}</span>
<span>{model}</span>
<span style={{ marginLeft: "0.7ch" }}>{`[ctx:${contextPct}%]`}</span>
</div>
{errorNote && (
<div>
<span style={{ color: fccTheme.error }}>{errorNote}</span>
{errorAction && (
<>
<span style={{ color: fccTheme.dim, margin: "0 0.7ch" }}>
{"\u00b7"}
</span>
<span style={{ color: fccTheme.clawdBody }}>{errorAction}</span>
</>
)}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,127 @@
import type { ReactNode } from "react";
import { fccTheme } from "./theme";
type TerminalWindowProps = {
title?: string;
width?: number | string;
height?: number | string;
children: ReactNode;
};
export function TerminalWindow({
title = "agutierrez — \u2728 Initialize new coding project — node · claude — 80\u00d724",
width = 760,
height = 520,
children,
}: TerminalWindowProps) {
return (
<div
style={{
width,
height,
display: "flex",
flexDirection: "column",
borderRadius: 10,
overflow: "hidden",
boxShadow:
"0 30px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.05)",
backgroundColor: fccTheme.terminalBg,
fontFamily: fccTheme.fontMono,
}}
>
<TitleBar title={title} />
<div
style={{
flex: 1,
padding: "14px 18px 16px 18px",
fontSize: 13,
lineHeight: 1.45,
color: fccTheme.text,
overflow: "hidden",
}}
>
{children}
</div>
</div>
);
}
function TitleBar({ title }: { title: string }) {
return (
<div
style={{
display: "flex",
alignItems: "center",
height: 28,
paddingInline: 12,
backgroundColor: fccTheme.terminalChrome,
borderBottom: "1px solid rgba(0,0,0,0.4)",
position: "relative",
}}
>
<div style={{ display: "flex", gap: 8 }}>
<TrafficLight color="#ff5f57" />
<TrafficLight color="#febc2e" />
<TrafficLight color="#28c840" />
</div>
<div
style={{
position: "absolute",
left: 0,
right: 0,
textAlign: "center",
fontSize: 12,
color: "rgba(255,255,255,0.85)",
fontWeight: 600,
pointerEvents: "none",
fontFamily:
"-apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 6,
height: "100%",
}}
>
<FolderIcon />
<span
style={{
maxWidth: "70%",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{title}
</span>
</div>
</div>
);
}
function TrafficLight({ color }: { color: string }) {
return (
<span
style={{
display: "inline-block",
width: 12,
height: 12,
borderRadius: "50%",
backgroundColor: color,
boxShadow: "inset 0 0 0 0.5px rgba(0,0,0,0.25)",
}}
/>
);
}
function FolderIcon() {
return (
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden>
<path
d="M1.5 4.5a1 1 0 0 1 1-1h3.3l1.4 1.4h6.3a1 1 0 0 1 1 1v5.6a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1V4.5Z"
stroke="rgba(255,255,255,0.75)"
strokeWidth="1.2"
/>
</svg>
);
}

View File

@@ -0,0 +1,16 @@
export const fccTheme = {
clawdBody: "rgb(215,119,87)",
clawdBackground: "rgb(0,0,0)",
text: "rgb(255,255,255)",
dim: "rgb(153,153,153)",
subtle: "rgb(80,80,80)",
success: "rgb(78,186,101)",
error: "rgb(255,107,128)",
claudeShimmer: "rgb(235,159,127)",
promptBorder: "rgb(136,136,136)",
bashBorder: "rgb(253,93,177)",
terminalBg: "rgb(0,0,0)",
terminalChrome: "rgb(55,55,57)",
fontMono:
"'Menlo', 'Monaco', 'SF Mono', 'JetBrains Mono', 'Consolas', ui-monospace, monospace",
} as const;

View File

@@ -0,0 +1,29 @@
"use client";
import { useEffect, useState } from "react";
import { fccTheme } from "./theme";
const FRAMES = ["\u2847", "\u284f", "\u285f", "\u287f", "\u28ff", "\u28f7", "\u28e7", "\u28c7"];
type ThinkingSpinnerProps = {
label?: string;
intervalMs?: number;
};
export function ThinkingSpinner({
label = "Thinking",
intervalMs = 80,
}: ThinkingSpinnerProps) {
const [i, setI] = useState(0);
useEffect(() => {
const id = setInterval(() => setI((n) => (n + 1) % FRAMES.length), intervalMs);
return () => clearInterval(id);
}, [intervalMs]);
return (
<div style={{ display: "flex", gap: "0.6ch", color: fccTheme.claudeShimmer }}>
<span style={{ color: fccTheme.clawdBody }}>{FRAMES[i]}</span>
<span style={{ color: fccTheme.dim, fontStyle: "italic" }}>{label}</span>
</div>
);
}

View File

@@ -0,0 +1,53 @@
import { Clawd, type ClawdPose } from "./clawd";
import { fccTheme } from "./theme";
type WelcomeProps = {
pose?: ClawdPose;
version?: string;
model?: string;
billing?: string;
cwd?: string;
};
export function Welcome({
pose = "default",
version = "2.1.101",
model = "Opus 4.6 (1M context)",
billing = "Claude Max",
cwd = "/Users/agutierrez",
}: WelcomeProps) {
return (
<div
style={{
display: "flex",
flexDirection: "row",
gap: "1ch",
alignItems: "flex-start",
fontFamily: fccTheme.fontMono,
color: fccTheme.text,
lineHeight: 1.15,
}}
>
<div style={{ flexShrink: 0, paddingTop: "0.1em" }}>
<Clawd pose={pose} />
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "0.05em",
paddingTop: "0.1em",
}}
>
<div>
<span style={{ fontWeight: 700 }}>Claude Code</span>{" "}
<span style={{ color: fccTheme.dim }}>v{version}</span>
</div>
<div style={{ color: fccTheme.dim }}>
{model} · {billing}
</div>
<div style={{ color: fccTheme.dim }}>{cwd}</div>
</div>
</div>
);
}

View File

@@ -3,6 +3,33 @@ import { useState } from "react";
import { Reveal, SectionIcon } from "./_reveal";
const FEATURES = [
{
key: "skills",
tab: "Skills",
title: "Publish a skill once, every peer invokes it",
body: "Write a skill in ~/.claude/skills/review-pr, share it to the mesh, and every teammate's Claude Code has /review-pr. Update the skill on your end → every peer auto-refreshes. No manual CLAUDE.md edits, no git pulls, no copy-paste.",
code: `share_skill(name: "review-pr", dir: "./.claude/skills/review-pr")
mesh_skill_deploy("review-pr")
list_skills() → all skills live on the mesh`,
},
{
key: "mcps",
tab: "MCPs",
title: "Share an MCP server once, every peer sees its tools",
body: "Register an MCP on your machine — Postgres, Stripe, internal API, whatever — then mesh_mcp_deploy it. Every peer's Claude Code auto-discovers the tools, with per-mesh scope and audit logs. Credentials never leave your machine.",
code: `mesh_mcp_register("postgres-prod", command: "npx mcp-postgres")
mesh_mcp_deploy("postgres-prod")
mesh_mcp_catalog() → every MCP live on the mesh`,
},
{
key: "commands",
tab: "Commands",
title: "Slash commands that travel with the mesh",
body: "Any slash command you've defined — /deploy, /audit, /review-pr — can be published to the mesh. Teammates invoke it from their own Claude Code. The command runs with your logic and rules, their context. Shared muscle memory, no copying files between repos.",
code: `share_skill(name: "deploy", kind: "command")
// Peer B types /deploy in their session
// → runs your publisher-side playbook in their repo`,
},
{
key: "groups",
tab: "Groups",
@@ -90,7 +117,7 @@ export const Features = () => {
className="mx-auto mt-4 max-w-xl text-center text-sm text-[var(--cm-fg-tertiary)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
43 MCP tools. Groups, state, memory, files, databases, vectors, streams all shipped.
Skills, MCPs, slash commands, groups, state, memory, files, databases, vectors, streams every primitive meshed, end-to-end encrypted.
</p>
</Reveal>
<Reveal delay={3}>

View File

@@ -0,0 +1,354 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { MeshHero } from "./fake-claude-code/mesh-hero";
import type { SessionStep } from "./fake-claude-code/session";
const NATURAL_W = 1600;
const NATURAL_H = 860;
const SCRIPT_A: SessionStep[] = [
{ type: "pause", durationMs: 400 },
{ type: "user-input", text: "share_skill /review-pr" },
{ type: "mesh-send", to: "Lug Nut", message: "share_skill /review-pr" },
{ type: "pause", durationMs: 1200 },
{ type: "mesh-receive", from: "Mou", message: "postgres-prod MCP live" },
{ type: "pause", durationMs: 800 },
{
type: "tool-use",
name: "mesh_tool_call",
args: "postgres-prod.query",
result: "142 rows",
},
{ type: "pause", durationMs: 1100 },
{ type: "mesh-send", to: "Mou", message: "thanks — skill in use" },
{ type: "pause", durationMs: 2200 },
];
const SCRIPT_B: SessionStep[] = [
{ type: "pause", durationMs: 700 },
{ type: "mesh-receive", from: "Alexis", message: "/review-pr shared" },
{ type: "pause", durationMs: 800 },
{ type: "user-input", text: "/review-pr PR #142" },
{ type: "thinking", durationMs: 700 },
{
type: "tool-use",
name: "Read",
args: "auth/middleware.ts",
result: "142 lines",
},
{ type: "pause", durationMs: 800 },
{ type: "mesh-send", to: "Mou", message: "found 2 issues in auth flow" },
{ type: "pause", durationMs: 1500 },
{ type: "mesh-receive", from: "Alexis", message: "thanks — skill in use" },
{ type: "pause", durationMs: 1600 },
];
const SCRIPT_C: SessionStep[] = [
{ type: "pause", durationMs: 300 },
{ type: "user-input", text: "expose postgres to mesh" },
{
type: "tool-use",
name: "mesh_mcp_deploy",
args: "postgres-prod",
result: "exposed to 6 peers",
},
{ type: "mesh-send", to: "Alexis", message: "postgres-prod MCP live" },
{ type: "pause", durationMs: 1400 },
{
type: "mesh-receive",
from: "Lug Nut",
message: "found 2 issues in auth flow",
},
{ type: "pause", durationMs: 700 },
{ type: "assistant-text", text: "Patching issues via mesh." },
{ type: "pause", durationMs: 900 },
{
type: "mesh-send",
to: "Lug Nut",
message: "fix pushed — rerun /review-pr",
},
{ type: "pause", durationMs: 1800 },
];
const SCRIPT_PIP: SessionStep[] = [
{ type: "pause", durationMs: 1200 },
{ type: "mesh-receive", from: "Alexis", message: "share_skill /review-pr" },
{ type: "pause", durationMs: 1600 },
{ type: "mesh-send", to: "Alexis", message: "cache warm" },
{ type: "pause", durationMs: 3200 },
];
const SCRIPT_RIPPLE: SessionStep[] = [
{ type: "pause", durationMs: 2100 },
{ type: "mesh-receive", from: "Mou", message: "postgres-prod MCP live" },
{ type: "pause", durationMs: 1800 },
{ type: "mesh-send", to: "Mou", message: "mirror ready" },
{ type: "pause", durationMs: 3000 },
];
const SCRIPT_NEBULA: SessionStep[] = [
{ type: "pause", durationMs: 2800 },
{ type: "mesh-receive", from: "Lug Nut", message: "need security review" },
{ type: "pause", durationMs: 1500 },
{ type: "mesh-send", to: "Lug Nut", message: "reviewed — LGTM" },
{ type: "pause", durationMs: 3000 },
];
const SCRIPT_JET: SessionStep[] = [
{ type: "pause", durationMs: 1800 },
{ type: "mesh-receive", from: "Alexis", message: "thanks — skill in use" },
{ type: "pause", durationMs: 1800 },
{ type: "mesh-send", to: "Alexis", message: "heartbeat ok" },
{ type: "pause", durationMs: 3200 },
];
const SCRIPT_VELA: SessionStep[] = [
{ type: "pause", durationMs: 900 },
{ type: "mesh-send", to: "Lug Nut", message: "broker uptime 99.98" },
{ type: "pause", durationMs: 2400 },
{ type: "mesh-receive", from: "Mou", message: "postgres-prod MCP live" },
{ type: "pause", durationMs: 3400 },
];
const SCRIPT_OREL: SessionStep[] = [
{ type: "pause", durationMs: 2400 },
{ type: "mesh-receive", from: "Alexis", message: "share_skill /review-pr" },
{ type: "pause", durationMs: 1600 },
{ type: "mesh-send", to: "Alexis", message: "mirrored downstream" },
{ type: "pause", durationMs: 3000 },
];
type HeroMeshAnimationProps = {
/**
* `cover` — fill both width and height of the parent, overflow clipped (for
* use as a hero background). `contain` — fit within width, height scales
* proportionally (standalone use).
*/
fit?: "cover" | "contain";
};
export function HeroMeshAnimation({ fit = "contain" }: HeroMeshAnimationProps) {
const outerRef = useRef<HTMLDivElement>(null);
const [fitScale, setFitScale] = useState(1);
useEffect(() => {
const el = outerRef.current;
if (!el) return;
const compute = (w: number, h: number) => {
if (fit === "cover") {
// Pick the larger ratio so the composition fills both dimensions.
// Never scale below 1 in cover mode — we want overflow if the parent
// is smaller than the natural size.
const s = Math.max(w / NATURAL_W, h / NATURAL_H);
setFitScale(Math.max(s, 0.001));
} else {
setFitScale(Math.min(1, w / NATURAL_W));
}
};
const ro = new ResizeObserver((entries) => {
const rect = entries[0]?.contentRect;
if (!rect) return;
compute(rect.width, rect.height);
});
ro.observe(el);
const rect = el.getBoundingClientRect();
compute(rect.width, rect.height);
return () => ro.disconnect();
}, [fit]);
const isCover = fit === "cover";
const scaledW = NATURAL_W * fitScale;
const scaledH = NATURAL_H * fitScale;
return (
<div
ref={outerRef}
className={isCover ? "h-full w-full" : "w-full"}
style={{
overflow: "hidden",
position: "relative",
...(isCover ? {} : { height: scaledH }),
}}
>
<div
style={{
width: scaledW,
height: scaledH,
...(isCover
? {
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}
: { margin: "0 auto" }),
}}
>
<div
style={{
width: NATURAL_W,
height: NATURAL_H,
transform: `scale(${fitScale})`,
transformOrigin: "top left",
}}
>
<MeshHero
width={NATURAL_W}
height={NATURAL_H}
sessions={[
{
id: "P_VELA",
displayName: "Vela",
title: "vela · claude — 80\u00d724",
cwd: "~/broker",
script: SCRIPT_VELA,
position: {
xPct: 50,
yPct: 10,
scale: 0.38,
opacity: 0.55,
saturate: 0.35,
blurPx: 0.6,
zIndex: 0,
},
},
{
id: "P_OREL",
displayName: "Orel",
title: "orel · claude — 80\u00d724",
cwd: "~/registry",
script: SCRIPT_OREL,
position: {
xPct: 50,
yPct: 88,
scale: 0.38,
opacity: 0.55,
saturate: 0.35,
blurPx: 0.6,
zIndex: 0,
},
},
{
id: "P1",
displayName: "Pip",
title: "pip · claude — 80\u00d724",
cwd: "~/tools",
script: SCRIPT_PIP,
position: {
xPct: 8,
yPct: 20,
scale: 0.42,
rotate: -4,
opacity: 0.6,
saturate: 0.4,
blurPx: 0.5,
zIndex: 0,
},
},
{
id: "P2",
displayName: "Ripple",
title: "ripple · claude — 80\u00d724",
cwd: "~/infra",
script: SCRIPT_RIPPLE,
position: {
xPct: 92,
yPct: 20,
scale: 0.42,
rotate: 4,
opacity: 0.6,
saturate: 0.4,
blurPx: 0.5,
zIndex: 0,
},
},
{
id: "P3",
displayName: "Nebula",
title: "nebula · claude — 80\u00d724",
cwd: "~/ops",
script: SCRIPT_NEBULA,
position: {
xPct: 10,
yPct: 82,
scale: 0.4,
rotate: 3,
opacity: 0.58,
saturate: 0.38,
blurPx: 0.5,
zIndex: 0,
},
},
{
id: "P4",
displayName: "Jet",
title: "jet · claude — 80\u00d724",
cwd: "~/monorepo",
script: SCRIPT_JET,
position: {
xPct: 90,
yPct: 82,
scale: 0.4,
rotate: -3,
opacity: 0.58,
saturate: 0.38,
blurPx: 0.5,
zIndex: 0,
},
},
{
id: "A",
displayName: "Alexis",
title: "agutierrez — alexis · claude — 80\u00d724",
cwd: "~/claudemesh",
script: SCRIPT_A,
position: {
xPct: 20,
yPct: 58,
scale: 0.65,
rotate: -3,
saturate: 1,
opacity: 1,
zIndex: 2,
},
},
{
id: "B",
displayName: "Lug Nut",
title: "agutierrez — lug-nut · claude — 80\u00d724",
cwd: "~/whyrating",
script: SCRIPT_B,
position: {
xPct: 50,
yPct: 40,
scale: 0.65,
rotate: 0,
saturate: 1,
opacity: 1,
zIndex: 2,
},
},
{
id: "C",
displayName: "Mou",
title: "agutierrez — mou · claude — 80\u00d724",
cwd: "~/mineryreport",
script: SCRIPT_C,
position: {
xPct: 80,
yPct: 58,
scale: 0.65,
rotate: 3,
saturate: 1,
opacity: 1,
zIndex: 2,
},
},
]}
/>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,118 @@
import Link from "next/link";
import { HeroMeshAnimation } from "./hero-mesh-animation";
import { Reveal, SectionIcon } from "./_reveal";
export const HeroWithMesh = () => {
return (
<section className="relative overflow-hidden border-b border-[var(--cm-border)] bg-[var(--cm-bg)]">
{/* Full-bleed mesh animation as hero background */}
<div className="pointer-events-none absolute inset-0 z-0">
<div className="absolute inset-0">
<HeroMeshAnimation fit="cover" />
</div>
{/* Radial vignette: dark where text sits, transparent at the edges
so the corner peers keep pulsing visibly */}
<div
className="absolute inset-0"
style={{
background:
"radial-gradient(ellipse 900px 540px at 50% 38%, rgba(5,5,5,0.92) 0%, rgba(5,5,5,0.75) 38%, rgba(5,5,5,0.3) 68%, rgba(5,5,5,0) 100%)",
}}
/>
{/* Top/bottom fades so the animation bleeds into surrounding sections */}
<div
className="absolute inset-x-0 top-0 h-32"
style={{
background:
"linear-gradient(to bottom, rgba(5,5,5,0.85) 0%, rgba(5,5,5,0) 100%)",
}}
/>
<div
className="absolute inset-x-0 bottom-0 h-32"
style={{
background:
"linear-gradient(to top, rgba(5,5,5,0.95) 0%, rgba(5,5,5,0) 100%)",
}}
/>
</div>
<div className="relative z-10 mx-auto flex max-w-[var(--cm-max-w)] flex-col items-center px-6 py-24 md:px-12 md:py-32">
<Reveal className="mb-8">
<SectionIcon glyph="mesh" />
</Reveal>
<Reveal delay={1}>
<h1
className="max-w-4xl text-center text-[clamp(2.75rem,7vw,5.25rem)] font-medium leading-[1.08] tracking-tight text-[var(--cm-fg)]"
style={{
fontFamily: "var(--cm-font-serif)",
textShadow: "0 2px 30px rgba(0,0,0,0.85)",
}}
>
Your Claude Code sessions{" "}
<span className="text-[var(--cm-clay)]">work alone.</span>
<br />
<span className="text-[var(--cm-fg-secondary)]">
claudemesh connects them.
</span>
</h1>
</Reveal>
<Reveal delay={2}>
<p
className="mx-auto mt-8 max-w-2xl text-center text-lg leading-[1.65] text-[var(--cm-fg-secondary)] md:text-xl"
style={{
fontFamily: "var(--cm-font-serif)",
textShadow: "0 2px 20px rgba(0,0,0,0.8)",
}}
>
Share context, files, skills, and MCPs across every Claude Code
session encrypted, with zero setup. The broker routes ciphertext.
It never reads your messages.
</p>
</Reveal>
<Reveal delay={3}>
<div className="mt-10 flex flex-col items-stretch gap-3 sm:flex-row sm:items-center">
<Link
href="/auth/register"
className="group inline-flex items-center justify-center gap-2 rounded-[var(--cm-radius-xs)] bg-[var(--cm-clay)] px-5 py-3 text-[15px] font-medium text-[var(--cm-fg)] shadow-[0_10px_40px_rgba(215,119,87,0.35)] transition-colors duration-300 hover:bg-[var(--cm-clay-hover)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
Start free
<span className="transition-transform duration-300 group-hover:translate-x-0.5">
</span>
</Link>
<div
className="flex items-center gap-2 rounded-[var(--cm-radius-xs)] border border-[var(--cm-border)] bg-[var(--cm-bg-elevated)]/85 px-4 py-3 text-[13px] text-[var(--cm-fg-secondary)] backdrop-blur-md"
style={{ fontFamily: "var(--cm-font-mono)" }}
>
<span className="text-[var(--cm-clay)]">$</span>
<span>curl -fsSL claudemesh.com/install | bash</span>
</div>
</div>
</Reveal>
<Reveal delay={4}>
<p
className="mt-14 text-center text-sm text-[var(--cm-fg-tertiary)]"
style={{
fontFamily: "var(--cm-font-sans)",
textShadow: "0 2px 16px rgba(0,0,0,0.8)",
}}
>
Open-source CLI · Free during public beta ·{" "}
<Link
href="https://github.com/alezmad/claudemesh-cli"
className="underline decoration-[var(--cm-fg-tertiary)] underline-offset-4 transition-colors hover:text-[var(--cm-fg)] hover:decoration-[var(--cm-clay)]"
>
View source
</Link>
</p>
</Reveal>
</div>
</section>
);
};

View File

@@ -0,0 +1,128 @@
import { Reveal, SectionIcon } from "./_reveal";
type Card = {
label: string;
title: string;
theyDo: string;
weDo: string;
tone: "compare" | "claim";
};
const CARDS: Card[] = [
{
label: "vs. MCP",
title: "One Claude to its tools",
theyDo:
"MCP wires one Claude session to external services — GitHub, Postgres, a browser. The tool never knows who called it, never talks back, never sees other sessions.",
weDo: "claudemesh ships as an MCP server itself. We extend the model: publish an MCP once, every peer's Claude Code sees its tools. Credentials stay on the publisher's machine.",
tone: "compare",
},
{
label: "vs. Subagents",
title: "Helpers inside one session",
theyDo:
"Subagents spawn helper agents within a single Claude Code session. They share one context, one terminal, one machine. When the session closes, they're gone.",
weDo: "claudemesh connects full, independent Claude Code sessions across machines, across developers, across continents. Each peer keeps its own repo, its own perspective, its own scrollback.",
tone: "compare",
},
{
label: "vs. OpenClaw",
title: "Autonomous agents that run while you sleep",
theyDo:
"OpenClaw runs unattended. One agent brain, many subagents, 200+ LLMs on tap. It triages issues overnight, opens PRs, pokes CI, reacts to webhooks — all without a human in the loop. Different job, and a good one.",
weDo: "claudemesh is about the sessions you're actively running. When your Claude Code is open and you're shipping, the mesh wires your session to your teammates'. OpenClaw automates overnight; claudemesh meshes your work hours. They compose — put an OpenClaw instance on the mesh and it joins as just another peer.",
tone: "compare",
},
{
label: "What claudemesh is",
title: "The wire between Claude Code sessions",
theyDo:
"Every Claude Code session today is an island. Context dies with the terminal. Skills and MCPs are per-developer. Teammates relay insights through Slack.",
weDo: "claudemesh is one thing: a peer network for Claude Code. Share context, files, skills, MCPs, and slash commands across sessions — end-to-end encrypted. The broker routes ciphertext. It never reads your messages.",
tone: "claim",
},
];
export const WhereMeshFits = () => {
return (
<section className="border-b border-[var(--cm-border)] bg-[var(--cm-bg)] px-6 py-24 md:px-12 md:py-32">
<div className="mx-auto max-w-[var(--cm-max-w)]">
<Reveal className="mb-6 flex justify-center">
<SectionIcon glyph="arrow" />
</Reveal>
<Reveal delay={1}>
<h2
className="mx-auto max-w-4xl text-center text-[clamp(2rem,4.5vw,3.25rem)] font-medium leading-[1.1] text-[var(--cm-fg)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
Where claudemesh fits
</h2>
</Reveal>
<Reveal delay={2}>
<p
className="mx-auto mt-4 max-w-2xl text-center text-sm leading-[1.6] text-[var(--cm-fg-tertiary)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
A quick tour of what claudemesh is and what it isn&apos;t. We
compose with the rest of the Claude Code ecosystem. We don&apos;t
replace any of it.
</p>
</Reveal>
<div className="mt-16 grid gap-5 md:grid-cols-2">
{CARDS.map((c) => {
const isClaim = c.tone === "claim";
return (
<Reveal key={c.label} delay={3}>
<div
className={
"flex h-full flex-col rounded-[var(--cm-radius-md)] border p-7 md:p-8 " +
(isClaim
? "border-[var(--cm-clay)]/60 bg-[var(--cm-clay)]/[0.06]"
: "border-[var(--cm-border)] bg-[var(--cm-bg-elevated)]")
}
>
<div
className={
"mb-3 text-[11px] uppercase tracking-[0.18em] " +
(isClaim
? "text-[var(--cm-clay)]"
: "text-[var(--cm-fg-tertiary)]")
}
style={{ fontFamily: "var(--cm-font-mono)" }}
>
{c.label}
</div>
<h3
className="mb-4 text-[22px] font-medium leading-snug text-[var(--cm-fg)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
{c.title}
</h3>
<p
className="text-[14px] leading-[1.65] text-[var(--cm-fg-tertiary)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
{c.theyDo}
</p>
<div className="my-4 h-px bg-[var(--cm-border)]" />
<p
className={
"text-[14px] leading-[1.65] " +
(isClaim
? "text-[var(--cm-fg)]"
: "text-[var(--cm-fg-secondary)]")
}
style={{ fontFamily: "var(--cm-font-serif)" }}
>
{c.weDo}
</p>
</div>
</Reveal>
);
})}
</div>
</div>
</section>
);
};