@dataimago/tokens 0.1.0-alpha.1 → 0.1.0-alpha.12

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.
package/README.md CHANGED
@@ -7,13 +7,17 @@ Canonical design tokens for the dataimago ethical AI design system.
7
7
  - `src/*.json` — the hand-authored JSON sources (colors, typography, spacing, effects, etc.).
8
8
  - `dist/tokens.css` / `dist/tokens.scss` — CSS custom properties emitted from the JSON sources, theme-aware (`[data-bs-theme]` + `prefers-color-scheme`).
9
9
  - `dist/index.{mjs,cjs,d.ts}` — typed JS/TS imports for token values.
10
+ - `dist/tokens.dtcg.json` — additive DTCG-style export for tools and agents. It does not replace the current runtime JSON shape.
10
11
 
11
12
  ## Palette
12
13
 
13
14
  - `ink` — warm neutrals, 50–900
14
- - `cream` — warm paper, 50–500
15
- - `copper` — canonical interactive color (primary on light mode at 500, hover at 600)
16
- - `accent` — emancipation copper-red, reserved for high moral weight
15
+ - `stone` — canonical page/surface neutral scale
16
+ - `forest` — house primary interactive scale
17
+ - `paper` — warm-surface option, retained as the renamed successor to the older `cream` framing
18
+ - `presentation-blue` — presentation/accent source scale
19
+ - `accent` — mode-aware semantic accent mapped to presentation-blue stops
20
+ - `copper` — preserved decorative/editorial warmth scale
17
21
 
18
22
  All semantic tokens (`content.primary`, `surface.page`, `interactive.primary`, etc.) resolve to values in that palette.
19
23
 
@@ -25,12 +29,19 @@ import colors from "@dataimago/tokens/colors";
25
29
 
26
30
  // In CSS:
27
31
  // @import "@dataimago/tokens/css";
32
+
33
+ // Tooling / agents:
34
+ // import dtcg from "@dataimago/tokens/dtcg";
28
35
  ```
29
36
 
30
37
  ## Authoring
31
38
 
32
39
  Edit JSON under `src/` — this package is the source of truth. Run `pnpm --filter @dataimago/tokens build` to regenerate `dist/`.
33
40
 
41
+ CSS-emitting files (`colors.json`, `theme-colors.json`, `typography.json`, `spacing.json`, `effects.json`) are structurally validated before build output is emitted. `frequent.json` and `components.json` remain metadata/reference manifests in this alpha line; do not treat them as normalized CSS-emitting token files until a migration records that decision.
42
+
43
+ The current CSS custom property set is protected by `tools/check-css-vars.mjs` so Level-1 and Level-2 consumers do not lose variables during tightening work.
44
+
34
45
  ## Versioning
35
46
 
36
47
  Changes go through [Changesets](https://github.com/changesets/changesets). Any palette or semantic-token change is at least a minor version bump in alpha.
package/dist/index.cjs CHANGED
@@ -3,19 +3,19 @@ const tokens = {
3
3
  "color": {
4
4
  "surface": {
5
5
  "page": {
6
- "light": "#fdf9f2",
7
- "dark": "#0c0a09",
8
- "description": "Main page background — cream-100 (light) / ink-900 (dark)"
6
+ "light": "#ededeb",
7
+ "dark": "#141410",
8
+ "description": "Main page background — warm-grey (light) / ink-900 (dark)"
9
9
  },
10
10
  "page-scrolled": {
11
- "light": "#fdf9f2",
12
- "dark": "#0c0a09",
11
+ "light": "#ededeb",
12
+ "dark": "#141410",
13
13
  "description": "Page background when navbar shrinks — matches page"
14
14
  },
15
15
  "elevated": {
16
- "light": "#fefdfb",
16
+ "light": "#f7f7f6",
17
17
  "dark": "#1c1917",
18
- "description": "Cards, modals, dropdowns — one step above page"
18
+ "description": "Cards, modals, dropdowns — one step above page (stone-50 on stone-100 / ink-800 on stone-900-aligned dark)"
19
19
  },
20
20
  "sunken": {
21
21
  "light": "#faf2e3",
@@ -23,13 +23,13 @@ const tokens = {
23
23
  "description": "Input fields, code blocks — below page level"
24
24
  },
25
25
  "navbar": {
26
- "light": "#fdf9f2",
27
- "dark": "#0c0a09",
26
+ "light": "#ededeb",
27
+ "dark": "#141410",
28
28
  "description": "Navbar background — matches page in both themes"
29
29
  },
30
30
  "navbar-scrolled": {
31
- "light": "#fdf9f2",
32
- "dark": "#0c0a09",
31
+ "light": "#ededeb",
32
+ "dark": "#141410",
33
33
  "description": "Navbar when scrolled/shrunk — matches page-scrolled"
34
34
  },
35
35
  "code": {
@@ -67,19 +67,19 @@ const tokens = {
67
67
  },
68
68
  "content": {
69
69
  "primary": {
70
- "light": "#0c0a09",
70
+ "light": "#14140f",
71
71
  "dark": "#fdf9f2",
72
- "description": "Primary text — ink-900 on cream-100 (light) / cream-100 on ink-900 (dark)"
72
+ "description": "Primary text — stone-900 on stone-100 (light, matches presentation-theme.scss exactly) / paper-100 on dark surface"
73
73
  },
74
74
  "secondary": {
75
- "light": "#57534e",
75
+ "light": "#6a6a6a",
76
76
  "dark": "#a8a29e",
77
- "description": "Secondary text — medium emphasis on both themes"
77
+ "description": "Secondary text — WCAG-AA on all light surfaces (≥4.6:1, darkened from stone-500 #7c7c7c which was 3.6:1) / ink-300 on dark"
78
78
  },
79
79
  "muted": {
80
- "light": "#78716c",
81
- "dark": "#78716c",
82
- "description": "Muted text — captions, meta info"
80
+ "light": "#706964",
81
+ "dark": "#89827d",
82
+ "description": "Muted text — captions, meta info. WCAG-AA on all surfaces in both modes (light ≥4.6:1, dark ≥4.6:1; darkened/lightened from #78716c which failed AA on page)"
83
83
  },
84
84
  "inverse": {
85
85
  "light": "#fdf9f2",
@@ -89,23 +89,23 @@ const tokens = {
89
89
  },
90
90
  "interactive": {
91
91
  "primary": {
92
- "light": "#d46820",
93
- "dark": "#f0a467",
94
- "description": "Primary interactive — copper-500 on light, copper-300 on dark"
92
+ "light": "#434739",
93
+ "dark": "#98a384",
94
+ "description": "Primary interactive — forest-700 on light (7.75:1 on stone-100, white text 9.0:1 — passes WCAG AAA) / forest-300 on dark"
95
95
  },
96
96
  "hover": {
97
- "light": "#b55017",
98
- "dark": "#ea8441",
99
- "description": "Hover state — copper-600 on light, copper-400 on dark"
97
+ "light": "#363a2e",
98
+ "dark": "#7e8a6b",
99
+ "description": "Hover state — forest-800 on light (slightly darker than canonical) / forest-400 on dark"
100
100
  },
101
101
  "accent": {
102
- "light": "#b54a27",
103
- "dark": "#b54a27",
104
- "description": "Accent — emancipation copper-red; moral-weight color"
102
+ "light": "#1f618d",
103
+ "dark": "#3498DB",
104
+ "description": "Accent — sky-blue moral-weight color (iteration-2). Light: accent-500 (5.68:1 on stone-100, AA with headroom; 6.66:1 white-on-color for backplates). Dark: accent-300 (5.41:1 on stone-900, AA with headroom — closes the iteration-1 dark-mode AA-large-only contrast gap). FIRST instance of mode-aware role mapping in the system: light and dark point to different palette stops rather than the same hex. The scale-name `accent` is preserved across iteration-1 → iteration-2 (consumer-side text-accent-500 keeps working). See wiki/decisions/iteration-2-blue-accent.md for the dialectical reframe of the iteration-1 emancipation-copper framing (sky/horizon/Enlightenment-blueprint/Reformation-Protestant traditions vs. Western-traffic-coding STOP-red)."
105
105
  },
106
106
  "focus": {
107
- "light": "#d46820",
108
- "dark": "#f0a467",
107
+ "light": "#434739",
108
+ "dark": "#98a384",
109
109
  "description": "Focus indicators — mirrors interactive.primary"
110
110
  }
111
111
  },
@@ -120,6 +120,32 @@ const tokens = {
120
120
  "dark": "#2980B9",
121
121
  "description": "Presentation accent hover/emphasized variant — Flat-UI belize-hole"
122
122
  }
123
+ },
124
+ "vertical-accent": {
125
+ "$comment": "Per-vertical brand accents — each Level-2 property's identity color, distinct from the house interactive.accent (the L1 sky-blue hub color). Mode-aware (light/dark stops) like interactive.accent. Used for app icons, navbar-logo emphasis, and per-vertical theming. Contrast vs surface.page verified WCAG: light values are the on-light marks, dark values the on-dark marks. dataimago (L1 hub) intentionally has no vertical accent — it uses interactive.accent + ink.",
126
+ "dissertation": {
127
+ "light": "#B87333",
128
+ "dark": "#CE8E5C",
129
+ "description": "dissertation-ai — copper (manuscript / annotation / the human hand in the margin). Re-authored at higher P3 chroma (~+28% light / +27% dark) for a vivid wide-gamut pop; sRGB fallback unchanged so contrast is preserved. Light: 3.23:1 on stone-100 (AA-large; large-mark use). Dark: #CE8E5C 6.72:1 on ink-900 (AA)."
130
+ },
131
+ "rpkg": {
132
+ "light": "#3B82F6",
133
+ "dark": "#60A5FA",
134
+ "description": "rpkg-ai — engineering blue. Light: 3.14:1 on stone-100 (AA-large; large-mark use). Dark: #60A5FA 7.26:1 on ink-900 (AA)."
135
+ },
136
+ "SGPc": {
137
+ "light": "#5A6310",
138
+ "dark": "#B2BF22",
139
+ "description": "SGPc-ai — growth yellow-green (decided 2026-06-26). The brand value is the bright #B2BF22, but it fails contrast on light surfaces (1.73:1), so LIGHT mode uses the darkened companion #5A6310 (5.55:1 on stone-100 — full AA) and DARK mode uses the bright #B2BF22 (9.12:1 on ink-900). See wiki/decisions/sgpc-accent.md."
140
+ }
141
+ },
142
+ "brand-mark": {
143
+ "$comment": "Brand-MARK colors (the dataimago monogram / wordmark logo treatment) — distinct from the interactive.accent role. The signature monogram red is a wide-gamut PILOT (see wiki/decisions/p3-oklch-color.md). It is mode-aware for AA-normal headroom: a single red cannot clear 4.5:1 on BOTH the light page (#ededeb) and ink-900 — light needs a deep red, dark needs a bright one. LIGHT uses the deepened #cc0000 (faithful P3, contrast-first); DARK keeps the vivid display-P3 primary red (#ff0000 fallback → color(display-p3 1 0 0)), where #ff0000 already passes AA-normal. Vivid override authored in tools/color-converter.mjs (dark-only for the red), emitted as a `#hex → color(display-p3 …)` cascade in the -p3 build.",
144
+ "red": {
145
+ "light": "#cc0000",
146
+ "dark": "#ff0000",
147
+ "description": "Signature monogram red — mode-aware for AA-normal. LIGHT: deepened #cc0000 (5.0:1 on stone-100 #ededeb / 5.5:1 on elevated — full AA-normal, safe at small sizes / as text). DARK: vivid #ff0000 fallback + display-P3 primary red (4.6:1 on ink-900 — AA-normal). Logo-mark color only — NOT the interactive accent (which is iteration-2 blue). Large navbar lift-wash marks keep the vivid display-P3 red (AA-large, large-mark use); this token is the AA-normal value for arbitrary/small mark reuse."
148
+ }
123
149
  }
124
150
  },
125
151
  "spacing": {
@@ -216,11 +242,22 @@ const tokens = {
216
242
  }
217
243
  },
218
244
  "transition": {
219
- "quick": "0.15s ease",
220
- "standard": "0.25s ease-in-out",
221
- "smooth": "0.35s ease",
222
- "logo": "0.3s",
223
- "description": "Animation durations - consistent timing"
245
+ "quick": {
246
+ "value": "0.15s ease",
247
+ "description": "Snap-fast micro-interactions — hover lifts, focus rings, immediate-feedback affordances"
248
+ },
249
+ "standard": {
250
+ "value": "0.25s ease-in-out",
251
+ "description": "Default for most state changes — toggles, button states, card hovers"
252
+ },
253
+ "smooth": {
254
+ "value": "0.35s ease",
255
+ "description": "Theme transitions, navbar collapse/expand, layout shifts that should feel deliberate"
256
+ },
257
+ "logo": {
258
+ "value": "0.3s",
259
+ "description": "Reserved for the dataimago wordmark / brand-mark transitions; do not borrow for general UI"
260
+ }
224
261
  },
225
262
  "card": {
226
263
  "background": "surface.elevated",
@@ -267,7 +304,7 @@ const tokens = {
267
304
  "font": {
268
305
  "family": {
269
306
  "sans": {
270
- "value": "Fira Sans, system-ui, sans-serif",
307
+ "value": "Noto Sans, Fira Sans, system-ui, sans-serif",
271
308
  "description": "Primary sans-serif font - body text"
272
309
  },
273
310
  "mono": {
@@ -275,16 +312,24 @@ const tokens = {
275
312
  "description": "Monospace font - code blocks and technical content"
276
313
  },
277
314
  "display": {
315
+ "value": "var(--font-family-display-editorial)",
316
+ "description": "Alias of display-editorial, and the load-bearing indirection .di-prose rebinds to swap a whole surface to the documentation family — do NOT rewrite heading rules to reference display-editorial directly or .di-prose stops working. Reference display-editorial/display-documentation explicitly only where the surface must NOT control the family (brand chrome, navbars). Its 0.3.0 removal was deferred 2026-08-10 under wiki/decisions/compatibility-contract.md (additive-only); removal now requires its own cleanup ADR. See wiki/decisions/display-family-split.md."
317
+ },
318
+ "display-editorial": {
278
319
  "value": "Josefin Sans, system-ui, sans-serif",
279
- "description": "Display font - headings and prominent text"
320
+ "description": "Editorial display family — Josefin Sans, the brand voice. Used on surfaces where the heading does rhetorical/branding work: hero/landing, marketing, story showcases, slide titles. Default for unmarked headings. Pair with weight 500 (h1) per wiki/decisions/h1-weight-medium.md."
321
+ },
322
+ "display-documentation": {
323
+ "value": "Noto Sans, system-ui, sans-serif",
324
+ "description": "Documentation display family — Noto Sans, the wayfinding voice. Used on surfaces where the heading does structural work: API docs, foundation Section h2s, KNOWLEDGE.md/CLAUDE.md headings, long-form research subheadings. Activated by wrapping content in .di-prose; pair with weight 600 (semibold). See wiki/decisions/display-family-split.md."
280
325
  },
281
326
  "brand": {
282
327
  "value": "Futura, system-ui, sans-serif",
283
328
  "description": "Brand font - dataimago navbar and branding elements"
284
329
  },
285
330
  "math": {
286
- "value": "Fira Math, Fira Sans Math, system-ui, sans-serif",
287
- "description": "Mathematical notation font"
331
+ "value": "Noto Sans Math, Noto Sans, system-ui, sans-serif",
332
+ "description": "Mathematical notation font — Noto Sans Math for broad Unicode math coverage; harmonises with the Noto Sans body stack"
288
333
  }
289
334
  },
290
335
  "size": {
@@ -351,9 +396,10 @@ const tokens = {
351
396
  "description": "Bold — h1, strong emphasis"
352
397
  },
353
398
  "heading": {
399
+ "_invariant": "Heading-weight cluster — 400 floor for all Josefin Sans headings (see wiki/decisions/josefin-weight-floor.md). Sub-400 weights (200, 300) are bound to the opt-in .di-numeric-display utility class, never the default for prose-tier headings. Per wiki/decisions/h1-weight-medium.md the prose-correct band for editorial headings is 400–500.",
354
400
  "h1": {
355
- "value": "var(--font-weight-bold)",
356
- "description": "Heading h1 weight — bold (700). Top-level voice; carries the most typographic presence."
401
+ "value": "var(--font-weight-medium)",
402
+ "description": "Heading h1 weight — medium (500). Top-level editorial voice; preserves Josefin Sans's geometric character at hero scale (was bold/700 before iteration-1; see wiki/decisions/h1-weight-medium.md)."
357
403
  },
358
404
  "h2": {
359
405
  "value": "var(--font-weight-medium)",
@@ -408,6 +454,17 @@ const tokens = {
408
454
  "value": "1.625",
409
455
  "description": "Relaxed line height - long-form content"
410
456
  }
457
+ },
458
+ "feature-settings": {
459
+ "_invariant": "OpenType feature settings cluster — display rules apply lining figures (lnum) so numerals render uniform-baseline at heading scale; body rules retain old-style figures where they belong (in-line numerical mentions in prose). See wiki/decisions/display-lining-figures.md.",
460
+ "display": {
461
+ "value": "\"lnum\"",
462
+ "description": "Lining figures (lnum) for display headings — uniform baseline, uniform height; corrects Josefin Sans's old-style default figures that read as casual at editorial/title scale. Consumed by every CSS rule whose font-family resolves to --font-family-display, --font-family-display-editorial, or --font-family-display-documentation."
463
+ },
464
+ "body": {
465
+ "value": "\"normal\"",
466
+ "description": "Default figure style for body text — preserves Josefin's old-style figures where they harmonize with lowercase letterforms in continuous prose."
467
+ }
411
468
  }
412
469
  }
413
470
  };
package/dist/index.mjs CHANGED
@@ -3,19 +3,19 @@ const tokens = {
3
3
  "color": {
4
4
  "surface": {
5
5
  "page": {
6
- "light": "#fdf9f2",
7
- "dark": "#0c0a09",
8
- "description": "Main page background — cream-100 (light) / ink-900 (dark)"
6
+ "light": "#ededeb",
7
+ "dark": "#141410",
8
+ "description": "Main page background — warm-grey (light) / ink-900 (dark)"
9
9
  },
10
10
  "page-scrolled": {
11
- "light": "#fdf9f2",
12
- "dark": "#0c0a09",
11
+ "light": "#ededeb",
12
+ "dark": "#141410",
13
13
  "description": "Page background when navbar shrinks — matches page"
14
14
  },
15
15
  "elevated": {
16
- "light": "#fefdfb",
16
+ "light": "#f7f7f6",
17
17
  "dark": "#1c1917",
18
- "description": "Cards, modals, dropdowns — one step above page"
18
+ "description": "Cards, modals, dropdowns — one step above page (stone-50 on stone-100 / ink-800 on stone-900-aligned dark)"
19
19
  },
20
20
  "sunken": {
21
21
  "light": "#faf2e3",
@@ -23,13 +23,13 @@ const tokens = {
23
23
  "description": "Input fields, code blocks — below page level"
24
24
  },
25
25
  "navbar": {
26
- "light": "#fdf9f2",
27
- "dark": "#0c0a09",
26
+ "light": "#ededeb",
27
+ "dark": "#141410",
28
28
  "description": "Navbar background — matches page in both themes"
29
29
  },
30
30
  "navbar-scrolled": {
31
- "light": "#fdf9f2",
32
- "dark": "#0c0a09",
31
+ "light": "#ededeb",
32
+ "dark": "#141410",
33
33
  "description": "Navbar when scrolled/shrunk — matches page-scrolled"
34
34
  },
35
35
  "code": {
@@ -67,19 +67,19 @@ const tokens = {
67
67
  },
68
68
  "content": {
69
69
  "primary": {
70
- "light": "#0c0a09",
70
+ "light": "#14140f",
71
71
  "dark": "#fdf9f2",
72
- "description": "Primary text — ink-900 on cream-100 (light) / cream-100 on ink-900 (dark)"
72
+ "description": "Primary text — stone-900 on stone-100 (light, matches presentation-theme.scss exactly) / paper-100 on dark surface"
73
73
  },
74
74
  "secondary": {
75
- "light": "#57534e",
75
+ "light": "#6a6a6a",
76
76
  "dark": "#a8a29e",
77
- "description": "Secondary text — medium emphasis on both themes"
77
+ "description": "Secondary text — WCAG-AA on all light surfaces (≥4.6:1, darkened from stone-500 #7c7c7c which was 3.6:1) / ink-300 on dark"
78
78
  },
79
79
  "muted": {
80
- "light": "#78716c",
81
- "dark": "#78716c",
82
- "description": "Muted text — captions, meta info"
80
+ "light": "#706964",
81
+ "dark": "#89827d",
82
+ "description": "Muted text — captions, meta info. WCAG-AA on all surfaces in both modes (light ≥4.6:1, dark ≥4.6:1; darkened/lightened from #78716c which failed AA on page)"
83
83
  },
84
84
  "inverse": {
85
85
  "light": "#fdf9f2",
@@ -89,23 +89,23 @@ const tokens = {
89
89
  },
90
90
  "interactive": {
91
91
  "primary": {
92
- "light": "#d46820",
93
- "dark": "#f0a467",
94
- "description": "Primary interactive — copper-500 on light, copper-300 on dark"
92
+ "light": "#434739",
93
+ "dark": "#98a384",
94
+ "description": "Primary interactive — forest-700 on light (7.75:1 on stone-100, white text 9.0:1 — passes WCAG AAA) / forest-300 on dark"
95
95
  },
96
96
  "hover": {
97
- "light": "#b55017",
98
- "dark": "#ea8441",
99
- "description": "Hover state — copper-600 on light, copper-400 on dark"
97
+ "light": "#363a2e",
98
+ "dark": "#7e8a6b",
99
+ "description": "Hover state — forest-800 on light (slightly darker than canonical) / forest-400 on dark"
100
100
  },
101
101
  "accent": {
102
- "light": "#b54a27",
103
- "dark": "#b54a27",
104
- "description": "Accent — emancipation copper-red; moral-weight color"
102
+ "light": "#1f618d",
103
+ "dark": "#3498DB",
104
+ "description": "Accent — sky-blue moral-weight color (iteration-2). Light: accent-500 (5.68:1 on stone-100, AA with headroom; 6.66:1 white-on-color for backplates). Dark: accent-300 (5.41:1 on stone-900, AA with headroom — closes the iteration-1 dark-mode AA-large-only contrast gap). FIRST instance of mode-aware role mapping in the system: light and dark point to different palette stops rather than the same hex. The scale-name `accent` is preserved across iteration-1 → iteration-2 (consumer-side text-accent-500 keeps working). See wiki/decisions/iteration-2-blue-accent.md for the dialectical reframe of the iteration-1 emancipation-copper framing (sky/horizon/Enlightenment-blueprint/Reformation-Protestant traditions vs. Western-traffic-coding STOP-red)."
105
105
  },
106
106
  "focus": {
107
- "light": "#d46820",
108
- "dark": "#f0a467",
107
+ "light": "#434739",
108
+ "dark": "#98a384",
109
109
  "description": "Focus indicators — mirrors interactive.primary"
110
110
  }
111
111
  },
@@ -120,6 +120,32 @@ const tokens = {
120
120
  "dark": "#2980B9",
121
121
  "description": "Presentation accent hover/emphasized variant — Flat-UI belize-hole"
122
122
  }
123
+ },
124
+ "vertical-accent": {
125
+ "$comment": "Per-vertical brand accents — each Level-2 property's identity color, distinct from the house interactive.accent (the L1 sky-blue hub color). Mode-aware (light/dark stops) like interactive.accent. Used for app icons, navbar-logo emphasis, and per-vertical theming. Contrast vs surface.page verified WCAG: light values are the on-light marks, dark values the on-dark marks. dataimago (L1 hub) intentionally has no vertical accent — it uses interactive.accent + ink.",
126
+ "dissertation": {
127
+ "light": "#B87333",
128
+ "dark": "#CE8E5C",
129
+ "description": "dissertation-ai — copper (manuscript / annotation / the human hand in the margin). Re-authored at higher P3 chroma (~+28% light / +27% dark) for a vivid wide-gamut pop; sRGB fallback unchanged so contrast is preserved. Light: 3.23:1 on stone-100 (AA-large; large-mark use). Dark: #CE8E5C 6.72:1 on ink-900 (AA)."
130
+ },
131
+ "rpkg": {
132
+ "light": "#3B82F6",
133
+ "dark": "#60A5FA",
134
+ "description": "rpkg-ai — engineering blue. Light: 3.14:1 on stone-100 (AA-large; large-mark use). Dark: #60A5FA 7.26:1 on ink-900 (AA)."
135
+ },
136
+ "SGPc": {
137
+ "light": "#5A6310",
138
+ "dark": "#B2BF22",
139
+ "description": "SGPc-ai — growth yellow-green (decided 2026-06-26). The brand value is the bright #B2BF22, but it fails contrast on light surfaces (1.73:1), so LIGHT mode uses the darkened companion #5A6310 (5.55:1 on stone-100 — full AA) and DARK mode uses the bright #B2BF22 (9.12:1 on ink-900). See wiki/decisions/sgpc-accent.md."
140
+ }
141
+ },
142
+ "brand-mark": {
143
+ "$comment": "Brand-MARK colors (the dataimago monogram / wordmark logo treatment) — distinct from the interactive.accent role. The signature monogram red is a wide-gamut PILOT (see wiki/decisions/p3-oklch-color.md). It is mode-aware for AA-normal headroom: a single red cannot clear 4.5:1 on BOTH the light page (#ededeb) and ink-900 — light needs a deep red, dark needs a bright one. LIGHT uses the deepened #cc0000 (faithful P3, contrast-first); DARK keeps the vivid display-P3 primary red (#ff0000 fallback → color(display-p3 1 0 0)), where #ff0000 already passes AA-normal. Vivid override authored in tools/color-converter.mjs (dark-only for the red), emitted as a `#hex → color(display-p3 …)` cascade in the -p3 build.",
144
+ "red": {
145
+ "light": "#cc0000",
146
+ "dark": "#ff0000",
147
+ "description": "Signature monogram red — mode-aware for AA-normal. LIGHT: deepened #cc0000 (5.0:1 on stone-100 #ededeb / 5.5:1 on elevated — full AA-normal, safe at small sizes / as text). DARK: vivid #ff0000 fallback + display-P3 primary red (4.6:1 on ink-900 — AA-normal). Logo-mark color only — NOT the interactive accent (which is iteration-2 blue). Large navbar lift-wash marks keep the vivid display-P3 red (AA-large, large-mark use); this token is the AA-normal value for arbitrary/small mark reuse."
148
+ }
123
149
  }
124
150
  },
125
151
  "spacing": {
@@ -216,11 +242,22 @@ const tokens = {
216
242
  }
217
243
  },
218
244
  "transition": {
219
- "quick": "0.15s ease",
220
- "standard": "0.25s ease-in-out",
221
- "smooth": "0.35s ease",
222
- "logo": "0.3s",
223
- "description": "Animation durations - consistent timing"
245
+ "quick": {
246
+ "value": "0.15s ease",
247
+ "description": "Snap-fast micro-interactions — hover lifts, focus rings, immediate-feedback affordances"
248
+ },
249
+ "standard": {
250
+ "value": "0.25s ease-in-out",
251
+ "description": "Default for most state changes — toggles, button states, card hovers"
252
+ },
253
+ "smooth": {
254
+ "value": "0.35s ease",
255
+ "description": "Theme transitions, navbar collapse/expand, layout shifts that should feel deliberate"
256
+ },
257
+ "logo": {
258
+ "value": "0.3s",
259
+ "description": "Reserved for the dataimago wordmark / brand-mark transitions; do not borrow for general UI"
260
+ }
224
261
  },
225
262
  "card": {
226
263
  "background": "surface.elevated",
@@ -267,7 +304,7 @@ const tokens = {
267
304
  "font": {
268
305
  "family": {
269
306
  "sans": {
270
- "value": "Fira Sans, system-ui, sans-serif",
307
+ "value": "Noto Sans, Fira Sans, system-ui, sans-serif",
271
308
  "description": "Primary sans-serif font - body text"
272
309
  },
273
310
  "mono": {
@@ -275,16 +312,24 @@ const tokens = {
275
312
  "description": "Monospace font - code blocks and technical content"
276
313
  },
277
314
  "display": {
315
+ "value": "var(--font-family-display-editorial)",
316
+ "description": "Alias of display-editorial, and the load-bearing indirection .di-prose rebinds to swap a whole surface to the documentation family — do NOT rewrite heading rules to reference display-editorial directly or .di-prose stops working. Reference display-editorial/display-documentation explicitly only where the surface must NOT control the family (brand chrome, navbars). Its 0.3.0 removal was deferred 2026-08-10 under wiki/decisions/compatibility-contract.md (additive-only); removal now requires its own cleanup ADR. See wiki/decisions/display-family-split.md."
317
+ },
318
+ "display-editorial": {
278
319
  "value": "Josefin Sans, system-ui, sans-serif",
279
- "description": "Display font - headings and prominent text"
320
+ "description": "Editorial display family — Josefin Sans, the brand voice. Used on surfaces where the heading does rhetorical/branding work: hero/landing, marketing, story showcases, slide titles. Default for unmarked headings. Pair with weight 500 (h1) per wiki/decisions/h1-weight-medium.md."
321
+ },
322
+ "display-documentation": {
323
+ "value": "Noto Sans, system-ui, sans-serif",
324
+ "description": "Documentation display family — Noto Sans, the wayfinding voice. Used on surfaces where the heading does structural work: API docs, foundation Section h2s, KNOWLEDGE.md/CLAUDE.md headings, long-form research subheadings. Activated by wrapping content in .di-prose; pair with weight 600 (semibold). See wiki/decisions/display-family-split.md."
280
325
  },
281
326
  "brand": {
282
327
  "value": "Futura, system-ui, sans-serif",
283
328
  "description": "Brand font - dataimago navbar and branding elements"
284
329
  },
285
330
  "math": {
286
- "value": "Fira Math, Fira Sans Math, system-ui, sans-serif",
287
- "description": "Mathematical notation font"
331
+ "value": "Noto Sans Math, Noto Sans, system-ui, sans-serif",
332
+ "description": "Mathematical notation font — Noto Sans Math for broad Unicode math coverage; harmonises with the Noto Sans body stack"
288
333
  }
289
334
  },
290
335
  "size": {
@@ -351,9 +396,10 @@ const tokens = {
351
396
  "description": "Bold — h1, strong emphasis"
352
397
  },
353
398
  "heading": {
399
+ "_invariant": "Heading-weight cluster — 400 floor for all Josefin Sans headings (see wiki/decisions/josefin-weight-floor.md). Sub-400 weights (200, 300) are bound to the opt-in .di-numeric-display utility class, never the default for prose-tier headings. Per wiki/decisions/h1-weight-medium.md the prose-correct band for editorial headings is 400–500.",
354
400
  "h1": {
355
- "value": "var(--font-weight-bold)",
356
- "description": "Heading h1 weight — bold (700). Top-level voice; carries the most typographic presence."
401
+ "value": "var(--font-weight-medium)",
402
+ "description": "Heading h1 weight — medium (500). Top-level editorial voice; preserves Josefin Sans's geometric character at hero scale (was bold/700 before iteration-1; see wiki/decisions/h1-weight-medium.md)."
357
403
  },
358
404
  "h2": {
359
405
  "value": "var(--font-weight-medium)",
@@ -408,6 +454,17 @@ const tokens = {
408
454
  "value": "1.625",
409
455
  "description": "Relaxed line height - long-form content"
410
456
  }
457
+ },
458
+ "feature-settings": {
459
+ "_invariant": "OpenType feature settings cluster — display rules apply lining figures (lnum) so numerals render uniform-baseline at heading scale; body rules retain old-style figures where they belong (in-line numerical mentions in prose). See wiki/decisions/display-lining-figures.md.",
460
+ "display": {
461
+ "value": "\"lnum\"",
462
+ "description": "Lining figures (lnum) for display headings — uniform baseline, uniform height; corrects Josefin Sans's old-style default figures that read as casual at editorial/title scale. Consumed by every CSS rule whose font-family resolves to --font-family-display, --font-family-display-editorial, or --font-family-display-documentation."
463
+ },
464
+ "body": {
465
+ "value": "\"normal\"",
466
+ "description": "Default figure style for body text — preserves Josefin's old-style figures where they harmonize with lowercase letterforms in continuous prose."
467
+ }
411
468
  }
412
469
  }
413
470
  };