remarque-tokens 0.6.1 → 0.8.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 +4 -2
- package/CHANGELOG.md +114 -0
- package/README.md +10 -2
- package/REMARQUE.md +130 -2
- package/package.json +11 -3
- package/print.css +140 -0
- package/prose.css +302 -0
- package/scripts/audit.mjs +1 -1
- package/scripts/drift-check.mjs +201 -0
- package/tokens-core.css +33 -1
- package/tokens.json +1 -1
package/AGENT_RULES.md
CHANGED
|
@@ -26,7 +26,7 @@ Execute in this exact order. Do not skip steps. Do not reorder.
|
|
|
26
26
|
|
|
27
27
|
8. **Validate readability on mobile** before adding any visual polish.
|
|
28
28
|
|
|
29
|
-
9. **Verify every page conforms** to one of the
|
|
29
|
+
9. **Verify every page conforms** to one of the seven archetypes (Essay, Project Dossier, Notebook, Landing, Reference/Docs, Changelog, Gallery).
|
|
30
30
|
|
|
31
31
|
**Why this order matters:** Most agents build polished components first and bolt on typography later. That produces the exact aesthetic Remarque exists to prevent. Typography and prose are the foundation. Everything else is furniture.
|
|
32
32
|
|
|
@@ -160,6 +160,8 @@ project/
|
|
|
160
160
|
└── notes # Notebook archetype
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
Three further archetypes — Reference/Docs, Changelog, Gallery — are specified in REMARQUE.md but have no reference page in this repo's demo site yet; build them per spec, they follow the same file-structure conventions as the four above (e.g. `docs/[slug]`, `changelog`, `gallery` or `browse`).
|
|
164
|
+
|
|
163
165
|
### Implementation Pitfalls (learned from reference implementation)
|
|
164
166
|
|
|
165
167
|
1. **Tailwind spacing collision:** Never map Remarque's `--space-N` values onto Tailwind's default numeric spacing keys — in v4 `@theme` that means no `--spacing-9`…`--spacing-12`, and in the v3 config no `spacing: { "5"…"12" }` overrides — or `mt-12` produces 192px instead of 48px. Both shipped artifacts namespace instead: the v3 config exposes `mt-remarque-9` etc., the reference v4 `@theme` uses `--spacing-remarque-N`. Use those, or `var(--space-N)` in arbitrary values.
|
|
@@ -195,7 +197,7 @@ Before considering any implementation complete, verify:
|
|
|
195
197
|
- [ ] Accent color appears in ≤2 roles per viewport
|
|
196
198
|
- [ ] No scroll-triggered or entrance animations exist
|
|
197
199
|
- [ ] Every image has a 1px border and mono caption
|
|
198
|
-
- [ ] Every page maps to an archetype (Essay, Dossier, Notebook, Landing)
|
|
200
|
+
- [ ] Every page maps to an archetype (Essay, Dossier, Notebook, Landing, Reference/Docs, Changelog, Gallery)
|
|
199
201
|
- [ ] Mobile version is roomy — not a compressed desktop layout
|
|
200
202
|
- [ ] Mobile nav links have ≥44px touch targets
|
|
201
203
|
- [ ] No pure white or pure black backgrounds
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,120 @@ 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.8.0 — 2026-07-21
|
|
8
|
+
|
|
9
|
+
Archetype-vocabulary and AI-agent-packaging release (design review; closes
|
|
10
|
+
#55, closes #57).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Three new page archetypes** (#55): Reference/Docs (persistent nav
|
|
14
|
+
rail, breadcrumb kicker, prev/next footer — reuses Essay's three-column
|
|
15
|
+
shape), Changelog (mono version/date headline, grouped Added/Changed/
|
|
16
|
+
Fixed lists — built from Notebook's entry structure), and Gallery
|
|
17
|
+
(content-wide cover grids, covers exempt from the reading-width cap,
|
|
18
|
+
border-only hover — formalized from tsundoku's documented reference
|
|
19
|
+
implementation, cited directly in REMARQUE.md). Seven archetypes total;
|
|
20
|
+
agents must not invent an eighth.
|
|
21
|
+
- **Plate** (#55): a screenshot-heavy-page subsection of Image Treatment —
|
|
22
|
+
numbered mono captions, a narrow 2-up exception for terminal captures.
|
|
23
|
+
- **CLI-tool landing guidance** (#55): a scoped, rationale-argued exception
|
|
24
|
+
to Landing's no-CTA rule for a single mono install command block.
|
|
25
|
+
- **Dataviz Tokens** (#55): chart grid/axis/categorical-ramp mapped onto
|
|
26
|
+
existing tokens and required to pass the audit — citing tsundoku's
|
|
27
|
+
orthogonal category-color system as precedent and naming the one gap
|
|
28
|
+
(never run through `remarque-audit`'s `CHECKS` array) this guidance
|
|
29
|
+
closes.
|
|
30
|
+
- **`remarque-tokens/agent-rules` and `remarque-tokens/spec` exports**
|
|
31
|
+
(#57): both files were already in `files`; this ships them as named
|
|
32
|
+
subpaths so a consumer can `require.resolve('remarque-tokens/agent-rules')`
|
|
33
|
+
instead of hardcoding a `node_modules` path.
|
|
34
|
+
- **`.claude/skills/remarque/SKILL.md`** (#57): a Claude Code skill
|
|
35
|
+
triggering on "remarque" / "design system" / new-page work. Loads
|
|
36
|
+
`AGENT_RULES.md` + `REMARQUE.md` + `tokens.json`, states the token-tier
|
|
37
|
+
rules, the audit command, and Pitfalls #6/#7 (string-form `@import`;
|
|
38
|
+
unlayered token import under Tailwind v4) inline — both pass a green
|
|
39
|
+
build while silently breaking, so they're worth surfacing without a
|
|
40
|
+
file round-trip.
|
|
41
|
+
- **`/tokens.json` on the demo site** (#57): `site/scripts/copy-tokens-json.mjs`
|
|
42
|
+
runs as a prebuild step (`npm run build` in `site/`), copying the
|
|
43
|
+
installed package's `tokens.json` into `public/` so it ships at
|
|
44
|
+
`https://williamzujkowski.github.io/remarque/tokens.json` — a remote
|
|
45
|
+
agent can fetch current token values instead of trusting training data.
|
|
46
|
+
The copy is generated, not a source file (`site/public/tokens.json` is
|
|
47
|
+
gitignored).
|
|
48
|
+
- Root README's "For AI Agents" section documents all three packaging
|
|
49
|
+
additions above.
|
|
50
|
+
|
|
51
|
+
No core-tier token values changed in this release.
|
|
52
|
+
|
|
53
|
+
## 0.7.0 — 2026-07-21
|
|
54
|
+
|
|
55
|
+
Typographic-completeness release (design review, #50/#51/#54): the
|
|
56
|
+
prose element vocabulary, the editorial microtypography layer, and a
|
|
57
|
+
print stylesheet — three gaps the review found between what Remarque
|
|
58
|
+
claims to be ("rooted in book typography") and what it shipped.
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
- **Prose vocabulary completion** (#50): `.remarque-prose` now styles
|
|
62
|
+
every remaining Markdown element — table/th/td/caption (+
|
|
63
|
+
`.scroll-wrap` overflow container, mono th with a 2px bottom rule,
|
|
64
|
+
1px row rules, no zebra, `.num` opt-in for tabular-lining numeric
|
|
65
|
+
cells), dl/dt/dd (serif-italic term over a muted description, the
|
|
66
|
+
"specimen list" pattern), h4–h6 (a descent through the three font
|
|
67
|
+
slots), kbd (mono, 1px border), mark (`--color-accent-subtle`'s first
|
|
68
|
+
sanctioned use), abbr[title] (dotted underline), sup/sub (footnote-
|
|
69
|
+
marker-safe sizing), and details/summary (quiet mono triangle
|
|
70
|
+
marker). No Markdown element renders unstyled after this release.
|
|
71
|
+
- **Editorial microtypography** (#51): the numerals policy
|
|
72
|
+
(`--text-meta` → tabular-nums lining-nums; `.remarque-prose` →
|
|
73
|
+
oldstyle-nums proportional-nums — REMARQUE.md:179 mandated this but
|
|
74
|
+
no CSS ever implemented it); `.text-label` (true small caps via
|
|
75
|
+
`font-variant-caps`, replacing `text-transform: uppercase`);
|
|
76
|
+
`.remarque-prose--dropcap` (opt-in Essay first-paragraph drop cap);
|
|
77
|
+
`.pullquote` (display italic, hairline rules, distinct from
|
|
78
|
+
blockquote); wrapping/optical defaults (`text-wrap: balance` on
|
|
79
|
+
headings, `text-wrap: pretty` + `hanging-punctuation` on prose,
|
|
80
|
+
`font-synthesis: none`, `font-optical-sizing: auto`). New "Editorial
|
|
81
|
+
Microtypography" spec section in REMARQUE.md.
|
|
82
|
+
- **`remarque-tokens/print.css`** (#54): a print layer scoped entirely
|
|
83
|
+
inside `@page`/`@media print` — forces the light palette with true
|
|
84
|
+
black-on-white (ink economy), hides nav/footer/TOC/theme-toggle/skip-
|
|
85
|
+
link, expands content to full page width at 2cm margins, sets body
|
|
86
|
+
to 11pt, appends `(url)` after external links, and applies
|
|
87
|
+
orphans/widows/break-inside discipline to figures/tables/pre. Own
|
|
88
|
+
subpath — not included by the `tokens.css` aggregator; import
|
|
89
|
+
explicitly.
|
|
90
|
+
- **Drift-detection CI** (#47/#48, consensus-armed now that 2+ sites
|
|
91
|
+
consume the package — williamzujkowski.github.io and tsundoku both pull
|
|
92
|
+
`remarque-tokens` from npm): `scripts/drift-check.mjs` (shipped in the
|
|
93
|
+
package; `files` + a `remarque-drift` bin entry) compares a consumer's
|
|
94
|
+
stylesheet against the INSTALLED package's `tokens.json`. Reuses
|
|
95
|
+
`scripts/lib/css-tokens.mjs` rather than a third CSS parser.
|
|
96
|
+
- CORE-tier token redefined with a different value → **FAIL** (exit 1),
|
|
97
|
+
unless the consumer's `DESIGN-DEVIATIONS.md` or `DESIGN-NOTES.md`
|
|
98
|
+
names the token — then **WARN** with a pointer to that doc.
|
|
99
|
+
- PALETTE-tier divergence → **INFO** (sanctioned personalization,
|
|
100
|
+
listed, never blocking).
|
|
101
|
+
- CORE-tier token consumed only via `var()`, never redeclared → no-op
|
|
102
|
+
(it resolves from the package import).
|
|
103
|
+
- Also prints the installed `remarque-tokens` version.
|
|
104
|
+
- `.github/workflows/token-drift.yml`: reusable `workflow_call` wrapper
|
|
105
|
+
consumers can call cross-repo (`css-file`, `package-dir` inputs).
|
|
106
|
+
- `scripts/test-drift.mjs`: fixture tests for the classification rules
|
|
107
|
+
(9 cases), wired into `deploy.yml` alongside `test-audit.mjs`.
|
|
108
|
+
|
|
109
|
+
### Changed
|
|
110
|
+
- `scripts/audit.mjs`'s token-file classification now recognizes
|
|
111
|
+
`print.css` (it carries literal `oklch()` values for the forced
|
|
112
|
+
print palette, same as a palette file).
|
|
113
|
+
- Demo site (`site/`): specimen page exercises the new table/dl/kbd/
|
|
114
|
+
mark/abbr/sup-sub/details vocabulary; the five faux-uppercase eyebrow
|
|
115
|
+
labels (`index.astro` x3, `tokens.astro`, `projects/remarque.astro`)
|
|
116
|
+
now use `.text-label`; `against-decoration.astro` demonstrates the
|
|
117
|
+
opt-in drop cap (`<Prose dropcap>`) and a `.pullquote`.
|
|
118
|
+
|
|
119
|
+
No core-tier token values changed in this release.
|
|
120
|
+
|
|
7
121
|
## 0.6.1 — 2026-07-21
|
|
8
122
|
|
|
9
123
|
### Fixed
|
package/README.md
CHANGED
|
@@ -58,6 +58,11 @@ See: REMARQUE.md for specification, AGENT_RULES.md for implementation contract,
|
|
|
58
58
|
|
|
59
59
|
The agent rules define build order, non-negotiable rules, disallowed patterns, and a quality checklist. Every decision is specified — agents don't need to guess.
|
|
60
60
|
|
|
61
|
+
Packaging for agent tooling:
|
|
62
|
+
- **npm exports:** `remarque-tokens/agent-rules` (→ `AGENT_RULES.md`) and `remarque-tokens/spec` (→ `REMARQUE.md`), alongside the existing `remarque-tokens/tokens.json`, so a project can point an agent at `node_modules/remarque-tokens/AGENT_RULES.md` without hardcoding a filename.
|
|
63
|
+
- **Claude Code skill:** [`.claude/skills/remarque/SKILL.md`](.claude/skills/remarque/SKILL.md) — triggers on "remarque" / "design system" / new-page work, loads all three files, and states the tier rules, the audit command, and the two build-time pitfalls (unlayered-token-import, string-form `@import`) that pass a green build while silently breaking.
|
|
64
|
+
- **Live tokens endpoint:** the demo site serves the current `tokens.json` at **https://williamzujkowski.github.io/remarque/tokens.json** — a remote agent can fetch current token values directly instead of trusting training data.
|
|
65
|
+
|
|
61
66
|
## Files
|
|
62
67
|
|
|
63
68
|
| File | Purpose |
|
|
@@ -83,14 +88,17 @@ The agent rules define build order, non-negotiable rules, disallowed patterns, a
|
|
|
83
88
|
|
|
84
89
|
## Page Archetypes
|
|
85
90
|
|
|
86
|
-
Every page conforms to one of
|
|
91
|
+
Every page conforms to one of seven archetypes:
|
|
87
92
|
|
|
88
93
|
- **Essay** — long-form writing with serif title, mono metadata, narrow reading column
|
|
89
94
|
- **Project Dossier** — structured project page with metadata block and architecture section
|
|
90
95
|
- **Notebook** — short-form notes with mono timestamps, no cards
|
|
91
96
|
- **Landing** — identity statement, content navigation, generous whitespace
|
|
97
|
+
- **Reference/Docs** — persistent nav rail, breadcrumb kicker, prev/next footer (reuses Essay's three-column shape)
|
|
98
|
+
- **Changelog** — mono version/date headlines, grouped Added/Changed/Fixed lists (built from Notebook's entry structure)
|
|
99
|
+
- **Gallery** — cover-grid pages at `--content-wide`, covers exempt from the reading-width cap (formalized from tsundoku's reference implementation)
|
|
92
100
|
|
|
93
|
-
See the [live demo](https://williamzujkowski.github.io/remarque/) for
|
|
101
|
+
See the [live demo](https://williamzujkowski.github.io/remarque/) for the original four archetypes in action — Reference/Docs, Changelog, and Gallery are specified in REMARQUE.md but don't yet have a demo page in this repo.
|
|
94
102
|
|
|
95
103
|
## Design Decisions
|
|
96
104
|
|
package/REMARQUE.md
CHANGED
|
@@ -100,6 +100,50 @@ This is why the measure is palette-tier: it is a property of the font choice, no
|
|
|
100
100
|
|
|
101
101
|
---
|
|
102
102
|
|
|
103
|
+
## Editorial Microtypography
|
|
104
|
+
|
|
105
|
+
The book-craft layer: numerals, small caps, drop caps, pull quotes, and optical wrapping. These are the details that separate typeset prose from a web page that merely uses nice fonts.
|
|
106
|
+
|
|
107
|
+
### Numerals
|
|
108
|
+
|
|
109
|
+
Two numeral registers, chosen by context:
|
|
110
|
+
|
|
111
|
+
- **`.remarque-prose`** uses `font-variant-numeric: oldstyle-nums proportional-nums` — text figures that sit quietly inside a sentence, the way numerals behave in well-set book prose.
|
|
112
|
+
- **`.text-meta`** (and anything sharing its role — dates, counts, table cells, timestamps) uses `font-variant-numeric: tabular-nums lining-nums` — full-height, fixed-width digits that align in columns and don't jitter as they change.
|
|
113
|
+
|
|
114
|
+
Never mix the two: a numeral inside running prose should never be tabular, and a numeral in a metadata row or table should never be oldstyle. Apply `.num` to any `<td>`/`<th>` holding numeric data to opt that cell into the tabular-lining register (see prose.css's table rules).
|
|
115
|
+
|
|
116
|
+
### Small Caps
|
|
117
|
+
|
|
118
|
+
`font-variant-caps: all-small-caps` — not `text-transform: uppercase` — for any label that wants a caps-like treatment. Faux uppercase renders letters at cap-height with no optical correction and reads louder than intended; true small caps render at x-height with corrected weight and spacing. `.text-label` (mono, `--text-meta`, all-small-caps, `--tracking-caps`, `--color-muted`) is the sanctioned utility. `text-transform: uppercase` is disallowed anywhere a label could use `.text-label` instead.
|
|
119
|
+
|
|
120
|
+
### Drop Caps (opt-in)
|
|
121
|
+
|
|
122
|
+
`.remarque-prose--dropcap`, applied alongside `.remarque-prose`, floats a large italic display-face initial letter on the first paragraph only — the way a printed essay opens its first page. Rules:
|
|
123
|
+
|
|
124
|
+
- Essay first paragraph only — never mid-article, never in Dossier/Notebook/Landing content.
|
|
125
|
+
- Uses `initial-letter` where supported, with a `float` + font-size fallback for browsers that lack it.
|
|
126
|
+
- Inherits `--color-fg` — the drop cap is not a place for accent color.
|
|
127
|
+
- Skipped automatically when the paragraph opens with a quote or inline code (the ornament assumes a plain capital letter).
|
|
128
|
+
- Opt-in only. Most pages should not use it; it is reserved for essays that want a printed, literary register.
|
|
129
|
+
|
|
130
|
+
### Pull Quotes
|
|
131
|
+
|
|
132
|
+
`.pullquote` is not `.remarque-prose blockquote`. A blockquote cites someone else; a pullquote re-states the article's own words as a visual accent — a magazine convention. Rules:
|
|
133
|
+
|
|
134
|
+
- Display italic at `--text-section` size, not blockquote's body-italic.
|
|
135
|
+
- Hairline rules above and below (`--color-border`), no background, no accent color.
|
|
136
|
+
- If the pulled text duplicates body copy verbatim, mark the element `<aside aria-hidden="true">` so screen readers don't hear it twice.
|
|
137
|
+
- At most one per screen — it is a rare accent, not a recurring motif.
|
|
138
|
+
|
|
139
|
+
### Wrapping & Optical Defaults
|
|
140
|
+
|
|
141
|
+
- Headings: `text-wrap: balance`, `font-optical-sizing: auto`, `font-synthesis: none` — headings never wrap ragged, optical sizing lets variable fonts pick the right grade, and browsers never fake bold/italic for faces that don't have them.
|
|
142
|
+
- Prose: `text-wrap: pretty` (avoids single-word orphan lines) and `hanging-punctuation: first last` (quotes and punctuation hang outside the measure rather than indenting the line).
|
|
143
|
+
- All four properties degrade safely — unsupported browsers silently ignore them and fall back to normal wrapping. Invisible when absent, unmistakable when present.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
103
147
|
## Visual Rules
|
|
104
148
|
|
|
105
149
|
### Do
|
|
@@ -128,7 +172,7 @@ This is why the measure is palette-tier: it is a property of the font choice, no
|
|
|
128
172
|
|
|
129
173
|
## Content Density Rule
|
|
130
174
|
|
|
131
|
-
No page should show more than three distinct content sections above the fold. The first screen of any page contains only: title, metadata, and the opening of content. Hero sections with CTAs, feature grids, and social proof are not part of Remarque's vocabulary.
|
|
175
|
+
No page should show more than three distinct content sections above the fold. The first screen of any page contains only: title, metadata, and the opening of content. Hero sections with CTAs, feature grids, and social proof are not part of Remarque's vocabulary. (The CLI-tool landing install block, see the Landing archetype, is a single command — not a CTA — and does not count against this rule.)
|
|
132
176
|
|
|
133
177
|
---
|
|
134
178
|
|
|
@@ -143,6 +187,28 @@ Remarque is typography-first, but projects include screenshots, architecture dia
|
|
|
143
187
|
- No rounded corners on images beyond `--radius-sm`
|
|
144
188
|
- Images are never decorative — every image must serve the content
|
|
145
189
|
|
|
190
|
+
### Plate (screenshot-heavy pages)
|
|
191
|
+
|
|
192
|
+
Reference/Docs and Project Dossier pages often need to show several screenshots — six or more UI captures walking through a workflow — where "one image at a time" doesn't scale. Plate is the sanctioned pattern; everything above still applies, it only adds:
|
|
193
|
+
|
|
194
|
+
- Default to single-column figures at `--content-reading` width, one screenshot per figure
|
|
195
|
+
- Every figure gets a numbered mono caption in the form `Fig. 01 — description` (`--font-mono`, `--text-meta`) — the number is mandatory once a page has more than one figure, so prose can cross-reference a specific plate
|
|
196
|
+
- A 2-up grid is permitted only for terminal/CLI captures, and only at `--content-standard` width — the one sanctioned exception to "images never wider than `--content-reading`," reserved for this narrow case
|
|
197
|
+
- A page with six or more plates gets prev/next figure navigation (mono, matching Reference/Docs' footer convention) rather than an unbroken scroll
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Dataviz Tokens
|
|
202
|
+
|
|
203
|
+
Charts are not yet a first-class Remarque surface, but tools built with the system inevitably need them. Rather than inventing chart-specific tokens, map dataviz elements onto vocabulary that already exists, so a chart survives a palette swap the same way prose and chrome do:
|
|
204
|
+
|
|
205
|
+
- **Grid lines** use `--color-border` — never a separate "chart gray." Grid lines are structural, not decorative, and already have a sanctioned quiet role.
|
|
206
|
+
- **Axis text and tick labels** use `--font-mono` at `--text-meta`, `--color-muted` — the same register as metadata rows and captions, never the body or display face.
|
|
207
|
+
- **Categorical color ramp** is derived, not hand-picked: rotate hue at the accent's lightness/chroma steps — the same discipline "Changing the Accent Hue" already prescribes (fix lightness, vary hue, reduce chroma until every value clears the gamut) — producing a small hue family distinct from `--color-accent` itself, so chart color is never mistaken for the system's one interactive signal.
|
|
208
|
+
- That ramp must be **audit-validated**, exactly like a palette override: every ramp color needs to clear gamut and 4.5:1 contrast against `--color-bg`/`--color-surface` in both themes before it ships.
|
|
209
|
+
|
|
210
|
+
**Precedent:** [tsundoku](https://github.com/williamzujkowski/tsundoku) built the underlying pattern already — its `DESIGN-NOTES.md` documents an 8-hue "orthogonal category system" (`--pop-pink`/`--pop-blue`/`--pop-green`/etc.), kept deliberately separate from `--color-accent` and used for content taxonomy (book categories, reading status) rather than interactivity. It held those hues to roughly the same lightness/contrast discipline as its audited palette, but by its own admission the hues were **never run through `remarque-audit`'s `CHECKS` array** — that is the one gap this guidance closes. A dataviz ramp should follow tsundoku's separation-from-accent instinct, but unlike its precedent, it must pass the audit before it ships.
|
|
211
|
+
|
|
146
212
|
---
|
|
147
213
|
|
|
148
214
|
## Motion Rules
|
|
@@ -280,7 +346,7 @@ This order is not a suggestion. It is the tiebreaker for every design decision.
|
|
|
280
346
|
|
|
281
347
|
## Page Archetypes
|
|
282
348
|
|
|
283
|
-
Every page built with Remarque must conform to one of these
|
|
349
|
+
Every page built with Remarque must conform to one of these seven archetypes: Essay, Project Dossier, Notebook, Landing, Reference/Docs, Changelog, Gallery. Agents and implementers should not invent new page structures — the last three exist precisely because real downstream projects (tsundoku, and the multi-site review that produced this revision) needed shapes the first four actively fought, and inventing an unsanctioned workaround is worse than extending the vocabulary here.
|
|
284
350
|
|
|
285
351
|
### Essay
|
|
286
352
|
|
|
@@ -352,6 +418,7 @@ The homepage or entry point. Sets the tone.
|
|
|
352
418
|
**Optionally includes:**
|
|
353
419
|
- Recent entries or updates (3–5 max)
|
|
354
420
|
- A brief "about" line
|
|
421
|
+
- **CLI-tool landing only:** a single mono install command block (styled as prose `<pre>`, copy button allowed) — the one sanctioned interactive element on an otherwise CTA-free page — and an optional `--help`-output figure (follows the Plate convention, see Image Treatment)
|
|
355
422
|
|
|
356
423
|
**Never includes:**
|
|
357
424
|
- Hero images or banners
|
|
@@ -360,6 +427,67 @@ The homepage or entry point. Sets the tone.
|
|
|
360
427
|
- Testimonial carousels
|
|
361
428
|
- Newsletter signup forms above the fold
|
|
362
429
|
|
|
430
|
+
The CLI install block is not a CTA exception in disguise — it is the single piece of information a CLI tool's landing page cannot omit (how do I get this), same status as a book's title. One block, one command, no surrounding marketing copy. A page with an install block *and* a separate "Sign up now" button has smuggled in a second CTA and fails this archetype.
|
|
431
|
+
|
|
432
|
+
### Reference/Docs
|
|
433
|
+
|
|
434
|
+
Persistent technical documentation: API references, config guides, multi-page manuals. Reuses the Essay archetype's existing three-column shape (nav rail outside the reading column · reading column · optional desktop ToC outside the reading column) — this is not a new layout engine, only a different left rail.
|
|
435
|
+
|
|
436
|
+
**Always includes:**
|
|
437
|
+
- A persistent left nav rail listing every doc page (mono, quiet, `--text-meta` — a list of links, not a component), sitting outside the reading column exactly like Essay's optional ToC sits outside it on the other side
|
|
438
|
+
- A breadcrumb kicker above the title (mono, `--text-meta`, e.g. `Reference / Configuration`)
|
|
439
|
+
- Content constrained to `--content-reading`, matching Essay's prose column
|
|
440
|
+
- A low-noise prev/next footer nav between adjacent doc pages (mono, matching Essay's footer nav)
|
|
441
|
+
|
|
442
|
+
**Optionally includes:**
|
|
443
|
+
- Tables and code blocks widening past the reading column to `--content-standard` when the content genuinely needs the room (a config reference table, a wide code sample) — the surrounding prose stays at `--content-reading`
|
|
444
|
+
- A desktop-only "on this page" ToC, reusing Essay's optional side-ToC convention, on the side opposite the nav rail
|
|
445
|
+
|
|
446
|
+
**Never includes:**
|
|
447
|
+
- Sidebar content mixed *into* the reading column itself (the nav rail is a fourth structural region, not reading-column content — same rule Essay already enforces for its own ToC)
|
|
448
|
+
- Card grids for the page listing (the nav rail is a list, not a grid of cards)
|
|
449
|
+
- More than one accent color
|
|
450
|
+
- CTAs, feature grids, or any Landing-archetype vocabulary
|
|
451
|
+
|
|
452
|
+
### Changelog
|
|
453
|
+
|
|
454
|
+
A tool or project's primary log of released changes. Built from the Notebook archetype's entry structure, not a new component: compact, mono-timestamped, content-forward entries, applied to version history instead of notes.
|
|
455
|
+
|
|
456
|
+
**Always includes:**
|
|
457
|
+
- A mono version + date headline per entry (e.g. `0.7.0 — 2026-07-21`) — semver renders as plain mono text, never a colored pill or badge
|
|
458
|
+
- Grouped category lists per entry (Added / Changed / Fixed, or whatever categories the project uses), reusing Notebook's compact entry rhythm
|
|
459
|
+
- Reverse-chronological order, most recent release first
|
|
460
|
+
|
|
461
|
+
**Optionally includes:**
|
|
462
|
+
- An inline mono link to the relevant issue/PR per line item
|
|
463
|
+
- A "compare" or diff link per version, mono, in the entry headline row
|
|
464
|
+
|
|
465
|
+
**Never includes:**
|
|
466
|
+
- Semver rendered as a pill/badge (`.rounded-full` chips, colored status pills) — plain mono text only, same rule as tags elsewhere in the system
|
|
467
|
+
- Card-based per-version layout
|
|
468
|
+
- Marketing framing of a change ("Exciting new feature!") — changelog prose stays as flat and factual as commit messages
|
|
469
|
+
|
|
470
|
+
A changelog embedded as an optional subsection of a Project Dossier (already sanctioned) remains valid for short update logs. Promote to the standalone Changelog archetype once the log is long enough, or important enough, to be the page rather than a section of one — typically once a tool ships its own versioned releases.
|
|
471
|
+
|
|
472
|
+
### Gallery
|
|
473
|
+
|
|
474
|
+
Cover-grid and catalog pages — a page whose primary content unit is a grid of images with a caption, not prose. None of the other archetypes are shaped for this: they are all fundamentally text/document-shaped. **Reference implementation: [tsundoku](https://github.com/williamzujkowski/tsundoku)**, which needed this shape immediately for its book-catalog `/browse` grid and documented it as an informal fifth archetype in its `DESIGN-NOTES.md` before this spec existed; Gallery formalizes that implementation rather than inventing a new one.
|
|
475
|
+
|
|
476
|
+
**Always includes:**
|
|
477
|
+
- A grid container widened beyond `--content-standard` (72rem) to `--content-wide` (88rem) — Gallery is the only archetype permitted to exceed `--content-standard`, because a grid of cover-aspect thumbnails needs more horizontal room before it feels cramped than an essay or a dossier ever would
|
|
478
|
+
- Cover/thumbnail images exempt from the `--content-reading` width cap that Image Treatment otherwise mandates — cover art *is* the content here, not illustrative decoration of prose. Every other Image Treatment rule still applies without exception: 1px `--color-border`, no drop shadow, mono captions
|
|
479
|
+
- Grid item hover restricted to a border-color shift only (`--motion-fast`) — no scale, no lift, no shadow growth, matching Motion Rules' general prohibition on attention-seeking animation
|
|
480
|
+
- Grid density driven by content (`repeat(auto-fill, minmax(...))` or breakpoint-based column counts), never a fixed card size that fights the image's native aspect ratio
|
|
481
|
+
|
|
482
|
+
**Optionally includes:**
|
|
483
|
+
- Prose-bearing sections within a Gallery page (an "about" blurb, an item description) narrowed back down to `--content-reading` — the widened container serves the grid only, not every element on the page
|
|
484
|
+
- Mono captions under each cover (title, author, or other item metadata)
|
|
485
|
+
|
|
486
|
+
**Never includes:**
|
|
487
|
+
- Card shadows or scale/lift-on-hover
|
|
488
|
+
- Masonry layouts (Notebook's existing prohibition applies here too — a fixed-ratio grid only)
|
|
489
|
+
- The `--content-wide` container used for anything other than the grid itself
|
|
490
|
+
|
|
363
491
|
---
|
|
364
492
|
|
|
365
493
|
## Signature Moves
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remarque-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=18"
|
|
6
6
|
},
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
"homepage": "https://williamzujkowski.github.io/remarque/",
|
|
23
23
|
"main": "tokens.css",
|
|
24
24
|
"bin": {
|
|
25
|
-
"remarque-audit": "scripts/audit.mjs"
|
|
25
|
+
"remarque-audit": "scripts/audit.mjs",
|
|
26
|
+
"remarque-drift": "scripts/drift-check.mjs"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
|
-
"audit": "node scripts/audit.mjs"
|
|
29
|
+
"audit": "node scripts/audit.mjs",
|
|
30
|
+
"drift": "node scripts/drift-check.mjs"
|
|
29
31
|
},
|
|
30
32
|
"exports": {
|
|
31
33
|
".": "./tokens.css",
|
|
@@ -43,6 +45,10 @@
|
|
|
43
45
|
"./tokens.json": "./tokens.json",
|
|
44
46
|
"./prose": "./prose.css",
|
|
45
47
|
"./prose.css": "./prose.css",
|
|
48
|
+
"./print": "./print.css",
|
|
49
|
+
"./print.css": "./print.css",
|
|
50
|
+
"./agent-rules": "./AGENT_RULES.md",
|
|
51
|
+
"./spec": "./REMARQUE.md",
|
|
46
52
|
"./package.json": "./package.json"
|
|
47
53
|
},
|
|
48
54
|
"files": [
|
|
@@ -52,6 +58,7 @@
|
|
|
52
58
|
"fonts.css",
|
|
53
59
|
"fonts/",
|
|
54
60
|
"scripts/audit.mjs",
|
|
61
|
+
"scripts/drift-check.mjs",
|
|
55
62
|
"tailwind.config.js",
|
|
56
63
|
"REMARQUE.md",
|
|
57
64
|
"AGENT_RULES.md",
|
|
@@ -61,6 +68,7 @@
|
|
|
61
68
|
"tokens.json",
|
|
62
69
|
"scripts/tokens-json.mjs",
|
|
63
70
|
"prose.css",
|
|
71
|
+
"print.css",
|
|
64
72
|
"scripts/lib/css-tokens.mjs"
|
|
65
73
|
]
|
|
66
74
|
}
|
package/print.css
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Remarque — Print Stylesheet
|
|
3
|
+
* ────────────────────────────
|
|
4
|
+
* A "rooted in book typography" system that prints screen chrome and
|
|
5
|
+
* dead links has not earned the claim. This file is scoped entirely
|
|
6
|
+
* inside @page/@media print — it has zero effect on screen rendering,
|
|
7
|
+
* so it is safe to import unconditionally alongside tokens.css.
|
|
8
|
+
*
|
|
9
|
+
* Own subpath (remarque-tokens/print, remarque-tokens/print.css) —
|
|
10
|
+
* NOT included by the tokens.css aggregator, since not every consumer
|
|
11
|
+
* wants it wired in automatically. Import explicitly:
|
|
12
|
+
* @import 'remarque-tokens/print.css';
|
|
13
|
+
* (string form — see AGENT_RULES.md Pitfall #6.)
|
|
14
|
+
*
|
|
15
|
+
* Selectors for screen chrome (nav/footer/TOC/theme toggle/skip link)
|
|
16
|
+
* target common conventions (<nav>, <footer>, #theme-toggle, the
|
|
17
|
+
* "Skip to content" anchor) rather than any one consumer's exact
|
|
18
|
+
* markup — a site with different class names should add its own
|
|
19
|
+
* @media print rule for anything this file doesn't catch.
|
|
20
|
+
*
|
|
21
|
+
* Sidenotes: this package does not yet ship a sidenotes module. When
|
|
22
|
+
* one lands, its narrow-viewport "inline" mode is exactly the right
|
|
23
|
+
* print behavior — print should never attempt the wide-margin
|
|
24
|
+
* side-rail layout a sidenotes module might use on desktop screens.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
@page {
|
|
28
|
+
margin: 2cm;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@media print {
|
|
32
|
+
/* ─── Force the light palette (ink economy) ───────────────
|
|
33
|
+
* True black on white, not the screen theme's warm near-black/
|
|
34
|
+
* off-white — printed pages are read in ambient light with no theme
|
|
35
|
+
* toggle available, and a warm off-white background prints as a
|
|
36
|
+
* visible ink wash on many printers. Overrides regardless of which
|
|
37
|
+
* dark-mode convention put the site in dark mode. */
|
|
38
|
+
:root,
|
|
39
|
+
[data-theme="light"],
|
|
40
|
+
[data-theme="dark"],
|
|
41
|
+
:root.dark {
|
|
42
|
+
--color-bg: oklch(1 0 0);
|
|
43
|
+
--color-bg-subtle: oklch(1 0 0);
|
|
44
|
+
--color-fg: oklch(0 0 0);
|
|
45
|
+
--color-fg-muted: oklch(0.25 0 0);
|
|
46
|
+
--color-muted: oklch(0.35 0 0);
|
|
47
|
+
--color-border: oklch(0.7 0 0);
|
|
48
|
+
--color-border-bold: oklch(0.4 0 0);
|
|
49
|
+
--color-surface: oklch(1 0 0);
|
|
50
|
+
--color-accent: oklch(0 0 0);
|
|
51
|
+
--color-accent-hover: oklch(0 0 0);
|
|
52
|
+
--color-accent-subtle: oklch(1 0 0);
|
|
53
|
+
--color-code-bg: oklch(0.96 0 0);
|
|
54
|
+
--color-code-fg: oklch(0 0 0);
|
|
55
|
+
--color-selection-bg: oklch(1 0 0);
|
|
56
|
+
--color-selection-fg: oklch(0 0 0);
|
|
57
|
+
--weight-display: 400;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
html,
|
|
61
|
+
body {
|
|
62
|
+
background: oklch(1 0 0) !important;
|
|
63
|
+
color: oklch(0 0 0) !important;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
* {
|
|
67
|
+
box-shadow: none !important;
|
|
68
|
+
text-shadow: none !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ─── Hide screen-only chrome ──────────────────────────────
|
|
72
|
+
* Navigation, footer, table of contents, theme toggle, skip link. */
|
|
73
|
+
nav,
|
|
74
|
+
footer,
|
|
75
|
+
.toc,
|
|
76
|
+
[class*="toc-"],
|
|
77
|
+
#theme-toggle,
|
|
78
|
+
[data-theme-toggle],
|
|
79
|
+
.theme-toggle,
|
|
80
|
+
a[href="#main-content"],
|
|
81
|
+
a[href^="#main"],
|
|
82
|
+
.skip-link,
|
|
83
|
+
.copy-btn {
|
|
84
|
+
display: none !important;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ─── Full-width reading column ────────────────────────────
|
|
88
|
+
* The page itself is already narrow at 2cm margins — don't also
|
|
89
|
+
* constrain content to the on-screen reading measure. */
|
|
90
|
+
.content-reading,
|
|
91
|
+
.content-standard,
|
|
92
|
+
.content-wide,
|
|
93
|
+
main {
|
|
94
|
+
max-width: 100% !important;
|
|
95
|
+
margin-inline: 0 !important;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
body {
|
|
99
|
+
font-size: 11pt;
|
|
100
|
+
line-height: 1.5;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ─── External links keep their URL on paper ─────────────── */
|
|
104
|
+
a[href^="http"]::after {
|
|
105
|
+
content: " (" attr(href) ")";
|
|
106
|
+
font-family: var(--font-mono);
|
|
107
|
+
font-size: 0.75em;
|
|
108
|
+
color: oklch(0 0 0);
|
|
109
|
+
word-break: break-all;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ─── Orphans, widows, and page-break discipline ──────────── */
|
|
113
|
+
p,
|
|
114
|
+
li,
|
|
115
|
+
dd,
|
|
116
|
+
blockquote {
|
|
117
|
+
orphans: 2;
|
|
118
|
+
widows: 2;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
h1,
|
|
122
|
+
h2,
|
|
123
|
+
h3,
|
|
124
|
+
h4,
|
|
125
|
+
h5,
|
|
126
|
+
h6 {
|
|
127
|
+
break-after: avoid;
|
|
128
|
+
orphans: 3;
|
|
129
|
+
widows: 3;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
figure,
|
|
133
|
+
table,
|
|
134
|
+
pre,
|
|
135
|
+
.remarque-prose pre,
|
|
136
|
+
.remarque-prose table,
|
|
137
|
+
.remarque-prose details {
|
|
138
|
+
break-inside: avoid;
|
|
139
|
+
}
|
|
140
|
+
}
|
package/prose.css
CHANGED
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
line-height: var(--leading-body);
|
|
21
21
|
color: var(--color-fg);
|
|
22
22
|
max-width: var(--content-reading);
|
|
23
|
+
/* Oldstyle proportional numerals — text figures that sit quietly
|
|
24
|
+
inside a sentence, the way numerals behave in well-set book prose.
|
|
25
|
+
Never mixed with .text-meta's tabular lining register (issue #51). */
|
|
26
|
+
font-variant-numeric: oldstyle-nums proportional-nums;
|
|
27
|
+
/* Optical defaults: avoid single-word orphan lines, let quotes and
|
|
28
|
+
punctuation hang outside the measure. Both degrade safely. */
|
|
29
|
+
text-wrap: pretty;
|
|
30
|
+
hanging-punctuation: first last;
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
.remarque-prose > * + * {
|
|
@@ -137,6 +145,300 @@
|
|
|
137
145
|
margin-top: var(--space-2);
|
|
138
146
|
}
|
|
139
147
|
|
|
148
|
+
/* ─── Headings, continued (h4–h6) ───────────────────────── */
|
|
149
|
+
/*
|
|
150
|
+
* h2/h3 are defined above. h4–h6 complete the vocabulary with a
|
|
151
|
+
* deliberate descent through the three font slots — h4 stays in the
|
|
152
|
+
* body voice (still prose), h5 borrows the display face's italic for a
|
|
153
|
+
* quieter sub-heading, h6 drops into the mono "technical label" voice
|
|
154
|
+
* used everywhere else for the deepest level of metadata. No
|
|
155
|
+
* text-transform: uppercase — see .text-label (tokens-core.css) for
|
|
156
|
+
* the true small-caps alternative.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
.remarque-prose h4 {
|
|
160
|
+
font-family: var(--font-body);
|
|
161
|
+
font-size: var(--text-body);
|
|
162
|
+
font-weight: var(--weight-semibold);
|
|
163
|
+
line-height: var(--leading-title);
|
|
164
|
+
margin-top: var(--space-5);
|
|
165
|
+
margin-bottom: var(--space-2);
|
|
166
|
+
color: var(--color-fg);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.remarque-prose h5 {
|
|
170
|
+
font-family: var(--font-display);
|
|
171
|
+
font-style: italic;
|
|
172
|
+
font-size: var(--text-body);
|
|
173
|
+
font-weight: var(--weight-medium);
|
|
174
|
+
line-height: var(--leading-title);
|
|
175
|
+
margin-top: var(--space-4);
|
|
176
|
+
margin-bottom: var(--space-2);
|
|
177
|
+
color: var(--color-fg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.remarque-prose h6 {
|
|
181
|
+
font-family: var(--font-mono);
|
|
182
|
+
font-size: var(--text-meta);
|
|
183
|
+
letter-spacing: var(--tracking-meta);
|
|
184
|
+
font-weight: var(--weight-semibold);
|
|
185
|
+
line-height: var(--leading-meta);
|
|
186
|
+
margin-top: var(--space-4);
|
|
187
|
+
margin-bottom: var(--space-2);
|
|
188
|
+
color: var(--color-muted);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Optical/wrapping defaults for prose headings (issue #51) — see the
|
|
192
|
+
matching rule on .text-display/.text-title/.text-section in
|
|
193
|
+
tokens-core.css. */
|
|
194
|
+
.remarque-prose h2,
|
|
195
|
+
.remarque-prose h3,
|
|
196
|
+
.remarque-prose h4,
|
|
197
|
+
.remarque-prose h5,
|
|
198
|
+
.remarque-prose h6 {
|
|
199
|
+
text-wrap: balance;
|
|
200
|
+
font-optical-sizing: auto;
|
|
201
|
+
font-synthesis: none;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ─── Tables ─────────────────────────────────────────────── */
|
|
205
|
+
/*
|
|
206
|
+
* Reading-width tables: mono th voice (metadata surface, matching
|
|
207
|
+
* figcaption/code/pre), a 2px rule under the header row, 1px row rules
|
|
208
|
+
* only (no vertical rules, no zebra striping — quiet by rule). Numeric
|
|
209
|
+
* columns opt in to tabular lining figures with the `.num` class on the
|
|
210
|
+
* relevant <th>/<td> (keeps digits from jittering as they change,
|
|
211
|
+
* matches the .text-meta numerals policy).
|
|
212
|
+
*
|
|
213
|
+
* Wide tables should not force the reading column wider than
|
|
214
|
+
* --content-reading — wrap them:
|
|
215
|
+
* <div class="scroll-wrap"><table>...</table></div>
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
.remarque-prose .scroll-wrap {
|
|
219
|
+
overflow-x: auto;
|
|
220
|
+
-webkit-overflow-scrolling: touch;
|
|
221
|
+
margin-top: var(--space-5);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.remarque-prose .scroll-wrap > table {
|
|
225
|
+
margin-top: 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.remarque-prose table {
|
|
229
|
+
width: 100%;
|
|
230
|
+
border-collapse: collapse;
|
|
231
|
+
font-size: var(--text-body);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.remarque-prose caption {
|
|
235
|
+
font-family: var(--font-mono);
|
|
236
|
+
font-size: var(--text-meta);
|
|
237
|
+
letter-spacing: var(--tracking-meta);
|
|
238
|
+
color: var(--color-muted);
|
|
239
|
+
text-align: left;
|
|
240
|
+
caption-side: top;
|
|
241
|
+
margin-bottom: var(--space-2);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.remarque-prose th {
|
|
245
|
+
font-family: var(--font-mono);
|
|
246
|
+
font-size: var(--text-meta);
|
|
247
|
+
letter-spacing: var(--tracking-meta);
|
|
248
|
+
font-weight: var(--weight-semibold);
|
|
249
|
+
text-align: left;
|
|
250
|
+
padding: var(--space-2) var(--space-3);
|
|
251
|
+
border-bottom: 2px solid var(--color-border-bold);
|
|
252
|
+
color: var(--color-fg);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.remarque-prose td {
|
|
256
|
+
padding: var(--space-2) var(--space-3);
|
|
257
|
+
border-bottom: var(--border-width) var(--border-style) var(--color-border);
|
|
258
|
+
color: var(--color-fg);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.remarque-prose th.num,
|
|
262
|
+
.remarque-prose td.num {
|
|
263
|
+
font-variant-numeric: tabular-nums lining-nums;
|
|
264
|
+
text-align: right;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* ─── Definition Lists ───────────────────────────────────── */
|
|
268
|
+
/*
|
|
269
|
+
* The "specimen list" pattern: a serif-italic term (dt) over a muted
|
|
270
|
+
* body-face description (dd), row rules like a table. Nest a
|
|
271
|
+
* `.text-label`/`.text-meta` span inside dt for a metadata line under
|
|
272
|
+
* the term — the utility already renders it in the mono voice, no
|
|
273
|
+
* extra CSS needed.
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
.remarque-prose dl {
|
|
277
|
+
border-top: var(--border-width) var(--border-style) var(--color-border);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.remarque-prose dt {
|
|
281
|
+
font-family: var(--font-display);
|
|
282
|
+
font-style: italic;
|
|
283
|
+
font-size: var(--text-body-lg);
|
|
284
|
+
line-height: var(--leading-title);
|
|
285
|
+
color: var(--color-fg);
|
|
286
|
+
margin-top: var(--space-4);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.remarque-prose dt:first-child {
|
|
290
|
+
margin-top: var(--space-4);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.remarque-prose dd {
|
|
294
|
+
margin-left: 0;
|
|
295
|
+
margin-top: var(--space-1);
|
|
296
|
+
padding-bottom: var(--space-4);
|
|
297
|
+
color: var(--color-fg-muted);
|
|
298
|
+
border-bottom: var(--border-width) var(--border-style) var(--color-border);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* ─── kbd, mark, abbr, sup/sub ───────────────────────────── */
|
|
302
|
+
|
|
303
|
+
.remarque-prose kbd {
|
|
304
|
+
font-family: var(--font-mono);
|
|
305
|
+
font-size: 0.85em;
|
|
306
|
+
padding: 0.1em 0.4em;
|
|
307
|
+
border: var(--border-width) var(--border-style) var(--color-border);
|
|
308
|
+
border-radius: var(--radius-sm);
|
|
309
|
+
background-color: var(--color-surface);
|
|
310
|
+
color: var(--color-fg);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* --color-accent-subtle's first sanctioned use (issue #50) — a quiet
|
|
314
|
+
highlight, not a callout background. */
|
|
315
|
+
.remarque-prose mark {
|
|
316
|
+
background-color: var(--color-accent-subtle);
|
|
317
|
+
color: var(--color-fg);
|
|
318
|
+
padding: 0.05em 0.2em;
|
|
319
|
+
border-radius: var(--radius-sm);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.remarque-prose abbr[title] {
|
|
323
|
+
text-decoration: underline dotted;
|
|
324
|
+
text-underline-offset: 0.2em;
|
|
325
|
+
cursor: help;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.remarque-prose sup,
|
|
329
|
+
.remarque-prose sub {
|
|
330
|
+
font-size: 0.75em;
|
|
331
|
+
line-height: 0;
|
|
332
|
+
position: relative;
|
|
333
|
+
vertical-align: baseline;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.remarque-prose sup {
|
|
337
|
+
top: -0.5em;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.remarque-prose sub {
|
|
341
|
+
bottom: -0.25em;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* ─── Details / Summary ──────────────────────────────────── */
|
|
345
|
+
/*
|
|
346
|
+
* Collapsible asides. Quiet marker: a small triangle in the mono
|
|
347
|
+
* metadata voice, no default browser disclosure widget.
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
.remarque-prose details {
|
|
351
|
+
border-top: var(--border-width) var(--border-style) var(--color-border);
|
|
352
|
+
padding-top: var(--space-3);
|
|
353
|
+
padding-bottom: var(--space-1);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.remarque-prose summary {
|
|
357
|
+
font-family: var(--font-mono);
|
|
358
|
+
font-size: var(--text-meta);
|
|
359
|
+
letter-spacing: var(--tracking-meta);
|
|
360
|
+
color: var(--color-muted);
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
list-style: none;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.remarque-prose summary::-webkit-details-marker {
|
|
366
|
+
display: none;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.remarque-prose summary::before {
|
|
370
|
+
content: "\25B8";
|
|
371
|
+
display: inline-block;
|
|
372
|
+
margin-right: var(--space-2);
|
|
373
|
+
color: var(--color-muted);
|
|
374
|
+
transition: transform var(--motion-fast) var(--motion-easing);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.remarque-prose details[open] summary::before {
|
|
378
|
+
transform: rotate(90deg);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.remarque-prose details > :not(summary) {
|
|
382
|
+
margin-top: var(--space-3);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
/* ─── Drop Cap (opt-in) ──────────────────────────────────── */
|
|
387
|
+
/*
|
|
388
|
+
* Essay first paragraph only. Add alongside .remarque-prose:
|
|
389
|
+
* <div class="remarque-prose remarque-prose--dropcap">
|
|
390
|
+
* `initial-letter` is used where supported, with a float + font-size
|
|
391
|
+
* fallback for browsers that lack it. Inherits --color-fg — the drop
|
|
392
|
+
* cap is not a place for accent color. Automatically skipped when the
|
|
393
|
+
* paragraph opens with a quote or inline code, since the ornament
|
|
394
|
+
* assumes a plain capital letter.
|
|
395
|
+
*/
|
|
396
|
+
|
|
397
|
+
.remarque-prose--dropcap > p:first-of-type:not(:has(> q:first-child)):not(:has(> code:first-child))::first-letter {
|
|
398
|
+
font-family: var(--font-display);
|
|
399
|
+
font-style: italic;
|
|
400
|
+
font-size: 3.5em;
|
|
401
|
+
line-height: 0.85;
|
|
402
|
+
float: left;
|
|
403
|
+
padding: 0.08em 0.12em 0 0;
|
|
404
|
+
color: var(--color-fg);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {
|
|
408
|
+
.remarque-prose--dropcap > p:first-of-type:not(:has(> q:first-child)):not(:has(> code:first-child))::first-letter {
|
|
409
|
+
initial-letter: 3.5;
|
|
410
|
+
-webkit-initial-letter: 3.5;
|
|
411
|
+
float: none;
|
|
412
|
+
padding: 0 0.08em 0 0;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* ─── Pull Quote ─────────────────────────────────────────── */
|
|
417
|
+
/*
|
|
418
|
+
* Not .remarque-prose blockquote — a blockquote cites someone else; a
|
|
419
|
+
* pullquote re-states the article's own words as a visual accent (a
|
|
420
|
+
* magazine convention). Display italic at section size, hairline rules
|
|
421
|
+
* above/below, no background, no accent color. If the pulled text
|
|
422
|
+
* duplicates body copy verbatim, mark the element aria-hidden so
|
|
423
|
+
* screen readers don't hear it twice:
|
|
424
|
+
* <aside class="pullquote" aria-hidden="true">...</aside>
|
|
425
|
+
* At most one per screen.
|
|
426
|
+
*/
|
|
427
|
+
|
|
428
|
+
.pullquote {
|
|
429
|
+
font-family: var(--font-display);
|
|
430
|
+
font-style: italic;
|
|
431
|
+
font-size: var(--text-section);
|
|
432
|
+
line-height: var(--leading-section);
|
|
433
|
+
color: var(--color-fg);
|
|
434
|
+
text-align: center;
|
|
435
|
+
max-width: var(--content-reading);
|
|
436
|
+
margin: var(--space-7) auto;
|
|
437
|
+
padding: var(--space-4) 0;
|
|
438
|
+
border-top: var(--border-width) var(--border-style) var(--color-border);
|
|
439
|
+
border-bottom: var(--border-width) var(--border-style) var(--color-border);
|
|
440
|
+
}
|
|
441
|
+
|
|
140
442
|
|
|
141
443
|
/* ─── The Remarque Mark ─────────────────────────────────── */
|
|
142
444
|
/*
|
package/scripts/audit.mjs
CHANGED
|
@@ -157,7 +157,7 @@ const FONT_FLOOR_REM = 0.8125, FONT_FLOOR_PX = 13;
|
|
|
157
157
|
const paletteRel = relative('.', PALETTE);
|
|
158
158
|
const isTokenFile = (p) =>
|
|
159
159
|
p === paletteRel ||
|
|
160
|
-
/tokens(-core|-palette)?\.css$|fonts\.css$|globals?\.css$|theme-deck\.css$|pages[\/\\]tokens\.astro$/.test(p);
|
|
160
|
+
/tokens(-core|-palette)?\.css$|fonts\.css$|globals?\.css$|theme-deck\.css$|print\.css$|pages[\/\\]tokens\.astro$/.test(p);
|
|
161
161
|
|
|
162
162
|
for (const file of walk(SRC)) {
|
|
163
163
|
const rel = relative('.', file);
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* remarque-drift — token drift check for consumers of remarque-tokens.
|
|
4
|
+
*
|
|
5
|
+
* node scripts/drift-check.mjs --css-file <path> --package-dir <dir>
|
|
6
|
+
* npx remarque-drift --css-file src/styles/global.css --package-dir .
|
|
7
|
+
*
|
|
8
|
+
* Compares a consumer's stylesheet against the INSTALLED remarque-tokens
|
|
9
|
+
* package (node_modules/remarque-tokens under --package-dir), using the
|
|
10
|
+
* package's own generated tokens.json as the source of truth. Reuses
|
|
11
|
+
* scripts/lib/css-tokens.mjs (same brace-aware parser as audit.mjs /
|
|
12
|
+
* tokens-json.mjs) rather than a third CSS parser.
|
|
13
|
+
*
|
|
14
|
+
* Classification (see REMARQUE.md "Token Tiers"):
|
|
15
|
+
*
|
|
16
|
+
* (a) CORE-tier token redeclared with a DIFFERENT value:
|
|
17
|
+
* - FAIL (exit 1) — undocumented core-tier override. The site has
|
|
18
|
+
* forked, by definition.
|
|
19
|
+
* - WARN — if a DESIGN-DEVIATIONS.md or DESIGN-NOTES.md in the
|
|
20
|
+
* consumer mentions the token name, the fork is a ratified,
|
|
21
|
+
* documented deviation; still surfaced, not a build failure.
|
|
22
|
+
* (b) PALETTE-tier token redeclared with a different value:
|
|
23
|
+
* - INFO — sanctioned personalization. Listed, not warned.
|
|
24
|
+
* (c) CORE-tier token used via var(--x) but never declared anywhere in
|
|
25
|
+
* the consumer's stylesheet: no-op. It resolves from the package
|
|
26
|
+
* import at build time; there is nothing to compare.
|
|
27
|
+
*
|
|
28
|
+
* Exit code: 1 iff there is at least one undocumented (FAIL) core-tier
|
|
29
|
+
* mismatch. WARN and INFO never fail the build.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
33
|
+
import { join, dirname, resolve } from 'node:path';
|
|
34
|
+
import { extractBlocks, declsOf, isLightRoot, isDarkBlock } from './lib/css-tokens.mjs';
|
|
35
|
+
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
function argOf(flag, dflt) {
|
|
38
|
+
const i = args.indexOf(flag);
|
|
39
|
+
return i !== -1 && args[i + 1] ? args[i + 1] : dflt;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CSS_FILE = argOf('--css-file', null);
|
|
43
|
+
const PACKAGE_DIR = resolve(argOf('--package-dir', '.'));
|
|
44
|
+
|
|
45
|
+
if (!CSS_FILE) {
|
|
46
|
+
console.error('usage: drift-check.mjs --css-file <path> --package-dir <dir>');
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
if (!existsSync(CSS_FILE)) {
|
|
50
|
+
console.error(`consumer stylesheet not found: ${CSS_FILE}`);
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const PKG_ROOT = join(PACKAGE_DIR, 'node_modules', 'remarque-tokens');
|
|
55
|
+
const PKG_JSON = join(PKG_ROOT, 'package.json');
|
|
56
|
+
const TOKENS_JSON = join(PKG_ROOT, 'tokens.json');
|
|
57
|
+
|
|
58
|
+
if (!existsSync(PKG_JSON) || !existsSync(TOKENS_JSON)) {
|
|
59
|
+
console.error(
|
|
60
|
+
`remarque-tokens not found under ${PACKAGE_DIR}/node_modules — ` +
|
|
61
|
+
`pass --package-dir pointing at the directory containing node_modules/remarque-tokens`
|
|
62
|
+
);
|
|
63
|
+
process.exit(2);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const installedVersion = JSON.parse(readFileSync(PKG_JSON, 'utf8')).version;
|
|
67
|
+
const tokens = JSON.parse(readFileSync(TOKENS_JSON, 'utf8'));
|
|
68
|
+
|
|
69
|
+
/* ── Deviation-doc lookup ──────────────────────────────────────────
|
|
70
|
+
* Accepts either filename (flagship uses DESIGN-DEVIATIONS.md; tsundoku
|
|
71
|
+
* uses DESIGN-NOTES.md — both are "this site's documented departures"
|
|
72
|
+
* docs, just named differently by each consumer). Searched in
|
|
73
|
+
* --package-dir, the consumer's cwd, and every directory between the
|
|
74
|
+
* stylesheet and the filesystem/git root, so it's found regardless of
|
|
75
|
+
* which directory happens to hold node_modules vs the doc. */
|
|
76
|
+
const DEVIATION_DOC_NAMES = ['DESIGN-DEVIATIONS.md', 'DESIGN-NOTES.md'];
|
|
77
|
+
|
|
78
|
+
function candidateDirs(cssFile, packageDir) {
|
|
79
|
+
const dirs = [packageDir, resolve('.')];
|
|
80
|
+
let dir = resolve(dirname(cssFile));
|
|
81
|
+
for (let i = 0; i < 8; i++) {
|
|
82
|
+
dirs.push(dir);
|
|
83
|
+
if (existsSync(join(dir, '.git'))) break;
|
|
84
|
+
const parent = dirname(dir);
|
|
85
|
+
if (parent === dir) break;
|
|
86
|
+
dir = parent;
|
|
87
|
+
}
|
|
88
|
+
return [...new Set(dirs)];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function findDeviationDoc(cssFile, packageDir) {
|
|
92
|
+
for (const dir of candidateDirs(cssFile, packageDir)) {
|
|
93
|
+
for (const name of DEVIATION_DOC_NAMES) {
|
|
94
|
+
const p = join(dir, name);
|
|
95
|
+
if (existsSync(p)) return p;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const deviationDocPath = findDeviationDoc(CSS_FILE, PACKAGE_DIR);
|
|
102
|
+
const deviationDocText = deviationDocPath ? readFileSync(deviationDocPath, 'utf8') : '';
|
|
103
|
+
|
|
104
|
+
function isDocumented(tokenName) {
|
|
105
|
+
if (!deviationDocPath) return false;
|
|
106
|
+
return deviationDocText.includes(`--${tokenName}`) || deviationDocText.includes(tokenName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ── Parse the consumer stylesheet ─────────────────────────────────── */
|
|
110
|
+
|
|
111
|
+
const blocks = extractBlocks(readFileSync(CSS_FILE, 'utf8'));
|
|
112
|
+
const lightDecls = declsOf(blocks, isLightRoot);
|
|
113
|
+
const darkOverrides = declsOf(blocks, isDarkBlock);
|
|
114
|
+
|
|
115
|
+
const norm = (v) => String(v).trim().replace(/\s+/g, ' ');
|
|
116
|
+
|
|
117
|
+
/* ── Classify ───────────────────────────────────────────────────────── */
|
|
118
|
+
|
|
119
|
+
const fails = [];
|
|
120
|
+
const warns = [];
|
|
121
|
+
const infos = [];
|
|
122
|
+
|
|
123
|
+
for (const [name, tok] of Object.entries(tokens.core || {})) {
|
|
124
|
+
const canonical = norm(tok.$value);
|
|
125
|
+
const declaredLight = Object.prototype.hasOwnProperty.call(lightDecls, name) ? lightDecls[name] : undefined;
|
|
126
|
+
const declaredDark = Object.prototype.hasOwnProperty.call(darkOverrides, name) ? darkOverrides[name] : undefined;
|
|
127
|
+
if (declaredLight === undefined && declaredDark === undefined) continue; // (c) no-op — resolves from the package import
|
|
128
|
+
|
|
129
|
+
const mismatches = [];
|
|
130
|
+
if (declaredLight !== undefined && norm(declaredLight) !== canonical) mismatches.push({ theme: 'light', value: declaredLight });
|
|
131
|
+
if (declaredDark !== undefined && norm(declaredDark) !== canonical) mismatches.push({ theme: 'dark', value: declaredDark });
|
|
132
|
+
if (mismatches.length === 0) continue; // declared but byte-identical to core — harmless duplicate, not a fork
|
|
133
|
+
|
|
134
|
+
const diffText = mismatches.map((m) => `${m.theme}: consumer defines "${m.value}"`).join('; ');
|
|
135
|
+
const entry = { name, canonical: tok.$value, mismatches };
|
|
136
|
+
if (isDocumented(name)) {
|
|
137
|
+
entry.doc = deviationDocPath;
|
|
138
|
+
warns.push(entry);
|
|
139
|
+
} else {
|
|
140
|
+
fails.push(entry);
|
|
141
|
+
}
|
|
142
|
+
void diffText;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const [name, tok] of Object.entries(tokens.palette || {})) {
|
|
146
|
+
const canonicalLight = norm(tok.light?.$value);
|
|
147
|
+
const canonicalDark = norm(tok.dark?.$value ?? tok.light?.$value);
|
|
148
|
+
const declaredLight = Object.prototype.hasOwnProperty.call(lightDecls, name) ? lightDecls[name] : undefined;
|
|
149
|
+
const declaredDarkRaw = Object.prototype.hasOwnProperty.call(darkOverrides, name) ? darkOverrides[name] : undefined;
|
|
150
|
+
if (declaredLight === undefined && declaredDarkRaw === undefined) continue; // not touched by the consumer at all
|
|
151
|
+
|
|
152
|
+
// CSS cascade: an undeclared dark value inherits the consumer's own light value.
|
|
153
|
+
const effectiveDark = declaredDarkRaw !== undefined ? declaredDarkRaw : declaredLight;
|
|
154
|
+
|
|
155
|
+
const diffs = [];
|
|
156
|
+
if (declaredLight !== undefined && norm(declaredLight) !== canonicalLight) diffs.push({ theme: 'light', value: declaredLight });
|
|
157
|
+
if (effectiveDark !== undefined && norm(effectiveDark) !== canonicalDark) diffs.push({ theme: 'dark', value: effectiveDark });
|
|
158
|
+
if (diffs.length === 0) continue; // declared, but matches the package default — no divergence to report
|
|
159
|
+
|
|
160
|
+
infos.push({ name, canonicalLight: tok.light?.$value, canonicalDark: tok.dark?.$value ?? tok.light?.$value, diffs });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ── Report ─────────────────────────────────────────────────────────── */
|
|
164
|
+
|
|
165
|
+
console.log('remarque-drift — token drift check');
|
|
166
|
+
console.log(` consumer stylesheet: ${CSS_FILE}`);
|
|
167
|
+
console.log(` installed remarque-tokens: ${installedVersion} (${PKG_ROOT})`);
|
|
168
|
+
console.log(` deviation doc: ${deviationDocPath || '(none found — DESIGN-DEVIATIONS.md / DESIGN-NOTES.md)'}`);
|
|
169
|
+
|
|
170
|
+
if (fails.length) {
|
|
171
|
+
console.log('\nFAIL — undocumented core-tier override(s) (this consumer has forked Remarque):');
|
|
172
|
+
for (const f of fails) {
|
|
173
|
+
for (const m of f.mismatches) {
|
|
174
|
+
console.log(` ✗ --${f.name} (${m.theme}): core ships "${f.canonical}", consumer defines "${m.value}"`);
|
|
175
|
+
}
|
|
176
|
+
console.log(` fix: revert to the core value, or document the deviation in DESIGN-DEVIATIONS.md / DESIGN-NOTES.md and mention "--${f.name}"`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (warns.length) {
|
|
181
|
+
console.log('\nWARN — documented core-tier deviation(s) (ratified, not a build failure):');
|
|
182
|
+
for (const w of warns) {
|
|
183
|
+
for (const m of w.mismatches) {
|
|
184
|
+
console.log(` ⚠ --${w.name} (${m.theme}): core ships "${w.canonical}", consumer defines "${m.value}" — see ${w.doc}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (infos.length) {
|
|
190
|
+
console.log('\nINFO — palette-tier divergence(s) (sanctioned personalization):');
|
|
191
|
+
for (const i of infos) {
|
|
192
|
+
for (const d of i.diffs) {
|
|
193
|
+
const canonical = d.theme === 'light' ? i.canonicalLight : i.canonicalDark;
|
|
194
|
+
console.log(` ℹ --${i.name} (${d.theme}): package default "${canonical}", consumer defines "${d.value}"`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(`\nSummary: ${fails.length} FAIL, ${warns.length} WARN, ${infos.length} INFO\n`);
|
|
200
|
+
|
|
201
|
+
process.exit(fails.length ? 1 : 0);
|
package/tokens-core.css
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
--tracking-title: -0.015em;
|
|
39
39
|
--tracking-body: 0em;
|
|
40
40
|
--tracking-meta: 0.02em; /* mono metadata gets slight tracking */
|
|
41
|
-
--tracking-caps: 0.06em; /* for
|
|
41
|
+
--tracking-caps: 0.06em; /* letter-spacing for .text-label (true small caps, issue #51) */
|
|
42
42
|
|
|
43
43
|
/* Font weights */
|
|
44
44
|
--weight-regular: 400;
|
|
@@ -149,6 +149,19 @@ html {
|
|
|
149
149
|
font-weight: var(--weight-medium);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/* Optical/wrapping defaults for display-scale headings (issue #51):
|
|
153
|
+
balanced line breaks, optical sizing on variable display faces, and
|
|
154
|
+
no synthesized bold/italic for faces that lack a real one. All three
|
|
155
|
+
degrade safely — unsupported browsers silently fall back to normal
|
|
156
|
+
wrapping and the font's default rendering. */
|
|
157
|
+
.text-display,
|
|
158
|
+
.text-title,
|
|
159
|
+
.text-section {
|
|
160
|
+
text-wrap: balance;
|
|
161
|
+
font-optical-sizing: auto;
|
|
162
|
+
font-synthesis: none;
|
|
163
|
+
}
|
|
164
|
+
|
|
152
165
|
.text-body {
|
|
153
166
|
font-family: var(--font-body);
|
|
154
167
|
font-size: var(--text-body);
|
|
@@ -172,6 +185,25 @@ html {
|
|
|
172
185
|
letter-spacing: var(--tracking-meta);
|
|
173
186
|
font-weight: var(--weight-regular);
|
|
174
187
|
color: var(--color-muted);
|
|
188
|
+
/* Tabular lining numerals — dates/counts/timestamps align in a
|
|
189
|
+
column and don't jitter as digits change. Mirrored by
|
|
190
|
+
.remarque-prose's oldstyle proportional register below; the two
|
|
191
|
+
never mix (issue #51). */
|
|
192
|
+
font-variant-numeric: tabular-nums lining-nums;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* True small caps for labels (issue #51) — font-variant-caps renders at
|
|
196
|
+
x-height with corrected weight/spacing; text-transform: uppercase
|
|
197
|
+
renders letters at cap-height with no optical correction and reads
|
|
198
|
+
louder than intended. Prefer .text-label over uppercase text
|
|
199
|
+
anywhere a label needs a caps-like treatment. */
|
|
200
|
+
.text-label {
|
|
201
|
+
font-family: var(--font-mono);
|
|
202
|
+
font-size: var(--text-meta);
|
|
203
|
+
line-height: var(--leading-meta);
|
|
204
|
+
font-variant-caps: all-small-caps;
|
|
205
|
+
letter-spacing: var(--tracking-caps);
|
|
206
|
+
color: var(--color-muted);
|
|
175
207
|
}
|
|
176
208
|
|
|
177
209
|
/* ─── Content Width Utilities ───────────────────────────── */
|
package/tokens.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$description": "Remarque design tokens — GENERATED from tokens-core.css + tokens-palette.css by scripts/tokens-json.mjs. Do not edit; the CSS is the source of truth.",
|
|
3
3
|
"$extensions": {
|
|
4
4
|
"remarque": {
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.8.0",
|
|
6
6
|
"tiers": {
|
|
7
7
|
"core": "immutable identity — overriding forks the system",
|
|
8
8
|
"palette": "sanctioned personalization surface — override freely, then run remarque-audit"
|