@nurix/etna 0.19.3 → 0.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/data/harness-index.json +1 -1
  2. package/data/harnesses/code/harness.json +1 -1
  3. package/data/harnesses/code/rules/frontend.md +3 -3
  4. package/data/harnesses/code/skills/design/SKILL.md +20 -8
  5. package/data/harnesses/code/skills/design/data/catalog/rules.csv +10 -5
  6. package/data/harnesses/code/skills/design/data/combinations/Readme.md +8 -4
  7. package/data/harnesses/code/skills/design/data/combinations/accent-presets.md +113 -0
  8. package/data/harnesses/code/skills/design/data/combinations/fonts-known.json +11 -0
  9. package/data/harnesses/code/skills/design/data/combinations/manifest.json +57 -10
  10. package/data/harnesses/code/skills/design/data/combinations/motion.csv +10 -0
  11. package/data/harnesses/code/skills/design/data/combinations/palettes.csv +92 -49
  12. package/data/harnesses/code/skills/design/data/combinations/styles.csv +67 -54
  13. package/data/harnesses/code/skills/design/data/combinations/typography.csv +64 -61
  14. package/data/harnesses/code/skills/design/data/design-md/library/expo.md +4 -4
  15. package/data/harnesses/code/skills/design/data/design-md/library/lovable.md +5 -5
  16. package/data/harnesses/code/skills/design/data/design-md/library/minimax.md +6 -6
  17. package/data/harnesses/code/skills/design/data/design-md/library/notion.md +4 -4
  18. package/data/harnesses/code/skills/design/data/design-md/library/runwayml.md +4 -4
  19. package/data/harnesses/code/skills/design/data/design-md/library/sentry.md +2 -2
  20. package/data/harnesses/code/skills/design/data/design-md/library/slack.md +6 -6
  21. package/data/harnesses/code/skills/design/data/design-md/library/webflow.md +5 -5
  22. package/data/harnesses/code/skills/design/data/design-md/nurix.md +11 -7
  23. package/data/harnesses/code/skills/design/data/design-tokens/SCHEMA.md +256 -141
  24. package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.tokens.json +309 -1
  25. package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.tokens.json +309 -1
  26. package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.tokens.json +309 -1
  27. package/data/harnesses/code/skills/design/data/design-tokens/apple/design.tokens.json +309 -1
  28. package/data/harnesses/code/skills/design/data/design-tokens/binance/design.tokens.json +309 -1
  29. package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.tokens.json +309 -1
  30. package/data/harnesses/code/skills/design/data/design-tokens/cal/design.tokens.json +313 -1
  31. package/data/harnesses/code/skills/design/data/design-tokens/clay/design.tokens.json +309 -1
  32. package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.tokens.json +309 -1
  33. package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.tokens.json +309 -1
  34. package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.tokens.json +309 -1
  35. package/data/harnesses/code/skills/design/data/design-tokens/composio/design.tokens.json +309 -1
  36. package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.tokens.json +309 -1
  37. package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.tokens.json +313 -1
  38. package/data/harnesses/code/skills/design/data/design-tokens/expo/design.tokens.json +309 -1
  39. package/data/harnesses/code/skills/design/data/design-tokens/figma/design.tokens.json +313 -1
  40. package/data/harnesses/code/skills/design/data/design-tokens/framer/design.tokens.json +309 -1
  41. package/data/harnesses/code/skills/design/data/design-tokens/goldens.json +123 -123
  42. package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.tokens.json +309 -1
  43. package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.tokens.json +309 -1
  44. package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.tokens.json +309 -1
  45. package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.tokens.json +313 -1
  46. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.resolver.json +1 -1
  47. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.dark.json +10 -0
  48. package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.json +309 -1
  49. package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.tokens.json +313 -1
  50. package/data/harnesses/code/skills/design/data/design-tokens/meta/design.tokens.json +313 -1
  51. package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.tokens.json +309 -1
  52. package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.tokens.json +309 -1
  53. package/data/harnesses/code/skills/design/data/design-tokens/miro/design.tokens.json +313 -1
  54. package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.tokens.json +309 -1
  55. package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.tokens.json +313 -1
  56. package/data/harnesses/code/skills/design/data/design-tokens/notion/design.tokens.json +309 -1
  57. package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.tokens.json +307 -2
  58. package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.tokens.json +309 -1
  59. package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.tokens.json +309 -1
  60. package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.tokens.json +309 -1
  61. package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.tokens.json +309 -1
  62. package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.tokens.json +309 -1
  63. package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.tokens.json +309 -1
  64. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.resolver.json +1 -1
  65. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.dark.json +10 -0
  66. package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.json +309 -1
  67. package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.tokens.json +309 -1
  68. package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.tokens.json +310 -2
  69. package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.tokens.json +313 -1
  70. package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.tokens.json +309 -1
  71. package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.tokens.json +309 -1
  72. package/data/harnesses/code/skills/design/data/design-tokens/slack/design.tokens.json +309 -1
  73. package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.tokens.json +309 -1
  74. package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.tokens.json +313 -1
  75. package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.tokens.json +313 -1
  76. package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.tokens.json +309 -1
  77. package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.tokens.json +313 -1
  78. package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.tokens.json +309 -1
  79. package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.tokens.json +309 -1
  80. package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.tokens.json +313 -1
  81. package/data/harnesses/code/skills/design/data/design-tokens/uber/design.tokens.json +309 -1
  82. package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.tokens.json +309 -1
  83. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.resolver.json +1 -1
  84. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.dark.json +10 -0
  85. package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.json +309 -1
  86. package/data/harnesses/code/skills/design/data/design-tokens/warp/design.tokens.json +309 -1
  87. package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.tokens.json +309 -1
  88. package/data/harnesses/code/skills/design/data/design-tokens/wired/design.tokens.json +309 -1
  89. package/data/harnesses/code/skills/design/data/design-tokens/wise/design.tokens.json +309 -1
  90. package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.tokens.json +309 -1
  91. package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.tokens.json +309 -1
  92. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/command-center.tsx +25 -25
  93. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/create-edit.tsx +18 -18
  94. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/dashboard.tsx +82 -105
  95. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/entity-detail.tsx +97 -93
  96. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/list-detail.tsx +16 -15
  97. package/data/harnesses/code/skills/design/data/operator-pack/exemplars/log-stream.tsx +34 -34
  98. package/data/harnesses/code/skills/design/data/operator-pack/surface-catalog.md +5 -5
  99. package/data/harnesses/code/skills/design/references/create.md +49 -7
  100. package/data/harnesses/code/skills/design/references/diagnosis.md +3 -3
  101. package/data/harnesses/code/skills/design/references/hard-rules.md +25 -3
  102. package/data/harnesses/code/skills/design/references/improve.md +32 -3
  103. package/data/harnesses/code/skills/design/references/interaction.md +3 -3
  104. package/data/harnesses/code/skills/design/references/introspect.md +1 -1
  105. package/data/harnesses/code/skills/design/references/lifecycle.md +2 -2
  106. package/data/harnesses/code/skills/design/references/personality.md +280 -133
  107. package/data/harnesses/code/skills/design/references/structure.md +22 -1
  108. package/data/harnesses/code/skills/design/scripts/convert-design-md.mjs +306 -26
  109. package/data/harnesses/code/skills/design/scripts/derive-personality.mjs +596 -104
  110. package/data/harnesses/code/skills/design/scripts/lib/checks.mjs +200 -11
  111. package/data/harnesses/code/skills/design/scripts/lib/vocabulary.mjs +65 -0
  112. package/data/harnesses/code/skills/design/scripts/phase-gate.mjs +95 -11
  113. package/data/harnesses/code/skills/design/scripts/preship-sweep.mjs +50 -5
  114. package/data/harnesses/code/skills/design/scripts/render-rung.mjs +214 -14
  115. package/data/harnesses/code/skills/design/scripts/scaffold.mjs +242 -25
  116. package/data/harnesses/code/skills/design/scripts/self-integrity.mjs +81 -6
  117. package/data/harnesses/code/skills/design/scripts/snapshot-goldens.mjs +101 -5
  118. package/data/harnesses/code/skills/design/scripts/validate-combinations.mjs +142 -17
  119. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- # design-tokens — schema `design-tokens/v1`
1
+ # design-tokens — schema `design-tokens/v2`
2
2
 
3
3
  A look travels as a **design-tokens artifact**: three JSON files in the pinned **DTCG 2025.10 Stable**
4
4
  grammar (W3C Design Tokens Community Group — the dated snapshot; the live drafts carry the spec's own
@@ -16,15 +16,21 @@ documentation format.
16
16
  **One schema, every origin.** A project's own artifact, an extracted artifact, a shipped library
17
17
  entry, and the Nurix default all use this identical shape; only the payload's `provenance` varies.
18
18
 
19
+ **v1 → v2 in one line:** everything structural survives; the vocabulary grows — **expression arrives
20
+ as tokens** (27 primitives, 36 color roles, a 7-step ramp, 4 elevation composites, an optional wash
21
+ pair, and the `theme-default` / `anchors` payload keys), so the no-literals rule and the token-lock
22
+ now guard a vocabulary with range. Existing v1 artifacts migrate mechanically:
23
+ `convert-design-md.mjs --upgrade-v2` (idempotent; the Migration section below).
24
+
19
25
  ---
20
26
 
21
27
  ## The three files
22
28
 
23
29
  | File | Ships | Contents |
24
30
  | --- | --- | --- |
25
- | `design.tokens.json` | always | The base/light set: the 26 visual primitives, the 34 color roles, the typography composites, and the file-level `$extensions["com.nurix.design"]` payload. The base set **is** the light rendering — light is never omitted. |
31
+ | `design.tokens.json` | always | The base/light set: the 27 visual primitives, the 36 color roles, the typography composites, the 4 elevation composites, the optional wash pair, and the file-level `$extensions["com.nurix.design"]` payload. The base set **is** the light rendering — light is never omitted, even when `theme-default` is `dark`. |
26
32
  | `design.tokens.dark.json` | only when the look has dark values | The dark modifier set: the `color` group remapped, nothing else. |
27
- | `design.resolver.json` | always | Sets, modifiers, and resolution order. Light-first. |
33
+ | `design.resolver.json` | always | Sets, modifiers, and resolution order. The theme modifier's `default` mirrors the payload's `theme-default`. |
28
34
 
29
35
  The three files sit together in one directory: a project's artifact lives at the repo root; a library
30
36
  entry lives at `data/design-tokens/<product-slug>/`; the Nurix default at `data/design-tokens/nurix/`.
@@ -47,19 +53,22 @@ the payload's `provenance` field:
47
53
  A token is an object with `$type` and `$value`, plus optional `$description` and `$extensions`.
48
54
  Groups nest; the file root is a group and carries the file-level `$extensions`. An alias is the
49
55
  string `"{path.to.token}"` and may appear anywhere a value of the target's type is legal, including
50
- composite sub-values; alias chains are legal, cycles are not. Exactly eight `$type` values appear —
51
- **never invent a custom `$type`**:
56
+ composite sub-values; alias chains are legal, cycles are not. Exactly ten `$type` values appear —
57
+ **never invent a custom `$type`** (`shadow` and `gradient` are DTCG 2025.10 composite types, not
58
+ inventions):
52
59
 
53
60
  | `$type` | `$value` shape | Used by |
54
61
  | --- | --- | --- |
55
- | `color` | `{ "colorSpace": "oklch"\|"srgb", "components": [n, n, n], "alpha"?: n, "hex"?: "#rrggbb" }` | the 34 color roles |
56
- | `dimension` | `{ "value": n, "unit": "px" }` (`px`\|`rem` per spec; this artifact always writes `px`) | `radius`, `border-weight`, `icon-size-base`, the 8 layout primitives, ramp `fontSize` |
57
- | `duration` | `{ "value": n, "unit": "ms" }` | the 4 duration primitives |
62
+ | `color` | `{ "colorSpace": "oklch"\|"srgb", "components": [n, n, n], "alpha"?: n, "hex"?: "#rrggbb" }` | the 36 color roles, shadow layer colors, gradient stop colors |
63
+ | `dimension` | `{ "value": n, "unit": "px" }` (`px`\|`rem` per spec; this artifact always writes `px`) | `radius`, `border-weight`, `icon-size-base`, the 8 layout primitives, ramp `fontSize`, shadow lengths |
64
+ | `duration` | `{ "value": n, "unit": "ms" }` | the 4 duration primitives, `stagger-unit` |
58
65
  | `cubicBezier` | `[x1, y1, x2, y2]` (numbers; x in 0–1) | `ease-default` |
59
66
  | `fontFamily` | array of family-name strings | `font-sans`, `font-mono`, `typography.family.*` |
60
67
  | `fontWeight` | number 100–900 | `font-weight-normal`, `font-weight-strong`, ramp `fontWeight` |
61
68
  | `number` | plain JSON number | `shadow-intensity`, `density-gap`, `density-inset`, `heading-tracking`, `contrast-boost`, `icon-stroke`, ramp `lineHeight` / `letterSpacing` |
62
- | `typography` | composite `{ fontFamily, fontSize, fontWeight, letterSpacing, lineHeight }` | the 6 ramp steps |
69
+ | `typography` | composite `{ fontFamily, fontSize, fontWeight, letterSpacing, lineHeight }` | the 7 ramp steps |
70
+ | `shadow` | composite **array of layers** `{ color, offsetX, offsetY, blur, spread }` (color/dimension sub-value shapes as above) | the 4 `elevation.*` tokens |
71
+ | `gradient` | composite **array of stops** `{ color, position }` (position a number 0–1) | the ≤ 2 `wash.*` tokens |
63
72
 
64
73
  Color rules: an oklch source stays `"colorSpace": "oklch"` with `components` carrying the literal
65
74
  `[L, C, H]` numbers from the source string; a hex/rgba (or any other CSS color) source becomes
@@ -86,15 +95,18 @@ Everything else in the artifact is spec-shaped.
86
95
 
87
96
  One vendor namespace, reverse-domain, on the **base file's root group only** (the dark file carries no
88
97
  payload; the resolver has no `$extensions`). It holds everything the look needs that is not a design
89
- token:
98
+ token — twelve keys in v2, plus the optional `look` record:
90
99
 
91
100
  | Key | Type | Legal values / shape |
92
101
  | --- | --- | --- |
93
- | `schema` | string | Literal `design-tokens/v1`. A parser refuses an unknown value. |
102
+ | `schema` | string | Literal `design-tokens/v2`. A parser refuses an unknown value. |
94
103
  | `dtcg` | string | Literal `2025.10` — the pinned DTCG snapshot this artifact conforms to. |
95
104
  | `product` | string | The product name; slugified (lowercase, non-alphanumeric runs → `-`) it becomes the `data-style` value. |
96
105
  | `provenance` | enum | `project` \| `extracted` \| `library` \| `default` |
97
106
  | `sourced` | string | ISO date the artifact was authored, extracted, or last re-sourced. |
107
+ | `theme-default` | enum | `light` \| `dark` — which context the app boots in: scaffold writes it to `<html data-theme>`, and the resolver's `theme.default` mirrors it. **The brief's explicit theme direction is binding.** Absent → `light`. The base set remains the light rendering either way. |
108
+ | `anchors` | string[] | 2–3 non-empty entries — the named real-product anchors from intake ("Linear", "Stripe Dashboard"), recorded so improve/judge runs critique against the same gestalt targets create aimed at. **Required on `provenance: project` artifacts**; optional on library/default/extracted. |
109
+ | `look` | object | **Optional.** The full intake look block persisted by create-authored artifacts — `theme` (`light` \| `dark` \| `both`), `anchors` (2–3 named products), `delight` (`baseline` \| `expressive`), `resolution` (`project` \| `library:<slug>` \| `seed:<style>+<palette>` \| `default`), `resolution-mode` (`confirmed` \| `recorded`). A provenance record, not a projection: `theme-default` and the top-level `anchors` remain the canonical keys emission consumes. Enum-checked when present (rule 2; the enums mirror the intake gate's `validateLookBlock`). Conforming tools preserve unknown payload keys — this one included. |
98
110
  | `verbosity` | enum | `full` \| `default` \| `compact` \| `expert` — emits `data-verbosity` on `<html>`. |
99
111
  | `feedback` | enum | `subtle` \| `minimal` — emits `data-feedback`. |
100
112
  | `density-tier` | enum | `spacious` \| `standard` \| `dense` — emits `data-density`; drives the density base multipliers. |
@@ -113,27 +125,28 @@ this vocabulary — a radius not on `radius-scale`, a spacing off `spacing-scale
113
125
  | --- | --- | --- |
114
126
  | `spacing-scale` | number[] (px) | Non-empty, ascending — on-scale values only. |
115
127
  | `radius-scale` | number[] (px) | Non-empty, ascending. |
116
- | `shadow-levels` | string[] | Non-empty; names drawn from `none` `2xs` `xs` `sm` `md` `lg` `xl` `2xl`, ordered by strictly increasing intensity. |
128
+ | `shadow-levels` | string[] | Non-empty; names drawn from `none` `2xs` `xs` `sm` `md` `lg` `xl` `2xl` `overlay`, ordered by strictly increasing intensity. The Nurix default's vocabulary is `["none", "sm", "md", "lg", "overlay"]`. |
117
129
  | `motion` | `{ "durations": number[], "easings": string[] }` | Durations in ms, non-empty, ascending; easings non-empty CSS easing strings. |
118
130
  | `icons` | `{ "family": string }` | The icon family, e.g. `lucide`. |
119
131
  | `accent-count` | number | ≥ 1. |
120
132
 
121
133
  The manifest carries only vocabulary that is not already a token — **it never restates a token
122
- value**. The allowlists that are tokens are read from the tokens: the 34 color values from the
134
+ value**. The allowlists that are tokens are read from the tokens: the 36 color values from the
123
135
  `color` group, the font stacks from `primitive.font-sans` / `primitive.font-mono`, the icon stroke
124
- from `primitive.icon-stroke`.
136
+ from `primitive.icon-stroke`, and the elevation/wash color literals from those groups' preserved
137
+ byte forms.
125
138
 
126
139
  ---
127
140
 
128
- ## `primitive.*` — the 26 visual primitives
141
+ ## `primitive.*` — the 27 visual primitives
129
142
 
130
- One group, `primitive`, holding all 26 (leaf names are law — they map 1:1 to the emitted CSS
143
+ One group, `primitive`, holding all 27 (leaf names are law — they map 1:1 to the emitted CSS
131
144
  variables, without the leading `--`). Every token is either concrete or hinted.
132
145
 
133
146
  | Family | Token (`primitive.…`) | `$type` | Notes | Example (source form) |
134
147
  | --- | --- | --- | --- | --- |
135
148
  | geometric | `radius` | `dimension` | | `6px` |
136
- | | `shadow-intensity` | `number` | 0–1 multiplier on the emitted `--shadow-*` alpha | `0.4` |
149
+ | | `shadow-intensity` | `number` | 0–1 master multiplier on every emitted `--shadow-*` alpha; 0 legally flattens everything (flat looks stay expressible). New looks default 1.0 — the elevation composites carry designed alphas. | `0.4` |
137
150
  | | `border-weight` | `dimension` | | `1px` |
138
151
  | | `density-gap` | `number` | multiplier on the `0.25rem` spacing base | `1` |
139
152
  | | `density-inset` | `number` | multiplier on component insets | `1` |
@@ -147,6 +160,7 @@ variables, without the leading `--`). Every token is either concrete or hinted.
147
160
  | | `duration-normal` | `duration` | | `150ms` |
148
161
  | | `duration-slow` | `duration` | | `250ms` |
149
162
  | | `ease-default` | `cubicBezier` | carries `.css` | `cubic-bezier(0.4,0,0.2,1)` |
163
+ | | `stagger-unit` | `duration` | motion-moment base, authored per look; legal 0–60ms, default 30 (reads polished; 0 reads utilitarian) | `30ms` |
150
164
  | surface | `contrast-boost` | `number` | multiplier, 1 = default | `1` |
151
165
  | iconography | `icon-stroke` | `number` | px stroke-width | `1.75` |
152
166
  | | `icon-size-base` | `dimension` | | `16px` |
@@ -159,46 +173,59 @@ variables, without the leading `--`). Every token is either concrete or hinted.
159
173
  | | `form-max-width` | `dimension` | | `640px` |
160
174
  | | `table-row-height` | `dimension` | | `40px` |
161
175
 
162
- **Derived, never stored.** Emission computes `--ease-enter`, `--ease-exit`, and `--stagger-delay`
163
- from `ease-default` and `duration-unit`. They are not tokens, not authored, and not part of the
164
- coverage gate. Do not add them.
176
+ **Derived, never stored.** Emission computes `--ease-enter` and `--ease-exit` from `ease-default`,
177
+ and `--stagger-delay` from `stagger-unit` (`--stagger-delay: var(--stagger-unit)` — the
178
+ `.motion-stagger` cascade base). They are not tokens, not authored, and not part of the coverage
179
+ gate. Do not add them.
165
180
 
166
181
  ---
167
182
 
168
- ## `color.*` — the 34 color roles
183
+ ## `color.*` — the 36 color roles
169
184
 
170
- One flat group, `color`, holding all 34 (leaf names map 1:1 to the emitted `--<name>` variables).
171
- Every token is `$type: "color"` with a typed `$value` **and** mandatory preserved `.css`.
185
+ One flat group, `color`, holding all 36 (leaf names map 1:1 to the emitted `--<name>` variables).
186
+ Every token is `$type: "color"` with a typed `$value` **and** mandatory preserved `.css` (an aliased
187
+ role takes both from its target).
172
188
 
173
- - **Semantic (17):** `background` · `foreground` · `card` · `card-foreground` · `muted` ·
189
+ - **Semantic (19):** `background` · `foreground` · `card` · `card-foreground` · `muted` ·
174
190
  `muted-foreground` · `border` · `input` · `ring` · `primary` · `primary-foreground` · `accent` ·
175
- `accent-foreground` · `destructive` · `destructive-foreground` · `success` · `warning`.
191
+ `accent-foreground` · `destructive` · `destructive-foreground` · `success` · `warning` ·
192
+ `surface-raised` · `surface-overlay`.
176
193
  - **Substrate (17):** `popover` · `popover-foreground` · `secondary` · `secondary-foreground` ·
177
194
  `chart-1` · `chart-2` · `chart-3` · `chart-4` · `chart-5` · `sidebar` · `sidebar-foreground` ·
178
195
  `sidebar-primary` · `sidebar-primary-foreground` · `sidebar-accent` · `sidebar-accent-foreground` ·
179
196
  `sidebar-border` · `sidebar-ring`.
180
197
 
181
- No consumed token falls back to a shadcn-gray default: emission sets all 34. A color token may alias
198
+ **`surface-raised` / `surface-overlay` — the dark ladder.** In light looks the two roles may alias
199
+ `{color.card}` / `{color.popover}` — zero visual change. In dark looks they carry the
200
+ **surface-lightness elevation ladder** (raised surfaces get *lighter*, ~+4–6 L points per step, hue
201
+ held — the physics dark cannot fake with shadows). Components express elevation as
202
+ `bg-surface-raised` / `bg-surface-overlay` instead of shadow-only. Both appear in the dark set (dark
203
+ completeness counts 36).
204
+
205
+ No consumed token falls back to a shadcn-gray default: emission sets all 36. A color token may alias
182
206
  another (`"$value": "{color.primary}"`), in which case it takes the target's `.css`; mechanical
183
- conversion writes literals, aliases are for authored files.
207
+ conversion writes literals, aliases are for authored files and the two ladder roles.
184
208
 
185
209
  ---
186
210
 
187
211
  ## `typography.*` — families and the ramp
188
212
 
189
213
  The first-class machine form of the type scale, in two subgroups. It reaches CSS variables only
190
- through the primitives it aliases; emitters and exemplars read the ramp as data (size, line-height,
191
- weight, tracking per step).
214
+ through the primitives it aliases; emitters read the ramp as data and materialize it as the seven
215
+ `text-*` utilities (the type-scale rule's fixed scale *is* the ramp — typ-09).
192
216
 
193
- **`typography.family.*`** — the heading/body pairing plus mono, all `$type: "fontFamily"`:
217
+ **`typography.family.*`** — the heading/body pairing plus mono, plus an optional display slot, all
218
+ `$type: "fontFamily"`. The three-*job* rhythm is display / body / numeric-mono (typ-06: ≤ 3 families,
219
+ each with a distinct job); `heading` stays for compat and typically aliases `display` or `body`:
194
220
 
195
221
  | Token | Value |
196
222
  | --- | --- |
197
223
  | `typography.family.heading` | `"{primitive.font-sans}"` — or its own stack when the look pairs a distinct heading family. |
198
224
  | `typography.family.body` | `"{primitive.font-sans}"` |
199
225
  | `typography.family.mono` | `"{primitive.font-mono}"` |
226
+ | `typography.family.display` | **optional** third-slot member; may alias `heading`. |
200
227
 
201
- **`typography.ramp.*`** — six fixed step names, each `$type: "typography"` carrying all five
228
+ **`typography.ramp.*`** — seven fixed step names, each `$type: "typography"` carrying all five
202
229
  sub-values:
203
230
 
204
231
  | Step | Role |
@@ -209,6 +236,7 @@ sub-values:
209
236
  | `body` | UI + prose |
210
237
  | `small` | labels, meta, dense table |
211
238
  | `caption` | uppercase eyebrow (text case is a usage rule; the composite carries no case slot) |
239
+ | `display` | the scale-drama step: KPI numerals, the one focal metric, a brand-forward page title. `fontSize` guidance 32–48px; `fontFamily` may alias `typography.family.display`. Usage is rule-gated, not schema-gated: display-step elements only on `brand-forward` surfaces, ≤ 1 per surface (typ-20). Baseline surfaces keep the 24px-h1 world. |
212
240
 
213
241
  | Sub-value | Shape |
214
242
  | --- | --- |
@@ -220,6 +248,48 @@ sub-values:
220
248
 
221
249
  ---
222
250
 
251
+ ## `elevation.*` — depth as a designed system
252
+
253
+ Four tokens, each `$type: "shadow"`, `$value` an **array of layers** (`{ color, offsetX, offsetY,
254
+ blur, spread }` per layer — the DTCG shadow shape; color sub-values follow the color rules incl.
255
+ `alpha`). Leaf names are law → emitted as `--shadow-sm`, `--shadow-md`, `--shadow-lg`,
256
+ `--shadow-overlay`, plus the matching `@theme` shadow utilities.
257
+
258
+ | Token | Role | Authoring guidance (seeds carry real values) |
259
+ | --- | --- | --- |
260
+ | `elevation.sm` | hairline lift — hover on rows/cards | 1–2 layers, y ≤ 2px, blur ≤ 4px |
261
+ | `elevation.md` | raised panel, dropdown | 2 layers (ambient + key) |
262
+ | `elevation.lg` | brand-forward feature panel | 2–3 layers, tinted |
263
+ | `elevation.overlay` | side panel / dialog scrim companion | widest blur; the only level a modal may use |
264
+
265
+ Rules:
266
+
267
+ - Shadow color is **brand-tintable** — a low-alpha tint toward the brand hue (e.g.
268
+ `rgba(0,55,112,0.08)`), never pure black at alpha ≥ 0.15.
269
+ - `primitive.shadow-intensity` stays the master multiplier: emission wraps every layer alpha as
270
+ `calc(<alpha> * var(--shadow-intensity))`, so `prefers-contrast: more` (which zeroes the
271
+ multiplier) and flat looks keep working.
272
+ - The **token-level `.css`** is the full serialized shadow list — the preserved byte form, carrying
273
+ every layer color's byte form inside it (composite sub-values have no `$extensions` slot of their
274
+ own). Zero lengths serialize bare `0`; the alpha may ride the `calc(… * var(--shadow-intensity))`
275
+ wrapper in the byte form — parse-back strips it to the layer alpha.
276
+ - The lever manifest's `shadow-levels` key is unchanged in shape; `overlay` joins its legal name
277
+ vocabulary.
278
+
279
+ ---
280
+
281
+ ## `wash.*` — the tasteful-gradient path (optional group)
282
+
283
+ At most two tokens, `$type: "gradient"` (the DTCG stops array; each stop's color follows the color
284
+ rules, `position` in 0–1). Emitted as `--wash-surface` / `--wash-emphasis`. **Optional presence**: a
285
+ look that declares no washes omits the group entirely — the coverage gate validates shape when
286
+ present, never requires it. The token-level `.css` carries the authored gradient (the stop list holds
287
+ no geometry — angle and shape live in the byte form). Consumption is budget-gated (only
288
+ `brand-forward` surfaces), and the anti-slop gradient ban (col-12 — neon purple/blue glows,
289
+ purple-on-white) applies to wash values: a wash is a low-chroma tint ramp, not a hero gradient.
290
+
291
+ ---
292
+
223
293
  ## The preserved-css pattern
224
294
 
225
295
  Every formatting-sensitive value carries **two forms that must agree**: the typed `$value` (the
@@ -227,18 +297,22 @@ queryable semantic) and `$extensions["com.nurix.design"].css` — the exact sour
227
297
  Emitters prefer the preserved string, so output is byte-stable across convert/emit round trips.
228
298
 
229
299
  - **Mandatory `.css`:** every `color` token; `heading-tracking` (sources vary — `0`, `0em`,
230
- `-0.015em`); `ease-default`; any `fontFamily` token carrying its own stack.
300
+ `-0.015em`); `ease-default`; any `fontFamily` token carrying its own stack; every `elevation.*`
301
+ token (the serialized shadow list); every `wash.*` token (the authored gradient).
231
302
  - **Omitted where serialization is deterministic:** `dimension` and `duration` serialize as
232
303
  `<value><unit>`; `number` and `fontWeight` as the plain number; `cubicBezier` (absent `.css`) as
233
304
  `cubic-bezier(x1, y1, x2, y2)`; `fontFamily` (absent `.css`) as the families joined with `", "`,
234
305
  multi-word names double-quoted.
235
- - **On a composite token**, `.css` is an object keyed by sub-value name, present for
236
- formatting-sensitive inline sub-values (e.g. `{ "letterSpacing": "0.02em" }`); an aliased sub-value
237
- takes its `.css` from the alias target.
306
+ - **On a composite token**, `.css` is an object keyed by sub-value name for `typography` (present
307
+ for formatting-sensitive inline sub-values, e.g. `{ "letterSpacing": "0.02em" }`; an aliased
308
+ sub-value takes its `.css` from the alias target) and a single string — the full serialized value —
309
+ for `shadow` and `gradient`.
238
310
  - **Parse-back verification is a converter requirement:** whatever writes a token must verify that
239
311
  parsing `.css` reproduces `$value` exactly, and that re-serializing `$value` reproduces the byte
240
312
  form — oklch components are the literal source numbers; srgb components round-trip through the
241
- `hex` field. The gate re-verifies.
313
+ `hex` field; a shadow's `.css` parses back to its layer array (the intensity `calc` wrapper strips
314
+ to the layer alpha); a gradient's `.css` must contain each stop color's byte form. The gate
315
+ re-verifies.
242
316
 
243
317
  ## Hints and the coverage tri-state
244
318
 
@@ -254,6 +328,8 @@ words. Every gated token is in exactly one state:
254
328
 
255
329
  Resolving a hint deletes the `hint` key and records the rationale in the token's `$description`, so
256
330
  the resolved artifact is self-explaining and re-derivable. Judgment is recorded, never silent.
331
+ (`--emit --provisional` — the golden/regression flow only — emits hinted tokens at their provisional
332
+ `$values` instead of refusing; it never ships output.)
257
333
 
258
334
  ```json
259
335
  {
@@ -273,23 +349,38 @@ resolves to:
273
349
  }
274
350
  ```
275
351
 
276
- ## Per-value provenance
352
+ ## Per-value provenance — the layer vocabulary
277
353
 
278
354
  A value's provenance rationale — why it diverges from a brand default, what judgment resolved its
279
355
  hint — lives in the token's `$description`. Mechanical conversion moves source comments here.
280
356
 
357
+ **The layer tags.** A `$description` that opens with a tag names the composition layer that set the
358
+ value (the layer model's audit surface): **`source:`** (the resolved source), **`brief:`** (a brief
359
+ binding), **`deviation:`** (a recorded deviation — the only layer that may contradict a domain owner
360
+ or leave the tables entirely; every such value carries its reason). Untagged values are inherited
361
+ from the resolved source or defaults. The mechanical-annotation prefixes the converters write
362
+ (`derived:`, `corpus:`, `rationale:`, `note:`, `synthesized:`, `brand:`, `dark:`) remain legal; any
363
+ other opening tag fails the gate. **Every off-manifest value carries `deviation:`** — an artifact
364
+ base value off its own lever-manifest allowlist without one fails rule 8. The artifact root's
365
+ `$description` carries the resolution record (which source won, the runner-up, the deciding
366
+ evidence).
367
+
281
368
  ---
282
369
 
283
370
  ## The dark set — `design.tokens.dark.json`
284
371
 
285
372
  Ships **only when the look has dark values**; light is never omitted — the base set is the light
286
- rendering. When it ships:
287
-
288
- - It carries exactly the `color` group, all 34 roles, same shape rules (typed `$value` + `.css`) —
289
- and nothing else: no primitives, no typography, no payload (the base file is the payload's single
290
- carrier).
291
- - Emission targets `[data-theme="dark"]`, remapping all 34; when the file is absent, no dark block is
292
- emitted and the project is light-only.
373
+ rendering (even for a `theme-default: dark` look; only the boot attribute and resolver default move).
374
+ When it ships:
375
+
376
+ - It carries exactly the `color` group, all 36 roles, same shape rules (typed `$value` + `.css`;
377
+ aliases legal — they resolve after overlay, so `"{color.card}"` in the dark file resolves to the
378
+ dark card) — and nothing else: no primitives, no typography, no elevation, no payload (the base
379
+ file is the payload's single carrier).
380
+ - Emission targets `:root[data-theme="dark"]` (specificity 0,2,0 — the same armor as the light
381
+ block; a lone attribute selector loses to the component library's later `:root` by source order),
382
+ remapping all 36; when the file is absent, no dark block is emitted and the project is light-only.
383
+ - The `surface-raised` / `surface-overlay` roles carry the dark surface-lightness ladder (above).
293
384
 
294
385
  ```json
295
386
  {
@@ -300,7 +391,7 @@ rendering. When it ships:
300
391
  }
301
392
  ```
302
393
 
303
- (Fragment — a real dark file carries all 34.)
394
+ (Fragment — a real dark file carries all 36.)
304
395
 
305
396
  ---
306
397
 
@@ -319,10 +410,10 @@ place.
319
410
  **Resolution order (deterministic):** concatenate the set files in array order; select one context
320
411
  per modifier (the `default` when unstated); overlay the context's files in order — a later file
321
412
  overrides an earlier one per token path, and an overlay may only override paths that exist in the
322
- base, never introduce new ones. Aliases resolve after overlay. **Light-first:** `theme.default` is
323
- always `light`, and the `light` context is always the empty overlay `[]` — the base set is the light
324
- rendering. A light-only look carries only the `light` context; a look with dark adds
325
- `"dark": ["design.tokens.dark.json"]`:
413
+ base, never introduce new ones. Aliases resolve after overlay. **The base set is always the light
414
+ rendering:** the `light` context is always the empty overlay `[]`, and `theme.default` mirrors the
415
+ payload's `theme-default` (`light` when absent — a dark-first look sets both to `dark`). A light-only
416
+ look carries only the `light` context; a look with dark adds `"dark": ["design.tokens.dark.json"]`:
326
417
 
327
418
  ```json
328
419
  {
@@ -343,73 +434,134 @@ The slug is the payload's `product`, slugified. Emission order is this document'
343
434
 
344
435
  | Source | Emitted target |
345
436
  | --- | --- |
346
- | `primitive.<name>` (24 static) | `[data-style="<slug>"] { --<name>: … }` — 1:1 by leaf name |
437
+ | `primitive.<name>` (25 static) | `[data-style="<slug>"] { --<name>: … }` — 1:1 by leaf name |
347
438
  | `primitive.density-gap` / `density-inset` | governed refs (`var(--density-base-gap/inset)`); the tier writes the base |
348
- | `color.<name>` (34) | `:root, :root[data-theme="light"] { --<name>: … }` |
349
- | dark set `color.<name>` | `[data-theme="dark"] { --<name>: … }` — only when the dark file ships |
439
+ | `color.<name>` (36) | `:root, :root[data-theme="light"] { --<name>: … }` |
440
+ | dark set `color.<name>` | `:root[data-theme="dark"] { --<name>: … }` — only when the dark file ships |
441
+ | `elevation.<level>` (4) | `--shadow-<level>` in the `[data-style]` block **and** the `@theme` shadow utilities (`shadow-sm` … `shadow-overlay`); every alpha rides `calc(… * var(--shadow-intensity))` |
442
+ | `wash.<slot>` (≤ 2, optional) | `--wash-surface` / `--wash-emphasis` in the `[data-style]` block — consumed only on budget-granted surfaces |
443
+ | `typography.ramp.<step>` (7) | `@theme` text utilities `text-h1` … `text-display` carrying size / line-height / weight / tracking per step — the ramp becomes *classes* (typ-09: the fixed scale IS the ramp) |
444
+ | `primitive.stagger-unit` | `--stagger-delay: var(--stagger-unit)` (derived) + the named motion classes `.motion-lift` (hover −1px translateY + `elevation.sm` at `duration-fast`/`ease-default`) and `.motion-stagger` (children arrival cascade at `--stagger-delay`, capped at 8 children — mot-08), both inert under `prefers-reduced-motion` (mot-03) |
350
445
  | `verbosity` / `feedback` / `density-tier` | `<html data-verbosity / data-feedback / data-density>` |
446
+ | `theme-default` | `<html data-theme>` (the boot theme; the toggle round-trips the same attribute) |
351
447
  | `product` | `<html data-style="<slug>">` |
352
- | `product-traits` | read by the front gate; never emitted |
353
- | `typography.family.*` / `typography.ramp.*` | data for emitters and exemplars; reaches CSS only through the primitives it aliases |
354
- | — (derived at emit) | `--ease-enter`, `--ease-exit`, `--stagger-delay` from `ease-default` + `duration-unit` |
448
+ | `product-traits` / `anchors` | read by the front gate and improve/judge runs; never emitted |
449
+ | `typography.family.*` | data for emitters and exemplars; reaches CSS only through the primitives it aliases |
450
+ | — (derived at emit) | `--ease-enter`, `--ease-exit` from `ease-default`; `--stagger-delay` from `stagger-unit` |
355
451
 
356
452
  ---
357
453
 
358
454
  ## Validation — what `--check` enforces
359
455
 
360
456
  The coverage gate. It exits non-zero and **names every offending path** so the fix is mechanical; an
361
- artifact that passes is guaranteed derivable.
457
+ artifact that passes is guaranteed derivable. Rules 1–12 keep their v1 numbering (amended where the
458
+ surface grew); rules 13–15 are the v2 additions.
362
459
 
363
460
  1. **Files** — `design.tokens.json` and `design.resolver.json` present and parse as JSON; every file
364
461
  the resolver references exists beside it; `design.tokens.dark.json` exists exactly when the
365
462
  resolver's `dark` context names it.
366
463
  2. **Payload** — the base file's root `$extensions["com.nurix.design"]` present with: `schema` literal
367
- `design-tokens/v1`; `dtcg` literal `2025.10`; `product` non-empty; `provenance` legal; `sourced` an
464
+ `design-tokens/v2`; `dtcg` literal `2025.10`; `product` non-empty; `provenance` legal; `sourced` an
368
465
  ISO date; `verbosity` / `feedback` / `density-tier` legal enum values (never hinted);
369
- `product-traits` a non-empty array; `lever-manifest` carrying all six keys.
370
- 3. **Primitive coverage** — all 26 `primitive.*` tokens present with the exact `$type` this schema
371
- assigns, each assigned or hinted; fails naming every absent, empty, or mistyped key.
372
- 4. **Color coverage** — all 34 `color.*` tokens present, `$type: "color"`, `colorSpace` one of
373
- `oklch` | `srgb`, `.css` present, each assigned or hinted; fails naming every missing token.
374
- 5. **Typography coverage** — `typography.family.heading|body|mono` present (`fontFamily`);
375
- `typography.ramp.h1|h2|h3|body|small|caption` present (`typography`), each composite carrying all
376
- five sub-values.
466
+ `product-traits` a non-empty array; `lever-manifest` carrying all six keys; `theme-default` legal
467
+ when present and mirrored by the resolver's theme default; `anchors` an array of 2–3 non-empty
468
+ strings when present — **required for `provenance: project`** artifacts; the optional `look`
469
+ block enum-checked when present (theme / delight / resolution-mode enums, 2–3 named anchors,
470
+ the resolution shape).
471
+ 3. **Primitive coverage** — all 27 `primitive.*` tokens present with the exact `$type` this schema
472
+ assigns, each assigned or hinted; `stagger-unit` within 0–60ms; fails naming every absent, empty,
473
+ or mistyped key.
474
+ 4. **Color coverage** — all 36 `color.*` tokens present, `$type: "color"`, `colorSpace` one of
475
+ `oklch` | `srgb`, `.css` present (via the target when aliased), each assigned or hinted; fails
476
+ naming every missing token.
477
+ 5. **Typography coverage** — `typography.family.heading|body|mono` present (`fontFamily`;
478
+ `family.display` optional but shape-checked when present);
479
+ `typography.ramp.h1|h2|h3|body|small|caption|display` present (`typography`), each composite
480
+ carrying all five sub-values.
377
481
  6. **Alias integrity** — every `{path}` reference resolves within the artifact to a token of the
378
482
  required type; no cycles.
379
483
  7. **Manifest scales** — `spacing-scale`, `radius-scale`, `motion.durations` non-empty and ascending;
380
- `motion.easings` non-empty; `shadow-levels` non-empty, drawn from the legal vocabulary, strictly
381
- increasing in intensity; `icons.family` non-empty; `accent-count` ≥ 1.
484
+ `motion.easings` non-empty; `shadow-levels` non-empty, drawn from the legal vocabulary (`overlay`
485
+ included), strictly increasing in intensity; `icons.family` non-empty; `accent-count` ≥ 1.
382
486
  8. **Manifest consistency** — the look's own base values sit on its own allowlist: `radius` ∈
383
487
  `radius-scale`; `duration-fast` / `duration-normal` / `duration-slow` ∈ `motion.durations`;
384
- `ease-default`'s serialization ∈ `motion.easings`.
488
+ `ease-default`'s serialization ∈ `motion.easings`. An off-manifest value is legal **only** with a
489
+ `deviation:` `$description` (the recorded-deviation rule).
385
490
  9. **Parse-back** — for every token carrying `.css`, parsing the string reproduces `$value` and
386
- re-serializing `$value` reproduces the byte form.
491
+ re-serializing `$value` reproduces the byte form; a shadow `.css` parses back to its layer array
492
+ (the intensity `calc` wrapper strips to the layer alpha); a gradient `.css` contains each stop
493
+ color's byte form.
387
494
  10. **Dark completeness** — the dark file, when present, carries exactly the `color` group with all
388
- 34 roles under the same shape rules, and nothing else.
389
- 11. **Spec conformance** — only the eight `$type` values this schema uses appear; no invented
495
+ 36 roles under the same shape rules (aliases legal), and nothing else.
496
+ 11. **Spec conformance** — only the ten `$type` values this schema uses appear; no invented
390
497
  `$type`; unknown `$extensions` namespaces are preserved, never authored.
391
498
  12. **Emission precondition** — `--emit` runs only over a passing artifact and refuses while any
392
- token carries a `hint`, naming every hinted path.
499
+ token carries a `hint`, naming every hinted path (`--provisional` — golden/regression flows
500
+ only — emits provisional values instead).
501
+ 13. **Elevation coverage** — all four `elevation.*` present, `$type: "shadow"`, ≥ 1 layer each,
502
+ every layer color rule-conformant (typed oklch|srgb; never pure black at alpha ≥ 0.15), lengths
503
+ dimension-shaped, the token-level `.css` present; each assigned or hinted; no level outside
504
+ `sm|md|lg|overlay`.
505
+ 14. **Wash shape** — when the `wash` group exists: ≤ 2 tokens, slots ⊆ `surface|emphasis`,
506
+ `$type: "gradient"`, ≥ 2 stops each with a rule-conformant color and a position in 0–1, `.css`
507
+ present. Presence is never required.
508
+ 15. **Provenance vocabulary** — a per-value `$description` opening with a tag uses a legal one
509
+ (`source:` / `brief:` / `deviation:` layer tags, or a mechanical-annotation prefix; untagged =
510
+ inherited); rule 8 enforces the off-manifest `deviation:` requirement.
511
+
512
+ Font binding and emitter completeness run at `--emit`, not `--check` — they compare the artifact
513
+ against emitted output and project dependencies (the emission contracts, not this file's gate).
514
+
515
+ ---
516
+
517
+ ## Migration — v1 artifacts to v2
518
+
519
+ `convert-design-md.mjs --upgrade-v2` is the mechanical, **idempotent** v1→v2 upgrade (it runs over
520
+ `data/design-tokens/*/`; the converter also applies the same transform to every fresh conversion):
521
+
522
+ 1. `schema` literal → `design-tokens/v2`.
523
+ 2. `elevation.*` derived from the v1 hardcoded slate recipes × the twin's `shadow-intensity` —
524
+ value-preserving (the emitted `--shadow-sm/md/lg` for an untouched twin are byte-identical before
525
+ and after migration, because the composites carry the recipe alphas and the intensity stays the
526
+ runtime `calc` multiplier), tagged `$description: "derived: v1 shadow-intensity migration"`.
527
+ Library entries whose design.md names real shadow values get **hinted** elevation instead,
528
+ resolved from the design.md prose at the data build.
529
+ 3. `color.surface-raised` / `surface-overlay` → aliases to `{color.card}` / `{color.popover}` (in
530
+ the dark file too, where they resolve to the dark card/popover; the +4–6 L ladder is owed at
531
+ budget-time).
532
+ 4. `typography.ramp.display` → **hinted** (provisional: h1 scaled ×1.75, weight from
533
+ `font-weight-strong`), hint text "display step unset — resolve from the product's design.md or
534
+ leave for budget-time". The Nurix default adopts the provisional derivation as its own look,
535
+ assigned.
536
+ 5. `primitive.stagger-unit` → 30ms default (the prior derived clamp's value).
537
+ 6. `theme-default` → `light` everywhere except twins whose design.md declares dark-first (only
538
+ twins carrying dark sets qualify; their design.md decides); the resolver's theme default mirrors
539
+ it.
540
+ 7. Re-run parse-back verification + the v2 gate over every migrated twin.
541
+
542
+ Because migration is deterministic + hint-carrying, no hand-rewrite of the library is needed; hints
543
+ surface exactly where judgment is owed.
393
544
 
394
545
  ---
395
546
 
396
547
  ## Worked example — the Nurix default (abbreviated)
397
548
 
398
549
  `data/design-tokens/nurix/design.tokens.json`, abbreviated to a representative subset of each token
399
- family — the real file carries all 26 primitives, all 34 color roles, and the full six-step ramp.
400
- Nurix ships no dark set, so `design.tokens.dark.json` is absent and the resolver carries only the
401
- `light` context.
550
+ family — the real file carries all 27 primitives, all 36 color roles, the full seven-step ramp, and
551
+ the four elevation composites. Nurix ships no dark set, so `design.tokens.dark.json` is absent and
552
+ the resolver carries only the `light` context.
402
553
 
403
554
  ```json
404
555
  {
405
556
  "$description": "Nurix default — the look a zero-input project wears. Abbreviated here; the shipped file is complete.",
406
557
  "$extensions": {
407
558
  "com.nurix.design": {
408
- "schema": "design-tokens/v1",
559
+ "schema": "design-tokens/v2",
409
560
  "dtcg": "2025.10",
410
561
  "product": "Nurix",
411
562
  "provenance": "default",
412
563
  "sourced": "2026-07-04",
564
+ "theme-default": "light",
413
565
  "product-traits": ["operator-console", "crm", "queue", "dashboard", "admin"],
414
566
  "verbosity": "default",
415
567
  "feedback": "subtle",
@@ -417,7 +569,7 @@ Nurix ships no dark set, so `design.tokens.dark.json` is absent and the resolver
417
569
  "lever-manifest": {
418
570
  "spacing-scale": [2, 4, 8, 12, 16, 24, 32, 48, 64],
419
571
  "radius-scale": [0, 4, 6, 12, 9999],
420
- "shadow-levels": ["none", "sm", "md"],
572
+ "shadow-levels": ["none", "sm", "md", "lg", "overlay"],
421
573
  "motion": { "durations": [100, 150, 250], "easings": ["cubic-bezier(0.4,0,0.2,1)"] },
422
574
  "icons": { "family": "lucide" },
423
575
  "accent-count": 1
@@ -435,44 +587,28 @@ Nurix ships no dark set, so `design.tokens.dark.json` is absent and the resolver
435
587
  "$value": 0.4,
436
588
  "$description": "brand is predominantly flat; shadow only lifts true overlays"
437
589
  },
438
- "border-weight": { "$type": "dimension", "$value": { "value": 1, "unit": "px" } },
439
- "density-gap": { "$type": "number", "$value": 1 },
440
590
  "font-sans": {
441
591
  "$type": "fontFamily",
442
592
  "$value": ["Inter", "system-ui", "sans-serif"],
443
593
  "$description": "brand's body font; the single sans slot serves dense body/table chrome",
444
594
  "$extensions": { "com.nurix.design": { "css": "Inter, system-ui, sans-serif" } }
445
595
  },
446
- "font-mono": {
447
- "$type": "fontFamily",
448
- "$value": ["Geist Mono", "ui-monospace", "SFMono-Regular", "monospace"],
449
- "$description": "Geist monospace sibling — IDs, codes, log lines",
450
- "$extensions": { "com.nurix.design": { "css": "Geist Mono, ui-monospace, SFMono-Regular, monospace" } }
451
- },
452
- "font-weight-normal": {
453
- "$type": "fontWeight",
454
- "$value": 400,
455
- "$description": "brand base 500 is display-tuned; dense operator body reads cleaner at 400"
456
- },
457
- "font-weight-strong": { "$type": "fontWeight", "$value": 600 },
458
596
  "heading-tracking": {
459
597
  "$type": "number",
460
598
  "$value": -0.01,
461
599
  "$description": "em multiplier; brand display tracks 0 — at operator heading sizes Inter reads tighter",
462
600
  "$extensions": { "com.nurix.design": { "css": "-0.01em" } }
463
601
  },
464
- "duration-unit": { "$type": "duration", "$value": { "value": 50, "unit": "ms" } },
465
602
  "ease-default": {
466
603
  "$type": "cubicBezier",
467
604
  "$value": [0.4, 0, 0.2, 1],
468
605
  "$extensions": { "com.nurix.design": { "css": "cubic-bezier(0.4,0,0.2,1)" } }
469
606
  },
470
- "icon-stroke": {
471
- "$type": "number",
472
- "$value": 1.75,
473
- "$description": "one step under lucide's 2 for a refined operator stroke"
607
+ "stagger-unit": {
608
+ "$type": "duration",
609
+ "$value": { "value": 30, "unit": "ms" },
610
+ "$description": "derived: v1→v2 migration — the 30ms default stagger base (0–60ms legal)"
474
611
  },
475
- "sidebar-width-expanded": { "$type": "dimension", "$value": { "value": 248, "unit": "px" } },
476
612
  "table-row-height": {
477
613
  "$type": "dimension",
478
614
  "$value": { "value": 40, "unit": "px" },
@@ -485,42 +621,21 @@ Nurix ships no dark set, so `design.tokens.dark.json` is absent and the resolver
485
621
  "$value": { "colorSpace": "oklch", "components": [1, 0, 0] },
486
622
  "$extensions": { "com.nurix.design": { "css": "oklch(1 0 0)" } }
487
623
  },
488
- "foreground": {
489
- "$type": "color",
490
- "$value": { "colorSpace": "oklch", "components": [0.2077, 0.0398, 265.75] },
491
- "$extensions": { "com.nurix.design": { "css": "oklch(0.2077 0.0398 265.75)" } }
492
- },
493
- "border": {
494
- "$type": "color",
495
- "$value": { "colorSpace": "oklch", "components": [0.9288, 0.0126, 255.51] },
496
- "$description": "brand #E2E8F0 — every separator, grid line, and field edge",
497
- "$extensions": { "com.nurix.design": { "css": "oklch(0.9288 0.0126 255.51)" } }
498
- },
499
624
  "primary": {
500
625
  "$type": "color",
501
626
  "$value": { "colorSpace": "oklch", "components": [0.5211, 0.2145, 263.89] },
502
627
  "$description": "brand #245AE2 — the one accent",
503
628
  "$extensions": { "com.nurix.design": { "css": "oklch(0.5211 0.2145 263.89)" } }
504
629
  },
505
- "primary-foreground": {
506
- "$type": "color",
507
- "$value": { "colorSpace": "oklch", "components": [1, 0, 0] },
508
- "$extensions": { "com.nurix.design": { "css": "oklch(1 0 0)" } }
509
- },
510
- "warning": {
511
- "$type": "color",
512
- "$value": { "colorSpace": "oklch", "components": [0.6658, 0.1574, 58.32] },
513
- "$extensions": { "com.nurix.design": { "css": "oklch(0.6658 0.1574 58.32)" } }
514
- },
515
- "chart-2": {
630
+ "surface-raised": {
516
631
  "$type": "color",
517
- "$value": { "colorSpace": "oklch", "components": [0.6089, 0.1109, 221.72] },
518
- "$extensions": { "com.nurix.design": { "css": "oklch(0.6089 0.1109 221.72)" } }
632
+ "$value": "{color.card}",
633
+ "$description": "derived: v1→v2 migration — light aliases card; dark looks carry the surface-lightness ladder"
519
634
  },
520
- "sidebar": {
635
+ "surface-overlay": {
521
636
  "$type": "color",
522
- "$value": { "colorSpace": "oklch", "components": [0.9842, 0.0034, 247.86] },
523
- "$extensions": { "com.nurix.design": { "css": "oklch(0.9842 0.0034 247.86)" } }
637
+ "$value": "{color.popover}",
638
+ "$description": "derived: v1→v2 migration — light aliases popover; dark looks carry the surface-lightness ladder"
524
639
  }
525
640
  },
526
641
  "typography": {
@@ -540,29 +655,29 @@ Nurix ships no dark set, so `design.tokens.dark.json` is absent and the resolver
540
655
  "letterSpacing": "{primitive.heading-tracking}"
541
656
  }
542
657
  },
543
- "body": {
544
- "$type": "typography",
545
- "$value": {
546
- "fontFamily": "{typography.family.body}",
547
- "fontSize": { "value": 14, "unit": "px" },
548
- "lineHeight": 1.45,
549
- "fontWeight": "{primitive.font-weight-normal}",
550
- "letterSpacing": 0
551
- }
552
- },
553
- "caption": {
658
+ "display": {
554
659
  "$type": "typography",
555
660
  "$value": {
556
- "fontFamily": "{typography.family.body}",
557
- "fontSize": { "value": 12, "unit": "px" },
558
- "lineHeight": 1.2,
661
+ "fontFamily": "{typography.family.heading}",
662
+ "fontSize": { "value": 42, "unit": "px" },
663
+ "lineHeight": 1.25,
559
664
  "fontWeight": "{primitive.font-weight-strong}",
560
- "letterSpacing": 0.02
665
+ "letterSpacing": "{primitive.heading-tracking}"
561
666
  },
562
- "$description": "uppercase eyebrow — text case is a usage rule; the composite carries no case slot",
563
- "$extensions": { "com.nurix.design": { "css": { "letterSpacing": "0.02em" } } }
667
+ "$description": "derived: v2 display step — the provisional h1 × 1.75 derivation adopted as the default's own look (KPI numerals, the one focal metric)"
564
668
  }
565
669
  }
670
+ },
671
+ "elevation": {
672
+ "sm": {
673
+ "$type": "shadow",
674
+ "$value": [
675
+ { "color": { "colorSpace": "srgb", "components": [0.05882, 0.0902, 0.16471], "alpha": 0.1, "hex": "#0f172a" }, "offsetX": { "value": 0, "unit": "px" }, "offsetY": { "value": 1, "unit": "px" }, "blur": { "value": 3, "unit": "px" }, "spread": { "value": 0, "unit": "px" } },
676
+ { "color": { "colorSpace": "srgb", "components": [0.05882, 0.0902, 0.16471], "alpha": 0.1, "hex": "#0f172a" }, "offsetX": { "value": 0, "unit": "px" }, "offsetY": { "value": 1, "unit": "px" }, "blur": { "value": 2, "unit": "px" }, "spread": { "value": -1, "unit": "px" } }
677
+ ],
678
+ "$description": "derived: v1 shadow-intensity migration",
679
+ "$extensions": { "com.nurix.design": { "css": "0 1px 3px 0 rgb(15 23 42 / calc(0.10 * var(--shadow-intensity))), 0 1px 2px -1px rgb(15 23 42 / calc(0.10 * var(--shadow-intensity)))" } }
680
+ }
566
681
  }
567
682
  }
568
683
  ```