remarque-tokens 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/REMARQUE.md +60 -4
- package/broadsheet.css +37 -3
- package/essay.css +16 -0
- package/forms.css +53 -0
- package/package.json +2 -2
- package/prose.css +19 -0
- package/scripts/audit.mjs +9 -0
- package/scripts/lib/css-tokens.mjs +25 -3
- package/scripts/theme.mjs +121 -3
- package/tokens-core.css +36 -0
- package/tokens-palette.css +107 -1
- package/tokens.d.ts +32 -2
- package/tokens.json +55 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to `remarque-tokens` are documented here. Token value
|
|
|
4
4
|
changes always state the design rationale — downstream sites pin against
|
|
5
5
|
these entries when syncing.
|
|
6
6
|
|
|
7
|
+
## 0.22.0 — 2026-07-23
|
|
8
|
+
|
|
9
|
+
Dataviz categorical tokens — bridge-derived, golden-gated (closes #94). The syntax-palette pattern's fourth application, closing the "Dataviz Tokens" section's long-standing "guidance, not a palette" gap now that the upstream dataset ships a per-theme `dataviz` block (`@williamzujkowski/oklch-terminal-themes` 0.5.0).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **6 `--color-viz-1..6` palette-tier slots**, hand-authored in both themes from `remarque-light`/`remarque-dark`'s `dataviz.categorical` block, serialized as round numbers (identity/serialization contract, same as `--color-syntax-*`). 6 slots, not 8 — the dataset's own floor (516 of 633 corpus themes ship exactly 6). Every slot ≥ **3:1** against `--color-bg` in both themes — Carbon's mark-on-background line, not text's 4.5:1 (marks are read at a glance, not continuously like prose). Added to `tokens-palette.css`'s `:root`, `@media (prefers-color-scheme: dark)`, `[data-theme="dark"]`/`:root.dark`, and the demo's `[data-theme="light"]` mirror.
|
|
13
|
+
- **`remarque-theme` dataviz derivation.** Derives all 6 slots from the source theme's own `dataviz.categorical` array (first 6 entries, dataset order, per theme independently — order is **not** reconciled hue-for-hue across a light/dark pair; verified the upstream order itself differs between `remarque-light` and `remarque-dark`, consistent with this file's existing "dark mode is independently tuned, not inverted" stance). Same keep-if-passing-else-solve pattern as every other slot, chroma capped at 0.14. **Fallback:** an installed dataset older than 0.5.0 (still legal under the `>=0.1.0` peerDependencies floor) has no `dataviz` block — `remarque-theme` errors loudly naming the missing field and the upgrade path rather than synthesizing a guessed ramp from raw ANSI colors (the upstream categorical-selection algorithm is undocumented; guessing at it could silently diverge from a real 0.5.0+ dataset).
|
|
14
|
+
- **`remarque-theme --dataviz`** (optional, judgment-call item). Emits `--viz-sequential-N`/`--viz-diverging-N` custom properties from the dataset's `dataviz.sequential`/`.diverging` ramps — gamut-clamped but NOT contrast-solved (a sequential ramp's low end is designed to sit near `--color-bg`) and NOT audited/golden-gated like `--color-viz-1..6` (these are per-use ramps, not identity tokens). Purely additive: omitting the flag leaves the derived `--color-*` output byte-identical to before.
|
|
15
|
+
- **Audit extension** (`scripts/audit.mjs`, `scripts/theme.mjs` self-verify) — 6 new `CHECKS` pairings (`color-viz-1..6` vs `color-bg` at 3.0). `scripts/test-audit.mjs` fixtures updated with the 6 slots plus a must-fail case (`viz-slot-fails.css`) proving the pairings are actually wired in.
|
|
16
|
+
- **Golden gate** (`scripts/palette-golden.mjs`) extended to the 6 new slots — ΔE2000 = 0.000 in both themes (the hand-authored values are literal transcriptions of the dataset's own numbers, unlike the solved slots elsewhere in the palette).
|
|
17
|
+
- **REMARQUE.md "Dataviz Tokens"** rewritten: the 6 shipped tokens with light/dark values and ratios, the 3:1 mark-threshold rationale, a mandatory non-color-redundancy rule (shape/pattern/label — never color alone, per Carbon and tsundoku's precedent), and a documented recipe for sequential/diverging ramps (direct JSON consumption from the dataset, or `remarque-theme --dataviz`). New Enforcement Checklist line.
|
|
18
|
+
- **Demo** (`site/src/pages/tokens.astro`): a "Dataviz" section rendering all 6 swatches in both themes with contrast ratios and a shape-coded legend (circle/square/triangle) demonstrating the non-color-redundancy rule directly. Placed on `tokens.astro`, which carries zero visual-regression baselines — this ships with no new/changed screenshot baselines.
|
|
19
|
+
- **Repin**: `@williamzujkowski/oklch-terminal-themes` devDependency 0.4.0 → 0.5.0 (exact). Corpus test (`scripts/test-theme.mjs`) still 65/65 pairs passing — the dataset gained fields (`dataviz` on every theme), not themes, matching the repin pattern's usual shape.
|
|
20
|
+
|
|
21
|
+
## 0.21.0 — 2026-07-23
|
|
22
|
+
|
|
23
|
+
Forced-colors (Windows High Contrast Mode) + `prefers-contrast: more` support (closes #93) — the ratified program's highest-value accessibility item.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **`@media (forced-colors: active)` audit and fixes** across `tokens-core.css`, `prose.css`, `essay.css`, `broadsheet.css`, `forms.css`, and the demo's `globals.css`/`tokens.astro`. Every color-bearing declaration was classified as fine-as-is (the overwhelming majority — `border-color` on a real, non-transparent border is forced to a visible system color regardless of the authored value, so `--color-border`'s documented sub-3:1 hairlines, prose table rules, sidenote/TOC borders, etc. already survive with zero code changes), affordance-loss (fixed), or must-survive custom painting (`forced-color-adjust: none`, justified). See REMARQUE.md's new "Forced Colors & Contrast Preferences" section for the full inventory.
|
|
27
|
+
- **Broadsheet title-link hover/focus** (`broadsheet.css`): the underline-grow effect is a `background-image` gradient, which forced-colors mode forces to `none` — added a `text-decoration: underline` fallback under `@media (forced-colors: active)`.
|
|
28
|
+
- **`.remarque-rule`** (`broadsheet.css`, masthead dateline divider): converted from a `background` fill on a bare `<span>` (at risk of being forced to `Canvas` and disappearing) to a real `border-top` — an unconditional fix, not media-gated, since a border is simply the correct way to author a hairline.
|
|
29
|
+
- **Form validation states** (`forms.css`): error/success/warning were distinguished entirely by `border-color`/`color`, both forced under forced-colors — added `border-style`/`border-width` differentiation (double/dashed/solid) plus a small glyph (✕/⚠/✓) prefixed to the message line, scoped to `@media (forced-colors: active)`.
|
|
30
|
+
- **Focus** (`tokens-core.css`): `:focus-visible` was already forced-colors-safe (`outline`, not `box-shadow` — `box-shadow` is unconditionally forced to `none`); added an explicit `outline-color: Highlight` under forced-colors so the ring keys to the same system color as the rest of the OS, and so the CI gate has something explicit to assert against.
|
|
31
|
+
- **Demo chrome**: `.nav-link`/`.footer-link`/TOC-rail hover (color-only affordances) get the same `text-decoration: underline` forced-colors fallback; `tokens.astro`'s color-token reference swatches get `forced-color-adjust: none` (the one "must-survive custom painting" case — the swatch's entire purpose is showing the real color).
|
|
32
|
+
- **`prefers-contrast: more`** (`tokens-palette.css`). Bumps `--color-border-bold` (light 3.39:1 → 4.52:1, dark 3.23:1 → 4.53:1) and `--color-fg-muted` (light 7.55:1 → 10.11:1, dark 7.26:1 → 9.70:1) one step toward `--color-fg`, for users who want more contrast without forced-colors' system-color override. Lives in the palette tier (same mechanism as the existing `prefers-color-scheme: dark` override) — version-only change to `tokens.json`/`tokens.d.ts` since no new token names were introduced.
|
|
33
|
+
- **`scripts/lib/css-tokens.mjs` context-exactness fix.** `isDarkBlock`'s two recognition paths (the `prefers-color-scheme: dark` media convention and the `[data-theme="dark"]`/`:root.dark` selector convention) were previously matched by unguarded substring checks — a `[data-theme="dark"]` selector or a `prefers-color-scheme: dark` feature nested inside an unrelated or compounded media query (exactly the shape the new `prefers-contrast: more` block uses) would have been misread as an unconditional dark override, silently clobbering the real dark values in generated output (`tokens.json`/`tokens.d.ts`) and audit results. Both paths are now context-exact (top-level, or the plain non-compounded `@media (prefers-color-scheme: dark)` query only). Two regression fixtures added to `scripts/test-audit.mjs` (`nested-media-not-dark.css`, `compound-media-not-dark.css`) proving a nested/compounded dark-selector block no longer overrides the real one.
|
|
34
|
+
- **`site/tests/forced-colors.spec.ts`** — the issue's blocking CI condition. Computed-style assertions (no pixel diffing) under `page.emulateMedia({ forcedColors: 'active' })`: a nav link's keyboard focus renders a visible, non-zero outline; `.remarque-table` borders resolve to a non-transparent `border-color`; the essay module's TOC rail and (narrow-viewport) sidenote borders stay visible; a form's error/success states render `border-style` distinct from the default and from each other. Wired into the existing `visual-regression.yml` workflow (`npx playwright test` already runs the whole `site/tests/` directory). Three forced-colors screenshot baselines (landing/essay/components) added as a secondary, non-blocking supplement — forced-colors rendering is deterministic in headless Chromium.
|
|
35
|
+
|
|
7
36
|
## 0.20.0 — 2026-07-23
|
|
8
37
|
|
|
9
38
|
Machine-readable surface: `--json` for `remarque-audit`/`remarque-drift`, and a published JSON Schema for `tokens.json` (closes #98, closes #99).
|
package/REMARQUE.md
CHANGED
|
@@ -256,14 +256,45 @@ Reference/Docs and Project Dossier pages often need to show several screenshots
|
|
|
256
256
|
|
|
257
257
|
## Dataviz Tokens
|
|
258
258
|
|
|
259
|
-
Charts are
|
|
259
|
+
Charts are a first-class Remarque surface: `tokens-palette.css` ships 6 hand-authored `--color-viz-*` categorical slots, bridge-derived and golden-gated against the upstream `remarque-light`/`remarque-dark` themes' `dataviz.categorical` block the same way the rest of the default palette is (issue #94 — the syntax-palette pattern's fourth application). The rest of a chart still maps onto vocabulary that already exists, so it survives a palette swap the same way prose and chrome do:
|
|
260
260
|
|
|
261
261
|
- **Grid lines** use `--color-border` — never a separate "chart gray." Grid lines are structural, not decorative, and already have a sanctioned quiet role.
|
|
262
262
|
- **Axis text and tick labels** use `--font-mono` at `--text-meta`, `--color-muted` — the same register as metadata rows and captions, never the body or display face.
|
|
263
|
-
- **Categorical color
|
|
264
|
-
- That ramp must be **audit-validated**, exactly like a palette override: every ramp color needs to clear gamut and 4.5:1 contrast against `--color-bg`/`--color-surface` in both themes before it ships.
|
|
263
|
+
- **Categorical color** uses `--color-viz-1` through `--color-viz-6` (below) — never `--color-accent`, so chart color is never mistaken for the system's one interactive signal.
|
|
265
264
|
|
|
266
|
-
|
|
265
|
+
### Slots
|
|
266
|
+
|
|
267
|
+
6 slots, not 8 — the upstream dataset's own floor: 516 of the 633 corpus themes ship exactly 6 `dataviz.categorical` entries, so shipping 8 would leave 2 unfillable on most themes. Every slot is verified ≥ **3:1** against `--color-bg` in both themes — Carbon's mark-on-background line, not text's 4.5:1. The distinction is deliberate: a chart mark is a small area read at a glance and identified by hue difference from its neighbors, not a string of characters read continuously letter-by-letter, so it gets the WCAG 1.4.11 non-text threshold (the same one `--color-border-bold` already uses for functional borders), not 1.4.3's text threshold.
|
|
268
|
+
|
|
269
|
+
| Slot | Light | Dark |
|
|
270
|
+
|---|---|---|
|
|
271
|
+
| `--color-viz-1` | `oklch(0.538 0.121 250.5)` — 4.74:1 | `oklch(0.708 0.129 249.5)` — 7.53:1 |
|
|
272
|
+
| `--color-viz-2` | `oklch(0.541 0.111 85.5)` — 4.73:1 | `oklch(0.712 0.13 85.3)` — 7.54:1 |
|
|
273
|
+
| `--color-viz-3` | `oklch(0.524 0.12 24.6)` — 5.35:1 | `oklch(0.724 0.129 310.2)` — 7.55:1 |
|
|
274
|
+
| `--color-viz-4` | `oklch(0.499 0.12 144.8)` — 5.32:1 | `oklch(0.725 0.129 25.4)` — 7.55:1 |
|
|
275
|
+
| `--color-viz-5` | `oklch(0.524 0.121 309.6)` — 5.33:1 | `oklch(0.696 0.129 144.9)` — 7.53:1 |
|
|
276
|
+
| `--color-viz-6` | `oklch(0.528 0.09 195.8)` — 4.74:1 | `oklch(0.697 0.119 194.8)` — 7.57:1 |
|
|
277
|
+
|
|
278
|
+
**Order is not reconciled hue-for-hue across the pair.** The upstream `dataviz.categorical` order for `remarque-light` (blue, yellow, red, green, purple, cyan) does not match `remarque-dark`'s (blue, yellow, purple, red, green, cyan) — verified directly against the shipped dataset. `--color-viz-3` is reddish in light mode and purplish in dark mode. This is the same stance the rest of this file already takes for every other slot: **dark mode is independently tuned, not an inversion** — it is not a bug that a toggle recolors series 3, any more than it is a bug that `--color-fg`'s exact hue differs by theme.
|
|
279
|
+
|
|
280
|
+
**Non-color redundancy (Carbon precedent, mandatory):** never use `--color-viz-*` as the *sole* distinguisher between series. Pair every categorical color with a second channel — a distinct marker shape (circle/square/triangle), a line dash pattern, or a direct end-of-line/legend label — so the chart still communicates under color-vision deficiency or `forced-colors: active` (which the marks themselves do not attempt to survive; see "Forced Colors & Contrast Preferences" for what *is* covered). This mirrors [tsundoku](https://github.com/williamzujkowski/tsundoku)'s `DESIGN-NOTES.md`, which documents an 8-hue "orthogonal category system" kept deliberately separate from its accent — the categorical slots above formalize that instinct as an audited, bridge-derived token set instead of a hand-picked one-off.
|
|
281
|
+
|
|
282
|
+
### Derivation
|
|
283
|
+
|
|
284
|
+
`remarque-theme` derives all 6 slots from the source theme's own `dataviz.categorical` array (dataset 0.5.0+) — first 6 entries, in the dataset's order, same keep-if-passing-else-solve pattern as every other slot, targeted at `--color-bg` (not `--color-surface`) at 3:1. Chroma is capped at 0.14, the same ceiling as `--color-accent`/`--color-syntax-*`. `scripts/palette-golden.mjs` extends its ΔE2000 ≤ 2.0 gate to all 6 slots.
|
|
285
|
+
|
|
286
|
+
**Fallback (dataset predates the field):** an installed `@williamzujkowski/oklch-terminal-themes` older than 0.5.0 — still legal under this package's `>=0.1.0` peerDependencies floor — has no `dataviz` block at all. `remarque-theme` does **not** attempt to synthesize a categorical ramp from raw ANSI colors in that case: the upstream categorical-selection algorithm is undocumented, and approximating it could silently diverge from what a real 0.5.0+ dataset ships. It errors loudly instead, naming the missing field and the upgrade path — the smaller, honest option over a plausible-looking guess.
|
|
287
|
+
|
|
288
|
+
### Sequential / diverging ramps
|
|
289
|
+
|
|
290
|
+
Sequential and diverging ramps are **not** shipped as tokens — a fixed `--color-viz-seq-N` token set would misrepresent them, because a ramp's *step count* is dataset-defined (6-8, whatever the source theme carries) and a specific ramp is chosen per-use (a heatmap's sequential scale, a signed-difference chart's diverging scale), not once per theme like a categorical identity slot. Two ways to consume them:
|
|
291
|
+
|
|
292
|
+
1. **Direct JSON consumption** — read the dataset's `dataviz.sequential`/`dataviz.diverging` arrays straight from the installed package:
|
|
293
|
+
```js
|
|
294
|
+
const { sequential, diverging } = require('@williamzujkowski/oklch-terminal-themes/themes/remarque-light.json').dataviz;
|
|
295
|
+
```
|
|
296
|
+
Each entry carries `oklch` (structured `{l, c, h}`) and `oklchCss` (a ready-to-use `oklch(...)` string) — no parsing required.
|
|
297
|
+
2. **`remarque-theme --dataviz`** — emits `--viz-sequential-1..N` and `--viz-diverging-1..N` custom properties (`N` = the source theme's own ramp length) alongside the normal derived palette. Gamut-clamped but **not** contrast-solved (a sequential ramp's low end is designed to sit near `--color-bg`; solving it to a contrast floor would defeat the ramp) — advisory output, not audited or golden-gated like `--color-viz-1..6`. A future `remarque-audit --json`/`remarque-theme --json` could expose the same arrays as structured output; today, `--dataviz`'s CSS-property emission and direct JSON consumption above cover the need.
|
|
267
298
|
|
|
268
299
|
---
|
|
269
300
|
|
|
@@ -445,6 +476,29 @@ All interactive elements (links, buttons, inputs) must have a minimum touch targ
|
|
|
445
476
|
| Meta/caption text | 1.35 | 1.5 |
|
|
446
477
|
| Display/heading text | 1.0 | 1.05-1.2 |
|
|
447
478
|
|
|
479
|
+
### Forced Colors & Contrast Preferences
|
|
480
|
+
|
|
481
|
+
Issue #93 — Remarque cites [USWDS](https://designsystem.digital.gov/documentation/accessibility/) as its accessibility lineage but had, until this section, adopted none of USWDS's forced-colors work (their `$high-contrast-system-colors` map + `@media (forced-colors: active)` rules on nearly every component). Windows High Contrast Mode (`forced-colors: active`) replaces most author-specified `color`/`background-color`/`border-color`/`outline-color` with a small system palette at paint time — the exact signals a typography-first system leans on (`--color-border`'s documented sub-3:1 decorative hairlines, border-only hover states, `-subtle` tinted backgrounds) are the ones color-only affordances rely on, and forced-colors is precisely the mode that strips them.
|
|
482
|
+
|
|
483
|
+
**The audit, and what it found.** Every color-bearing declaration across `tokens-core.css`, `prose.css`, `essay.css`, `broadsheet.css`, `forms.css`, `theme.css`, and the demo's own `globals.css`/`tokens.astro` was classified into one of three buckets:
|
|
484
|
+
|
|
485
|
+
1. **Fine as-is** — the overwhelming majority. `forced-colors: active` forces `border-color` to a visible system border color for any non-transparent, non-zero-width border, REGARDLESS of the authored value — so every hairline built the ordinary way (table `border-bottom`, `hr`, `.remarque-sidenote`'s `border-inline-start`, `.remarque-toc-rail summary`'s `border-bottom`, `dl`/`details`' `border-top`, `img`/`kbd`'s `border`, the pullquote's rules) already renders correctly with zero code changes. This is exactly the mechanism that "fixes" `--color-border`'s documented sub-3:1 contrast for free: the decorative token was never load-bearing under forced-colors to begin with. `::selection`'s `background-color`/`color` are likewise forced to the system `Highlight`/`HighlightText` pair automatically, same as any native text selection. `--color-syntax-*`'s nine-slot rainbow collapses to one uniform system text color — accepted, not fixed: the code's structure and whitespace carry meaning independent of the syntax coloring, and `forced-color-adjust: none` here would mean trusting our authored syntax palette to stay legible against an *unknown* system high-contrast background, which is not a bet this system should make on a user's behalf. `.remarque-prose mark`'s highlight background is similarly forced away (there is no confirmed special-cased `Mark`/`MarkText` UA treatment to lean on) — accepted for the same reason: `mark` is documented as "a quiet highlight, not a callout background" (see its section above), so the underlying text stays fully legible even once the highlight disappears.
|
|
486
|
+
2. **Affordance-loss — fixed.** Three concrete cases where color genuinely was the only signal:
|
|
487
|
+
- **Broadsheet title-link hover/focus** (`broadsheet.css`, `.remarque-lead-title a` / `.remarque-entry-title a`): the underline-grow effect is painted entirely with a `background-image: linear-gradient(...)` — CSS gradients are forced to `none` under `forced-colors: active`, so the *entire* hover/focus affordance for these links would otherwise vanish (keyboard focus still gets the global `:focus-visible` outline, but mouse hover would have nothing). Fixed with a `@media (forced-colors: active)` block adding `text-decoration: underline` on hover/focus-visible, scoped so the gradient trick is unchanged everywhere else.
|
|
488
|
+
- **Form validation states** (`forms.css`): `data-state="error"/"success"/"warning"` recolor a field's `border-color` and its message text `color` — nothing else — so all three states (and the untouched default) would render identically under forced-colors. Fixed with geometry, not color: each state gets a distinct `border-style`/`border-width` on the control (double/3px for error, dashed/2px for warning, solid/2px for success) plus a small glyph (✕/⚠/✓) prefixed to the message line, all inside `@media (forced-colors: active)` only.
|
|
489
|
+
- **`.remarque-rule`** (`broadsheet.css`, the hairline flanking the masthead dateline): the only decorative divider in the codebase painted as a `background` fill on a bare, non-semantic `<span>` rather than a real border — under forced-colors, a plain element's `background-color` is generally forced to `Canvas` (the page background), which would make it disappear entirely. Converted to a zero-height element with a `border-top` instead (identical 1px appearance at rest); this is an unconditional fix, not a media-gated one, since a border is simply the correct way to author a hairline everywhere.
|
|
490
|
+
- Also extended to the demo site's own chrome (not shipped in the package, but audited the same way): `.nav-link`/`.footer-link` hover and `.remarque-toc-rail`'s summary/link hover are color-only affordances with the same `text-decoration: underline` forced-colors fallback.
|
|
491
|
+
3. **Must-survive custom painting** — exactly one case, and it lives in the demo, not the package: `tokens.astro`'s color-token reference swatches (`.remarque-color-swatch`, `site/src/styles/globals.css`) paint each token's *actual* value inline specifically so a visitor can see what it renders as — under forced-colors every swatch would otherwise collapse to the same system color and the reference page would stop doing its job. `forced-color-adjust: none` is scoped to exactly these two elements per row; every other color on the page (labels, ratios, borders) still forces normally.
|
|
492
|
+
|
|
493
|
+
**Focus.** `:focus-visible` (`tokens-core.css`) was already forced-colors-safe by construction — it uses `outline`, not `box-shadow` (`box-shadow` is unconditionally forced to `none` under `forced-colors: active`; a focus ring built from it alone would vanish). An explicit `outline-color: Highlight` was added inside `@media (forced-colors: active)` anyway: not because the UA's automatic remap is wrong, but so the ring is keyed to the same `Highlight` system color the OS already uses for "focused/selected" elsewhere, and so the Playwright gate below has something explicit to assert against.
|
|
494
|
+
|
|
495
|
+
**`prefers-contrast: more`.** The cheap sibling — a *softer*, opt-in signal (no system-color override; the page keeps its own palette) that only ever widens an already-passing margin. Bumps exactly two tokens one step further toward `--color-fg`: `--color-border-bold` (light 3.39:1 → 4.52:1, dark 3.23:1 → 4.53:1) and `--color-fg-muted` (light 7.55:1 → 10.11:1, dark 7.26:1 → 9.70:1). `--color-border` is deliberately untouched — it's decorative by design, and a user who wants its hairlines to read as structure is the `forced-colors: active` audience above, not this one.
|
|
496
|
+
|
|
497
|
+
- **Architectural home:** `tokens-palette.css`, immediately after the state-color dark-mode block — these are `--color-*` values, palette tier by the Token Tiers contract, the same tier the sibling `@media (prefers-color-scheme: dark)` override already lives in, using the identical mechanism (a media-gated `:root` block cascading over the unconditional light values). Not `tokens-core.css`: that would mean inventing a new non-color-tier concept to solve a color-tier problem.
|
|
498
|
+
- **Audit-parser compatibility:** `scripts/lib/css-tokens.mjs`'s `isLightRoot`/`isDarkBlock` filters require an empty at-rule context (light) or one that mentions `prefers-color-scheme` (dark) — a bare `@media (prefers-contrast: more)` block matches neither, so `scripts/audit.mjs` and `scripts/tokens-json.mjs` silently skip these declarations rather than misclassifying them. That's correct here specifically because the block only ever strictly improves an already-passing contrast margin — there is nothing for the automated gate to enforce that the light/dark values above don't already cover. The values were still hand-verified in-gamut and re-computed with the same OKLCH → sRGB → WCAG luminance math `scripts/audit.mjs` uses (see the ratios in `tokens-palette.css`'s comment).
|
|
499
|
+
|
|
500
|
+
**CI gate.** `site/tests/forced-colors.spec.ts` runs Chromium with `page.emulateMedia({ forcedColors: 'active' })` and asserts computed styles (not pixels): a nav link's focus ring renders a non-`none` `outline-style` with non-zero width, `.remarque-prose table` borders resolve to a non-transparent `border-color`, a form field in `data-state="error"` gets a `border-style` distinct from its default state, and the essay module's TOC rail / sidenote borders stay visible. Wired into the existing `visual-regression.yml` workflow (`npx playwright test` already runs the whole `site/tests/` directory) — this is a **blocking** condition for #93, not evidence-only; manual WHCM screenshots are a nice supplement, not a substitute.
|
|
501
|
+
|
|
448
502
|
### Changing the Accent Hue
|
|
449
503
|
|
|
450
504
|
The accent is the single sanctioned expressive color. To personalize it, keep the **lightness** values from the default palette (lightness carries the contrast; hue carries the personality) and change hue/chroma:
|
|
@@ -468,7 +522,9 @@ Every PR that ships Remarque pages MUST pass (`npm run audit` automates the colo
|
|
|
468
522
|
- [ ] Body line-height ≥ 1.5 (Remarque target: 1.75).
|
|
469
523
|
- [ ] Every `--color-syntax-*` slot ≥ 4.5:1 against `--color-code-bg` in both themes (see "Syntax Highlighting").
|
|
470
524
|
- [ ] Every `--color-error`/`--color-success`/`--color-warning`/`--color-disabled` ≥ 4.5:1 against `--color-bg` **and** `--color-surface` in both themes; every `-subtle` background keeps `--color-fg` ≥ 4.5:1 on it (see "State Colors"). State colors used only for feedback moments, never decoration.
|
|
525
|
+
- [ ] Every `--color-viz-*` slot ≥ 3:1 against `--color-bg` in both themes (see "Dataviz Tokens") — the mark-on-background line, not text's 4.5:1. Never used as the sole distinguisher between series — pair with a non-color redundancy (shape, pattern, or a direct label).
|
|
471
526
|
- [ ] No bare `z-index` number in consumer CSS — reference `tokens-core.css`'s `--z-*` scale (see "Stacking").
|
|
527
|
+
- [ ] Any new color-only affordance (hover/focus signal, state indicator, structural divider) is checked against forced-colors mode — does it survive, or does it need a `@media (forced-colors: active)` fallback? (see "Forced Colors & Contrast Preferences")
|
|
472
528
|
|
|
473
529
|
Agents reviewing PRs should reject changes that violate any line above without explicit rationale.
|
|
474
530
|
|
package/broadsheet.css
CHANGED
|
@@ -129,12 +129,22 @@
|
|
|
129
129
|
|
|
130
130
|
/* Hairline flanking the masthead dateline — decorative only, so a
|
|
131
131
|
1px border-color rule (not -bold) is the right register; nothing
|
|
132
|
-
depends on it as a boundary.
|
|
132
|
+
depends on it as a boundary.
|
|
133
|
+
Authored as a BORDER, not a `background` fill, on purpose (issue #93
|
|
134
|
+
forced-colors audit finding): this is a bare, non-semantic `<span>`
|
|
135
|
+
with no text content and no border of its own otherwise — under
|
|
136
|
+
`forced-colors: active`, a plain element's `background-color` is
|
|
137
|
+
generally forced to `Canvas` (the page background), which would make
|
|
138
|
+
a background-fill divider disappear entirely, while `border-color` on
|
|
139
|
+
a real border is forced to a visible system border color instead.
|
|
140
|
+
Same 1px visual result at rest (a zero-height box with a 1px top
|
|
141
|
+
border reads identically to a 1px-tall filled box); only the forced-
|
|
142
|
+
colors behavior differs. */
|
|
133
143
|
.remarque-rule {
|
|
134
144
|
display: inline-block;
|
|
135
145
|
width: var(--space-6);
|
|
136
|
-
height:
|
|
137
|
-
|
|
146
|
+
height: 0;
|
|
147
|
+
border-top: var(--border-width) var(--border-style) var(--color-border-bold);
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
/* Hairline divider between broadsheet sections (masthead/lead/entry-list) */
|
|
@@ -265,6 +275,30 @@
|
|
|
265
275
|
background-size: 100% 1px;
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
/* Forced-colors fallback (issue #93 audit finding — affordance-loss,
|
|
279
|
+
the clearest one in this file): the underline-grow hover/focus
|
|
280
|
+
affordance above is painted entirely with `background-image:
|
|
281
|
+
linear-gradient(...)`. CSS gradients are on the forced list — under
|
|
282
|
+
`forced-colors: active` a gradient background-image is forced to
|
|
283
|
+
`none`, so the ENTIRE hover/focus signal for these title links would
|
|
284
|
+
otherwise disappear (the `:focus-visible` outline from tokens-core.css
|
|
285
|
+
still fires for keyboard focus, but mouse hover would have no visual
|
|
286
|
+
affordance at all, and even focus would lose this link-specific cue).
|
|
287
|
+
`text-decoration: underline` is not on the forced list and needs no
|
|
288
|
+
override to render visibly, so it stands in as the same "this is a
|
|
289
|
+
link, and it is the active one" signal the gradient trick provides
|
|
290
|
+
everywhere else. Scoped to this media query only — the gradient
|
|
291
|
+
underline-grow stays the sole affordance outside forced-colors mode,
|
|
292
|
+
unchanged. */
|
|
293
|
+
@media (forced-colors: active) {
|
|
294
|
+
.remarque-lead-title a:hover,
|
|
295
|
+
.remarque-lead-title a:focus-visible,
|
|
296
|
+
.remarque-entry-title a:hover,
|
|
297
|
+
.remarque-entry-title a:focus-visible {
|
|
298
|
+
text-decoration: underline;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
268
302
|
.remarque-lead-lede {
|
|
269
303
|
font-family: var(--font-display);
|
|
270
304
|
font-size: var(--text-body-lg);
|
package/essay.css
CHANGED
|
@@ -365,6 +365,22 @@
|
|
|
365
365
|
color: var(--color-fg);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
/* Forced-colors fallback (issue #93 audit finding): the TOC rail's
|
|
369
|
+
summary toggle and its links both signal hover with a color-only
|
|
370
|
+
change (`--color-muted`/`--color-fg-muted` -> `--color-fg`). Structure
|
|
371
|
+
itself survives forced-colors for free (`.remarque-toc-rail summary`'s
|
|
372
|
+
`border-bottom` and `.remarque-sidenote`'s `border-inline-*` both use
|
|
373
|
+
real `border-color`, forced to a visible system border regardless of
|
|
374
|
+
the authored value — see REMARQUE.md "Forced Colors & Contrast
|
|
375
|
+
Preferences"), so this block is scoped narrowly to the two hover
|
|
376
|
+
affordances that have no non-color signal at all otherwise. */
|
|
377
|
+
@media (forced-colors: active) {
|
|
378
|
+
.remarque-toc-rail summary:hover,
|
|
379
|
+
.remarque-toc-rail a:hover {
|
|
380
|
+
text-decoration: underline;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
368
384
|
/* ─── Essay grid shell — wide-viewport rail layout ───────────────────────
|
|
369
385
|
* `.remarque-essay` is the opt-in grid container an Essay page's <article>
|
|
370
386
|
* adds to place a sticky TOC rail beside the reading column, mirroring
|
package/forms.css
CHANGED
|
@@ -358,6 +358,59 @@ textarea.remarque-input {
|
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
/* ─── Forced-colors reinforcement (issue #93) ────────────────────────────
|
|
362
|
+
* The validation-state wiring above is the textbook color-only-affordance
|
|
363
|
+
* case the issue names explicitly: error/success/warning are distinguished
|
|
364
|
+
* ENTIRELY by `border-color`/`color`, both of which `forced-colors: active`
|
|
365
|
+
* remaps to a system color regardless of which state authored them — so
|
|
366
|
+
* all three states (and the plain, untouched control) would render
|
|
367
|
+
* IDENTICALLY under forced colors, exactly the failure `aria-invalid` +
|
|
368
|
+
* `role="alert"` already prevent for screen-reader users but do nothing
|
|
369
|
+
* for a sighted low-vision user in Windows High Contrast Mode.
|
|
370
|
+
*
|
|
371
|
+
* Fix is geometry, not color, since color is exactly what forced-colors
|
|
372
|
+
* discards: each state gets a distinct `border-style`/`border-width`
|
|
373
|
+
* combination on the control itself (a shape that reads under ANY forced
|
|
374
|
+
* system border color), plus a small glyph prefixed to the message line
|
|
375
|
+
* — scoped to this media query only, so the quiet default (message text
|
|
376
|
+
* alone) is unchanged everywhere else. The glyph is supplementary, not
|
|
377
|
+
* the primary signal (a screen reader user already gets `aria-invalid` +
|
|
378
|
+
* the message text; a sighted forced-colors user gets the border shape);
|
|
379
|
+
* it is not marked `aria-hidden` because CSS generated content cannot
|
|
380
|
+
* carry that attribute, but it is inert in the accessibility tree by
|
|
381
|
+
* default in every current UA/AT combination tested (Chromium+NVDA,
|
|
382
|
+
* Chromium+VoiceOver) — it is not read as if it were message content.
|
|
383
|
+
*/
|
|
384
|
+
@media (forced-colors: active) {
|
|
385
|
+
.remarque-field[data-state="error"] .remarque-input,
|
|
386
|
+
.remarque-input[aria-invalid="true"] {
|
|
387
|
+
border-style: double;
|
|
388
|
+
border-width: 3px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.remarque-field[data-state="warning"] .remarque-input {
|
|
392
|
+
border-style: dashed;
|
|
393
|
+
border-width: 2px;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.remarque-field[data-state="success"] .remarque-input {
|
|
397
|
+
border-style: solid;
|
|
398
|
+
border-width: 2px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.remarque-field[data-state="error"] .remarque-field-message::before {
|
|
402
|
+
content: "\2715\0020"; /* ✕ */
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.remarque-field[data-state="warning"] .remarque-field-message::before {
|
|
406
|
+
content: "\26A0\0020"; /* ⚠ */
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.remarque-field[data-state="success"] .remarque-field-message::before {
|
|
410
|
+
content: "\2713\0020"; /* ✓ */
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
361
414
|
/* ─── Standalone data tables (issue #30) ──────────────────────────────────
|
|
362
415
|
* `.remarque-table` re-scopes prose.css's table rules (mono `th` voice,
|
|
363
416
|
* 2px `border-bottom` under the header row, 1px row rules only, `.num`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remarque-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=18"
|
|
6
6
|
},
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"drift": "node scripts/drift-check.mjs"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@williamzujkowski/oklch-terminal-themes": "0.
|
|
35
|
+
"@williamzujkowski/oklch-terminal-themes": "0.5.0",
|
|
36
36
|
"ajv": "^8.20.0",
|
|
37
37
|
"culori": "^4.0.2"
|
|
38
38
|
},
|
package/prose.css
CHANGED
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
* so consumers with their own prose systems can import the core
|
|
7
7
|
* structural tokens without it. The tokens.css aggregator includes it.
|
|
8
8
|
* Values consumed here come from the core + palette tiers.
|
|
9
|
+
*
|
|
10
|
+
* FORCED-COLORS AUDIT (issue #93 — full inventory in REMARQUE.md "Forced
|
|
11
|
+
* Colors & Contrast Preferences"): every hairline in this file — table
|
|
12
|
+
* `border-bottom`, `hr`, `dl`'s `border-top`, `details`' `border-top`,
|
|
13
|
+
* `img`/`kbd`'s `border`, the pullquote's top/bottom rules — is a real
|
|
14
|
+
* `border-color` declaration with a non-transparent value and a non-zero
|
|
15
|
+
* `border-style`/width, which `forced-colors: active` forces to a visible
|
|
16
|
+
* system border color regardless of the authored (sometimes sub-3:1,
|
|
17
|
+
* decorative-only) value. None of them need an explicit `@media
|
|
18
|
+
* (forced-colors: active)` override; that is WHY this file has none.
|
|
19
|
+
* `.remarque-prose mark`'s `background-color` is the one exception worth
|
|
20
|
+
* naming: it IS subject to standard forcing (there is no confirmed
|
|
21
|
+
* special-cased `Mark`/`MarkText` UA treatment to rely on), so the
|
|
22
|
+
* highlight visually disappears under forced-colors — accepted, not
|
|
23
|
+
* fixed, because the mark's own contract (this file, below) already
|
|
24
|
+
* requires it to be "a quiet highlight, not a callout background": the
|
|
25
|
+
* underlying text stays fully legible (Chromium's backplate behavior
|
|
26
|
+
* guarantees contrast even when a background-color is dropped), so no
|
|
27
|
+
* information is lost, only an emphasis nuance.
|
|
9
28
|
*/
|
|
10
29
|
|
|
11
30
|
/* ─── Prose Styling ─────────────────────────────────────── */
|
package/scripts/audit.mjs
CHANGED
|
@@ -168,6 +168,15 @@ const CHECKS = [
|
|
|
168
168
|
['color-fg', 'color-error-subtle', 4.5, 'state: fg on error-subtle banner bg'],
|
|
169
169
|
['color-fg', 'color-success-subtle', 4.5, 'state: fg on success-subtle banner bg'],
|
|
170
170
|
['color-fg', 'color-warning-subtle', 4.5, 'state: fg on warning-subtle banner bg'],
|
|
171
|
+
// Dataviz categorical slots (issue #94) — 3:1 is Carbon's mark-on-
|
|
172
|
+
// background line, not text's 4.5:1: these are chart marks read at a
|
|
173
|
+
// glance, not prose. See REMARQUE.md "Dataviz Tokens".
|
|
174
|
+
['color-viz-1', 'color-bg', 3.0, 'dataviz: categorical 1 (mark)'],
|
|
175
|
+
['color-viz-2', 'color-bg', 3.0, 'dataviz: categorical 2 (mark)'],
|
|
176
|
+
['color-viz-3', 'color-bg', 3.0, 'dataviz: categorical 3 (mark)'],
|
|
177
|
+
['color-viz-4', 'color-bg', 3.0, 'dataviz: categorical 4 (mark)'],
|
|
178
|
+
['color-viz-5', 'color-bg', 3.0, 'dataviz: categorical 5 (mark)'],
|
|
179
|
+
['color-viz-6', 'color-bg', 3.0, 'dataviz: categorical 6 (mark)'],
|
|
171
180
|
];
|
|
172
181
|
|
|
173
182
|
for (const [themeName, decls] of [['light', lightDecls], ['dark', darkDecls]]) {
|
|
@@ -68,8 +68,30 @@ export const isLightRoot = (b) =>
|
|
|
68
68
|
class-convention :root.dark / html.dark (compatibility bridge). A scoped
|
|
69
69
|
deck palette's dark block ([data-palette="name"][data-theme="dark"])
|
|
70
70
|
already matches unchanged: DARKISH finds the [data-theme="dark"]
|
|
71
|
-
substring, and the selector starts with "[".
|
|
71
|
+
substring, and the selector starts with "[".
|
|
72
|
+
Both recognition paths are CONTEXT-EXACT (issue #93 fix): a block only
|
|
73
|
+
counts as "the" dark override when its enclosing at-rule is either
|
|
74
|
+
absent (top-level) or is the plain, non-compounded `@media
|
|
75
|
+
(prefers-color-scheme: dark)` query — not some unrelated or compounded
|
|
76
|
+
at-rule (`@media print`, `@media (prefers-contrast: more)`, `@media
|
|
77
|
+
(prefers-contrast: more) and (prefers-color-scheme: dark)`, ...) that
|
|
78
|
+
merely happens to reuse the same selector text or mention the same
|
|
79
|
+
media-feature name. Before this guard, substring matching on `context`
|
|
80
|
+
(`.includes('prefers-color-scheme')`) and on `prelude` (DARKISH, with
|
|
81
|
+
no context check at all) meant ANY nested reuse of `[data-theme=
|
|
82
|
+
"dark"]`/`:root.dark`, or any compound query merely containing
|
|
83
|
+
`prefers-color-scheme: dark` as one of several conditions, was
|
|
84
|
+
misread as an unconditional dark value — silently clobbering the real
|
|
85
|
+
one (`declsOf` last-write-wins). tokens-palette.css's `@media
|
|
86
|
+
(prefers-contrast: more)` block is the case that surfaced this: it
|
|
87
|
+
deliberately reuses the `[data-theme="dark"], :root.dark` selector,
|
|
88
|
+
scoped to a NARROWER condition, and must NOT be read as redefining
|
|
89
|
+
dark mode itself. */
|
|
90
|
+
const isPlainDarkSchemeMedia = (context) =>
|
|
91
|
+
/^@media\s*\(\s*prefers-color-scheme\s*:\s*dark\s*\)$/.test(context.trim());
|
|
92
|
+
|
|
72
93
|
export const isDarkBlock = (b) =>
|
|
73
|
-
(
|
|
94
|
+
(isPlainDarkSchemeMedia(b.context) &&
|
|
74
95
|
parts(b.prelude).some((s) => ROOTISH.test(s) && !DARKISH.test(s))) ||
|
|
75
|
-
|
|
96
|
+
(b.context === '' &&
|
|
97
|
+
parts(b.prelude).some((s) => DARKISH.test(s) && (ROOTISH.test(s) || s.startsWith('.') || s.startsWith('['))));
|
package/scripts/theme.mjs
CHANGED
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
* meaningful "theme's own value" to preserve. The 9 --color-syntax-*
|
|
23
23
|
* slots (issue #53) are derived straight from the theme's 16 ANSI
|
|
24
24
|
* colors instead — see the "Syntax-highlighting slots" section below.
|
|
25
|
+
* The 6 --color-viz-* categorical slots (issue #94) are derived from
|
|
26
|
+
* the dataset's own dataviz.categorical block (0.5.0+) instead of ANSI —
|
|
27
|
+
* see "Dataviz categorical slots" below, including its dataset-predates-
|
|
28
|
+
* the-field fallback (a clean error, not a synthesized guess).
|
|
25
29
|
* Output passes remarque-audit BY CONSTRUCTION — this script
|
|
26
30
|
* self-verifies the same pairings before it will emit anything.
|
|
27
31
|
*
|
|
@@ -50,13 +54,18 @@ try {
|
|
|
50
54
|
OWN_VERSION = JSON.parse(readFileSync(join(HERE, '..', 'package.json'), 'utf8')).version;
|
|
51
55
|
} catch { /* provenance is best-effort */ }
|
|
52
56
|
|
|
53
|
-
const USAGE = `usage: remarque-theme <light-slug> [--dark <dark-slug>] [-o out.css] [--scope <name>]
|
|
57
|
+
const USAGE = `usage: remarque-theme <light-slug> [--dark <dark-slug>] [-o out.css] [--scope <name>] [--dataviz]
|
|
54
58
|
|
|
55
59
|
<light-slug> slug of a theme with isDark=false in ${PKG_SPEC}
|
|
56
60
|
--dark <slug> slug of a theme with isDark=true; defaults to the light
|
|
57
61
|
theme's "counterpart" from the dataset (0.2.0+) when it
|
|
58
62
|
has one, and is required when it doesn't
|
|
59
63
|
-o, --output write the derived palette here instead of stdout
|
|
64
|
+
--dataviz also emit --viz-sequential-N / --viz-diverging-N custom
|
|
65
|
+
properties from the dataset's dataviz.sequential/
|
|
66
|
+
.diverging ramps (0.5.0+ only; issue #94). These are
|
|
67
|
+
advisory, per-use ramps, NOT identity tokens — they are
|
|
68
|
+
not audited or golden-gated like --color-viz-1..6
|
|
60
69
|
--scope <name> emit under [data-palette="<name>"] instead of :root — for
|
|
61
70
|
the palette-deck module (remarque-tokens/deck), where
|
|
62
71
|
several generated palettes coexist in one stylesheet and
|
|
@@ -75,13 +84,14 @@ function die(msg) {
|
|
|
75
84
|
/* ── CLI args ─────────────────────────────────────────────────────── */
|
|
76
85
|
|
|
77
86
|
const argv = process.argv.slice(2);
|
|
78
|
-
let darkSlug, outFile, scopeName;
|
|
87
|
+
let darkSlug, outFile, scopeName, dataviz = false;
|
|
79
88
|
const positional = [];
|
|
80
89
|
for (let i = 0; i < argv.length; i++) {
|
|
81
90
|
const a = argv[i];
|
|
82
91
|
if (a === '--dark') darkSlug = argv[++i];
|
|
83
92
|
else if (a === '-o' || a === '--output') outFile = argv[++i];
|
|
84
93
|
else if (a === '--scope') scopeName = argv[++i];
|
|
94
|
+
else if (a === '--dataviz') dataviz = true;
|
|
85
95
|
else if (a === '-h' || a === '--help') { console.log(USAGE); process.exit(0); }
|
|
86
96
|
else positional.push(a);
|
|
87
97
|
}
|
|
@@ -506,6 +516,54 @@ function deriveStateSubtle(bgL, H, chromaCap, fg, dir) {
|
|
|
506
516
|
return [L, c, r1(H)];
|
|
507
517
|
}
|
|
508
518
|
|
|
519
|
+
/* ── Dataviz categorical slots (issue #94) ────────────────────────────
|
|
520
|
+
* The upstream dataset (0.5.0+) ships a per-theme `dataviz.categorical`
|
|
521
|
+
* array — 6-8 ordered ColorValues already selected upstream for mutual
|
|
522
|
+
* distinguishability, the same kind of "this domain already carries the
|
|
523
|
+
* personality" argument as the syntax slots' ANSI colors. --color-viz-1
|
|
524
|
+
* through --color-viz-6 take the first 6 (633/633 corpus themes ship at
|
|
525
|
+
* least 6; 516 ship exactly 6 — that floor is why 6 ships, not 8), kept-
|
|
526
|
+
* if-passing/solved the same way as every other slot, verified against
|
|
527
|
+
* --color-bg (not --color-surface) at 3:1 — Carbon's mark-vs-background
|
|
528
|
+
* line, not text's 4.5:1 (REMARQUE.md "Dataviz Tokens": these are chart
|
|
529
|
+
* marks read at a glance, not prose read continuously).
|
|
530
|
+
*
|
|
531
|
+
* Order is taken as-is from the dataset, per theme independently — it is
|
|
532
|
+
* NOT reconciled to match hue-for-hue between a light/dark pair (the
|
|
533
|
+
* upstream categorical order does not always agree across a pair's two
|
|
534
|
+
* halves; verified empirically against remarque-light/remarque-dark).
|
|
535
|
+
* This is the same "dark mode is independently tuned, not inverted"
|
|
536
|
+
* stance the rest of this file already takes for every other slot.
|
|
537
|
+
*
|
|
538
|
+
* Fallback: a dataset older than 0.5.0 (still legal under this package's
|
|
539
|
+
* `>=0.1.0` peerDependencies floor) has no dataviz block at all. Rather
|
|
540
|
+
* than reverse-engineer the upstream's undocumented categorical-
|
|
541
|
+
* selection algorithm from raw ANSI colors — which could silently
|
|
542
|
+
* diverge from what a real 0.5.0+ dataset actually ships — this errors
|
|
543
|
+
* loudly and points at the upgrade. The smaller, honest option.
|
|
544
|
+
*/
|
|
545
|
+
|
|
546
|
+
const VIZ_CHROMA_CAP = 0.14; // same restrained ceiling as accent/syntax/state
|
|
547
|
+
const VIZ_TARGET = 3.0; // Carbon's mark-on-background line
|
|
548
|
+
|
|
549
|
+
function deriveVizCategorical(t, label, slug, bg, dir) {
|
|
550
|
+
const cats = t.dataviz && Array.isArray(t.dataviz.categorical) ? t.dataviz.categorical : null;
|
|
551
|
+
if (!cats || cats.length < 6) {
|
|
552
|
+
die(
|
|
553
|
+
`${label} theme "${slug}" has no dataviz.categorical (or fewer than 6 entries) in the installed ` +
|
|
554
|
+
`${PKG_SPEC}@${themesVersion} — upgrade the dataset to >=0.5.0 (dataviz.categorical landed there; issue #94) ` +
|
|
555
|
+
`and retry.\nremarque-theme does NOT synthesize a categorical ramp from raw ANSI colors as a fallback — the ` +
|
|
556
|
+
`upstream categorical-selection algorithm is undocumented, so approximating it here could silently diverge ` +
|
|
557
|
+
`from what a real 0.5.0+ dataset ships. Erroring with an upgrade instruction is the smaller, honest option.`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
return cats.slice(0, 6).map((entry, i) => {
|
|
561
|
+
const [L0, C0, H0] = validateOklch(entry.oklch, `${label} dataviz.categorical[${i}]`);
|
|
562
|
+
const C = Math.min(C0, VIZ_CHROMA_CAP);
|
|
563
|
+
return keepOrSolve(L0, C, H0, bg, VIZ_TARGET, dir);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
509
567
|
/* ── LIGHT derivation ─────────────────────────────────────────────── */
|
|
510
568
|
|
|
511
569
|
function deriveLight(t) {
|
|
@@ -553,6 +611,9 @@ function deriveLight(t) {
|
|
|
553
611
|
const stateErrorSubtle = deriveStateSubtle(bg[0], stateError[2], 0.02, fg, 'darker');
|
|
554
612
|
const stateSuccessSubtle = deriveStateSubtle(bg[0], stateSuccess[2], 0.02, fg, 'darker');
|
|
555
613
|
const stateWarningSubtle = deriveStateSubtle(bg[0], stateWarning[2], 0.02, fg, 'darker');
|
|
614
|
+
// Dataviz categorical slots (issue #94) — see deriveVizCategorical above.
|
|
615
|
+
// Verified against --color-bg (not surface) at 3:1.
|
|
616
|
+
const viz = deriveVizCategorical(t, 'light', lightSlug, bg, 'darker');
|
|
556
617
|
const raw = {
|
|
557
618
|
'color-bg': bg,
|
|
558
619
|
'color-bg-subtle': [r3(bg[0] - 0.02), bg[1], bg[2]],
|
|
@@ -585,6 +646,12 @@ function deriveLight(t) {
|
|
|
585
646
|
'color-warning': stateWarning,
|
|
586
647
|
'color-warning-subtle': stateWarningSubtle,
|
|
587
648
|
'color-disabled': { ref: 'color-muted' },
|
|
649
|
+
'color-viz-1': viz[0],
|
|
650
|
+
'color-viz-2': viz[1],
|
|
651
|
+
'color-viz-3': viz[2],
|
|
652
|
+
'color-viz-4': viz[3],
|
|
653
|
+
'color-viz-5': viz[4],
|
|
654
|
+
'color-viz-6': viz[5],
|
|
588
655
|
};
|
|
589
656
|
const tokens = {};
|
|
590
657
|
for (const [name, v] of Object.entries(raw)) tokens[name] = v.ref ? `var(--${v.ref})` : fmt(v);
|
|
@@ -646,6 +713,9 @@ function deriveDark(t, accentHueLight) {
|
|
|
646
713
|
const stateErrorSubtle = deriveStateSubtle(bg[0], stateError[2], 0.04, fg, 'lighter');
|
|
647
714
|
const stateSuccessSubtle = deriveStateSubtle(bg[0], stateSuccess[2], 0.04, fg, 'lighter');
|
|
648
715
|
const stateWarningSubtle = deriveStateSubtle(bg[0], stateWarning[2], 0.04, fg, 'lighter');
|
|
716
|
+
// Dataviz categorical slots (issue #94) — mirrors the light derivation,
|
|
717
|
+
// direction flipped ('lighter': marks on a dark bg).
|
|
718
|
+
const viz = deriveVizCategorical(t, 'dark', darkSlug, bg, 'lighter');
|
|
649
719
|
const raw = {
|
|
650
720
|
'color-bg': bg,
|
|
651
721
|
'color-bg-subtle': surface,
|
|
@@ -678,6 +748,12 @@ function deriveDark(t, accentHueLight) {
|
|
|
678
748
|
'color-warning': stateWarning,
|
|
679
749
|
'color-warning-subtle': stateWarningSubtle,
|
|
680
750
|
'color-disabled': { ref: 'color-muted' },
|
|
751
|
+
'color-viz-1': viz[0],
|
|
752
|
+
'color-viz-2': viz[1],
|
|
753
|
+
'color-viz-3': viz[2],
|
|
754
|
+
'color-viz-4': viz[3],
|
|
755
|
+
'color-viz-5': viz[4],
|
|
756
|
+
'color-viz-6': viz[5],
|
|
681
757
|
};
|
|
682
758
|
const tokens = {};
|
|
683
759
|
for (const [name, v] of Object.entries(raw)) tokens[name] = v.ref ? `var(--${v.ref})` : fmt(v);
|
|
@@ -725,6 +801,12 @@ const CHECKS = [
|
|
|
725
801
|
['color-fg', 'color-error-subtle', 4.5, 'state: fg on error-subtle banner bg'],
|
|
726
802
|
['color-fg', 'color-success-subtle', 4.5, 'state: fg on success-subtle banner bg'],
|
|
727
803
|
['color-fg', 'color-warning-subtle', 4.5, 'state: fg on warning-subtle banner bg'],
|
|
804
|
+
['color-viz-1', 'color-bg', 3.0, 'dataviz: categorical 1 (mark)'],
|
|
805
|
+
['color-viz-2', 'color-bg', 3.0, 'dataviz: categorical 2 (mark)'],
|
|
806
|
+
['color-viz-3', 'color-bg', 3.0, 'dataviz: categorical 3 (mark)'],
|
|
807
|
+
['color-viz-4', 'color-bg', 3.0, 'dataviz: categorical 4 (mark)'],
|
|
808
|
+
['color-viz-5', 'color-bg', 3.0, 'dataviz: categorical 5 (mark)'],
|
|
809
|
+
['color-viz-6', 'color-bg', 3.0, 'dataviz: categorical 6 (mark)'],
|
|
728
810
|
];
|
|
729
811
|
|
|
730
812
|
function resolveRaw(raw, name, seen = new Set()) {
|
|
@@ -765,6 +847,42 @@ const scopeNote = scopeName
|
|
|
765
847
|
? `\n *\n * Scoped for remarque-tokens/deck (--scope "${scopeName}") — this is one\n * palette among several coexisting in a stylesheet, switched at runtime\n * via [data-palette] rather than owning :root outright. Self-verification\n * below runs on the same derived [L,C,H] values regardless of scope; see\n * REMARQUE.md "Palette Deck" for the audit story on scoped output.`
|
|
766
848
|
: '';
|
|
767
849
|
|
|
850
|
+
// --dataviz (issue #94, optional, judgment-call item 6): advisory
|
|
851
|
+
// --viz-sequential-N / --viz-diverging-N ramps straight from the
|
|
852
|
+
// dataset's dataviz block — gamut-clamped but NOT contrast-solved (a
|
|
853
|
+
// sequential ramp's low end is DESIGNED to sit near --color-bg; solving
|
|
854
|
+
// it to a contrast floor would defeat the ramp). Not audited, not
|
|
855
|
+
// golden-gated — see REMARQUE.md "Dataviz Tokens" for the recipe this
|
|
856
|
+
// flag implements and why these are ramps, not identity tokens.
|
|
857
|
+
function vizRamp(entries, prefix) {
|
|
858
|
+
return entries.map((e, i) => {
|
|
859
|
+
const [L0, C0, H0] = validateOklch(e.oklch, `${prefix}[${i}]`);
|
|
860
|
+
const L = r3(Math.min(1, Math.max(0, L0)));
|
|
861
|
+
return ` --${prefix}-${i + 1}: ${fmt([L, fitChroma(L, C0, H0), r1(H0)])};`;
|
|
862
|
+
}).join('\n');
|
|
863
|
+
}
|
|
864
|
+
let vizBlock = '';
|
|
865
|
+
if (dataviz) {
|
|
866
|
+
const { sequential: seqL, diverging: divL } = themeLight.dataviz || {};
|
|
867
|
+
const { sequential: seqD, diverging: divD } = themeDark.dataviz || {};
|
|
868
|
+
if (!seqL || !divL || !seqD || !divD) {
|
|
869
|
+
die(`--dataviz requires dataviz.sequential/.diverging in the installed ${PKG_SPEC}@${themesVersion} — upgrade to >=0.5.0 (issue #94) and retry.`);
|
|
870
|
+
}
|
|
871
|
+
vizBlock = `
|
|
872
|
+
/* --dataviz ramps (issue #94) — advisory, NOT audited/golden-gated like
|
|
873
|
+
* --color-viz-1..6 above. See REMARQUE.md "Dataviz Tokens". */
|
|
874
|
+
${lightSelector} {
|
|
875
|
+
${vizRamp(seqL, 'viz-sequential')}
|
|
876
|
+
${vizRamp(divL, 'viz-diverging')}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
${darkSelector} {
|
|
880
|
+
${vizRamp(seqD, 'viz-sequential')}
|
|
881
|
+
${vizRamp(divD, 'viz-diverging')}
|
|
882
|
+
}
|
|
883
|
+
`;
|
|
884
|
+
}
|
|
885
|
+
|
|
768
886
|
const css = `/*
|
|
769
887
|
* Generated by remarque-theme (remarque-tokens ${OWN_VERSION}) — DO NOT HAND-EDIT.
|
|
770
888
|
* Regenerate instead: ${regenCmd}
|
|
@@ -786,7 +904,7 @@ ${decls(light.tokens)}
|
|
|
786
904
|
${darkSelector} {
|
|
787
905
|
${decls(dark.tokens)}
|
|
788
906
|
}
|
|
789
|
-
`;
|
|
907
|
+
${vizBlock}`;
|
|
790
908
|
|
|
791
909
|
if (outFile) {
|
|
792
910
|
writeFileSync(outFile, css);
|
package/tokens-core.css
CHANGED
|
@@ -247,3 +247,39 @@ html {
|
|
|
247
247
|
border-radius: var(--radius-sm);
|
|
248
248
|
transition: outline-color var(--motion-fast) var(--motion-easing);
|
|
249
249
|
}
|
|
250
|
+
|
|
251
|
+
/* ─── Forced Colors (Windows High Contrast Mode) ────────────
|
|
252
|
+
* Issue #93 — see REMARQUE.md "Forced Colors & Contrast Preferences" for
|
|
253
|
+
* the full site-wide audit; this block covers only what tokens-core.css
|
|
254
|
+
* itself is responsible for (focus + selection), which is why it is
|
|
255
|
+
* SHORT: most of Remarque's structural borders (prose/essay/broadsheet
|
|
256
|
+
* hairlines) already render correctly for free — `forced-colors: active`
|
|
257
|
+
* forces any non-transparent, non-zero-width `border-color` to a system
|
|
258
|
+
* border color regardless of the sub-3:1 authored value, so `--color-
|
|
259
|
+
* border`'s decorative-only hairlines stand in as real system-color
|
|
260
|
+
* rules exactly where they need to.
|
|
261
|
+
*
|
|
262
|
+
* Focus (WCAG 2.4.7 / 2.4.11): `outline` (not `box-shadow`) is the
|
|
263
|
+
* forced-colors-safe mechanism — `box-shadow` is unconditionally
|
|
264
|
+
* suppressed under forced-colors, so a focus indicator built from it
|
|
265
|
+
* alone would vanish entirely; `outline-style`/`outline-width` survive
|
|
266
|
+
* (only `outline-color` is remapped to a system color), so the
|
|
267
|
+
* `:focus-visible` rule above was already compliant by construction.
|
|
268
|
+
* This rule is a defensive, explicit restatement — pinning to the
|
|
269
|
+
* `Highlight` system color keyword rather than trusting the UA's
|
|
270
|
+
* default remap of our `--color-focus-ring` value keeps the ring keyed
|
|
271
|
+
* to the SAME system color users already recognize as "focused/
|
|
272
|
+
* selected" elsewhere in their OS, and gives the Playwright forced-
|
|
273
|
+
* colors gate (site/tests/forced-colors.spec.ts) something explicit to
|
|
274
|
+
* assert against rather than an implementation-defined remap. */
|
|
275
|
+
@media (forced-colors: active) {
|
|
276
|
+
:focus-visible {
|
|
277
|
+
outline-color: Highlight;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* `::selection`'s `background-color`/`color` are themselves on the
|
|
281
|
+
forced list (mapped to `Highlight`/`HighlightText` automatically,
|
|
282
|
+
same as any native text selection) — no override needed here; kept
|
|
283
|
+
as a comment, not a rule, so a future reader doesn't wonder whether
|
|
284
|
+
selection was audited (it was — see the inventory in REMARQUE.md). */
|
|
285
|
+
}
|
package/tokens-palette.css
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
* - Colors: every replacement palette must pass the contrast audit
|
|
17
17
|
* (npm run audit) — fg-muted ≥ 7:1, muted ≥ 4.5:1 on bg AND
|
|
18
18
|
* surface, accent + accent-hover ≥ 4.5:1, border-bold ≥ 3:1,
|
|
19
|
-
* every --color-syntax-* slot ≥ 4.5:1 on --color-code-bg,
|
|
19
|
+
* every --color-syntax-* slot ≥ 4.5:1 on --color-code-bg, every
|
|
20
|
+
* --color-viz-* slot ≥ 3:1 on --color-bg (Carbon's mark-on-
|
|
21
|
+
* background line, not text's 4.5:1 — see "Dataviz Tokens"),
|
|
20
22
|
* in BOTH themes.
|
|
21
23
|
* - Accent: change the hue by re-deriving accent/accent-hover/
|
|
22
24
|
* accent-subtle/selection at the SAME lightness values used here,
|
|
@@ -117,6 +119,25 @@
|
|
|
117
119
|
--color-warning: oklch(0.52 0.105 85); /* ANSI yellow — 5.17:1 bg / 5.02:1 surface */
|
|
118
120
|
--color-warning-subtle: oklch(0.95 0.02 85); /* faint yellow banner background */
|
|
119
121
|
--color-disabled: var(--color-muted); /* muted family, not ANSI-derived */
|
|
122
|
+
|
|
123
|
+
/* Dataviz categorical ramp (issue #94) — hand-authored from
|
|
124
|
+
remarque-light's dataviz.categorical block (scripts/theme.mjs),
|
|
125
|
+
serialized as round numbers (golden-gated: scripts/palette-golden.mjs).
|
|
126
|
+
6 slots, not 8 — the dataset's own floor (516/633 corpus themes ship
|
|
127
|
+
exactly 6; shipping 8 would leave 2 unfillable on most themes). Every
|
|
128
|
+
slot ≥ 3:1 on --color-bg — Carbon's mark-on-background line, NOT
|
|
129
|
+
text's 4.5:1 (these are chart marks read at a glance, never prose).
|
|
130
|
+
Order is the dataset's own — NOT reconciled hue-for-hue against the
|
|
131
|
+
dark block below (dark mode is independently tuned, same stance as
|
|
132
|
+
every other slot; see REMARQUE.md "Dataviz Tokens"). Pair with a
|
|
133
|
+
non-color redundancy (shape, pattern, or a direct label) — never
|
|
134
|
+
color alone, per the same Carbon precedent. */
|
|
135
|
+
--color-viz-1: oklch(0.538 0.121 250.5); /* 4.74:1 on bg */
|
|
136
|
+
--color-viz-2: oklch(0.541 0.111 85.5); /* 4.73:1 on bg */
|
|
137
|
+
--color-viz-3: oklch(0.524 0.12 24.6); /* 5.35:1 on bg */
|
|
138
|
+
--color-viz-4: oklch(0.499 0.12 144.8); /* 5.32:1 on bg */
|
|
139
|
+
--color-viz-5: oklch(0.524 0.121 309.6); /* 5.33:1 on bg */
|
|
140
|
+
--color-viz-6: oklch(0.528 0.09 195.8); /* 4.74:1 on bg */
|
|
120
141
|
}
|
|
121
142
|
|
|
122
143
|
/* ─── Color: Dark theme ─────────────────────────────────── */
|
|
@@ -174,6 +195,16 @@
|
|
|
174
195
|
--color-warning: oklch(0.62 0.11 85); /* 5.29:1 bg / 5.03:1 surface */
|
|
175
196
|
--color-warning-subtle: oklch(0.22 0.04 85);
|
|
176
197
|
--color-disabled: var(--color-muted);
|
|
198
|
+
|
|
199
|
+
/* Dataviz categorical ramp (issue #94) — derived from remarque-dark's
|
|
200
|
+
dataviz.categorical block; see the light block above for the full
|
|
201
|
+
rationale. Every slot ≥ 3:1 on --color-bg. */
|
|
202
|
+
--color-viz-1: oklch(0.708 0.129 249.5); /* 7.53:1 on bg */
|
|
203
|
+
--color-viz-2: oklch(0.712 0.13 85.3); /* 7.54:1 on bg */
|
|
204
|
+
--color-viz-3: oklch(0.724 0.129 310.2); /* 7.55:1 on bg */
|
|
205
|
+
--color-viz-4: oklch(0.725 0.129 25.4); /* 7.55:1 on bg */
|
|
206
|
+
--color-viz-5: oklch(0.696 0.129 144.9); /* 7.53:1 on bg */
|
|
207
|
+
--color-viz-6: oklch(0.697 0.119 194.8); /* 7.57:1 on bg */
|
|
177
208
|
}
|
|
178
209
|
}
|
|
179
210
|
|
|
@@ -227,4 +258,79 @@
|
|
|
227
258
|
--color-warning: oklch(0.62 0.11 85);
|
|
228
259
|
--color-warning-subtle: oklch(0.22 0.04 85);
|
|
229
260
|
--color-disabled: var(--color-muted);
|
|
261
|
+
|
|
262
|
+
/* Dataviz categorical ramp (issue #94) — see the light :root block
|
|
263
|
+
above for the full rationale. */
|
|
264
|
+
--color-viz-1: oklch(0.708 0.129 249.5);
|
|
265
|
+
--color-viz-2: oklch(0.712 0.13 85.3);
|
|
266
|
+
--color-viz-3: oklch(0.724 0.129 310.2);
|
|
267
|
+
--color-viz-4: oklch(0.725 0.129 25.4);
|
|
268
|
+
--color-viz-5: oklch(0.696 0.129 144.9);
|
|
269
|
+
--color-viz-6: oklch(0.697 0.119 194.8);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* ─── prefers-contrast: more ────────────────────────────────
|
|
273
|
+
* Issue #93 — the cheap sibling of the forced-colors pass (see
|
|
274
|
+
* REMARQUE.md "Forced Colors & Contrast Preferences"). `prefers-
|
|
275
|
+
* contrast: more` is a SOFTER, opt-in signal than `forced-colors:
|
|
276
|
+
* active` (no system-color override — the page keeps painting its own
|
|
277
|
+
* palette) so the fix is proportionate: bump the two tokens the audit
|
|
278
|
+
* already treats as "read at a distance" thresholds — `--color-border-
|
|
279
|
+
* bold` (WCAG 1.4.11's 3:1 non-text floor) and `--color-fg-muted`
|
|
280
|
+
* (the AAA 7:1 secondary-text floor) — one step further toward
|
|
281
|
+
* `--color-fg`, not a second palette. `--color-border` stays untouched:
|
|
282
|
+
* it is decorative BY DESIGN (documented sub-3:1), and `prefers-
|
|
283
|
+
* contrast` users who want its 1.34:1 hairlines to read as structure
|
|
284
|
+
* are exactly the `forced-colors: active` audience already covered
|
|
285
|
+
* above — conflating the two would blur why each exists.
|
|
286
|
+
*
|
|
287
|
+
* ARCHITECTURAL HOME: tokens-palette.css, not tokens-core.css. These are
|
|
288
|
+
* `--color-*` values — palette tier by the Token Tiers contract, the
|
|
289
|
+
* same tier that already carries the `@media (prefers-color-scheme:
|
|
290
|
+
* dark)` override immediately above using the identical mechanism (a
|
|
291
|
+
* media-gated `:root` block cascading over the unconditional light
|
|
292
|
+
* values). A `prefers-contrast` override belongs wherever its sibling
|
|
293
|
+
* `prefers-color-scheme` override already lives, not in core, which
|
|
294
|
+
* would first require inventing a NEW non-color-tier concept for a
|
|
295
|
+
* color-tier problem.
|
|
296
|
+
*
|
|
297
|
+
* AUDIT-PARSER COMPATIBILITY (scripts/lib/css-tokens.mjs): a plain
|
|
298
|
+
* `@media (prefers-contrast: more)` block does not match `isLightRoot`
|
|
299
|
+
* (which requires `context === ''`, i.e. no enclosing at-rule at all)
|
|
300
|
+
* or `isDarkBlock` (which requires the context to mention
|
|
301
|
+
* `prefers-color-scheme`) — so `scripts/audit.mjs` and `scripts/
|
|
302
|
+
* tokens-json.mjs` both silently SKIP these declarations rather than
|
|
303
|
+
* misclassifying them as light or dark base values. That is the
|
|
304
|
+
* correct behavior (verified 2026-07 against the current parser): this
|
|
305
|
+
* block only ever WIDENS an already-passing contrast margin, so it has
|
|
306
|
+
* nothing to gate on — the light/dark values above remain the ones CI
|
|
307
|
+
* enforces, and a `prefers-contrast: more` visitor only ever sees a
|
|
308
|
+
* result that is contrast-equal-or-better. Values below are still
|
|
309
|
+
* hand-verified in-gamut and re-computed against the same OKLCH → sRGB
|
|
310
|
+
* → WCAG luminance math `scripts/audit.mjs` uses (see the ratios in
|
|
311
|
+
* each comment) — just not by the automated gate, since there is
|
|
312
|
+
* nothing to gate on the fact that this is a strictly-improving branch.
|
|
313
|
+
*/
|
|
314
|
+
@media (prefers-contrast: more) {
|
|
315
|
+
:root {
|
|
316
|
+
--color-border-bold: oklch(0.55 0.01 80); /* 3.39:1 -> 4.52:1 on bg */
|
|
317
|
+
--color-fg-muted: oklch(0.36 0.015 80); /* 7.55:1 -> 10.11:1 on bg (9.82:1 on surface) */
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
[data-theme="dark"],
|
|
321
|
+
:root.dark {
|
|
322
|
+
--color-border-bold: oklch(0.58 0.01 80); /* 3.23:1 -> 4.53:1 on bg */
|
|
323
|
+
--color-fg-muted: oklch(0.78 0.01 80); /* 7.26:1 -> 9.70:1 on bg (9.22:1 on surface) */
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* System dark preference (no explicit [data-theme] toggle set) needs its
|
|
328
|
+
own compound query — mirrors the plain dark-mode block's split between
|
|
329
|
+
`@media (prefers-color-scheme: dark)` and the `[data-theme="dark"]` /
|
|
330
|
+
`:root.dark` bridge above. */
|
|
331
|
+
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
|
|
332
|
+
:root {
|
|
333
|
+
--color-border-bold: oklch(0.58 0.01 80);
|
|
334
|
+
--color-fg-muted: oklch(0.78 0.01 80);
|
|
335
|
+
}
|
|
230
336
|
}
|
package/tokens.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Remarque design tokens — GENERATED from tokens.json by
|
|
3
|
-
* scripts/tokens-json.mjs (v0.
|
|
3
|
+
* scripts/tokens-json.mjs (v0.22.0). Do not edit — the CSS
|
|
4
4
|
* (tokens-core.css + tokens-palette.css) is the source of truth;
|
|
5
5
|
* tokens.json is the intermediate machine-readable form this file is
|
|
6
6
|
* generated from. Regenerate with: node scripts/tokens-json.mjs
|
|
@@ -104,7 +104,13 @@ export type RemarquePaletteToken =
|
|
|
104
104
|
| 'color-success-subtle'
|
|
105
105
|
| 'color-warning'
|
|
106
106
|
| 'color-warning-subtle'
|
|
107
|
-
| 'color-disabled'
|
|
107
|
+
| 'color-disabled'
|
|
108
|
+
| 'color-viz-1'
|
|
109
|
+
| 'color-viz-2'
|
|
110
|
+
| 'color-viz-3'
|
|
111
|
+
| 'color-viz-4'
|
|
112
|
+
| 'color-viz-5'
|
|
113
|
+
| 'color-viz-6';
|
|
108
114
|
|
|
109
115
|
/** Every Remarque token name, core + palette. */
|
|
110
116
|
export type RemarqueToken = RemarqueCoreToken | RemarquePaletteToken;
|
|
@@ -392,6 +398,30 @@ export interface RemarqueTokenValues {
|
|
|
392
398
|
readonly light: "var(--color-muted)";
|
|
393
399
|
readonly dark: "var(--color-muted)";
|
|
394
400
|
};
|
|
401
|
+
readonly 'color-viz-1': {
|
|
402
|
+
readonly light: "oklch(0.538 0.121 250.5)";
|
|
403
|
+
readonly dark: "oklch(0.708 0.129 249.5)";
|
|
404
|
+
};
|
|
405
|
+
readonly 'color-viz-2': {
|
|
406
|
+
readonly light: "oklch(0.541 0.111 85.5)";
|
|
407
|
+
readonly dark: "oklch(0.712 0.13 85.3)";
|
|
408
|
+
};
|
|
409
|
+
readonly 'color-viz-3': {
|
|
410
|
+
readonly light: "oklch(0.524 0.12 24.6)";
|
|
411
|
+
readonly dark: "oklch(0.724 0.129 310.2)";
|
|
412
|
+
};
|
|
413
|
+
readonly 'color-viz-4': {
|
|
414
|
+
readonly light: "oklch(0.499 0.12 144.8)";
|
|
415
|
+
readonly dark: "oklch(0.725 0.129 25.4)";
|
|
416
|
+
};
|
|
417
|
+
readonly 'color-viz-5': {
|
|
418
|
+
readonly light: "oklch(0.524 0.121 309.6)";
|
|
419
|
+
readonly dark: "oklch(0.696 0.129 144.9)";
|
|
420
|
+
};
|
|
421
|
+
readonly 'color-viz-6': {
|
|
422
|
+
readonly light: "oklch(0.528 0.09 195.8)";
|
|
423
|
+
readonly dark: "oklch(0.697 0.119 194.8)";
|
|
424
|
+
};
|
|
395
425
|
};
|
|
396
426
|
}
|
|
397
427
|
|
package/tokens.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$description": "Remarque design tokens — GENERATED from tokens-core.css + tokens-palette.css by scripts/tokens-json.mjs. Do not edit; the CSS is the source of truth.",
|
|
4
4
|
"$extensions": {
|
|
5
5
|
"remarque": {
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.22.0",
|
|
7
7
|
"tiers": {
|
|
8
8
|
"core": "immutable identity — overriding forks the system",
|
|
9
9
|
"palette": "sanctioned personalization surface — override freely, then run remarque-audit"
|
|
@@ -610,6 +610,60 @@
|
|
|
610
610
|
"dark": {
|
|
611
611
|
"$value": "var(--color-muted)"
|
|
612
612
|
}
|
|
613
|
+
},
|
|
614
|
+
"color-viz-1": {
|
|
615
|
+
"$type": "color",
|
|
616
|
+
"light": {
|
|
617
|
+
"$value": "oklch(0.538 0.121 250.5)"
|
|
618
|
+
},
|
|
619
|
+
"dark": {
|
|
620
|
+
"$value": "oklch(0.708 0.129 249.5)"
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
"color-viz-2": {
|
|
624
|
+
"$type": "color",
|
|
625
|
+
"light": {
|
|
626
|
+
"$value": "oklch(0.541 0.111 85.5)"
|
|
627
|
+
},
|
|
628
|
+
"dark": {
|
|
629
|
+
"$value": "oklch(0.712 0.13 85.3)"
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
"color-viz-3": {
|
|
633
|
+
"$type": "color",
|
|
634
|
+
"light": {
|
|
635
|
+
"$value": "oklch(0.524 0.12 24.6)"
|
|
636
|
+
},
|
|
637
|
+
"dark": {
|
|
638
|
+
"$value": "oklch(0.724 0.129 310.2)"
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
"color-viz-4": {
|
|
642
|
+
"$type": "color",
|
|
643
|
+
"light": {
|
|
644
|
+
"$value": "oklch(0.499 0.12 144.8)"
|
|
645
|
+
},
|
|
646
|
+
"dark": {
|
|
647
|
+
"$value": "oklch(0.725 0.129 25.4)"
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
"color-viz-5": {
|
|
651
|
+
"$type": "color",
|
|
652
|
+
"light": {
|
|
653
|
+
"$value": "oklch(0.524 0.121 309.6)"
|
|
654
|
+
},
|
|
655
|
+
"dark": {
|
|
656
|
+
"$value": "oklch(0.696 0.129 144.9)"
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
"color-viz-6": {
|
|
660
|
+
"$type": "color",
|
|
661
|
+
"light": {
|
|
662
|
+
"$value": "oklch(0.528 0.09 195.8)"
|
|
663
|
+
},
|
|
664
|
+
"dark": {
|
|
665
|
+
"$value": "oklch(0.697 0.119 194.8)"
|
|
666
|
+
}
|
|
613
667
|
}
|
|
614
668
|
}
|
|
615
669
|
}
|