You are implementing UI for a monochrome-first design system. Color exists only for semantic meaning. All surfaces, borders, and text are achromatic by default.
- Achromatic base — Use pure gray for all surfaces, borders, and text. Do NOT use tinted neutrals. Instead, use the neutral palette (C:0.000) for all non-semantic elements.
- Color = meaning — Apply chromatic color only for semantic callouts, diagrams, status indicators, and data viz. Before using any color, answer: "what does this hue mean here?" If there is no semantic answer, use neutral gray instead.
- Equal hue standing — All 9 chromatic hues have equal weight. Do NOT treat any single hue as "the brand color." Instead, select hue based on semantic meaning (see Color Selection Procedure).
- Flat construction — Do NOT use gradients, shadows, or glows. Instead, use solid fills, clean borders (1px solid), and whitespace for visual hierarchy. Exception: All emoji representations (Noto SVGs via
scripts/fetch-emoji.js, custom inline SVG recreations, or<img>fallbacks) are exempt — emoji visuals must never be modified to conform to this system. - Typographic interaction — Identify interactive elements by typography and shape. Use underlines + font-weight for links. Use dark/light fills for buttons. Do NOT rely on color to signal interactivity. Instead, use shape, weight, and underlines.
- Color budget: 60-30-10 — 60% achromatic surfaces, 30% elevated gray, 10% semantic color. Default to 95/5 for content pages. Reserve 60-30-10 for diagram-heavy pages only.
- Curved default, angular accent — Use rounded forms (squircle containers, Bezier connectors) as the default shape vocabulary. Reserve angular forms (diamonds, chevrons, sharp miters) for high-arousal semantic states (error, warning, code). Do NOT mix angular containers with positive-valence content. Instead, match shape curvature to semantic valence (see Illustration System).
- Motion is purposive — Every animated element must answer: "what does this motion orient, teach, or confirm?" If no answer, use no animation. Do NOT animate for decoration. Reserve looping motion (idle states) for semantic signals only: active authoring, AI processing, data flow, system readiness. Max 2 simultaneous idle loop classes per figure (staggered DOM instances of the same animation class count as 1).
- Static completeness — Design the final settled state first. Animation reveals content; it does not define it. Every figure must communicate its full concept in the phase=1 state with no motion.
| Hue | Semantic Role | Apply To |
|---|---|---|
| Error (H:25°) | Danger, critical | Error states, breaking changes, destructive actions |
| Warning (H:70°) | Caution, attention | Warnings, deprecation notices, hallucination risk |
| Success (H:155°) | Validated, complete | Completed states, validation passes, active connections |
| Cyan (H:195°) | System, code | System components, code generation, infrastructure |
| Indigo (H:250°) | Knowledge, data | Documentation, context retrieval, data references |
| Violet (H:285°) | AI transformation | AI processing, transformation steps, synthesis operations |
| Magenta (H:320°) | AI creative | LLM agents, creative processes, prompt engineering |
| Neutral | Human actor / base | Human actors, developer intent — achromatic by design |
Removed hues: Lime (H:110°) aliased to Success — semantically overlapping at 45° gap.
Rose (H:355°) aliased to Neutral — human actors represented achromatic per the base principle.
CSS tokens --visual-lime and --visual-rose remain as aliases for backward compatibility.
| Context | Light Mode Shade | Dark Mode Shade |
|---|---|---|
| Semantic text and icons | Pareto-optimal¹ (WCAG AA ≥4.5:1 on white) | 400 (WCAG AA on #0d1117) |
| Subtle tinted backgrounds | 50 | 950 |
| Borders, decorative fills | 100–200 | 700–800 |
| Mid-tone accents | 500 | 500 |
| Darkest text on colored bg | 900 | — |
| Token | Light mode | Dark mode | OKLCH |
|---|---|---|---|
--visual-error |
#ee0028 | #ec7069 | Pareto-optimal, H:25° |
--visual-warning |
#a76900 | #cd8c37 | Gamut-clipped C=0.125, H:70° |
--visual-success |
#00894d | #48b475 | C=0.137, H:155° |
--visual-cyan |
#008485 | #00b2b2 | Gamut-clipped C=0.095, H:195° |
--visual-indigo |
#307ac0 | #53a0ec | C=0.13, H:250° |
--visual-violet |
#736cc3 | #938eeb | C=0.13, H:285° |
--visual-magenta |
#9d5fab | #c07ecf | C=0.13, H:320° |
--visual-neutral |
#666666 | #9b9b9b | Achromatic |
--visual-lime |
→ --visual-success |
→ --visual-success |
Alias — removed from spectrum |
--visual-rose |
→ --visual-neutral |
→ --visual-neutral |
Alias — removed from spectrum |
Chroma normalization: Categorical hues (indigo, violet, magenta) are capped at C=0.13 to enforce visual equal-standing. The previous Pareto-optimal approach produced violet/magenta at C≈0.29 — three times louder than cyan (C=0.095). The 1.37× residual variation is physical gamut limits at constrained hue angles (cyan, warning). Error retains Pareto-optimal for its semantic role as a high-arousal danger signal.
Transparent tints at 10% light / 15% dark:
--visual-bg-{hue}: color-mix(in srgb, var(--visual-{hue}) 10%, transparent);
/* Dark mode: 15% instead of 10% */All 10 hues (error, warning, lime, success, cyan, indigo, violet, magenta, rose, neutral) have --visual-bg-* tokens.
| Role | Font | CSS Variable | Weights |
|---|---|---|---|
| Display / headings | Space Grotesk | --font-display |
600, 700 |
| Body text | Inter | --font-body |
400, 500, 600, 700, 800 |
| Code — default | Monaspace Neon | --font-mono |
400, 500, 600, 700 |
| Code — AI voice | Monaspace Argon | --font-mono-ai |
400, 500 |
| Code — spec/schema | Monaspace Xenon | --font-mono-spec |
400, 500, 600 |
| Code — human note | Monaspace Radon | --font-mono-human |
400 |
| Code — keyword/op | Monaspace Krypton | --font-mono-keyword |
400, 500 |
--font-display: 'Space Grotesk', system-ui, sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
--font-mono: 'Monaspace Neon', monospace;
--font-mono-ai: 'Monaspace Argon', monospace;
--font-mono-spec: 'Monaspace Xenon', monospace;
--font-mono-human: 'Monaspace Radon', monospace;
--font-mono-keyword: 'Monaspace Krypton', monospace;
--font-mono-features: 'calt' 1, 'liga' 0;Apply --font-display to h1, h2. Apply --font-body to body. Monaspace faces share identical metrics — mix freely.
Apply to all Monaspace containers:
font-feature-settings: var(--font-mono-features);caltON — Texture healing for even visual density across the monospace grid.ligaOFF — Prevents ambiguous ligatures. Opt in per-context via stylistic sets (ss01–ss10), never globally.
Color encodes category. Typeface encodes speaker. These axes are orthogonal — a keyword can be Krypton and cyan; a comment can be Radon and muted gray.
| Face | Voice | Apply To |
|---|---|---|
| Neon | System / neutral | Source code, terminal output, config files. Default for all <code> and prompt block base text. |
| Argon | AI agent | LLM responses, agent reasoning traces, AI-generated explanations, ghost text. |
| Xenon | Authoritative / structural | Spec IDs, schema keys, API contracts, system boundary labels, constraint rules. |
| Radon | Human / informal | Code comments, developer notes, prompt drafts, TODO markers. |
| Krypton | Technical / mechanical | Keywords, operators, CLI flags, file paths in callouts, taxonomy labels. |
Code blocks: Neon base. Comments in Radon. Keywords in Krypton. AI output in Argon.
Prompt blocks: Five classes, each assigned two voices (max-2 constraint applies per block):
| Class | Base | Highlight | Highlight tokens |
|---|---|---|---|
| Example | Neon | Krypton | Tool names, file paths, HTTP codes/headers, library names, protocol refs, search queries |
| Template | Neon | Xenon | Placeholders ($VAR, {VAR}, [name]), deliverable filenames, section headers |
| Command | Krypton | Xenon | URLs, file paths, @element-refs |
| Response | Argon | Krypton | Code tokens within AI-generated text |
| Constraint | Neon | Xenon | DO NOT, ALWAYS, Instead — rule-enforcement keywords |
| Human | Radon | — | Human-voice text: persona attribution ("You are a [role]"), conversational/polite phrasing, natural-language requests |
Radon marks any text written in a human voice — persona strings, polite conversational prompts, natural-language requests. It is never used for technical content. Apply as <code class="mono-human"> within a Neon block. Radon is scarce: one span per block maximum.
Spec tables: Spec IDs in Xenon. Verification methods in Neon. Rationale in Argon.
Diagram labels: System/boundary names in Xenon. Agent labels in Argon. Human actors in Radon. Data flows in Krypton.
Inline code: Default Neon. Override via utility class: <code class="mono-ai">, <code class="mono-spec">, etc. Inside .prompt-example, bare <code> defaults to Krypton — no class needed. Override to Xenon with <code class="mono-spec"> for Template/Constraint highlights.
Voice faces: monospace only. Max 2 per block. Radon is scarce — human voice only, never for emphasis or technical content. Fallback: var(--font-mono) → monospace.
Use <PromptExample> for all inline prompt illustrations in MDX docs. Do NOT use fenced code blocks for prompt examples — they lose the typographic voice layer.
import PromptExample from '@site/src/components/PromptExample';The component renders a <div className="prompt-example"> and automatically colorizes markdown structural punctuation (#, ##, -, 1.) in cyan (--visual-cyan) by wrapping them in <span className="md-punct">. No manual <span> needed — just use the component.
CSS class: .prompt-example (defined in website/src/css/custom.css)
- Base font:
--font-mono(Neon) <code>children default to--font-mono-keyword(Krypton) — correct for Example and Command classes- Override for Template/Constraint class: add
className="mono-spec"to placeholder or constraint-keyword<code>elements (Xenon) - Override for Response class: add
className="mono-ai"to the<div>itself (Argon base)
{/* Example class — Krypton highlights */}
<PromptExample>
Use <code>ChunkHound</code> to search for <code>"error patterns"</code> in our codebase.
</PromptExample>
{/* Template class — Xenon placeholders */}
<PromptExample>
Analyze <code className="mono-spec">$FAILURE_DESCRIPTION</code> and propose a fix.
</PromptExample>
{/* Constraint class — Xenon enforcement keywords */}
<PromptExample>
<code className="mono-spec">Do NOT</code> store passwords in plain text.
<code className="mono-spec">Instead</code>, use bcrypt with <code>10 salt rounds</code>.
</PromptExample>
{/* Human class — Radon for human-voice text */}
<PromptExample>
<code className="mono-human">Could you help me write a function to validate email addresses?<br />
Thanks in advance!</code>
</PromptExample>
{/* Response class — Argon base (pass className via wrapper if needed) */}
<div className="mono-ai prompt-example">
I found 3 locations where raw errors leak: <code>auth.ts:47</code>, <code>users.ts:112</code>…
</div>.md-punct — Applied automatically by <PromptExample> to leading markdown structural characters (#, ##, -, 1.). Color: var(--visual-cyan). Do not apply manually.
For line breaks within a prompt block: use <br /> only for intentional structural breaks (one requirement per line in Exact/Command prompts). Prose-style prompts (Exploration) wrap freely — no <br />.
| Tier | Purpose | Naming | Example |
|---|---|---|---|
| Primitive | Raw palette values | {hue}-{shade} |
cyan-600, neutral-200 |
| Semantic | UI surfaces, text, borders, illustration | --{category}-{role} |
--surface-page, --visual-cyan |
| Component | Per-component overrides | (not in this document) | — |
| Token | Light Mode | Dark Mode |
|---|---|---|
--surface-page |
#ffffff | #0d1117 |
--surface-raised |
#f5f5f5 (neutral-50) | #161b22 |
--surface-muted |
#e8e8e8 (neutral-100) | #3d3d3d (neutral-800) |
--text-heading |
#2b2b2b (neutral-900) | #e8e8e8 (neutral-100) |
--text-body |
#505050 (neutral-700) | #d4d4d4 (neutral-200) |
--text-muted |
#808080 (neutral-500) | #9b9b9b (neutral-400) |
--border-subtle |
#d4d4d4 (neutral-200) | #3d3d3d (neutral-800) |
--border-default |
#b7b7b7 (neutral-300) | #505050 (neutral-700) |
Base unit: 8px. All spacing and line-heights snap to 8px grid multiples.
| Token | Value |
|---|---|
--space-0 |
0px |
--space-px |
1px |
--space-0h |
4px |
--space-1 |
8px |
--space-2 |
16px |
--space-3 |
24px |
--space-4 |
32px |
--space-5 |
48px |
--space-6 |
64px |
--space-7 |
80px |
--space-8 |
96px |
--space-9 |
128px |
--space-10 |
160px |
| Purpose | Steps | Values |
|---|---|---|
| Component padding | step 2–3 | 16–24px |
| Section gap | step 5–6 | 48–64px |
| Page margin | step 7–8 | 80–96px |
Do NOT use arbitrary pixel values for spacing. Instead, use --space-* tokens.
Minor Third (1.200), base 16px. Sizes integer-rounded. Line-heights 8px-snapped.
| Token | Size | Line-Height | Role |
|---|---|---|---|
--text-xs |
11px | 24px (--lh-sm) |
Fine print, captions |
--text-sm |
13px | 24px (--lh-sm) |
Secondary, metadata |
--text-base |
16px | 24px (--lh-sm) |
Body text |
--text-lg |
19px | 32px (--lh-lg) |
Lead paragraphs |
--text-xl |
23px | 32px (--lh-lg) |
h4 subheadings |
--text-2xl |
28px | 40px (--lh-2xl) |
h3 section headings |
--text-3xl |
33px | 40px (--lh-2xl) |
h2 major headings |
--text-4xl |
40px | 48px (--lh-3xl) |
h1 page titles |
Do NOT use computed line-height: 1.5. Instead, assign --lh-* tokens.
Apply max-width: 66ch to text containers.
| Token | Value |
|---|---|
--radius-none |
0px |
--radius-sm |
4px |
--radius-md |
8px |
--radius-lg |
12px |
--radius-xl |
16px |
--radius-2xl |
24px |
--radius-full |
9999px |
| Context | Adjustment | Radius |
|---|---|---|
| Error/danger | -50% | 2px |
| Warning | -25% | 2px |
| Success | +25% | 4px |
| Neutral/info | default | 3px |
| Avatar | circle | 50% |
| Input fields | -25% | 2px |
Do NOT apply high curvature (--radius-full) to error or warning elements. Instead, use --radius-sm or lower.
Use --radius-md (8px) for cards and containers. Use --radius-sm (4px) for inputs and badges.
Do NOT apply border-radius to accent-bordered elements (3–4px border-left or border-top callouts, blockquotes, step indicators). Radius rounds the accent stroke's endpoints into decoration that communicates nothing. Instead, use border-radius: 0 and let the accent border terminate with sharp edges. Exception: cards with a full surrounding border (all four sides) may use --radius-md even when one side carries a thicker accent override.
| Token | Thickness | Light Color | Dark Color |
|---|---|---|---|
--border-subtle |
1px | #d4d4d4 (neutral-200) | #3d3d3d (neutral-800) |
--border-default |
1px | #b7b7b7 (neutral-300) | #505050 (neutral-700) |
--border-emphasis |
1px | #808080 (neutral-500) | #808080 (neutral-500) |
--border-strong |
2px | #505050 (neutral-700) | #b7b7b7 (neutral-300) |
--border-accent |
3px | semantic color | semantic color |
Do NOT exceed 4 visible structural borders per viewport section. Instead, use spacing or surface tone for grouping.
Reserve --border-accent (3px) for semantic callouts only.
| Token | Height | H-Padding | Use |
|---|---|---|---|
--target-sm |
32px | 16px | Tertiary actions, inline buttons, tags |
--target-md |
40px | 24px | Secondary actions, form inputs |
--target-lg |
48px | 32px | Primary actions, main CTAs |
--target-xl |
56px | 48px | Hero CTAs, prominent actions |
All interactive elements must be at least 24×24 CSS px (WCAG 2.2 AA). Primary actions use --target-lg (48px) to meet WCAG AAA.
Do NOT place adjacent interactive elements closer than 8px apart.
| Relationship | Spacing |
|---|---|
| Tightly related | 8px (--space-1) |
| Within-group | 16px (--space-2) |
| Between-group | 48px (--space-5) |
| Between-section | 64–96px (--space-6 – --space-8) |
Within-group spacing must be less than half the between-group spacing.
Do NOT use equal spacing within and between groups. Instead, ensure at least a 2× step difference.
Content follows a single-column vertical flow. Block elements (figures, code blocks, admonitions) interrupt the prose column and span its full width.
| Transition | Spacing | Token |
|---|---|---|
| Paragraph → paragraph | 16px | --space-2 |
| Paragraph → block element | 32px | --space-4 |
| Block element → paragraph | 32px | --space-4 |
| Block element → block element | 24px | --space-3 |
| Section heading → first element | 16px | --space-2 |
| Last element → section heading | 64px | --space-6 |
Apply max-width: 66ch to prose containers. Block elements (figures, code blocks) may extend to the content column's full width but do NOT exceed it.
Do NOT place a figure before its first textual reference. Instead, the figure appears immediately after the paragraph that introduces it.
Use semantic <figure> and <figcaption> for all visual block elements — diagrams, screenshots, illustrations, and annotated code.
<figure>
<!-- SVG diagram, image, or code block -->
<figcaption>Figure 4.3 — Context window token allocation across three agent turns.</figcaption>
</figure>| Property | Value |
|---|---|
| Caption font | --text-sm (13px) |
| Caption color | --text-muted |
| Caption spacing | --space-1 above caption |
| Figure margin | --space-4 top and bottom |
| Numbering | Optional, section-based: Figure {section}.{n} — |
| Figure Type | Width | Alignment |
|---|---|---|
| Diagram (SVG) | 100% of content column | Centered via margin-inline: auto |
| Screenshot | Intrinsic, max 100% | Centered |
| Inline icon pair | Intrinsic | Inline with text |
Do NOT use figures without captions. Every <figure> must contain a <figcaption> that describes the content.
Do NOT use <img> directly for diagrams or illustrations. Instead, wrap in <figure> with a descriptive caption.
| Pattern | Primitive | Use When |
|---|---|---|
| Collapsible depth | <details> / <summary> |
Optional deep-dive, implementation detail, proof, or derivation |
| Parallel alternatives | <Tabs> |
Multiple equivalent approaches (languages, frameworks, OS) |
| Semantic alert | Admonition (:::type) |
Contextual warnings, tips, or prerequisites that interrupt flow |
| Question | If Yes → |
|---|---|
| Is this content required to understand the main argument? | Keep inline — do NOT hide it |
| Does the reader choose one of N equivalent paths? | <Tabs> |
| Is this a tangent that only some readers need? | <details> |
| Does this interrupt flow with a warning, tip, or prerequisite? | Admonition |
Do NOT hide critical content behind <details>. Instead, keep essential information in the primary prose flow.
Do NOT nest disclosure patterns. A <details> inside a <Tabs> panel (or vice versa) adds cognitive overhead. Instead, flatten the structure.
Content occupies three tiers; tiers 1–2 must be self-sufficient.
| Tier | Elements | Role |
|---|---|---|
| 1 — Primary | Prose paragraphs, headings, inline code | Core argument and explanation |
| 2 — Secondary | Figures, code blocks, tables | Evidence, demonstration, specification |
| 3 — Tertiary | Admonitions, <details>, footnotes |
Supplementary context, caveats, deep-dives |
Do NOT place essential information exclusively in tier 3. Instead, state the key point in tier 1 prose, then elaborate in tier 3 if needed.
Do NOT exceed 3 consecutive block elements (tier 2 or 3) without intervening prose. Instead, add a bridging sentence that connects the blocks to the argument.
| Variant | Background | Border | Text | Use |
|---|---|---|---|---|
| Primary CTA | cyan-600 (light) / cyan-400 (dark) | none | white | Main conversion action (1 per page max) |
| Primary | #2b2b2b (light) / #e8e8e8 (dark) | none | white (light) / dark (dark) | Standard primary actions |
| Outline | transparent | dark | dark | Secondary actions |
| Ghost | transparent | none | underlined dark | Tertiary / inline actions |
Chromatic color is permitted on Primary CTA only — one per page, using var(--visual-cyan) as background. This is the sole exception to achromatic buttons. All other button variants remain neutral. Do NOT introduce additional hue variants. Hover: darken fill (shift to cyan-700/cyan-300). Do NOT change hue on hover.
White text on cyan-600 (#007576) achieves 5.38:1 contrast ratio (WCAG AA). White text on cyan-400 (#00b2b2) in dark mode achieves sufficient contrast on the lighter teal fill.
| Mode | Background | Text | Border |
|---|---|---|---|
| Light | shade-50 | shade-600 | 1px solid shade-600 |
| Dark | shade-600 | white | none |
Example (cyan light): background: #d4fffe; color: #007576; border: 1px solid #007576;
Example (cyan dark): background: #007576; color: #fff;
- Border:
1px solid var(--border-default),border-radius: var(--radius-md) - Background:
var(--surface-raised)orvar(--surface-page) - Hover: shift border to neutral-400. Do NOT add color on hover. Instead, increase border contrast only.
- 3px left border in semantic color + colored label text. Body text stays neutral.
- Example:
border-left: 3px solid #1369b0;with<span style="color:#1369b0;">TIP</span> - Do NOT apply border-radius to callout containers. Instead, use
border-radius: 0. The accent border's sharp endpoints reinforce its directional intent.
| State | Treatment |
|---|---|
| Rest | neutral border |
| Hover | border lightens (neutral-700 → neutral-400) |
| Focus | darker border (contrast shift) |
| Active | darker fill or inverted contrast |
All state changes use contrast/weight shifts. Do NOT introduce hue changes on interaction states. Instead, shift lightness within the neutral palette.
- Border: neutral. Focus: darker border.
- Do NOT use colored focus rings. Instead, increase border contrast on focus.
Docusaurus admonition types map to the semantic hue palette and use the Callout Borders pattern (3px left border + colored label).
| Admonition | Hue | Token | Label Color |
|---|---|---|---|
:::tip |
Cyan (H:195°) | --visual-cyan |
var(--visual-cyan) |
:::info |
Indigo (H:250°) | --visual-indigo |
var(--visual-indigo) |
:::note |
Neutral | --visual-neutral |
var(--visual-neutral) |
:::caution |
Warning (H:70°) | --visual-warning |
var(--visual-warning) |
:::danger |
Error (H:25°) | --visual-error |
var(--visual-error) |
Body text inside admonitions stays --text-body. Only the label and left border carry the semantic color.
Do NOT apply background tints to admonitions. Instead, use --surface-raised for the container background, matching the flat construction constraint.
Curved forms (Smooth Circuit) are default. Angular forms (Terminal Geometry) reserved for high-arousal states. See ILLUSTRATION_GUIDE.md.
All actor primitives share a bounding-box grid for visual equal-standing.
| Primitive | Emoji Ref | Bounding Box | Semantic Color |
|---|---|---|---|
OperatorNode |
🧑💻 | 40×40 (primary) / 32×32 (worker) | Neutral (--visual-neutral) |
AgentNode |
🤖 | 40×40 (primary) / 32×32 (worker) | Original emoji palette (hardcoded) |
All coordinates in ActorNodes.tsx are annotated: // Computed via scripts/compute-actor-coords.js.
Hierarchy is expressed through size only, never through color.
| Role | BB Size | Use |
|---|---|---|
| Primary / orchestrator | 40×40 | Top of hierarchy, one per level |
| Worker / delegate | 32×32 | Bottom of hierarchy, multiple parallel |
Do NOT distinguish orchestrator from worker agents by color. Use 40→32 size differential only. Do NOT place primary and worker actors in the same horizontal row without applying the size step.
OperatorNode — Smooth Circuit head, Terminal Geometry legs:
- Head circle:
r = BB × 0.15. Fill--visual-bg-neutral, stroke--visual-neutral2px. - Shoulder U-path: Smooth Circuit (
stroke-linecap="round"stroke-linejoin="round"). Span = BB × 0.90, corner radius = BB × 0.10. - Legs: two lines (Terminal Geometry,
stroke-linecap="square") spreading to BB base.
AgentNode — Original Google Noto 🤖 emoji (/img/emoji/u1f916.svg):
- Delegates to
NotoEmojicomponent withcodepoint="1f916". - Gradient ID isolation is automatic; SVG
<image>renders as a separate document.
NotoEmoji — Generic wrapper for any Noto emoji:
<NotoEmoji codepoint="1f4a1" x={...} y={...} size={40} />renders/img/emoji/u1f4a1.svg.- Add new emojis via
node scripts/fetch-emoji.js <codepoint>— caches raw inscripts/.noto-cache/, writes cleaned SVG towebsite/static/img/emoji/u{cp}.svg.
Use NotoEmoji (via <image>) for static emoji nodes. The SVG renders as an opaque sub-document — individual paths inside it are not accessible to the parent SVG's CSS or SMIL, so per-path animation is impossible.
Use a hand-coded component (e.g. AuthorWaveNode) when the emoji needs animation. Inline the paths directly into the parent SVG so CSS transforms and className can target individual elements. Obtain source paths from the Noto SVG (via scripts/fetch-emoji.js); simplify fills to flat colors (no gradients) and omit any paths not visible at the target size.
ALL NotoEmoji instances render bare — no background <rect>, no filled container, no exceptions.
- The emoji IS the node. Its bounding box IS the hit area.
- Do NOT wrap
NotoEmojiin a colored<rect>,<circle>, or any other filled shape. - Do NOT add squircle or pill containers behind emoji nodes — those are structural region shapes (see Shape Selection Procedure).
Ghost placeholders provide spatial mass before a node enters, preserving layout balance (per the "every act-state must be visually complete" rule).
- Construction: dashed-stroke rect (
strokeDasharray="3 4",strokeWidth={1}), semantic background fill (--visual-bg-{hue}), semantic stroke (--visual-{hue}). - Size: matches the emoji's bounding box (or the head portion for actor primitives with body geometry).
- Lifecycle:
ghostShown→ visible while node is pending;ghostHidden→opacity: 0when node enters. - The ghost is the ONLY rect. The settled node is a bare emoji with no background shape.
Note:
PromptCardwas renamed toPromptIcon.TravelingPromptCardis its animated variant — used for delegation-flow motion along an edge path; not a separate primitive.
Every edge connecting actor nodes carries either:
- A
PromptIconartifact (36×20 centered prompt card, used in delegation flows), or - A plain Bezier arc with arrowhead (for return flow / result propagation).
Do NOT use other metaphors (looms, punch cards, pipes) for prompt artifacts.
| Family | Forms | Superellipse n | Default For |
|---|---|---|---|
| Smooth Circuit | Squircle containers, Bezier connectors, circular endpoints, round caps | n = 3–4 (squircle), n = 2 (circle) | Positive-valence: success, AI, system, knowledge, progress |
| Terminal Geometry | Diamond accents, chevron arrows, angular miters, bracket syntax | n = 1–1.5 (diamond), 45° angles | High-arousal: error, warning, code structure, human action |
Do NOT use strict-rectangle-only construction (90° routing, sharp corners on all containers). Instead, use squircle containers (n=3–4) even for box-like elements.
Do NOT apply angular containers (diamonds, sharp-cornered shapes) to success, AI, or system content. Instead, use squircle or circular containers for positive-valence elements.
Smooth Circuit for all hues except Error and Warning (Terminal Geometry).
| Content Type | Container | SVG Implementation |
|---|---|---|
| System node / module | Squircle | <rect rx="10"> (40px box) or superellipse <path> |
| Agent / AI process region | Circle or pill | <circle> or <rect rx="50%"> |
| Data / knowledge | Rounded rect | <rect rx="8"> |
| Human actor region | Circle or squircle | <circle> or <rect rx="10"> |
| Generic container | Squircle | <rect rx="10"> to <rect rx="14"> |
| Code / terminal | Diamond or sharp rect | <polygon> (4-point) or <rect rx="2"> |
| Error state | Sharp rect or diamond | <rect rx="2"> or <polygon> |
| Warning state | Triangle or diamond | <polygon> (3-point up) |
Note: These containers describe structural regions and grouping shapes — not per-node backgrounds. Emoji icon nodes always render bare (see Actor Primitives → Emoji Node Rendering).
AgentNodeis a bare 🤖 emoji, not a circle container; "Agent / AI process region" refers to a panel or zone grouping multiple agent nodes.
| Connection Type | Connector | SVG Implementation |
|---|---|---|
| Data flow (happy path) | Bezier curve | <path d="M… C…"> with stroke-linecap="round" |
| Error / rejection path | Angular polyline | <polyline> with stroke-linecap="square" |
| Bidirectional | Bezier with markers at both ends | marker-start + marker-end |
| Optional / dashed | Bezier with dash array | stroke-dasharray="6 4" |
| Token | Value | Purpose |
|---|---|---|
--stroke-fine |
1px | Grid lines, hairlines, decorative rules |
--stroke-light |
1.5px | Secondary connectors, annotations |
--stroke-default |
2px | Standard connectors, internal details |
--stroke-medium |
2.5px | Container outlines, primary shapes |
--stroke-heavy |
3px | Emphasized elements, primary flow arrows |
--stroke-accent |
4px | Semantic accent strokes (1 per diagram max) |
Do NOT exceed 4px stroke width. Instead, use fill-weight or size increase for emphasis.
Do NOT use more than 3 distinct stroke weights in a single diagram.
| Type | Marker Size | refX | Polygon Points |
|---|---|---|---|
| Standard | 6×6 | 5 | 0 0, 6 3, 0 6 |
| Large | 8×8 | 6 | 0 0, 8 4, 0 8 |
Use Standard (6px) by default. Use Large (8px) only in narrow viewBox diagrams (< 400px wide). Arrow fill inherits from stroke color.
- Grid snap — All anchor points snap to the 8px spatial grid (
--space-*tokens). Minimum shape dimension = 8px. - Preferred angles — 15°, 30°, 45°, 60°, 75°, 90°. Other angles require justification.
- Proportional corner radius — Scale
rxproportionally:rx = height × 0.25(range 8–16px). Do NOT use a fixedrxregardless of box size. - Minimum gap — 8px (
--space-1) between shapes. 16px (--space-2) between shape and label. - Label placement — Labels go directly adjacent to elements (spatial contiguity). Do NOT use a separate legend when inline labels fit.
- Coherence — Every shape serves a communicative purpose. Do NOT add decorative shapes.
Do NOT use SVG filters (<filter>, feDropShadow, feGaussianBlur). Instead, use stroke-weight hierarchy and surface tone for emphasis.
Do NOT use SVG <linearGradient> or <radialGradient> for fills. Instead, use solid --visual-bg-* tint tokens.
Do NOT use box-shadow on diagram containers. Instead, use border with --border-default or semantic color.
| Tier | Canvas / viewBox | Style | Use |
|---|---|---|---|
| UI icon | 0 0 24 24 |
Outline stroke (currentColor) |
Inline text, buttons, nav, phase indicators |
| Illustration icon | 0 0 128 128 |
Flat filled (Noto emoji style) | Bullet icons, feature cards, hero callouts |
Both tiers share --icon-* size tokens for rendered width/height. The viewBox is fixed per tier; scaling is via width/height attributes.
| Token | Value | Purpose |
|---|---|---|
--icon-sm |
16px | Inline text icons |
--icon-md |
24px | Default UI icons |
--icon-lg |
32px | Card/callout icons |
--icon-xl |
48px | Diagram node icons |
--icon-2xl |
64px | Hero/feature icons |
Do NOT create icons at arbitrary sizes. Instead, use --icon-* tokens.
| Property | Value |
|---|---|
fill |
none (outline style) |
stroke |
currentColor |
stroke-width |
2 (at 24px canvas) |
stroke-linecap |
round (Smooth Circuit) or square (Terminal Geometry) |
stroke-linejoin |
round (Smooth Circuit) or miter (Terminal Geometry) |
Illustration icons follow a Noto Color Emoji–inspired flat construction standard. All 128×128 viewBox icons must comply. This section applies to custom illustration icons — emoji representations (fetched, inlined, or recreated from Noto) are exempt per constraint #4.
- Flat fills only — solid color shapes. No tonal gradation within the same structural part.
- Layered silhouette — 3–5 overlapping filled shapes that read as a bold silhouette at 32px.
- Geometric simplicity — minimize path points; prefer arcs and clean curves over organic detail.
- Achromatic structure + chromatic accent — neutral fills for structure,
currentColorfor the one semantic layer.
Maximum 4 distinct fill values per icon:
| Slot | Fill | Neutral shade | Role |
|---|---|---|---|
| Body | hardcoded hex | neutral-600 (#666666) |
Primary structural mass |
| Detail | hardcoded hex | neutral-900 (#2b2b2b) |
Dark accents, internal features |
| Secondary | hardcoded hex | neutral-300 (#b7b7b7) |
Secondary body, bezels, minor marks |
| Accent | currentColor |
inherited from parent | Semantic tint (solid or opacity="0.15" wash) |
Slot usage: Body + Accent are required. Detail and Secondary are optional. Total ≤ 4.
- Every illustration icon MUST have ≥1 element with
fill="currentColor". - Two permitted opacity values:
1(solid accent) and0.15(tinted wash). No other opacities. - Do NOT hardcode semantic hex colors (e.g.,
#ad3735). UsecurrentColorso the parent assigns meaning via--visual-*. - Structural fills must be opaque. A path that serves as backdrop for other visible elements (e.g., a clock face behind tick marks) must use a neutral hex fill, not a
currentColorwash. Reserveopacity="0.15"washes for overlay accents that layer on top of opaque structure (e.g., a tinted face layered over a#666666body).
- Highlight fills (lighter neutral on darker to simulate reflection)
- Shadow ridge fills (darker neutral alongside structural neutral to simulate depth)
- Multiple tonal layers on the same part (e.g., neutral-100 + -300 + -500 + -600 on one object)
- SVG filters, gradients, or opacity shading on neutral fills
- neutral-50 (
#f5f5f5), neutral-100 (#e8e8e8), neutral-400 (#9b9b9b), neutral-500 (#808080) as fill colors — these exist only for 3D simulation
| Diagram Type | ViewBox Width | Typical Height |
|---|---|---|
| Inline icon pair | 100–200 | 48–64px |
| Flow diagram | 400–600 | 160–240px |
| System diagram | 500–1000 | 300–500px |
| Comparison (side-by-side) | 600–1000 | 200–400px |
| Figure caption | Same as parent figure | --text-sm line-height (24px) |
All diagram containers use width: 100% with SVG viewBox controlling aspect ratio. Do NOT set fixed pixel widths on diagram containers.
Every SVG diagram requires:
role="img"on the<svg>elementaria-labeldescribing the diagram's content and relationships- Semantic color paired with a non-color indicator (shape difference, label text, or pattern)
Do NOT rely on color alone to distinguish diagram elements. Instead, combine color + shape + label.
Generated from ANIMATION_GUIDE.md Phase 2 (agent-executed math). Brand profile: productive style, medium arousal, medium pleasure. Do NOT override computed values. Re-run ANIMATION_GUIDE.md scripts if brand PAD profile changes.
| PAD Axis | Level | Motion consequence |
|---|---|---|
| Pleasure | Medium | Productive curves; slight warmth on diagram reveals |
| Arousal | Low–Medium | Moderate duration (150–240ms); 80ms stagger |
| Dominance | Medium | Ease-out settle; no spring bounce on standard UI |
Motion style: Productive. Expressive curves reserved for actor diagram reveals only — never for UI chrome, buttons, or tables.
/* Duration */
--duration-instant: 70ms; /* toggle, checkbox — at perception floor */
--duration-fast: 110ms; /* opacity/color, badge update */
--duration-subtle: 150ms; /* node entrance, icon swap */
--duration-moderate: 240ms; /* connector draw, widget reveal */
--duration-deliberate: 400ms; /* large panel, full-section reveal */
--duration-ambient: 700ms; /* background overlay — not user-triggered */
/* Exit variants (×0.75 per NNGroup asymmetry rule) */
--duration-fast-exit: 80ms;
--duration-subtle-exit: 110ms;
--duration-moderate-exit: 180ms;
--duration-deliberate-exit: 300ms;
/* Easing — productive style */
--ease-enter: cubic-bezier(0.00, 0.00, 0.38, 0.9); /* entrance: decelerate to rest */
--ease-exit: cubic-bezier(0.20, 0.00, 1.00, 0.9); /* exit: accelerate away */
--ease-standard: cubic-bezier(0.20, 0.00, 0.38, 0.9); /* reposition within viewport */
--ease-linear: linear; /* spinners, progress bars only */
/* Stagger */
--motion-stagger-sm: 60ms; /* 5–8 items */
--motion-stagger-md: 80ms; /* 3–4 items */
--motion-stagger-lg: 100ms; /* 2 items */
/* Reveal offsets */
--motion-reveal-y-scroll: 12px; /* scroll-reveal: elements rise into viewport */
--motion-reveal-y-load: -8px; /* page-load: elements settle downward */
--motion-reveal-scale: 0.96; /* modal/dialog only — never for lists */Do NOT use the CSS ease keyword. Use --ease-* tokens exclusively.
Do NOT animate width, height, left, top, margin, or padding. Use opacity and transform only.
Five archetypes. Each has a fixed animation grammar. Do NOT mix grammars.
| Archetype | Trigger | Acts | Entrance | Idle | Budget |
|---|---|---|---|---|---|
| Actor diagram | scroll phase 0→1 | 4–6 phase-gated | fadeIn + translateY(12px) per node at --duration-subtle; connectors draw via stroke-dashoffset at --duration-moderate |
cursor-blink / status-pulse / ready-breathe per act | ≤1000ms story |
| Data viz | scroll phase 0→1 | 2–3 | Containers simultaneous; connectors draw last | flow-drift (optional) | ≤600ms |
| Interactive widget | scroll reveal (IO) | 1 | Unit fadeIn + translateY(12px) at --duration-moderate |
none — user-driven | ≤300ms |
| Data table | scroll reveal (IO) | 1 | Row stagger at --motion-stagger-sm (60ms/row) |
ping-once on badges at row entry |
≤500ms |
| Sticky narrative | chapter ID | N | Chapter transition at --duration-subtle; exit at --duration-subtle-exit |
per-chapter idle | chapter-driven |
Entrance keyframe (shared by all archetypes):
@keyframes actEnter {
from { opacity: 0; transform: translateY(var(--motion-reveal-y-scroll)); }
to { opacity: 1; transform: translateY(0); }
}
/* apply: animation: actEnter --duration-subtle --ease-enter both; */Connector draw-on:
/* set stroke-dasharray = stroke-dashoffset = el.getTotalLength() via JS on mount */
@keyframes drawPath { to { stroke-dashoffset: 0; } }
/* apply: animation: drawPath --duration-moderate --ease-enter both; */An act is a discrete visual state of a figure. Acts advance monotonically as scroll phase increases — never reverse.
phase 0→1 (from ScrollDrivenFigure context, or chapter ID from NarrativeFigure)
│
▼
useActs(actDefs, phase) → { wasReached(id), isCurrentAct(id) }
│
├── wasReached(id) → element visible, settled appearance
├── isCurrentAct(id) → apply idle class
└── !wasReached(id) → opacity: 0; pointer-events: none
Each ActDef: { id: string, threshold: number } where threshold is 0–1 (phase-driven) or a chapter ID string (narrative-driven).
Do NOT put act logic in CSS animation-delay chains. Use useActs + conditional class names. CSS modules define entrance keyframes; the hook controls when they fire.
Every act-state must be a visually complete, balanced composition. Elements that arrive in later acts must have placeholder mass (ghost geometry, neutral fill) in earlier acts to preserve spatial balance. A diagram that looks broken at any act boundary violates this rule.
All idle classes defined once in custom.css. Do NOT define idle keyframes per-component.
| Class | Motion | Loop | Semantic use |
|---|---|---|---|
.idle-cursor-blink |
opacity 1→0→1 step-end |
1000ms | Active authoring (prompt artifact during composing act) |
.idle-status-pulse |
opacity 1→0.5→1 ease-in-out |
2000ms | AI processing; connector during transit |
.idle-flow-drift |
translateX 0→2px→0 ease-in-out |
3000ms | Data moving through a channel |
.idle-ready-breathe |
scale 1→1.02→1 ease-in-out |
4000ms | Agent idle; system ready |
.ping-once |
scale 1→1.4; opacity 1→0 ease-out |
400ms, 1× | One-shot attention on act entry |
.idle-arm-rock |
rotate 0→-4°→0 ease-in-out |
5000ms | Human authoring; developer working |
.idle-palm-wave |
rotate 0→±18°→0 ease-in-out |
5000ms | Greeting; interaction acknowledgement |
.idle-gear-spin |
rotate 0→360° linear |
2500ms | Mechanical processing; LLM inference engine |
Rules:
- Max 2 idle loop classes simultaneously per figure. Staggered instances of the same class on multiple DOM elements count as 1.
cursor-blinkandstatus-pulsenever together. ping-onceis exempt (transient;animation-iteration-count: 1).- Remove idle classes via
useActswhen the act advances — do NOT let them run in settled state.
| Content | Order |
|---|---|
| Actor diagram nodes | Data-flow order (source → sink) |
| Connector drawing | After both connected nodes are settled |
| Labels / legends | Always last, after the elements they label |
| Data table rows | Top-to-bottom (reading order) |
| Parallel equal-weight grid | Simultaneous — stagger implies false hierarchy |
| Form fields | Top-to-bottom (completion order) |
ScrollDrivenFigure enforces the entire contract at the wrapper level. Diagram components do NOT check prefers-reduced-motion directly.
| Condition | Behavior |
|---|---|
prefers-reduced-motion: reduce |
ScrollDrivenFigure sets phase=1 on mount; all acts fire instantly; diagrams render in final settled state; idle loop classes removed |
| No JavaScript | Diagrams render in final settled state (SSR default) |
| No CSS scroll-timeline | IntersectionObserver fires phase=1 on first intersection; entrance animations play time-based (correct degradation) |
@media (prefers-reduced-motion: reduce) {
.idle-cursor-blink, .idle-status-pulse,
.idle-flow-drift, .idle-ready-breathe, .ping-once,
.idle-arm-rock, .idle-palm-wave, .idle-gear-spin {
animation: none !important;
}
}Do NOT use animation-duration: 0 — use 0.01ms. Below 40ms, browsers may not fire animationend reliably.
| Property | Value |
|---|---|
| Page background | #0d1117 |
| Surface/cards | #161b22 |
| Muted surface | #3d3d3d (neutral-800) |
| Body text | #cdd6d6 (neutral-200) |
| Heading text | #e2eae9 (neutral-100) |
| Semantic colors | shade-400 (not shade-600) |
| Bg tint opacity | 15% (not 10%) |
Do NOT use pure #000000 for backgrounds. Instead, use #0d1117. Do NOT use pure #ffffff for text. Instead, use neutral-100 (#e2eae9) or neutral-200 (#cdd6d6).
All --visual-* tokens shift from shade-600 to shade-400 in dark mode.
- WCAG AA contrast: shade-600 on white ≥ 4.5:1. shade-400 on #0d1117 ≥ 4.5:1.
- Redundant encoding: Every color signal must pair with a non-color indicator (icon, text label, pattern, or underline). Do NOT convey information through color alone. Instead, always add icon + text label alongside color.
- Error example: red border + icon + "Error:" text label. Success: green border + icon + "Success:" text.
- Links: Must have underline or equivalent non-color indicator.
- Colorblind-safe: Success uses teal-green (H:155°), distinguishable from error red under protanopia/deuteranopia.
</>monochrome glyph. Dark on light backgrounds, light on dark backgrounds.- Do NOT create colored logo variants. The mark is always achromatic.
- SVG with
prefers-color-schememedia query for light/dark switching .icofallback at 32x32, Apple touch icon at 180x180
- Dark background (#111111), white title text, optional 3px semantic accent line
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #f5f5f5 | #e8e8e8 | #d4d4d4 | #b7b7b7 | #9b9b9b | #808080 | #666666 | #505050 | #3d3d3d | #2b2b2b | #222222 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #fff2f0 | #ffdfdc | #ffc3bd | #ff958d | #ec7069 | #ce514d | #ad3735 | #8d2324 | #701719 | #520e10 | #410b0c |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #fff3e6 | #ffe3c3 | #fcca91 | #e6aa63 | #cd8c37 | #b17000 | #8e5900 | #704500 | #573400 | #402400 | #331b00 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #f7fac9 | #eaedb0 | #d8da8d | #bcbe5c | #a1a22b | #868600 | #6b6b00 | #535400 | #404000 | #2e2e00 | #242400 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #ddffe8 | #bef8d1 | #9fe8b8 | #72ce95 | #48b475 | #1c985a | #007a44 | #006034 | #004a27 | #00361a | #002a13 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #d4fffe | #a5faf9 | #7aeae9 | #2ad0d0 | #00b2b2 | #009393 | #007576 | #005c5c | #004747 | #003333 | #002828 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #eef6ff | #d7eaff | #b4d8ff | #7cbdff | #53a0ec | #3284d0 | #1369b0 | #005190 | #003e71 | #002c54 | #002242 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #f4f4ff | #e5e5ff | #cfcfff | #b0adff | #938eeb | #7971d0 | #6057af | #4b4290 | #393172 | #282254 | #1f1b42 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #fcf0ff | #f9ddff | #f2bffd | #da9de8 | #c07ecf | #a462b4 | #874895 | #6d3579 | #55265f | #3d1a45 | #301436 |
| 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950 |
|---|---|---|---|---|---|---|---|---|---|---|
| #fff1f6 | #ffdde9 | #ffbfd7 | #f198bb | #d7799f | #bb5c84 | #9b436a | #7e3053 | #632240 | #48172d | #391223 |