remarque-tokens 0.21.0 → 0.22.0

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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to `remarque-tokens` are documented here. Token value
4
4
  changes always state the design rationale — downstream sites pin against
5
5
  these entries when syncing.
6
6
 
7
+ ## 0.22.0 — 2026-07-23
8
+
9
+ Dataviz categorical tokens — bridge-derived, golden-gated (closes #94). The syntax-palette pattern's fourth application, closing the "Dataviz Tokens" section's long-standing "guidance, not a palette" gap now that the upstream dataset ships a per-theme `dataviz` block (`@williamzujkowski/oklch-terminal-themes` 0.5.0).
10
+
11
+ ### Added
12
+ - **6 `--color-viz-1..6` palette-tier slots**, hand-authored in both themes from `remarque-light`/`remarque-dark`'s `dataviz.categorical` block, serialized as round numbers (identity/serialization contract, same as `--color-syntax-*`). 6 slots, not 8 — the dataset's own floor (516 of 633 corpus themes ship exactly 6). Every slot ≥ **3:1** against `--color-bg` in both themes — Carbon's mark-on-background line, not text's 4.5:1 (marks are read at a glance, not continuously like prose). Added to `tokens-palette.css`'s `:root`, `@media (prefers-color-scheme: dark)`, `[data-theme="dark"]`/`:root.dark`, and the demo's `[data-theme="light"]` mirror.
13
+ - **`remarque-theme` dataviz derivation.** Derives all 6 slots from the source theme's own `dataviz.categorical` array (first 6 entries, dataset order, per theme independently — order is **not** reconciled hue-for-hue across a light/dark pair; verified the upstream order itself differs between `remarque-light` and `remarque-dark`, consistent with this file's existing "dark mode is independently tuned, not inverted" stance). Same keep-if-passing-else-solve pattern as every other slot, chroma capped at 0.14. **Fallback:** an installed dataset older than 0.5.0 (still legal under the `>=0.1.0` peerDependencies floor) has no `dataviz` block — `remarque-theme` errors loudly naming the missing field and the upgrade path rather than synthesizing a guessed ramp from raw ANSI colors (the upstream categorical-selection algorithm is undocumented; guessing at it could silently diverge from a real 0.5.0+ dataset).
14
+ - **`remarque-theme --dataviz`** (optional, judgment-call item). Emits `--viz-sequential-N`/`--viz-diverging-N` custom properties from the dataset's `dataviz.sequential`/`.diverging` ramps — gamut-clamped but NOT contrast-solved (a sequential ramp's low end is designed to sit near `--color-bg`) and NOT audited/golden-gated like `--color-viz-1..6` (these are per-use ramps, not identity tokens). Purely additive: omitting the flag leaves the derived `--color-*` output byte-identical to before.
15
+ - **Audit extension** (`scripts/audit.mjs`, `scripts/theme.mjs` self-verify) — 6 new `CHECKS` pairings (`color-viz-1..6` vs `color-bg` at 3.0). `scripts/test-audit.mjs` fixtures updated with the 6 slots plus a must-fail case (`viz-slot-fails.css`) proving the pairings are actually wired in.
16
+ - **Golden gate** (`scripts/palette-golden.mjs`) extended to the 6 new slots — ΔE2000 = 0.000 in both themes (the hand-authored values are literal transcriptions of the dataset's own numbers, unlike the solved slots elsewhere in the palette).
17
+ - **REMARQUE.md "Dataviz Tokens"** rewritten: the 6 shipped tokens with light/dark values and ratios, the 3:1 mark-threshold rationale, a mandatory non-color-redundancy rule (shape/pattern/label — never color alone, per Carbon and tsundoku's precedent), and a documented recipe for sequential/diverging ramps (direct JSON consumption from the dataset, or `remarque-theme --dataviz`). New Enforcement Checklist line.
18
+ - **Demo** (`site/src/pages/tokens.astro`): a "Dataviz" section rendering all 6 swatches in both themes with contrast ratios and a shape-coded legend (circle/square/triangle) demonstrating the non-color-redundancy rule directly. Placed on `tokens.astro`, which carries zero visual-regression baselines — this ships with no new/changed screenshot baselines.
19
+ - **Repin**: `@williamzujkowski/oklch-terminal-themes` devDependency 0.4.0 → 0.5.0 (exact). Corpus test (`scripts/test-theme.mjs`) still 65/65 pairs passing — the dataset gained fields (`dataviz` on every theme), not themes, matching the repin pattern's usual shape.
20
+
7
21
  ## 0.21.0 — 2026-07-23
8
22
 
9
23
  Forced-colors (Windows High Contrast Mode) + `prefers-contrast: more` support (closes #93) — the ratified program's highest-value accessibility item.
package/REMARQUE.md CHANGED
@@ -256,14 +256,45 @@ Reference/Docs and Project Dossier pages often need to show several screenshots
256
256
 
257
257
  ## Dataviz Tokens
258
258
 
259
- Charts are not yet a first-class Remarque surface, but tools built with the system inevitably need them. Rather than inventing chart-specific tokens, map dataviz elements onto vocabulary that already exists, so a chart survives a palette swap the same way prose and chrome do:
259
+ Charts are a first-class Remarque surface: `tokens-palette.css` ships 6 hand-authored `--color-viz-*` categorical slots, bridge-derived and golden-gated against the upstream `remarque-light`/`remarque-dark` themes' `dataviz.categorical` block the same way the rest of the default palette is (issue #94 — the syntax-palette pattern's fourth application). The rest of a chart still maps onto vocabulary that already exists, so it survives a palette swap the same way prose and chrome do:
260
260
 
261
261
  - **Grid lines** use `--color-border` — never a separate "chart gray." Grid lines are structural, not decorative, and already have a sanctioned quiet role.
262
262
  - **Axis text and tick labels** use `--font-mono` at `--text-meta`, `--color-muted` — the same register as metadata rows and captions, never the body or display face.
263
- - **Categorical color ramp** is derived, not hand-picked: rotate hue at the accent's lightness/chroma steps — the same discipline "Changing the Accent Hue" already prescribes (fix lightness, vary hue, reduce chroma until every value clears the gamut) — producing a small hue family distinct from `--color-accent` itself, so chart color is never mistaken for the system's one interactive signal.
264
- - That ramp must be **audit-validated**, exactly like a palette override: every ramp color needs to clear gamut and 4.5:1 contrast against `--color-bg`/`--color-surface` in both themes before it ships.
263
+ - **Categorical color** uses `--color-viz-1` through `--color-viz-6` (below) — never `--color-accent`, so chart color is never mistaken for the system's one interactive signal.
265
264
 
266
- **Precedent:** [tsundoku](https://github.com/williamzujkowski/tsundoku) built the underlying pattern already — its `DESIGN-NOTES.md` documents an 8-hue "orthogonal category system" (`--pop-pink`/`--pop-blue`/`--pop-green`/etc.), kept deliberately separate from `--color-accent` and used for content taxonomy (book categories, reading status) rather than interactivity. It held those hues to roughly the same lightness/contrast discipline as its audited palette, but by its own admission the hues were **never run through `remarque-audit`'s `CHECKS` array** — that is the one gap this guidance closes. A dataviz ramp should follow tsundoku's separation-from-accent instinct, but unlike its precedent, it must pass the audit before it ships.
265
+ ### Slots
266
+
267
+ 6 slots, not 8 — the upstream dataset's own floor: 516 of the 633 corpus themes ship exactly 6 `dataviz.categorical` entries, so shipping 8 would leave 2 unfillable on most themes. Every slot is verified ≥ **3:1** against `--color-bg` in both themes — Carbon's mark-on-background line, not text's 4.5:1. The distinction is deliberate: a chart mark is a small area read at a glance and identified by hue difference from its neighbors, not a string of characters read continuously letter-by-letter, so it gets the WCAG 1.4.11 non-text threshold (the same one `--color-border-bold` already uses for functional borders), not 1.4.3's text threshold.
268
+
269
+ | Slot | Light | Dark |
270
+ |---|---|---|
271
+ | `--color-viz-1` | `oklch(0.538 0.121 250.5)` — 4.74:1 | `oklch(0.708 0.129 249.5)` — 7.53:1 |
272
+ | `--color-viz-2` | `oklch(0.541 0.111 85.5)` — 4.73:1 | `oklch(0.712 0.13 85.3)` — 7.54:1 |
273
+ | `--color-viz-3` | `oklch(0.524 0.12 24.6)` — 5.35:1 | `oklch(0.724 0.129 310.2)` — 7.55:1 |
274
+ | `--color-viz-4` | `oklch(0.499 0.12 144.8)` — 5.32:1 | `oklch(0.725 0.129 25.4)` — 7.55:1 |
275
+ | `--color-viz-5` | `oklch(0.524 0.121 309.6)` — 5.33:1 | `oklch(0.696 0.129 144.9)` — 7.53:1 |
276
+ | `--color-viz-6` | `oklch(0.528 0.09 195.8)` — 4.74:1 | `oklch(0.697 0.119 194.8)` — 7.57:1 |
277
+
278
+ **Order is not reconciled hue-for-hue across the pair.** The upstream `dataviz.categorical` order for `remarque-light` (blue, yellow, red, green, purple, cyan) does not match `remarque-dark`'s (blue, yellow, purple, red, green, cyan) — verified directly against the shipped dataset. `--color-viz-3` is reddish in light mode and purplish in dark mode. This is the same stance the rest of this file already takes for every other slot: **dark mode is independently tuned, not an inversion** — it is not a bug that a toggle recolors series 3, any more than it is a bug that `--color-fg`'s exact hue differs by theme.
279
+
280
+ **Non-color redundancy (Carbon precedent, mandatory):** never use `--color-viz-*` as the *sole* distinguisher between series. Pair every categorical color with a second channel — a distinct marker shape (circle/square/triangle), a line dash pattern, or a direct end-of-line/legend label — so the chart still communicates under color-vision deficiency or `forced-colors: active` (which the marks themselves do not attempt to survive; see "Forced Colors & Contrast Preferences" for what *is* covered). This mirrors [tsundoku](https://github.com/williamzujkowski/tsundoku)'s `DESIGN-NOTES.md`, which documents an 8-hue "orthogonal category system" kept deliberately separate from its accent — the categorical slots above formalize that instinct as an audited, bridge-derived token set instead of a hand-picked one-off.
281
+
282
+ ### Derivation
283
+
284
+ `remarque-theme` derives all 6 slots from the source theme's own `dataviz.categorical` array (dataset 0.5.0+) — first 6 entries, in the dataset's order, same keep-if-passing-else-solve pattern as every other slot, targeted at `--color-bg` (not `--color-surface`) at 3:1. Chroma is capped at 0.14, the same ceiling as `--color-accent`/`--color-syntax-*`. `scripts/palette-golden.mjs` extends its ΔE2000 ≤ 2.0 gate to all 6 slots.
285
+
286
+ **Fallback (dataset predates the field):** an installed `@williamzujkowski/oklch-terminal-themes` older than 0.5.0 — still legal under this package's `>=0.1.0` peerDependencies floor — has no `dataviz` block at all. `remarque-theme` does **not** attempt to synthesize a categorical ramp from raw ANSI colors in that case: the upstream categorical-selection algorithm is undocumented, and approximating it could silently diverge from what a real 0.5.0+ dataset ships. It errors loudly instead, naming the missing field and the upgrade path — the smaller, honest option over a plausible-looking guess.
287
+
288
+ ### Sequential / diverging ramps
289
+
290
+ Sequential and diverging ramps are **not** shipped as tokens — a fixed `--color-viz-seq-N` token set would misrepresent them, because a ramp's *step count* is dataset-defined (6-8, whatever the source theme carries) and a specific ramp is chosen per-use (a heatmap's sequential scale, a signed-difference chart's diverging scale), not once per theme like a categorical identity slot. Two ways to consume them:
291
+
292
+ 1. **Direct JSON consumption** — read the dataset's `dataviz.sequential`/`dataviz.diverging` arrays straight from the installed package:
293
+ ```js
294
+ const { sequential, diverging } = require('@williamzujkowski/oklch-terminal-themes/themes/remarque-light.json').dataviz;
295
+ ```
296
+ Each entry carries `oklch` (structured `{l, c, h}`) and `oklchCss` (a ready-to-use `oklch(...)` string) — no parsing required.
297
+ 2. **`remarque-theme --dataviz`** — emits `--viz-sequential-1..N` and `--viz-diverging-1..N` custom properties (`N` = the source theme's own ramp length) alongside the normal derived palette. Gamut-clamped but **not** contrast-solved (a sequential ramp's low end is designed to sit near `--color-bg`; solving it to a contrast floor would defeat the ramp) — advisory output, not audited or golden-gated like `--color-viz-1..6`. A future `remarque-audit --json`/`remarque-theme --json` could expose the same arrays as structured output; today, `--dataviz`'s CSS-property emission and direct JSON consumption above cover the need.
267
298
 
268
299
  ---
269
300
 
@@ -491,6 +522,7 @@ Every PR that ships Remarque pages MUST pass (`npm run audit` automates the colo
491
522
  - [ ] Body line-height ≥ 1.5 (Remarque target: 1.75).
492
523
  - [ ] Every `--color-syntax-*` slot ≥ 4.5:1 against `--color-code-bg` in both themes (see "Syntax Highlighting").
493
524
  - [ ] Every `--color-error`/`--color-success`/`--color-warning`/`--color-disabled` ≥ 4.5:1 against `--color-bg` **and** `--color-surface` in both themes; every `-subtle` background keeps `--color-fg` ≥ 4.5:1 on it (see "State Colors"). State colors used only for feedback moments, never decoration.
525
+ - [ ] Every `--color-viz-*` slot ≥ 3:1 against `--color-bg` in both themes (see "Dataviz Tokens") — the mark-on-background line, not text's 4.5:1. Never used as the sole distinguisher between series — pair with a non-color redundancy (shape, pattern, or a direct label).
494
526
  - [ ] No bare `z-index` number in consumer CSS — reference `tokens-core.css`'s `--z-*` scale (see "Stacking").
495
527
  - [ ] Any new color-only affordance (hover/focus signal, state indicator, structural divider) is checked against forced-colors mode — does it survive, or does it need a `@media (forced-colors: active)` fallback? (see "Forced Colors & Contrast Preferences")
496
528
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remarque-tokens",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "engines": {
5
5
  "node": ">=18"
6
6
  },
@@ -32,7 +32,7 @@
32
32
  "drift": "node scripts/drift-check.mjs"
33
33
  },
34
34
  "devDependencies": {
35
- "@williamzujkowski/oklch-terminal-themes": "0.4.0",
35
+ "@williamzujkowski/oklch-terminal-themes": "0.5.0",
36
36
  "ajv": "^8.20.0",
37
37
  "culori": "^4.0.2"
38
38
  },
package/scripts/audit.mjs CHANGED
@@ -168,6 +168,15 @@ const CHECKS = [
168
168
  ['color-fg', 'color-error-subtle', 4.5, 'state: fg on error-subtle banner bg'],
169
169
  ['color-fg', 'color-success-subtle', 4.5, 'state: fg on success-subtle banner bg'],
170
170
  ['color-fg', 'color-warning-subtle', 4.5, 'state: fg on warning-subtle banner bg'],
171
+ // Dataviz categorical slots (issue #94) — 3:1 is Carbon's mark-on-
172
+ // background line, not text's 4.5:1: these are chart marks read at a
173
+ // glance, not prose. See REMARQUE.md "Dataviz Tokens".
174
+ ['color-viz-1', 'color-bg', 3.0, 'dataviz: categorical 1 (mark)'],
175
+ ['color-viz-2', 'color-bg', 3.0, 'dataviz: categorical 2 (mark)'],
176
+ ['color-viz-3', 'color-bg', 3.0, 'dataviz: categorical 3 (mark)'],
177
+ ['color-viz-4', 'color-bg', 3.0, 'dataviz: categorical 4 (mark)'],
178
+ ['color-viz-5', 'color-bg', 3.0, 'dataviz: categorical 5 (mark)'],
179
+ ['color-viz-6', 'color-bg', 3.0, 'dataviz: categorical 6 (mark)'],
171
180
  ];
172
181
 
173
182
  for (const [themeName, decls] of [['light', lightDecls], ['dark', darkDecls]]) {
package/scripts/theme.mjs CHANGED
@@ -22,6 +22,10 @@
22
22
  * meaningful "theme's own value" to preserve. The 9 --color-syntax-*
23
23
  * slots (issue #53) are derived straight from the theme's 16 ANSI
24
24
  * colors instead — see the "Syntax-highlighting slots" section below.
25
+ * The 6 --color-viz-* categorical slots (issue #94) are derived from
26
+ * the dataset's own dataviz.categorical block (0.5.0+) instead of ANSI —
27
+ * see "Dataviz categorical slots" below, including its dataset-predates-
28
+ * the-field fallback (a clean error, not a synthesized guess).
25
29
  * Output passes remarque-audit BY CONSTRUCTION — this script
26
30
  * self-verifies the same pairings before it will emit anything.
27
31
  *
@@ -50,13 +54,18 @@ try {
50
54
  OWN_VERSION = JSON.parse(readFileSync(join(HERE, '..', 'package.json'), 'utf8')).version;
51
55
  } catch { /* provenance is best-effort */ }
52
56
 
53
- const USAGE = `usage: remarque-theme <light-slug> [--dark <dark-slug>] [-o out.css] [--scope <name>]
57
+ const USAGE = `usage: remarque-theme <light-slug> [--dark <dark-slug>] [-o out.css] [--scope <name>] [--dataviz]
54
58
 
55
59
  <light-slug> slug of a theme with isDark=false in ${PKG_SPEC}
56
60
  --dark <slug> slug of a theme with isDark=true; defaults to the light
57
61
  theme's "counterpart" from the dataset (0.2.0+) when it
58
62
  has one, and is required when it doesn't
59
63
  -o, --output write the derived palette here instead of stdout
64
+ --dataviz also emit --viz-sequential-N / --viz-diverging-N custom
65
+ properties from the dataset's dataviz.sequential/
66
+ .diverging ramps (0.5.0+ only; issue #94). These are
67
+ advisory, per-use ramps, NOT identity tokens — they are
68
+ not audited or golden-gated like --color-viz-1..6
60
69
  --scope <name> emit under [data-palette="<name>"] instead of :root — for
61
70
  the palette-deck module (remarque-tokens/deck), where
62
71
  several generated palettes coexist in one stylesheet and
@@ -75,13 +84,14 @@ function die(msg) {
75
84
  /* ── CLI args ─────────────────────────────────────────────────────── */
76
85
 
77
86
  const argv = process.argv.slice(2);
78
- let darkSlug, outFile, scopeName;
87
+ let darkSlug, outFile, scopeName, dataviz = false;
79
88
  const positional = [];
80
89
  for (let i = 0; i < argv.length; i++) {
81
90
  const a = argv[i];
82
91
  if (a === '--dark') darkSlug = argv[++i];
83
92
  else if (a === '-o' || a === '--output') outFile = argv[++i];
84
93
  else if (a === '--scope') scopeName = argv[++i];
94
+ else if (a === '--dataviz') dataviz = true;
85
95
  else if (a === '-h' || a === '--help') { console.log(USAGE); process.exit(0); }
86
96
  else positional.push(a);
87
97
  }
@@ -506,6 +516,54 @@ function deriveStateSubtle(bgL, H, chromaCap, fg, dir) {
506
516
  return [L, c, r1(H)];
507
517
  }
508
518
 
519
+ /* ── Dataviz categorical slots (issue #94) ────────────────────────────
520
+ * The upstream dataset (0.5.0+) ships a per-theme `dataviz.categorical`
521
+ * array — 6-8 ordered ColorValues already selected upstream for mutual
522
+ * distinguishability, the same kind of "this domain already carries the
523
+ * personality" argument as the syntax slots' ANSI colors. --color-viz-1
524
+ * through --color-viz-6 take the first 6 (633/633 corpus themes ship at
525
+ * least 6; 516 ship exactly 6 — that floor is why 6 ships, not 8), kept-
526
+ * if-passing/solved the same way as every other slot, verified against
527
+ * --color-bg (not --color-surface) at 3:1 — Carbon's mark-vs-background
528
+ * line, not text's 4.5:1 (REMARQUE.md "Dataviz Tokens": these are chart
529
+ * marks read at a glance, not prose read continuously).
530
+ *
531
+ * Order is taken as-is from the dataset, per theme independently — it is
532
+ * NOT reconciled to match hue-for-hue between a light/dark pair (the
533
+ * upstream categorical order does not always agree across a pair's two
534
+ * halves; verified empirically against remarque-light/remarque-dark).
535
+ * This is the same "dark mode is independently tuned, not inverted"
536
+ * stance the rest of this file already takes for every other slot.
537
+ *
538
+ * Fallback: a dataset older than 0.5.0 (still legal under this package's
539
+ * `>=0.1.0` peerDependencies floor) has no dataviz block at all. Rather
540
+ * than reverse-engineer the upstream's undocumented categorical-
541
+ * selection algorithm from raw ANSI colors — which could silently
542
+ * diverge from what a real 0.5.0+ dataset actually ships — this errors
543
+ * loudly and points at the upgrade. The smaller, honest option.
544
+ */
545
+
546
+ const VIZ_CHROMA_CAP = 0.14; // same restrained ceiling as accent/syntax/state
547
+ const VIZ_TARGET = 3.0; // Carbon's mark-on-background line
548
+
549
+ function deriveVizCategorical(t, label, slug, bg, dir) {
550
+ const cats = t.dataviz && Array.isArray(t.dataviz.categorical) ? t.dataviz.categorical : null;
551
+ if (!cats || cats.length < 6) {
552
+ die(
553
+ `${label} theme "${slug}" has no dataviz.categorical (or fewer than 6 entries) in the installed ` +
554
+ `${PKG_SPEC}@${themesVersion} — upgrade the dataset to >=0.5.0 (dataviz.categorical landed there; issue #94) ` +
555
+ `and retry.\nremarque-theme does NOT synthesize a categorical ramp from raw ANSI colors as a fallback — the ` +
556
+ `upstream categorical-selection algorithm is undocumented, so approximating it here could silently diverge ` +
557
+ `from what a real 0.5.0+ dataset ships. Erroring with an upgrade instruction is the smaller, honest option.`
558
+ );
559
+ }
560
+ return cats.slice(0, 6).map((entry, i) => {
561
+ const [L0, C0, H0] = validateOklch(entry.oklch, `${label} dataviz.categorical[${i}]`);
562
+ const C = Math.min(C0, VIZ_CHROMA_CAP);
563
+ return keepOrSolve(L0, C, H0, bg, VIZ_TARGET, dir);
564
+ });
565
+ }
566
+
509
567
  /* ── LIGHT derivation ─────────────────────────────────────────────── */
510
568
 
511
569
  function deriveLight(t) {
@@ -553,6 +611,9 @@ function deriveLight(t) {
553
611
  const stateErrorSubtle = deriveStateSubtle(bg[0], stateError[2], 0.02, fg, 'darker');
554
612
  const stateSuccessSubtle = deriveStateSubtle(bg[0], stateSuccess[2], 0.02, fg, 'darker');
555
613
  const stateWarningSubtle = deriveStateSubtle(bg[0], stateWarning[2], 0.02, fg, 'darker');
614
+ // Dataviz categorical slots (issue #94) — see deriveVizCategorical above.
615
+ // Verified against --color-bg (not surface) at 3:1.
616
+ const viz = deriveVizCategorical(t, 'light', lightSlug, bg, 'darker');
556
617
  const raw = {
557
618
  'color-bg': bg,
558
619
  'color-bg-subtle': [r3(bg[0] - 0.02), bg[1], bg[2]],
@@ -585,6 +646,12 @@ function deriveLight(t) {
585
646
  'color-warning': stateWarning,
586
647
  'color-warning-subtle': stateWarningSubtle,
587
648
  'color-disabled': { ref: 'color-muted' },
649
+ 'color-viz-1': viz[0],
650
+ 'color-viz-2': viz[1],
651
+ 'color-viz-3': viz[2],
652
+ 'color-viz-4': viz[3],
653
+ 'color-viz-5': viz[4],
654
+ 'color-viz-6': viz[5],
588
655
  };
589
656
  const tokens = {};
590
657
  for (const [name, v] of Object.entries(raw)) tokens[name] = v.ref ? `var(--${v.ref})` : fmt(v);
@@ -646,6 +713,9 @@ function deriveDark(t, accentHueLight) {
646
713
  const stateErrorSubtle = deriveStateSubtle(bg[0], stateError[2], 0.04, fg, 'lighter');
647
714
  const stateSuccessSubtle = deriveStateSubtle(bg[0], stateSuccess[2], 0.04, fg, 'lighter');
648
715
  const stateWarningSubtle = deriveStateSubtle(bg[0], stateWarning[2], 0.04, fg, 'lighter');
716
+ // Dataviz categorical slots (issue #94) — mirrors the light derivation,
717
+ // direction flipped ('lighter': marks on a dark bg).
718
+ const viz = deriveVizCategorical(t, 'dark', darkSlug, bg, 'lighter');
649
719
  const raw = {
650
720
  'color-bg': bg,
651
721
  'color-bg-subtle': surface,
@@ -678,6 +748,12 @@ function deriveDark(t, accentHueLight) {
678
748
  'color-warning': stateWarning,
679
749
  'color-warning-subtle': stateWarningSubtle,
680
750
  'color-disabled': { ref: 'color-muted' },
751
+ 'color-viz-1': viz[0],
752
+ 'color-viz-2': viz[1],
753
+ 'color-viz-3': viz[2],
754
+ 'color-viz-4': viz[3],
755
+ 'color-viz-5': viz[4],
756
+ 'color-viz-6': viz[5],
681
757
  };
682
758
  const tokens = {};
683
759
  for (const [name, v] of Object.entries(raw)) tokens[name] = v.ref ? `var(--${v.ref})` : fmt(v);
@@ -725,6 +801,12 @@ const CHECKS = [
725
801
  ['color-fg', 'color-error-subtle', 4.5, 'state: fg on error-subtle banner bg'],
726
802
  ['color-fg', 'color-success-subtle', 4.5, 'state: fg on success-subtle banner bg'],
727
803
  ['color-fg', 'color-warning-subtle', 4.5, 'state: fg on warning-subtle banner bg'],
804
+ ['color-viz-1', 'color-bg', 3.0, 'dataviz: categorical 1 (mark)'],
805
+ ['color-viz-2', 'color-bg', 3.0, 'dataviz: categorical 2 (mark)'],
806
+ ['color-viz-3', 'color-bg', 3.0, 'dataviz: categorical 3 (mark)'],
807
+ ['color-viz-4', 'color-bg', 3.0, 'dataviz: categorical 4 (mark)'],
808
+ ['color-viz-5', 'color-bg', 3.0, 'dataviz: categorical 5 (mark)'],
809
+ ['color-viz-6', 'color-bg', 3.0, 'dataviz: categorical 6 (mark)'],
728
810
  ];
729
811
 
730
812
  function resolveRaw(raw, name, seen = new Set()) {
@@ -765,6 +847,42 @@ const scopeNote = scopeName
765
847
  ? `\n *\n * Scoped for remarque-tokens/deck (--scope "${scopeName}") — this is one\n * palette among several coexisting in a stylesheet, switched at runtime\n * via [data-palette] rather than owning :root outright. Self-verification\n * below runs on the same derived [L,C,H] values regardless of scope; see\n * REMARQUE.md "Palette Deck" for the audit story on scoped output.`
766
848
  : '';
767
849
 
850
+ // --dataviz (issue #94, optional, judgment-call item 6): advisory
851
+ // --viz-sequential-N / --viz-diverging-N ramps straight from the
852
+ // dataset's dataviz block — gamut-clamped but NOT contrast-solved (a
853
+ // sequential ramp's low end is DESIGNED to sit near --color-bg; solving
854
+ // it to a contrast floor would defeat the ramp). Not audited, not
855
+ // golden-gated — see REMARQUE.md "Dataviz Tokens" for the recipe this
856
+ // flag implements and why these are ramps, not identity tokens.
857
+ function vizRamp(entries, prefix) {
858
+ return entries.map((e, i) => {
859
+ const [L0, C0, H0] = validateOklch(e.oklch, `${prefix}[${i}]`);
860
+ const L = r3(Math.min(1, Math.max(0, L0)));
861
+ return ` --${prefix}-${i + 1}: ${fmt([L, fitChroma(L, C0, H0), r1(H0)])};`;
862
+ }).join('\n');
863
+ }
864
+ let vizBlock = '';
865
+ if (dataviz) {
866
+ const { sequential: seqL, diverging: divL } = themeLight.dataviz || {};
867
+ const { sequential: seqD, diverging: divD } = themeDark.dataviz || {};
868
+ if (!seqL || !divL || !seqD || !divD) {
869
+ die(`--dataviz requires dataviz.sequential/.diverging in the installed ${PKG_SPEC}@${themesVersion} — upgrade to >=0.5.0 (issue #94) and retry.`);
870
+ }
871
+ vizBlock = `
872
+ /* --dataviz ramps (issue #94) — advisory, NOT audited/golden-gated like
873
+ * --color-viz-1..6 above. See REMARQUE.md "Dataviz Tokens". */
874
+ ${lightSelector} {
875
+ ${vizRamp(seqL, 'viz-sequential')}
876
+ ${vizRamp(divL, 'viz-diverging')}
877
+ }
878
+
879
+ ${darkSelector} {
880
+ ${vizRamp(seqD, 'viz-sequential')}
881
+ ${vizRamp(divD, 'viz-diverging')}
882
+ }
883
+ `;
884
+ }
885
+
768
886
  const css = `/*
769
887
  * Generated by remarque-theme (remarque-tokens ${OWN_VERSION}) — DO NOT HAND-EDIT.
770
888
  * Regenerate instead: ${regenCmd}
@@ -786,7 +904,7 @@ ${decls(light.tokens)}
786
904
  ${darkSelector} {
787
905
  ${decls(dark.tokens)}
788
906
  }
789
- `;
907
+ ${vizBlock}`;
790
908
 
791
909
  if (outFile) {
792
910
  writeFileSync(outFile, css);
@@ -16,7 +16,9 @@
16
16
  * - Colors: every replacement palette must pass the contrast audit
17
17
  * (npm run audit) — fg-muted ≥ 7:1, muted ≥ 4.5:1 on bg AND
18
18
  * surface, accent + accent-hover ≥ 4.5:1, border-bold ≥ 3:1,
19
- * every --color-syntax-* slot ≥ 4.5:1 on --color-code-bg,
19
+ * every --color-syntax-* slot ≥ 4.5:1 on --color-code-bg, every
20
+ * --color-viz-* slot ≥ 3:1 on --color-bg (Carbon's mark-on-
21
+ * background line, not text's 4.5:1 — see "Dataviz Tokens"),
20
22
  * in BOTH themes.
21
23
  * - Accent: change the hue by re-deriving accent/accent-hover/
22
24
  * accent-subtle/selection at the SAME lightness values used here,
@@ -117,6 +119,25 @@
117
119
  --color-warning: oklch(0.52 0.105 85); /* ANSI yellow — 5.17:1 bg / 5.02:1 surface */
118
120
  --color-warning-subtle: oklch(0.95 0.02 85); /* faint yellow banner background */
119
121
  --color-disabled: var(--color-muted); /* muted family, not ANSI-derived */
122
+
123
+ /* Dataviz categorical ramp (issue #94) — hand-authored from
124
+ remarque-light's dataviz.categorical block (scripts/theme.mjs),
125
+ serialized as round numbers (golden-gated: scripts/palette-golden.mjs).
126
+ 6 slots, not 8 — the dataset's own floor (516/633 corpus themes ship
127
+ exactly 6; shipping 8 would leave 2 unfillable on most themes). Every
128
+ slot ≥ 3:1 on --color-bg — Carbon's mark-on-background line, NOT
129
+ text's 4.5:1 (these are chart marks read at a glance, never prose).
130
+ Order is the dataset's own — NOT reconciled hue-for-hue against the
131
+ dark block below (dark mode is independently tuned, same stance as
132
+ every other slot; see REMARQUE.md "Dataviz Tokens"). Pair with a
133
+ non-color redundancy (shape, pattern, or a direct label) — never
134
+ color alone, per the same Carbon precedent. */
135
+ --color-viz-1: oklch(0.538 0.121 250.5); /* 4.74:1 on bg */
136
+ --color-viz-2: oklch(0.541 0.111 85.5); /* 4.73:1 on bg */
137
+ --color-viz-3: oklch(0.524 0.12 24.6); /* 5.35:1 on bg */
138
+ --color-viz-4: oklch(0.499 0.12 144.8); /* 5.32:1 on bg */
139
+ --color-viz-5: oklch(0.524 0.121 309.6); /* 5.33:1 on bg */
140
+ --color-viz-6: oklch(0.528 0.09 195.8); /* 4.74:1 on bg */
120
141
  }
121
142
 
122
143
  /* ─── Color: Dark theme ─────────────────────────────────── */
@@ -174,6 +195,16 @@
174
195
  --color-warning: oklch(0.62 0.11 85); /* 5.29:1 bg / 5.03:1 surface */
175
196
  --color-warning-subtle: oklch(0.22 0.04 85);
176
197
  --color-disabled: var(--color-muted);
198
+
199
+ /* Dataviz categorical ramp (issue #94) — derived from remarque-dark's
200
+ dataviz.categorical block; see the light block above for the full
201
+ rationale. Every slot ≥ 3:1 on --color-bg. */
202
+ --color-viz-1: oklch(0.708 0.129 249.5); /* 7.53:1 on bg */
203
+ --color-viz-2: oklch(0.712 0.13 85.3); /* 7.54:1 on bg */
204
+ --color-viz-3: oklch(0.724 0.129 310.2); /* 7.55:1 on bg */
205
+ --color-viz-4: oklch(0.725 0.129 25.4); /* 7.55:1 on bg */
206
+ --color-viz-5: oklch(0.696 0.129 144.9); /* 7.53:1 on bg */
207
+ --color-viz-6: oklch(0.697 0.119 194.8); /* 7.57:1 on bg */
177
208
  }
178
209
  }
179
210
 
@@ -227,6 +258,15 @@
227
258
  --color-warning: oklch(0.62 0.11 85);
228
259
  --color-warning-subtle: oklch(0.22 0.04 85);
229
260
  --color-disabled: var(--color-muted);
261
+
262
+ /* Dataviz categorical ramp (issue #94) — see the light :root block
263
+ above for the full rationale. */
264
+ --color-viz-1: oklch(0.708 0.129 249.5);
265
+ --color-viz-2: oklch(0.712 0.13 85.3);
266
+ --color-viz-3: oklch(0.724 0.129 310.2);
267
+ --color-viz-4: oklch(0.725 0.129 25.4);
268
+ --color-viz-5: oklch(0.696 0.129 144.9);
269
+ --color-viz-6: oklch(0.697 0.119 194.8);
230
270
  }
231
271
 
232
272
  /* ─── prefers-contrast: more ────────────────────────────────
package/tokens.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Remarque design tokens — GENERATED from tokens.json by
3
- * scripts/tokens-json.mjs (v0.21.0). Do not edit — the CSS
3
+ * scripts/tokens-json.mjs (v0.22.0). Do not edit — the CSS
4
4
  * (tokens-core.css + tokens-palette.css) is the source of truth;
5
5
  * tokens.json is the intermediate machine-readable form this file is
6
6
  * generated from. Regenerate with: node scripts/tokens-json.mjs
@@ -104,7 +104,13 @@ export type RemarquePaletteToken =
104
104
  | 'color-success-subtle'
105
105
  | 'color-warning'
106
106
  | 'color-warning-subtle'
107
- | 'color-disabled';
107
+ | 'color-disabled'
108
+ | 'color-viz-1'
109
+ | 'color-viz-2'
110
+ | 'color-viz-3'
111
+ | 'color-viz-4'
112
+ | 'color-viz-5'
113
+ | 'color-viz-6';
108
114
 
109
115
  /** Every Remarque token name, core + palette. */
110
116
  export type RemarqueToken = RemarqueCoreToken | RemarquePaletteToken;
@@ -392,6 +398,30 @@ export interface RemarqueTokenValues {
392
398
  readonly light: "var(--color-muted)";
393
399
  readonly dark: "var(--color-muted)";
394
400
  };
401
+ readonly 'color-viz-1': {
402
+ readonly light: "oklch(0.538 0.121 250.5)";
403
+ readonly dark: "oklch(0.708 0.129 249.5)";
404
+ };
405
+ readonly 'color-viz-2': {
406
+ readonly light: "oklch(0.541 0.111 85.5)";
407
+ readonly dark: "oklch(0.712 0.13 85.3)";
408
+ };
409
+ readonly 'color-viz-3': {
410
+ readonly light: "oklch(0.524 0.12 24.6)";
411
+ readonly dark: "oklch(0.724 0.129 310.2)";
412
+ };
413
+ readonly 'color-viz-4': {
414
+ readonly light: "oklch(0.499 0.12 144.8)";
415
+ readonly dark: "oklch(0.725 0.129 25.4)";
416
+ };
417
+ readonly 'color-viz-5': {
418
+ readonly light: "oklch(0.524 0.121 309.6)";
419
+ readonly dark: "oklch(0.696 0.129 144.9)";
420
+ };
421
+ readonly 'color-viz-6': {
422
+ readonly light: "oklch(0.528 0.09 195.8)";
423
+ readonly dark: "oklch(0.697 0.119 194.8)";
424
+ };
395
425
  };
396
426
  }
397
427
 
package/tokens.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "$description": "Remarque design tokens — GENERATED from tokens-core.css + tokens-palette.css by scripts/tokens-json.mjs. Do not edit; the CSS is the source of truth.",
4
4
  "$extensions": {
5
5
  "remarque": {
6
- "version": "0.21.0",
6
+ "version": "0.22.0",
7
7
  "tiers": {
8
8
  "core": "immutable identity — overriding forks the system",
9
9
  "palette": "sanctioned personalization surface — override freely, then run remarque-audit"
@@ -610,6 +610,60 @@
610
610
  "dark": {
611
611
  "$value": "var(--color-muted)"
612
612
  }
613
+ },
614
+ "color-viz-1": {
615
+ "$type": "color",
616
+ "light": {
617
+ "$value": "oklch(0.538 0.121 250.5)"
618
+ },
619
+ "dark": {
620
+ "$value": "oklch(0.708 0.129 249.5)"
621
+ }
622
+ },
623
+ "color-viz-2": {
624
+ "$type": "color",
625
+ "light": {
626
+ "$value": "oklch(0.541 0.111 85.5)"
627
+ },
628
+ "dark": {
629
+ "$value": "oklch(0.712 0.13 85.3)"
630
+ }
631
+ },
632
+ "color-viz-3": {
633
+ "$type": "color",
634
+ "light": {
635
+ "$value": "oklch(0.524 0.12 24.6)"
636
+ },
637
+ "dark": {
638
+ "$value": "oklch(0.724 0.129 310.2)"
639
+ }
640
+ },
641
+ "color-viz-4": {
642
+ "$type": "color",
643
+ "light": {
644
+ "$value": "oklch(0.499 0.12 144.8)"
645
+ },
646
+ "dark": {
647
+ "$value": "oklch(0.725 0.129 25.4)"
648
+ }
649
+ },
650
+ "color-viz-5": {
651
+ "$type": "color",
652
+ "light": {
653
+ "$value": "oklch(0.524 0.121 309.6)"
654
+ },
655
+ "dark": {
656
+ "$value": "oklch(0.696 0.129 144.9)"
657
+ }
658
+ },
659
+ "color-viz-6": {
660
+ "$type": "color",
661
+ "light": {
662
+ "$value": "oklch(0.528 0.09 195.8)"
663
+ },
664
+ "dark": {
665
+ "$value": "oklch(0.697 0.119 194.8)"
666
+ }
613
667
  }
614
668
  }
615
669
  }