remarque-tokens 0.28.0 → 0.29.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
@@ -8,7 +8,7 @@ Read `REMARQUE.md` first for the full system specification. This file tells you
8
8
 
9
9
  ## Prefer the Registry Over Transcribing Prose
10
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.**
11
+ For the Essay Module, the Broadsheet pattern, Forms and the Chrome Module (`remarque-tokens/essay`, `remarque-tokens/broadsheet`, `remarque-tokens/forms`, `remarque-tokens/chrome`), 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
12
 
13
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
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.
@@ -222,6 +222,7 @@ Before considering any implementation complete, verify:
222
222
  - [ ] Every page maps to an archetype (Essay, Dossier, Notebook, Landing, Reference/Docs, Changelog, Gallery)
223
223
  - [ ] If the Essay uses sidenotes/a TOC rail (`remarque-tokens/essay`): `.remarque-sidenote-ref`/`.remarque-sidenote` alternate in strict DOM order, the rail never intrudes into `.remarque-prose`'s own measure, and the page renders correctly with `essay.css`'s `@media` block deleted (the narrow-viewport/no-JS fallback)
224
224
  - [ ] If a Landing/archive page uses the Broadsheet pattern (`remarque-tokens/broadsheet`): entry numerals are generated from `data-entry-number` via `attr()` (not `counter()`), the entry list stays a `<ul>` (not `<ol>` — the numeral is `aria-hidden` and decorative), and every kicker/dateline row uses the label voice: tracked capitals (`text-transform: uppercase` + `--tracking-caps`), because the default mono face has no small caps; `font-variant-caps: all-small-caps` only on a label face that has `smcp` + `c2sc` (REMARQUE.md "Small Caps"; `remarque-audit` fails synthesized small caps)
225
+ - [ ] Site chrome uses `remarque-tokens/chrome` instead of hand-rolled copies: `.remarque-skip-link` is the first focusable element and targets `<main id="main-content" tabindex="-1">`; a breadcrumb's current page is `<span aria-current="page">`, never a link, with no separator typed into the DOM; prev/next links carry `rel="prev"`/`rel="next"` and an endpoint with nowhere to go is omitted (or plain text in numbered pagination), never a disabled-looking link; tags are plain mono links with `rel="tag"`, no border, fill or radius (REMARQUE.md "Chrome Module")
225
226
  - [ ] If a page uses form controls (`remarque-tokens/forms`): checkboxes/radios are styled with `accent-color` only — never `appearance: none` plus a hand-drawn replacement; every control (input, button, checkbox/radio label) is ≥44×44px; every input collecting known personal data has a valid `autocomplete` token and paste is never blocked (WCAG 3.3.7/3.3.8); validation state lives on `.remarque-field[data-state]` AND a real `aria-invalid`/`aria-describedby` pair on the input, not `data-state` alone; disabled controls use `--color-disabled`, never a state color
226
227
  - [ ] If a page uses the Palette Deck (`remarque-tokens/deck`): `data-palette` is set/cleared on the same root element as `data-theme` (they compose, never conflict); the FOUC-safe restore is a synchronous classic `<script>` physically in `<head>` (never `type="module"`, which defers); the switcher control itself is a native, keyboard-operable element (e.g. `.remarque-input` `<select>`) at ≥44×44px, not a hand-rolled widget; every registered palette was generated via `remarque-theme --scope`, never hand-authored
227
228
  - [ ] Mobile version is roomy — not a compressed desktop layout
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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.29.0 — 2026-10-24
8
+
9
+ The "chrome" minor of the 2026-10 roadmap (#133, ratified 7–0 in `vote-1792819887868-esau595`): a new `remarque-tokens/chrome` module and four registry items. Additive: nothing existing changes for consumers who don't import it.
10
+
11
+ ### Added — Chrome Module (#127; additive)
12
+
13
+ - **New subpath `remarque-tokens/chrome` (`chrome.css`):** the site-chrome and wayfinding pieces the spec required and never shipped CSS for, so every consumer hand-rolled them. The skip link had four implementations across the flagship, tsundoku, the starter and this demo, and one tsundoku pagination kept its "disabled" endpoints as focusable links. Not aggregated into `tokens.css`.
14
+ - **Skip link** (`.remarque-skip-link`, plus `.remarque-sr-only`): first focusable element, targets `<main id="main-content" tabindex="-1">`, hidden by clip-path until focused (never an off-screen offset, which breaks in RTL), then a filled fg-on-bg block on `--z-skip-link`, 44px tall, bordered under forced colors.
15
+ - **Breadcrumb** (`nav.remarque-breadcrumb > ol`): mono `--text-meta`, the current page `<span aria-current="page">`, separators generated with empty alt text so screen readers don't read them.
16
+ - **Pager** (`.remarque-pager`): prev/next with `rel`, a label line over the target's title, arrows that flip in RTL, an optional series row with a `<details>` part list. Plus numbered `.remarque-pagination`. An endpoint with nowhere to go is omitted, or plain text in pagination.
17
+ - **Tags** (`.remarque-tags`): plain mono links, `rel="tag"`, no border, fill or radius. An outlined chip is a documented deviation.
18
+ - The module sets no outer margins on the lists consumers position, because it's unlayered and would beat Tailwind utilities (Pitfall #7).
19
+ - **Four new registry items** (`skip-link`, `breadcrumb`, `pager`, `tags`), each with a REMARQUE.md "Chrome Module" markup contract that `scripts/test-registry.mjs` asserts: the skip-link target, a current page that isn't a link, no DOM separators, `rel` on pager and tag links, no disabled-looking links, and no border, fill or radius on tags.
20
+ - `print.css` hides the skip link, pager and pagination. AGENT_RULES gains a chrome checklist line. `remarque-audit`'s font-feature check and the budget/closure test cover the new module.
21
+ - Demo: the layout's Tailwind skip-link chain, which set `focus:outline-none`, is replaced by `.remarque-skip-link`; essays end with the pager; the dossier has a breadcrumb; `/components` gains a Chrome section. New `site/tests/chrome.spec.ts` covers the keyboard skip flow, separators and arrows out of the accessibility tree, pager sides with an omitted endpoint, tag styling, and the 44px pagination targets.
22
+
7
23
  ## 0.28.0 — 2026-10-24
8
24
 
9
25
  The "truth" minor of the 2026-10 roadmap (#133): it makes the spec's claims true on the default stack. It was researched with Open Design and nexus-agents, ratified 7–0 (`vote-1792819887868-esau595`), and landed as one PR per issue, each with its own regression test proven red on 0.27.0. **Consumer-visible changes:**
package/README.md CHANGED
@@ -107,14 +107,15 @@ Output" shape changes) to pick up the current playbook.
107
107
  | `tokens-core.css` | Core tier — type scale, spacing, widths, radius, motion, prose styling. Never overridden |
108
108
  | `tokens-palette.css` | Palette tier — font slots, colors, accent, reading measure. The sanctioned personalization surface |
109
109
  | `prose.css` | `.remarque-prose` long-form styling — own subpath so sites with their own prose system can skip it |
110
+ | `chrome.css` | Site chrome and wayfinding (`remarque-tokens/chrome`, 0.29): skip link + `.remarque-sr-only`, breadcrumb, pager/pagination, tags — see REMARQUE.md "Chrome Module" |
110
111
  | `scripts/audit.mjs` | `npm run audit` — enforces the spec's contrast/gamut/font-floor/no-hardcoded-color checklist (`--json` for structured output) |
111
112
  | `scripts/drift-check.mjs` | `npx remarque-drift` — token drift check for consumers (`--json` for structured output) |
112
113
  | `tokens.json` + `tokens.d.ts` | Generated machine-readable token inventory + TypeScript types (`scripts/tokens-json.mjs`) |
113
114
  | `tokens.schema.json` | Generated JSON Schema (draft 2020-12) for `tokens.json`, published alongside it |
114
- | `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" |
115
+ | `registry.json` + `registry/` | Generated markup-contract registry index + per-item files for essay/broadsheet/forms/skip-link/breadcrumb/pager/tags/palette-deck (`scripts/build-registry.mjs`) — see REMARQUE.md "The Registry" |
115
116
  | `registry-item.schema.json` + `registry.schema.json` | Generated JSON Schemas (draft 2020-12) for the per-item and index registry files |
116
117
  | `skills/remarque/SKILL.md` + `skills/remarque-adopt/SKILL.md` | Generated packaging copies of the two Claude Code skills (`scripts/build-skills.mjs`) — canonical source is `.claude/skills/*/SKILL.md`; see "Installing the skills" above |
117
- | `fonts.css` + `fonts/` | Self-hosted @font-face declarations and woff2 files (no CDN requests) |
118
+ | `fonts.css` + `fonts/` | Self-hosted @font-face declarations and woff2 files (no CDN requests); `fonts/features.json` lists each face's OpenType features for `remarque-audit`'s font-feature check |
118
119
  | `tailwind.config.js` | Tailwind CSS **v3** configuration (v4 projects use an `@theme` block instead) |
119
120
  | `package.json` | npm package manifest for `remarque-tokens` |
120
121
 
@@ -131,7 +132,8 @@ Gzip (level 9) / brotli (max) bytes per shipped stylesheet, enforced by `scripts
131
132
  | `essay.css` | 7,914 | 6,680 |
132
133
  | `broadsheet.css` | 7,463 | 6,241 |
133
134
  | `forms.css` | 7,709 | 6,378 |
134
- | `print.css` | 2,529 | 2,093 |
135
+ | `chrome.css` | 3,296 | 2,772 |
136
+ | `print.css` | 2,542 | 2,095 |
135
137
  | `theme.css` | 1,744 | 1,452 |
136
138
  | `fonts.css` | 612 | 484 |
137
139
 
package/REMARQUE.md CHANGED
@@ -1351,6 +1351,98 @@ instead and keep Remarque to the page's editorial chrome around it.
1351
1351
 
1352
1352
  ---
1353
1353
 
1354
+ ## Chrome Module
1355
+
1356
+ `remarque-tokens/chrome` (`chrome.css`, 0.29, #127): the site-chrome and wayfinding pieces this spec requires and never shipped CSS for, so every consumer hand-rolled its own. The skip link alone had four implementations across the flagship, tsundoku, the starter and this demo. One `left: -9999px` broke under RTL, and a tsundoku pagination kept its "disabled" endpoints as focusable links with `pointer-events: none`. The module isn't aggregated into `tokens.css`; import it after the tokens. Everything in it speaks in the mono meta register, and every link meets the 44px floor on coarse pointers. Each piece is a registry item whose markup contract `scripts/test-registry.mjs` asserts.
1357
+
1358
+ ### Skip link
1359
+
1360
+ The first focusable element in `<body>`, targeting `<main id="main-content" tabindex="-1">`. It's visually hidden (clip-path, never an off-screen offset) until focused, then shows as a filled `--color-fg` block at the top inline-start corner on `--z-skip-link`. The `tabindex="-1"` lets focus land on `<main>`; `chrome.css` drops the outline for that programmatic focus. `.remarque-sr-only` is the same visually-hidden pattern for any text that's for screen readers only. `print.css` hides the skip link.
1361
+
1362
+ <!-- registry-usage:skip-link — scripts/build-registry.mjs (issue #127)
1363
+ extracts the fenced block immediately below into
1364
+ registry/skip-link.json's usage.html verbatim. -->
1365
+ ```html
1366
+ <body>
1367
+ <a class="remarque-skip-link" href="#main-content">Skip to content</a>
1368
+ <header>…site navigation…</header>
1369
+ <main id="main-content" tabindex="-1">…</main>
1370
+ </body>
1371
+ ```
1372
+
1373
+ ### Breadcrumb
1374
+
1375
+ The Reference/Docs "breadcrumb kicker": `nav[aria-label="Breadcrumb"] > ol`. The last item is the current page: `aria-current="page"` on a `<span>`, **not a link**. The separator is generated by CSS with empty alt text, so screen readers don't read "slash", and is never typed into the DOM.
1376
+
1377
+ <!-- registry-usage:breadcrumb — scripts/build-registry.mjs (issue #127)
1378
+ extracts the fenced block immediately below into
1379
+ registry/breadcrumb.json's usage.html verbatim. -->
1380
+ ```html
1381
+ <nav class="remarque-breadcrumb" aria-label="Breadcrumb">
1382
+ <ol>
1383
+ <li><a href="/">Home</a></li>
1384
+ <li><a href="/reference/">Reference</a></li>
1385
+ <li><span aria-current="page">Configuration</span></li>
1386
+ </ol>
1387
+ </nav>
1388
+ ```
1389
+
1390
+ ### Pager
1391
+
1392
+ Prev/next between adjacent pages: the Essay and Reference/Docs footers, and Plate figure navigation. Links carry `rel="prev"` and `rel="next"`; a label line ("Previous", "Next", in the label voice) sits over the target's title. **An endpoint with nothing beyond it is omitted, never rendered as a disabled-looking link.** `.remarque-pager-next` stays on the inline-end side even when it's alone. An optional `.remarque-pager-series` row gives "Part N of M" and a `<details>` list of every part, with the current one `aria-current="page"` and not a link. There's no progress bar; the text carries it. Archives use `.remarque-pagination`: numbered pages in `nav[aria-label="Pagination"] > ol`, where the current page is `aria-current="page"` text, and prev/next at the ends are links when there's somewhere to go and plain text when there isn't.
1393
+
1394
+ <!-- registry-usage:pager — scripts/build-registry.mjs (issue #127)
1395
+ extracts the fenced block immediately below into
1396
+ registry/pager.json's usage.html verbatim. -->
1397
+ ```html
1398
+ <nav class="remarque-pager" aria-label="More writing">
1399
+ <div class="remarque-pager-series">
1400
+ <details>
1401
+ <summary>Part 2 of 3 · Quiet Interfaces</summary>
1402
+ <ol>
1403
+ <li><a href="/writing/quiet-1/">Against Decoration</a></li>
1404
+ <li><span aria-current="page">Typography as Interface</span></li>
1405
+ <li><a href="/writing/quiet-3/">The Reading Column</a></li>
1406
+ </ol>
1407
+ </details>
1408
+ </div>
1409
+ <a class="remarque-pager-prev" href="/writing/quiet-1/" rel="prev">
1410
+ <span class="remarque-pager-label">Previous</span>
1411
+ <span class="remarque-pager-title">Against Decoration</span>
1412
+ </a>
1413
+ <a class="remarque-pager-next" href="/writing/quiet-3/" rel="next">
1414
+ <span class="remarque-pager-label">Next</span>
1415
+ <span class="remarque-pager-title">The Reading Column</span>
1416
+ </a>
1417
+ </nav>
1418
+
1419
+ <nav class="remarque-pagination" aria-label="Pagination">
1420
+ <ol>
1421
+ <li><span>Previous</span></li>
1422
+ <li><span aria-current="page">1</span></li>
1423
+ <li><a href="/writing/page/2/">2</a></li>
1424
+ <li><a href="/writing/page/3/">3</a></li>
1425
+ <li><a href="/writing/page/2/" rel="next">Next</a></li>
1426
+ </ol>
1427
+ </nav>
1428
+ ```
1429
+
1430
+ ### Tags
1431
+
1432
+ "Tags or categories (mono text, not pill badges)": a wrapping row of plain mono links in a `<ul>` with an accessible name, each with `rel="tag"`. **No border, fill or radius.** An outlined or filled chip is a documented deviation (`DESIGN-DEVIATIONS.md`), not personalization, the same rule as semver pills in the Changelog archetype.
1433
+
1434
+ <!-- registry-usage:tags — scripts/build-registry.mjs (issue #127)
1435
+ extracts the fenced block immediately below into
1436
+ registry/tags.json's usage.html verbatim. -->
1437
+ ```html
1438
+ <ul class="remarque-tags" aria-label="Tags">
1439
+ <li><a href="/tags/typography/" rel="tag">typography</a></li>
1440
+ <li><a href="/tags/design-systems/" rel="tag">design-systems</a></li>
1441
+ </ul>
1442
+ ```
1443
+
1444
+ ---
1445
+
1354
1446
  ## Palette Deck
1355
1447
 
1356
1448
  A tiny runtime (`remarque-tokens/deck`, `deck.js`) for switching between
package/chrome.css ADDED
@@ -0,0 +1,364 @@
1
+ /*
2
+ * Remarque — Chrome Module (skip link, breadcrumb, pager, tags)
3
+ * ───────────────────────────────────────────────────────────────
4
+ * The site-chrome and wayfinding pieces the spec requires but never
5
+ * shipped, so every consumer hand-rolled its own (issue #127, roadmap
6
+ * 0.29.0). Four implementations of the skip link alone existed across the
7
+ * flagship, tsundoku, the starter and this demo; one tsundoku pagination
8
+ * kept its disabled endpoints focusable. GOV.UK Frontend and USWDS ship
9
+ * all of these as components.
10
+ *
11
+ * Own subpath (remarque-tokens/chrome, remarque-tokens/chrome.css), not
12
+ * part of the tokens.css aggregator. Import after the tokens:
13
+ * @import 'remarque-tokens/chrome.css';
14
+ * (string form — AGENT_RULES.md Pitfall #6.)
15
+ *
16
+ * Voice: everything here is metadata, so it speaks in the mono meta
17
+ * register (--font-mono, --text-meta, --tracking-meta), never the accent
18
+ * as decoration, never a border or fill around a tag. Every link gets the
19
+ * 44px floor on coarse pointers (WCAG 2.5.8 is met at 24px; Remarque's
20
+ * floor is 44). Markup contracts live in REMARQUE.md "Chrome Module" and
21
+ * are asserted by scripts/test-registry.mjs.
22
+ */
23
+
24
+ /* ─── Screen-reader-only text ────────────────────────────────────────────
25
+ * Visually hidden, still in the accessibility tree. clip-path, not the
26
+ * deprecated clip. */
27
+ .remarque-sr-only {
28
+ position: absolute;
29
+ width: 1px;
30
+ height: 1px;
31
+ margin: -1px;
32
+ padding: 0;
33
+ overflow: hidden;
34
+ clip-path: inset(50%);
35
+ white-space: nowrap;
36
+ border: 0;
37
+ }
38
+
39
+ /* ─── 1. Skip link ───────────────────────────────────────────────────────
40
+ * First focusable element on the page. Hidden like .remarque-sr-only until
41
+ * it takes focus, then a filled fg-on-bg block at the top inline-start
42
+ * corner, above everything (--z-skip-link). It's never moved off-screen
43
+ * with left: -9999px, which breaks in RTL and under zoom. The target is
44
+ * <main id="main-content" tabindex="-1">: the tabindex lets the browser
45
+ * move focus there, and the rule below keeps that programmatic focus from
46
+ * outlining the whole page. */
47
+ .remarque-skip-link {
48
+ position: absolute;
49
+ inset-block-start: var(--space-4);
50
+ inset-inline-start: var(--space-4);
51
+ z-index: var(--z-skip-link);
52
+ display: inline-flex;
53
+ align-items: center;
54
+ min-height: 44px;
55
+ padding-inline: var(--space-4);
56
+ font-family: var(--font-mono);
57
+ font-size: var(--text-meta);
58
+ letter-spacing: var(--tracking-meta);
59
+ color: var(--color-bg);
60
+ background-color: var(--color-fg);
61
+ border-radius: var(--radius-sm);
62
+ text-decoration: none;
63
+ }
64
+
65
+ .remarque-skip-link:not(:focus) {
66
+ width: 1px;
67
+ height: 1px;
68
+ min-height: 0;
69
+ margin: -1px;
70
+ padding: 0;
71
+ overflow: hidden;
72
+ clip-path: inset(50%);
73
+ white-space: nowrap;
74
+ }
75
+
76
+ main[tabindex="-1"]:focus {
77
+ outline: none;
78
+ }
79
+
80
+ /* ─── 2. Breadcrumb ──────────────────────────────────────────────────────
81
+ * The Reference/Docs "breadcrumb kicker" (REMARQUE.md Page Archetypes):
82
+ * nav[aria-label="Breadcrumb"] > ol. The separator is generated, with an
83
+ * empty alt text so screen readers don't read "slash" between items (the
84
+ * first declaration is the fallback for engines without alt text in
85
+ * content). The current page is the last item, aria-current="page", and
86
+ * not a link. */
87
+ .remarque-breadcrumb ol {
88
+ display: flex;
89
+ flex-wrap: wrap;
90
+ align-items: center;
91
+ margin: 0;
92
+ padding: 0;
93
+ list-style: none;
94
+ font-family: var(--font-mono);
95
+ font-size: var(--text-meta);
96
+ line-height: var(--leading-meta);
97
+ letter-spacing: var(--tracking-meta);
98
+ color: var(--color-muted);
99
+ }
100
+
101
+ .remarque-breadcrumb li + li::before {
102
+ content: "/";
103
+ content: "/" / "";
104
+ margin-inline: var(--space-2);
105
+ color: var(--color-muted);
106
+ }
107
+
108
+ .remarque-breadcrumb a {
109
+ color: var(--color-fg-muted);
110
+ text-decoration: none;
111
+ transition: color var(--motion-fast) var(--motion-easing);
112
+ }
113
+
114
+ .remarque-breadcrumb a:hover {
115
+ color: var(--color-fg);
116
+ text-decoration: underline;
117
+ }
118
+
119
+ .remarque-breadcrumb [aria-current="page"] {
120
+ color: var(--color-fg);
121
+ }
122
+
123
+ /* ─── 3. Pager: prev/next, series, numbered pagination ──────────────────
124
+ * Prev/next between adjacent pages (Essay footer, Reference/Docs footer,
125
+ * Plate figure navigation): links carry rel="prev"/rel="next" and sit at
126
+ * the inline-start and inline-end of one row. An endpoint with nothing
127
+ * beyond it is OMITTED (or, in numbered pagination, rendered as plain
128
+ * text): never a link made to look disabled, which stays focusable and
129
+ * announces as a link. Each link is a small label over the target's
130
+ * title, so the title is the thing you read. */
131
+ .remarque-pager {
132
+ display: grid;
133
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
134
+ gap: var(--space-5);
135
+ margin-block-start: var(--space-8);
136
+ padding-block-start: var(--space-6);
137
+ border-block-start: var(--border-width) var(--border-style) var(--color-border);
138
+ }
139
+
140
+ .remarque-pager-prev,
141
+ .remarque-pager-next {
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: var(--space-1);
145
+ min-height: 44px;
146
+ justify-content: center;
147
+ text-decoration: none;
148
+ overflow-wrap: anywhere;
149
+ }
150
+
151
+ .remarque-pager-prev {
152
+ grid-column: 1;
153
+ text-align: start;
154
+ }
155
+
156
+ .remarque-pager-next {
157
+ grid-column: 2;
158
+ text-align: end;
159
+ }
160
+
161
+ /* The label line: "Previous" / "Next", in the label voice. */
162
+ .remarque-pager-label {
163
+ font-family: var(--font-mono);
164
+ font-size: var(--text-meta);
165
+ line-height: var(--leading-meta);
166
+ text-transform: uppercase;
167
+ letter-spacing: var(--tracking-caps);
168
+ color: var(--color-muted);
169
+ }
170
+
171
+ .remarque-pager-title {
172
+ font-family: var(--font-body);
173
+ font-size: var(--text-body);
174
+ line-height: var(--leading-meta);
175
+ color: var(--color-accent);
176
+ transition: color var(--motion-fast) var(--motion-easing);
177
+ }
178
+
179
+ .remarque-pager-prev:hover .remarque-pager-title,
180
+ .remarque-pager-next:hover .remarque-pager-title {
181
+ color: var(--color-accent-hover);
182
+ text-decoration: underline;
183
+ }
184
+
185
+ /* Arrows point along the inline direction, so they flip in RTL. They're
186
+ generated and carry empty alt text: rel and the label already say it. */
187
+ .remarque-pager-prev .remarque-pager-label::before {
188
+ content: "\2190\00a0";
189
+ content: "\2190\00a0" / "";
190
+ }
191
+
192
+ .remarque-pager-next .remarque-pager-label::after {
193
+ content: "\00a0\2192";
194
+ content: "\00a0\2192" / "";
195
+ }
196
+
197
+ .remarque-pager:dir(rtl) .remarque-pager-prev .remarque-pager-label::before {
198
+ content: "\2192\00a0";
199
+ content: "\2192\00a0" / "";
200
+ }
201
+
202
+ .remarque-pager:dir(rtl) .remarque-pager-next .remarque-pager-label::after {
203
+ content: "\00a0\2190";
204
+ content: "\00a0\2190" / "";
205
+ }
206
+
207
+ /* Optional series row: "Part 2 of 4 · Series name" plus a <details> list
208
+ of every part, the current one aria-current="page" and not a link. It
209
+ spans both columns, above the prev/next pair. No progress bar: the text
210
+ says it. */
211
+ .remarque-pager-series {
212
+ grid-column: 1 / -1;
213
+ font-family: var(--font-mono);
214
+ font-size: var(--text-meta);
215
+ line-height: var(--leading-meta);
216
+ letter-spacing: var(--tracking-meta);
217
+ color: var(--color-muted);
218
+ }
219
+
220
+ .remarque-pager-series summary {
221
+ display: inline-flex;
222
+ align-items: center;
223
+ gap: var(--space-2);
224
+ min-height: 44px;
225
+ cursor: pointer;
226
+ list-style: none;
227
+ color: var(--color-fg-muted);
228
+ }
229
+
230
+ /* inline-flex drops the native ::marker, so draw the same marker the
231
+ prose and TOC disclosures use; it points along the inline direction. */
232
+ .remarque-pager-series summary::-webkit-details-marker {
233
+ display: none;
234
+ }
235
+
236
+ .remarque-pager-series summary::before {
237
+ content: "\25B8";
238
+ content: "\25B8" / "";
239
+ color: var(--color-muted);
240
+ transition: transform var(--motion-fast) var(--motion-easing);
241
+ }
242
+
243
+ .remarque-pager-series details[open] summary::before {
244
+ transform: rotate(90deg);
245
+ }
246
+
247
+ .remarque-pager-series summary:dir(rtl)::before {
248
+ transform: scaleX(-1);
249
+ }
250
+
251
+ .remarque-pager-series ol {
252
+ margin: var(--space-2) 0 0;
253
+ padding-inline-start: var(--space-5);
254
+ }
255
+
256
+ .remarque-pager-series li {
257
+ margin-block: var(--space-1);
258
+ }
259
+
260
+ .remarque-pager-series a {
261
+ color: var(--color-fg-muted);
262
+ }
263
+
264
+ .remarque-pager-series [aria-current="page"] {
265
+ color: var(--color-fg);
266
+ }
267
+
268
+ /* Numbered pagination for archives: nav[aria-label="Pagination"] > ol.
269
+ The current page is aria-current="page" text; prev/next at the ends are
270
+ links when there's somewhere to go and plain text when there isn't. */
271
+ .remarque-pagination ol {
272
+ display: flex;
273
+ flex-wrap: wrap;
274
+ align-items: center;
275
+ gap: var(--space-1);
276
+ margin: 0;
277
+ padding: 0;
278
+ list-style: none;
279
+ font-family: var(--font-mono);
280
+ font-size: var(--text-meta);
281
+ font-variant-numeric: tabular-nums lining-nums;
282
+ letter-spacing: var(--tracking-meta);
283
+ color: var(--color-muted);
284
+ }
285
+
286
+ .remarque-pagination a,
287
+ .remarque-pagination li > span {
288
+ display: inline-flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ min-width: 44px;
292
+ min-height: 44px;
293
+ padding-inline: var(--space-2);
294
+ }
295
+
296
+ .remarque-pagination a {
297
+ color: var(--color-fg-muted);
298
+ text-decoration: none;
299
+ }
300
+
301
+ .remarque-pagination a:hover {
302
+ color: var(--color-fg);
303
+ text-decoration: underline;
304
+ }
305
+
306
+ .remarque-pagination [aria-current="page"] {
307
+ color: var(--color-fg);
308
+ text-decoration: underline;
309
+ text-decoration-thickness: 2px;
310
+ text-underline-offset: 0.3em;
311
+ }
312
+
313
+ /* ─── 4. Tags ────────────────────────────────────────────────────────────
314
+ * "Tags or categories (mono text, not pill badges)": plain mono links in a
315
+ * wrapping row. No border, no fill, no radius. An outlined chip is a
316
+ * documented deviation, not personalization (REMARQUE.md "Chrome
317
+ * Module"). <ul> with an accessible name; links carry rel="tag". */
318
+ .remarque-tags {
319
+ display: flex;
320
+ flex-wrap: wrap;
321
+ column-gap: var(--space-4);
322
+ row-gap: var(--space-1);
323
+ /* No margin: where the row sits is the page's call, and this file is
324
+ unlayered, so a margin here would beat a Tailwind utility on the same
325
+ element (AGENT_RULES Pitfall #7). Same for .remarque-pagination. */
326
+ padding: 0;
327
+ list-style: none;
328
+ font-family: var(--font-mono);
329
+ font-size: var(--text-meta);
330
+ line-height: var(--leading-meta);
331
+ letter-spacing: var(--tracking-meta);
332
+ }
333
+
334
+ .remarque-tags a {
335
+ color: var(--color-fg-muted);
336
+ text-decoration: none;
337
+ overflow-wrap: anywhere;
338
+ transition: color var(--motion-fast) var(--motion-easing);
339
+ }
340
+
341
+ .remarque-tags a:hover {
342
+ color: var(--color-fg);
343
+ text-decoration: underline;
344
+ }
345
+
346
+ /* ─── Coarse pointers: the 44px floor on every inline chrome link ─────── */
347
+ @media (pointer: coarse) {
348
+ .remarque-breadcrumb a,
349
+ .remarque-breadcrumb [aria-current="page"],
350
+ .remarque-tags a,
351
+ .remarque-pager-series a {
352
+ display: inline-flex;
353
+ align-items: center;
354
+ min-height: 44px;
355
+ }
356
+ }
357
+
358
+ /* ─── Forced colors ──────────────────────────────────────────────────────
359
+ * The skip link's fill is forced to Canvas, so give it a real border. */
360
+ @media (forced-colors: active) {
361
+ .remarque-skip-link {
362
+ border: 1px solid CanvasText;
363
+ }
364
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remarque-tokens",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -78,6 +78,8 @@
78
78
  "./broadsheet.css": "./broadsheet.css",
79
79
  "./forms": "./forms.css",
80
80
  "./forms.css": "./forms.css",
81
+ "./chrome": "./chrome.css",
82
+ "./chrome.css": "./chrome.css",
81
83
  "./deck": "./deck.js",
82
84
  "./deck.js": "./deck.js",
83
85
  "./agent-rules": "./AGENT_RULES.md",
@@ -115,6 +117,7 @@
115
117
  "essay.css",
116
118
  "broadsheet.css",
117
119
  "forms.css",
120
+ "chrome.css",
118
121
  "deck.js",
119
122
  "scripts/lib/css-tokens.mjs",
120
123
  "scripts/lib/palette-coherence.mjs",
package/print.css CHANGED
@@ -88,6 +88,9 @@
88
88
  a[href="#main-content"],
89
89
  a[href^="#main"],
90
90
  .skip-link,
91
+ .remarque-skip-link,
92
+ .remarque-pager,
93
+ .remarque-pagination,
91
94
  .copy-btn {
92
95
  display: none !important;
93
96
  }
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "https://williamzujkowski.github.io/remarque/registry-item.schema.json",
3
+ "name": "breadcrumb",
4
+ "type": "remarque:contract",
5
+ "title": "Breadcrumb (chrome.css)",
6
+ "description": "The Reference/Docs breadcrumb kicker: `nav[aria-label=\"Breadcrumb\"] > ol`, the current page last as `<span aria-current=\"page\">` (never a link), separators generated by CSS with empty alt text and never typed into the DOM.",
7
+ "version": "0.29.0",
8
+ "integrity": "sha256-V+FqUVopXBpnu9jfd8frbZJcs3NWmjv8KBuU/Uknaco=",
9
+ "dependencies": [
10
+ "remarque-tokens"
11
+ ],
12
+ "cssVars": [
13
+ "--border-style",
14
+ "--border-width",
15
+ "--color-accent",
16
+ "--color-accent-hover",
17
+ "--color-bg",
18
+ "--color-border",
19
+ "--color-fg",
20
+ "--color-fg-muted",
21
+ "--color-muted",
22
+ "--font-body",
23
+ "--font-mono",
24
+ "--leading-meta",
25
+ "--motion-easing",
26
+ "--motion-fast",
27
+ "--radius-sm",
28
+ "--space-1",
29
+ "--space-2",
30
+ "--space-4",
31
+ "--space-5",
32
+ "--space-6",
33
+ "--space-8",
34
+ "--text-body",
35
+ "--text-meta",
36
+ "--tracking-caps",
37
+ "--tracking-meta",
38
+ "--z-skip-link"
39
+ ],
40
+ "docs": "https://github.com/williamzujkowski/remarque/blob/main/REMARQUE.md#chrome-module",
41
+ "files": [
42
+ {
43
+ "path": "chrome.css",
44
+ "content": "/*\n * Remarque — Chrome Module (skip link, breadcrumb, pager, tags)\n * ───────────────────────────────────────────────────────────────\n * The site-chrome and wayfinding pieces the spec requires but never\n * shipped, so every consumer hand-rolled its own (issue #127, roadmap\n * 0.29.0). Four implementations of the skip link alone existed across the\n * flagship, tsundoku, the starter and this demo; one tsundoku pagination\n * kept its disabled endpoints focusable. GOV.UK Frontend and USWDS ship\n * all of these as components.\n *\n * Own subpath (remarque-tokens/chrome, remarque-tokens/chrome.css), not\n * part of the tokens.css aggregator. Import after the tokens:\n * @import 'remarque-tokens/chrome.css';\n * (string form — AGENT_RULES.md Pitfall #6.)\n *\n * Voice: everything here is metadata, so it speaks in the mono meta\n * register (--font-mono, --text-meta, --tracking-meta), never the accent\n * as decoration, never a border or fill around a tag. Every link gets the\n * 44px floor on coarse pointers (WCAG 2.5.8 is met at 24px; Remarque's\n * floor is 44). Markup contracts live in REMARQUE.md \"Chrome Module\" and\n * are asserted by scripts/test-registry.mjs.\n */\n\n/* ─── Screen-reader-only text ────────────────────────────────────────────\n * Visually hidden, still in the accessibility tree. clip-path, not the\n * deprecated clip. */\n.remarque-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/* ─── 1. Skip link ───────────────────────────────────────────────────────\n * First focusable element on the page. Hidden like .remarque-sr-only until\n * it takes focus, then a filled fg-on-bg block at the top inline-start\n * corner, above everything (--z-skip-link). It's never moved off-screen\n * with left: -9999px, which breaks in RTL and under zoom. The target is\n * <main id=\"main-content\" tabindex=\"-1\">: the tabindex lets the browser\n * move focus there, and the rule below keeps that programmatic focus from\n * outlining the whole page. */\n.remarque-skip-link {\n position: absolute;\n inset-block-start: var(--space-4);\n inset-inline-start: var(--space-4);\n z-index: var(--z-skip-link);\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n padding-inline: var(--space-4);\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-bg);\n background-color: var(--color-fg);\n border-radius: var(--radius-sm);\n text-decoration: none;\n}\n\n.remarque-skip-link:not(:focus) {\n width: 1px;\n height: 1px;\n min-height: 0;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\nmain[tabindex=\"-1\"]:focus {\n outline: none;\n}\n\n/* ─── 2. Breadcrumb ──────────────────────────────────────────────────────\n * The Reference/Docs \"breadcrumb kicker\" (REMARQUE.md Page Archetypes):\n * nav[aria-label=\"Breadcrumb\"] > ol. The separator is generated, with an\n * empty alt text so screen readers don't read \"slash\" between items (the\n * first declaration is the fallback for engines without alt text in\n * content). The current page is the last item, aria-current=\"page\", and\n * not a link. */\n.remarque-breadcrumb ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb li + li::before {\n content: \"/\";\n content: \"/\" / \"\";\n margin-inline: var(--space-2);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb a {\n color: var(--color-fg-muted);\n text-decoration: none;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-breadcrumb a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-breadcrumb [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* ─── 3. Pager: prev/next, series, numbered pagination ──────────────────\n * Prev/next between adjacent pages (Essay footer, Reference/Docs footer,\n * Plate figure navigation): links carry rel=\"prev\"/rel=\"next\" and sit at\n * the inline-start and inline-end of one row. An endpoint with nothing\n * beyond it is OMITTED (or, in numbered pagination, rendered as plain\n * text): never a link made to look disabled, which stays focusable and\n * announces as a link. Each link is a small label over the target's\n * title, so the title is the thing you read. */\n.remarque-pager {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: var(--space-5);\n margin-block-start: var(--space-8);\n padding-block-start: var(--space-6);\n border-block-start: var(--border-width) var(--border-style) var(--color-border);\n}\n\n.remarque-pager-prev,\n.remarque-pager-next {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-height: 44px;\n justify-content: center;\n text-decoration: none;\n overflow-wrap: anywhere;\n}\n\n.remarque-pager-prev {\n grid-column: 1;\n text-align: start;\n}\n\n.remarque-pager-next {\n grid-column: 2;\n text-align: end;\n}\n\n/* The label line: \"Previous\" / \"Next\", in the label voice. */\n.remarque-pager-label {\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n text-transform: uppercase;\n letter-spacing: var(--tracking-caps);\n color: var(--color-muted);\n}\n\n.remarque-pager-title {\n font-family: var(--font-body);\n font-size: var(--text-body);\n line-height: var(--leading-meta);\n color: var(--color-accent);\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-prev:hover .remarque-pager-title,\n.remarque-pager-next:hover .remarque-pager-title {\n color: var(--color-accent-hover);\n text-decoration: underline;\n}\n\n/* Arrows point along the inline direction, so they flip in RTL. They're\n generated and carry empty alt text: rel and the label already say it. */\n.remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2190\\00a0\";\n content: \"\\2190\\00a0\" / \"\";\n}\n\n.remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2192\";\n content: \"\\00a0\\2192\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2192\\00a0\";\n content: \"\\2192\\00a0\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2190\";\n content: \"\\00a0\\2190\" / \"\";\n}\n\n/* Optional series row: \"Part 2 of 4 · Series name\" plus a <details> list\n of every part, the current one aria-current=\"page\" and not a link. It\n spans both columns, above the prev/next pair. No progress bar: the text\n says it. */\n.remarque-pager-series {\n grid-column: 1 / -1;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pager-series summary {\n display: inline-flex;\n align-items: center;\n gap: var(--space-2);\n min-height: 44px;\n cursor: pointer;\n list-style: none;\n color: var(--color-fg-muted);\n}\n\n/* inline-flex drops the native ::marker, so draw the same marker the\n prose and TOC disclosures use; it points along the inline direction. */\n.remarque-pager-series summary::-webkit-details-marker {\n display: none;\n}\n\n.remarque-pager-series summary::before {\n content: \"\\25B8\";\n content: \"\\25B8\" / \"\";\n color: var(--color-muted);\n transition: transform var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-series details[open] summary::before {\n transform: rotate(90deg);\n}\n\n.remarque-pager-series summary:dir(rtl)::before {\n transform: scaleX(-1);\n}\n\n.remarque-pager-series ol {\n margin: var(--space-2) 0 0;\n padding-inline-start: var(--space-5);\n}\n\n.remarque-pager-series li {\n margin-block: var(--space-1);\n}\n\n.remarque-pager-series a {\n color: var(--color-fg-muted);\n}\n\n.remarque-pager-series [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* Numbered pagination for archives: nav[aria-label=\"Pagination\"] > ol.\n The current page is aria-current=\"page\" text; prev/next at the ends are\n links when there's somewhere to go and plain text when there isn't. */\n.remarque-pagination ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n font-variant-numeric: tabular-nums lining-nums;\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pagination a,\n.remarque-pagination li > span {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 44px;\n min-height: 44px;\n padding-inline: var(--space-2);\n}\n\n.remarque-pagination a {\n color: var(--color-fg-muted);\n text-decoration: none;\n}\n\n.remarque-pagination a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-pagination [aria-current=\"page\"] {\n color: var(--color-fg);\n text-decoration: underline;\n text-decoration-thickness: 2px;\n text-underline-offset: 0.3em;\n}\n\n/* ─── 4. Tags ────────────────────────────────────────────────────────────\n * \"Tags or categories (mono text, not pill badges)\": plain mono links in a\n * wrapping row. No border, no fill, no radius. An outlined chip is a\n * documented deviation, not personalization (REMARQUE.md \"Chrome\n * Module\"). <ul> with an accessible name; links carry rel=\"tag\". */\n.remarque-tags {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--space-4);\n row-gap: var(--space-1);\n /* No margin: where the row sits is the page's call, and this file is\n unlayered, so a margin here would beat a Tailwind utility on the same\n element (AGENT_RULES Pitfall #7). Same for .remarque-pagination. */\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n}\n\n.remarque-tags a {\n color: var(--color-fg-muted);\n text-decoration: none;\n overflow-wrap: anywhere;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-tags a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n/* ─── Coarse pointers: the 44px floor on every inline chrome link ─────── */\n@media (pointer: coarse) {\n .remarque-breadcrumb a,\n .remarque-breadcrumb [aria-current=\"page\"],\n .remarque-tags a,\n .remarque-pager-series a {\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n }\n}\n\n/* ─── Forced colors ──────────────────────────────────────────────────────\n * The skip link's fill is forced to Canvas, so give it a real border. */\n@media (forced-colors: active) {\n .remarque-skip-link {\n border: 1px solid CanvasText;\n }\n}\n",
45
+ "type": "remarque:css"
46
+ },
47
+ {
48
+ "path": "usage.html",
49
+ "content": "<nav class=\"remarque-breadcrumb\" aria-label=\"Breadcrumb\">\n <ol>\n <li><a href=\"/\">Home</a></li>\n <li><a href=\"/reference/\">Reference</a></li>\n <li><span aria-current=\"page\">Configuration</span></li>\n </ol>\n</nav>\n",
50
+ "type": "remarque:markup"
51
+ }
52
+ ]
53
+ }
@@ -4,7 +4,7 @@
4
4
  "type": "remarque:contract",
5
5
  "title": "Broadsheet Pattern",
6
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 the label voice: tracked capitals, because the default mono face has no small caps (`font-variant-caps: all-small-caps` only on a face with `smcp` + `c2sc`; remarque-audit checks).",
7
- "version": "0.28.0",
7
+ "version": "0.29.0",
8
8
  "integrity": "sha256-0gdOLXhIStXi2nrbSvWuyaOvCfjAdTdJAeZkprafekU=",
9
9
  "dependencies": [
10
10
  "remarque-tokens"
@@ -4,7 +4,7 @@
4
4
  "type": "remarque:contract",
5
5
  "title": "Essay Module",
6
6
  "description": "Sidenotes + sticky TOC rail for long-form Essay pages: margin notes relocated into real DOM order, right after the paragraph that cites them, numbered by CSS counter and labeled `aria-label=\"Note N\"` in the same order the counter advances — the mechanism that prevents the sidenote aria-label/DOM-order transcription bug found in the flagship migration (#89, williamzujkowski.github.io#380).",
7
- "version": "0.28.0",
7
+ "version": "0.29.0",
8
8
  "integrity": "sha256-HwTP/hiT/oid/YHBlzSECSvG5wSg9ICYVfns7luc6SA=",
9
9
  "dependencies": [
10
10
  "remarque-tokens"
@@ -4,7 +4,7 @@
4
4
  "type": "remarque:contract",
5
5
  "title": "Forms Primitives",
6
6
  "description": "Native field/input/checkbox/radio/button primitives with state-color wiring. Every `.remarque-input` pairs `for`/`id` with its `.remarque-field-label`, and every `.remarque-field-message` is wired via `aria-describedby` on the control — the paint-layer `data-state` attribute is never the only signal; `aria-invalid`/`aria-describedby` on the input carry the real accessibility contract.",
7
- "version": "0.28.0",
7
+ "version": "0.29.0",
8
8
  "integrity": "sha256-Vxmu0D2080+Y8n0TcWzF2DGOlDbBNsdJ268j5Odg5BQ=",
9
9
  "dependencies": [
10
10
  "remarque-tokens"
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "https://williamzujkowski.github.io/remarque/registry-item.schema.json",
3
+ "name": "pager",
4
+ "type": "remarque:contract",
5
+ "title": "Pager and Pagination (chrome.css)",
6
+ "description": "Prev/next between adjacent pages with `rel=\"prev\"`/`rel=\"next\"`, an optional series row (\"Part N of M\" plus a <details> list with the current part `aria-current=\"page\"`), and numbered `.remarque-pagination`. An endpoint with nowhere to go is omitted or plain text, never a disabled-looking link that stays focusable.",
7
+ "version": "0.29.0",
8
+ "integrity": "sha256-cTKt1fprH/XLtYSympI+pfjM+IO0rhinvazf1gGQKIo=",
9
+ "dependencies": [
10
+ "remarque-tokens"
11
+ ],
12
+ "cssVars": [
13
+ "--border-style",
14
+ "--border-width",
15
+ "--color-accent",
16
+ "--color-accent-hover",
17
+ "--color-bg",
18
+ "--color-border",
19
+ "--color-fg",
20
+ "--color-fg-muted",
21
+ "--color-muted",
22
+ "--font-body",
23
+ "--font-mono",
24
+ "--leading-meta",
25
+ "--motion-easing",
26
+ "--motion-fast",
27
+ "--radius-sm",
28
+ "--space-1",
29
+ "--space-2",
30
+ "--space-4",
31
+ "--space-5",
32
+ "--space-6",
33
+ "--space-8",
34
+ "--text-body",
35
+ "--text-meta",
36
+ "--tracking-caps",
37
+ "--tracking-meta",
38
+ "--z-skip-link"
39
+ ],
40
+ "docs": "https://github.com/williamzujkowski/remarque/blob/main/REMARQUE.md#chrome-module",
41
+ "files": [
42
+ {
43
+ "path": "chrome.css",
44
+ "content": "/*\n * Remarque — Chrome Module (skip link, breadcrumb, pager, tags)\n * ───────────────────────────────────────────────────────────────\n * The site-chrome and wayfinding pieces the spec requires but never\n * shipped, so every consumer hand-rolled its own (issue #127, roadmap\n * 0.29.0). Four implementations of the skip link alone existed across the\n * flagship, tsundoku, the starter and this demo; one tsundoku pagination\n * kept its disabled endpoints focusable. GOV.UK Frontend and USWDS ship\n * all of these as components.\n *\n * Own subpath (remarque-tokens/chrome, remarque-tokens/chrome.css), not\n * part of the tokens.css aggregator. Import after the tokens:\n * @import 'remarque-tokens/chrome.css';\n * (string form — AGENT_RULES.md Pitfall #6.)\n *\n * Voice: everything here is metadata, so it speaks in the mono meta\n * register (--font-mono, --text-meta, --tracking-meta), never the accent\n * as decoration, never a border or fill around a tag. Every link gets the\n * 44px floor on coarse pointers (WCAG 2.5.8 is met at 24px; Remarque's\n * floor is 44). Markup contracts live in REMARQUE.md \"Chrome Module\" and\n * are asserted by scripts/test-registry.mjs.\n */\n\n/* ─── Screen-reader-only text ────────────────────────────────────────────\n * Visually hidden, still in the accessibility tree. clip-path, not the\n * deprecated clip. */\n.remarque-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/* ─── 1. Skip link ───────────────────────────────────────────────────────\n * First focusable element on the page. Hidden like .remarque-sr-only until\n * it takes focus, then a filled fg-on-bg block at the top inline-start\n * corner, above everything (--z-skip-link). It's never moved off-screen\n * with left: -9999px, which breaks in RTL and under zoom. The target is\n * <main id=\"main-content\" tabindex=\"-1\">: the tabindex lets the browser\n * move focus there, and the rule below keeps that programmatic focus from\n * outlining the whole page. */\n.remarque-skip-link {\n position: absolute;\n inset-block-start: var(--space-4);\n inset-inline-start: var(--space-4);\n z-index: var(--z-skip-link);\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n padding-inline: var(--space-4);\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-bg);\n background-color: var(--color-fg);\n border-radius: var(--radius-sm);\n text-decoration: none;\n}\n\n.remarque-skip-link:not(:focus) {\n width: 1px;\n height: 1px;\n min-height: 0;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\nmain[tabindex=\"-1\"]:focus {\n outline: none;\n}\n\n/* ─── 2. Breadcrumb ──────────────────────────────────────────────────────\n * The Reference/Docs \"breadcrumb kicker\" (REMARQUE.md Page Archetypes):\n * nav[aria-label=\"Breadcrumb\"] > ol. The separator is generated, with an\n * empty alt text so screen readers don't read \"slash\" between items (the\n * first declaration is the fallback for engines without alt text in\n * content). The current page is the last item, aria-current=\"page\", and\n * not a link. */\n.remarque-breadcrumb ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb li + li::before {\n content: \"/\";\n content: \"/\" / \"\";\n margin-inline: var(--space-2);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb a {\n color: var(--color-fg-muted);\n text-decoration: none;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-breadcrumb a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-breadcrumb [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* ─── 3. Pager: prev/next, series, numbered pagination ──────────────────\n * Prev/next between adjacent pages (Essay footer, Reference/Docs footer,\n * Plate figure navigation): links carry rel=\"prev\"/rel=\"next\" and sit at\n * the inline-start and inline-end of one row. An endpoint with nothing\n * beyond it is OMITTED (or, in numbered pagination, rendered as plain\n * text): never a link made to look disabled, which stays focusable and\n * announces as a link. Each link is a small label over the target's\n * title, so the title is the thing you read. */\n.remarque-pager {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: var(--space-5);\n margin-block-start: var(--space-8);\n padding-block-start: var(--space-6);\n border-block-start: var(--border-width) var(--border-style) var(--color-border);\n}\n\n.remarque-pager-prev,\n.remarque-pager-next {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-height: 44px;\n justify-content: center;\n text-decoration: none;\n overflow-wrap: anywhere;\n}\n\n.remarque-pager-prev {\n grid-column: 1;\n text-align: start;\n}\n\n.remarque-pager-next {\n grid-column: 2;\n text-align: end;\n}\n\n/* The label line: \"Previous\" / \"Next\", in the label voice. */\n.remarque-pager-label {\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n text-transform: uppercase;\n letter-spacing: var(--tracking-caps);\n color: var(--color-muted);\n}\n\n.remarque-pager-title {\n font-family: var(--font-body);\n font-size: var(--text-body);\n line-height: var(--leading-meta);\n color: var(--color-accent);\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-prev:hover .remarque-pager-title,\n.remarque-pager-next:hover .remarque-pager-title {\n color: var(--color-accent-hover);\n text-decoration: underline;\n}\n\n/* Arrows point along the inline direction, so they flip in RTL. They're\n generated and carry empty alt text: rel and the label already say it. */\n.remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2190\\00a0\";\n content: \"\\2190\\00a0\" / \"\";\n}\n\n.remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2192\";\n content: \"\\00a0\\2192\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2192\\00a0\";\n content: \"\\2192\\00a0\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2190\";\n content: \"\\00a0\\2190\" / \"\";\n}\n\n/* Optional series row: \"Part 2 of 4 · Series name\" plus a <details> list\n of every part, the current one aria-current=\"page\" and not a link. It\n spans both columns, above the prev/next pair. No progress bar: the text\n says it. */\n.remarque-pager-series {\n grid-column: 1 / -1;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pager-series summary {\n display: inline-flex;\n align-items: center;\n gap: var(--space-2);\n min-height: 44px;\n cursor: pointer;\n list-style: none;\n color: var(--color-fg-muted);\n}\n\n/* inline-flex drops the native ::marker, so draw the same marker the\n prose and TOC disclosures use; it points along the inline direction. */\n.remarque-pager-series summary::-webkit-details-marker {\n display: none;\n}\n\n.remarque-pager-series summary::before {\n content: \"\\25B8\";\n content: \"\\25B8\" / \"\";\n color: var(--color-muted);\n transition: transform var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-series details[open] summary::before {\n transform: rotate(90deg);\n}\n\n.remarque-pager-series summary:dir(rtl)::before {\n transform: scaleX(-1);\n}\n\n.remarque-pager-series ol {\n margin: var(--space-2) 0 0;\n padding-inline-start: var(--space-5);\n}\n\n.remarque-pager-series li {\n margin-block: var(--space-1);\n}\n\n.remarque-pager-series a {\n color: var(--color-fg-muted);\n}\n\n.remarque-pager-series [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* Numbered pagination for archives: nav[aria-label=\"Pagination\"] > ol.\n The current page is aria-current=\"page\" text; prev/next at the ends are\n links when there's somewhere to go and plain text when there isn't. */\n.remarque-pagination ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n font-variant-numeric: tabular-nums lining-nums;\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pagination a,\n.remarque-pagination li > span {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 44px;\n min-height: 44px;\n padding-inline: var(--space-2);\n}\n\n.remarque-pagination a {\n color: var(--color-fg-muted);\n text-decoration: none;\n}\n\n.remarque-pagination a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-pagination [aria-current=\"page\"] {\n color: var(--color-fg);\n text-decoration: underline;\n text-decoration-thickness: 2px;\n text-underline-offset: 0.3em;\n}\n\n/* ─── 4. Tags ────────────────────────────────────────────────────────────\n * \"Tags or categories (mono text, not pill badges)\": plain mono links in a\n * wrapping row. No border, no fill, no radius. An outlined chip is a\n * documented deviation, not personalization (REMARQUE.md \"Chrome\n * Module\"). <ul> with an accessible name; links carry rel=\"tag\". */\n.remarque-tags {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--space-4);\n row-gap: var(--space-1);\n /* No margin: where the row sits is the page's call, and this file is\n unlayered, so a margin here would beat a Tailwind utility on the same\n element (AGENT_RULES Pitfall #7). Same for .remarque-pagination. */\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n}\n\n.remarque-tags a {\n color: var(--color-fg-muted);\n text-decoration: none;\n overflow-wrap: anywhere;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-tags a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n/* ─── Coarse pointers: the 44px floor on every inline chrome link ─────── */\n@media (pointer: coarse) {\n .remarque-breadcrumb a,\n .remarque-breadcrumb [aria-current=\"page\"],\n .remarque-tags a,\n .remarque-pager-series a {\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n }\n}\n\n/* ─── Forced colors ──────────────────────────────────────────────────────\n * The skip link's fill is forced to Canvas, so give it a real border. */\n@media (forced-colors: active) {\n .remarque-skip-link {\n border: 1px solid CanvasText;\n }\n}\n",
45
+ "type": "remarque:css"
46
+ },
47
+ {
48
+ "path": "usage.html",
49
+ "content": "<nav class=\"remarque-pager\" aria-label=\"More writing\">\n <div class=\"remarque-pager-series\">\n <details>\n <summary>Part 2 of 3 · Quiet Interfaces</summary>\n <ol>\n <li><a href=\"/writing/quiet-1/\">Against Decoration</a></li>\n <li><span aria-current=\"page\">Typography as Interface</span></li>\n <li><a href=\"/writing/quiet-3/\">The Reading Column</a></li>\n </ol>\n </details>\n </div>\n <a class=\"remarque-pager-prev\" href=\"/writing/quiet-1/\" rel=\"prev\">\n <span class=\"remarque-pager-label\">Previous</span>\n <span class=\"remarque-pager-title\">Against Decoration</span>\n </a>\n <a class=\"remarque-pager-next\" href=\"/writing/quiet-3/\" rel=\"next\">\n <span class=\"remarque-pager-label\">Next</span>\n <span class=\"remarque-pager-title\">The Reading Column</span>\n </a>\n</nav>\n\n<nav class=\"remarque-pagination\" aria-label=\"Pagination\">\n <ol>\n <li><span>Previous</span></li>\n <li><span aria-current=\"page\">1</span></li>\n <li><a href=\"/writing/page/2/\">2</a></li>\n <li><a href=\"/writing/page/3/\">3</a></li>\n <li><a href=\"/writing/page/2/\" rel=\"next\">Next</a></li>\n </ol>\n</nav>\n",
50
+ "type": "remarque:markup"
51
+ }
52
+ ]
53
+ }
@@ -4,7 +4,7 @@
4
4
  "type": "remarque:contract",
5
5
  "title": "Palette Deck (markup contract only — no executable content)",
6
6
  "description": "Markup/wiring contract for the Palette Deck: set `data-palette` on the same root element as `data-theme` and the two compose independently. This item ships ONLY that HTML fragment. `deck.js` — the runtime module itself, a dependency-free ~60-line ESM file — is deliberately NOT embedded here: the registry’s blocking no-executable-content condition is held to an unambiguous zero-`<script>`-tag bar across every item, so neither `deck.js` nor the FOUC-restore `<script>` sample documented alongside it in REMARQUE.md is included. Consume `deck.js` the normal way, via the `remarque-tokens/deck` package import (`dependencies` below); read the FOUC-restore snippet from the `docs` URL.",
7
- "version": "0.28.0",
7
+ "version": "0.29.0",
8
8
  "integrity": "sha256-WGgBZ2j0VnLPRAZlzv/4G1Wu9rKqTdXf1536dWWu8nU=",
9
9
  "dependencies": [
10
10
  "remarque-tokens"
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "https://williamzujkowski.github.io/remarque/registry-item.schema.json",
3
+ "name": "skip-link",
4
+ "type": "remarque:contract",
5
+ "title": "Skip Link (chrome.css)",
6
+ "description": "The first focusable element in <body>: `.remarque-skip-link` targets `<main id=\"main-content\" tabindex=\"-1\">`, visually hidden by clip-path (never an off-screen offset) until focused, then a filled fg-on-bg block on `--z-skip-link`. Ships with `.remarque-sr-only`. Shares chrome.css with the breadcrumb, pager and tags items.",
7
+ "version": "0.29.0",
8
+ "integrity": "sha256-qMjHT0szK/Abv4lAXodoVKcXEa0tpJV44vjfp6Cyzqs=",
9
+ "dependencies": [
10
+ "remarque-tokens"
11
+ ],
12
+ "cssVars": [
13
+ "--border-style",
14
+ "--border-width",
15
+ "--color-accent",
16
+ "--color-accent-hover",
17
+ "--color-bg",
18
+ "--color-border",
19
+ "--color-fg",
20
+ "--color-fg-muted",
21
+ "--color-muted",
22
+ "--font-body",
23
+ "--font-mono",
24
+ "--leading-meta",
25
+ "--motion-easing",
26
+ "--motion-fast",
27
+ "--radius-sm",
28
+ "--space-1",
29
+ "--space-2",
30
+ "--space-4",
31
+ "--space-5",
32
+ "--space-6",
33
+ "--space-8",
34
+ "--text-body",
35
+ "--text-meta",
36
+ "--tracking-caps",
37
+ "--tracking-meta",
38
+ "--z-skip-link"
39
+ ],
40
+ "docs": "https://github.com/williamzujkowski/remarque/blob/main/REMARQUE.md#chrome-module",
41
+ "files": [
42
+ {
43
+ "path": "chrome.css",
44
+ "content": "/*\n * Remarque — Chrome Module (skip link, breadcrumb, pager, tags)\n * ───────────────────────────────────────────────────────────────\n * The site-chrome and wayfinding pieces the spec requires but never\n * shipped, so every consumer hand-rolled its own (issue #127, roadmap\n * 0.29.0). Four implementations of the skip link alone existed across the\n * flagship, tsundoku, the starter and this demo; one tsundoku pagination\n * kept its disabled endpoints focusable. GOV.UK Frontend and USWDS ship\n * all of these as components.\n *\n * Own subpath (remarque-tokens/chrome, remarque-tokens/chrome.css), not\n * part of the tokens.css aggregator. Import after the tokens:\n * @import 'remarque-tokens/chrome.css';\n * (string form — AGENT_RULES.md Pitfall #6.)\n *\n * Voice: everything here is metadata, so it speaks in the mono meta\n * register (--font-mono, --text-meta, --tracking-meta), never the accent\n * as decoration, never a border or fill around a tag. Every link gets the\n * 44px floor on coarse pointers (WCAG 2.5.8 is met at 24px; Remarque's\n * floor is 44). Markup contracts live in REMARQUE.md \"Chrome Module\" and\n * are asserted by scripts/test-registry.mjs.\n */\n\n/* ─── Screen-reader-only text ────────────────────────────────────────────\n * Visually hidden, still in the accessibility tree. clip-path, not the\n * deprecated clip. */\n.remarque-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/* ─── 1. Skip link ───────────────────────────────────────────────────────\n * First focusable element on the page. Hidden like .remarque-sr-only until\n * it takes focus, then a filled fg-on-bg block at the top inline-start\n * corner, above everything (--z-skip-link). It's never moved off-screen\n * with left: -9999px, which breaks in RTL and under zoom. The target is\n * <main id=\"main-content\" tabindex=\"-1\">: the tabindex lets the browser\n * move focus there, and the rule below keeps that programmatic focus from\n * outlining the whole page. */\n.remarque-skip-link {\n position: absolute;\n inset-block-start: var(--space-4);\n inset-inline-start: var(--space-4);\n z-index: var(--z-skip-link);\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n padding-inline: var(--space-4);\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-bg);\n background-color: var(--color-fg);\n border-radius: var(--radius-sm);\n text-decoration: none;\n}\n\n.remarque-skip-link:not(:focus) {\n width: 1px;\n height: 1px;\n min-height: 0;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\nmain[tabindex=\"-1\"]:focus {\n outline: none;\n}\n\n/* ─── 2. Breadcrumb ──────────────────────────────────────────────────────\n * The Reference/Docs \"breadcrumb kicker\" (REMARQUE.md Page Archetypes):\n * nav[aria-label=\"Breadcrumb\"] > ol. The separator is generated, with an\n * empty alt text so screen readers don't read \"slash\" between items (the\n * first declaration is the fallback for engines without alt text in\n * content). The current page is the last item, aria-current=\"page\", and\n * not a link. */\n.remarque-breadcrumb ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb li + li::before {\n content: \"/\";\n content: \"/\" / \"\";\n margin-inline: var(--space-2);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb a {\n color: var(--color-fg-muted);\n text-decoration: none;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-breadcrumb a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-breadcrumb [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* ─── 3. Pager: prev/next, series, numbered pagination ──────────────────\n * Prev/next between adjacent pages (Essay footer, Reference/Docs footer,\n * Plate figure navigation): links carry rel=\"prev\"/rel=\"next\" and sit at\n * the inline-start and inline-end of one row. An endpoint with nothing\n * beyond it is OMITTED (or, in numbered pagination, rendered as plain\n * text): never a link made to look disabled, which stays focusable and\n * announces as a link. Each link is a small label over the target's\n * title, so the title is the thing you read. */\n.remarque-pager {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: var(--space-5);\n margin-block-start: var(--space-8);\n padding-block-start: var(--space-6);\n border-block-start: var(--border-width) var(--border-style) var(--color-border);\n}\n\n.remarque-pager-prev,\n.remarque-pager-next {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-height: 44px;\n justify-content: center;\n text-decoration: none;\n overflow-wrap: anywhere;\n}\n\n.remarque-pager-prev {\n grid-column: 1;\n text-align: start;\n}\n\n.remarque-pager-next {\n grid-column: 2;\n text-align: end;\n}\n\n/* The label line: \"Previous\" / \"Next\", in the label voice. */\n.remarque-pager-label {\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n text-transform: uppercase;\n letter-spacing: var(--tracking-caps);\n color: var(--color-muted);\n}\n\n.remarque-pager-title {\n font-family: var(--font-body);\n font-size: var(--text-body);\n line-height: var(--leading-meta);\n color: var(--color-accent);\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-prev:hover .remarque-pager-title,\n.remarque-pager-next:hover .remarque-pager-title {\n color: var(--color-accent-hover);\n text-decoration: underline;\n}\n\n/* Arrows point along the inline direction, so they flip in RTL. They're\n generated and carry empty alt text: rel and the label already say it. */\n.remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2190\\00a0\";\n content: \"\\2190\\00a0\" / \"\";\n}\n\n.remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2192\";\n content: \"\\00a0\\2192\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2192\\00a0\";\n content: \"\\2192\\00a0\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2190\";\n content: \"\\00a0\\2190\" / \"\";\n}\n\n/* Optional series row: \"Part 2 of 4 · Series name\" plus a <details> list\n of every part, the current one aria-current=\"page\" and not a link. It\n spans both columns, above the prev/next pair. No progress bar: the text\n says it. */\n.remarque-pager-series {\n grid-column: 1 / -1;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pager-series summary {\n display: inline-flex;\n align-items: center;\n gap: var(--space-2);\n min-height: 44px;\n cursor: pointer;\n list-style: none;\n color: var(--color-fg-muted);\n}\n\n/* inline-flex drops the native ::marker, so draw the same marker the\n prose and TOC disclosures use; it points along the inline direction. */\n.remarque-pager-series summary::-webkit-details-marker {\n display: none;\n}\n\n.remarque-pager-series summary::before {\n content: \"\\25B8\";\n content: \"\\25B8\" / \"\";\n color: var(--color-muted);\n transition: transform var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-series details[open] summary::before {\n transform: rotate(90deg);\n}\n\n.remarque-pager-series summary:dir(rtl)::before {\n transform: scaleX(-1);\n}\n\n.remarque-pager-series ol {\n margin: var(--space-2) 0 0;\n padding-inline-start: var(--space-5);\n}\n\n.remarque-pager-series li {\n margin-block: var(--space-1);\n}\n\n.remarque-pager-series a {\n color: var(--color-fg-muted);\n}\n\n.remarque-pager-series [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* Numbered pagination for archives: nav[aria-label=\"Pagination\"] > ol.\n The current page is aria-current=\"page\" text; prev/next at the ends are\n links when there's somewhere to go and plain text when there isn't. */\n.remarque-pagination ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n font-variant-numeric: tabular-nums lining-nums;\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pagination a,\n.remarque-pagination li > span {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 44px;\n min-height: 44px;\n padding-inline: var(--space-2);\n}\n\n.remarque-pagination a {\n color: var(--color-fg-muted);\n text-decoration: none;\n}\n\n.remarque-pagination a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-pagination [aria-current=\"page\"] {\n color: var(--color-fg);\n text-decoration: underline;\n text-decoration-thickness: 2px;\n text-underline-offset: 0.3em;\n}\n\n/* ─── 4. Tags ────────────────────────────────────────────────────────────\n * \"Tags or categories (mono text, not pill badges)\": plain mono links in a\n * wrapping row. No border, no fill, no radius. An outlined chip is a\n * documented deviation, not personalization (REMARQUE.md \"Chrome\n * Module\"). <ul> with an accessible name; links carry rel=\"tag\". */\n.remarque-tags {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--space-4);\n row-gap: var(--space-1);\n /* No margin: where the row sits is the page's call, and this file is\n unlayered, so a margin here would beat a Tailwind utility on the same\n element (AGENT_RULES Pitfall #7). Same for .remarque-pagination. */\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n}\n\n.remarque-tags a {\n color: var(--color-fg-muted);\n text-decoration: none;\n overflow-wrap: anywhere;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-tags a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n/* ─── Coarse pointers: the 44px floor on every inline chrome link ─────── */\n@media (pointer: coarse) {\n .remarque-breadcrumb a,\n .remarque-breadcrumb [aria-current=\"page\"],\n .remarque-tags a,\n .remarque-pager-series a {\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n }\n}\n\n/* ─── Forced colors ──────────────────────────────────────────────────────\n * The skip link's fill is forced to Canvas, so give it a real border. */\n@media (forced-colors: active) {\n .remarque-skip-link {\n border: 1px solid CanvasText;\n }\n}\n",
45
+ "type": "remarque:css"
46
+ },
47
+ {
48
+ "path": "usage.html",
49
+ "content": "<body>\n <a class=\"remarque-skip-link\" href=\"#main-content\">Skip to content</a>\n <header>…site navigation…</header>\n <main id=\"main-content\" tabindex=\"-1\">…</main>\n</body>\n",
50
+ "type": "remarque:markup"
51
+ }
52
+ ]
53
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "https://williamzujkowski.github.io/remarque/registry-item.schema.json",
3
+ "name": "tags",
4
+ "type": "remarque:contract",
5
+ "title": "Tags (chrome.css)",
6
+ "description": "Tags as plain mono links in a `<ul>` with an accessible name, each `rel=\"tag\"`. No border, fill or radius: an outlined or filled chip is a documented deviation, not personalization.",
7
+ "version": "0.29.0",
8
+ "integrity": "sha256-g38UvUKiMIU1P9kIhKxjNSg+KKnhsmB8pNdbtVHZpe4=",
9
+ "dependencies": [
10
+ "remarque-tokens"
11
+ ],
12
+ "cssVars": [
13
+ "--border-style",
14
+ "--border-width",
15
+ "--color-accent",
16
+ "--color-accent-hover",
17
+ "--color-bg",
18
+ "--color-border",
19
+ "--color-fg",
20
+ "--color-fg-muted",
21
+ "--color-muted",
22
+ "--font-body",
23
+ "--font-mono",
24
+ "--leading-meta",
25
+ "--motion-easing",
26
+ "--motion-fast",
27
+ "--radius-sm",
28
+ "--space-1",
29
+ "--space-2",
30
+ "--space-4",
31
+ "--space-5",
32
+ "--space-6",
33
+ "--space-8",
34
+ "--text-body",
35
+ "--text-meta",
36
+ "--tracking-caps",
37
+ "--tracking-meta",
38
+ "--z-skip-link"
39
+ ],
40
+ "docs": "https://github.com/williamzujkowski/remarque/blob/main/REMARQUE.md#chrome-module",
41
+ "files": [
42
+ {
43
+ "path": "chrome.css",
44
+ "content": "/*\n * Remarque — Chrome Module (skip link, breadcrumb, pager, tags)\n * ───────────────────────────────────────────────────────────────\n * The site-chrome and wayfinding pieces the spec requires but never\n * shipped, so every consumer hand-rolled its own (issue #127, roadmap\n * 0.29.0). Four implementations of the skip link alone existed across the\n * flagship, tsundoku, the starter and this demo; one tsundoku pagination\n * kept its disabled endpoints focusable. GOV.UK Frontend and USWDS ship\n * all of these as components.\n *\n * Own subpath (remarque-tokens/chrome, remarque-tokens/chrome.css), not\n * part of the tokens.css aggregator. Import after the tokens:\n * @import 'remarque-tokens/chrome.css';\n * (string form — AGENT_RULES.md Pitfall #6.)\n *\n * Voice: everything here is metadata, so it speaks in the mono meta\n * register (--font-mono, --text-meta, --tracking-meta), never the accent\n * as decoration, never a border or fill around a tag. Every link gets the\n * 44px floor on coarse pointers (WCAG 2.5.8 is met at 24px; Remarque's\n * floor is 44). Markup contracts live in REMARQUE.md \"Chrome Module\" and\n * are asserted by scripts/test-registry.mjs.\n */\n\n/* ─── Screen-reader-only text ────────────────────────────────────────────\n * Visually hidden, still in the accessibility tree. clip-path, not the\n * deprecated clip. */\n.remarque-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/* ─── 1. Skip link ───────────────────────────────────────────────────────\n * First focusable element on the page. Hidden like .remarque-sr-only until\n * it takes focus, then a filled fg-on-bg block at the top inline-start\n * corner, above everything (--z-skip-link). It's never moved off-screen\n * with left: -9999px, which breaks in RTL and under zoom. The target is\n * <main id=\"main-content\" tabindex=\"-1\">: the tabindex lets the browser\n * move focus there, and the rule below keeps that programmatic focus from\n * outlining the whole page. */\n.remarque-skip-link {\n position: absolute;\n inset-block-start: var(--space-4);\n inset-inline-start: var(--space-4);\n z-index: var(--z-skip-link);\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n padding-inline: var(--space-4);\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-bg);\n background-color: var(--color-fg);\n border-radius: var(--radius-sm);\n text-decoration: none;\n}\n\n.remarque-skip-link:not(:focus) {\n width: 1px;\n height: 1px;\n min-height: 0;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\nmain[tabindex=\"-1\"]:focus {\n outline: none;\n}\n\n/* ─── 2. Breadcrumb ──────────────────────────────────────────────────────\n * The Reference/Docs \"breadcrumb kicker\" (REMARQUE.md Page Archetypes):\n * nav[aria-label=\"Breadcrumb\"] > ol. The separator is generated, with an\n * empty alt text so screen readers don't read \"slash\" between items (the\n * first declaration is the fallback for engines without alt text in\n * content). The current page is the last item, aria-current=\"page\", and\n * not a link. */\n.remarque-breadcrumb ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb li + li::before {\n content: \"/\";\n content: \"/\" / \"\";\n margin-inline: var(--space-2);\n color: var(--color-muted);\n}\n\n.remarque-breadcrumb a {\n color: var(--color-fg-muted);\n text-decoration: none;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-breadcrumb a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-breadcrumb [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* ─── 3. Pager: prev/next, series, numbered pagination ──────────────────\n * Prev/next between adjacent pages (Essay footer, Reference/Docs footer,\n * Plate figure navigation): links carry rel=\"prev\"/rel=\"next\" and sit at\n * the inline-start and inline-end of one row. An endpoint with nothing\n * beyond it is OMITTED (or, in numbered pagination, rendered as plain\n * text): never a link made to look disabled, which stays focusable and\n * announces as a link. Each link is a small label over the target's\n * title, so the title is the thing you read. */\n.remarque-pager {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: var(--space-5);\n margin-block-start: var(--space-8);\n padding-block-start: var(--space-6);\n border-block-start: var(--border-width) var(--border-style) var(--color-border);\n}\n\n.remarque-pager-prev,\n.remarque-pager-next {\n display: flex;\n flex-direction: column;\n gap: var(--space-1);\n min-height: 44px;\n justify-content: center;\n text-decoration: none;\n overflow-wrap: anywhere;\n}\n\n.remarque-pager-prev {\n grid-column: 1;\n text-align: start;\n}\n\n.remarque-pager-next {\n grid-column: 2;\n text-align: end;\n}\n\n/* The label line: \"Previous\" / \"Next\", in the label voice. */\n.remarque-pager-label {\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n text-transform: uppercase;\n letter-spacing: var(--tracking-caps);\n color: var(--color-muted);\n}\n\n.remarque-pager-title {\n font-family: var(--font-body);\n font-size: var(--text-body);\n line-height: var(--leading-meta);\n color: var(--color-accent);\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-prev:hover .remarque-pager-title,\n.remarque-pager-next:hover .remarque-pager-title {\n color: var(--color-accent-hover);\n text-decoration: underline;\n}\n\n/* Arrows point along the inline direction, so they flip in RTL. They're\n generated and carry empty alt text: rel and the label already say it. */\n.remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2190\\00a0\";\n content: \"\\2190\\00a0\" / \"\";\n}\n\n.remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2192\";\n content: \"\\00a0\\2192\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-prev .remarque-pager-label::before {\n content: \"\\2192\\00a0\";\n content: \"\\2192\\00a0\" / \"\";\n}\n\n.remarque-pager:dir(rtl) .remarque-pager-next .remarque-pager-label::after {\n content: \"\\00a0\\2190\";\n content: \"\\00a0\\2190\" / \"\";\n}\n\n/* Optional series row: \"Part 2 of 4 · Series name\" plus a <details> list\n of every part, the current one aria-current=\"page\" and not a link. It\n spans both columns, above the prev/next pair. No progress bar: the text\n says it. */\n.remarque-pager-series {\n grid-column: 1 / -1;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pager-series summary {\n display: inline-flex;\n align-items: center;\n gap: var(--space-2);\n min-height: 44px;\n cursor: pointer;\n list-style: none;\n color: var(--color-fg-muted);\n}\n\n/* inline-flex drops the native ::marker, so draw the same marker the\n prose and TOC disclosures use; it points along the inline direction. */\n.remarque-pager-series summary::-webkit-details-marker {\n display: none;\n}\n\n.remarque-pager-series summary::before {\n content: \"\\25B8\";\n content: \"\\25B8\" / \"\";\n color: var(--color-muted);\n transition: transform var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-pager-series details[open] summary::before {\n transform: rotate(90deg);\n}\n\n.remarque-pager-series summary:dir(rtl)::before {\n transform: scaleX(-1);\n}\n\n.remarque-pager-series ol {\n margin: var(--space-2) 0 0;\n padding-inline-start: var(--space-5);\n}\n\n.remarque-pager-series li {\n margin-block: var(--space-1);\n}\n\n.remarque-pager-series a {\n color: var(--color-fg-muted);\n}\n\n.remarque-pager-series [aria-current=\"page\"] {\n color: var(--color-fg);\n}\n\n/* Numbered pagination for archives: nav[aria-label=\"Pagination\"] > ol.\n The current page is aria-current=\"page\" text; prev/next at the ends are\n links when there's somewhere to go and plain text when there isn't. */\n.remarque-pagination ol {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-1);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n font-variant-numeric: tabular-nums lining-nums;\n letter-spacing: var(--tracking-meta);\n color: var(--color-muted);\n}\n\n.remarque-pagination a,\n.remarque-pagination li > span {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 44px;\n min-height: 44px;\n padding-inline: var(--space-2);\n}\n\n.remarque-pagination a {\n color: var(--color-fg-muted);\n text-decoration: none;\n}\n\n.remarque-pagination a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n.remarque-pagination [aria-current=\"page\"] {\n color: var(--color-fg);\n text-decoration: underline;\n text-decoration-thickness: 2px;\n text-underline-offset: 0.3em;\n}\n\n/* ─── 4. Tags ────────────────────────────────────────────────────────────\n * \"Tags or categories (mono text, not pill badges)\": plain mono links in a\n * wrapping row. No border, no fill, no radius. An outlined chip is a\n * documented deviation, not personalization (REMARQUE.md \"Chrome\n * Module\"). <ul> with an accessible name; links carry rel=\"tag\". */\n.remarque-tags {\n display: flex;\n flex-wrap: wrap;\n column-gap: var(--space-4);\n row-gap: var(--space-1);\n /* No margin: where the row sits is the page's call, and this file is\n unlayered, so a margin here would beat a Tailwind utility on the same\n element (AGENT_RULES Pitfall #7). Same for .remarque-pagination. */\n padding: 0;\n list-style: none;\n font-family: var(--font-mono);\n font-size: var(--text-meta);\n line-height: var(--leading-meta);\n letter-spacing: var(--tracking-meta);\n}\n\n.remarque-tags a {\n color: var(--color-fg-muted);\n text-decoration: none;\n overflow-wrap: anywhere;\n transition: color var(--motion-fast) var(--motion-easing);\n}\n\n.remarque-tags a:hover {\n color: var(--color-fg);\n text-decoration: underline;\n}\n\n/* ─── Coarse pointers: the 44px floor on every inline chrome link ─────── */\n@media (pointer: coarse) {\n .remarque-breadcrumb a,\n .remarque-breadcrumb [aria-current=\"page\"],\n .remarque-tags a,\n .remarque-pager-series a {\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n }\n}\n\n/* ─── Forced colors ──────────────────────────────────────────────────────\n * The skip link's fill is forced to Canvas, so give it a real border. */\n@media (forced-colors: active) {\n .remarque-skip-link {\n border: 1px solid CanvasText;\n }\n}\n",
45
+ "type": "remarque:css"
46
+ },
47
+ {
48
+ "path": "usage.html",
49
+ "content": "<ul class=\"remarque-tags\" aria-label=\"Tags\">\n <li><a href=\"/tags/typography/\" rel=\"tag\">typography</a></li>\n <li><a href=\"/tags/design-systems/\" rel=\"tag\">design-systems</a></li>\n</ul>\n",
50
+ "type": "remarque:markup"
51
+ }
52
+ ]
53
+ }
package/registry.json CHANGED
@@ -2,14 +2,14 @@
2
2
  "$schema": "https://williamzujkowski.github.io/remarque/registry.schema.json",
3
3
  "name": "remarque-tokens",
4
4
  "homepage": "https://williamzujkowski.github.io/remarque/",
5
- "version": "0.28.0",
5
+ "version": "0.29.0",
6
6
  "items": [
7
7
  {
8
8
  "name": "essay",
9
9
  "type": "remarque:contract",
10
10
  "title": "Essay Module",
11
11
  "description": "Sidenotes + sticky TOC rail for long-form Essay pages: margin notes relocated into real DOM order, right after the paragraph that cites them, numbered by CSS counter and labeled `aria-label=\"Note N\"` in the same order the counter advances — the mechanism that prevents the sidenote aria-label/DOM-order transcription bug found in the flagship migration (#89, williamzujkowski.github.io#380).",
12
- "version": "0.28.0",
12
+ "version": "0.29.0",
13
13
  "integrity": "sha256-HwTP/hiT/oid/YHBlzSECSvG5wSg9ICYVfns7luc6SA=",
14
14
  "file": "registry/essay.json"
15
15
  },
@@ -18,7 +18,7 @@
18
18
  "type": "remarque:contract",
19
19
  "title": "Broadsheet Pattern",
20
20
  "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 the label voice: tracked capitals, because the default mono face has no small caps (`font-variant-caps: all-small-caps` only on a face with `smcp` + `c2sc`; remarque-audit checks).",
21
- "version": "0.28.0",
21
+ "version": "0.29.0",
22
22
  "integrity": "sha256-0gdOLXhIStXi2nrbSvWuyaOvCfjAdTdJAeZkprafekU=",
23
23
  "file": "registry/broadsheet.json"
24
24
  },
@@ -27,16 +27,52 @@
27
27
  "type": "remarque:contract",
28
28
  "title": "Forms Primitives",
29
29
  "description": "Native field/input/checkbox/radio/button primitives with state-color wiring. Every `.remarque-input` pairs `for`/`id` with its `.remarque-field-label`, and every `.remarque-field-message` is wired via `aria-describedby` on the control — the paint-layer `data-state` attribute is never the only signal; `aria-invalid`/`aria-describedby` on the input carry the real accessibility contract.",
30
- "version": "0.28.0",
30
+ "version": "0.29.0",
31
31
  "integrity": "sha256-Vxmu0D2080+Y8n0TcWzF2DGOlDbBNsdJ268j5Odg5BQ=",
32
32
  "file": "registry/forms.json"
33
33
  },
34
+ {
35
+ "name": "skip-link",
36
+ "type": "remarque:contract",
37
+ "title": "Skip Link (chrome.css)",
38
+ "description": "The first focusable element in <body>: `.remarque-skip-link` targets `<main id=\"main-content\" tabindex=\"-1\">`, visually hidden by clip-path (never an off-screen offset) until focused, then a filled fg-on-bg block on `--z-skip-link`. Ships with `.remarque-sr-only`. Shares chrome.css with the breadcrumb, pager and tags items.",
39
+ "version": "0.29.0",
40
+ "integrity": "sha256-qMjHT0szK/Abv4lAXodoVKcXEa0tpJV44vjfp6Cyzqs=",
41
+ "file": "registry/skip-link.json"
42
+ },
43
+ {
44
+ "name": "breadcrumb",
45
+ "type": "remarque:contract",
46
+ "title": "Breadcrumb (chrome.css)",
47
+ "description": "The Reference/Docs breadcrumb kicker: `nav[aria-label=\"Breadcrumb\"] > ol`, the current page last as `<span aria-current=\"page\">` (never a link), separators generated by CSS with empty alt text and never typed into the DOM.",
48
+ "version": "0.29.0",
49
+ "integrity": "sha256-V+FqUVopXBpnu9jfd8frbZJcs3NWmjv8KBuU/Uknaco=",
50
+ "file": "registry/breadcrumb.json"
51
+ },
52
+ {
53
+ "name": "pager",
54
+ "type": "remarque:contract",
55
+ "title": "Pager and Pagination (chrome.css)",
56
+ "description": "Prev/next between adjacent pages with `rel=\"prev\"`/`rel=\"next\"`, an optional series row (\"Part N of M\" plus a <details> list with the current part `aria-current=\"page\"`), and numbered `.remarque-pagination`. An endpoint with nowhere to go is omitted or plain text, never a disabled-looking link that stays focusable.",
57
+ "version": "0.29.0",
58
+ "integrity": "sha256-cTKt1fprH/XLtYSympI+pfjM+IO0rhinvazf1gGQKIo=",
59
+ "file": "registry/pager.json"
60
+ },
61
+ {
62
+ "name": "tags",
63
+ "type": "remarque:contract",
64
+ "title": "Tags (chrome.css)",
65
+ "description": "Tags as plain mono links in a `<ul>` with an accessible name, each `rel=\"tag\"`. No border, fill or radius: an outlined or filled chip is a documented deviation, not personalization.",
66
+ "version": "0.29.0",
67
+ "integrity": "sha256-g38UvUKiMIU1P9kIhKxjNSg+KKnhsmB8pNdbtVHZpe4=",
68
+ "file": "registry/tags.json"
69
+ },
34
70
  {
35
71
  "name": "palette-deck",
36
72
  "type": "remarque:contract",
37
73
  "title": "Palette Deck (markup contract only — no executable content)",
38
74
  "description": "Markup/wiring contract for the Palette Deck: set `data-palette` on the same root element as `data-theme` and the two compose independently. This item ships ONLY that HTML fragment. `deck.js` — the runtime module itself, a dependency-free ~60-line ESM file — is deliberately NOT embedded here: the registry’s blocking no-executable-content condition is held to an unambiguous zero-`<script>`-tag bar across every item, so neither `deck.js` nor the FOUC-restore `<script>` sample documented alongside it in REMARQUE.md is included. Consume `deck.js` the normal way, via the `remarque-tokens/deck` package import (`dependencies` below); read the FOUC-restore snippet from the `docs` URL.",
39
- "version": "0.28.0",
75
+ "version": "0.29.0",
40
76
  "integrity": "sha256-WGgBZ2j0VnLPRAZlzv/4G1Wu9rKqTdXf1536dWWu8nU=",
41
77
  "file": "registry/palette-deck.json"
42
78
  }
package/scripts/audit.mjs CHANGED
@@ -327,7 +327,7 @@ for (const file of walk(SRC)) {
327
327
  const fontStacks = {};
328
328
  // declsOf keys drop the leading --
329
329
  for (const t of ['font-body', 'font-display', 'font-mono']) fontStacks[`--${t}`] = lightDecls[t] || pkgDefaults[t];
330
- const MODULES = ['tokens-core.css', 'prose.css', 'essay.css', 'broadsheet.css', 'forms.css'];
330
+ const MODULES = ['tokens-core.css', 'prose.css', 'essay.css', 'broadsheet.css', 'forms.css', 'chrome.css'];
331
331
  const targets = MODULES.map((m) => [join(PKG_ROOT, m), `remarque-tokens/${m}`]);
332
332
  for (const file of walk(SRC)) if (file.endsWith('.css')) targets.push([file, relative('.', file)]);
333
333
  const seen = new Set();
@@ -79,6 +79,38 @@ const ITEM_DEFS = [
79
79
  docs: `${SPEC_BASE}#forms`,
80
80
  cssPaths: ['forms.css'],
81
81
  },
82
+ {
83
+ name: 'skip-link',
84
+ title: 'Skip Link (chrome.css)',
85
+ description:
86
+ 'The first focusable element in <body>: `.remarque-skip-link` targets `<main id="main-content" tabindex="-1">`, visually hidden by clip-path (never an off-screen offset) until focused, then a filled fg-on-bg block on `--z-skip-link`. Ships with `.remarque-sr-only`. Shares chrome.css with the breadcrumb, pager and tags items.',
87
+ docs: `${SPEC_BASE}#chrome-module`,
88
+ cssPaths: ['chrome.css'],
89
+ },
90
+ {
91
+ name: 'breadcrumb',
92
+ title: 'Breadcrumb (chrome.css)',
93
+ description:
94
+ 'The Reference/Docs breadcrumb kicker: `nav[aria-label="Breadcrumb"] > ol`, the current page last as `<span aria-current="page">` (never a link), separators generated by CSS with empty alt text and never typed into the DOM.',
95
+ docs: `${SPEC_BASE}#chrome-module`,
96
+ cssPaths: ['chrome.css'],
97
+ },
98
+ {
99
+ name: 'pager',
100
+ title: 'Pager and Pagination (chrome.css)',
101
+ description:
102
+ 'Prev/next between adjacent pages with `rel="prev"`/`rel="next"`, an optional series row ("Part N of M" plus a <details> list with the current part `aria-current="page"`), and numbered `.remarque-pagination`. An endpoint with nowhere to go is omitted or plain text, never a disabled-looking link that stays focusable.',
103
+ docs: `${SPEC_BASE}#chrome-module`,
104
+ cssPaths: ['chrome.css'],
105
+ },
106
+ {
107
+ name: 'tags',
108
+ title: 'Tags (chrome.css)',
109
+ description:
110
+ 'Tags as plain mono links in a `<ul>` with an accessible name, each `rel="tag"`. No border, fill or radius: an outlined or filled chip is a documented deviation, not personalization.',
111
+ docs: `${SPEC_BASE}#chrome-module`,
112
+ cssPaths: ['chrome.css'],
113
+ },
82
114
  {
83
115
  name: 'palette-deck',
84
116
  title: 'Palette Deck (markup contract only — no executable content)',
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.28.0). Do not edit — the CSS
3
+ * scripts/tokens-json.mjs (v0.29.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
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.28.0",
6
+ "version": "0.29.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"