remarque-tokens 0.10.0 → 0.12.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/AGENT_RULES.md +1 -0
- package/CHANGELOG.md +99 -0
- package/README.md +7 -0
- package/REMARQUE.md +3 -1
- package/package.json +14 -4
- package/scripts/theme.mjs +87 -23
- package/scripts/tokens-json.mjs +156 -10
- package/tokens.d.ts +299 -0
- package/tokens.json +1 -1
package/AGENT_RULES.md
CHANGED
|
@@ -42,6 +42,7 @@ These are not guidelines. Agents must follow them literally.
|
|
|
42
42
|
- Site personalization happens ONLY by overriding `tokens-palette.css` tokens (font slots from the approved pairings, colors, accent, `--content-reading` per the measure table) in a stylesheet loaded after the tokens
|
|
43
43
|
- After ANY palette change, run the audit and fix every failure before shipping — `npm run audit` inside this repo; `npx remarque-audit --palette <file> --src <dir>` in a consumer project (the npm script only exists here)
|
|
44
44
|
- A generated palette override (from `npx remarque-theme <light> --dark <dark>`, see REMARQUE.md "Color Providers") is regenerated, never hand-edited — if it needs to change, change the source theme slugs and re-run `remarque-theme`, don't patch the emitted CSS directly
|
|
45
|
+
- The DEFAULT palette in `tokens-palette.css` is bound to the upstream `remarque-light`/`remarque-dark` themes (REMARQUE.md "Color Providers" identity/serialization contract) — `node scripts/palette-golden.mjs` must stay green. Changing a default color means changing the upstream themes first (or in lockstep with the hand file), never editing `tokens-palette.css` alone to chase a design change; a value edited only on this side is drift, not personalization, and the golden gate exists to catch it
|
|
45
46
|
|
|
46
47
|
### Typography
|
|
47
48
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,105 @@ 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.12.0 — 2026-07-23
|
|
8
|
+
|
|
9
|
+
TypeScript types and a README "Used By" section (closes #34, closes #35).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`tokens.d.ts`** — generated from `tokens.json` by `scripts/tokens-json.mjs`
|
|
13
|
+
(regenerates in the same run as `tokens.json`; `--check` verifies both are
|
|
14
|
+
fresh, wired into the same `deploy.yml` CI step). Ships literal-union types
|
|
15
|
+
for every token name (`RemarqueCoreToken`, `RemarquePaletteToken`,
|
|
16
|
+
`RemarqueToken`), a `RemarqueCssVar` template-literal type
|
|
17
|
+
(`` `--${RemarqueToken}` ``), structural interfaces matching tokens.json's
|
|
18
|
+
actual shape (`RemarqueTokensFile`, `RemarqueCoreTokenEntry`,
|
|
19
|
+
`RemarquePaletteTokenEntry`), a `RemarqueTokenValues` interface with the
|
|
20
|
+
live value of every token as a literal type, and an ambient
|
|
21
|
+
`declare module 'remarque-tokens/tokens.json'` so
|
|
22
|
+
`import tokens from 'remarque-tokens/tokens.json'` is precisely typed
|
|
23
|
+
without depending on the consumer's `resolveJsonModule`. This package has
|
|
24
|
+
no JS entry point — the types exist for editors/TS consumers who author
|
|
25
|
+
Remarque token names or read `tokens.json` programmatically.
|
|
26
|
+
`package.json` wires `types`, an `./tokens.d.ts` export, and a `types`
|
|
27
|
+
condition on the existing `./tokens.json` export; `scripts/test-types.mjs`
|
|
28
|
+
gates token-name coverage, the exported type surface, and the
|
|
29
|
+
`package.json` wiring (string assertions, no `typescript` dependency
|
|
30
|
+
added).
|
|
31
|
+
- **README "Used By" section** — the demo site, `williamzujkowski.github.io`
|
|
32
|
+
(core-tier npm consumer), `tsundoku` (full-palette npm consumer, custom
|
|
33
|
+
accent hue), and `remarque-starter` (template repo), one line each on how
|
|
34
|
+
they consume the package.
|
|
35
|
+
|
|
36
|
+
## 0.11.0 — 2026-07-23
|
|
37
|
+
|
|
38
|
+
Palette golden gate: the default palette is now bound, by CI, to the
|
|
39
|
+
upstream `remarque-light`/`remarque-dark` themes (Phase 3 of the
|
|
40
|
+
color-provider integration, closes #76; consensus-ratified 3-0,
|
|
41
|
+
2026-07-23).
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- **`remarque-theme`: lightness is now KEEP-IF-PASSING, not always
|
|
45
|
+
solved.** For `fg`, `accent`, `accent-hover`, and (in dark)
|
|
46
|
+
`selection-fg`/`code-fg`, the bridge now keeps the theme's own
|
|
47
|
+
authored lightness when it already clears the slot's contrast target,
|
|
48
|
+
and only solves (binary search) when it doesn't. Previously every one
|
|
49
|
+
of these slots was solved unconditionally — e.g. `fg` always landed
|
|
50
|
+
exactly on the 10:1 threshold, flattening a well-designed theme's own
|
|
51
|
+
~13:1+ contrast down to the floor. The `fg` target itself also drops
|
|
52
|
+
from 10:1 to 7:1 (the actual AAA line this slot exists to clear) when
|
|
53
|
+
a solve is needed at all. **This changes derived output for every
|
|
54
|
+
existing theme pairing** — most of the 61-pair corpus shifts at least
|
|
55
|
+
one slot (see the PR for the full before/after) — hence the minor
|
|
56
|
+
bump rather than a patch. Every pair still passes `remarque-audit`;
|
|
57
|
+
the corpus property test (`test-theme.mjs`) stays green.
|
|
58
|
+
- **`fg-muted` and `border-bold` gain contrast headroom over their solved
|
|
59
|
+
minimums** (−0.01 L and −0.02 L further from the background,
|
|
60
|
+
respectively; mirrored in dark). Slots that exist to clear a legal line
|
|
61
|
+
shouldn't sit exactly on it: borders at 3.0:1 are fragile against
|
|
62
|
+
antialiasing, AAA text at 7.0:1 has no margin for rendering variance.
|
|
63
|
+
This encodes the same headroom the hand-authored default has always
|
|
64
|
+
carried (border-bold at 3.39:1, fg-muted at 7.55:1) into the
|
|
65
|
+
derivation itself — it's what reconciled the golden gate's last two
|
|
66
|
+
outliers without loosening the ΔE threshold.
|
|
67
|
+
- All other slots (`muted`, the bg ladder, `selection-bg`,
|
|
68
|
+
`accent-subtle`) are unaffected — solved/derived unconditionally, same
|
|
69
|
+
as before.
|
|
70
|
+
- **Accent selection now prefers the dataset's `accent` field** (dataset
|
|
71
|
+
0.3.0+, upstream #133) — the same cursor-else-most-chromatic-ANSI
|
|
72
|
+
heuristic, computed upstream where it can be curated per theme. The
|
|
73
|
+
local heuristic remains as fallback for older datasets. Dev pin bumped
|
|
74
|
+
`0.2.0` → `0.3.0`, whose native themes are OKLCH-authored (upstream
|
|
75
|
+
#132): the Remarque themes now carry the design palette's exact
|
|
76
|
+
values, which is what lets the golden gate below hold at ΔE ≤ 2.0
|
|
77
|
+
with most tokens exactly 0.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
- **`scripts/palette-golden.mjs`** (new CI gate, wired into
|
|
81
|
+
`deploy.yml` right after the `remarque-theme` corpus test): derives
|
|
82
|
+
the reference palette from the pinned `remarque-light`/`remarque-dark`
|
|
83
|
+
themes and compares every `--color-*` token in both themes against
|
|
84
|
+
the hand-authored `tokens-palette.css`, resolving `var()` chains,
|
|
85
|
+
via CIEDE2000 (`culori`'s `differenceCiede2000` — not hand-rolled ΔE
|
|
86
|
+
math). Asserts ΔE2000 ≤ 2.0 per token and an exact `weight-display`
|
|
87
|
+
match; the full per-token table prints unconditionally (not just on
|
|
88
|
+
failure) so drift stays visible before it reaches the threshold. A
|
|
89
|
+
breach means the upstream themes and the hand file have diverged —
|
|
90
|
+
reconcile one against the other, the gate does not loosen.
|
|
91
|
+
- **Site mirror check**, same script: asserts every `--color-*` value in
|
|
92
|
+
`site/src/styles/globals.css`'s `[data-theme="light"]` block is
|
|
93
|
+
string-identical (whitespace-normalized) to `tokens-palette.css`'s
|
|
94
|
+
`:root` light value — the known drift trap called out in #76.
|
|
95
|
+
- `culori` added as a devDependency (already present transitively via
|
|
96
|
+
`@williamzujkowski/oklch-terminal-themes`; now pinned directly since
|
|
97
|
+
`palette-golden.mjs` imports it).
|
|
98
|
+
- REMARQUE.md "Color Providers": documents the identity/serialization
|
|
99
|
+
contract — the upstream themes are the source of truth for the
|
|
100
|
+
default palette's identity, `tokens-palette.css` is its human-authored
|
|
101
|
+
serialization, and the golden gate is what keeps them from drifting
|
|
102
|
+
apart. AGENT_RULES.md: palette color changes must keep the golden
|
|
103
|
+
gate green; changing a default color means changing the upstream
|
|
104
|
+
themes first (or in lockstep), not hand-editing the CSS alone.
|
|
105
|
+
|
|
7
106
|
## 0.10.0 — 2026-07-23
|
|
8
107
|
|
|
9
108
|
Color-provider Phase 2 groundwork: the upstream dataset now pairs its own
|
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,12 +80,14 @@ 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
|
|
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.
|
|
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
|
|
|
87
87
|
**Pairing contract:** dataset 0.2.0+ carries a `counterpart` field linking each paired theme to its canonical opposite-polarity variant ([`oklch-terminal-themes#128`](https://github.com/williamzujkowski/oklch-terminal-themes/issues/128)), and `--dark` defaults to it — `npx remarque-theme rose-pine-dawn` alone derives the rosé-pine pair. For a light theme without a counterpart (or an older dataset), `--dark <slug>` remains required; nothing is ever guessed from names. `remarque-theme` also rejects a light slug that isn't tagged `isDark: false` and a dark slug that isn't tagged `isDark: true`.
|
|
88
88
|
|
|
89
|
+
**Identity/serialization contract (#76):** the default palette shipped in `tokens-palette.css` is not an independent hand-design — its **identity** is bound to the upstream `remarque-light`/`remarque-dark` native themes in `@williamzujkowski/oklch-terminal-themes` (pinned exact devDependency), derived through the same `remarque-theme` bridge as any other consumer's palette. `tokens-palette.css` itself is that identity's **serialization**: a stable, human-authored file with designed round numbers and hand comments, kept perceptually identical to what the bridge derives. `scripts/palette-golden.mjs` is the CI gate that proves this — it derives the reference palette from the pinned themes, compares every `--color-*` token in both files via CIEDE2000 (ΔE2000 ≤ 2.0, reported per token, always printed so drift stays visible before it reaches the threshold), and separately asserts that `site/src/styles/globals.css`'s `[data-theme="light"]` mirror block stays string-identical to `tokens-palette.css`'s `:root` light value (a known drift trap). A breach means the upstream themes and the hand file have diverged — reconcile one against the other; the gate does not loosen.
|
|
90
|
+
|
|
89
91
|
---
|
|
90
92
|
|
|
91
93
|
## Font Slots
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remarque-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
"drift": "node scripts/drift-check.mjs"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@williamzujkowski/oklch-terminal-themes": "0.
|
|
35
|
+
"@williamzujkowski/oklch-terminal-themes": "0.3.0",
|
|
36
|
+
"culori": "^4.0.2"
|
|
35
37
|
},
|
|
36
38
|
"peerDependencies": {
|
|
37
39
|
"@williamzujkowski/oklch-terminal-themes": ">=0.1.0 <0.3.0"
|
|
@@ -42,7 +44,10 @@
|
|
|
42
44
|
}
|
|
43
45
|
},
|
|
44
46
|
"exports": {
|
|
45
|
-
".":
|
|
47
|
+
".": {
|
|
48
|
+
"types": "./tokens.d.ts",
|
|
49
|
+
"default": "./tokens.css"
|
|
50
|
+
},
|
|
46
51
|
"./tokens": "./tokens.css",
|
|
47
52
|
"./tokens.css": "./tokens.css",
|
|
48
53
|
"./core": "./tokens-core.css",
|
|
@@ -54,7 +59,11 @@
|
|
|
54
59
|
"./tailwind": "./tailwind.config.js",
|
|
55
60
|
"./theme.css": "./theme.css",
|
|
56
61
|
"./theme": "./theme.css",
|
|
57
|
-
"./tokens.json":
|
|
62
|
+
"./tokens.json": {
|
|
63
|
+
"types": "./tokens.d.ts",
|
|
64
|
+
"default": "./tokens.json"
|
|
65
|
+
},
|
|
66
|
+
"./tokens.d.ts": "./tokens.d.ts",
|
|
58
67
|
"./prose": "./prose.css",
|
|
59
68
|
"./prose.css": "./prose.css",
|
|
60
69
|
"./print": "./print.css",
|
|
@@ -79,6 +88,7 @@
|
|
|
79
88
|
"CHANGELOG.md",
|
|
80
89
|
"theme.css",
|
|
81
90
|
"tokens.json",
|
|
91
|
+
"tokens.d.ts",
|
|
82
92
|
"scripts/tokens-json.mjs",
|
|
83
93
|
"prose.css",
|
|
84
94
|
"print.css",
|
package/scripts/theme.mjs
CHANGED
|
@@ -10,9 +10,16 @@
|
|
|
10
10
|
* ANSI slots — most of Remarque's 15 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
|
-
* personality)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* personality); lightness is KEEP-IF-PASSING — a few load-bearing slots
|
|
14
|
+
* (fg, accent, accent-hover, and dark's selection-fg/code-fg) keep the
|
|
15
|
+
* theme's own authored lightness when it already clears the slot's
|
|
16
|
+
* ratio target, and are solved by binary search ONLY when it doesn't.
|
|
17
|
+
* A well-designed input theme comes through close to its own feel
|
|
18
|
+
* instead of being flattened to the exact threshold; a theme that fails
|
|
19
|
+
* still gets a value that passes, same as before (#76). Every other
|
|
20
|
+
* slot (fg-muted, muted, border-bold, the bg ladder, selection-bg,
|
|
21
|
+
* accent-subtle) is solved/derived as before — those don't have a
|
|
22
|
+
* meaningful "theme's own value" to preserve. Output passes
|
|
16
23
|
* remarque-audit BY CONSTRUCTION — this script self-verifies the same
|
|
17
24
|
* pairings before it will emit anything.
|
|
18
25
|
*
|
|
@@ -254,24 +261,51 @@ function solveL(C, H, bg, target, dir) {
|
|
|
254
261
|
);
|
|
255
262
|
}
|
|
256
263
|
|
|
257
|
-
/* Accent
|
|
264
|
+
/* Accent source: cursor if it's chromatic, else the most-chromatic classic
|
|
265
|
+
* ANSI. Carries l too (not just h/c) — keep-if-passing needs the source's
|
|
266
|
+
* own lightness as the candidate to test before falling back to solveL. */
|
|
258
267
|
function accentHue(theme, label) {
|
|
268
|
+
// Dataset 0.3.0+ publishes a curated/computed accent per theme — prefer
|
|
269
|
+
// it (it's this same heuristic, computed upstream where it can be
|
|
270
|
+
// curated per-theme). Validated like any other theme color; the local
|
|
271
|
+
// heuristic below stays as the fallback for older datasets.
|
|
272
|
+
if (theme.accent && theme.accent.oklch) {
|
|
273
|
+
const [l, c, h] = validateOklch(theme.accent.oklch, `${label} accent`);
|
|
274
|
+
return { l, c, h, from: `accent:${String(theme.accent.source)}` };
|
|
275
|
+
}
|
|
259
276
|
const cursor = theme.colors && theme.colors.cursor && theme.colors.cursor.oklch;
|
|
260
277
|
if (cursor) {
|
|
261
|
-
const [, c, h] = validateOklch(cursor, `${label} colors.cursor`);
|
|
262
|
-
if (c >= 0.05) return { h, c, from: 'cursor' };
|
|
278
|
+
const [l, c, h] = validateOklch(cursor, `${label} colors.cursor`);
|
|
279
|
+
if (c >= 0.05) return { l, h, c, from: 'cursor' };
|
|
263
280
|
}
|
|
264
281
|
const names = ['blue', 'purple', 'red', 'green', 'cyan', 'yellow'];
|
|
265
282
|
const cands = [];
|
|
266
283
|
for (const name of names) {
|
|
267
284
|
const o = theme.colors && theme.colors[name] && theme.colors[name].oklch;
|
|
268
285
|
if (!o) continue;
|
|
269
|
-
const [, c, h] = validateOklch(o, `${label} colors.${name}`);
|
|
270
|
-
cands.push({ name, c, h });
|
|
286
|
+
const [l, c, h] = validateOklch(o, `${label} colors.${name}`);
|
|
287
|
+
cands.push({ name, l, c, h });
|
|
271
288
|
}
|
|
272
289
|
if (!cands.length) die(`${label}: no classic ANSI colors available to derive an accent hue`);
|
|
273
290
|
cands.sort((a, b) => b.c - a.c);
|
|
274
|
-
return { h: cands[0].h, c: cands[0].c, from: cands[0].name };
|
|
291
|
+
return { l: cands[0].l, h: cands[0].h, c: cands[0].c, from: cands[0].name };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Keep-if-passing: if the theme's own (quantized) lightness at this slot
|
|
295
|
+
* already clears `target` against `bg`, keep it (chroma gamut-clamped,
|
|
296
|
+
* hue rounded) — no margin, this is the theme's own value, not a solve.
|
|
297
|
+
* Only when it fails do we fall back to solveL, which keeps its existing
|
|
298
|
+
* ×1.03 search margin. `dir` only matters for that fallback path. */
|
|
299
|
+
function keepOrSolve(L0, C, H, bg, target, dir) {
|
|
300
|
+
// Clamp into the valid domain first — an offset candidate (accent-hover,
|
|
301
|
+
// dark selection-fg/code-fg) can walk L past 0/1 for a theme whose own
|
|
302
|
+
// slot already sits near an extreme; ratio()'s luminance clip() would
|
|
303
|
+
// otherwise let an out-of-[0,1] L "pass" here only to fail the raw
|
|
304
|
+
// gamut check downstream in selfVerify.
|
|
305
|
+
const L = Math.min(1, Math.max(0, r3(L0)));
|
|
306
|
+
const c = fitChroma(L, C, H);
|
|
307
|
+
if (ratio([L, c, H], bg) >= target) return [L, c, r1(H)];
|
|
308
|
+
return solveL(C, H, bg, target, dir);
|
|
275
309
|
}
|
|
276
310
|
|
|
277
311
|
function fmt(triple) {
|
|
@@ -280,6 +314,17 @@ function fmt(triple) {
|
|
|
280
314
|
return `oklch(${L} ${C} ${H})`;
|
|
281
315
|
}
|
|
282
316
|
|
|
317
|
+
/* Push a solved triple further from the background by delta L — headroom
|
|
318
|
+
* over the legal minimum for slots that shouldn't sit at it (functional
|
|
319
|
+
* borders at exactly 3:1 are fragile against antialiasing; AAA text at
|
|
320
|
+
* exactly 7:1 has no margin for rendering variance). Direction is always
|
|
321
|
+
* away from bg, so the ratio only increases — never re-verified against
|
|
322
|
+
* the target, only re-clamped for gamut. */
|
|
323
|
+
function withHeadroom([L0, , H], delta, dir, C) {
|
|
324
|
+
const L = r3(Math.min(1, Math.max(0, dir === 'darker' ? L0 - delta : L0 + delta)));
|
|
325
|
+
return [L, fitChroma(L, C, H), r1(H)];
|
|
326
|
+
}
|
|
327
|
+
|
|
283
328
|
/* ── LIGHT derivation ─────────────────────────────────────────────── */
|
|
284
329
|
|
|
285
330
|
function deriveLight(t) {
|
|
@@ -289,16 +334,23 @@ function deriveLight(t) {
|
|
|
289
334
|
const bgC = fitChroma(bgL, Math.min(bgC0, 0.02), bgH);
|
|
290
335
|
const bg = [bgL, bgC, bgH];
|
|
291
336
|
const surface = [r3(bgL - 0.01), bgC, bgH];
|
|
292
|
-
const [, fgC0, fgH] = slotLch(t, 'foreground', 'light');
|
|
337
|
+
const [fgL0, fgC0, fgH] = slotLch(t, 'foreground', 'light');
|
|
293
338
|
const fgC = Math.min(fgC0, 0.03);
|
|
294
|
-
|
|
295
|
-
|
|
339
|
+
// fg: keep the theme's own foreground lightness if it already clears the
|
|
340
|
+
// AAA floor (7:1) against surface; solve only if it doesn't (#76 — this
|
|
341
|
+
// used to always solve to 10:1, flattening well-designed themes).
|
|
342
|
+
const fg = keepOrSolve(fgL0, fgC, fgH, surface, 7.0, 'darker');
|
|
343
|
+
const fgMuted = withHeadroom(solveL(fgC, fgH, bg, 7.0, 'darker'), 0.01, 'darker', fgC);
|
|
296
344
|
const muted = solveL(Math.min(fgC0, 0.02), fgH, surface, 4.5, 'darker');
|
|
297
|
-
const borderBold = solveL(0.01, bgH, bg, 3.0, 'darker');
|
|
345
|
+
const borderBold = withHeadroom(solveL(0.01, bgH, bg, 3.0, 'darker'), 0.02, 'darker', 0.01);
|
|
298
346
|
const ac = accentHue(t, 'light');
|
|
299
347
|
const acC = Math.min(ac.c, 0.14);
|
|
300
|
-
|
|
301
|
-
|
|
348
|
+
// accent: keep the accent-source's own lightness (cursor or chosen ANSI
|
|
349
|
+
// color) if it already clears 4.5:1 on bg; solve only if it doesn't.
|
|
350
|
+
const accent = keepOrSolve(ac.l, acC, ac.h, bg, 4.5, 'darker');
|
|
351
|
+
// accent-hover: still the designed -0.08 offset from accent, but that
|
|
352
|
+
// offset is verified against 4.5:1 on bg and solve-adjusted if it breaks.
|
|
353
|
+
const accentHover = keepOrSolve(accent[0] - 0.08, acC * 0.8, ac.h, bg, 4.5, 'darker');
|
|
302
354
|
const raw = {
|
|
303
355
|
'color-bg': bg,
|
|
304
356
|
'color-bg-subtle': [r3(bg[0] - 0.02), bg[1], bg[2]],
|
|
@@ -330,21 +382,33 @@ function deriveDark(t, accentHueLight) {
|
|
|
330
382
|
const bgC = fitChroma(bgL, Math.min(bgC0, 0.03), bgH);
|
|
331
383
|
const bg = [bgL, bgC, bgH];
|
|
332
384
|
const surface = [r3(bg[0] + 0.03), bgC, bgH];
|
|
333
|
-
const [, fgC0, fgH] = slotLch(t, 'foreground', 'dark');
|
|
385
|
+
const [fgL0, fgC0, fgH] = slotLch(t, 'foreground', 'dark');
|
|
334
386
|
const fgC = Math.min(fgC0, 0.02);
|
|
335
|
-
|
|
336
|
-
|
|
387
|
+
// fg: keep the theme's own foreground lightness if it already clears the
|
|
388
|
+
// AAA floor (7:1) against surface; solve only if it doesn't (mirrors
|
|
389
|
+
// light — direction flipped, see #76).
|
|
390
|
+
const fg = keepOrSolve(fgL0, fgC, fgH, surface, 7.0, 'lighter');
|
|
391
|
+
const fgMuted = withHeadroom(solveL(fgC, fgH, bg, 7.0, 'lighter'), 0.01, 'lighter', fgC);
|
|
337
392
|
const muted = solveL(fgC, fgH, surface, 4.5, 'lighter');
|
|
338
|
-
const borderBold = solveL(0.01, bgH, bg, 3.0, 'lighter');
|
|
393
|
+
const borderBold = withHeadroom(solveL(0.01, bgH, bg, 3.0, 'lighter'), 0.02, 'lighter', 0.01);
|
|
339
394
|
const ac = accentHue(t, 'dark');
|
|
340
395
|
const h = accentHueLight ?? ac.h; // keep hue consistent across the pair
|
|
341
396
|
const acC = Math.min(ac.c, 0.12);
|
|
342
|
-
|
|
343
|
-
|
|
397
|
+
// accent: keep the accent-source's own lightness if it already clears
|
|
398
|
+
// 4.5:1 on bg; solve only if it doesn't.
|
|
399
|
+
const accent = keepOrSolve(ac.l, acC, h, bg, 4.5, 'lighter');
|
|
400
|
+
// accent-hover: still the designed +0.07 offset from accent, verified
|
|
401
|
+
// against 4.5:1 on bg and solve-adjusted if the offset breaks it.
|
|
402
|
+
const accentHover = keepOrSolve(accent[0] + 0.07, acC, h, bg, 4.5, 'lighter');
|
|
344
403
|
const selBg = [r3(bg[0] + 0.14), fitChroma(r3(bg[0] + 0.14), 0.06, h), r1(h)];
|
|
345
|
-
|
|
404
|
+
// selection-fg: "slightly brighter than fg on selection" — derived from
|
|
405
|
+
// the (kept-or-solved) dark fg's own L/C/H, +0.02 lightness, verified
|
|
406
|
+
// against 4.5:1 on selection-bg and solved only if that breaks it.
|
|
407
|
+
const selFg = keepOrSolve(fg[0] + 0.02, fg[1], fg[2], selBg, 4.5, 'lighter');
|
|
346
408
|
const codeBg = [r3(bg[0] + 0.04), bgC, bgH];
|
|
347
|
-
|
|
409
|
+
// code-fg: "slightly dimmer than fg" — same pattern, -0.02, verified
|
|
410
|
+
// against the AAA 7:1 line on code-bg.
|
|
411
|
+
const codeFg = keepOrSolve(fg[0] - 0.02, fg[1], fg[2], codeBg, 7.0, 'lighter');
|
|
348
412
|
const raw = {
|
|
349
413
|
'color-bg': bg,
|
|
350
414
|
'color-bg-subtle': surface,
|
package/scripts/tokens-json.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/*
|
|
3
|
-
* Generates tokens.json (W3C design-tokens flavored)
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
224
|
+
console.log('tokens.json and tokens.d.ts are fresh ✓');
|
|
80
225
|
} else {
|
|
81
226
|
writeFileSync('tokens.json', json);
|
|
82
|
-
|
|
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
|
}
|
package/tokens.d.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Remarque design tokens — GENERATED from tokens.json by
|
|
3
|
+
* scripts/tokens-json.mjs (v0.12.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
|
+
|
|
86
|
+
/** Every Remarque token name, core + palette. */
|
|
87
|
+
export type RemarqueToken = RemarqueCoreToken | RemarquePaletteToken;
|
|
88
|
+
|
|
89
|
+
/** A Remarque token name as its CSS custom-property form, e.g. `--text-body`. */
|
|
90
|
+
export type RemarqueCssVar = `--${RemarqueToken}`;
|
|
91
|
+
|
|
92
|
+
/** W3C design-tokens `$type` values used across tokens.json. */
|
|
93
|
+
export type RemarqueTokenType =
|
|
94
|
+
| 'color'
|
|
95
|
+
| 'dimension'
|
|
96
|
+
| 'number'
|
|
97
|
+
| 'fontFamily'
|
|
98
|
+
| 'fontWeight'
|
|
99
|
+
| 'duration'
|
|
100
|
+
| 'string';
|
|
101
|
+
|
|
102
|
+
/** Shape of a single core-tier entry in tokens.json's `core` map. */
|
|
103
|
+
export interface RemarqueCoreTokenEntry {
|
|
104
|
+
readonly $value: string | number;
|
|
105
|
+
readonly $type: RemarqueTokenType;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Shape of one theme side (`light`/`dark`) of a palette-tier entry. */
|
|
109
|
+
export interface RemarquePaletteTokenSideValue {
|
|
110
|
+
readonly $value: string | number;
|
|
111
|
+
readonly $extensions?: {
|
|
112
|
+
readonly remarque?: {
|
|
113
|
+
readonly inheritedFromLight?: boolean;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Shape of a single palette-tier entry in tokens.json's `palette` map. */
|
|
119
|
+
export interface RemarquePaletteTokenEntry {
|
|
120
|
+
readonly $type: RemarqueTokenType;
|
|
121
|
+
readonly light: RemarquePaletteTokenSideValue;
|
|
122
|
+
readonly dark: RemarquePaletteTokenSideValue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Typed shape of tokens.json's default export — the whole generated
|
|
127
|
+
* file, structurally. Use this when reading tokens.json dynamically
|
|
128
|
+
* (`JSON.parse`, a bundler JSON import without the ambient module
|
|
129
|
+
* below applying, etc).
|
|
130
|
+
*/
|
|
131
|
+
export interface RemarqueTokensFile {
|
|
132
|
+
readonly $description: string;
|
|
133
|
+
readonly $extensions: {
|
|
134
|
+
readonly remarque: {
|
|
135
|
+
readonly version: string;
|
|
136
|
+
readonly tiers: {
|
|
137
|
+
readonly core: string;
|
|
138
|
+
readonly palette: string;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
readonly core: { readonly [K in RemarqueCoreToken]: RemarqueCoreTokenEntry };
|
|
143
|
+
readonly palette: { readonly [K in RemarquePaletteToken]: RemarquePaletteTokenEntry };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The actual current value of every token, as literal types — precise
|
|
148
|
+
* autocomplete for "what is `--text-body` right now", not just "what
|
|
149
|
+
* are its shape and type". Regenerated alongside tokens.json, so these
|
|
150
|
+
* literals track the live CSS across releases.
|
|
151
|
+
*/
|
|
152
|
+
export interface RemarqueTokenValues {
|
|
153
|
+
readonly core: {
|
|
154
|
+
readonly 'text-display': "clamp(2.75rem, 5.5vw, 5rem)";
|
|
155
|
+
readonly 'text-title': "clamp(1.875rem, 3.5vw, 3rem)";
|
|
156
|
+
readonly 'text-section': "clamp(1.375rem, 2.25vw, 2rem)";
|
|
157
|
+
readonly 'text-body-lg': "1.1875rem";
|
|
158
|
+
readonly 'text-body': "1.0625rem";
|
|
159
|
+
readonly 'text-meta': "0.875rem";
|
|
160
|
+
readonly 'text-micro': "0.8125rem";
|
|
161
|
+
readonly 'leading-display': 1.05;
|
|
162
|
+
readonly 'leading-title': 1.15;
|
|
163
|
+
readonly 'leading-section': 1.2;
|
|
164
|
+
readonly 'leading-body': 1.75;
|
|
165
|
+
readonly 'leading-meta': 1.5;
|
|
166
|
+
readonly 'tracking-display': "-0.02em";
|
|
167
|
+
readonly 'tracking-title': "-0.015em";
|
|
168
|
+
readonly 'tracking-body': "0em";
|
|
169
|
+
readonly 'tracking-meta': "0.02em";
|
|
170
|
+
readonly 'tracking-caps': "0.06em";
|
|
171
|
+
readonly 'weight-regular': 400;
|
|
172
|
+
readonly 'weight-medium': 500;
|
|
173
|
+
readonly 'weight-semibold': 600;
|
|
174
|
+
readonly 'space-1': "0.25rem";
|
|
175
|
+
readonly 'space-2': "0.5rem";
|
|
176
|
+
readonly 'space-3': "0.75rem";
|
|
177
|
+
readonly 'space-4': "1rem";
|
|
178
|
+
readonly 'space-5': "1.5rem";
|
|
179
|
+
readonly 'space-6': "2rem";
|
|
180
|
+
readonly 'space-7': "3rem";
|
|
181
|
+
readonly 'space-8': "4rem";
|
|
182
|
+
readonly 'space-9': "6rem";
|
|
183
|
+
readonly 'space-10': "8rem";
|
|
184
|
+
readonly 'space-11': "10rem";
|
|
185
|
+
readonly 'space-12': "12rem";
|
|
186
|
+
readonly 'content-standard': "72rem";
|
|
187
|
+
readonly 'content-wide': "88rem";
|
|
188
|
+
readonly 'radius-sm': "0.25rem";
|
|
189
|
+
readonly 'radius-md': "0.5rem";
|
|
190
|
+
readonly 'radius-none': "0";
|
|
191
|
+
readonly 'border-width': "1px";
|
|
192
|
+
readonly 'border-style': "solid";
|
|
193
|
+
readonly 'motion-fast': "120ms";
|
|
194
|
+
readonly 'motion-normal': "180ms";
|
|
195
|
+
readonly 'motion-easing': "ease-out";
|
|
196
|
+
};
|
|
197
|
+
readonly palette: {
|
|
198
|
+
readonly 'font-display': {
|
|
199
|
+
readonly light: "\"Newsreader\", Georgia, \"Times New Roman\", serif";
|
|
200
|
+
readonly dark: "\"Newsreader\", Georgia, \"Times New Roman\", serif";
|
|
201
|
+
};
|
|
202
|
+
readonly 'font-body': {
|
|
203
|
+
readonly light: "\"Inter\", ui-sans-serif, system-ui, -apple-system, sans-serif";
|
|
204
|
+
readonly dark: "\"Inter\", ui-sans-serif, system-ui, -apple-system, sans-serif";
|
|
205
|
+
};
|
|
206
|
+
readonly 'font-mono': {
|
|
207
|
+
readonly light: "\"JetBrains Mono\", ui-monospace, \"Cascadia Code\", \"Fira Code\", monospace";
|
|
208
|
+
readonly dark: "\"JetBrains Mono\", ui-monospace, \"Cascadia Code\", \"Fira Code\", monospace";
|
|
209
|
+
};
|
|
210
|
+
readonly 'content-reading': {
|
|
211
|
+
readonly light: "46rem";
|
|
212
|
+
readonly dark: "46rem";
|
|
213
|
+
};
|
|
214
|
+
readonly 'weight-display': {
|
|
215
|
+
readonly light: 400;
|
|
216
|
+
readonly dark: 500;
|
|
217
|
+
};
|
|
218
|
+
readonly 'color-bg': {
|
|
219
|
+
readonly light: "oklch(0.975 0.005 80)";
|
|
220
|
+
readonly dark: "oklch(0.16 0.01 80)";
|
|
221
|
+
};
|
|
222
|
+
readonly 'color-bg-subtle': {
|
|
223
|
+
readonly light: "oklch(0.955 0.005 80)";
|
|
224
|
+
readonly dark: "oklch(0.19 0.01 80)";
|
|
225
|
+
};
|
|
226
|
+
readonly 'color-fg': {
|
|
227
|
+
readonly light: "oklch(0.18 0.01 80)";
|
|
228
|
+
readonly dark: "oklch(0.90 0.005 80)";
|
|
229
|
+
};
|
|
230
|
+
readonly 'color-fg-muted': {
|
|
231
|
+
readonly light: "oklch(0.43 0.015 80)";
|
|
232
|
+
readonly dark: "oklch(0.70 0.01 80)";
|
|
233
|
+
};
|
|
234
|
+
readonly 'color-muted': {
|
|
235
|
+
readonly light: "oklch(0.54 0.01 80)";
|
|
236
|
+
readonly dark: "oklch(0.60 0.01 80)";
|
|
237
|
+
};
|
|
238
|
+
readonly 'color-border': {
|
|
239
|
+
readonly light: "oklch(0.88 0.005 80)";
|
|
240
|
+
readonly dark: "oklch(0.25 0.005 80)";
|
|
241
|
+
};
|
|
242
|
+
readonly 'color-border-bold': {
|
|
243
|
+
readonly light: "oklch(0.62 0.01 80)";
|
|
244
|
+
readonly dark: "oklch(0.50 0.01 80)";
|
|
245
|
+
};
|
|
246
|
+
readonly 'color-surface': {
|
|
247
|
+
readonly light: "oklch(0.965 0.005 80)";
|
|
248
|
+
readonly dark: "oklch(0.19 0.01 80)";
|
|
249
|
+
};
|
|
250
|
+
readonly 'color-accent': {
|
|
251
|
+
readonly light: "oklch(0.50 0.14 250)";
|
|
252
|
+
readonly dark: "oklch(0.68 0.12 250)";
|
|
253
|
+
};
|
|
254
|
+
readonly 'color-accent-hover': {
|
|
255
|
+
readonly light: "oklch(0.42 0.11 250)";
|
|
256
|
+
readonly dark: "oklch(0.75 0.12 250)";
|
|
257
|
+
};
|
|
258
|
+
readonly 'color-accent-subtle': {
|
|
259
|
+
readonly light: "oklch(0.95 0.02 250)";
|
|
260
|
+
readonly dark: "oklch(0.22 0.04 250)";
|
|
261
|
+
};
|
|
262
|
+
readonly 'color-link': {
|
|
263
|
+
readonly light: "var(--color-accent)";
|
|
264
|
+
readonly dark: "var(--color-accent)";
|
|
265
|
+
};
|
|
266
|
+
readonly 'color-link-hover': {
|
|
267
|
+
readonly light: "var(--color-accent-hover)";
|
|
268
|
+
readonly dark: "var(--color-accent-hover)";
|
|
269
|
+
};
|
|
270
|
+
readonly 'color-focus-ring': {
|
|
271
|
+
readonly light: "var(--color-accent)";
|
|
272
|
+
readonly dark: "var(--color-accent)";
|
|
273
|
+
};
|
|
274
|
+
readonly 'color-selection-bg': {
|
|
275
|
+
readonly light: "oklch(0.92 0.04 250)";
|
|
276
|
+
readonly dark: "oklch(0.30 0.06 250)";
|
|
277
|
+
};
|
|
278
|
+
readonly 'color-selection-fg': {
|
|
279
|
+
readonly light: "var(--color-fg)";
|
|
280
|
+
readonly dark: "oklch(0.92 0.005 80)";
|
|
281
|
+
};
|
|
282
|
+
readonly 'color-code-bg': {
|
|
283
|
+
readonly light: "oklch(0.945 0.005 80)";
|
|
284
|
+
readonly dark: "oklch(0.20 0.005 80)";
|
|
285
|
+
};
|
|
286
|
+
readonly 'color-code-fg': {
|
|
287
|
+
readonly light: "var(--color-fg)";
|
|
288
|
+
readonly dark: "oklch(0.88 0.005 80)";
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Precise types for `import tokens from 'remarque-tokens/tokens.json'`
|
|
294
|
+
// (and any other bundler-JSON-import path resolving through this
|
|
295
|
+
// subpath) without depending on the consumer's `resolveJsonModule`.
|
|
296
|
+
declare module 'remarque-tokens/tokens.json' {
|
|
297
|
+
const tokens: RemarqueTokensFile;
|
|
298
|
+
export default tokens;
|
|
299
|
+
}
|
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.
|
|
5
|
+
"version": "0.12.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"
|