remarque-tokens 0.11.0 → 0.13.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,87 @@ 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.13.0 — 2026-07-23
8
+
9
+ Syntax-highlighting palette — 9 `--color-syntax-*` slots, ANSI-derived, golden-gated (closes #53; consensus-ratified 3-0 with a binding taxonomy design record, 2026-07-23).
10
+
11
+ ### Added
12
+ - **`--color-syntax-keyword/string/constant/comment/function/type/punctuation/variable/link`** —
13
+ 9 new palette-tier slots in `tokens-palette.css`, hand-authored in both
14
+ themes, quiet/low-chroma (chroma capped at 0.14, the same ceiling as
15
+ `--color-accent`). Every slot is ≥ 4.5:1 against `--color-code-bg`
16
+ (not `--color-bg`) in both themes — a new dimension the audit didn't
17
+ cover before this release.
18
+ - **`scripts/audit.mjs`** — 9 new `CHECKS` pairings (each syntax slot vs.
19
+ `color-code-bg`, 4.5:1, both themes). `scripts/test-audit.mjs`'s
20
+ fixture palettes carry the 9 slots and a new must-fail case proves
21
+ they're actually wired into `CHECKS`, not just parsed.
22
+ - **`scripts/theme.mjs` (`remarque-theme`)** — derives all 9 slots from a
23
+ source theme's 16 ANSI colors (`keyword`←blue, `string`←green,
24
+ `constant`←yellow, `function`←purple, `type`←cyan, `comment`←`brightBlack`
25
+ in dark themes / a solved muted neutral in light themes,
26
+ `punctuation`←a derived neutral, `variable`←fg-adjacent,
27
+ `link`←the derived accent, aliased when it clears 4.5:1 on `code-bg`),
28
+ same keep-if-passing-else-solve pattern and in-gamut chroma clamping as
29
+ every other slot. `scripts/test-theme.mjs`'s 61-pair corpus (every
30
+ counterpart-paired theme in the installed `oklch-terminal-themes`
31
+ dataset) now runs the extended audit — 61/61 pass.
32
+ - **`scripts/palette-golden.mjs`** — extends the ΔE2000 ≤ 2.0 golden gate
33
+ to the 9 syntax slots (now 24 `--color-*` tokens × 2 themes). The
34
+ hand-authored defaults are the cleaned round-number serialization of
35
+ values derived from `remarque-light`/`remarque-dark`'s ANSI colors,
36
+ same identity/serialization contract as the rest of the default
37
+ palette (#76).
38
+ - **REMARQUE.md "Syntax Highlighting"** — the slot table with Shiki
39
+ `createCssVariablesTheme` and Prism class mappings, the exact Astro
40
+ wiring snippet (pass the theme *object*, never the `'css-variables'`
41
+ string — Astro renames its prefix), the CSS variable bridge required
42
+ to make that wiring actually render (Shiki's `variablePrefix` prepends
43
+ rather than renames its internal `token-*` names), a full Prism CSS
44
+ mapping block, the three documented divergences (`type` ≡ `function`
45
+ under Shiki, plain operators inherit foreground, diff markers out of
46
+ scope), and the comment-contrast policy (4.5:1 held; GitHub
47
+ light/dark-default and VS Code Dark+ all clear it).
48
+ - **Demo site (`site/`)** — wired `astro.config.mjs`'s `markdown.shikiConfig`
49
+ and the Type Specimen page's new "Syntax Highlighting" section with
50
+ the `createCssVariablesTheme` recipe and its CSS bridge
51
+ (`site/src/styles/globals.css`), so at least one built page exercises
52
+ every slot in both themes. `shiki` added as an explicit `site`
53
+ devDependency (Astro already carries it transitively; pinned
54
+ explicitly so the resolution is intentional, not an accident of
55
+ hoisting). Playwright baselines updated for the 4 `specimen-*`
56
+ screenshots (the only page with a highlighted code block); the other
57
+ 16 baselines are unchanged.
58
+
59
+ ## 0.12.0 — 2026-07-23
60
+
61
+ TypeScript types and a README "Used By" section (closes #34, closes #35).
62
+
63
+ ### Added
64
+ - **`tokens.d.ts`** — generated from `tokens.json` by `scripts/tokens-json.mjs`
65
+ (regenerates in the same run as `tokens.json`; `--check` verifies both are
66
+ fresh, wired into the same `deploy.yml` CI step). Ships literal-union types
67
+ for every token name (`RemarqueCoreToken`, `RemarquePaletteToken`,
68
+ `RemarqueToken`), a `RemarqueCssVar` template-literal type
69
+ (`` `--${RemarqueToken}` ``), structural interfaces matching tokens.json's
70
+ actual shape (`RemarqueTokensFile`, `RemarqueCoreTokenEntry`,
71
+ `RemarquePaletteTokenEntry`), a `RemarqueTokenValues` interface with the
72
+ live value of every token as a literal type, and an ambient
73
+ `declare module 'remarque-tokens/tokens.json'` so
74
+ `import tokens from 'remarque-tokens/tokens.json'` is precisely typed
75
+ without depending on the consumer's `resolveJsonModule`. This package has
76
+ no JS entry point — the types exist for editors/TS consumers who author
77
+ Remarque token names or read `tokens.json` programmatically.
78
+ `package.json` wires `types`, an `./tokens.d.ts` export, and a `types`
79
+ condition on the existing `./tokens.json` export; `scripts/test-types.mjs`
80
+ gates token-name coverage, the exported type surface, and the
81
+ `package.json` wiring (string assertions, no `typescript` dependency
82
+ added).
83
+ - **README "Used By" section** — the demo site, `williamzujkowski.github.io`
84
+ (core-tier npm consumer), `tsundoku` (full-palette npm consumer, custom
85
+ accent hue), and `remarque-starter` (template repo), one line each on how
86
+ they consume the package.
87
+
7
88
  ## 0.11.0 — 2026-07-23
8
89
 
9
90
  Palette golden gate: the default palette is now bound, by CI, to the
package/README.md CHANGED
@@ -19,6 +19,13 @@ Most developer sites inherit the visual language of SaaS dashboards or component
19
19
  - **Self-hosted fonts** — no Google CDN dependency. Strict CSP. GDPR-compliant.
20
20
  - **AI-native** — designed to be consumed by Claude Code, Cursor, Copilot, and other AI coding tools with zero aesthetic drift.
21
21
 
22
+ ## Used By
23
+
24
+ - **[Remarque demo site](https://williamzujkowski.github.io/remarque/)** — the `site/` directory in this repo; reference implementation of all four demoed archetypes, deployed to GitHub Pages.
25
+ - **[williamzujkowski.github.io](https://github.com/williamzujkowski/williamzujkowski.github.io)** — flagship personal site; core-tier npm consumer (`remarque-tokens/core` from npm, hand-authored palette layer on top).
26
+ - **[tsundoku](https://github.com/williamzujkowski/tsundoku)** — bookshelf/reading-log site; full-palette npm consumer, custom terracotta "book cloth" accent hue over the default palette for its card-catalog identity.
27
+ - **[remarque-starter](https://github.com/williamzujkowski/remarque-starter)** — template repo; Astro scaffold pulling `remarque-tokens` from npm with the audit wired into CI, meant to be forked.
28
+
22
29
  ## Install
23
30
 
24
31
  ```bash
package/REMARQUE.md CHANGED
@@ -80,7 +80,7 @@ The palette tier (all `--color-*` tokens) can be *supplied* rather than hand-aut
80
80
  npx remarque-theme <light-slug> [--dark <dark-slug>] [-o palette-override.css]
81
81
  ```
82
82
 
83
- Terminal themes carry only `background/foreground/cursor/selection` + 16 ANSI slots — a few of Remarque's 15 semantic slots map directly, and most themes fail the AAA `fg-muted` 7:1 line as authored. So `remarque-theme` *derives* rather than maps: **hue and chroma carry the theme's personality; lightness is KEEP-IF-PASSING**. For the slots that have a meaningful "theme's own value" — `fg`, `accent`, `accent-hover`, and (in dark) `selection-fg`/`code-fg` — the theme's own authored lightness is kept as-is when it already clears the slot's contrast target; lightness is solved (binary search, same targets as the Enforcement Checklist below, with in-gamut chroma clamping) *only* when it doesn't. Every other slot (`fg-muted`, `muted`, `border-bold`, the bg ladder, `selection-bg`, `accent-subtle`) is solved/derived unconditionally, as it always was — those don't carry an independent authored value to preserve. This means a well-designed input theme comes through close to its own feel instead of being flattened to the exact threshold; a theme that fails a target still gets a value that passes, exactly as before. The accent hue comes from the theme's cursor color if it's chromatic, otherwise the most saturated classic ANSI color, and is kept consistent between the light and dark half of a pair.
83
+ Terminal themes carry only `background/foreground/cursor/selection` + 16 ANSI slots — a few of Remarque's 24 semantic slots map directly, and most themes fail the AAA `fg-muted` 7:1 line as authored. So `remarque-theme` *derives* rather than maps: **hue and chroma carry the theme's personality; lightness is KEEP-IF-PASSING**. For the slots that have a meaningful "theme's own value" — `fg`, `accent`, `accent-hover`, and (in dark) `selection-fg`/`code-fg` — the theme's own authored lightness is kept as-is when it already clears the slot's contrast target; lightness is solved (binary search, same targets as the Enforcement Checklist below, with in-gamut chroma clamping) *only* when it doesn't. Every other slot (`fg-muted`, `muted`, `border-bold`, the bg ladder, `selection-bg`, `accent-subtle`) is solved/derived unconditionally, as it always was — those don't carry an independent authored value to preserve. This means a well-designed input theme comes through close to its own feel instead of being flattened to the exact threshold; a theme that fails a target still gets a value that passes, exactly as before. The accent hue comes from the theme's cursor color if it's chromatic, otherwise the most saturated classic ANSI color, and is kept consistent between the light and dark half of a pair.
84
84
 
85
85
  The audit remains the gate regardless of provenance — `remarque-theme` self-verifies its own output against the same checks before it will emit anything, but a site that hand-edits a generated palette (or points at a provider with pathological input colors) still runs through `remarque-audit` in CI like any other palette.
86
86
 
@@ -231,6 +231,113 @@ Charts are not yet a first-class Remarque surface, but tools built with the syst
231
231
 
232
232
  ---
233
233
 
234
+ ## Syntax Highlighting
235
+
236
+ Code blocks are a first-class Remarque surface, not a place to reach for a vendor Shiki/Prism theme: `tokens-palette.css` defines 9 `--color-syntax-*` slots, hand-authored in both themes and golden-gated against the upstream `remarque-light`/`remarque-dark` themes' ANSI colors the same way the rest of the default palette is (issue #53). Because they are ordinary palette-tier tokens, they inherit palette swaps and theme-deck runtime switches for free — a vendor theme never does.
237
+
238
+ ### Slots
239
+
240
+ | Slot | Shiki (`createCssVariablesTheme`) | Prism |
241
+ |---|---|---|
242
+ | `--color-syntax-keyword` | `token-keyword` | `.keyword`, `.atrule` |
243
+ | `--color-syntax-string` | `token-string` + `token-string-expression` (aliased — regex/template/interpolated are string-like) | `.string`, `.char`, `.attr-value` |
244
+ | `--color-syntax-constant` | `token-constant` | `.number`, `.boolean`, `.constant`, `.symbol` |
245
+ | `--color-syntax-comment` | `token-comment` | `.comment`, `.prolog`, `.doctype`, `.cdata` |
246
+ | `--color-syntax-function` | `token-function` | `.function` |
247
+ | `--color-syntax-type` | (same Shiki variable as `function` — see divergence below) | `.class-name` |
248
+ | `--color-syntax-punctuation` | `token-punctuation` | `.operator`, `.punctuation` |
249
+ | `--color-syntax-variable` | `token-parameter` | `.variable`, `.property`, `.tag`, `.attr-name` |
250
+ | `--color-syntax-link` | `token-link` | `.url` |
251
+
252
+ Slot names were validated against Shiki's `theme-css-variables.ts` and Prism's reference-theme token vocabulary before being frozen (panel condition on #53) — every name above maps to a real token both highlighters actually emit.
253
+
254
+ ### Derivation
255
+
256
+ `remarque-theme` derives all 9 slots from the source theme's 16 ANSI colors — the same keep-if-passing-else-solve pattern as every other slot, targeted at `--color-code-bg` (not `--color-bg`) at 4.5:1: `keyword`←blue, `string`←green, `constant`←yellow (the terminal convention for numbers/booleans), `function`←purple, `type`←cyan, `comment`←`brightBlack` in dark themes (solved to a muted neutral on the theme's own fg hue in light themes, where `brightBlack` is often too pale to clear 4.5:1 on `code-bg`), `punctuation`←a derived neutral in the muted family, `variable`←the theme's own fg hue/chroma with a slight lightness offset, and `link`←the already-derived accent triple, aliased with `var(--color-accent)` when it clears 4.5:1 on `code-bg` (solved standalone when it doesn't — accent's guarantee is against `--color-bg`, and `code-bg` sits close enough to drift below the line on some corpus themes). Chroma is capped at 0.14, the same ceiling as `--color-accent`, so syntax colors stay in the system's quiet, one-accent register. `scripts/palette-golden.mjs` extends its ΔE2000 ≤ 2.0 gate to all 9 slots, exactly like the rest of the default palette.
257
+
258
+ ### Astro / Shiki wiring
259
+
260
+ Shiki v1+ ships `createCssVariablesTheme` — pass it as an object, not the `'css-variables'` string (Astro renames the string form's variable prefix to `--astro-code-*`, which silently breaks the mapping):
261
+
262
+ ```js
263
+ import { createCssVariablesTheme } from 'shiki'; // or '@shikijs/core'
264
+
265
+ const theme = createCssVariablesTheme({
266
+ name: 'remarque',
267
+ variablePrefix: '--color-syntax-',
268
+ fontStyle: true,
269
+ });
270
+
271
+ // markdown/MDX: { shikiConfig: { theme } }
272
+ // <Code> component: <Code code={...} lang="..." theme={theme} />
273
+ ```
274
+
275
+ **The CSS bridge (required, not optional):** `createCssVariablesTheme`'s `variablePrefix` is a plain string prepend, not a rename — Shiki's own internal slot names already start with `token-` (`token-keyword`, `token-string-expression`, `token-constant`, …) plus a bare `foreground`/`background`, so `variablePrefix: '--color-syntax-'` makes Shiki bake `color:var(--color-syntax-token-keyword)` into every span, not `var(--color-syntax-keyword)`. Left undefined, that variable resolves to nothing and the span silently inherits its parent's color — the highlighting looks like it's missing, not broken. One small bridge block closes the gap (Astro's Shiki integration puts an `.astro-code` class on the `<pre>`; scope to it so these implementation-detail names don't leak into the page's global custom-property namespace):
276
+
277
+ ```css
278
+ .astro-code {
279
+ --color-syntax-foreground: var(--color-code-fg);
280
+ --color-syntax-background: var(--color-code-bg);
281
+ --color-syntax-token-keyword: var(--color-syntax-keyword);
282
+ --color-syntax-token-string: var(--color-syntax-string);
283
+ --color-syntax-token-string-expression: var(--color-syntax-string);
284
+ --color-syntax-token-constant: var(--color-syntax-constant);
285
+ --color-syntax-token-comment: var(--color-syntax-comment);
286
+ --color-syntax-token-function: var(--color-syntax-function);
287
+ --color-syntax-token-parameter: var(--color-syntax-variable);
288
+ --color-syntax-token-punctuation: var(--color-syntax-punctuation);
289
+ --color-syntax-token-link: var(--color-syntax-link);
290
+ /* token-inserted/-deleted/-changed intentionally left undefined —
291
+ diff markers, out of scope (see "Documented divergences" below);
292
+ they fall back to inherited foreground, which is a safe default. */
293
+ }
294
+ ```
295
+
296
+ There is no separate `token-type` — see the `type` ≡ `function` divergence below; `--color-syntax-type` is real for Prism but never appears as a Shiki variable.
297
+
298
+ ### Prism CSS mapping
299
+
300
+ Prism ships no CSS-variables mode — wire the classes directly:
301
+
302
+ ```css
303
+ .token.keyword,
304
+ .token.atrule { color: var(--color-syntax-keyword); }
305
+ .token.string,
306
+ .token.char,
307
+ .token.attr-value { color: var(--color-syntax-string); }
308
+ .token.number,
309
+ .token.boolean,
310
+ .token.constant,
311
+ .token.symbol { color: var(--color-syntax-constant); }
312
+ .token.comment,
313
+ .token.prolog,
314
+ .token.doctype,
315
+ .token.cdata { color: var(--color-syntax-comment); }
316
+ .token.function { color: var(--color-syntax-function); }
317
+ .token.class-name { color: var(--color-syntax-type); }
318
+ .token.operator,
319
+ .token.punctuation { color: var(--color-syntax-punctuation); }
320
+ .token.variable,
321
+ .token.property,
322
+ .token.tag,
323
+ .token.attr-name { color: var(--color-syntax-variable); }
324
+ .token.url { color: var(--color-syntax-link); }
325
+ ```
326
+
327
+ ### Documented divergences
328
+
329
+ These are deliberate, not bugs:
330
+
331
+ - **`type` ≡ `function` under Shiki.** Shiki's css-variables mode folds `entity.name.type` into the function-scoped variable, so the two slots render identically in Shiki output even though Prism (`.class-name` vs. `.function`) distinguishes them. The tokens stay separate because Prism needs them separate.
332
+ - **Plain operators inherit the foreground.** Shiki has no scope for a bare operator token; it renders in `--color-fg` like surrounding text rather than `--color-syntax-punctuation`. Prism's `.operator` class does map to punctuation.
333
+ - **Diff markers are out of scope.** `token-inserted`/`token-deleted`/`token-changed` (Shiki) and equivalent Prism diff classes are not covered by this token set — a future `--color-diff-*` family would own them if a concrete consumer needs one.
334
+
335
+ ### Comment-contrast policy
336
+
337
+ The 4.5:1 floor is held, not relaxed, for `--color-syntax-comment` — muted comments are an editorial convention, not a license to fail AA. Survey of shipping syntax themes: GitHub's light-default theme holds 4.55:1, GitHub's dark-default holds 6.15:1, and VS Code's Dark+ holds 5.0:1 — all pass. Only legacy/neglected themes fail (Prism's own default theme sits at 3.64:1; the legacy `github-dark` theme sits at 3.05:1). A muted-comment convention is achievable within AA; Remarque does not carry a documented exception for this slot.
338
+
339
+ ---
340
+
234
341
  ## Motion Rules
235
342
 
236
343
  Motion in Remarque is nearly invisible. The only permitted motion:
@@ -303,6 +410,7 @@ Every PR that ships Remarque pages MUST pass (`npm run audit` automates the colo
303
410
  - [ ] `font-variant-numeric: tabular-nums lining-nums` on all metadata that mixes with dates/counts.
304
411
  - [ ] No hardcoded hex/rgb colors — only `var(--color-*)` tokens.
305
412
  - [ ] Body line-height ≥ 1.5 (Remarque target: 1.75).
413
+ - [ ] Every `--color-syntax-*` slot ≥ 4.5:1 against `--color-code-bg` in both themes (see "Syntax Highlighting").
306
414
 
307
415
  Agents reviewing PRs should reject changes that violate any line above without explicit rationale.
308
416
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remarque-tokens",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "engines": {
5
5
  "node": ">=18"
6
6
  },
@@ -21,6 +21,7 @@
21
21
  },
22
22
  "homepage": "https://williamzujkowski.github.io/remarque/",
23
23
  "main": "tokens.css",
24
+ "types": "./tokens.d.ts",
24
25
  "bin": {
25
26
  "remarque-audit": "scripts/audit.mjs",
26
27
  "remarque-drift": "scripts/drift-check.mjs",
@@ -43,7 +44,10 @@
43
44
  }
44
45
  },
45
46
  "exports": {
46
- ".": "./tokens.css",
47
+ ".": {
48
+ "types": "./tokens.d.ts",
49
+ "default": "./tokens.css"
50
+ },
47
51
  "./tokens": "./tokens.css",
48
52
  "./tokens.css": "./tokens.css",
49
53
  "./core": "./tokens-core.css",
@@ -55,7 +59,11 @@
55
59
  "./tailwind": "./tailwind.config.js",
56
60
  "./theme.css": "./theme.css",
57
61
  "./theme": "./theme.css",
58
- "./tokens.json": "./tokens.json",
62
+ "./tokens.json": {
63
+ "types": "./tokens.d.ts",
64
+ "default": "./tokens.json"
65
+ },
66
+ "./tokens.d.ts": "./tokens.d.ts",
59
67
  "./prose": "./prose.css",
60
68
  "./prose.css": "./prose.css",
61
69
  "./print": "./print.css",
@@ -80,6 +88,7 @@
80
88
  "CHANGELOG.md",
81
89
  "theme.css",
82
90
  "tokens.json",
91
+ "tokens.d.ts",
83
92
  "scripts/tokens-json.mjs",
84
93
  "prose.css",
85
94
  "print.css",
package/scripts/audit.mjs CHANGED
@@ -113,6 +113,15 @@ const CHECKS = [
113
113
  ['color-code-fg', 'color-code-bg', 4.5, 'code text'],
114
114
  ['color-border-bold', 'color-bg', 3.0, 'functional borders (WCAG 1.4.11)'],
115
115
  ['color-selection-fg', 'color-selection-bg', 4.5, 'selected text'],
116
+ ['color-syntax-keyword', 'color-code-bg', 4.5, 'syntax: keyword'],
117
+ ['color-syntax-string', 'color-code-bg', 4.5, 'syntax: string'],
118
+ ['color-syntax-constant', 'color-code-bg', 4.5, 'syntax: constant'],
119
+ ['color-syntax-comment', 'color-code-bg', 4.5, 'syntax: comment'],
120
+ ['color-syntax-function', 'color-code-bg', 4.5, 'syntax: function'],
121
+ ['color-syntax-type', 'color-code-bg', 4.5, 'syntax: type'],
122
+ ['color-syntax-punctuation', 'color-code-bg', 4.5, 'syntax: punctuation'],
123
+ ['color-syntax-variable', 'color-code-bg', 4.5, 'syntax: variable'],
124
+ ['color-syntax-link', 'color-code-bg', 4.5, 'syntax: link'],
116
125
  ];
117
126
 
118
127
  for (const [themeName, decls] of [['light', lightDecls], ['dark', darkDecls]]) {
package/scripts/theme.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  * npx remarque-theme <light-slug> --dark <dark-slug> [-o out.css]
8
8
  *
9
9
  * Terminal themes carry only background/foreground/cursor/selection + 16
10
- * ANSI slots — most of Remarque's 15 semantic slots don't map directly,
10
+ * ANSI slots — most of Remarque's 24 semantic slots don't map directly,
11
11
  * and most themes fail the AAA fg-muted 7:1 line as authored. So this
12
12
  * DERIVES rather than maps: hue + chroma come from the theme (its
13
13
  * personality); lightness is KEEP-IF-PASSING — a few load-bearing slots
@@ -19,9 +19,11 @@
19
19
  * still gets a value that passes, same as before (#76). Every other
20
20
  * slot (fg-muted, muted, border-bold, the bg ladder, selection-bg,
21
21
  * accent-subtle) is solved/derived as before — those don't have a
22
- * meaningful "theme's own value" to preserve. Output passes
23
- * remarque-audit BY CONSTRUCTION this script self-verifies the same
24
- * pairings before it will emit anything.
22
+ * meaningful "theme's own value" to preserve. The 9 --color-syntax-*
23
+ * slots (issue #53) are derived straight from the theme's 16 ANSI
24
+ * colors instead see the "Syntax-highlighting slots" section below.
25
+ * Output passes remarque-audit BY CONSTRUCTION — this script
26
+ * self-verifies the same pairings before it will emit anything.
25
27
  *
26
28
  * Security (non-negotiable, per the panel security review on #75):
27
29
  * - Both slugs are checked against the package's own index.json BEFORE
@@ -325,6 +327,66 @@ function withHeadroom([L0, , H], delta, dir, C) {
325
327
  return [L, fitChroma(L, C, H), r1(H)];
326
328
  }
327
329
 
330
+ /* ── Syntax-highlighting slots (issue #53) ───────────────────────────
331
+ * Terminal ANSI colors ARE syntax colors in their native domain, so the
332
+ * 9 --color-syntax-* slots are derived straight from a theme's 16 ANSI
333
+ * colors rather than invented fresh — zero new upstream schema work.
334
+ * Every slot targets --color-code-bg (not --color-bg) at 4.5:1, same
335
+ * keep-if-passing-else-solve pattern as every other slot above: the
336
+ * ANSI color's own lightness is kept when it already clears the ratio,
337
+ * solved (same binary search, same in-gamut chroma clamping) only when
338
+ * it doesn't. Chroma is capped at 0.14 — the same ceiling already used
339
+ * for --color-accent — so syntax colors stay in the system's "quiet,
340
+ * one-accent" register rather than turning carnival-bright. */
341
+
342
+ const SYNTAX_CHROMA_CAP = 0.14;
343
+
344
+ /* keyword/string/constant/function/type: a direct ANSI slot, kept if it
345
+ * already passes vs code-bg, solved (same hue) if it doesn't. */
346
+ function deriveSyntaxAnsi(t, ansiName, label, codeBg, dir) {
347
+ const [L0, C0, H0] = slotLch(t, ansiName, label);
348
+ const C = Math.min(C0, SYNTAX_CHROMA_CAP);
349
+ return keepOrSolve(L0, C, H0, codeBg, 4.5, dir);
350
+ }
351
+
352
+ /* comment: brightBlack in dark themes (the ANSI convention); in light
353
+ * themes brightBlack is often too pale to hold 4.5:1 on code-bg, so the
354
+ * fallback solves a muted neutral on the theme's own fg hue instead of
355
+ * brightBlack's hue. */
356
+ function deriveSyntaxComment(t, label, codeBg, dir, fallbackC, fallbackH) {
357
+ const [L0, C0, H0] = slotLch(t, 'brightBlack', label);
358
+ const C = Math.min(C0, SYNTAX_CHROMA_CAP);
359
+ const L = r3(Math.min(1, Math.max(0, L0)));
360
+ const c = fitChroma(L, C, H0);
361
+ if (ratio([L, c, H0], codeBg) >= 4.5) return [L, c, r1(H0)];
362
+ return solveL(fallbackC, fallbackH, codeBg, 4.5, dir);
363
+ }
364
+
365
+ /* punctuation: a derived neutral in the muted family (fg hue, low
366
+ * chroma), targeted at code-bg rather than surface. */
367
+ function deriveSyntaxPunctuation(fgC, fgH, codeBg, dir) {
368
+ return solveL(Math.min(fgC, 0.02), fgH, codeBg, 4.5, dir);
369
+ }
370
+
371
+ /* variable: fg-adjacent — the theme's own fg hue/chroma, offset a little
372
+ * so it reads as "quieter than plain text," verified (and solved if
373
+ * needed) against code-bg rather than assumed from the fg/surface pass. */
374
+ function deriveSyntaxVariable(fg, codeBg, dir, offset) {
375
+ return keepOrSolve(fg[0] + offset, fg[1], fg[2], codeBg, 4.5, dir);
376
+ }
377
+
378
+ /* link: alias the already-derived accent triple when it clears 4.5:1 on
379
+ * code-bg (accent is only guaranteed against --color-bg, and code-bg
380
+ * sits a little off from bg, so this isn't automatic) — solve a
381
+ * standalone value at the accent's own hue/chroma only when it doesn't,
382
+ * same as every other slot. Aliasing means a site's accent-hue swap
383
+ * (REMARQUE.md "Changing the Accent Hue") carries the link color with
384
+ * it for free whenever the alias holds. */
385
+ function deriveSyntaxLink(accent, codeBg, dir) {
386
+ if (ratio(accent, codeBg) >= 4.5) return { ref: 'color-accent' };
387
+ return keepOrSolve(accent[0], accent[1], accent[2], codeBg, 4.5, dir);
388
+ }
389
+
328
390
  /* ── LIGHT derivation ─────────────────────────────────────────────── */
329
391
 
330
392
  function deriveLight(t) {
@@ -351,6 +413,18 @@ function deriveLight(t) {
351
413
  // accent-hover: still the designed -0.08 offset from accent, but that
352
414
  // offset is verified against 4.5:1 on bg and solve-adjusted if it breaks.
353
415
  const accentHover = keepOrSolve(accent[0] - 0.08, acC * 0.8, ac.h, bg, 4.5, 'darker');
416
+ const codeBg = [r3(bg[0] - 0.03), bg[1], bg[2]];
417
+ // Syntax-highlighting slots (issue #53) — see the derivation helpers
418
+ // above. All verified against code-bg (not bg) at 4.5:1.
419
+ const synKeyword = deriveSyntaxAnsi(t, 'blue', 'light', codeBg, 'darker');
420
+ const synString = deriveSyntaxAnsi(t, 'green', 'light', codeBg, 'darker');
421
+ const synConstant = deriveSyntaxAnsi(t, 'yellow', 'light', codeBg, 'darker');
422
+ const synFunction = deriveSyntaxAnsi(t, 'purple', 'light', codeBg, 'darker');
423
+ const synType = deriveSyntaxAnsi(t, 'cyan', 'light', codeBg, 'darker');
424
+ const synComment = deriveSyntaxComment(t, 'light', codeBg, 'darker', fgC, fgH);
425
+ const synPunctuation = deriveSyntaxPunctuation(fgC, fgH, codeBg, 'darker');
426
+ const synVariable = deriveSyntaxVariable(fg, codeBg, 'darker', 0.08);
427
+ const synLink = deriveSyntaxLink(accent, codeBg, 'darker');
354
428
  const raw = {
355
429
  'color-bg': bg,
356
430
  'color-bg-subtle': [r3(bg[0] - 0.02), bg[1], bg[2]],
@@ -365,8 +439,17 @@ function deriveLight(t) {
365
439
  'color-accent-subtle': [0.95, fitChroma(0.95, 0.02, ac.h), r1(ac.h)],
366
440
  'color-selection-bg': [0.92, fitChroma(0.92, 0.04, ac.h), r1(ac.h)],
367
441
  'color-selection-fg': { ref: 'color-fg' },
368
- 'color-code-bg': [r3(bg[0] - 0.03), bg[1], bg[2]],
442
+ 'color-code-bg': codeBg,
369
443
  'color-code-fg': { ref: 'color-fg' },
444
+ 'color-syntax-keyword': synKeyword,
445
+ 'color-syntax-string': synString,
446
+ 'color-syntax-constant': synConstant,
447
+ 'color-syntax-comment': synComment,
448
+ 'color-syntax-function': synFunction,
449
+ 'color-syntax-type': synType,
450
+ 'color-syntax-punctuation': synPunctuation,
451
+ 'color-syntax-variable': synVariable,
452
+ 'color-syntax-link': synLink,
370
453
  };
371
454
  const tokens = {};
372
455
  for (const [name, v] of Object.entries(raw)) tokens[name] = v.ref ? `var(--${v.ref})` : fmt(v);
@@ -409,6 +492,17 @@ function deriveDark(t, accentHueLight) {
409
492
  // code-fg: "slightly dimmer than fg" — same pattern, -0.02, verified
410
493
  // against the AAA 7:1 line on code-bg.
411
494
  const codeFg = keepOrSolve(fg[0] - 0.02, fg[1], fg[2], codeBg, 7.0, 'lighter');
495
+ // Syntax-highlighting slots (issue #53) — mirrors the light derivation,
496
+ // direction flipped ('lighter': text on a dark code-bg).
497
+ const synKeyword = deriveSyntaxAnsi(t, 'blue', 'dark', codeBg, 'lighter');
498
+ const synString = deriveSyntaxAnsi(t, 'green', 'dark', codeBg, 'lighter');
499
+ const synConstant = deriveSyntaxAnsi(t, 'yellow', 'dark', codeBg, 'lighter');
500
+ const synFunction = deriveSyntaxAnsi(t, 'purple', 'dark', codeBg, 'lighter');
501
+ const synType = deriveSyntaxAnsi(t, 'cyan', 'dark', codeBg, 'lighter');
502
+ const synComment = deriveSyntaxComment(t, 'dark', codeBg, 'lighter', fgC, fgH);
503
+ const synPunctuation = deriveSyntaxPunctuation(fgC, fgH, codeBg, 'lighter');
504
+ const synVariable = deriveSyntaxVariable(fg, codeBg, 'lighter', -0.08);
505
+ const synLink = deriveSyntaxLink(accent, codeBg, 'lighter');
412
506
  const raw = {
413
507
  'color-bg': bg,
414
508
  'color-bg-subtle': surface,
@@ -425,9 +519,18 @@ function deriveDark(t, accentHueLight) {
425
519
  'color-selection-fg': selFg,
426
520
  'color-code-bg': codeBg,
427
521
  'color-code-fg': codeFg,
522
+ 'color-syntax-keyword': synKeyword,
523
+ 'color-syntax-string': synString,
524
+ 'color-syntax-constant': synConstant,
525
+ 'color-syntax-comment': synComment,
526
+ 'color-syntax-function': synFunction,
527
+ 'color-syntax-type': synType,
528
+ 'color-syntax-punctuation': synPunctuation,
529
+ 'color-syntax-variable': synVariable,
530
+ 'color-syntax-link': synLink,
428
531
  };
429
532
  const tokens = {};
430
- for (const [name, v] of Object.entries(raw)) tokens[name] = fmt(v);
533
+ for (const [name, v] of Object.entries(raw)) tokens[name] = v.ref ? `var(--${v.ref})` : fmt(v);
431
534
  tokens['weight-display'] = '500';
432
535
  return { raw, tokens };
433
536
  }
@@ -452,6 +555,15 @@ const CHECKS = [
452
555
  ['color-code-fg', 'color-code-bg', 4.5, 'code text'],
453
556
  ['color-border-bold', 'color-bg', 3.0, 'functional borders (WCAG 1.4.11)'],
454
557
  ['color-selection-fg', 'color-selection-bg', 4.5, 'selected text'],
558
+ ['color-syntax-keyword', 'color-code-bg', 4.5, 'syntax: keyword'],
559
+ ['color-syntax-string', 'color-code-bg', 4.5, 'syntax: string'],
560
+ ['color-syntax-constant', 'color-code-bg', 4.5, 'syntax: constant'],
561
+ ['color-syntax-comment', 'color-code-bg', 4.5, 'syntax: comment'],
562
+ ['color-syntax-function', 'color-code-bg', 4.5, 'syntax: function'],
563
+ ['color-syntax-type', 'color-code-bg', 4.5, 'syntax: type'],
564
+ ['color-syntax-punctuation', 'color-code-bg', 4.5, 'syntax: punctuation'],
565
+ ['color-syntax-variable', 'color-code-bg', 4.5, 'syntax: variable'],
566
+ ['color-syntax-link', 'color-code-bg', 4.5, 'syntax: link'],
455
567
  ];
456
568
 
457
569
  function resolveRaw(raw, name, seen = new Set()) {
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /*
3
- * Generates tokens.json (W3C design-tokens flavored) FROM the CSS —
4
- * tokens-core.css and tokens-palette.css remain the single source of
5
- * truth; this file is derived output for tooling and AI agents.
3
+ * Generates tokens.json (W3C design-tokens flavored) and tokens.d.ts
4
+ * (TypeScript literal-union types) FROM the CSS — tokens-core.css and
5
+ * tokens-palette.css remain the single source of truth; both files
6
+ * here are derived output for tooling, AI agents, and TS editors.
6
7
  *
7
- * node scripts/tokens-json.mjs # (re)write tokens.json
8
- * node scripts/tokens-json.mjs --check # exit 1 if tokens.json is stale (CI)
8
+ * node scripts/tokens-json.mjs # (re)write tokens.json + tokens.d.ts
9
+ * node scripts/tokens-json.mjs --check # exit 1 if either is stale (CI)
9
10
  *
10
11
  * Multi-theme values use per-token light/dark groups with $value on each
11
12
  * (the W3C spec has no native theming; this follows its $extensions
@@ -70,14 +71,159 @@ for (const [name, value] of Object.entries(lightDecls)) {
70
71
 
71
72
  const json = JSON.stringify(out, null, 2) + '\n';
72
73
 
74
+ /* ── tokens.d.ts ──────────────────────────────────────────────────────
75
+ * Literal-union types + a typed shape for tokens.json, generated from
76
+ * the same in-memory `out` object that produces tokens.json — so the
77
+ * two derived files can never disagree with each other, only (briefly,
78
+ * until regenerated) with the CSS. The package ships no JS entry point;
79
+ * this file exists purely for editors/TS consumers that either author
80
+ * Remarque token names (`RemarqueToken`, `RemarqueCssVar`) or read
81
+ * tokens.json programmatically (`RemarqueTokensFile`, wired via the
82
+ * ambient `declare module 'remarque-tokens/tokens.json'` below).
83
+ */
84
+ function tsLiteral(value) {
85
+ return typeof value === 'number' ? String(value) : JSON.stringify(value);
86
+ }
87
+
88
+ function renderDts(tokens, pkgVersion) {
89
+ const coreNames = Object.keys(tokens.core);
90
+ const paletteNames = Object.keys(tokens.palette);
91
+
92
+ const coreUnion = coreNames.map((n) => ` | '${n}'`).join('\n');
93
+ const paletteUnion = paletteNames.map((n) => ` | '${n}'`).join('\n');
94
+
95
+ const coreValues = coreNames
96
+ .map((n) => ` readonly '${n}': ${tsLiteral(tokens.core[n].$value)};`)
97
+ .join('\n');
98
+ const paletteValues = paletteNames
99
+ .map((n) => {
100
+ const t = tokens.palette[n];
101
+ return ` readonly '${n}': {\n readonly light: ${tsLiteral(t.light.$value)};\n readonly dark: ${tsLiteral(t.dark.$value)};\n };`;
102
+ })
103
+ .join('\n');
104
+
105
+ return `/*
106
+ * Remarque design tokens — GENERATED from tokens.json by
107
+ * scripts/tokens-json.mjs (v${pkgVersion}). Do not edit — the CSS
108
+ * (tokens-core.css + tokens-palette.css) is the source of truth;
109
+ * tokens.json is the intermediate machine-readable form this file is
110
+ * generated from. Regenerate with: node scripts/tokens-json.mjs
111
+ *
112
+ * This package has no JS entry point — these types exist for editors
113
+ * and TypeScript consumers that either author Remarque token names
114
+ * (RemarqueToken, RemarqueCssVar) or read tokens.json programmatically
115
+ * (RemarqueTokensFile / RemarqueTokenValues, and the ambient module
116
+ * declaration below for \`import tokens from 'remarque-tokens/tokens.json'\`).
117
+ */
118
+
119
+ /** All core-tier token names (tokens-core.css). Never overridden. */
120
+ export type RemarqueCoreToken =
121
+ ${coreUnion};
122
+
123
+ /** All palette-tier token names (tokens-palette.css). The sanctioned personalization surface. */
124
+ export type RemarquePaletteToken =
125
+ ${paletteUnion};
126
+
127
+ /** Every Remarque token name, core + palette. */
128
+ export type RemarqueToken = RemarqueCoreToken | RemarquePaletteToken;
129
+
130
+ /** A Remarque token name as its CSS custom-property form, e.g. \`--text-body\`. */
131
+ export type RemarqueCssVar = \`--${'$'}{RemarqueToken}\`;
132
+
133
+ /** W3C design-tokens \`$type\` values used across tokens.json. */
134
+ export type RemarqueTokenType =
135
+ | 'color'
136
+ | 'dimension'
137
+ | 'number'
138
+ | 'fontFamily'
139
+ | 'fontWeight'
140
+ | 'duration'
141
+ | 'string';
142
+
143
+ /** Shape of a single core-tier entry in tokens.json's \`core\` map. */
144
+ export interface RemarqueCoreTokenEntry {
145
+ readonly $value: string | number;
146
+ readonly $type: RemarqueTokenType;
147
+ }
148
+
149
+ /** Shape of one theme side (\`light\`/\`dark\`) of a palette-tier entry. */
150
+ export interface RemarquePaletteTokenSideValue {
151
+ readonly $value: string | number;
152
+ readonly $extensions?: {
153
+ readonly remarque?: {
154
+ readonly inheritedFromLight?: boolean;
155
+ };
156
+ };
157
+ }
158
+
159
+ /** Shape of a single palette-tier entry in tokens.json's \`palette\` map. */
160
+ export interface RemarquePaletteTokenEntry {
161
+ readonly $type: RemarqueTokenType;
162
+ readonly light: RemarquePaletteTokenSideValue;
163
+ readonly dark: RemarquePaletteTokenSideValue;
164
+ }
165
+
166
+ /**
167
+ * Typed shape of tokens.json's default export — the whole generated
168
+ * file, structurally. Use this when reading tokens.json dynamically
169
+ * (\`JSON.parse\`, a bundler JSON import without the ambient module
170
+ * below applying, etc).
171
+ */
172
+ export interface RemarqueTokensFile {
173
+ readonly $description: string;
174
+ readonly $extensions: {
175
+ readonly remarque: {
176
+ readonly version: string;
177
+ readonly tiers: {
178
+ readonly core: string;
179
+ readonly palette: string;
180
+ };
181
+ };
182
+ };
183
+ readonly core: { readonly [K in RemarqueCoreToken]: RemarqueCoreTokenEntry };
184
+ readonly palette: { readonly [K in RemarquePaletteToken]: RemarquePaletteTokenEntry };
185
+ }
186
+
187
+ /**
188
+ * The actual current value of every token, as literal types — precise
189
+ * autocomplete for "what is \`--text-body\` right now", not just "what
190
+ * are its shape and type". Regenerated alongside tokens.json, so these
191
+ * literals track the live CSS across releases.
192
+ */
193
+ export interface RemarqueTokenValues {
194
+ readonly core: {
195
+ ${coreValues}
196
+ };
197
+ readonly palette: {
198
+ ${paletteValues}
199
+ };
200
+ }
201
+
202
+ // Precise types for \`import tokens from 'remarque-tokens/tokens.json'\`
203
+ // (and any other bundler-JSON-import path resolving through this
204
+ // subpath) without depending on the consumer's \`resolveJsonModule\`.
205
+ declare module 'remarque-tokens/tokens.json' {
206
+ const tokens: RemarqueTokensFile;
207
+ export default tokens;
208
+ }
209
+ `;
210
+ }
211
+
212
+ const dts = renderDts(out, version);
213
+
73
214
  if (process.argv.includes('--check')) {
74
- const current = existsSync('tokens.json') ? readFileSync('tokens.json', 'utf8') : '';
75
- if (current !== json) {
76
- console.error('tokens.json is stale run: node scripts/tokens-json.mjs');
215
+ const currentJson = existsSync('tokens.json') ? readFileSync('tokens.json', 'utf8') : '';
216
+ const currentDts = existsSync('tokens.d.ts') ? readFileSync('tokens.d.ts', 'utf8') : '';
217
+ const staleJson = currentJson !== json;
218
+ const staleDts = currentDts !== dts;
219
+ if (staleJson || staleDts) {
220
+ if (staleJson) console.error('tokens.json is stale — run: node scripts/tokens-json.mjs');
221
+ if (staleDts) console.error('tokens.d.ts is stale — run: node scripts/tokens-json.mjs');
77
222
  process.exit(1);
78
223
  }
79
- console.log('tokens.json is fresh ✓');
224
+ console.log('tokens.json and tokens.d.ts are fresh ✓');
80
225
  } else {
81
226
  writeFileSync('tokens.json', json);
82
- console.log(`tokens.json written — ${Object.keys(out.core).length} core + ${Object.keys(out.palette).length} palette tokens (v${version})`);
227
+ writeFileSync('tokens.d.ts', dts);
228
+ console.log(`tokens.json + tokens.d.ts written — ${Object.keys(out.core).length} core + ${Object.keys(out.palette).length} palette tokens (v${version})`);
83
229
  }
@@ -16,6 +16,7 @@
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
20
  * in BOTH themes.
20
21
  * - Accent: change the hue by re-deriving accent/accent-hover/
21
22
  * accent-subtle/selection at the SAME lightness values used here,
@@ -77,6 +78,22 @@
77
78
  --color-selection-fg: var(--color-fg);
78
79
  --color-code-bg: oklch(0.945 0.005 80); /* inline code background */
79
80
  --color-code-fg: var(--color-fg);
81
+
82
+ /* Syntax highlighting (issue #53) — quiet, low-chroma slots derived
83
+ from remarque-light's ANSI colors (scripts/theme.mjs), serialized
84
+ as round numbers (golden-gated: scripts/palette-golden.mjs). Every
85
+ slot ≥ 4.5:1 on --color-code-bg. Mapping recipe (Shiki css-variables
86
+ + Prism) is documented in REMARQUE.md's "Syntax Highlighting"
87
+ section. */
88
+ --color-syntax-keyword: oklch(0.51 0.12 250); /* ANSI blue — 4.88:1 on code-bg */
89
+ --color-syntax-string: oklch(0.50 0.12 145); /* ANSI green — 4.85:1 */
90
+ --color-syntax-constant: oklch(0.51 0.105 85); /* ANSI yellow (numbers/booleans convention) — 4.94:1 */
91
+ --color-syntax-comment: oklch(0.52 0.01 80); /* solved muted neutral (fg hue) — brightBlack is too pale on code-bg here — 4.69:1 */
92
+ --color-syntax-function: oklch(0.52 0.12 310); /* ANSI purple — 4.96:1 */
93
+ --color-syntax-type: oklch(0.50 0.085 196); /* ANSI cyan — 4.90:1 */
94
+ --color-syntax-punctuation: oklch(0.52 0.01 80); /* derived neutral, muted family — 4.69:1 */
95
+ --color-syntax-variable: oklch(0.26 0.01 80); /* fg-adjacent (slight L offset) — 13.23:1 */
96
+ --color-syntax-link: var(--color-accent); /* accent already clears code-bg — 4.5:1+ */
80
97
  }
81
98
 
82
99
  /* ─── Color: Dark theme ─────────────────────────────────── */
@@ -110,6 +127,18 @@
110
127
  --color-selection-fg: oklch(0.92 0.005 80);
111
128
  --color-code-bg: oklch(0.20 0.005 80);
112
129
  --color-code-fg: oklch(0.88 0.005 80);
130
+
131
+ /* Syntax highlighting (issue #53) — derived from remarque-dark's
132
+ ANSI colors; see the light block above for the full rationale. */
133
+ --color-syntax-keyword: oklch(0.61 0.11 250); /* ANSI blue — 4.80:1 on code-bg */
134
+ --color-syntax-string: oklch(0.60 0.11 145); /* ANSI green — 4.80:1 */
135
+ --color-syntax-constant: oklch(0.61 0.11 84); /* ANSI yellow — 4.73:1 */
136
+ --color-syntax-comment: oklch(0.60 0.005 80); /* ANSI brightBlack — 4.59:1 */
137
+ --color-syntax-function: oklch(0.62 0.11 310); /* ANSI purple — 4.75:1 */
138
+ --color-syntax-type: oklch(0.60 0.10 195); /* ANSI cyan — 4.81:1 */
139
+ --color-syntax-punctuation: oklch(0.60 0.005 80); /* derived neutral, muted family — 4.59:1 */
140
+ --color-syntax-variable: oklch(0.82 0.005 80); /* fg-adjacent (slight L offset) — 10.37:1 */
141
+ --color-syntax-link: var(--color-accent); /* accent already clears code-bg */
113
142
  }
114
143
  }
115
144
 
@@ -141,4 +170,16 @@
141
170
  --color-selection-fg: oklch(0.92 0.005 80);
142
171
  --color-code-bg: oklch(0.20 0.005 80);
143
172
  --color-code-fg: oklch(0.88 0.005 80);
173
+
174
+ /* Syntax highlighting (issue #53) — derived from remarque-dark's
175
+ ANSI colors; see the light block above for the full rationale. */
176
+ --color-syntax-keyword: oklch(0.61 0.11 250);
177
+ --color-syntax-string: oklch(0.60 0.11 145);
178
+ --color-syntax-constant: oklch(0.61 0.11 84);
179
+ --color-syntax-comment: oklch(0.60 0.005 80);
180
+ --color-syntax-function: oklch(0.62 0.11 310);
181
+ --color-syntax-type: oklch(0.60 0.10 195);
182
+ --color-syntax-punctuation: oklch(0.60 0.005 80);
183
+ --color-syntax-variable: oklch(0.82 0.005 80);
184
+ --color-syntax-link: var(--color-accent);
144
185
  }
package/tokens.d.ts ADDED
@@ -0,0 +1,344 @@
1
+ /*
2
+ * Remarque design tokens — GENERATED from tokens.json by
3
+ * scripts/tokens-json.mjs (v0.13.0). Do not edit — the CSS
4
+ * (tokens-core.css + tokens-palette.css) is the source of truth;
5
+ * tokens.json is the intermediate machine-readable form this file is
6
+ * generated from. Regenerate with: node scripts/tokens-json.mjs
7
+ *
8
+ * This package has no JS entry point — these types exist for editors
9
+ * and TypeScript consumers that either author Remarque token names
10
+ * (RemarqueToken, RemarqueCssVar) or read tokens.json programmatically
11
+ * (RemarqueTokensFile / RemarqueTokenValues, and the ambient module
12
+ * declaration below for `import tokens from 'remarque-tokens/tokens.json'`).
13
+ */
14
+
15
+ /** All core-tier token names (tokens-core.css). Never overridden. */
16
+ export type RemarqueCoreToken =
17
+ | 'text-display'
18
+ | 'text-title'
19
+ | 'text-section'
20
+ | 'text-body-lg'
21
+ | 'text-body'
22
+ | 'text-meta'
23
+ | 'text-micro'
24
+ | 'leading-display'
25
+ | 'leading-title'
26
+ | 'leading-section'
27
+ | 'leading-body'
28
+ | 'leading-meta'
29
+ | 'tracking-display'
30
+ | 'tracking-title'
31
+ | 'tracking-body'
32
+ | 'tracking-meta'
33
+ | 'tracking-caps'
34
+ | 'weight-regular'
35
+ | 'weight-medium'
36
+ | 'weight-semibold'
37
+ | 'space-1'
38
+ | 'space-2'
39
+ | 'space-3'
40
+ | 'space-4'
41
+ | 'space-5'
42
+ | 'space-6'
43
+ | 'space-7'
44
+ | 'space-8'
45
+ | 'space-9'
46
+ | 'space-10'
47
+ | 'space-11'
48
+ | 'space-12'
49
+ | 'content-standard'
50
+ | 'content-wide'
51
+ | 'radius-sm'
52
+ | 'radius-md'
53
+ | 'radius-none'
54
+ | 'border-width'
55
+ | 'border-style'
56
+ | 'motion-fast'
57
+ | 'motion-normal'
58
+ | 'motion-easing';
59
+
60
+ /** All palette-tier token names (tokens-palette.css). The sanctioned personalization surface. */
61
+ export type RemarquePaletteToken =
62
+ | 'font-display'
63
+ | 'font-body'
64
+ | 'font-mono'
65
+ | 'content-reading'
66
+ | 'weight-display'
67
+ | 'color-bg'
68
+ | 'color-bg-subtle'
69
+ | 'color-fg'
70
+ | 'color-fg-muted'
71
+ | 'color-muted'
72
+ | 'color-border'
73
+ | 'color-border-bold'
74
+ | 'color-surface'
75
+ | 'color-accent'
76
+ | 'color-accent-hover'
77
+ | 'color-accent-subtle'
78
+ | 'color-link'
79
+ | 'color-link-hover'
80
+ | 'color-focus-ring'
81
+ | 'color-selection-bg'
82
+ | 'color-selection-fg'
83
+ | 'color-code-bg'
84
+ | 'color-code-fg'
85
+ | 'color-syntax-keyword'
86
+ | 'color-syntax-string'
87
+ | 'color-syntax-constant'
88
+ | 'color-syntax-comment'
89
+ | 'color-syntax-function'
90
+ | 'color-syntax-type'
91
+ | 'color-syntax-punctuation'
92
+ | 'color-syntax-variable'
93
+ | 'color-syntax-link';
94
+
95
+ /** Every Remarque token name, core + palette. */
96
+ export type RemarqueToken = RemarqueCoreToken | RemarquePaletteToken;
97
+
98
+ /** A Remarque token name as its CSS custom-property form, e.g. `--text-body`. */
99
+ export type RemarqueCssVar = `--${RemarqueToken}`;
100
+
101
+ /** W3C design-tokens `$type` values used across tokens.json. */
102
+ export type RemarqueTokenType =
103
+ | 'color'
104
+ | 'dimension'
105
+ | 'number'
106
+ | 'fontFamily'
107
+ | 'fontWeight'
108
+ | 'duration'
109
+ | 'string';
110
+
111
+ /** Shape of a single core-tier entry in tokens.json's `core` map. */
112
+ export interface RemarqueCoreTokenEntry {
113
+ readonly $value: string | number;
114
+ readonly $type: RemarqueTokenType;
115
+ }
116
+
117
+ /** Shape of one theme side (`light`/`dark`) of a palette-tier entry. */
118
+ export interface RemarquePaletteTokenSideValue {
119
+ readonly $value: string | number;
120
+ readonly $extensions?: {
121
+ readonly remarque?: {
122
+ readonly inheritedFromLight?: boolean;
123
+ };
124
+ };
125
+ }
126
+
127
+ /** Shape of a single palette-tier entry in tokens.json's `palette` map. */
128
+ export interface RemarquePaletteTokenEntry {
129
+ readonly $type: RemarqueTokenType;
130
+ readonly light: RemarquePaletteTokenSideValue;
131
+ readonly dark: RemarquePaletteTokenSideValue;
132
+ }
133
+
134
+ /**
135
+ * Typed shape of tokens.json's default export — the whole generated
136
+ * file, structurally. Use this when reading tokens.json dynamically
137
+ * (`JSON.parse`, a bundler JSON import without the ambient module
138
+ * below applying, etc).
139
+ */
140
+ export interface RemarqueTokensFile {
141
+ readonly $description: string;
142
+ readonly $extensions: {
143
+ readonly remarque: {
144
+ readonly version: string;
145
+ readonly tiers: {
146
+ readonly core: string;
147
+ readonly palette: string;
148
+ };
149
+ };
150
+ };
151
+ readonly core: { readonly [K in RemarqueCoreToken]: RemarqueCoreTokenEntry };
152
+ readonly palette: { readonly [K in RemarquePaletteToken]: RemarquePaletteTokenEntry };
153
+ }
154
+
155
+ /**
156
+ * The actual current value of every token, as literal types — precise
157
+ * autocomplete for "what is `--text-body` right now", not just "what
158
+ * are its shape and type". Regenerated alongside tokens.json, so these
159
+ * literals track the live CSS across releases.
160
+ */
161
+ export interface RemarqueTokenValues {
162
+ readonly core: {
163
+ readonly 'text-display': "clamp(2.75rem, 5.5vw, 5rem)";
164
+ readonly 'text-title': "clamp(1.875rem, 3.5vw, 3rem)";
165
+ readonly 'text-section': "clamp(1.375rem, 2.25vw, 2rem)";
166
+ readonly 'text-body-lg': "1.1875rem";
167
+ readonly 'text-body': "1.0625rem";
168
+ readonly 'text-meta': "0.875rem";
169
+ readonly 'text-micro': "0.8125rem";
170
+ readonly 'leading-display': 1.05;
171
+ readonly 'leading-title': 1.15;
172
+ readonly 'leading-section': 1.2;
173
+ readonly 'leading-body': 1.75;
174
+ readonly 'leading-meta': 1.5;
175
+ readonly 'tracking-display': "-0.02em";
176
+ readonly 'tracking-title': "-0.015em";
177
+ readonly 'tracking-body': "0em";
178
+ readonly 'tracking-meta': "0.02em";
179
+ readonly 'tracking-caps': "0.06em";
180
+ readonly 'weight-regular': 400;
181
+ readonly 'weight-medium': 500;
182
+ readonly 'weight-semibold': 600;
183
+ readonly 'space-1': "0.25rem";
184
+ readonly 'space-2': "0.5rem";
185
+ readonly 'space-3': "0.75rem";
186
+ readonly 'space-4': "1rem";
187
+ readonly 'space-5': "1.5rem";
188
+ readonly 'space-6': "2rem";
189
+ readonly 'space-7': "3rem";
190
+ readonly 'space-8': "4rem";
191
+ readonly 'space-9': "6rem";
192
+ readonly 'space-10': "8rem";
193
+ readonly 'space-11': "10rem";
194
+ readonly 'space-12': "12rem";
195
+ readonly 'content-standard': "72rem";
196
+ readonly 'content-wide': "88rem";
197
+ readonly 'radius-sm': "0.25rem";
198
+ readonly 'radius-md': "0.5rem";
199
+ readonly 'radius-none': "0";
200
+ readonly 'border-width': "1px";
201
+ readonly 'border-style': "solid";
202
+ readonly 'motion-fast': "120ms";
203
+ readonly 'motion-normal': "180ms";
204
+ readonly 'motion-easing': "ease-out";
205
+ };
206
+ readonly palette: {
207
+ readonly 'font-display': {
208
+ readonly light: "\"Newsreader\", Georgia, \"Times New Roman\", serif";
209
+ readonly dark: "\"Newsreader\", Georgia, \"Times New Roman\", serif";
210
+ };
211
+ readonly 'font-body': {
212
+ readonly light: "\"Inter\", ui-sans-serif, system-ui, -apple-system, sans-serif";
213
+ readonly dark: "\"Inter\", ui-sans-serif, system-ui, -apple-system, sans-serif";
214
+ };
215
+ readonly 'font-mono': {
216
+ readonly light: "\"JetBrains Mono\", ui-monospace, \"Cascadia Code\", \"Fira Code\", monospace";
217
+ readonly dark: "\"JetBrains Mono\", ui-monospace, \"Cascadia Code\", \"Fira Code\", monospace";
218
+ };
219
+ readonly 'content-reading': {
220
+ readonly light: "46rem";
221
+ readonly dark: "46rem";
222
+ };
223
+ readonly 'weight-display': {
224
+ readonly light: 400;
225
+ readonly dark: 500;
226
+ };
227
+ readonly 'color-bg': {
228
+ readonly light: "oklch(0.975 0.005 80)";
229
+ readonly dark: "oklch(0.16 0.01 80)";
230
+ };
231
+ readonly 'color-bg-subtle': {
232
+ readonly light: "oklch(0.955 0.005 80)";
233
+ readonly dark: "oklch(0.19 0.01 80)";
234
+ };
235
+ readonly 'color-fg': {
236
+ readonly light: "oklch(0.18 0.01 80)";
237
+ readonly dark: "oklch(0.90 0.005 80)";
238
+ };
239
+ readonly 'color-fg-muted': {
240
+ readonly light: "oklch(0.43 0.015 80)";
241
+ readonly dark: "oklch(0.70 0.01 80)";
242
+ };
243
+ readonly 'color-muted': {
244
+ readonly light: "oklch(0.54 0.01 80)";
245
+ readonly dark: "oklch(0.60 0.01 80)";
246
+ };
247
+ readonly 'color-border': {
248
+ readonly light: "oklch(0.88 0.005 80)";
249
+ readonly dark: "oklch(0.25 0.005 80)";
250
+ };
251
+ readonly 'color-border-bold': {
252
+ readonly light: "oklch(0.62 0.01 80)";
253
+ readonly dark: "oklch(0.50 0.01 80)";
254
+ };
255
+ readonly 'color-surface': {
256
+ readonly light: "oklch(0.965 0.005 80)";
257
+ readonly dark: "oklch(0.19 0.01 80)";
258
+ };
259
+ readonly 'color-accent': {
260
+ readonly light: "oklch(0.50 0.14 250)";
261
+ readonly dark: "oklch(0.68 0.12 250)";
262
+ };
263
+ readonly 'color-accent-hover': {
264
+ readonly light: "oklch(0.42 0.11 250)";
265
+ readonly dark: "oklch(0.75 0.12 250)";
266
+ };
267
+ readonly 'color-accent-subtle': {
268
+ readonly light: "oklch(0.95 0.02 250)";
269
+ readonly dark: "oklch(0.22 0.04 250)";
270
+ };
271
+ readonly 'color-link': {
272
+ readonly light: "var(--color-accent)";
273
+ readonly dark: "var(--color-accent)";
274
+ };
275
+ readonly 'color-link-hover': {
276
+ readonly light: "var(--color-accent-hover)";
277
+ readonly dark: "var(--color-accent-hover)";
278
+ };
279
+ readonly 'color-focus-ring': {
280
+ readonly light: "var(--color-accent)";
281
+ readonly dark: "var(--color-accent)";
282
+ };
283
+ readonly 'color-selection-bg': {
284
+ readonly light: "oklch(0.92 0.04 250)";
285
+ readonly dark: "oklch(0.30 0.06 250)";
286
+ };
287
+ readonly 'color-selection-fg': {
288
+ readonly light: "var(--color-fg)";
289
+ readonly dark: "oklch(0.92 0.005 80)";
290
+ };
291
+ readonly 'color-code-bg': {
292
+ readonly light: "oklch(0.945 0.005 80)";
293
+ readonly dark: "oklch(0.20 0.005 80)";
294
+ };
295
+ readonly 'color-code-fg': {
296
+ readonly light: "var(--color-fg)";
297
+ readonly dark: "oklch(0.88 0.005 80)";
298
+ };
299
+ readonly 'color-syntax-keyword': {
300
+ readonly light: "oklch(0.51 0.12 250)";
301
+ readonly dark: "oklch(0.61 0.11 250)";
302
+ };
303
+ readonly 'color-syntax-string': {
304
+ readonly light: "oklch(0.50 0.12 145)";
305
+ readonly dark: "oklch(0.60 0.11 145)";
306
+ };
307
+ readonly 'color-syntax-constant': {
308
+ readonly light: "oklch(0.51 0.105 85)";
309
+ readonly dark: "oklch(0.61 0.11 84)";
310
+ };
311
+ readonly 'color-syntax-comment': {
312
+ readonly light: "oklch(0.52 0.01 80)";
313
+ readonly dark: "oklch(0.60 0.005 80)";
314
+ };
315
+ readonly 'color-syntax-function': {
316
+ readonly light: "oklch(0.52 0.12 310)";
317
+ readonly dark: "oklch(0.62 0.11 310)";
318
+ };
319
+ readonly 'color-syntax-type': {
320
+ readonly light: "oklch(0.50 0.085 196)";
321
+ readonly dark: "oklch(0.60 0.10 195)";
322
+ };
323
+ readonly 'color-syntax-punctuation': {
324
+ readonly light: "oklch(0.52 0.01 80)";
325
+ readonly dark: "oklch(0.60 0.005 80)";
326
+ };
327
+ readonly 'color-syntax-variable': {
328
+ readonly light: "oklch(0.26 0.01 80)";
329
+ readonly dark: "oklch(0.82 0.005 80)";
330
+ };
331
+ readonly 'color-syntax-link': {
332
+ readonly light: "var(--color-accent)";
333
+ readonly dark: "var(--color-accent)";
334
+ };
335
+ };
336
+ }
337
+
338
+ // Precise types for `import tokens from 'remarque-tokens/tokens.json'`
339
+ // (and any other bundler-JSON-import path resolving through this
340
+ // subpath) without depending on the consumer's `resolveJsonModule`.
341
+ declare module 'remarque-tokens/tokens.json' {
342
+ const tokens: RemarqueTokensFile;
343
+ export default tokens;
344
+ }
package/tokens.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$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.",
3
3
  "$extensions": {
4
4
  "remarque": {
5
- "version": "0.11.0",
5
+ "version": "0.13.0",
6
6
  "tiers": {
7
7
  "core": "immutable identity — overriding forks the system",
8
8
  "palette": "sanctioned personalization surface — override freely, then run remarque-audit"
@@ -421,6 +421,87 @@
421
421
  "dark": {
422
422
  "$value": "oklch(0.88 0.005 80)"
423
423
  }
424
+ },
425
+ "color-syntax-keyword": {
426
+ "$type": "color",
427
+ "light": {
428
+ "$value": "oklch(0.51 0.12 250)"
429
+ },
430
+ "dark": {
431
+ "$value": "oklch(0.61 0.11 250)"
432
+ }
433
+ },
434
+ "color-syntax-string": {
435
+ "$type": "color",
436
+ "light": {
437
+ "$value": "oklch(0.50 0.12 145)"
438
+ },
439
+ "dark": {
440
+ "$value": "oklch(0.60 0.11 145)"
441
+ }
442
+ },
443
+ "color-syntax-constant": {
444
+ "$type": "color",
445
+ "light": {
446
+ "$value": "oklch(0.51 0.105 85)"
447
+ },
448
+ "dark": {
449
+ "$value": "oklch(0.61 0.11 84)"
450
+ }
451
+ },
452
+ "color-syntax-comment": {
453
+ "$type": "color",
454
+ "light": {
455
+ "$value": "oklch(0.52 0.01 80)"
456
+ },
457
+ "dark": {
458
+ "$value": "oklch(0.60 0.005 80)"
459
+ }
460
+ },
461
+ "color-syntax-function": {
462
+ "$type": "color",
463
+ "light": {
464
+ "$value": "oklch(0.52 0.12 310)"
465
+ },
466
+ "dark": {
467
+ "$value": "oklch(0.62 0.11 310)"
468
+ }
469
+ },
470
+ "color-syntax-type": {
471
+ "$type": "color",
472
+ "light": {
473
+ "$value": "oklch(0.50 0.085 196)"
474
+ },
475
+ "dark": {
476
+ "$value": "oklch(0.60 0.10 195)"
477
+ }
478
+ },
479
+ "color-syntax-punctuation": {
480
+ "$type": "color",
481
+ "light": {
482
+ "$value": "oklch(0.52 0.01 80)"
483
+ },
484
+ "dark": {
485
+ "$value": "oklch(0.60 0.005 80)"
486
+ }
487
+ },
488
+ "color-syntax-variable": {
489
+ "$type": "color",
490
+ "light": {
491
+ "$value": "oklch(0.26 0.01 80)"
492
+ },
493
+ "dark": {
494
+ "$value": "oklch(0.82 0.005 80)"
495
+ }
496
+ },
497
+ "color-syntax-link": {
498
+ "$type": "color",
499
+ "light": {
500
+ "$value": "var(--color-accent)"
501
+ },
502
+ "dark": {
503
+ "$value": "var(--color-accent)"
504
+ }
424
505
  }
425
506
  }
426
507
  }