remarque-tokens 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/REMARQUE.md +44 -1
- package/package.json +1 -1
- package/prose.css +26 -0
- package/scripts/audit.mjs +1 -0
- package/scripts/lib/css-tokens.mjs +10 -4
- package/tokens-core.css +2 -2
- package/tokens-palette.css +8 -0
- package/tokens.json +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,36 @@ 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.6.1 — 2026-07-21
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
Three audit/parser bugs from real-world adoption (#67, found by tsundoku):
|
|
11
|
+
- Body-less at-statements (`@import`, `@charset`) preceding a block no
|
|
12
|
+
longer pollute its prelude and misclassify it as an at-rule — palette
|
|
13
|
+
files may begin with imports.
|
|
14
|
+
- Selector classification accepts qualified root forms (`html:root`,
|
|
15
|
+
`html[data-theme="dark"]`, `body.dark`, …) instead of exact strings.
|
|
16
|
+
- `--src` scans now strip HTML `<!-- -->` comments (line-numbers
|
|
17
|
+
preserved) like CSS comments, ending issue-number false positives in
|
|
18
|
+
.astro/.svelte markup.
|
|
19
|
+
|
|
20
|
+
Two new convention fixtures (leading-import, qualified-selectors) lock
|
|
21
|
+
the fixes in CI. Consumers can drop their documented workarounds.
|
|
22
|
+
|
|
23
|
+
## 0.6.0 — 2026-07-20
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **`--weight-display`** (palette tier, theme-conditional): display faces
|
|
27
|
+
render at 400 in light mode and 500 in dark — light-on-dark text is
|
|
28
|
+
optically thinner (halation), and high-contrast serif hairlines were
|
|
29
|
+
disappearing at display sizes on dark backgrounds (#59).
|
|
30
|
+
`.text-display`/`.text-title` consume it with a 400 fallback, so
|
|
31
|
+
palettes that don't define it are unaffected.
|
|
32
|
+
- **The remarque mark** (`.remarque-endmark` in prose.css): the system's
|
|
33
|
+
namesake made visible — a small muted fleuron closing every essay.
|
|
34
|
+
One per essay, aria-hidden, deliberately outside the latin subsets so
|
|
35
|
+
it falls to the serif fallback stack.
|
|
36
|
+
|
|
7
37
|
## 0.5.1 — 2026-07-20
|
|
8
38
|
|
|
9
39
|
Two `remarque-audit` fixes found by the flagship site's adoption (its
|
package/REMARQUE.md
CHANGED
|
@@ -222,6 +222,48 @@ Agents reviewing PRs should reject changes that violate any line above without e
|
|
|
222
222
|
|
|
223
223
|
---
|
|
224
224
|
|
|
225
|
+
## Lineage: Butterick's Five Rules
|
|
226
|
+
|
|
227
|
+
Remarque's core values descend from Matthew Butterick's [*Typography in
|
|
228
|
+
Ten Minutes*](https://practicaltypography.com/typography-in-ten-minutes.html)
|
|
229
|
+
(an acknowledged inspiration). The mapping, including where and why we
|
|
230
|
+
deviate — values below are asserted against `tokens.json` in CI:
|
|
231
|
+
|
|
232
|
+
| Butterick rule | His guidance | Remarque | Status |
|
|
233
|
+
|---|---|---|---|
|
|
234
|
+
| 1. Body text first | Typographic quality is determined largely by body text | AGENT_RULES.md Build Order steps 1–4 are tokens → typography → shell → prose, before any component | Aligned by construction |
|
|
235
|
+
| 2. Point size | 15–25 px on the web | `--text-body: 1.0625rem` (17px); lead paragraphs `--text-body-lg: 1.1875rem` (19px) | Aligned |
|
|
236
|
+
| 3. Line spacing | 120–145% of point size | `--leading-body: 1.75` (175%) | **Deliberate deviation** — see below |
|
|
237
|
+
| 4. Line length | 45–90 characters per line | `--content-reading: 46rem` ≈ 70 ch at 17px Inter; the measure-compensation table targets 66–72 ch for every approved body face | Aligned |
|
|
238
|
+
| 5. Font choice | Professional fonts, never default system faces | Newsreader (Production Type), Inter (Rasmus Andersson), JetBrains Mono — professionally designed, self-hosted | Aligned in spirit: these are free but professional-grade faces; Butterick's stricter preference is paid fonts |
|
|
239
|
+
|
|
240
|
+
### The line-spacing deviation, argued
|
|
241
|
+
|
|
242
|
+
Remarque's 175% body leading sits **outside Butterick's 120–145% range,
|
|
243
|
+
at the airy extreme of quality web practice (commonly 150–165%)**. We
|
|
244
|
+
own that plainly rather than argue it away. The reasons it is a
|
|
245
|
+
deliberate identity choice, not an oversight:
|
|
246
|
+
|
|
247
|
+
1. **Leading scales with measure.** Classic guidance ties looser leading
|
|
248
|
+
to longer lines; Remarque's ~70 ch measure sits in the upper-middle
|
|
249
|
+
of Butterick's own 45–90 ch range, and screen prose at 17px benefits
|
|
250
|
+
from more air than his print-derived band.
|
|
251
|
+
2. **Token-hierarchy coherence.** Literal conformance (≤145%) would put
|
|
252
|
+
body leading *below* `--leading-meta: 1.5` — which is pinned by the
|
|
253
|
+
USWDS/WCAG floors this spec also adopts. Body text reading tighter
|
|
254
|
+
than caption text is not a hierarchy this system will ship.
|
|
255
|
+
3. **Identity.** "Generous — this is what makes prose readable" predates
|
|
256
|
+
this audit; the spacious register is part of what makes a Remarque
|
|
257
|
+
page recognizable (see Signature Moves).
|
|
258
|
+
|
|
259
|
+
The trade-off is real: 1.75 spends more vertical space and reads calmer
|
|
260
|
+
and slower than 1.5–1.65. Sites wanting Butterick-tighter rhythm are
|
|
261
|
+
choosing a different identity — that is a fork, and an honorable one,
|
|
262
|
+
but it is not Remarque. (Ratified 3–0 by design-review panel, 2026-07-20,
|
|
263
|
+
over the alternative of changing the token.)
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
225
267
|
## Decision Order
|
|
226
268
|
|
|
227
269
|
When multiple design options are possible, choose in this order:
|
|
@@ -324,7 +366,8 @@ The homepage or entry point. Sets the tone.
|
|
|
324
366
|
|
|
325
367
|
These are the repeatable visual tells that make a Remarque site recognizable:
|
|
326
368
|
|
|
327
|
-
-
|
|
369
|
+
- The remarque mark: a small muted fleuron closing every essay (`.remarque-endmark`) — the namesake made visible, one per essay, never decorated further
|
|
370
|
+
- Serif hero/page titles (Newsreader at display scale; display weight bumps one step in dark mode to keep serif hairlines alive)
|
|
328
371
|
- Mono metadata rows beneath titles
|
|
329
372
|
- Constrained body width with generous margins
|
|
330
373
|
- Subtle `1px` section dividers using `--color-border`
|
package/package.json
CHANGED
package/prose.css
CHANGED
|
@@ -137,3 +137,29 @@
|
|
|
137
137
|
margin-top: var(--space-2);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
|
|
141
|
+
/* ─── The Remarque Mark ─────────────────────────────────── */
|
|
142
|
+
/*
|
|
143
|
+
* The system's namesake, made visible: a small mark closing every
|
|
144
|
+
* essay, the way a printer's fleuron ends a chapter. Quiet by rule —
|
|
145
|
+
* muted color, meta scale, one per essay, nothing else on its line.
|
|
146
|
+
*
|
|
147
|
+
* Markup (aria-hidden — it is punctuation, not content):
|
|
148
|
+
* <div class="remarque-endmark" aria-hidden="true"></div>
|
|
149
|
+
* placed after the last element inside .remarque-prose.
|
|
150
|
+
*
|
|
151
|
+
* The fleuron (U+2766) is outside the latin font subsets by design;
|
|
152
|
+
* it intentionally falls through to the serif fallback stack.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
.remarque-endmark {
|
|
156
|
+
text-align: center;
|
|
157
|
+
margin-top: var(--space-7);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.remarque-endmark::after {
|
|
161
|
+
content: "\2766";
|
|
162
|
+
font-family: var(--font-display);
|
|
163
|
+
font-size: var(--text-body);
|
|
164
|
+
color: var(--color-muted);
|
|
165
|
+
}
|
package/scripts/audit.mjs
CHANGED
|
@@ -165,6 +165,7 @@ for (const file of walk(SRC)) {
|
|
|
165
165
|
// prose like "(issue #324)" in a comment trips the hex-color regex.
|
|
166
166
|
const text = readFileSync(file, 'utf8')
|
|
167
167
|
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '))
|
|
168
|
+
.replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, ' '))
|
|
168
169
|
.replace(/(^|[^:])\/\/[^\n]*/g, (m, p1) => p1 + ' '.repeat(m.length - p1.length));
|
|
169
170
|
const lines = text.split('\n');
|
|
170
171
|
lines.forEach((line, i) => {
|
|
@@ -16,7 +16,10 @@ export function extractBlocks(css) {
|
|
|
16
16
|
while (i < text.length) {
|
|
17
17
|
const open = text.indexOf('{', i);
|
|
18
18
|
if (open === -1) break;
|
|
19
|
-
|
|
19
|
+
// Body-less at-statements (@import 'x'; @charset ...;) preceding a block
|
|
20
|
+
// would otherwise pollute its prelude and misclassify it as an at-rule
|
|
21
|
+
// (#67): keep only the segment after the last ';'.
|
|
22
|
+
const prelude = text.slice(i, open).split(';').pop().trim();
|
|
20
23
|
let depth = 1, j = open + 1;
|
|
21
24
|
while (j < text.length && depth > 0) {
|
|
22
25
|
if (text[j] === '{') depth++;
|
|
@@ -49,12 +52,15 @@ export function declsOf(blocks, filterFn) {
|
|
|
49
52
|
dark block in the class convention) as light. */
|
|
50
53
|
const parts = (prelude) => prelude.split(',').map((s) => s.trim());
|
|
51
54
|
|
|
55
|
+
const DARKISH = /(\.dark\b|\[data-theme="dark"\])/;
|
|
56
|
+
const ROOTISH = /^(:root|html|body)\b/;
|
|
52
57
|
export const isLightRoot = (b) =>
|
|
53
|
-
b.context === '' && parts(b.prelude).some((s) =>
|
|
58
|
+
b.context === '' && parts(b.prelude).some((s) =>
|
|
59
|
+
(ROOTISH.test(s) && !DARKISH.test(s)) || s === '[data-theme="light"]');
|
|
54
60
|
|
|
55
61
|
/* Dark: media-query :root, the canonical [data-theme="dark"], or the
|
|
56
62
|
class-convention :root.dark / html.dark (compatibility bridge). */
|
|
57
63
|
export const isDarkBlock = (b) =>
|
|
58
64
|
(b.context.includes('prefers-color-scheme') && b.context.includes('dark') &&
|
|
59
|
-
parts(b.prelude).some((s) => s
|
|
60
|
-
parts(b.prelude).some((s) => s
|
|
65
|
+
parts(b.prelude).some((s) => ROOTISH.test(s) && !DARKISH.test(s))) ||
|
|
66
|
+
parts(b.prelude).some((s) => DARKISH.test(s) && (ROOTISH.test(s) || s.startsWith('.') || s.startsWith('[')));
|
package/tokens-core.css
CHANGED
|
@@ -130,7 +130,7 @@ html {
|
|
|
130
130
|
font-size: var(--text-display);
|
|
131
131
|
line-height: var(--leading-display);
|
|
132
132
|
letter-spacing: var(--tracking-display);
|
|
133
|
-
font-weight: var(--weight-regular);
|
|
133
|
+
font-weight: var(--weight-display, var(--weight-regular));
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
.text-title {
|
|
@@ -138,7 +138,7 @@ html {
|
|
|
138
138
|
font-size: var(--text-title);
|
|
139
139
|
line-height: var(--leading-title);
|
|
140
140
|
letter-spacing: var(--tracking-title);
|
|
141
|
-
font-weight: var(--weight-regular);
|
|
141
|
+
font-weight: var(--weight-display, var(--weight-regular));
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
.text-section {
|
package/tokens-palette.css
CHANGED
|
@@ -34,6 +34,12 @@
|
|
|
34
34
|
/* Reading measure — tuned to the body font (see REMARQUE.md measure table).
|
|
35
35
|
46rem ≈ 70 characters per line at 17px Inter. */
|
|
36
36
|
--content-reading: 46rem;
|
|
37
|
+
|
|
38
|
+
/* Display weight — palette-tier because it is theme-conditional:
|
|
39
|
+
light-on-dark text renders optically thinner (halation), so dark
|
|
40
|
+
mode bumps display faces one step to keep serif hairlines alive.
|
|
41
|
+
(issue #59) */
|
|
42
|
+
--weight-display: 400;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/* ─── Color: Light theme (default) ──────────────────────── */
|
|
@@ -89,6 +95,7 @@
|
|
|
89
95
|
--color-bg: oklch(0.16 0.01 80); /* warm charcoal, not black */
|
|
90
96
|
--color-bg-subtle: oklch(0.19 0.01 80);
|
|
91
97
|
--color-fg: oklch(0.90 0.005 80); /* soft white, not pure */
|
|
98
|
+
--weight-display: 500; /* compensate thin serif hairlines on dark */
|
|
92
99
|
--color-fg-muted: oklch(0.70 0.01 80); /* 7.26:1 bg (AAA) */
|
|
93
100
|
--color-muted: oklch(0.60 0.01 80); /* 4.92:1 bg / 4.68:1 surface (AA with margin) */
|
|
94
101
|
--color-border: oklch(0.25 0.005 80); /* decorative only — see light-theme note */
|
|
@@ -116,6 +123,7 @@
|
|
|
116
123
|
light-dark(), which obsoletes both selectors. */
|
|
117
124
|
[data-theme="dark"],
|
|
118
125
|
:root.dark {
|
|
126
|
+
--weight-display: 500;
|
|
119
127
|
--color-bg: oklch(0.16 0.01 80);
|
|
120
128
|
--color-bg-subtle: oklch(0.19 0.01 80);
|
|
121
129
|
--color-fg: oklch(0.90 0.005 80);
|
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.6.1",
|
|
6
6
|
"tiers": {
|
|
7
7
|
"core": "immutable identity — overriding forks the system",
|
|
8
8
|
"palette": "sanctioned personalization surface — override freely, then run remarque-audit"
|
|
@@ -236,6 +236,15 @@
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
},
|
|
239
|
+
"weight-display": {
|
|
240
|
+
"$type": "fontWeight",
|
|
241
|
+
"light": {
|
|
242
|
+
"$value": 400
|
|
243
|
+
},
|
|
244
|
+
"dark": {
|
|
245
|
+
"$value": 500
|
|
246
|
+
}
|
|
247
|
+
},
|
|
239
248
|
"color-bg": {
|
|
240
249
|
"$type": "color",
|
|
241
250
|
"light": {
|