feat(designbook): technology-neutral IR + stack-adapter pipeline (WHYNOT-WP-0002 T01-T06)

Author the design language once in the canonical React designbook and project it
one-way onto each stack: React -> designbook/ -> ir/ -> adapters/<stack>/.

Phase 0 — contracts & governance (T01-T03):
- ir/SCHEMA.md + ir/schema/{component,tokens}.schema.json — neutral IR contract
  (W3C DTCG tokens; React prop -> HTML attribute mapping; non-portable props flagged).
- adapters/ADAPTER_CONTRACT.md — inputs, drift-report + parity-result shapes,
  idempotency rules, CI exit codes (0 ok / 2 usage / 3 drift / 4 parity / 5 internal).
- .claude/rules/designbook-propagation.md + DesignSystemIntroduction.md §5.1 —
  one-way directionality + drift-resolution workflow.

T04 — canonical React designbook + the missing pull tool:
- The bundled /design-sync skill only PUSHES repo->cloud; it cannot populate
  designbook/. Added scripts/designbook_pull.py + `make designbook-pull`, which drives
  the local claude binary headless (acceptEdits) so DesignSync fetch+write runs in a
  subprocess (contents never hit the orchestrator's context). Pulled 44 files;
  excludes the _whynot-design-seed/ self-copy. Corrected the docs that wrongly called
  /design-sync the pull.

T05 — IR extractor (scripts/ir-extract.mjs + `make ir`):
- ir/tokens.json (80 tokens, DTCG, var() -> {ref} alias resolution); ir/components/*.json
  (10 contracts parsed from .jsx signatures: enum/boolean/number inference, prop->attr
  map, style/callback marked non-portable); ir/exemplars/*.

T06 — Lit token adapter (adapters/lit/ + `make adapt-lit`):
- Full-gen tokens into src/styles/colors_and_type.css :root (marker-bounded, idempotent
  no-op on re-run; hand-authored type CSS preserved).

NOTE: token regen synced Lit to canonical React — fonts IBM Plex -> system stacks and 8
status tokens added. This is a VISUAL change: review and run `pnpm test:visual:update`
before merge. Remaining: T07 scaffold+drift, T08 parity, T09 runbook, T10 2nd-adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 12:36:24 +02:00
parent d149f965a3
commit 0d688ca94a
80 changed files with 6439 additions and 106 deletions

33
ir/README.md Normal file
View File

@@ -0,0 +1,33 @@
# `ir/` — technology-neutral design blueprint
This directory holds the **intermediate representation (IR)** of the whynot design
language: tokens, per-component contracts, and reference exemplars, in a form that
carries no framework assumptions.
## Decision: `ir/` is committed
`ir/` is **checked into git**, on purpose. The IR is the diffable blueprint of the
shared language — committing it means a re-extract (`make ir`) surfaces every
blueprint change as a reviewable git diff, and adapters have a stable, versioned
input. It is a build *input*, not a throwaway build *output*.
What is committed:
- `tokens.json` — all tokens, W3C DTCG format.
- `components/<Name>.json` — one contract per component.
- `exemplars/<Name>.{png,html}` — reference renders from the designbook preview.
- `schema/` + `SCHEMA.md` — the contract definitions (this is what T01 delivered).
## Direction of flow
```
Claude Design (React) ──/design-sync──▶ designbook/ ──make ir──▶ ir/ ──make adapt-lit──▶ adapters/lit/
```
One-way. The only writer of `tokens.json`, `components/`, and `exemplars/` is the
extractor (`scripts/ir-extract.mjs`, T05). **Do not hand-edit those** — change the
language in Claude Design and re-propagate. The `schema/` files and these docs are
the exception: they are authored here.
See [`SCHEMA.md`](./SCHEMA.md) for the full contract spec and a worked `Button`
exemplar.

141
ir/SCHEMA.md Normal file
View File

@@ -0,0 +1,141 @@
# whynot IR — Schema
> The **technology-neutral blueprint** for the whynot design language.
> Part of WHYNOT-WP-0002. The IR is the pivot between the canonical React
> designbook and every per-stack adapter (Lit is the reference adapter).
## Why an IR exists
Claude Design's `/design-sync` produces a **React-bound** designbook. A non-React
design system "has nothing for the design agent to build with." The IR breaks that
binding: React stays the *authoring surface*, but the IR — committed, diffable,
framework-free — is the actual contract that adapters project onto each stack.
**Directionality is one-way: React → IR → stacks.** Nothing writes back to `ir/`
except the extractor (`scripts/ir-extract.mjs`, T05). A change to the shared
language is made in Claude Design and re-propagated; the IR is never hand-edited.
## Layout
```
ir/
SCHEMA.md ← this file (narrative spec)
README.md ← the committed-blueprint decision + workflow
schema/
tokens.schema.json ← JSON Schema for tokens.json (W3C DTCG)
component.schema.json ← JSON Schema for each components/<Name>.json
tokens.json ← all design tokens, W3C DTCG format (emitted by T05)
components/<Name>.json ← one contract per component (emitted by T05)
exemplars/<Name>.{png,html} ← reference render from the designbook (emitted by T05)
```
## Tokens — `ir/tokens.json`
Adopts the **W3C Design Tokens Community Group** format: every token is an object
with `$value` and (optionally inherited) `$type`; groups nest; `$description`
carries documentation. This is a published standard rather than a bespoke shape,
so the token layer can feed Style Dictionary or any DTCG tool unchanged.
```json
{
"color": {
"$type": "color",
"ink": { "$value": "#0A0A0A", "$description": "Near-black. The only fill most of the time." },
"line": { "$value": "#E5E5E2", "$description": "Default 1px wireframe rule." }
}
}
```
> **Migration note.** The repo's current `tokens/*.json` use the older draft shape
> (`value`/`type`, no `$` prefix). The extractor (T05) normalises them to the
> `$`-prefixed DTCG shape on the way into `ir/tokens.json`. Validate with
> `schema/tokens.schema.json`.
## Component contract — `ir/components/<Name>.json`
Captures everything an adapter needs to scaffold a stub and detect drift, with no
framework assumptions. Validate with `schema/component.schema.json`. Fields:
| Field | Meaning |
|---|---|
| `name` | PascalCase canonical name (`Button`). |
| `tag` | Advisory custom-element tag (`wn-button`). |
| `group` | Designbook group (`atoms`, `chrome`, `form`). |
| `description` | Purpose, from the React `.prompt.md`. |
| `props[]` | Public inputs — see below. |
| `slots[]` | Named/default content slots. |
| `events[]` | Emitted events (e.g. `wn-dismiss`). |
| `variants[]` | Variant axes — named dimensions with discrete values. |
| `docsRef` | Path to source docs under `designbook/`. |
| `exemplarRef` | Path to the reference render under `ir/exemplars/`. |
### The prop → attribute mapping (the crux)
React props are camelCase properties; Lit/Vue/plain-HTML bind **attributes**
(kebab-case). Each prop therefore records **both** identities plus a portability
flag:
```json
{
"name": "iconEnd", // React prop, camelCase
"type": "string",
"attribute": "icon-end", // HTML attribute an attribute-driven adapter binds
"portable": true
}
```
- `attribute: false` means the prop is deliberately **not** an attribute
(property-only, or non-portable).
- `portable: false` marks props that don't map cleanly to an attribute — objects,
render props, callbacks. **Adapters MUST surface non-portable props as drift,
never silently drop them** (open risk in the workplan). Such props pair with
`type` ∈ {`object`, `function`, `node`}.
This mapping is exactly what the Lit elements already encode, e.g.
`iconEnd: { type: String, attribute: "icon-end" }` in `src/elements/atoms.js`.
## Worked exemplar — `Button`
Derived from the existing `<wn-button>` (`src/elements/atoms.js`) to show the
target shape the React extractor must produce:
```json
{
"name": "Button",
"tag": "wn-button",
"group": "atoms",
"description": "Primary action control. Renders a <button>, or an <a> when href is set.",
"props": [
{ "name": "variant", "type": "enum", "attribute": "variant", "enum": ["primary", "secondary", "ghost"], "default": "secondary" },
{ "name": "size", "type": "enum", "attribute": "size", "enum": ["sm", "md", "lg"], "default": "md" },
{ "name": "icon", "type": "string", "attribute": "icon" },
{ "name": "iconEnd", "type": "string", "attribute": "icon-end" },
{ "name": "type", "type": "string", "attribute": "type", "default": "button" },
{ "name": "disabled", "type": "boolean", "attribute": "disabled", "default": false },
{ "name": "href", "type": "string", "attribute": "href" }
],
"slots": [
{ "name": "default", "description": "Button label." }
],
"events": [],
"variants": [
{ "axis": "variant", "values": ["primary", "secondary", "ghost"], "default": "secondary" },
{ "axis": "size", "values": ["sm", "md", "lg"], "default": "md" }
],
"docsRef": "designbook/components/atoms/Button/Button.prompt.md",
"exemplarRef": "ir/exemplars/Button.html"
}
```
> The `enum` values for `variant` are illustrative of the contract shape; the
> authoritative values come from the React source at extraction time (T05).
## Validation
```bash
# once an extractor exists (T05):
node scripts/ir-validate.mjs # validates tokens.json + components/*.json against schema/
```
Until then, the schemas are usable with any draft-2020-12 validator (ajv, etc.).
The extractor (T05) MUST validate its output against these schemas before writing.

63
ir/components/Button.json Normal file
View File

@@ -0,0 +1,63 @@
{
"name": "Button",
"tag": "wn-button",
"group": "atoms",
"description": "Button — extracted from designbook ui_kits/whynot-control/Atoms.jsx.",
"props": [
{
"name": "variant",
"attribute": "variant",
"type": "enum",
"enum": [
"secondary",
"primary",
"ghost"
],
"default": "secondary"
},
{
"name": "onClick",
"type": "function",
"attribute": false,
"portable": false,
"description": "React callback prop — surface as an event on attribute-driven stacks."
},
{
"name": "style",
"type": "object",
"attribute": false,
"portable": false,
"description": "React inline style override — not portable to an attribute."
},
{
"name": "icon",
"attribute": "icon",
"type": "boolean"
}
],
"slots": [
{
"name": "default",
"description": "Default content."
}
],
"events": [
{
"name": "wn-click",
"description": "Emitted for onClick."
}
],
"variants": [
{
"axis": "variant",
"values": [
"secondary",
"primary",
"ghost"
],
"default": "secondary"
}
],
"docsRef": "designbook/ui_kits/whynot-control/Atoms.jsx",
"exemplarRef": "ir/exemplars/Button.html"
}

View File

@@ -0,0 +1,23 @@
{
"name": "Eyebrow",
"tag": "wn-eyebrow",
"group": "atoms",
"description": "Eyebrow — extracted from designbook ui_kits/whynot-control/Atoms.jsx.",
"props": [
{
"name": "style",
"type": "object",
"attribute": false,
"portable": false,
"description": "React inline style override — not portable to an attribute."
}
],
"slots": [
{
"name": "default",
"description": "Default content."
}
],
"docsRef": "designbook/ui_kits/whynot-control/Atoms.jsx",
"exemplarRef": "ir/exemplars/Eyebrow.html"
}

27
ir/components/Icon.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "Icon",
"tag": "wn-icon",
"group": "atoms",
"description": "Icon — extracted from designbook ui_kits/whynot-control/Atoms.jsx.",
"props": [
{
"name": "name",
"attribute": "name",
"type": "string"
},
{
"name": "size",
"attribute": "size",
"type": "number",
"default": 16
},
{
"name": "style",
"type": "object",
"attribute": false,
"portable": false,
"description": "React inline style override — not portable to an attribute."
}
],
"docsRef": "designbook/ui_kits/whynot-control/Atoms.jsx"
}

View File

@@ -0,0 +1,30 @@
{
"name": "PageHeader",
"tag": "wn-page-header",
"group": "chrome",
"description": "PageHeader — extracted from designbook ui_kits/whynot-control/Chrome.jsx.",
"props": [
{
"name": "eyebrow",
"attribute": "eyebrow",
"type": "boolean"
},
{
"name": "title",
"attribute": "title",
"type": "string"
},
{
"name": "lede",
"attribute": "lede",
"type": "boolean"
},
{
"name": "actions",
"attribute": "actions",
"type": "boolean"
}
],
"docsRef": "designbook/ui_kits/whynot-control/Chrome.jsx",
"exemplarRef": "ir/exemplars/PageHeader.html"
}

View File

@@ -0,0 +1,16 @@
{
"name": "PipelineStrip",
"tag": "wn-pipeline-strip",
"group": "chrome",
"description": "PipelineStrip — extracted from designbook ui_kits/whynot-control/Chrome.jsx.",
"props": [
{
"name": "activeIdx",
"attribute": "active-idx",
"type": "number",
"default": 3
}
],
"docsRef": "designbook/ui_kits/whynot-control/Chrome.jsx",
"exemplarRef": "ir/exemplars/PipelineStrip.html"
}

View File

@@ -0,0 +1,39 @@
{
"name": "Sidebar",
"tag": "wn-sidebar",
"group": "chrome",
"description": "Sidebar — extracted from designbook ui_kits/whynot-control/Chrome.jsx.",
"props": [
{
"name": "current",
"attribute": "current",
"type": "enum",
"enum": [
"doc:"
]
},
{
"name": "onNav",
"type": "function",
"attribute": false,
"portable": false,
"description": "React callback prop — surface as an event on attribute-driven stacks."
}
],
"events": [
{
"name": "wn-nav",
"description": "Emitted for onNav."
}
],
"variants": [
{
"axis": "current",
"values": [
"doc:"
]
}
],
"docsRef": "designbook/ui_kits/whynot-control/Chrome.jsx",
"exemplarRef": "ir/exemplars/Sidebar.html"
}

View File

@@ -0,0 +1,28 @@
{
"name": "StageDot",
"tag": "wn-stage-dot",
"group": "atoms",
"description": "StageDot — extracted from designbook ui_kits/whynot-control/Atoms.jsx.",
"props": [
{
"name": "level",
"attribute": "level",
"type": "string",
"default": "S2"
},
{
"name": "label",
"attribute": "label",
"type": "string"
},
{
"name": "style",
"type": "object",
"attribute": false,
"portable": false,
"description": "React inline style override — not portable to an attribute."
}
],
"docsRef": "designbook/ui_kits/whynot-control/Atoms.jsx",
"exemplarRef": "ir/exemplars/StageDot.html"
}

22
ir/components/Stamp.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "Stamp",
"tag": "wn-stamp",
"group": "atoms",
"description": "Stamp — extracted from designbook ui_kits/whynot-control/Atoms.jsx.",
"props": [
{
"name": "style",
"type": "object",
"attribute": false,
"portable": false,
"description": "React inline style override — not portable to an attribute."
}
],
"slots": [
{
"name": "default",
"description": "Default content."
}
],
"docsRef": "designbook/ui_kits/whynot-control/Atoms.jsx"
}

33
ir/components/Tag.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "Tag",
"tag": "wn-tag",
"group": "atoms",
"description": "Tag — extracted from designbook ui_kits/whynot-control/Atoms.jsx.",
"props": [
{
"name": "active",
"attribute": "active",
"type": "boolean"
},
{
"name": "draft",
"attribute": "draft",
"type": "boolean"
},
{
"name": "style",
"type": "object",
"attribute": false,
"portable": false,
"description": "React inline style override — not portable to an attribute."
}
],
"slots": [
{
"name": "default",
"description": "Default content."
}
],
"docsRef": "designbook/ui_kits/whynot-control/Atoms.jsx",
"exemplarRef": "ir/exemplars/Tag.html"
}

23
ir/components/TopNav.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "TopNav",
"tag": "wn-top-nav",
"group": "chrome",
"description": "TopNav — extracted from designbook ui_kits/whynot-control/Chrome.jsx.",
"props": [
{
"name": "onNew",
"type": "function",
"attribute": false,
"portable": false,
"description": "React callback prop — surface as an event on attribute-driven stacks."
}
],
"events": [
{
"name": "wn-new",
"description": "Emitted for onNew."
}
],
"docsRef": "designbook/ui_kits/whynot-control/Chrome.jsx",
"exemplarRef": "ir/exemplars/TopNav.html"
}

40
ir/exemplars/Button.html Normal file
View File

@@ -0,0 +1,40 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Buttons" subtitle="Primary · secondary · ghost" viewport="700x240" -->
<html><head>
<meta charset="utf-8"><title>Buttons</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { padding: 28px 32px; background: var(--paper); }
.row-label { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); margin-bottom: 16px; }
.grid { display: grid; grid-template-columns: repeat(4, max-content); gap: 12px 14px; align-items: center; }
.col-h { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); }
.row-h { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); }
.btn { font: 500 13px var(--ff-sans); letter-spacing: -0.005em; padding: 9px 16px; border-radius: var(--r-2); border: 1px solid transparent; cursor: pointer; transition: background 120ms ease, border-color 120ms ease, color 120ms ease; }
.btn.primary { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn.primary.hover { background: var(--ink-2); border-color: var(--ink-2); }
.btn.primary.disabled { background: var(--ink-5); border-color: var(--ink-5); color: var(--paper); cursor: not-allowed; }
.btn.secondary { background: var(--paper); color: var(--ink); border-color: var(--border); }
.btn.secondary.hover { border-color: var(--ink); }
.btn.secondary.disabled { color: var(--ink-5); border-color: var(--border); cursor: not-allowed; }
.btn.ghost { background: transparent; color: var(--ink); border-color: transparent; padding-left: 8px; padding-right: 8px; }
.btn.ghost.hover { background: var(--paper-3); }
.btn.ghost.disabled { color: var(--ink-5); cursor: not-allowed; }
</style></head>
<body>
<div class="row-label">Buttons — primary · secondary · ghost</div>
<div class="grid">
<div></div><div class="col-h">Default</div><div class="col-h">Hover</div><div class="col-h">Disabled</div>
<div class="row-h">Primary</div>
<button class="btn primary">Promote prototype</button>
<button class="btn primary hover">Promote prototype</button>
<button class="btn primary disabled">Promote prototype</button>
<div class="row-h">Secondary</div>
<button class="btn secondary">Park</button>
<button class="btn secondary hover">Park</button>
<button class="btn secondary disabled">Park</button>
<div class="row-h">Ghost</div>
<button class="btn ghost">View signal</button>
<button class="btn ghost hover">View signal</button>
<button class="btn ghost disabled">View signal</button>
</div>
</body></html>

44
ir/exemplars/Eyebrow.html Normal file
View File

@@ -0,0 +1,44 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Labels & Tags" subtitle="Stage tags · signal dots" viewport="700x200" -->
<html><head>
<meta charset="utf-8"><title>Labels & Tags</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { padding: 24px 32px; background: var(--paper); }
.row-label { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); margin-bottom: 14px; }
.stack { display: flex; flex-direction: column; gap: 18px; }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.tag { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase; padding: 5px 10px; border-radius: var(--r-pill); border: 1px solid var(--border); color: var(--fg-2); background: var(--paper); }
.tag.active { color: var(--paper); background: var(--ink); border-color: var(--ink); }
.tag.draft { background: var(--hi); color: var(--hi-ink); border-color: transparent; }
.stage { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase; padding: 5px 10px; color: var(--fg-2); display: inline-flex; align-items: center; gap: 6px; }
.stage .dot { width: 8px; height: 8px; border-radius: 999px; background: var(--ink); }
.stage.s0 .dot { background: #B5B5B3 } .stage.s1 .dot { background: #8A8A8A }
.stage.s2 .dot { background: #5C5C5C } .stage.s3 .dot { background: #0A0A0A }
.stage.s4 .dot { background: #FFD400 }
</style></head>
<body>
<div class="stack">
<div>
<div class="row-label">Tags — default · active · draft</div>
<div class="row">
<span class="tag">Raw Idea</span>
<span class="tag">Prototype Candidate</span>
<span class="tag active">Experiment</span>
<span class="tag">Promotion Candidate</span>
<span class="tag">Parked</span>
<span class="tag draft">Draft</span>
</div>
</div>
<div>
<div class="row-label">Signal dots — inline indicator</div>
<div class="row">
<span class="stage s0"><span class="dot"></span>S0 · No signal</span>
<span class="stage s1"><span class="dot"></span>S1 · Weak</span>
<span class="stage s2"><span class="dot"></span>S2 · Medium</span>
<span class="stage s3"><span class="dot"></span>S3 · Strong</span>
<span class="stage s4"><span class="dot"></span>S4 · Commercial</span>
</div>
</div>
</div>
</body></html>

View File

@@ -0,0 +1,37 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Top Navigation" subtitle="56px · 1px hairline · ⌘K search" viewport="900x160" -->
<html><head>
<meta charset="utf-8"><title>Top Navigation</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { margin: 0; background: var(--paper-2); font-family: var(--ff-sans); min-height: 200px; }
.nav { height: 56px; background: rgba(255,255,255,0.92); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 32px; padding: 0 24px; }
.brand { display: flex; align-items: center; gap: 10px; font: 500 14px var(--ff-sans); }
.brand img { width: 22px; height: 22px; }
.brand .org { font-family: var(--ff-mono); font-size: 12px; color: var(--fg-3); letter-spacing: 0.04em; }
.links { display: flex; gap: 22px; }
.links a { font: 500 13px var(--ff-sans); color: var(--fg-2); text-decoration: none; padding: 6px 0; border-bottom: 1px solid transparent; }
.links a.active { color: var(--fg-1); border-bottom-color: var(--ink); }
.right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.right .search { font: 400 12px var(--ff-mono); color: var(--fg-3); border: 1px solid var(--border); padding: 6px 10px; border-radius: var(--r-1); display: flex; align-items: center; gap: 8px; min-width: 200px; }
.right .kbd { margin-left: auto; padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font-size: 10px; }
.right .btn { font: 500 12px var(--ff-sans); padding: 7px 12px; border-radius: var(--r-2); border: 1px solid var(--ink); background: var(--ink); color: var(--paper); cursor: pointer; }
.body-preview { padding: 32px 24px; color: var(--fg-3); font: 400 13px var(--ff-mono); }
</style></head>
<body>
<nav class="nav">
<div class="brand"><img src="../assets/whynot-logo.png" alt=""><span>whynot</span><span class="org">/ control</span></div>
<div class="links">
<a class="active" href="#">Inbox</a>
<a href="#">Prototypes</a>
<a href="#">Signals</a>
<a href="#">Betas</a>
<a href="#">Decisions</a>
</div>
<div class="right">
<div class="search"><span>Search…</span><span class="kbd">⌘ K</span></div>
<button class="btn">+ New idea</button>
</div>
</nav>
<div class="body-preview">// 56px height · 1px hairline · rgba(255,255,255,0.92) when scrolled</div>
</body></html>

View File

@@ -0,0 +1,30 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Pipeline" subtitle="Lifecycle stage tracker" viewport="700x180" -->
<html><head>
<meta charset="utf-8"><title>Pipeline / Lifecycle</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { padding: 28px 32px; background: var(--paper); }
.row-label { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); margin-bottom: 22px; }
.pipeline { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0; position: relative; }
.stage { padding: 10px 12px 14px; border-top: 2px solid var(--border); display: flex; flex-direction: column; gap: 4px; position: relative; }
.stage.done { border-top-color: var(--ink); }
.stage.active { border-top-color: var(--hi-2); }
.stage .num { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-3); }
.stage.done .num, .stage.active .num { color: var(--fg-1); }
.stage .name { font: 500 14px/1.25 var(--ff-sans); color: var(--fg-1); }
.stage.pending .name { color: var(--fg-3); }
.stage .meta { font: 400 11px/1.35 var(--ff-mono); color: var(--fg-3); }
.arrow { position: absolute; top: -8px; right: -7px; font: 400 14px var(--ff-mono); color: var(--ink-5); }
.stage.active .arrow, .stage.done .arrow { color: var(--ink); }
</style></head>
<body>
<div class="row-label">Pipeline — Raw → Candidate → Experiment → Signal → Decision</div>
<div class="pipeline">
<div class="stage done"><span class="num">Stage 0</span><span class="name">Raw idea</span><span class="meta">inbox/</span></div>
<div class="stage done"><span class="num">Stage 1</span><span class="name">Triage</span><span class="meta">2026-02-12</span><span class="arrow"></span></div>
<div class="stage done"><span class="num">Stage 2</span><span class="name">Prototype card</span><span class="meta">prototypes/</span><span class="arrow"></span></div>
<div class="stage active"><span class="num">Stage 3</span><span class="name">Experiment</span><span class="meta">ends 2026-04-01</span><span class="arrow"></span></div>
<div class="stage pending"><span class="num">Stage 4</span><span class="name">Signal review</span><span class="meta">— pending</span><span class="arrow"></span></div>
</div>
</body></html>

85
ir/exemplars/Sidebar.html Normal file
View File

@@ -0,0 +1,85 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Left Navigation" subtitle="Grouped sidebar · active state · minimal variant" viewport="700x420" -->
<html><head>
<meta charset="utf-8"><title>Left Navigation</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { margin: 0; padding: 20px; background: var(--paper); display: flex; gap: 28px; align-items: stretch; }
.frame { display: flex; flex-direction: column; gap: 8px; }
.frame > .cap { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); padding-left: 12px; }
.leftnav {
width: 220px; box-sizing: border-box;
display: flex; flex-direction: column; gap: 28px;
padding: 24px 8px 24px 12px;
border-right: 1px solid var(--line-soft);
min-height: 360px;
}
.brand { display: flex; align-items: center; gap: 8px; padding: 0 10px; }
.brand img { width: 20px; height: 20px; }
.brand .nm { font: 500 14px var(--ff-sans); color: var(--fg-1); }
.brand .slug { font: 400 12px var(--ff-mono); color: var(--fg-3); }
.body { display: flex; flex-direction: column; gap: 28px; flex: 1; }
.section { display: flex; flex-direction: column; gap: 8px; }
.section .lbl { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); padding-left: 12px; opacity: 0.7; }
.items { display: flex; flex-direction: column; gap: 1px; }
.item {
display: flex; align-items: center; gap: 10px;
padding: 6px 10px; border-left: 2px solid transparent;
color: var(--fg-3); font: 400 13px var(--ff-sans); cursor: pointer;
text-decoration: none;
}
.item .ic { width: 16px; height: 16px; stroke: currentColor; stroke-width: 1.5; fill: none; flex: none; }
.item .t { flex: 1; }
.item .n { margin-left: auto; font: 400 11px var(--ff-mono); color: var(--ink-5); }
.item.active { color: var(--fg-1); font-weight: 500; border-left-color: var(--ink); }
.item.active .n { color: var(--fg-3); }
.item.doc { font: 400 12px var(--ff-mono); }
.footer { margin-top: auto; display: flex; align-items: center; gap: 8px; padding: 0 12px; font: 400 11px var(--ff-mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--fg-3); }
.footer .dot { width: 5px; height: 5px; border-radius: 999px; background: var(--ink-4); }
</style></head>
<body>
<div class="frame">
<span class="cap">Default · grouped, with active state</span>
<nav class="leftnav">
<div class="brand"><img src="../assets/whynot-logo.png" alt=""><span class="nm">whynot</span><span class="slug">/ control</span></div>
<div class="body">
<div class="section">
<span class="lbl">Work</span>
<div class="items">
<a class="item"><svg class="ic" viewBox="0 0 24 24"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg><span class="t">Inbox</span><span class="n">7</span></a>
<a class="item active"><svg class="ic" viewBox="0 0 24 24"><path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"/><path d="M6.453 15h11.094"/><path d="M8.5 2h7"/></svg><span class="t">Prototypes</span><span class="n">4</span></a>
<a class="item"><svg class="ic" viewBox="0 0 24 24"><path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.5.5 0 0 1-.96 0L9.24 2.18a.5.5 0 0 0-.96 0l-2.35 8.36A2 2 0 0 1 4 12H2"/></svg><span class="t">Signals</span><span class="n">12</span></a>
<a class="item"><svg class="ic" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/><path d="M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"/></svg><span class="t">Betas</span><span class="n">1</span></a>
<a class="item"><svg class="ic" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg><span class="t">Decisions</span><span class="n">3</span></a>
</div>
</div>
<div class="section">
<span class="lbl">Control docs</span>
<div class="items">
<a class="item doc"><span class="t">INTENT.md</span></a>
<a class="item doc"><span class="t">SCOPE.md</span></a>
<a class="item doc"><span class="t">OPERATING_MODEL.md</span></a>
</div>
</div>
</div>
<div class="footer"><span class="dot"></span><span>A1 · Incubating</span></div>
</nav>
</div>
<div class="frame">
<span class="cap">Minimal · no brand, no icons</span>
<nav class="leftnav" style="min-height: 360px;">
<div class="body">
<div class="section">
<span class="lbl">Navigate</span>
<div class="items">
<a class="item active"><span class="t">Overview</span></a>
<a class="item"><span class="t">Prototypes</span><span class="n">4</span></a>
<a class="item"><span class="t">Signals</span><span class="n">12</span></a>
<a class="item"><span class="t">Settings</span></a>
</div>
</div>
</div>
</nav>
</div>
</body></html>

View File

@@ -0,0 +1,44 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Labels & Tags" subtitle="Stage tags · signal dots" viewport="700x200" -->
<html><head>
<meta charset="utf-8"><title>Labels & Tags</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { padding: 24px 32px; background: var(--paper); }
.row-label { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); margin-bottom: 14px; }
.stack { display: flex; flex-direction: column; gap: 18px; }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.tag { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase; padding: 5px 10px; border-radius: var(--r-pill); border: 1px solid var(--border); color: var(--fg-2); background: var(--paper); }
.tag.active { color: var(--paper); background: var(--ink); border-color: var(--ink); }
.tag.draft { background: var(--hi); color: var(--hi-ink); border-color: transparent; }
.stage { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase; padding: 5px 10px; color: var(--fg-2); display: inline-flex; align-items: center; gap: 6px; }
.stage .dot { width: 8px; height: 8px; border-radius: 999px; background: var(--ink); }
.stage.s0 .dot { background: #B5B5B3 } .stage.s1 .dot { background: #8A8A8A }
.stage.s2 .dot { background: #5C5C5C } .stage.s3 .dot { background: #0A0A0A }
.stage.s4 .dot { background: #FFD400 }
</style></head>
<body>
<div class="stack">
<div>
<div class="row-label">Tags — default · active · draft</div>
<div class="row">
<span class="tag">Raw Idea</span>
<span class="tag">Prototype Candidate</span>
<span class="tag active">Experiment</span>
<span class="tag">Promotion Candidate</span>
<span class="tag">Parked</span>
<span class="tag draft">Draft</span>
</div>
</div>
<div>
<div class="row-label">Signal dots — inline indicator</div>
<div class="row">
<span class="stage s0"><span class="dot"></span>S0 · No signal</span>
<span class="stage s1"><span class="dot"></span>S1 · Weak</span>
<span class="stage s2"><span class="dot"></span>S2 · Medium</span>
<span class="stage s3"><span class="dot"></span>S3 · Strong</span>
<span class="stage s4"><span class="dot"></span>S4 · Commercial</span>
</div>
</div>
</div>
</body></html>

44
ir/exemplars/Tag.html Normal file
View File

@@ -0,0 +1,44 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Labels & Tags" subtitle="Stage tags · signal dots" viewport="700x200" -->
<html><head>
<meta charset="utf-8"><title>Labels & Tags</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { padding: 24px 32px; background: var(--paper); }
.row-label { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); margin-bottom: 14px; }
.stack { display: flex; flex-direction: column; gap: 18px; }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.tag { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase; padding: 5px 10px; border-radius: var(--r-pill); border: 1px solid var(--border); color: var(--fg-2); background: var(--paper); }
.tag.active { color: var(--paper); background: var(--ink); border-color: var(--ink); }
.tag.draft { background: var(--hi); color: var(--hi-ink); border-color: transparent; }
.stage { font: 500 10px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase; padding: 5px 10px; color: var(--fg-2); display: inline-flex; align-items: center; gap: 6px; }
.stage .dot { width: 8px; height: 8px; border-radius: 999px; background: var(--ink); }
.stage.s0 .dot { background: #B5B5B3 } .stage.s1 .dot { background: #8A8A8A }
.stage.s2 .dot { background: #5C5C5C } .stage.s3 .dot { background: #0A0A0A }
.stage.s4 .dot { background: #FFD400 }
</style></head>
<body>
<div class="stack">
<div>
<div class="row-label">Tags — default · active · draft</div>
<div class="row">
<span class="tag">Raw Idea</span>
<span class="tag">Prototype Candidate</span>
<span class="tag active">Experiment</span>
<span class="tag">Promotion Candidate</span>
<span class="tag">Parked</span>
<span class="tag draft">Draft</span>
</div>
</div>
<div>
<div class="row-label">Signal dots — inline indicator</div>
<div class="row">
<span class="stage s0"><span class="dot"></span>S0 · No signal</span>
<span class="stage s1"><span class="dot"></span>S1 · Weak</span>
<span class="stage s2"><span class="dot"></span>S2 · Medium</span>
<span class="stage s3"><span class="dot"></span>S3 · Strong</span>
<span class="stage s4"><span class="dot"></span>S4 · Commercial</span>
</div>
</div>
</div>
</body></html>

37
ir/exemplars/TopNav.html Normal file
View File

@@ -0,0 +1,37 @@
<!doctype html>
<!-- @dsCard group="Components" name="Components · Top Navigation" subtitle="56px · 1px hairline · ⌘K search" viewport="900x160" -->
<html><head>
<meta charset="utf-8"><title>Top Navigation</title>
<link rel="stylesheet" href="../colors_and_type.css">
<style>
body { margin: 0; background: var(--paper-2); font-family: var(--ff-sans); min-height: 200px; }
.nav { height: 56px; background: rgba(255,255,255,0.92); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 32px; padding: 0 24px; }
.brand { display: flex; align-items: center; gap: 10px; font: 500 14px var(--ff-sans); }
.brand img { width: 22px; height: 22px; }
.brand .org { font-family: var(--ff-mono); font-size: 12px; color: var(--fg-3); letter-spacing: 0.04em; }
.links { display: flex; gap: 22px; }
.links a { font: 500 13px var(--ff-sans); color: var(--fg-2); text-decoration: none; padding: 6px 0; border-bottom: 1px solid transparent; }
.links a.active { color: var(--fg-1); border-bottom-color: var(--ink); }
.right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.right .search { font: 400 12px var(--ff-mono); color: var(--fg-3); border: 1px solid var(--border); padding: 6px 10px; border-radius: var(--r-1); display: flex; align-items: center; gap: 8px; min-width: 200px; }
.right .kbd { margin-left: auto; padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font-size: 10px; }
.right .btn { font: 500 12px var(--ff-sans); padding: 7px 12px; border-radius: var(--r-2); border: 1px solid var(--ink); background: var(--ink); color: var(--paper); cursor: pointer; }
.body-preview { padding: 32px 24px; color: var(--fg-3); font: 400 13px var(--ff-mono); }
</style></head>
<body>
<nav class="nav">
<div class="brand"><img src="../assets/whynot-logo.png" alt=""><span>whynot</span><span class="org">/ control</span></div>
<div class="links">
<a class="active" href="#">Inbox</a>
<a href="#">Prototypes</a>
<a href="#">Signals</a>
<a href="#">Betas</a>
<a href="#">Decisions</a>
</div>
<div class="right">
<div class="search"><span>Search…</span><span class="kbd">⌘ K</span></div>
<button class="btn">+ New idea</button>
</div>
</nav>
<div class="body-preview">// 56px height · 1px hairline · rgba(255,255,255,0.92) when scrolled</div>
</body></html>

View File

@@ -0,0 +1,159 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://whynot.design/ir/schema/component.schema.json",
"title": "whynot IR — Component Contract",
"description": "Technology-neutral contract for one component in the whynot designbook. Extracted one-way from the canonical React designbook (WHYNOT-WP-0002). Adapters consume this to scaffold stubs and detect drift; they never write back to it.",
"type": "object",
"additionalProperties": false,
"required": ["name", "group", "description", "props"],
"properties": {
"name": {
"type": "string",
"description": "Canonical component name in PascalCase (e.g. \"Button\"). The Lit adapter maps this to the <wn-button> tag.",
"pattern": "^[A-Z][A-Za-z0-9]*$"
},
"tag": {
"type": "string",
"description": "Suggested custom-element tag for web-component adapters (e.g. \"wn-button\"). Advisory; an adapter owns its own naming.",
"pattern": "^[a-z][a-z0-9-]*$"
},
"group": {
"type": "string",
"description": "Grouping from the designbook manifest (e.g. \"atoms\", \"chrome\", \"form\"). Mirrors src/elements/<group>.js in the Lit adapter."
},
"description": {
"type": "string",
"description": "One- or two-sentence purpose, sourced from the React component's .prompt.md docs."
},
"props": {
"type": "array",
"description": "Public inputs. Each prop carries its React identity AND its projection onto an HTML attribute, so attribute-driven stacks (Lit, Vue, plain HTML) are first-class.",
"items": { "$ref": "#/$defs/prop" }
},
"slots": {
"type": "array",
"description": "Named/default content slots.",
"items": { "$ref": "#/$defs/slot" }
},
"events": {
"type": "array",
"description": "Events the component emits.",
"items": { "$ref": "#/$defs/event" }
},
"variants": {
"type": "array",
"description": "Variant axes — each axis is a named dimension with discrete values (e.g. axis \"variant\" = [primary, secondary]). Usually derived from an enum prop.",
"items": { "$ref": "#/$defs/variantAxis" }
},
"docsRef": {
"type": "string",
"description": "Path (relative to repo root) to the source docs in designbook/, e.g. designbook/components/atoms/Button/Button.prompt.md."
},
"exemplarRef": {
"type": "string",
"description": "Path (relative to repo root) to the reference render under ir/exemplars/, e.g. ir/exemplars/Button.html. Parity (T08) diffs the adapter's render against this."
}
},
"$defs": {
"prop": {
"type": "object",
"additionalProperties": false,
"required": ["name", "type", "attribute"],
"properties": {
"name": {
"type": "string",
"description": "React prop name, camelCase (e.g. \"iconEnd\")."
},
"type": {
"type": "string",
"description": "Neutral type. \"enum\" pairs with `enum`. Non-attribute-portable shapes use \"object\", \"function\", or \"node\" and MUST set portable:false.",
"enum": ["string", "number", "boolean", "enum", "object", "function", "node"]
},
"attribute": {
"description": "HTML attribute name an attribute-driven adapter binds this prop to (kebab-case), e.g. \"icon-end\". `false` means the prop is intentionally not exposed as an attribute (property-only or non-portable).",
"oneOf": [
{ "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
{ "type": "boolean", "const": false }
]
},
"enum": {
"type": "array",
"description": "Allowed values when type is \"enum\".",
"items": { "type": "string" }
},
"default": {
"description": "Default value as authored in the React source. Type matches `type`."
},
"required": {
"type": "boolean",
"default": false,
"description": "Whether the consumer must supply this prop."
},
"portable": {
"type": "boolean",
"default": true,
"description": "False marks props that do not map cleanly to an HTML attribute (objects, render props, callbacks). Adapters MUST surface non-portable props as drift, never silently drop them (see open risks)."
},
"description": {
"type": "string"
}
},
"allOf": [
{
"if": { "properties": { "type": { "const": "enum" } } },
"then": { "required": ["enum"] }
}
]
},
"slot": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Slot name; use \"default\" for the unnamed default slot."
},
"description": { "type": "string" },
"required": { "type": "boolean", "default": false }
}
},
"event": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Emitted event name as dispatched by the component, e.g. \"wn-dismiss\"."
},
"description": { "type": "string" },
"detail": {
"type": "string",
"description": "Free-text description of the event's detail payload shape."
}
}
},
"variantAxis": {
"type": "object",
"additionalProperties": false,
"required": ["axis", "values"],
"properties": {
"axis": {
"type": "string",
"description": "Name of the variant dimension, usually the driving prop name (e.g. \"variant\", \"size\")."
},
"values": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"description": "Discrete values along this axis."
},
"default": {
"type": "string",
"description": "Default value for the axis."
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://whynot.design/ir/schema/tokens.schema.json",
"title": "whynot IR — Design Tokens (W3C DTCG)",
"description": "ir/tokens.json adopts the W3C Design Tokens Community Group format ($value/$type) so the token layer is a standard, not a bespoke shape. NOTE: the repo's existing tokens/*.json use the older draft shape (value/type, no $); the IR extractor (T05) normalises to $-prefixed DTCG.",
"type": "object",
"$ref": "#/$defs/group",
"$defs": {
"token": {
"type": "object",
"required": ["$value"],
"properties": {
"$value": {
"description": "The token value. For type=color this is a hex/colour string; for dimension a CSS length; aliases use {group.token} reference syntax."
},
"$type": {
"type": "string",
"description": "DTCG type. May be inherited from an ancestor group.",
"enum": [
"color",
"dimension",
"fontFamily",
"fontWeight",
"duration",
"cubicBezier",
"number",
"string",
"shadow",
"border",
"typography",
"transition",
"gradient"
]
},
"$description": {
"type": "string",
"description": "Human-readable note. Carries over the `comment` field from the legacy token files."
}
},
"$comment": "A token is any object carrying $value. Properties beyond the $-prefixed ones are disallowed at token level via the group dispatch below."
},
"group": {
"type": "object",
"description": "A DTCG group: a map of names to sub-groups or tokens. $-prefixed keys are group metadata; every other key is a child node.",
"properties": {
"$type": { "type": "string" },
"$description": { "type": "string" }
},
"patternProperties": {
"^[^$].*$": {
"oneOf": [
{ "$ref": "#/$defs/token" },
{ "$ref": "#/$defs/group" }
]
}
},
"additionalProperties": false
}
}
}

266
ir/tokens.json Normal file
View File

@@ -0,0 +1,266 @@
{
"color": {
"$type": "color",
"ink": {
"$value": "#0A0A0A"
},
"ink-2": {
"$value": "#1F1F1F"
},
"ink-3": {
"$value": "#5C5C5C"
},
"ink-4": {
"$value": "#8A8A8A"
},
"ink-5": {
"$value": "#B5B5B3"
},
"line": {
"$value": "#E5E5E2"
},
"line-strong": {
"$value": "#C9C9C5"
},
"line-soft": {
"$value": "#F0F0EC"
},
"paper": {
"$value": "#FFFFFF"
},
"paper-2": {
"$value": "#FAFAF7"
},
"paper-3": {
"$value": "#F4F4EF"
},
"fg-1": {
"$value": "{color.ink}"
},
"fg-2": {
"$value": "{color.ink-3}"
},
"fg-3": {
"$value": "{color.ink-4}"
},
"fg-mute": {
"$value": "{color.ink-5}"
},
"fg-on-dark": {
"$value": "#FAFAF7"
},
"bg-1": {
"$value": "{color.paper}"
},
"bg-2": {
"$value": "{color.paper-2}"
},
"bg-3": {
"$value": "{color.paper-3}"
},
"bg-invert": {
"$value": "{color.ink}"
},
"border": {
"$value": "{color.line}"
},
"border-strong": {
"$value": "{color.line-strong}"
},
"border-soft": {
"$value": "{color.line-soft}"
},
"hi": {
"$value": "#FFE14A"
},
"hi-2": {
"$value": "#FFD400"
},
"hi-ink": {
"$value": "#1A1500"
},
"status-raw": {
"$value": "#B5B5B3"
},
"status-weak": {
"$value": "#8A8A8A"
},
"status-medium": {
"$value": "#5C5C5C"
},
"status-strong": {
"$value": "#0A0A0A"
},
"status-commercial": {
"$value": "#FFD400"
},
"status-error": {
"$value": "#B33A2E"
},
"status-error-bg": {
"$value": "#FCF3F1"
},
"status-warn": {
"$value": "#C28000"
},
"status-warn-bg": {
"$value": "#FFFCEB"
},
"status-success": {
"$value": "#2F6B3A"
},
"status-success-bg": {
"$value": "#F2F7F2"
},
"status-info": {
"$value": "#2E5C8A"
},
"status-info-bg": {
"$value": "#F2F5FA"
}
},
"fontFamily": {
"$type": "fontFamily",
"ff-sans": {
"$value": "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif"
},
"ff-mono": {
"$value": "ui-monospace, \"SF Mono\", Menlo, Consolas, monospace"
},
"ff-serif": {
"$value": "ui-serif, Georgia, \"Times New Roman\", serif"
}
},
"fontSize": {
"$type": "dimension",
"fs-xs": {
"$value": "11px"
},
"fs-sm": {
"$value": "13px"
},
"fs-base": {
"$value": "15px"
},
"fs-md": {
"$value": "17px"
},
"fs-lg": {
"$value": "20px"
},
"fs-xl": {
"$value": "24px"
},
"fs-2xl": {
"$value": "32px"
},
"fs-3xl": {
"$value": "44px"
},
"fs-4xl": {
"$value": "64px"
},
"fs-5xl": {
"$value": "96px"
}
},
"lineHeight": {
"$type": "number",
"lh-tight": {
"$value": "1.05"
},
"lh-snug": {
"$value": "1.25"
},
"lh-base": {
"$value": "1.5"
},
"lh-loose": {
"$value": "1.7"
}
},
"letterSpacing": {
"$type": "dimension",
"tr-tight": {
"$value": "-0.02em"
},
"tr-snug": {
"$value": "-0.01em"
},
"tr-base": {
"$value": "0em"
},
"tr-mono": {
"$value": "0.02em"
},
"tr-label": {
"$value": "0.08em"
}
},
"space": {
"$type": "dimension",
"sp-1": {
"$value": "4px"
},
"sp-2": {
"$value": "8px"
},
"sp-3": {
"$value": "12px"
},
"sp-4": {
"$value": "16px"
},
"sp-5": {
"$value": "24px"
},
"sp-6": {
"$value": "32px"
},
"sp-7": {
"$value": "48px"
},
"sp-8": {
"$value": "64px"
},
"sp-9": {
"$value": "96px"
},
"sp-10": {
"$value": "128px"
}
},
"radius": {
"$type": "dimension",
"r-0": {
"$value": "0px"
},
"r-1": {
"$value": "2px"
},
"r-2": {
"$value": "4px"
},
"r-3": {
"$value": "8px"
},
"r-pill": {
"$value": "999px"
}
},
"shadow": {
"$type": "shadow",
"shadow-0": {
"$value": "none"
},
"shadow-1": {
"$value": "0 1px 0 var(--line)"
},
"shadow-2": {
"$value": "0 1px 0 var(--line-strong)"
},
"shadow-3": {
"$value": "0 4px 12px -6px rgba(10,10,10,0.10)"
}
}
}