remarque-tokens 0.21.0 → 0.23.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 CHANGED
@@ -6,6 +6,17 @@ Read `REMARQUE.md` first for the full system specification. This file tells you
6
6
 
7
7
  ---
8
8
 
9
+ ## Prefer the Registry Over Transcribing Prose
10
+
11
+ For the Essay Module, the Broadsheet pattern, and Forms (`remarque-tokens/essay`, `remarque-tokens/broadsheet`, `remarque-tokens/forms`), a machine-readable markup-contract registry ships alongside the spec (REMARQUE.md "The Registry"; issue #100). **Agents building a Remarque page SHOULD fetch the relevant registry item and apply its `usage.html`/CSS `content` verbatim, rather than re-typing the markup from this file's or REMARQUE.md's prose.**
12
+
13
+ - Fetch `https://williamzujkowski.github.io/remarque/registry/<name>.json` (`essay`, `broadsheet`, `forms`, or `palette-deck`), or read it from an installed package at `node_modules/remarque-tokens/registry/<name>.json` — same content either way.
14
+ - The known-good markup lives in `files[].content` where `type` is `"remarque:markup"`; the CSS module (if any) is in the `"remarque:css"` entry. Both are the exact, versioned, hash-pinned (`integrity`) bytes — copy them, don't re-derive them from memory.
15
+ - This exists specifically to prevent transcription bugs like #89 (a sidenote `aria-label` that drifted out of DOM order because an agent hand-copied ~80 lines of spec prose instead of applying known-good markup). Reading REMARQUE.md's prose for the *reasoning* behind a contract is still expected; reading it as the *source of the markup you type* is the failure mode the registry exists to close.
16
+ - No CLI installer ships this round — there is no `npx` command that writes these files into a project for you. Fetch the JSON, then apply the markup with your own editing tools.
17
+
18
+ ---
19
+
9
20
  ## Build Order
10
21
 
11
22
  Execute in this exact order. Do not skip steps. Do not reorder.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,40 @@ 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.23.0 — 2026-07-23
8
+
9
+ Machine-readable markup-contract registry (closes #100) — the ratified program's flagship item. Funded on the panel's amended rationale: it mechanically prevents the markup-contract transcription bug class (the sidenote DOM-order/aria-label failure found in the flagship migration, #89), not on "category-defining" positioning.
10
+
11
+ ### Added
12
+ - **`registry.json` + `registry/{essay,broadsheet,forms,palette-deck}.json`** — four items, shaped like shadcn/ui's `registry-item.json`/`registry.json` (`https://ui.shadcn.com/schema/registry-item.json`, `.../registry.json`), adopted where the shape fits and explicitly NOT a parallel invented schema. Per-item: `name`/`type`/`title`/`description`/`dependencies`/`cssVars`/`docs`/`files` kept from shadcn's vocabulary; `type` mints one honest value (`remarque:contract` — none of shadcn's `registry:lib/component/ui/hook/...` describe a CSS+HTML contract with no installable component); `cssVars` is repurposed from "values to merge" to "the flat list of `--custom-property` names this item's CSS reads via `var(...)`, mechanically grepped"; `docs` is repurposed from a markdown blob to a single HTTPS URL into REMARQUE.md. `registry.json` is a lightweight index (pointer + pinned version/hash per item), not shadcn's full-item embedding — full payloads live only in `registry/<name>.json`. Every field-level adaptation and omission (`tailwind`/`css`/`envVars`/`font`/`extends`/`style`/`iconLibrary`/`baseColor`/`theme`/`files[].target`/`meta` — all omitted, none apply) is documented inline in the two vendored schemas and in REMARQUE.md's new "The Registry" section.
13
+ - **Security (panel-mandated, blocking), all enforced by `scripts/test-registry.mjs`:** every item pins a `version` (the exact package release) and an `integrity` hash (`sha256-<base64>`, W3C Subresource Integrity's own shape, computed over `files` at build time and independently recomputed in the test gate — not just carried over); every `docs`/`homepage` URL is `https://`; no item contains executable content anywhere (`scripts/test-registry.mjs` asserts no file's `content`, across every item, contains the substring `<script`). **`deck.js`** — the Palette Deck's runtime module, the one JS asset this package ships — is deliberately EXCLUDED from the registry entirely rather than included-and-hashed: the panel's "no executable content... files are HTML/CSS data" condition reads as absolute, so the honest call is a zero-`<script>`-tag bar with no case-by-case exception. `registry/palette-deck.json` ships only the `data-theme`/`data-palette` HTML fragment, not the FOUC-restore `<script>` sample documented beside it in REMARQUE.md — consumers still get `deck.js` via the ordinary `remarque-tokens/deck` package import.
14
+ - **Single-sourcing.** Every item's CSS `content` is read verbatim from the real `.css` file at build time (`essay.css`/`broadsheet.css`/`forms.css`) — never a second hand-copied literal. Every item's `usage.html` is extracted from REMARQUE.md's own "Markup contract" fenced samples via a `<!-- registry-usage:<name> -->` marker comment (new `scripts/lib/registry-extract.mjs`, `extractUsageHtml`) — the spec prose stays the ONE hand-authored copy; the registry is a derived build artifact, the same relationship `tokens.json` already has to the CSS.
15
+ - **`scripts/build-registry.mjs`** (`--check` for CI freshness, mirroring `scripts/tokens-json.mjs`) generates `registry.json` + `registry/*.json` in one pass from the CSS + REMARQUE.md markers + `package.json`'s version.
16
+ - **`scripts/test-registry.mjs`** (the enforce-don't-instruct gate this issue is funded on) — schema validation (`ajv`, draft 2020-12, with negative fixtures proving the schemas actually reject malformed input) plus mechanical re-derivation of the exact contract lines #89 was about, parsed straight out of each generated `usage.html`: every essay `.remarque-sidenote-ref` carries `aria-label="Note N"` and refs/notes strictly alternate in DOM order; the essay TOC rail carries `aria-label`; every broadsheet `.remarque-entry-numeral` carries `data-entry-number`; every forms `<input id>` has a matching `<label for>` and every `aria-describedby` target id exists in the sample.
17
+ - **Vendored schemas** `registry-item.schema.json` + `registry.schema.json` (draft 2020-12, hand-authored/adapted from shadcn's — see "Added" above for the field-by-field rationale).
18
+ - **CI wiring** (`deploy.yml`): `registry.json`/`registry/*.json` freshness-gated alongside `tokens.json` (`node scripts/build-registry.mjs --check`); `scripts/test-registry.mjs` runs alongside the other fixture suites.
19
+ - **Served by the demo site** (`site/scripts/copy-tokens-json.mjs`, extended): `registry.json`, `registry/*.json`, and both schemas copy into `public/` alongside `tokens.json`, published at `/registry.json`, `/registry/<name>.json`, `/registry-item.schema.json`, `/registry.schema.json`.
20
+ - **npm packaging:** `package.json` `files`/`exports` extended — `remarque-tokens/registry.json`, `remarque-tokens/registry/*` (subpath pattern → `registry/*.json`), `remarque-tokens/registry-item.schema.json`, `remarque-tokens/registry.schema.json`.
21
+ - **Docs.** REMARQUE.md's new "The Registry" section (what it is, the shape adaptation, the security model, the single-sourcing mechanism, what it deliberately is NOT — no CLI installer, no archetype "starter-page" items this round — and the fetch URLs). AGENT_RULES.md's new "Prefer the Registry Over Transcribing Prose" section — agents building Remarque pages should fetch a registry item rather than re-typing markup from spec prose. README's "For AI Agents" section and Files table updated with the registry's live endpoints.
22
+ - **Scope note:** the mandatory four items (essay, broadsheet, forms, palette-deck) are the whole of this release — per-archetype "starter-page" items were floated as an IF-cheap extra and SKIPPED: archetype pages live as demo-specific Astro files, not as a reusable module with one canonical markup sample, so there was nothing to single-source without inventing a new "canonical" sample from scratch.
23
+ - Version-only regeneration of `tokens.json`/`tokens.d.ts` (no CSS/token changes this release).
24
+
25
+ ### Fixed
26
+ - **`peerDependencies` range for `@williamzujkowski/oklch-terminal-themes` was stale and broke every real consumer install.** Set to `>=0.1.0 <0.3.0` back when the dataset was at 0.1.0 and never widened as it shipped 0.4.0 (issue #94's dataviz fields) and 0.5.0 (the current devDependency pin) — `npm install remarque-tokens @williamzujkowski/oklch-terminal-themes@0.5.0` failed with `ERESOLVE` for any consumer following the documented "Color Providers" instructions, with no `--legacy-peer-deps` escape hatch offered or implied anywhere in the docs. Invisible from inside this repo because `npm ci` here installs both packages directly as siblings (root devDependency + peerDependency), which never exercises npm's peer-resolution algorithm the way a consumer's plain `npm install` does. Widened to `>=0.1.0 <1.0.0` — the dataset's changes have been strictly additive since 0.1.0 and `remarque-theme`/`remarque-audit` runtime-validate every field they read (see e.g. the dataviz-fallback error path added in 0.22.0), so the honest ceiling is the dataset's own 1.0 breaking-change boundary, not a minor-by-minor cap that recreates this exact break on every upstream release.
27
+ - **New `scripts/test-pack.mjs`** — a real consumer smoke test closing the gap that let the above go unnoticed: `npm pack`s the actual tarball (not the repo checkout), installs it plus the pinned dataset devDependency version into a scratch project with plain `npm install` (deliberately no `--legacy-peer-deps` — the assertion IS that peer resolution succeeds), then runs `npx remarque-theme remarque-light -o out.css` and `npx remarque-audit --palette out.css --src . --json` from that install and asserts `passed: true`. Wired into `deploy.yml` after the other gates (needs registry network access, which the existing devDependency-install step already has).
28
+
29
+ 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).
30
+
31
+ ### Added
32
+ - **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.
33
+ - **`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).
34
+ - **`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.
35
+ - **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.
36
+ - **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).
37
+ - **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.
38
+ - **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.
39
+ - **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.
40
+
7
41
  ## 0.21.0 — 2026-07-23
8
42
 
9
43
  Forced-colors (Windows High Contrast Mode) + `prefers-contrast: more` support (closes #93) — the ratified program's highest-value accessibility item.
package/README.md CHANGED
@@ -71,6 +71,7 @@ Packaging for agent tooling:
71
71
  - **npm exports:** `remarque-tokens/agent-rules` (→ `AGENT_RULES.md`) and `remarque-tokens/spec` (→ `REMARQUE.md`), alongside the existing `remarque-tokens/tokens.json`, so a project can point an agent at `node_modules/remarque-tokens/AGENT_RULES.md` without hardcoding a filename.
72
72
  - **Claude Code skill:** [`.claude/skills/remarque/SKILL.md`](.claude/skills/remarque/SKILL.md) — triggers on "remarque" / "design system" / new-page work, loads all three files, and states the tier rules, the audit command, and the two build-time pitfalls (unlayered-token-import, string-form `@import`) that pass a green build while silently breaking.
73
73
  - **Live tokens endpoint:** the demo site serves the current `tokens.json` at **https://williamzujkowski.github.io/remarque/tokens.json**, and its schema at **https://williamzujkowski.github.io/remarque/tokens.schema.json** — a remote agent can fetch current token values (and validate their shape) directly instead of trusting training data.
74
+ - **Markup-contract registry:** a shadcn-`registry-item.json`-shaped, version-pinned, hash-verified registry of known-good markup for the Essay/Broadsheet/Forms/Palette Deck modules — **https://williamzujkowski.github.io/remarque/registry.json** (index) and **https://williamzujkowski.github.io/remarque/registry/essay.json** (per item; also `broadsheet`/`forms`/`palette-deck`). Fetch and apply the markup instead of transcribing it from spec prose — see REMARQUE.md's "The Registry" and AGENT_RULES.md's "Prefer the Registry Over Transcribing Prose."
74
75
 
75
76
  ## Files
76
77
 
@@ -86,6 +87,8 @@ Packaging for agent tooling:
86
87
  | `scripts/drift-check.mjs` | `npx remarque-drift` — token drift check for consumers (`--json` for structured output) |
87
88
  | `tokens.json` + `tokens.d.ts` | Generated machine-readable token inventory + TypeScript types (`scripts/tokens-json.mjs`) |
88
89
  | `tokens.schema.json` | Generated JSON Schema (draft 2020-12) for `tokens.json`, published alongside it |
90
+ | `registry.json` + `registry/` | Generated markup-contract registry index + per-item files for essay/broadsheet/forms/palette-deck (`scripts/build-registry.mjs`) — see REMARQUE.md "The Registry" |
91
+ | `registry-item.schema.json` + `registry.schema.json` | Generated JSON Schemas (draft 2020-12) for the per-item and index registry files |
89
92
  | `fonts.css` + `fonts/` | Self-hosted @font-face declarations and woff2 files (no CDN requests) |
90
93
  | `tailwind.config.js` | Tailwind CSS **v3** configuration (v4 projects use an `@theme` block instead) |
91
94
  | `package.json` | npm package manifest for `remarque-tokens` |
package/REMARQUE.md CHANGED
@@ -146,6 +146,8 @@ Remarque uses a three-slot font system. Each slot has a strict role, and each sl
146
146
 
147
147
  This is why the measure is palette-tier: it is a property of the font choice, not of the system's identity. The *target character count* is the invariant.
148
148
 
149
+ **Known limitation: Latin script only.** The table above, and the `ch`-based measure math behind it, assume Latin-alphabet conventions — character width roughly proportional to em-height, word-space-separated tokens, left-to-right flow. None of that holds for CJK, Arabic, or Devanagari body text: CJK measure is conventionally counted in full-width characters per line, a different unit than a Latin `ch` entirely; Arabic's contextual letterforms and right-to-left flow change what "character count" even means; Devanagari's conjunct consonants and matras make per-character width too irregular for a single ch-target to describe. Remarque has not derived a measure-compensation row for any non-Latin script — a site setting CJK/Arabic/Devanagari body copy needs to work out its own measure the way this table works out Latin ones; the ch-target does not transfer by assumption. Named here honestly, in the same spirit as this spec's Butterick line-spacing deviation write-up further down, rather than left for a future non-Latin consumer to discover the hard way.
150
+
149
151
  ---
150
152
 
151
153
  ## Editorial Microtypography
@@ -256,14 +258,45 @@ Reference/Docs and Project Dossier pages often need to show several screenshots
256
258
 
257
259
  ## Dataviz Tokens
258
260
 
259
- Charts are not yet a first-class Remarque surface, but tools built with the system inevitably need them. Rather than inventing chart-specific tokens, map dataviz elements onto vocabulary that already exists, so a chart survives a palette swap the same way prose and chrome do:
261
+ 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
262
 
261
263
  - **Grid lines** use `--color-border` — never a separate "chart gray." Grid lines are structural, not decorative, and already have a sanctioned quiet role.
262
264
  - **Axis text and tick labels** use `--font-mono` at `--text-meta`, `--color-muted` — the same register as metadata rows and captions, never the body or display face.
263
- - **Categorical color ramp** is derived, not hand-picked: rotate hue at the accent's lightness/chroma steps — the same discipline "Changing the Accent Hue" already prescribes (fix lightness, vary hue, reduce chroma until every value clears the gamut) — producing a small hue family distinct from `--color-accent` itself, so chart color is never mistaken for the system's one interactive signal.
264
- - That ramp must be **audit-validated**, exactly like a palette override: every ramp color needs to clear gamut and 4.5:1 contrast against `--color-bg`/`--color-surface` in both themes before it ships.
265
+ - **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.
266
+
267
+ ### Slots
268
+
269
+ 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.
270
+
271
+ | Slot | Light | Dark |
272
+ |---|---|---|
273
+ | `--color-viz-1` | `oklch(0.538 0.121 250.5)` — 4.74:1 | `oklch(0.708 0.129 249.5)` — 7.53:1 |
274
+ | `--color-viz-2` | `oklch(0.541 0.111 85.5)` — 4.73:1 | `oklch(0.712 0.13 85.3)` — 7.54:1 |
275
+ | `--color-viz-3` | `oklch(0.524 0.12 24.6)` — 5.35:1 | `oklch(0.724 0.129 310.2)` — 7.55:1 |
276
+ | `--color-viz-4` | `oklch(0.499 0.12 144.8)` — 5.32:1 | `oklch(0.725 0.129 25.4)` — 7.55:1 |
277
+ | `--color-viz-5` | `oklch(0.524 0.121 309.6)` — 5.33:1 | `oklch(0.696 0.129 144.9)` — 7.53:1 |
278
+ | `--color-viz-6` | `oklch(0.528 0.09 195.8)` — 4.74:1 | `oklch(0.697 0.119 194.8)` — 7.57:1 |
279
+
280
+ **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.
281
+
282
+ **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.
283
+
284
+ ### Derivation
265
285
 
266
- **Precedent:** [tsundoku](https://github.com/williamzujkowski/tsundoku) built the underlying pattern already — its `DESIGN-NOTES.md` documents an 8-hue "orthogonal category system" (`--pop-pink`/`--pop-blue`/`--pop-green`/etc.), kept deliberately separate from `--color-accent` and used for content taxonomy (book categories, reading status) rather than interactivity. It held those hues to roughly the same lightness/contrast discipline as its audited palette, but by its own admission the hues were **never run through `remarque-audit`'s `CHECKS` array** that is the one gap this guidance closes. A dataviz ramp should follow tsundoku's separation-from-accent instinct, but unlike its precedent, it must pass the audit before it ships.
286
+ `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.
287
+
288
+ **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.
289
+
290
+ ### Sequential / diverging ramps
291
+
292
+ 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:
293
+
294
+ 1. **Direct JSON consumption** — read the dataset's `dataviz.sequential`/`dataviz.diverging` arrays straight from the installed package:
295
+ ```js
296
+ const { sequential, diverging } = require('@williamzujkowski/oklch-terminal-themes/themes/remarque-light.json').dataviz;
297
+ ```
298
+ Each entry carries `oklch` (structured `{l, c, h}`) and `oklchCss` (a ready-to-use `oklch(...)` string) — no parsing required.
299
+ 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
300
 
268
301
  ---
269
302
 
@@ -491,11 +524,20 @@ Every PR that ships Remarque pages MUST pass (`npm run audit` automates the colo
491
524
  - [ ] Body line-height ≥ 1.5 (Remarque target: 1.75).
492
525
  - [ ] Every `--color-syntax-*` slot ≥ 4.5:1 against `--color-code-bg` in both themes (see "Syntax Highlighting").
493
526
  - [ ] 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.
527
+ - [ ] Every `--color-viz-*` slot ≥ 3:1 against `--color-bg` in both themes (see "Dataviz Tokens") — the mark-on-background line, not text's 4.5:1. Never used as the sole distinguisher between series — pair with a non-color redundancy (shape, pattern, or a direct label).
494
528
  - [ ] No bare `z-index` number in consumer CSS — reference `tokens-core.css`'s `--z-*` scale (see "Stacking").
495
529
  - [ ] Any new color-only affordance (hover/focus signal, state indicator, structural divider) is checked against forced-colors mode — does it survive, or does it need a `@media (forced-colors: active)` fallback? (see "Forced Colors & Contrast Preferences")
496
530
 
497
531
  Agents reviewing PRs should reject changes that violate any line above without explicit rationale.
498
532
 
533
+ ### APCA: Considered, Not Adopted (2026-07-23)
534
+
535
+ APCA (Accessible Perceptual Contrast Algorithm) was pulled into the WCAG3 working draft in 2023 and remains exploratory — no ratified conformance model specifies it, and that status is unchanged as of this writing. Remarque holds **WCAG 2.x's ratio-based contrast** as the enforcement gate (see "Contrast Ratios" above and the Enforcement Checklist) rather than building the audit toward an unratified algorithm.
536
+
537
+ This is a deliberate policy/data split, not an oversight: the upstream `@williamzujkowski/oklch-terminal-themes` dataset is adding APCA Lc values as *data* on its themes (oklch-terminal-themes#151) — a reasonable thing for a dataset to expose so consumers can experiment. Remarque's own audit gate does not consume it. Data availability and policy adoption are two different questions, and this project is only answering one of them right now.
538
+
539
+ **Revisit trigger:** a ratified WCAG3 conformance model that specifies an actual APCA (or successor) algorithm and threshold set. Until then, this is dated rather than re-litigated on every PR that mentions contrast.
540
+
499
541
  ---
500
542
 
501
543
  ## Lineage: Butterick's Five Rules
@@ -721,6 +763,10 @@ for the value-by-value mapping).
721
763
 
722
764
  ### Markup contract
723
765
 
766
+ <!-- registry-usage:essay — scripts/build-registry.mjs (issue #100) extracts
767
+ the fenced block immediately below into registry/essay.json's
768
+ usage.html verbatim. Keep this the single, hand-authored known-good
769
+ sample — do not fork a second copy for the registry. -->
724
770
  ```html
725
771
  <article class="remarque-essay">
726
772
  <header class="content-reading">
@@ -902,6 +948,9 @@ Four primitives, composed independently — a page can use any subset:
902
948
 
903
949
  ### Markup contract
904
950
 
951
+ <!-- registry-usage:broadsheet — scripts/build-registry.mjs (issue #100)
952
+ extracts the fenced block immediately below into
953
+ registry/broadsheet.json's usage.html verbatim. -->
905
954
  ```html
906
955
  <header class="remarque-masthead">
907
956
  <p class="remarque-masthead-kicker"><span>Field Notes</span></p>
@@ -1032,6 +1081,9 @@ existing literal.
1032
1081
 
1033
1082
  ### Markup contract
1034
1083
 
1084
+ <!-- registry-usage:forms — scripts/build-registry.mjs (issue #100) extracts
1085
+ the fenced block immediately below into registry/forms.json's
1086
+ usage.html verbatim. -->
1035
1087
  ```html
1036
1088
  <div class="remarque-field">
1037
1089
  <label class="remarque-field-label" for="email">Email</label>
@@ -1239,6 +1291,15 @@ Set (or clear) `data-palette` on `<html>`, alongside the existing
1239
1291
  carries *both* light and dark halves, so the light/dark toggle keeps
1240
1292
  working no matter which palette (or none) is active:
1241
1293
 
1294
+ <!-- registry-usage:palette-deck — scripts/build-registry.mjs (issue #100)
1295
+ extracts the fenced block immediately below into
1296
+ registry/palette-deck.json's usage.html verbatim. The FOUC-safe
1297
+ `<script>` snippet a few lines down is deliberately NOT marked for
1298
+ extraction — see REMARQUE.md "The Registry" and the PR description
1299
+ for why (the registry's blocking no-executable-content condition is
1300
+ held to an unambiguous zero-`<script>`-tag bar across every item;
1301
+ that snippet's full text still lives here, one section up, and is
1302
+ linked from the registry item's `docs` URL). -->
1242
1303
  ```html
1243
1304
  <html data-theme="dark" data-palette="gruvbox">
1244
1305
  ```
@@ -1291,6 +1352,175 @@ switching between them became the only piece worth a shared module.
1291
1352
 
1292
1353
  ---
1293
1354
 
1355
+ ## The Registry
1356
+
1357
+ A machine-readable markup-contract registry — `registry.json` (index) +
1358
+ `registry/<name>.json` (four full items: `essay`, `broadsheet`, `forms`,
1359
+ `palette-deck`) — shipped for the ratified program's flagship item
1360
+ (issue #100). The funding rationale is narrow and mechanical, not
1361
+ "category-defining": it exists to prevent the exact bug class found in
1362
+ the flagship migration's sidenote implementation (#89,
1363
+ williamzujkowski.github.io#380) — an aria-label that drifted out of DOM
1364
+ order because the markup contract lived only as ~80 lines of spec prose
1365
+ an agent had to transcribe by hand. A registry item is that same known-
1366
+ good markup as versioned, hash-pinned DATA an agent fetches and applies
1367
+ instead.
1368
+
1369
+ **Shape.** Adopted from shadcn/ui's `registry-item.json`/`registry.json`
1370
+ (`https://ui.shadcn.com/schema/registry-item.json`,
1371
+ `https://ui.shadcn.com/schema/registry.json`) — field NAMES that fit a
1372
+ CSS+HTML contract are kept as-is (`name`, `title`, `description`,
1373
+ `dependencies`, `registryDependencies`, `files[].path`/`content`/`type`,
1374
+ `cssVars`, `docs`); this is deliberately **not** a parallel invented
1375
+ schema. The two vendored, hand-authored schemas
1376
+ (`registry-item.schema.json`, `registry.schema.json` at the package
1377
+ root) document every adaptation and omission inline, in the property
1378
+ descriptions themselves — the summary:
1379
+
1380
+ - **`type` is `"remarque:contract"`**, not one of shadcn's `registry:*`
1381
+ values (`registry:lib`/`component`/`ui`/`hook`/`page`/...). None of
1382
+ those describe a dependency-free CSS module plus its markup sample —
1383
+ every shadcn value assumes an installable React-ecosystem artifact.
1384
+ One honest value covers all four items today, since they're all the
1385
+ same KIND of thing; a second value would only be minted for a future
1386
+ item that is a genuinely different kind of contract.
1387
+ - **`cssVars` is repurposed**: shadcn's `cssVars` holds theme
1388
+ {`theme`/`light`/`dark`} VALUES to merge into a consumer's stylesheet.
1389
+ This registry ships whole CSS files verbatim instead (single-sourced
1390
+ from the real `.css` at build time — see "Single-sourcing" below), so
1391
+ there's nothing to merge; `cssVars` here is the flat, deduped list of
1392
+ `--custom-property` NAMES the item's CSS actually reads via `var(...)`
1393
+ — the palette-tier tokens it consumes, mechanically grepped, never
1394
+ hand-maintained.
1395
+ - **`docs` is a URL, not markdown.** shadcn's `docs` is a documentation
1396
+ blob; this registry's `docs` is a single HTTPS link into this file's
1397
+ relevant section. The registry doesn't duplicate spec prose — it
1398
+ points back at the one place that prose lives.
1399
+ - **Omitted fields** (documented once here, not repeated per-field):
1400
+ `tailwind`/`css`/`envVars` (no Tailwind config, direct CSS injection,
1401
+ or env vars — these items ship whole files, not fragments to splice
1402
+ in), `font`/`extends`/`style`/`iconLibrary`/`baseColor`/`theme` (all
1403
+ `registry:font`/`registry:base`-specific — not applicable to any item
1404
+ here), `files[].target` (no CLI installer — see "What this deliberately
1405
+ is NOT"), `meta` (no per-item metadata that doesn't already have a
1406
+ named field).
1407
+ - **`registry.json` is a lightweight index**, not shadcn's full-item
1408
+ embedding. shadcn's `items` array holds complete registry-item objects
1409
+ inline; this index instead lists one pointer per item (`name`, `type`,
1410
+ `title`, `description`, `version`, `integrity`, `file`) and leaves the
1411
+ full payload — all file content — in `registry/<name>.json` only, so a
1412
+ consumer can discover what exists and check its pinned hash before
1413
+ deciding whether to fetch the full item.
1414
+
1415
+ **Security (panel-mandated, blocking).** Every item carries a `version`
1416
+ (the exact `remarque-tokens` release it was generated from) and an
1417
+ `integrity` hash (`sha256-<base64>`, the same shape as W3C Subresource
1418
+ Integrity — computed over the item's `files` at build time by
1419
+ `scripts/lib/registry-extract.mjs`'s `sha256OfFiles`); `registry.json`'s
1420
+ index mirrors both per item, so a consumer can check the pin before
1421
+ fetching the full payload and must recheck it against what it actually
1422
+ receives. Every `docs`/`homepage` URL is `https://`. No item contains
1423
+ executable content — `scripts/test-registry.mjs` asserts no file's
1424
+ `content`, across every item without exception, contains the substring
1425
+ `<script`.
1426
+
1427
+ That last rule is why `deck.js` — the Palette Deck's runtime module, a
1428
+ dependency-free ~60-line ESM file, and the one JS asset this package
1429
+ ships — is **not embedded in the registry at all**, even hashed and
1430
+ labeled. The panel's condition reads as an absolute ("no executable
1431
+ content in registry items ... files are HTML/CSS data"), and the honest
1432
+ call under a *blocking* security condition is to hold that line without
1433
+ exception rather than carve out a documented-but-included special case.
1434
+ Concretely: `registry/palette-deck.json` ships **only** the
1435
+ `data-theme`/`data-palette` HTML fragment from this file's "Markup/wiring
1436
+ contract" section — it does *not* include the FOUC-safe restore
1437
+ `<script>` sample documented a few lines below that fragment, even
1438
+ though that snippet is (arguably) still "just an HTML example." Holding
1439
+ every item to a zero-`<script>`-tag bar, with no case-by-case judgment
1440
+ call about which scripts "count," is a simpler rule to mechanically
1441
+ enforce forever than "no script tags, except this one, which is fine
1442
+ because—". Consumers keep getting `deck.js` the normal way — the
1443
+ `remarque-tokens/deck` package import, listed as an ordinary
1444
+ `dependencies` entry on every item — and the FOUC snippet's full text
1445
+ from this section, linked via the item's `docs` URL.
1446
+
1447
+ **Modeling the tokens dependency.** essay/broadsheet/forms all consume
1448
+ palette-tier tokens, and the issue asked whether `tokens` should be a
1449
+ fifth registry item or a documented external dependency. It's the
1450
+ latter: every item's `dependencies` array lists `"remarque-tokens"` — an
1451
+ ordinary npm dependency, the same field shadcn uses for npm packages a
1452
+ component needs. Modeling tokens as a *registry item* would mean
1453
+ re-embedding (a subset of, or all of) `tokens.json`'s content a second
1454
+ time in a differently-shaped artifact; `tokens.json`/`tokens.schema.json`
1455
+ already exist as the purpose-built machine-readable form of the tokens
1456
+ (issue #99), served at the same site alongside this registry. Depending
1457
+ on the package that ships them is the smaller, honest mechanism.
1458
+
1459
+ **Single-sourcing.** Every item's CSS `content` is read verbatim from the
1460
+ real `.css` file at build time (`essay.css`, `broadsheet.css`,
1461
+ `forms.css`) — never a second hand-copied literal. Every item's
1462
+ `usage.html` is extracted from THIS file's own "Markup contract" fenced
1463
+ ```html blocks via an HTML-comment marker
1464
+ (`<!-- registry-usage:<name> -->`) placed immediately before the fence —
1465
+ `scripts/lib/registry-extract.mjs`'s `extractUsageHtml` finds the marker,
1466
+ then the next fenced block, and lifts its content verbatim. This spec's
1467
+ prose stays the ONE hand-authored copy of each known-good sample; the
1468
+ registry is a build artifact derived from it, exactly like `tokens.json`
1469
+ is derived from the CSS. `scripts/build-registry.mjs --check` gates
1470
+ freshness in CI the same way `scripts/tokens-json.mjs --check` already
1471
+ does for tokens.
1472
+
1473
+ **Validation (the enforce-don't-instruct house pattern).**
1474
+ `scripts/test-registry.mjs` is the CI gate, wired alongside the other
1475
+ freshness/fixture checks in `deploy.yml`. It re-derives, mechanically,
1476
+ the exact contract lines this issue is funded on — not by trusting the
1477
+ prose, by parsing the generated `usage.html` itself:
1478
+
1479
+ - Every essay `.remarque-sidenote-ref` carries `aria-label="Note N"`, and
1480
+ refs/notes strictly alternate in DOM order — the precise shape of the
1481
+ #89 bug.
1482
+ - The essay TOC rail (`.remarque-toc-rail`) carries `aria-label`.
1483
+ - Every broadsheet `.remarque-entry-numeral` carries `data-entry-number`.
1484
+ - Every forms `<input id="...">` has a matching `<label for="...">`, and
1485
+ every `aria-describedby` target id exists in the sample.
1486
+ - No file, in any item, contains `<script` (the no-executable-content
1487
+ rule above).
1488
+
1489
+ Plus the structural gates any generated artifact needs: schema
1490
+ validation (via `ajv`, the same devDependency `tokens.schema.json`
1491
+ already uses) with negative fixtures proving the schemas actually reject
1492
+ malformed input, and an independent recomputation of every `integrity`
1493
+ hash proving it wasn't just carried over from a stale build.
1494
+
1495
+ **What this deliberately is NOT.** No CLI installer — there is no
1496
+ `npx remarque-registry add essay` command, and no `files[].target`
1497
+ field to resolve an install path against. An agent's expected workflow
1498
+ is fetch the JSON, read `usage.html`/the CSS `content`, and apply it with
1499
+ its own editing tools — the registry replaces transcription-from-prose,
1500
+ not an installer. No archetype "starter-page" items this round: the
1501
+ issue floated one per archetype (Essay, Dossier, Notebook, ...) as an
1502
+ IF-cheap extra, and the honest call is SKIP — archetype pages live as
1503
+ demo-specific Astro files (`site/src/pages/**`), not as a reusable
1504
+ module with one canonical markup sample the way the four shipped items
1505
+ are, so extracting one would mean inventing a new "canonical" sample
1506
+ rather than single-sourcing an existing one.
1507
+
1508
+ **Fetching it.** The demo site serves the registry the same way it
1509
+ serves `tokens.json` (`site/scripts/copy-tokens-json.mjs`, extended):
1510
+
1511
+ - Index: **https://williamzujkowski.github.io/remarque/registry.json**
1512
+ - Per-item: **https://williamzujkowski.github.io/remarque/registry/essay.json**
1513
+ (and `broadsheet.json`, `forms.json`, `palette-deck.json`)
1514
+ - Schemas: **https://williamzujkowski.github.io/remarque/registry-item.schema.json**,
1515
+ **https://williamzujkowski.github.io/remarque/registry.schema.json**
1516
+
1517
+ npm package exports mirror the same paths: `remarque-tokens/registry.json`,
1518
+ `remarque-tokens/registry/essay` (the `./registry/*` subpath maps to
1519
+ `./registry/*.json`), `remarque-tokens/registry-item.schema.json`,
1520
+ `remarque-tokens/registry.schema.json`.
1521
+
1522
+ ---
1523
+
1294
1524
  ## Signature Moves
1295
1525
 
1296
1526
  These are the repeatable visual tells that make a Remarque site recognizable:
@@ -1321,6 +1551,40 @@ An implementation succeeds when:
1321
1551
 
1322
1552
  ---
1323
1553
 
1554
+ ## Governance & Deprecation
1555
+
1556
+ Remarque is small enough that governance fits on a page, but it now ships as a published npm package with multiple consuming sites and a CI gate other repos call into — enough surface that the rules deserve writing down rather than living only in maintainer habit. The precedent here is deliberately modest: closer to how a single-maintainer Carbon or Polaris component documents its own contract than to either project's full RFC process.
1557
+
1558
+ ### Semver contract
1559
+
1560
+ Remarque ships as a CSS token system, not an application — "breaking" means something different than it does for a library with a JS API surface:
1561
+
1562
+ - **MAJOR** — a token is **removed** or **renamed**. Anything a consumer's `var(--color-x)` or a `tokens.json` lookup resolves today must keep resolving after a minor or patch bump; only a major bump may take that away.
1563
+ - **MINOR** — a token's **value changes**, or a token is **added**. Value changes always carry a stated rationale in the CHANGELOG entry (a contrast fix, a gamut correction, an upstream dataset repin) — the change is minor because the *name* stays stable and every consumer reads it through `var()` indirection, but it is never silent. Additive changes (new tokens, new optional CLI flags, new subpath exports) are minor by definition — nothing existing changes shape.
1564
+ - **PATCH** — tool/script fixes with no token or CSS output change (a `remarque-audit` parser bug, a `tokens-json.mjs` generation fix, a fixture correction). If `tokens.json`/`tokens.d.ts`'s content is byte-identical before and after, it's a patch.
1565
+
1566
+ The project is still pre-1.0 — under strict SemVer every 0.x release is technically allowed to break anything — but every release to date has in practice held to the contract above (no token has ever been removed or renamed; see the version history in CHANGELOG.md). 1.0 is the point where that becomes a promise instead of an observed pattern.
1567
+
1568
+ ### Deprecation windows
1569
+
1570
+ A token or convention slated for removal is announced in the CHANGELOG at least one MINOR release before it disappears, named explicitly with a target version — not left implicit in a commit message. The table below is the centralized registry of every open sunset commitment; a PR that adds a new one should add a row here in the same commit, not just mention it once in the CHANGELOG.
1571
+
1572
+ | Deprecated / bridged item | Introduced | Sunset target | Rationale |
1573
+ |---|---|---|---|
1574
+ | `:root.dark` compatibility selector (alongside canonical `[data-theme="dark"]`) | 0.5.0 | 1.0 | Bridges class-keyed consumer sites onto the attribute-keyed convention `remarque-audit` treats as canonical — the audit already parses both forms, so the bridge costs nothing today. Platform endgame to watch instead: `color-scheme` + `light-dark()` (see "Technology Stack"). |
1575
+
1576
+ No other open sunset commitments exist as of this writing (grepped against REMARQUE.md and CHANGELOG.md for "sunset"/"deprecated"). This table is meant to be the single place to look — but only if every future commitment is added here, not just announced in passing.
1577
+
1578
+ ### Decision-log habit
1579
+
1580
+ Every design decision with more than one reasonable answer — a token value, a deviation from a cited precedent (Butterick, Carbon, USWDS), a scope change to an in-flight issue — gets a recorded vote, not just an implementation. The convention has been practiced since 0.5.0; this section states it as policy rather than leaving it implicit:
1581
+
1582
+ - The CHANGELOG entry for the release states the vote count (e.g. "ratified 3-0 by consensus panel") and, when the outcome wasn't unanimous or shipped with conditions, what those conditions were.
1583
+ - REMARQUE.md carries the same treatment inline wherever a deviation is asserted against upstream guidance — see "Lineage: Butterick's Five Rules" below for the fullest example: the guidance, the deviation, the argued rationale, and the ratification line together.
1584
+ - The habit exists so a future maintainer — human or agent — can tell the difference between "nobody thought about this" and "this was considered and decided," without reconstructing the reasoning from a diff.
1585
+
1586
+ ---
1587
+
1324
1588
  ## What Remarque Is Named For
1325
1589
 
1326
1590
  In fine art printmaking, a *remarque* is a small original drawing made by the artist in the margin of a print. Found only in limited proof editions, the remarque is the mark that says: this was made by a specific person, with intention, by hand.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remarque-tokens",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "engines": {
5
5
  "node": ">=18"
6
6
  },
@@ -32,12 +32,12 @@
32
32
  "drift": "node scripts/drift-check.mjs"
33
33
  },
34
34
  "devDependencies": {
35
- "@williamzujkowski/oklch-terminal-themes": "0.4.0",
35
+ "@williamzujkowski/oklch-terminal-themes": "0.5.0",
36
36
  "ajv": "^8.20.0",
37
37
  "culori": "^4.0.2"
38
38
  },
39
39
  "peerDependencies": {
40
- "@williamzujkowski/oklch-terminal-themes": ">=0.1.0 <0.3.0"
40
+ "@williamzujkowski/oklch-terminal-themes": ">=0.1.0 <1.0.0"
41
41
  },
42
42
  "peerDependenciesMeta": {
43
43
  "@williamzujkowski/oklch-terminal-themes": {
@@ -80,6 +80,10 @@
80
80
  "./deck.js": "./deck.js",
81
81
  "./agent-rules": "./AGENT_RULES.md",
82
82
  "./spec": "./REMARQUE.md",
83
+ "./registry.json": "./registry.json",
84
+ "./registry-item.schema.json": "./registry-item.schema.json",
85
+ "./registry.schema.json": "./registry.schema.json",
86
+ "./registry/*": "./registry/*.json",
83
87
  "./package.json": "./package.json"
84
88
  },
85
89
  "files": [
@@ -107,6 +111,12 @@
107
111
  "broadsheet.css",
108
112
  "forms.css",
109
113
  "deck.js",
110
- "scripts/lib/css-tokens.mjs"
114
+ "scripts/lib/css-tokens.mjs",
115
+ "registry.json",
116
+ "registry/",
117
+ "registry-item.schema.json",
118
+ "registry.schema.json",
119
+ "scripts/build-registry.mjs",
120
+ "scripts/lib/registry-extract.mjs"
111
121
  ]
112
122
  }
@@ -0,0 +1,66 @@
1
+ {
2
+ "$schema": "https://williamzujkowski.github.io/remarque/registry-item.schema.json",
3
+ "name": "broadsheet",
4
+ "type": "remarque:contract",
5
+ "title": "Broadsheet Pattern",
6
+ "description": "The editorial Landing/archive pattern: masthead, lead article, numbered entry list, and post-header kicker. Entry numerals are generated from `data-entry-number` via `attr()`, never `counter()`, and every kicker/dateline row is true `font-variant-caps: all-small-caps`, never `text-transform: uppercase`.",
7
+ "version": "0.23.0",
8
+ "integrity": "sha256-BZea38PRdob4OfmdGN0eDw3U0adl7SEsdrjKgSewK9A=",
9
+ "dependencies": [
10
+ "remarque-tokens"
11
+ ],
12
+ "cssVars": [
13
+ "--border-style",
14
+ "--border-width",
15
+ "--color-accent",
16
+ "--color-border",
17
+ "--color-border-bold",
18
+ "--color-fg",
19
+ "--color-fg-muted",
20
+ "--color-muted",
21
+ "--content-reading",
22
+ "--font-body",
23
+ "--font-display",
24
+ "--font-mono",
25
+ "--leading-body",
26
+ "--leading-display",
27
+ "--leading-meta",
28
+ "--leading-section",
29
+ "--motion-easing",
30
+ "--motion-normal",
31
+ "--remarque-masthead-size",
32
+ "--space-10",
33
+ "--space-2",
34
+ "--space-3",
35
+ "--space-4",
36
+ "--space-5",
37
+ "--space-6",
38
+ "--space-7",
39
+ "--space-8",
40
+ "--text-body",
41
+ "--text-body-lg",
42
+ "--text-display",
43
+ "--text-meta",
44
+ "--text-section",
45
+ "--text-title",
46
+ "--tracking-caps",
47
+ "--tracking-display",
48
+ "--tracking-title",
49
+ "--weight-display",
50
+ "--weight-medium",
51
+ "--weight-regular"
52
+ ],
53
+ "docs": "https://github.com/williamzujkowski/remarque/blob/main/REMARQUE.md#broadsheet",
54
+ "files": [
55
+ {
56
+ "path": "broadsheet.css",
57
+ "content": "/*\n * Remarque — Broadsheet Pattern (masthead, lead, entry list, post kicker)\n * ────────────────────────────────────────────────────────────────────\n * Four editorial-layout primitives for Landing/archive contexts — the\n * typography-maximalist end of the system. Newspaper-broadsheet voice:\n * a nameplate, a featured lead story, a numbered contents list, and a\n * post-header kicker line, built entirely from tokens already shipped\n * in tokens-core.css / tokens-palette.css.\n *\n * PROVENANCE — graduated, not invented here. Validated downstream on\n * williamzujkowski.github.io (landing PR #213, archive/tag/post-header\n * PR #214), ratified for upstreaming in issue #36. Source: `astro-site/\n * src/components/BroadsheetEntry.astro` + `global.css`'s \"Broadsheet\n * layout\" block. As with the essay module (#52), no site-specific\n * literal was copied — every dimension below is re-derived from this\n * package's own tokens; see the inline comments for the value-by-value\n * mapping and the few places the re-expression corrects the source\n * (`text-transform: uppercase` -> `font-variant-caps: all-small-caps`,\n * per REMARQUE.md \"Small Caps\", predates that rule same as essay.css's\n * TOC summary did) or simplifies it (font-weight 300 has no Remarque\n * core token; the italic/lede treatments below lean on color + style\n * alone rather than introduce an un-tokenized weight).\n *\n * Own subpath (remarque-tokens/broadsheet, remarque-tokens/broadsheet.css)\n * — NOT included by the tokens.css aggregator: the Landing/archive\n * pattern is opt-in, most Remarque pages want none of it. Import\n * explicitly, after prose.css if both are in use:\n * @import 'remarque-tokens/broadsheet.css';\n * (string form — see AGENT_RULES.md Pitfall #6.)\n *\n * ─── MARKUP CONTRACT (summary — full version in REMARQUE.md) ──────────\n *\n * <header class=\"remarque-masthead\">\n * <p class=\"remarque-masthead-kicker\"><span>Field Notes</span></p>\n * <h1 class=\"remarque-masthead-title\">Quiet <em>Signals</em></h1>\n * <p class=\"remarque-masthead-dateline\">\n * <span class=\"remarque-rule\" aria-hidden=\"true\"></span>\n * <span>July 23, 2026</span>\n * <span class=\"remarque-rule\" aria-hidden=\"true\"></span>\n * </p>\n * </header>\n *\n * <article class=\"remarque-lead\">\n * <p class=\"remarque-lead-kicker\">\n * <span class=\"remarque-lead-section\">Essay</span>\n * <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n * <span>8 min read</span>\n * <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n * <time datetime=\"2026-04-08\">Apr 8, 2026</time>\n * </p>\n * <h2 class=\"remarque-lead-title\"><a href=\"/writing/x\">Title</a></h2>\n * <p class=\"remarque-lead-lede\">Lede paragraph...</p>\n * </article>\n *\n * <ul class=\"remarque-entry-list\">\n * <li class=\"remarque-entry\">\n * <span class=\"remarque-entry-numeral\" data-entry-number=\"2\"\n * aria-hidden=\"true\"></span>\n * <div class=\"remarque-entry-body\">\n * <p class=\"remarque-entry-kicker\">...</p>\n * <h3 class=\"remarque-entry-title\"><a href=\"/writing/y\">Title</a></h3>\n * <p class=\"remarque-entry-excerpt\">Excerpt...</p>\n * </div>\n * </li>\n * </ul>\n *\n * <header class=\"remarque-post-header\">\n * <p class=\"remarque-post-kicker\">\n * <span class=\"remarque-post-kicker-section\">Essay</span>\n * <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n * <span>8 min read</span>\n * <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n * <time datetime=\"2026-04-08\">Apr 8, 2026</time>\n * </p>\n * <h1 class=\"text-display font-display\">Post Title</h1>\n * </header>\n *\n * `<ul>` (not `<ol>`), deliberately, for the entry list: the visual\n * numeral is decorative (`aria-hidden`, generated from `data-entry-\n * number` via `attr()` — see below), and an `<ol>` would have screen\n * readers announce \"1 of 2\" alongside it, doubling up on the same\n * information in two voices. Matches the flagship's own choice.\n */\n\n/* ─── Shared meta-voice bits ─────────────────────────────────────────\n * Every kicker/dateline row below repeats these declarations rather\n * than depending on `.text-label` as a mixin (tokens-core.css) — same\n * non-mixin precedent as essay.css's TOC summary. All-small-caps, not\n * `text-transform: uppercase` (REMARQUE.md \"Small Caps\": faux uppercase\n * renders at cap-height with no optical correction; the flagship\n * predates that rule and used `text-transform: uppercase` throughout\n * this pattern). `--text-meta` (14px), not `--text-micro` (13px) — the\n * words in a kicker (\"Essay\", \"Field Notes\") are read as content, not\n * bare timestamps/fine print, the same distinction essay.css's sidenote\n * marker already draws against the 13px floor. Tabular lining figures\n * on every numeral here (dates, counts) per REMARQUE.md's Numerals\n * rule — never mixed with the oldstyle register `.remarque-entry-\n * numeral` uses below for the same reason `.text-meta` carries it.\n */\n.remarque-masthead-kicker,\n.remarque-masthead-dateline,\n.remarque-lead-kicker,\n.remarque-entry-kicker,\n.remarque-post-kicker {\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n font-variant-caps: all-small-caps;\n font-variant-numeric: tabular-nums lining-nums;\n letter-spacing: var(--tracking-caps);\n color: var(--color-muted);\n margin: 0;\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.5em;\n}\n\n.remarque-dot {\n color: var(--color-border-bold);\n}\n\n.remarque-lead-section,\n.remarque-entry-section,\n.remarque-post-kicker-section {\n color: var(--color-accent);\n font-weight: var(--weight-medium);\n}\n\n/* Hairline flanking the masthead dateline — decorative only, so a\n 1px border-color rule (not -bold) is the right register; nothing\n depends on it as a boundary.\n Authored as a BORDER, not a `background` fill, on purpose (issue #93\n forced-colors audit finding): this is a bare, non-semantic `<span>`\n with no text content and no border of its own otherwise — under\n `forced-colors: active`, a plain element's `background-color` is\n generally forced to `Canvas` (the page background), which would make\n a background-fill divider disappear entirely, while `border-color` on\n a real border is forced to a visible system border color instead.\n Same 1px visual result at rest (a zero-height box with a 1px top\n border reads identically to a 1px-tall filled box); only the forced-\n colors behavior differs. */\n.remarque-rule {\n display: inline-block;\n width: var(--space-6);\n height: 0;\n border-top: var(--border-width) var(--border-style) var(--color-border-bold);\n}\n\n/* Hairline divider between broadsheet sections (masthead/lead/entry-list) */\n.remarque-broadsheet-rule {\n max-width: var(--content-reading);\n margin: var(--space-6) auto;\n border: 0;\n border-top: var(--border-width) var(--border-style) var(--color-border);\n}\n\n/* ─── 1. Masthead ─────────────────────────────────────────────────────\n * The nameplate. Centered, oversized, the one place in Remarque a\n * title outgrows `--text-display` (tokens-core.css's largest type-\n * scale rung, clamp(2.75rem, 5.5vw, 5rem)) — a Landing masthead is a\n * register above even a Landing page's own `<h1>`.\n *\n * FLUID-TYPE DECISION (issue #36's required call): the existing scale\n * does not reach the flagship's validated clamp(3.5rem, 9vw, 7.5rem).\n * Rather than assert those as bare literals (or add a new core\n * `--text-masthead` token, which the issue explicitly rules out), this\n * is a documented, file-scoped fluid exception whose min/max BOUNDS are\n * arithmetic on core spacing tokens already in tokens-core.css — not\n * new constants:\n * min = --space-7 (3rem) + --space-2 (0.5rem) = 3.5rem\n * max = --space-10 (8rem) - --space-2 (0.5rem) = 7.5rem\n * both resolve to the exact flagship values that were already\n * validated (contrast/gamut/font-floor audits + axe) downstream. The\n * 9vw fluid rate is kept as-is: it continues the same escalating-per-\n * tier vw progression tokens-core.css's own scale already uses\n * (2.25vw section -> 3.5vw title -> 5.5vw display), one step further\n * for a register one step past display. This custom property lives\n * here, not in tokens-core.css — it is NOT a new core token, it is\n * local arithmetic on existing ones, scoped to this opt-in pattern.\n */\n.remarque-masthead {\n --remarque-masthead-size: clamp(\n calc(var(--space-7) + var(--space-2)),\n 9vw,\n calc(var(--space-10) - var(--space-2))\n );\n max-width: var(--content-reading);\n margin: var(--space-8) auto var(--space-6);\n text-align: center;\n}\n\n.remarque-masthead-title {\n font-family: var(--font-display);\n font-size: var(--remarque-masthead-size);\n /* Reuses --leading-display/--tracking-display rather than the\n flagship's bespoke 0.92/-0.035em — a fully tokenized simplification\n at a scale where the difference is not perceptible at normal\n reading distance. */\n line-height: var(--leading-display);\n letter-spacing: var(--tracking-display);\n /* Same --weight-display escape hatch .text-display/.text-title use\n (tokens-core.css) — a palette can compensate thin serif hairlines\n in dark mode (tokens-palette.css already does, 400 light / 500\n dark) and the masthead picks that up for free. */\n font-weight: var(--weight-display, var(--weight-regular));\n color: var(--color-fg);\n margin: 0;\n font-feature-settings: \"liga\" 1, \"kern\" 1;\n font-optical-sizing: auto;\n font-synthesis: none;\n text-wrap: balance;\n}\n\n.remarque-masthead-title em {\n font-style: italic;\n color: var(--color-fg-muted);\n}\n\n.remarque-masthead-dateline {\n margin-block-start: var(--space-4);\n justify-content: center;\n}\n\n/* ─── 2. Lead Article ─────────────────────────────────────────────────\n * The featured story below the masthead (or standing alone atop an\n * archive). Title register: --text-display reaches close enough to\n * the flagship's clamp(2.25rem, 4.5vw, 3.75rem) (2.75-5rem vs.\n * 2.25-3.75rem) that no exception is needed here — \"express via\n * existing type tokens\" (issue #36), the masthead above is the one\n * register that genuinely doesn't fit.\n */\n.remarque-lead {\n max-width: var(--content-reading);\n margin: var(--space-7) auto var(--space-6);\n}\n\n.remarque-lead-kicker {\n margin-block-end: var(--space-3);\n}\n\n.remarque-lead-title {\n font-family: var(--font-display);\n font-size: var(--text-display);\n line-height: var(--leading-display);\n letter-spacing: var(--tracking-display);\n font-weight: var(--weight-display, var(--weight-regular));\n margin: 0 0 var(--space-4);\n text-wrap: balance;\n font-optical-sizing: auto;\n font-synthesis: none;\n}\n\n.remarque-lead-title a,\n.remarque-entry-title a {\n color: var(--color-fg);\n text-decoration: none;\n /* Underline-grow hover (issue #36 \"Findings\"): AA-compliant, and\n reduced-motion-safe for free — tokens-core.css zeroes\n --motion-normal under prefers-reduced-motion, so this needs no\n separate @media guard the way an opacity/transform animation\n would. --motion-easing/--motion-normal replace the flagship's\n hardcoded `220ms ease`. */\n background-image: linear-gradient(var(--color-fg), var(--color-fg));\n background-size: 0 1px;\n background-repeat: no-repeat;\n background-position: 0 95%;\n transition: background-size var(--motion-normal) var(--motion-easing);\n}\n\n.remarque-lead-title a:hover,\n.remarque-lead-title a:focus-visible,\n.remarque-entry-title a:hover,\n.remarque-entry-title a:focus-visible {\n background-size: 100% 1px;\n}\n\n/* Forced-colors fallback (issue #93 audit finding — affordance-loss,\n the clearest one in this file): the underline-grow hover/focus\n affordance above is painted entirely with `background-image:\n linear-gradient(...)`. CSS gradients are on the forced list — under\n `forced-colors: active` a gradient background-image is forced to\n `none`, so the ENTIRE hover/focus signal for these title links would\n otherwise disappear (the `:focus-visible` outline from tokens-core.css\n still fires for keyboard focus, but mouse hover would have no visual\n affordance at all, and even focus would lose this link-specific cue).\n `text-decoration: underline` is not on the forced list and needs no\n override to render visibly, so it stands in as the same \"this is a\n link, and it is the active one\" signal the gradient trick provides\n everywhere else. Scoped to this media query only — the gradient\n underline-grow stays the sole affordance outside forced-colors mode,\n unchanged. */\n@media (forced-colors: active) {\n .remarque-lead-title a:hover,\n .remarque-lead-title a:focus-visible,\n .remarque-entry-title a:hover,\n .remarque-entry-title a:focus-visible {\n text-decoration: underline;\n }\n}\n\n.remarque-lead-lede {\n font-family: var(--font-display);\n font-size: var(--text-body-lg);\n line-height: var(--leading-body);\n font-style: italic;\n font-weight: var(--weight-regular);\n color: var(--color-fg-muted);\n margin: 0;\n text-wrap: pretty;\n hanging-punctuation: first last;\n}\n\n/* Drop cap on italic text needs an explicit ::first-letter override\n (issue #36 \"Findings\") — it cannot just inherit italic from the\n parent's font-style the way a roman-text drop cap can, since this\n pseudo-element re-asserts every typographic property of its own.\n Same mechanics/sizing as prose.css's `.remarque-prose--dropcap`\n (3.5em / 0.85 line-height / initial-letter progressive enhancement)\n for one consistent drop-cap voice across the system, rather than a\n second bespoke number. */\n.remarque-lead-lede::first-letter {\n font-family: var(--font-display);\n font-style: italic;\n font-weight: var(--weight-display, var(--weight-regular));\n font-size: 3.5em;\n line-height: 0.85;\n float: left;\n padding: 0.08em 0.12em 0 0;\n color: var(--color-fg);\n}\n\n@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {\n .remarque-lead-lede::first-letter {\n initial-letter: 3.5;\n -webkit-initial-letter: 3.5;\n float: none;\n padding: 0 0.08em 0 0;\n }\n}\n\n/* ─── 3. Numbered Entry List ──────────────────────────────────────────\n * `container-type: inline-size` — the 2-column/1-column collapse below\n * responds to how much room the list itself has, not the viewport (a\n * clean fit for a container query, matching the flagship's choice; it\n * degrades correctly if this list is ever embedded in a narrower\n * column than the page itself).\n */\n.remarque-entry-list {\n container-type: inline-size;\n max-width: var(--content-reading);\n margin: 0 auto;\n padding: 0;\n list-style: none;\n}\n\n.remarque-entry {\n display: grid;\n /* Numeral column sized in `ch` (intrinsic to the numeral glyphs\n themselves), not an asserted rem literal — the flagship's\n `minmax(3.5rem, 5rem)` re-expressed as \"just wide enough for the\n content,\" the more typographically disciplined choice per\n REMARQUE.md's Decision Protocol when a core token doesn't already\n cover a dimension. */\n grid-template-columns: minmax(2.5ch, 4ch) 1fr;\n gap: var(--space-4);\n padding: var(--space-5) 0;\n border-bottom: var(--border-width) var(--border-style) var(--color-border);\n align-items: start;\n}\n\n.remarque-entry:last-child {\n border-bottom: 0;\n}\n\n/* Oldstyle italic numerals in the outer margin, generated from\n `data-entry-number` via `attr()` — NOT CSS `counter()` (issue #36\n \"Findings\": counter-generated content does not receive the OpenType\n oldstyle-figure treatment reliably across Newsreader's weights, only\n real text/attr() content does). `attr()` reads from the element the\n pseudo-content is generated on, so `data-entry-number` lives on\n `.remarque-entry-numeral` itself, not the parent `<li>`. Both\n `font-variant-numeric: oldstyle-nums proportional-nums` AND\n `font-feature-settings: \"onum\" 1, \"pnum\" 1` are required together\n for reliable rendering across weights — either alone is\n inconsistent (issue #36 \"Findings\"). */\n.remarque-entry-numeral {\n display: block;\n}\n\n.remarque-entry-numeral::before {\n content: attr(data-entry-number);\n font-family: var(--font-display);\n font-style: italic;\n font-size: var(--text-title);\n line-height: 0.95;\n color: var(--color-border-bold);\n font-variant-numeric: oldstyle-nums proportional-nums;\n font-feature-settings: \"onum\" 1, \"pnum\" 1;\n text-align: right;\n display: block;\n padding-block-start: 0.1em;\n user-select: none;\n transition: color var(--motion-normal) var(--motion-easing);\n}\n\n.remarque-entry:hover .remarque-entry-numeral::before,\n.remarque-entry:focus-within .remarque-entry-numeral::before {\n color: var(--color-accent);\n}\n\n.remarque-entry-body {\n min-width: 0; /* allow the grid child to shrink below its content's intrinsic width */\n}\n\n.remarque-entry-kicker {\n margin-block-end: var(--space-2);\n}\n\n.remarque-entry-title {\n font-family: var(--font-display);\n font-size: var(--text-section);\n line-height: var(--leading-section);\n letter-spacing: var(--tracking-title);\n font-weight: var(--weight-medium);\n margin: 0 0 var(--space-2);\n text-wrap: balance;\n font-optical-sizing: auto;\n font-synthesis: none;\n}\n\n.remarque-entry-excerpt {\n font-family: var(--font-body);\n font-size: var(--text-body);\n line-height: var(--leading-body);\n color: var(--color-fg-muted);\n margin: 0;\n text-wrap: pretty;\n}\n\n/* Collapse to one column on a narrow container — `40rem`, exactly\n `--space-10` (8rem) x 5. Container-query conditions can't reference\n custom properties or `calc()` (unlike a plain declaration value, the\n condition itself is parsed before the cascade resolves custom\n properties, and Lightning CSS's minifier rejects a `calc()` there\n outright) so this is spelled as the literal it resolves to, with the\n token-multiple noted here rather than in the syntax — matches the\n flagship's own `640px` breakpoint exactly. */\n@container (max-width: 40rem) {\n .remarque-entry {\n grid-template-columns: 1fr;\n gap: var(--space-2);\n }\n\n .remarque-entry-numeral::before {\n text-align: left;\n font-size: var(--text-section);\n padding-block-start: 0;\n }\n}\n\n/* ─── 4. Post-Header Kicker ───────────────────────────────────────────\n * The mono \"Section · Reading time · Date\" line above an individual\n * post's title — the same kicker voice as the lead/entry rows above,\n * scaled down to a single-line header. Pair with any Remarque heading\n * class (`.text-display`, `.text-title`); this rule only styles the\n * kicker line itself, not the title beneath it.\n */\n.remarque-post-header {\n margin-block-end: var(--space-7);\n}\n\n.remarque-post-kicker {\n margin-block-end: var(--space-3);\n}\n",
58
+ "type": "remarque:css"
59
+ },
60
+ {
61
+ "path": "usage.html",
62
+ "content": "<header class=\"remarque-masthead\">\n <p class=\"remarque-masthead-kicker\"><span>Field Notes</span></p>\n <h1 class=\"remarque-masthead-title\">Quiet <em>Signals</em></h1>\n <p class=\"remarque-masthead-dateline\">\n <span class=\"remarque-rule\" aria-hidden=\"true\"></span>\n <span>July 23, 2026</span>\n <span class=\"remarque-rule\" aria-hidden=\"true\"></span>\n </p>\n</header>\n\n<article class=\"remarque-lead\">\n <p class=\"remarque-lead-kicker\">\n <span class=\"remarque-lead-section\">Essay</span>\n <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n <span>8 min read</span>\n <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n <time datetime=\"2026-04-08\">Apr 8, 2026</time>\n </p>\n <h2 class=\"remarque-lead-title\"><a href=\"/writing/x\">Title</a></h2>\n <p class=\"remarque-lead-lede\">Lede paragraph…</p>\n</article>\n\n<ul class=\"remarque-entry-list\">\n <li class=\"remarque-entry\">\n <span class=\"remarque-entry-numeral\" data-entry-number=\"2\" aria-hidden=\"true\"></span>\n <div class=\"remarque-entry-body\">\n <p class=\"remarque-entry-kicker\">\n <span class=\"remarque-entry-section\">Essay</span>\n <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n <span>6 min read</span>\n <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n <time datetime=\"2026-03-29\">Mar 29, 2026</time>\n </p>\n <h3 class=\"remarque-entry-title\"><a href=\"/writing/y\">Title</a></h3>\n <p class=\"remarque-entry-excerpt\">Excerpt…</p>\n </div>\n </li>\n</ul>\n\n<header class=\"remarque-post-header\">\n <p class=\"remarque-post-kicker\">\n <span class=\"remarque-post-kicker-section\">Essay</span>\n <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n <span>8 min read</span>\n <span class=\"remarque-dot\" aria-hidden=\"true\">&middot;</span>\n <time datetime=\"2026-04-08\">Apr 8, 2026</time>\n </p>\n <h1 class=\"text-display font-display\">Post Title</h1>\n</header>\n",
63
+ "type": "remarque:markup"
64
+ }
65
+ ]
66
+ }