v1.1DESIGN SYSTEM

The Paperclip Design System

One token model, two surfaces. The product's shipped tokens are canonical; the marketing site vendors the same colors, type, and radius. Direct. Confident. Never decorative.

01FOUNDATIONS

Editorial gravity. Industrial precision.

Paperclip is the app people use to manage AI agents for work. The brand and the product share one design system: serious software for serious work — direct, confident, never decorative.

Principles

FOUR RULES
01 · CLARITY OVER CLEVERNESS

Show structure. Hide ornament.

Hairlines, generous whitespace, strong type hierarchy. No drop shadows, no gradient backgrounds, no decorative blobs. If a pixel doesn't earn its place, cut it.

02 · ONE VOCABULARY, TWO SURFACES

The brand and the product are the same system.

Same tokens, same type ramp, same motion. The marketing site feels like a continuation of the product, not a different app. Differences are density and content, not vocabulary.

03 • THE MOTIF WITH MEANING

The capsule represents the agent.

The 1:2 capsule shape is reserved for agent representation — capsule fields, org chart nodes, status indicators, avatars. Never on chrome, never on buttons. That's why it stays meaningful.

04 · THE HUMAN IS IN CHARGE

Every surface telegraphs control.

Pause, resume, override, reassign, terminate. The visual language emphasizes legibility, traceability and accountability. Buttons read like commands. Status reads like telemetry. The interface never pretends the agent is choosing — the operator is.

Surfaces

WHERE THE SYSTEM APPLIES

One token model spans two production surfaces. The product's ui/src/index.css is canonical; the website vendors those shipped tokens (npm run tokens:sync) and layers a few documented brand exceptions. Theme, density, and content adapt; the vocabulary doesn't.

SURFACE
Marketing

Editorial, persuasion-first. Next (Vercel) in production; Astro is frozen legacy. Vendors the product tokens; keeps Inter Tight + JetBrains Mono + pill CTAs as brand exceptions.

paperclip.ing · /brand · /blog · /changelog
SURFACE
Product

Grid-disciplined, scanning-first. Light + dark. React 19 + Tailwind v4 (@theme, no config) + shadcn/Radix. Defines the tokens.

app · cockpit · org · traces · billing
03TYPOGRAPHY

Three typefaces. Each with one job.

Inter Tight carries display and editorial weight. Inter does every other word on the page. JetBrains Mono carries the engineering credibility — telemetry, code, timestamps. Nothing else.

Families

THREE STACKS
DISPLAY

Inter Tight

500 Medium · 600 Semibold · 700 Bold

The display voice. All headings, display copy, big moments. Default weight 600 — tight tracking provides the editorial gravity without leaning on bold.

Paperclip runs your business.
font-family: 'Inter Tight', sans-serif
weight: 600 · line-height: 1.02–1.2 · letter-spacing: -0.04em → -0.02em
BODY

Inter

400 · 500 · 600 · 700

The workhorse. Body, labels, navigation, buttons, the wordmark. ss01 + cv11 are enabled site-wide.

One token model. Two surfaces. Direct, confident, never decorative.
font-family: 'Inter', sans-serif
font-feature-settings: 'ss01', 'cv11'
MONO

JetBrains

400 · 500

The engineering layer. Timestamps, hex codes, agent names, tool calls, terminal, telemetry chips. tabular-nums always on.

$ paperclip status --agent ceo
 ▸ alive  budget 64%  4h 32m
font-family: 'JetBrains Mono', monospace

Documented brand exceptions: Inter Tight (--font-serif) and JetBrains Mono (--font-mono) are the website's display and mono faces. The product uses self-hosted InterVariable for everything — headings included (--font-heading = --font-sans) — and the system ui-monospace stack for mono. Inter (body) is shared across both.

Type ramp

11 ROLES

Use the role, not the size. Display roles use Inter Tight at 600. Body and labels use Inter. Mono is JetBrains Mono.

ROLE SAMPLE SPEC
display-1 Headline. clamp(42, 5.5vw, 80) · 600 · ‑0.04em
display-2 Section heading clamp(40, 5.5vw, 72) · 600 · ‑0.035em
display-3 Subsection clamp(28, 3.5vw, 44) · 600 · ‑0.03em
display-4 Card title 26 · 600 · ‑0.02em
lede Subhead / hero lede 20 / 1.45 · 400 · 0
body-lg Body large — feature copy 18 / 1.5 · 400 · 0
body Body — running text, paragraphs, default UI copy. 16 / 1.6 · 400 · 0
body-sm Body small — secondary text, table cells, dense UI. 14 / 1.5 · 400 · 0
caption EYEBROWS · LABELS · CATEGORY TAGS 11 · 500 · +0.08em · UPPER
mono $ paperclip status — 1042 events 13 / 1.4 · 400 · tabular-nums
mono-sm 14:32:07 · #1042 · CEO Hermes 11 / 1.4 · 400

Pairing in practice

EDITORIAL RHYTHM
MARKETING BLOCK

Heartbeats keep the lights on.

Agents wake on a schedule, check their work, and act. Delegation flows up and down the org chart automatically.

SHIPPED 14:32:07 PST · 2026‑11‑22
PRODUCT MOMENT

You're in control.

Pause. Resume. Override. Reassign. Terminate.

ACTIONS 5 verbs · always available

Type do & don't

RULES
DO
Set the goal. Approve the work.

Inter Tight at 600. Tight tracking. Sentence-case. The tight spacing does the editorial work.

DON'T
SET THE GOAL. APPROVE THE WORK.

No all-caps display. No loose tracking on display sizes. Caps are reserved for the caption role.

DO
12:48:02 · CEO Hermes · run_tests() passed

Use the mono stack for timestamps, IDs, tool calls, agent names, hex values, terminal output. tabular-nums keeps columns aligned.

DON'T
12:48:02 — CEO Hermes — run_tests() passed

Don't dress system telemetry in editorial type. Mono is the layer that signals "this is real, this happened, this was logged."

04COLOR

A palette with purpose.

A neutral spine, color only where color carries meaning. The product ships an OKLCH grayscale — white surfaces separated by borders, not tonal fills — and the website vendors the same scale. The warm "manila" ramp has been retired: it survives only inside the agent-4 capsule gradient. Status colors carry product meaning; the agent palette is saturated gradients reserved for agents and org-chart nodes, never for chrome or buttons.

Neutral ramp

DE-WARMED · PRODUCT SCALE

The product's canonical neutrals are OKLCH grays (--background--foreground); the website surfaces them as named literals so components can reference a fixed tone. There is no warm cast--card equals --background (both white), and surfaces are told apart by --border, not by tinted fills. manila, linen, and vellum are retired aliases that now resolve to white / neutral gray.

Bond
#FFFFFF
--bond · --background
Page background · cards · inputs (light). oklch(1 0 0)
Parchment
#FAFAFA
--parchment · ≈ --muted
De-warmed off-white — banded / raised surfaces only
Stone
#E5E5E5
--stone · --border
Neutral hairline — borders + dividers (light). oklch(0.922 0 0)
Aluminum
#A8AEB2
--aluminum · ≈ --muted-foreground
Captions · placeholders · muted text
Graphite
#52585D
--graphite
Secondary text + mono labels in light mode
Charcoal
#141413
--charcoal · --background (dark)
Dark-mode base surface. oklch(0.145 0 0)
Ink
#0A0A0A
--ink · --foreground
Primary text · neutral accent / primary fill (light)

Semantic tokens

THEME-AWARE

Components reference these — they swap with theme. In the product they are OKLCH (--background, --foreground, --card, --muted-foreground, --border, --primary); the website maps them onto the compat names below. Note --surface equals --bg in light: cards are white and separated by --rule, not a tonal fill.

TOKEN LIGHT DARK USAGE
--bg bond · oklch(1 0 0) charcoal · oklch(0.145) Page background (--background)
--surface bond (= --bg) oklch(0.205) Cards · panels (--card) — bordered, not filled
--rule stone · oklch(0.922) white / 10% Hairline borders + dividers (--border)
--text ink · oklch(0.145) off-white · oklch(0.985) Primary text (--foreground)
--text-2 graphite #A3A3A3 Secondary text · subhead
--text-muted aluminum · oklch(0.556) oklch(0.708) Captions · placeholders · mono labels (--muted-foreground)
--accent ink (neutral) off-white Primary action · focus (--primary) — neutral; green is deferred

Status colors

SEMANTIC · SIX

Six semantic colors carry meaning across both surfaces — gray is a first-class status color (inert / resting), not just chrome. Each card documents the full chip recipe per theme: light chips use a tinted fill + saturated text; dark chips use a translucent fill of the solid hue, a solid-color text, and a 45%-alpha border. Alpha suffixes (e.g. #2563EB·18%) are written in the codebase as 8-digit hex — #2563eb2e.

Gray · Inert · Resting
#A8AEB2
--aluminum · gray
LIGHT CHIP
bg#F5F3F0 text#52585D border#A8AEB2
DARK CHIP
bg#6E6960·14% text#9A958A border#9E958A·45%
Blue · Live · Running
#2563EB
--blue · --info
LIGHT CHIP
bg#DBEAFE text#1D4ED8 border#2563EB
DARK CHIP
bg#2563EB·18% text#5B8DEF border#2563EB·45%
Amber · Queued · Paused
#F59E0B
--amber · --warning
LIGHT CHIP
bg#FEF3C7 text#B45309 border#F59E0B
DARK CHIP
bg#F59E0B·14% text#F59E0B border#F59E0B·45%
Signal · Success · Done
#22C55E
--signal · --success
LIGHT CHIP
bg#DCFCE7 text#188A3C border#22C55E
DARK CHIP
bg#22C55E·12% text#34D06F border#22C55E·45%
Red · Error · Blocked
#DC2626
--red · --error
LIGHT CHIP
bg#FEE2E2 text#991B1B border#DC2626
DARK CHIP
bg#DC2626·18% text#EF4444 border#DC2626·45%
Violet · In review
#7C3AED
--violet · --status-task-in_review
LIGHT CHIP
bg#EDE9FE text#5B21B6 border#7C3AED
DARK CHIP
bg#7C3AED·18% text#A78BFA border#7C3AED·45%

Violet is a shipped status token--status-task-in_review (#7C3AED), rendered with the CircleDot glyph. It's scoped to the in_review state, not a general-purpose chrome color. Full chip semantics + live examples in §13 States & Status.

Agent palette

12 PRESETS · CAPSULES ONLY

Brand use only. Twelve saturated gradients reserved for the capsule motif — agent representation, org-chart nodes, status indicators, testimonial avatars. Never on buttons, status pills, or generic chrome. That's why the palette stays meaningful.

ON LIGHT BACKGROUND
ON DARK BACKGROUND
01 · pink → forest
02 · lavender → pink
03 · purple → maroon
04 · cream → amber
05 · cobalt → green
06 · orange → maroon
07 · sky → hot pink
08 · mint → violet
09 · peach → cobalt
10 · yellow → teal
11 · peri → mauve
12 · teal → green
custom
CUSTOM CAPSULE — pick a flat color or two-color gradient
top bottom
linear-gradient(to bottom, #F3B49E, #1F4ED4)

Color usage

RULES
DO
Approve Override

Use ink + neutral grays for chrome. Green is a status color (success / done), not the accent — it earns its place only when a state truly is "succeeded."

DON'T
Approve Override

No gradient buttons. No agent-palette colors on chrome. Saturation in chrome dilutes the brand-language signal.

DO
3 active agents

Agent palette belongs on the capsule motif — agent representation only.

DON'T

Never on buttons or product chrome. A gradient badge is fine as a marketing flourish — the website landing page uses one — but the palette is never a product-UI decoration kit.

05SPACING & LAYOUT

8‑point base. Fluid where it scales.

The website's rhythm is a small t‑shirt scale on an 8‑pixel base, with the large end expressed in CSS clamp() so whitespace stays generous on big screens and collapses gracefully on small ones. The product deliberately ships no fixed spacing scale — its DESIGN.md defers the scale to a human and only requires that spacing route through tokens, never raw px.

Spacing scale

WEBSITE · 8‑PT BASE · FLUID TOP END

These are the website tokens (src/styles/global.css). Steps xsmd are fixed; lg and up are fluid. --space-section is the single vertical‑rhythm token for marketing sections.

TOKEN VALUE VISUAL USAGE
--space-xs0.5rem · 8px
Inline gaps, chip padding, label→input
--space-sm1rem · 16px
Tight card padding, list row gap
--space-md1.5rem · 24px
Default card padding, control gaps
--space-lgclamp(2rem, 1.5rem + 1.5vw, 3rem)
Card‑to‑card spacing in marketing
--space-xlclamp(3rem, 2rem + 3vw, 5rem)
Subsection blocks
--space-2xlclamp(4rem, 2.5rem + 4.5vw, 7rem)
Large editorial gaps
--space-3xlclamp(5rem, 3rem + 6vw, 10rem)
Page‑top, hero padding
--space-sectionclamp(6.25rem, +0.75vw, 6.9rem)fluidMarketing section padding‑y (single rhythm token)
--space-section-gapclamp(2rem, 5vw, 6rem)fluidGap between stacked sections

Product note: the product has no --space-* scale — spacing uses Tailwind's step utilities through tokens, and the canonical scale is deliberately deferred (DESIGN.md: "spacing routes through tokens; the final scale is a human decision"). Don't cite a numeric product spacing token — there isn't one yet.

06BORDER RADIUS

One anchor. The whole ladder follows.

Radius isn't a set of magic numbers — it's a single knob. --radius: 0.5rem (8px) is the anchor, and every other step is calc()'d off it, so the entire corner language retunes from one variable. Shape still carries meaning: a rounded rect is a card, a full‑round pill is a badge, and a stadium capsule is an agent.

The ladder

ONE ANCHOR · calc() LADDER

--radius: 0.5rem = rounded-lg = 8px is the anchor. Every step is calc(--radius × n), so the scale is strictly monotonic and retunes from one variable. The website exposes --r-sm / --r-md / --r-lg mapped onto it.

sm
×0.6 · 4.8px
md
×0.8 · 6.4px
lg · anchor
×1.0 · 8px
xl
×1.4 · 11.2px
2xl
×1.8 · 14.4px
3xl
×2.2 · 17.6px
4xl
×2.6 · 20.8px

4.8 → 6.4 → 8 (lg · anchor) → 11.2 → 14.4 → 17.6 → 20.8 px  ·  usage: sm inputs/chips, md buttons (rounded-md), lg cards/popovers, xl+ dialogs/sheets

SPECIAL SHAPES · rounded-full — off the numeric ladder
PILL

Badges · status pills · the marketing CTA (a website brand exception)

CAPSULE

Agents only — a 1:2 stadium (AgentCapsule)

Shape vocabulary

WHAT EACH CORNER MEANS

In the product, buttons are rounded-md — pill is reserved for badges, status pills, and the agent capsule. The marketing site keeps a pill primary CTA as a documented brand exception, echoing the capsule at the chrome level; everything else uses the rounded‑rect ladder.

DO
Badge

Button = rounded-md. Badge + capsule = rounded-full. Each shape reads as a distinct kind of thing.

DON'T
Badge

Don't square off buttons or badges, and don't invent per‑component radii. Retune the one --radius anchor instead.

07ICONOGRAPHY

Functional.
Not decorative.

Icons come from lucide-react ^0.577.0 — one library, consistently stroked. They label actions, states, or functionality; they never compete with the capsule motif for attention. The task-status glyphs in §13 are a canonical subset (CircleDashed, Circle, RotateCw, CircleDot, CircleCheck, CircleMinus, Ban).

Icon grid

LUCIDE · 24PX · 2PX STROKE
clock
crosshair
arrow-right
check
x
pause
play
square
paperclip
menu
search
settings
list
app-window
plus
minus
info
triangle-alert

Sizing

FOUR STEPS
12px · inline
16px · UI default
20px · button
32px · feature
Stroke weight tightens as icons grow: 2.0 → 1.5px. All icons share the same 24-viewbox base path; we just scale.

Do & don't

RULES
DO

Stroke only. currentColor. Rounded line caps + joins.

DON'T

No filled glyphs in chrome. Fills compete with capsule-motif fills.

DON'T

No emoji in product UI or technical docs. Emoji is a different visual language.

08IMAGERY & MOTIFS

The capsule is the agent.

Every agent is a capsule. Every capsule is an agent. The mark and the metaphor are the same shape.

The capsule

PROPORTION 1 : ≥2
2x x

Construction: border-radius: 9999px (or 50% of width). Fill: smooth linear-gradient(to bottom, anchor-a, anchor-b). Flat single-color variant available for contexts that need a solid mark.

Applications

FOUR USES
CAPSULE FIELD — MARKETING MOMENT

Eight agents. Each its own gradient. Different heights signal cost, status, or output.

ORG CHART NODES
CEO
Hermes
CTO
Codex
CMO
OpenClaw
COO
Claude
HEARTBEAT PULSE — 8×16 MINI CAPSULE
CEO Hermes — alive
CTO Codex — waiting
CMO OpenClaw — blocked
COO Claude — asleep
AVATARS — 40PX CIRCLES, AGENT GRADIENTS
H
O
M
C
C
+7

One initial on the agent gradient until a profile photo is uploaded. Same gradient classes as capsules — every visual of an agent shares one DNA.

Graphic generator

CAPSULE COMPOSITIONS

The graphic generator produces on-brand artifacts — OG images, blog covers, social posts, slide backdrops — by arranging capsules into templates and colorizing them from palettes. Below: a mini, in-doc version. Production tool: paperclip-graphic-generator.

TEMPLATE
PALETTE
PARAMETERS
BACKGROUND
In production: the full generator exports PNG (raster) at preset sizes (X / Twitter post, Open Graph, blog cover, etc.) and SVG (vector, scales any size).
09TONE OF VOICE

Direct. Confident. Never decorative.

Paperclip speaks with authority about complex systems but never obscures meaning. Short sentences. Active verbs. Technical when needed, never when not.

Four pillars

CLARITY · CONTROL · HONESTY · CREDIBILITY
CLARITY

One idea per sentence. Cut what doesn't earn its place.

"Paperclip uses those agents. It orchestrates them into a company — with org charts, budgets, goals, governance, and accountability."
CONTROL

The human is always in charge. Imperative verbs.

"Pause. Resume. Override. Reassign. Terminate. You have full control over every agent in the org."
HONESTY

Say what Paperclip is, and what it isn't. Specificity, not superlatives.

"Not a chatbot. Not an agent framework. Not a workflow builder."
CREDIBILITY

Precise technical language where it matters. Observable, traceable, logged.

"Full observability. Every task traced. Every decision explained. Every tool call logged."

What to avoid

DON'T
  • NOBuzzwords without substance — "revolutionary", "game-changing", "next-gen".
  • NOPassive voice where active is clearer.
  • NOVague claims — "powerful", "robust", "scalable" — without specifics.
  • NOExclamation marks in product copy.
  • NOEmoji in technical documentation.
  • NOSynonyms for one concept — it's a task everywhere, never an "issue" or "ticket". One name per concept.

In practice

REWRITES
BEFORE

"Paperclip is a revolutionary, AI-powered next-gen platform that empowers teams to leverage the full potential of their agentic workforce!"

AFTER

"Paperclip orchestrates a team of AI agents. Org charts, budgets, goals. You hire. You approve. You ship."

BEFORE

"Various optimizations have been applied to ensure that runaway costs are mitigated through robust budget enforcement mechanisms."

AFTER

"Every agent gets a monthly budget. When they hit it, they stop. No runaway costs. Hard limits, enforced by the system."

BEFORE

"🚀 Get started with Paperclip today and unlock the future of work! 💼✨"

AFTER

"Open source. Self-hosted. npx paperclipai onboard and you're running."

10MOTION

Purposeful. Never showy.

Animation signals life and state. Entrance reveals stagger. Active elements pulse. Transitions ease at 250ms. Nothing bounces unless it has a reason — and almost nothing does.

Easings & durations

WEBSITE · TWO EASINGS · THREE DURATIONS

These are the website's motion tokens (src/styles/tokens.css). The product ships no --ease/--dur tokens — its motion lives in named @keyframes and utility classes instead (see below).

TOKEN VALUE WHERE
--ease-entercubic-bezier(.2,.8,.2,1)Entrance, hover lifts, primary motion
--ease-exitcubic-bezier(.4,0,1,.6)Exit, dismissal
--t-micro150msHover color shifts, focus rings
--t-short250msDefault UI transitions, button hover
--t-medium400msTheme swap, panel slide-in

Product motion = keyframe utilities. Reserved for states that demand attention: .hb-pulse (1.6s — running agents), .hb-blink (1.2s — errored agents), .typing-dots, .shimmer-text, .activity-row-enter, .cot-line-enter/-exit. All honor prefers-reduced-motion.

Reusable patterns

FIVE
STAGGERED ENTRANCE

450ms ease-out · 140ms stagger · translateY 12 → 0

HEARTBEAT PULSE

1.6s ease-in-out · infinite · alive · scale 0.85 ↔ 1.0

HOVER TRANSITION

200ms ease-out · color shift only · no transform, no bounce

SCROLL REVEAL
Headlines fade in.

700ms ease-out · opacity 0 → 1 · translateY 24 → 0

THEME SWAP

400ms ease-out · background + color crossfade

Reduced motion

ALWAYS RESPECT

Every motion pattern has a no-motion fallback. If the user prefers reduced motion, animations stop or shorten to a 100ms color crossfade.

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 100ms !important;
    scroll-behavior: auto !important;
  }
}
11COMPONENTS

Living elements. Tested states.

The product ships one component per job — shadcn/ui patterns on Radix primitives, variants via class-variance-authority, icons from lucide-react. Everything renders in light and dark; every interactive control has explicit default, hover, focus, active, and disabled states. Marketing chrome (pill CTAs, hero flourishes) is a documented brand exception layered on the same tokens.

SHARED PRIMITIVES · ui/src/components/ui/

alert-dialog · avatar · badge · breadcrumb · button · card · checkbox · collapsible · command · dialog · dropdown-menu · input · label · popover · radio-card · resizable-panels · scroll-area · select · separator · sheet · skeleton · tabs · textarea · toggle-switch · tooltip. One of each — variants are props, not new components.

Buttons

6 VARIANTS · SEMANTIC · 5 STATES

The Button (CVA) ships six variants: default · secondary · outline · ghost · destructive · link, all rounded-md in the product. Two semantic treatments override the fill by meaning — approve is green (bg-green-700), terminate red (destructive). The marketing site renders its primary CTA as a fully-rounded ink pill — a documented brand exception. Green is never the neutral accent; it earns its place only on the approve action.

PRODUCT VARIANTS · rounded-md
DEFAULT
SECONDARY
OUTLINE
GHOST
DESTRUCTIVE
LINK
SEMANTIC ACTION & MARKETING PILL
APPROVE · GREEN
bg-green-700 · approval only
MARKETING · PILL
fully rounded (--r-pill) — website CTA only
STATE EXPLORER
default
hover
focus
active
disabled
loading
SIZES · xs · sm · default · lg (+ icon variants)

Form controls

INPUT · TEXTAREA · SELECT · CHECKBOX · RADIO-CARD · TOGGLE
TEXT INPUT
TEXTAREA + SELECT
CHECKBOX · RADIO · TOGGLE


Auto-pause at budget cap
Email weekly summary

Radios ship as radio-card (a selectable card, not a bare dot). There is no slider or stepper primitive — a numeric budget is a plain input.

Cards

ONE PRIMITIVE · COMPOSED

There is one Card. It's a bordered white surface (--card = --background, separated by --border) — the variants below are compositions (content, an inverted dark callout, a selected state), not separate components.

FEATURE CARD

Schedule.

Agents wake on schedule or on demand. Every action logged.

CEO Hermes
claude-sonnet-4-5 · 4h
Active
$23.40 / $60↑ 12 tasks
CARD · INVERSE

You're in control.

Same card pattern, dark surface. Used in marketing callouts and product modals.

INTERACTIVE CARD (BYOB)
Claude Code
claude-sonnet-4-5
Selected state — adapts to theme. Light: bond surface, ink text. Dark: raised surface, off-white text.

Menus & navigation

OPEN PATTERNS
DROPDOWN MENU
Click "Actions" to open. Click outside to close. Each item is a real button.
TABS — UNDERLINE
CEO → CTO → engineers. Capsule nodes, hairline connectors.
BREADCRUMBS
Hover a link to lift to ink.
COMMAND PALETTE · ⌘K
⌘K
Actions
Pause CEO Hermes⌘P
Create task…⌘N
Jump to Org chartG O
cmdk palette — fuzzy search across agents, tasks, projects, and actions.

Overlays

DIALOG · ALERT-DIALOG · POPOVER · SHEET · TOOLTIP

Radix-backed overlays. A confirming Dialog and a Popover are shown; the same family covers alert-dialog (destructive confirms), sheet (side panels), and tooltip. There is no toast primitive.

DIALOG
Terminate CEO Hermes?

All in-flight tasks will be cancelled and reassigned to COO Claude. This is reversible from the audit log within 24h.

POPOVER
Monthly budget
$10$60$500

Progress & loaders

DETERMINATE + INDETERMINATE
PROGRESS BAR
Budget — CEO Hermes$23.40 / $60
Budget — CMO OpenClaw$32 / $40 · 80%
Budget — CTO Codex$50 / $50 · 100%
LOADERS
SPINNER
DOTS
INDETERMINATE BAR
SKELETON

Terminal & code

ENGINEERING TONE

The onboarding terminal is a marketing depiction. In-product, rich text and code run through @mdxeditor/editor + CodeMirror; both use the --font-mono token and the same neutral surfaces.

paperclip — bash
$ npx paperclipai onboard --yes

   Embedded postgres started
   Company created: note-taking-app
   CEO Hermes online (claude-sonnet-4-5)
   Dashboard at http://localhost:3100

$ 
paperclip.config.ts
// Self-host config
export default {
  company: 'note-taking-app',
  db: 'postgres://localhost',
  heartbeat: 4, // hours
  budgets: { ceo: 60 },
}
12DATA & VIZ

Telemetry, not decoration.

Charts read like instruments. Ink lines on hairline grids. Mono labels. No gradients in chart fills — saturation stays in the agent palette where it carries meaning.

Cost panel

PER-AGENT BUDGETS
Monthly cost — November
$0 / $240 · 0%
AGENT BUDGET USED COST
CEO Hermes
$23.40 / $60
CMO OpenClaw
$32.00 / $40
CTO Codex
$50.00 / $50
COO Claude
$5.40 / $30

Heartbeats & sparklines

SYSTEM TELEMETRY
CEO Hermes
4h ago
CMO OpenClaw
8h ago
CTO Codex
blocked
COO Claude
asleep

Conversation trace

EVENT LOG
#1042 · Deploy updated pricing page
In progress
14:32:07 Goal set: "Deploy to prod after tests pass" You GOAL SET
14:33:14 Budget set: $50 hard limit You BUDGET SET
14:34:02 run_tests() — 142 passed, 0 failed CTO Codex PASSED
14:34:38 deploy_to_staging() — succeeded in 28s CTO Codex DEPLOYED
14:35:11 smoke_test() — 1 timeout, retrying CTO Codex RETRYING
13STATES & STATUS

Color is meaning. Shape is meaning.

One color language across every entity — agents, tasks, projects. The governing rule: blue = liveness. Actively running work is blue; queued, idle, or inert work is not. Every status below is rendered with the real chip CSS, so what you see is the spec.

The brand chip palette

SIX COLORS · LIGHT + DARK

Six semantic colors. Each is a brand .task-chip: a 1px-bordered pill with distinct light and dark values. Light chips use a tinted fill + saturated text; dark chips use a translucent fill of the solid hue + solid-color text + a 45%-alpha border. Toggle the page theme to see both states live.

COLOR CHIP LIGHT — BG / TEXT / BORDER DARK — BG / TEXT / BORDER
gray
inert / resting
gray #F5F3F0 / #52585D / #A8AEB2 #6E6960·14% / #9A958A / #9E958A·45%
blue
live / running
blue #DBEAFE / #1D4ED8 / #2563EB #2563EB·18% / #5B8DEF / #2563EB·45%
amber
queued / paused
amber #FEF3C7 / #B45309 / #F59E0B #F59E0B·14% / #F59E0B / #F59E0B·45%
green
done / online
green #DCFCE7 / #188A3C / #22C55E #22C55E·12% / #34D06F / #22C55E·45%
red
error / blocked
red #FEE2E2 / #991B1B / #DC2626 #DC2626·18% / #EF4444 / #DC2626·45%
violet
awaiting review · reserved
violet #EDE9FE / #5B21B6 / #7C3AED #7C3AED·18% / #A78BFA / #7C3AED·45%

Alpha suffixes (e.g. ·18%) are written in the codebase as 8-digit hex — #2563eb2e = #2563EB at 18% alpha. Violet is a shipped chip — it's the sixth entry in brandChipBadge, scoped to task in_review (it isn't a general-purpose chrome color).

Task states

SEVEN STATES · BOARD VOCABULARY

Every task lives in exactly one of these states. The blue = liveness rule applies: in_progress is blue (actively worked), todo is amber (queued — ready but not live). The icon, color, and copy are fixed so the same status reads identically on the board, in a row, and in a chip.

CHIP VIEW
Backlog Todo In progress In review Done Cancelled Blocked
GLYPHS — LUCIDE‑REACT ^0.577.0 · StatusGlyph.tsx
STATUS LUCIDE ICON GLYPH HUE — --status-task-icon-* (light / dark)
backlogCircleDashed#52585D / #9A958A
todoCircle#CC7A00 / #FBBF24
in_progressRotateCw#2563EB (both)
in_reviewCircleDot#7C3AED / #9474F0
doneCircleCheck#16A34A / #34D06F
blockedCircleMinus#DC2626 (both)
cancelledBan#52585D / #9A958A
in_queue (synthetic)CircleMinus → blue#2563EB
DERIVED BLOCKED SUB-STATES — COLLAPSED TO TWO
SUB-STATE CHIP COLOR SELF-HEALS?
In queue
blocked behind a live child
In queue blue Yes — unblocks when the child finishes. No action needed.
blocked
plain blocked = needs a human
Blocked red No — needs a human decision / triage / unblock.
BOARD VIEW — KANBAN
Backlog12
Migrate to pg 16
Add SSO
Todo8
Build pricing page
Write release notes
In progress3
Deploy pricing v2
WebSocket handler
In review2
Auth refactor
Done47
Set up CI
Cancelled4
Redo logo
Blocked1
SSL cert auth

Agent statuses

FOUR STATES · HEARTBEAT CAPSULE

The agents list renders four states. The indicator is a heartbeat capsule (8×16, r4, solid fill); the row chip is the matching brand chip. active aliases idle; terminated and pending_approval are excluded from the list — the hiring gate lives in the task thread as a violet in_review task.

STATUS CAPSULE CHIP COLOR BEHAVIOUR
idle
active aliases this
idle gray Online, no active run — the resting / available state. Capsule is still.
running running blue · live A heartbeat is executing right now. Capsule pulseshb-pulse 1.6s.
paused paused amber Halted — manual / budget / system. Capsule is still. (Amber, not gray, so it doesn't collide with idle.)
error error red The agent itself is broken (e.g. not connected to a model) — distinct from a task failing. Capsule blinkshb-blink 1.2s.
Light — #FFFFFF
Agents
HermesCEO1midle
VegaEngineer30srunning
IrisSecurity1hpaused
KiloAnalyst3herror
Dark — #141413
Agents
HermesCEO1midle
VegaEngineer30srunning
IrisSecurity1hpaused
KiloAnalyst3herror

Project statuses

FIVE STATUSES + TWO OVERLAYS

Five project statuses. Unlike tasks, projects render as text-only chips (the generic statusBadge — Tailwind classes, no glyph): in_progress is blue (liveness), completed green, and backlog / planned / cancelled are neutral muted. paused and archived are overlays, not enum values.

STATUS CHIP COLOR MEANING
backlog backlog gray · muted Parked / unscheduled — not yet picked up.
planned planned gray · muted Committed but not started (bg-muted, like backlog).
in_progress in progress blue · live Actively being worked — has live work. Liveness.
completed completed green Delivered / closed out (matches task done).
cancelled cancelled gray · muted Abandoned, not resumed. Muted + strikethrough.
OVERLAYS — NOT ENUM VALUES
OVERLAY CHIP COLOR MEANING
paused
pauseReason
paused amber Halted (manual / budget / system). Overlay on the base status — consistent with a paused agent.
archived
archivedAt
— hidden from the list (no chip) — Filtered out of All Projects, like terminated agents.
Light — #FFFFFF
Projects
Paperclip App12 tasksin progress
Paperclip Website8 tasksplanned
companies.io3 tasksbacklog
Onboarding revamp5 taskscompleted
Dark — #141413
Projects
Paperclip App12 tasksin progress
Paperclip Website8 tasksplanned
companies.io3 tasksbacklog
Onboarding revamp5 taskscompleted

One color language

CROSS-ENTITY

Colors mean the same thing whether the row is an agent, a task, or a project. When you build new UI, choose the color by what the state means, then apply the chip token.

COLOR MEANS AGENT TASK PROJECT
blue live — actively running now running in_progress · In queue in_progress
amber queued / paused — ready or halted, not live paused todo planned · paused
green done / online — finished or healthy done completed
violet awaiting review — needs a human look (hiring gate → task) in_review
red error / blocked — broken, needs a human error blocked
gray inert — resting, parked, or cancelled idle · terminated backlog · cancelled backlog · cancelled

Heartbeat states

AGENT LIVENESS

Motion is reserved for states that demand attention: only running pulses and only error blinks — idle and paused are still. Product token: 8×16, r4, solid fill (rendered larger here for display). Both animations honor prefers-reduced-motion.

IDLE
Still · gray
RUNNING
Pulse 1.6s · blue
PAUSED
Still · amber
ERROR
Blink 1.2s · red

Capsule grays are theme-aware: #A8AEB2 light / #6E6960 dark. Blue, amber, and red fills are literal in both themes.

Copy-paste tokens

FOR ENGINEERS

Every chip above maps to these exact exports in ui/src/lib/status-colors.ts. The chip recipe (bg/text/border) and the standalone glyph hue are two layers: chips use brandChipBadge; a bare StatusGlyph next to text uses the AA-tuned --status-task-icon-* vars so it clears WCAG 3:1. Use the maps; don't hand-write hex.

ui/src/lib/status-colors.ts
// Brand .task-chip palette — light + dark, 1px border (all six colors)
export const brandChipBadge = {
  gray:   "bg-[#F5F3F0] text-[#52585D] border-[#A8AEB2] dark:bg-[#6e696024] dark:text-[#9A958A] dark:border-[#9e958a73]",
  blue:   "bg-[#DBEAFE] text-[#1D4ED8] border-[#2563EB] dark:bg-[#2563eb2e] dark:text-[#2563EB] dark:border-[#2563eb73]",
  amber:  "bg-[#FEF3C7] text-[#B45309] border-[#F59E0B] dark:bg-[#f59e0b24] dark:text-[#F59E0B] dark:border-[#f59e0b73]",
  green:  "bg-[#DCFCE7] text-[#188A3C] border-[#22C55E] dark:bg-[#22c55e1f] dark:text-[#22C55E] dark:border-[#22c55e73]",
  violet: "bg-[#EDE9FE] text-[#5B21B6] border-[#7C3AED] dark:bg-[#7c3aed2e] dark:text-[#7C3AED] dark:border-[#7c3aed73]",
  red:    "bg-[#FEE2E2] text-[#991B1B] border-[#DC2626] dark:bg-[#dc26262e] dark:text-[#DC2626] dark:border-[#dc262673]",
};

// Status → brand color name
issueStatusColor = { backlog:"gray", todo:"amber", in_progress:"blue", in_review:"violet", done:"green", blocked:"red", cancelled:"gray" };
agentStatusColor = { idle:"gray", active:"gray", running:"blue", paused:"amber", error:"red" };
// projects have no brand-chip map — they render text-only via statusBadge (Tailwind)

// Chip base hue vs. AA-tuned GLYPH hue (two separate var layers)
taskStatusVar     = { in_progress:"--status-task-in_progress", todo:"--status-task-todo", /* … */ };
taskStatusIconVar = { todo:"--status-task-icon-todo", done:"--status-task-icon-done", /* … */ };

// Standalone glyph = lucide-react ^0.577.0 (StatusGlyph.tsx)
statusGlyph = { backlog:CircleDashed, todo:Circle, in_progress:RotateCw,
                in_review:CircleDot, done:CircleCheck, blocked:CircleMinus, cancelled:Ban };

// Heartbeat capsule (agents) — 8×16, r4, solid fill
agentStatusCapsule = { gray:"#A8AEB2 / dark #6E6960", blue:"#2563EB", amber:"#F59E0B", red:"#DC2626" };
agentStatusMotion  = { running:"hb-pulse", error:"hb-blink" };  // durations in index.css keyframes
14DESIGN TOKENS

One canonical source.

The core product's ui/src/index.css is the single source of truth: every value is a CSS custom property, exposed to Tailwind v4 through @theme inline — there is no tailwind.config file. The website vendors those shipped tokens and maps them onto its own names, kept aligned by npm run tokens:sync. Same values, one flow, no hand-copied hex.

CSS variables

CANONICAL · PRODUCT

Defined once in ui/src/index.css. The semantic layer is OKLCH and swaps under .dark; status hues and agent gradients are literal in both themes. Radius is a single anchor plus a calc() ladder.

ui/src/index.css
:root {
  /* ── Semantic — OKLCH, shadcn core (light) ── */
  --background:  oklch(1 0 0);
  --foreground:  oklch(0.145 0 0);
  --card:        oklch(1 0 0);          /* = --background (bordered, not filled) */
  --popover:     oklch(1 0 0);
  --primary:     oklch(0.205 0 0);      /* neutral primary / accent */
  --primary-foreground: oklch(0.985 0 0);
  --secondary:   oklch(0.97 0 0);
  --muted:       oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent:      oklch(0.97 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --border:      oklch(0.922 0 0);
  --input:       oklch(0.922 0 0);
  --ring:        oklch(0.708 0 0);
  --chart-1: oklch(0.87 0 0);  /* … grayscale ramp … */
  --chart-5: oklch(0.269 0 0);

  /* ── Radius — one anchor, calc ladder ── */
  --radius:     0.5rem;                     /* rounded-lg IS the anchor (8px) */
  --radius-sm:  calc(var(--radius) * 0.6);
  --radius-md:  calc(var(--radius) * 0.8);
  --radius-lg:  var(--radius);
  --radius-xl:  calc(var(--radius) * 1.4);
  --radius-2xl: calc(var(--radius) * 1.8);

  /* ── Status hues — one base hue per state (mode-independent) ── */
  --status-task-backlog:     #a8aeb2;
  --status-task-todo:        #f59e0b;
  --status-task-in_progress: #2563eb;   /* liveness */
  --status-task-in_review:   #7c3aed;
  --status-task-done:        #22c55e;
  --status-task-blocked:     #dc2626;
  --status-task-cancelled:   #a8aeb2;
  --status-agent-idle:    #a8aeb2;  --status-agent-running: #2563eb;
  --status-agent-paused:  #f59e0b;  --status-agent-error:   #dc2626;
  /* AA-tuned glyph hues get light+dark overrides, e.g.
     --status-task-icon-todo: #cc7a00;  --status-task-icon-done: #16a34a; */

  /* ── Agent capsule gradients — fixed hex, identical both themes ── */
  --agent-1a: #f7cfdc; --agent-1b: #1f7a3a;   /* … through --agent-10a/10b … */
  --agent-4a: #f3e6c4; --agent-4b: #e3a21a;   /* the one surviving manila hex */

  /* ── Type — self-hosted InterVariable + system mono ── */
  --font-sans:    "InterVariable", "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono:    ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
  --font-heading: var(--font-sans);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card:       oklch(0.205 0 0);
  --primary:    oklch(0.922 0 0);       /* flips near-white on dark */
  --muted-foreground: oklch(0.708 0 0);
  --border:     oklch(1 0 0 / 10%);
  --input:      oklch(1 0 0 / 15%);
  --ring:       oklch(0.556 0 0);
}

Tailwind v4 theme

PRODUCT · @THEME · NO CONFIG

There is no tailwind.config.js. Tokens are wired to utilities in CSS via @theme inline, so bg-background, text-foreground, border-border, and rounded-lg resolve to the vars above — and dark mode flips through the .dark class without duplicating hex in TypeScript.

ui/src/index.css — @theme block
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));

@theme inline {
  /* semantic colors point at the CSS vars → dark mode flips for free */
  --color-background:       var(--background);
  --color-foreground:       var(--foreground);
  --color-card:             var(--card);
  --color-primary:          var(--primary);
  --color-secondary:        var(--secondary);
  --color-muted:            var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent:           var(--accent);
  --color-destructive:      var(--destructive);
  --color-border:           var(--border);
  --color-input:            var(--input);
  --color-ring:             var(--ring);

  /* radius ladder + fonts */
  --radius-sm: var(--radius-sm);
  --radius-md: var(--radius-md);
  --radius-lg: var(--radius-lg);
  --font-sans: var(--font-sans);
  --font-mono: var(--font-mono);
}
/* → utilities: bg-background · text-foreground · border-border ·
     rounded-lg · font-mono · text-muted-foreground … */

Website vendor & sync

WEBSITE · GENERATED

The website is a different stack (vanilla CSS · Astro + Next), so the product's shipped tokens are vendored, not imported. A generated copy carries the OKLCH neutrals/status/radius verbatim; a hand-authored map renames them to the site's component tokens plus the documented brand exceptions. It's CSS-to-CSS — there is no tokens.json or Figma export.

token flow — npm run tokens:sync
paperclip/ui/src/index.css          CANONICAL — the product ships it
        │
        │   npm run tokens:sync   (reads the core repo, stamps the commit)
        ▼
web/app/styles/core-tokens.css      GENERATED · do not edit
        │   the :root + .dark neutral / semantic / status / radius tokens,
        │   copied verbatim (OKLCH). Product-only domain tokens omitted.
        │
        │   @import
        ▼
web/app/styles/tokens.css           CANONICAL MAP · Next (web/) = production
        │   --bg      = var(--background)     --rule = var(--border)
        │   --surface = var(--card)           --r-md = var(--radius)
        │   --success/--warning/--error/--info = var(--status-task-*)
        │   BRAND EXCEPTIONS (website ≠ product, on purpose):
        │     --font-serif = Inter Tight 600   (product headings = Inter)
        │     --font-mono  = JetBrains Mono     (product = system ui-monospace)
        │     --color-accent = var(--ink-t)     (neutral; green swap deferred)
        │     --r-pill = 9999px                 (pill CTA — website only)
        │
        │   npm run tokens:sync mirrors DOWN to
        ▼
src/styles/tokens.css               FROZEN Astro mirror · never hand-edit

# verify:  grep -ri manila src web   →  only #f3e6c4 (agent-4 gradient)