reviewzy 0.2.0 → 0.3.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.
@@ -1,8 +1,11 @@
1
1
  /* ============================================================
2
- reviewzy — design tokens
2
+ design tokens
3
+ Dark by default, and still full of light.
4
+ Neutrals hold hue 280 (Catppuccin lavender haze), never grey.
5
+ Every ratio in the comments measured, not estimated.
3
6
  ============================================================ */
4
7
 
5
- /* Self-hosted variable fonts — ship the woff2 subsets in fonts/ alongside these
8
+ /* Self-hosted variable fonts - ship the woff2 subsets in fonts/ alongside these
6
9
  @font-face blocks. No CDN dependency; faster, private, offline-safe. Subsets:
7
10
  latin / latin-ext / cyrillic. Subset your own with fonttools/glyphhanger. */
8
11
  @font-face {
@@ -45,38 +48,226 @@
45
48
  src: url("fonts/jetbrainsmono-latin-ext.woff2") format("woff2");
46
49
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
47
50
  }
48
-
49
51
  :root {
50
- /* Accents */
51
- --accent: #43ABE5;
52
- --accent-hover: #3FA9CC;
53
- --accent-press: #2E93B5;
54
- --accent-soft: color-mix(in srgb, #43ABE5 10%, transparent);
55
- --accent-ring: color-mix(in srgb, #43ABE5 42%, transparent);
56
-
57
- --accent-2: #D97757;
58
- --accent-2-soft: color-mix(in srgb, #D97757 18%, transparent);
59
-
60
- --info: #74C7EC;
61
- --success: #A6E3A1;
62
- --warning: #F9E2AF;
63
- --danger: #F38BA8;
64
-
65
- /* Typography */
52
+ color-scheme: dark;
53
+
54
+ /* ---- Surfaces. The page is NOT the darkest thing possible;
55
+ raising the floor is what makes a dark theme feel airy.
56
+ Surfaces rise TOWARD the light, wells sink below. ---- */
57
+ --well: oklch(0.215 0.020 280); /* inputs, code, tracks */
58
+ --bg: oklch(0.255 0.024 280); /* the page */
59
+ --surface-1: oklch(0.300 0.024 280); /* cards, menus */
60
+ --surface-2: oklch(0.345 0.026 280); /* hover, nested */
61
+ --surface-3: oklch(0.395 0.028 280); /* pressed, selected */
62
+
63
+ /* The system draws its own scrollbar, so the thumb is a graphic this file owns
64
+ and it clears 3:1 on every ground a scroller has: 5.20:1 on --well, 4.68 on
65
+ --bg, 4.05 on --surface-1, 3.43 on a --surface-2 hover tint. --surface-3, the
66
+ colour the retired native rule used, measures 1.67:1 on --bg. */
67
+ --scrollbar-thumb: oklch(0.640 0.028 280);
68
+
69
+ /* Lines are a fallback, not the separation device */
70
+ --line: oklch(0.345 0.026 280);
71
+ --line-soft: oklch(0.310 0.024 280);
72
+
73
+ /* ---- Elevation. A lit top edge plus a dark contact shadow, both in-hue.
74
+ Never rgb(0 0 0): black shadow on lavender goes muddy.
75
+
76
+ A DARK PAGE HAS NO ROOM FOR A WIDE SOFT SHADOW. The page sits 46/255 on
77
+ the blue channel, so a shadow has 46 levels of headroom, and a wide blur
78
+ spends most of them below the quantization floor. The final band, where
79
+ the shadow paints exactly 1 level, is then wide, flat and bounded by a
80
+ clean closed curve: that curve is what reads as a ring drawn around the
81
+ box rather than as light. It widens with the outermost blur radius and
82
+ with how faint the shadow is. Measured beside a card: 28px of blur
83
+ peaking 5 levels deep drew 15px of ring, 40px peaking 10 drew 14px, and
84
+ 14px peaking 11 drew 4px. Every blur here stays at or under 22px, every
85
+ coat lands with real depth, which is also what a physical shadow does:
86
+ dark where the object meets the surface, gone a short way out.
87
+
88
+ Measured on a 300x140 card, blue channel against --bg, deepest pixel and
89
+ the width of that last 1-level band:
90
+ --elev-1 24 levels under, 12 beside; band 5px under, 5px beside
91
+ --elev-2 23 / 12; band 7 / 6
92
+ --elev-float 34 / 18; band 8 / 6
93
+ The shipped v1 set measured 9 / 5 with a 15px band, which is the ring.
94
+
95
+ THE EDGE IS WHAT THE EYE READS AS THE CARD STARTING, not the tone step.
96
+ A card sits .045 L over the page and .065 is not available: the fourth
97
+ text step falls to 4.40:1 on it. So separation is bought on the edge and
98
+ the contact instead, where no colour moves and no ratio re-derives. */
99
+ --lit-1: inset 0 1px 0 0 oklch(1 0 0 / 0.085);
100
+ --lit-2: inset 0 1px 0 0 oklch(1 0 0 / 0.100);
101
+ --elev-1: var(--lit-1),
102
+ 0 1px 2px -1px oklch(0.11 0.025 280 / 0.72),
103
+ 0 3px 6px -2px oklch(0.11 0.025 280 / 0.68),
104
+ 0 8px 16px -6px oklch(0.11 0.025 280 / 0.64);
105
+ --elev-2: var(--lit-2),
106
+ 0 1px 2px -1px oklch(0.11 0.025 280 / 0.66),
107
+ 0 4px 8px -2px oklch(0.11 0.025 280 / 0.62),
108
+ 0 10px 18px -8px oklch(0.11 0.025 280 / 0.58);
109
+ --elev-float: var(--lit-2),
110
+ 0 2px 4px -1px oklch(0.09 0.022 280 / 0.72),
111
+ 0 6px 12px -3px oklch(0.09 0.022 280 / 0.68),
112
+ 0 14px 22px -10px oklch(0.09 0.022 280 / 0.66);
113
+ --backdrop: oklch(0.16 0.02 280 / 0.66);
114
+
115
+ /* ---- Glass. Real background blur, on the surfaces something passes behind:
116
+ the mobile topbar, the stuck section header, the floating panels, and a
117
+ panel over the scrim. The navbar is opaque in both of its states. The scrim
118
+ itself stays a flat tint.
119
+
120
+ NEITHER THE ALPHA NOR THE BAR TONE IS TASTE. A glass surface takes on part
121
+ of whatever is behind it, and on a dark page that is nearly always dark, so
122
+ a surface tinted with --surface-1 reads as an opaque slab whatever the blur
123
+ (the 0.90 first material moved 22 levels over a white plate and two or
124
+ three over real content: every panel read as "not blurred"). Bars therefore
125
+ tint with --bg: a bar is the page held still, and content passing under it
126
+ is the only thing that says a surface is there. Panels keep the raised
127
+ tone at the clearest alpha the dim ramp allows: --text-2 holds 4.67:1 on
128
+ the worst case at 0.86 and fails at 0.82 (4.07, round 1), so 0.86 is the
129
+ floor. On that worst case, a white plate composited through the panel,
130
+ --text holds 6.48:1, --glass-dim 4.67, --glass-danger 4.62. The panel
131
+ moves 35.7 sRGB levels black to white, the measured (1 - alpha) * 255
132
+ identity confirmed on a plate. Filter chains do not buy the floor back:
133
+ crushing the backdrop enough to protect it crushes the blur with it,
134
+ measured across nine chains. ---- */
135
+ --glass-bar-tint: oklch(0.255 0.024 280 / 0.90);
136
+ /* the bar carries only --text and --text-2: 6.21:1 for text-2 on the bar's
137
+ worst case (a white plate through 0.90 of the page colour), no re-floor */
138
+ --glass-tint: oklch(0.300 0.024 280 / 0.86);
139
+ --glass-blur: 22px;
140
+ --glass-sat: 1.25;
141
+ --glass-dim: oklch(0.800 0.030 280); /* --text-3 and --text-4 on a panel:
142
+ 4.67:1 on the worst case; unlifted, .720 / .680 fall to 3.52 / 3.03 */
143
+ --glass-danger: oklch(0.84 0.140 15); /* 4.62:1 on the panel worst case.
144
+ --danger is 3.31:1 there, and the round-1 value L 0.78 fell to 4.04 at
145
+ 0.86, so the re-floor moves with the alpha */
146
+ --glass-disabled: oklch(0.700 0.030 280); /* 3.27:1 on the panel worst case.
147
+ --text-disabled falls to 2.49 there, and the opacity: 0.45 costume this
148
+ replaces measured 2.17, which is what the panel had been shipping */
149
+ /* A translucent tint over a translucent surface composites twice and drifts 21
150
+ levels with the backdrop: --accent-text on it measures 4.40:1. The same mix
151
+ laid down opaque holds one tone and 5.95:1. */
152
+ --glass-quiet: color-mix(in oklab, var(--accent) 16%, var(--surface-1));
153
+ /* The one place a drawn line beats the lit edge. A glass panel has no reliable
154
+ tone difference from its backdrop, since it takes that backdrop on, so the
155
+ edge is what says where the pane starts. Still a hairline. */
156
+ --glass-edge: oklch(1 0 0 / 0.10);
157
+ /* a dimmed icon is a graphic against the surface it sits on: on the dark
158
+ glass panel 0.85 leaves a text-3 icon at 2.99:1, under the 3:1 floor, so
159
+ the glass block lifts this to 0.90: 3.15 dark and 3.89 light for the
160
+ dimmest icons on the panel worst cases */
161
+ --icon-dim: 0.7;
162
+
163
+ /* ---- Text. Nothing is pure white; purity reads as hard. ---- */
164
+ --text: oklch(0.900 0.030 280); /* 11.70:1 on --bg */
165
+ --text-2: oklch(0.800 0.030 280); /* 8.43:1 */
166
+ --text-3: oklch(0.720 0.030 280); /* 6.35:1 */
167
+ --text-4: oklch(0.680 0.030 280); /* 5.47:1, the floor */
168
+ /* the hint inside a control. One step under --text-4 so a placeholder does
169
+ not read as a typed value or as metadata: 4.99:1 on --well. */
170
+ --text-placeholder: oklch(0.630 0.030 280);
171
+ /* a control that cannot act, her call 2026-09-02, replacing the opacity: 0.45
172
+ ten declarations across twelve component classes rode. It takes the 3:1 graphics floor rather than the 4.5:1
173
+ text floor, so it can sit a real step under --text-4: 4.99:1 on --well, 4.50
174
+ on --bg, 3.89 on --surface-1, 3.29 on the --surface-2 a disabled fill takes.
175
+ Dark shares the placeholder's value on purpose, since both say "present, not
176
+ live" and this ramp has exactly one step down there. Light does not: its
177
+ placeholder is pinned to --text-4 by the text floor, and a disabled label
178
+ pinned with it would read as enabled metadata, which is the whole defect. */
179
+ --text-disabled: oklch(0.630 0.030 280);
180
+
181
+ /* ---- Accent: Catppuccin sapphire. The break in the overcast. ---- */
182
+ --accent: oklch(0.76 0.110 238); /* 7.49:1 as text on --bg */
183
+ --accent-hover: oklch(0.82 0.115 238); /* brighter in dark */
184
+ --accent-press: oklch(0.70 0.105 238);
185
+ --accent-text: oklch(0.82 0.115 238); /* 9.15:1 as type, the only accent as text */
186
+ --accent-quiet: oklch(0.76 0.110 238 / 0.16);
187
+ --accent-line: oklch(0.76 0.110 238 / 0.38);
188
+ --accent-glow: 0 0 10px 0 oklch(0.76 0.110 238 / 0.55);
189
+ --on-accent: oklch(0.22 0.020 280); /* 8.22:1 on the fill */
190
+
191
+ --accent-2: oklch(0.72 0.130 42); /* editorial, 6.09:1, once per screen */
192
+ --on-accent-2: oklch(0.22 0.020 42); /* 6.69:1 on the fill */
193
+
194
+ /* ---- Status. Levelled under --text so no pill outshouts a title. ---- */
195
+ --success: oklch(0.80 0.130 150); /* 8.89:1 */
196
+ --warning: oklch(0.84 0.110 90); /* 9.68:1 */
197
+ --danger: oklch(0.72 0.140 15); /* 5.98:1 */
198
+ --on-success: oklch(0.22 0.02 150); /* 9.68:1 on the fill */
199
+ --on-warning: oklch(0.22 0.02 90); /* 10.59:1 on the fill */
200
+ --on-danger: oklch(0.22 0.02 15); /* 6.58:1 on the fill */
201
+ --info: var(--accent); /* folded in: it was the same hue */
202
+
203
+ /* ---- Chart series. Four slots; the ORDER is the CVD mechanism, and what it
204
+ buys is that each dichromat's CLOSEST pair never lands on adjacent slots.
205
+ Simulated (Vienot, Brettel & Mollon 1999) and measured as oklab distance:
206
+ protan's closest pair is 1-3 at 0.0355 with orange sitting between them,
207
+ deutan's is 2-4 at 0.0516 with mauve sitting between them, and the closest
208
+ pair a legend does put side by side is 3-4 at 0.1666 / 0.1419. A fifth
209
+ series is a legend problem, not a palette problem, and yellow and red never
210
+ join a series: they read as status.
211
+
212
+ THOSE TWO ARE THE ONLY DICHROMACIES THIS RAMP IS MEASURED AGAINST. Vienot
213
+ publishes a projection plane for protan and deutan only; the tritan row that
214
+ circulates with them moved an equal-lightness blue/yellow pair from 0.2630
215
+ APART to 0.3601, so it cannot be a tritan simulation and no number taken
216
+ from it belongs here. Until one is modelled, treat a two-series chart as
217
+ needing direct labels rather than a legend.
218
+
219
+ Slot 3 is mauve rather than the sky it shipped as. Sky measured 0.0955 from
220
+ sapphire in normal vision, 2.5x closer than any other pair, and the two read
221
+ as one series; mauve puts the ramp's worst normal-vision pair at 0.1411.
222
+ Measured on --surface-1 (WCAG 1.4.11, 3:1 graphics floor):
223
+ 5.21 / 5.27 / 6.62 / 6.45:1. Re-measure if --surface-1 moves. ---- */
224
+ --chart-series-1: oklch(0.70 0.110 238); /* sapphire */
225
+ --chart-series-2: oklch(0.72 0.130 42); /* orange */
226
+ --chart-series-3: oklch(0.78 0.100 305); /* mauve */
227
+ --chart-series-4: oklch(0.75 0.120 150); /* green */
228
+
229
+ /* ---- Radius. Softer than v1's 1px, which rendered as 0. ---- */
230
+ --r-1: 10px; /* controls */
231
+ --r-2: 14px; /* cards, menus */
232
+ --r-3: 18px; /* modals, sheets */
233
+ --r-pill: 999px;
234
+
235
+ /* ---- Motion. Duration follows distance. ---- */
236
+ --ease: cubic-bezier(0.2, 0, 0, 1); /* state changes, exits */
237
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* entrances, arrivals */
238
+ --dur-tint: 120ms; /* no travel: colour, opacity, border */
239
+ --dur-near: 180ms; /* under 12px: toast, popover, tooltip */
240
+ --dur-far: 260ms; /* a panel's worth: sheet, accordion */
241
+ --dur-cross: 360ms; /* across the viewport: drawer */
242
+ --dur-slide: 440ms; /* sliding indicators only */
243
+ --stagger: 40ms; /* per item, capped at 6 */
244
+ --dur-out: calc(var(--dur-tint) * 0.6); /* exits run at 0.6x */
245
+ --dur-out-near: calc(var(--dur-near) * 0.6);
246
+ --dur-out-far: calc(var(--dur-far) * 0.6);
247
+
248
+ /* ---- Type. 8 distinct steps; 11px and 12px are real tokens. ---- */
249
+ --t-1: 11px; --t-2: 12px; --t-3: 14px; --t-4: 16px;
250
+ --t-5: 20px; --t-6: 26px; --t-7: 34px; --t-8: 46px;
251
+ --caps: 0.09em; /* the one tracking value */
252
+
253
+ /* ---- Controls share one height ladder with inputs ---- */
254
+ --ctl-sm: 30px; --ctl: 36px; --ctl-lg: 44px;
255
+
256
+ /* ---- Layout ---- */
257
+ --page-w: 1200px; /* .page column; one override retunes a build */
258
+ /* prose measure, MEASURED against loaded Onest at --t-4 rather than estimated:
259
+ 528px lands 64.9 and 68.1 characters per line on two samples, 664px lands
260
+ 84.4 and 85.3. A line that runs the full page column is a line nobody
261
+ finishes. Erring short is the safer side of 66. */
262
+ --page-narrow: 528px;
263
+ --navbar-h: 56px; /* the sticky header and the anchor offset both derive from it */
264
+ --topbar-h: 52px; /* the mobile bar; the sticky header pins under this one below 768px */
265
+
266
+ /* ---- Fonts ---- */
66
267
  --font-sans: "Onest", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
67
268
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
68
269
 
69
- /* Type scale */
70
- --text-xs: 12px;
71
- --text-sm: 13px;
72
- --text-base: 14px;
73
- --text-md: 16px;
74
- --text-lg: 18px;
75
- --text-xl: 22px;
76
- --text-2xl: 28px;
77
- --text-3xl: 36px;
78
-
79
- /* Spacing (4px scale) */
270
+ /* ---- Spacing (4px scale) ---- */
80
271
  --space-0: 0;
81
272
  --space-1: 4px;
82
273
  --space-2: 8px;
@@ -89,91 +280,148 @@
89
280
  --space-12: 48px;
90
281
  --space-16: 64px;
91
282
  --space-20: 80px;
283
+ }
92
284
 
93
- /* Radius — hairline 1px is the canonical default; 0 sharp / 6px rounded are opt-in */
94
- --radius: 1px;
95
- --radius-pill: 999px;
285
+ /* Radius preset: sharp is the opt-in variant, real 0 */
286
+ [data-radius="sharp"] { --r-1: 0px; --r-2: 0px; --r-3: 0px; }
96
287
 
97
- /* Elevation — only one shadow exists in the system */
98
- --shadow-float: 0 1px 2px rgb(0 0 0 / 0.08), 0 8px 24px rgb(0 0 0 / 0.24);
288
+ /* ---- Light theme (Latte). EVERY colour and elevation token is
289
+ re-declared, because a partial override is the bug that broke
290
+ v1's light theme: its tints were mixed from the dark accent.
291
+ Ratios measured against THIS block's --bg. ---- */
292
+ [data-theme="light"] {
293
+ color-scheme: light;
99
294
 
100
- /* Motion */
101
- --ease: cubic-bezier(0.2, 0, 0, 1);
102
- --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
103
- --dur-fast: 120ms;
104
- --dur-base: 180ms;
105
- --dur-slow: 240ms;
106
- }
295
+ --surface-3: oklch(0.905 0.010 275); /* pressed: light goes DOWN */
296
+ /* 3.58:1 on --well, 3.92 on --bg, 4.40 on --surface-1, 3.69 on the hover tint */
297
+ --scrollbar-thumb: oklch(0.570 0.010 275);
298
+ --surface-2: oklch(0.935 0.008 275); /* hover */
299
+ --well: oklch(0.925 0.008 275);
300
+ --bg: oklch(0.955 0.006 275);
301
+ --surface-1: oklch(0.995 0.003 275); /* cards still rise */
107
302
 
108
- /* Radius presets — base is 1px hairline; override per project */
109
- [data-radius="sharp"] { --radius: 0px; }
110
- [data-radius="soft"] { --radius: 1px; }
111
- [data-radius="rounded"] { --radius: 6px; }
303
+ --line: oklch(0.890 0.012 275); /* barely there, 1.22:1 */
304
+ --line-soft: oklch(0.920 0.010 275);
112
305
 
113
- /* ---- Dark mode (default) — Mocha ---- */
114
- [data-theme="dark"], :root {
115
- --bg: #1E1E2E;
116
- --bg-raised: #181825;
117
- --bg-sunken: #11111B;
118
- --bg-hover: #282838;
306
+ --text: oklch(0.300 0.020 275); /* 11.98:1 */
307
+ --text-2: oklch(0.420 0.020 275); /* 7.44:1 */
308
+ --text-3: oklch(0.500 0.018 275); /* 5.27:1 */
309
+ --text-4: oklch(0.515 0.016 275); /* 4.94:1 on --bg, 4.52:1 on --well, the floor */
310
+ /* 4.52:1 on --well is the floor already; light has no headroom under it, so
311
+ the placeholder holds --text-4's value and the dark block alone dims. */
312
+ --text-placeholder: oklch(0.515 0.016 275);
313
+ /* the 3:1 floor is what buys the step the 4.5:1 one denies the placeholder:
314
+ 3.23:1 on --surface-3, 3.44 on --well, 3.54 on --surface-2, 3.76 on --bg,
315
+ 4.23 on --surface-1 */
316
+ --text-disabled: oklch(0.580 0.016 275);
119
317
 
120
- --line: #313244;
121
- --line-strong: #45475A;
122
- --line-soft: #26263A;
318
+ --accent: oklch(0.55 0.140 238); /* 4.60:1 under --on-accent; 4.09:1 as text */
319
+ --accent-hover: oklch(0.49 0.135 238);
320
+ --accent-press: oklch(0.44 0.125 238);
321
+ --accent-text: oklch(0.50 0.130 238); /* 5.06:1 as type; accent as text fails 4.5 */
322
+ --accent-quiet: oklch(0.55 0.140 238 / 0.12);
323
+ --accent-line: oklch(0.55 0.140 238 / 0.34);
324
+ --accent-glow: 0 0 10px 0 oklch(0.55 0.140 238 / 0.30);
325
+ --on-accent: oklch(0.995 0.003 238);
123
326
 
124
- --text: #CDD6F4;
125
- --text-muted: #BAC2DE;
126
- --text-dim: #A6ADC8;
127
- --text-faint: #7F849C;
128
- --text-on-accent: #11111B;
129
- }
327
+ --accent-2: oklch(0.52 0.150 42); /* 5.16:1 */
328
+ --on-accent-2: oklch(0.995 0.003 42);
130
329
 
131
- /* ---- Light mode — Latte ---- */
132
- [data-theme="light"] {
133
- --bg: #EFF1F5;
134
- --bg-raised: #FFFFFF;
135
- --bg-sunken: #E6E9EF;
136
- --bg-hover: #DCE0E8;
330
+ --success: oklch(0.50 0.150 150); /* 4.85:1 */
331
+ --warning: oklch(0.50 0.130 90); /* 5.25:1 */
332
+ --danger: oklch(0.52 0.190 15); /* 5.36:1 */
333
+ --on-success: oklch(0.995 0.003 150);
334
+ --on-warning: oklch(0.995 0.003 90);
335
+ --on-danger: oklch(0.995 0.003 15);
336
+ --info: var(--accent);
137
337
 
138
- --line: #CCD0DA;
139
- --line-strong: #BCC0CC;
140
- --line-soft: #E6E9EF;
338
+ /* The lit edge flips to the underside: light falls from above, so
339
+ on a light page the visible edge is the shadow, not the highlight.
340
+ Same geometry as the dark set, so both themes carry one shadow shape and
341
+ only the alphas differ. A light page has 244 levels of headroom, so the
342
+ banding that governs the dark set is not the binding constraint here; the
343
+ geometry is shared because a component must not change depth on a toggle.
344
+ Measured on a 300x140 card, blue channel against --bg, deepest pixel and
345
+ the last 1-level band: --elev-1 33 under / 13 beside, band 4 / 4;
346
+ --elev-2 32 / 14, band 3 / 2; --elev-float 42 / 19, band 5 / 3. */
347
+ --lit-1: inset 0 -1px 0 0 oklch(0.55 0.03 275 / 0.10);
348
+ --lit-2: inset 0 -1px 0 0 oklch(0.55 0.03 275 / 0.12);
349
+ --elev-1: var(--lit-1),
350
+ 0 1px 2px -1px oklch(0.55 0.03 275 / 0.26),
351
+ 0 3px 6px -2px oklch(0.55 0.03 275 / 0.23),
352
+ 0 8px 16px -6px oklch(0.55 0.03 275 / 0.21);
353
+ --elev-2: var(--lit-2),
354
+ 0 1px 2px -1px oklch(0.55 0.03 275 / 0.24),
355
+ 0 4px 8px -2px oklch(0.55 0.03 275 / 0.21),
356
+ 0 10px 18px -8px oklch(0.55 0.03 275 / 0.19);
357
+ --elev-float: var(--lit-2),
358
+ 0 2px 4px -1px oklch(0.55 0.03 275 / 0.26),
359
+ 0 6px 12px -3px oklch(0.55 0.03 275 / 0.23),
360
+ 0 14px 22px -10px oklch(0.55 0.03 275 / 0.21);
361
+ --backdrop: oklch(0.45 0.02 275 / 0.32);
141
362
 
142
- --text: #1E1E2E;
143
- --text-muted: #4C4F69;
144
- --text-dim: #6C6F85;
145
- --text-faint: #9CA0B0;
146
- --text-on-accent: #FFFFFF;
363
+ /* Glass. The same split the dark block ships: bars tint with --bg, panels
364
+ hold surface-1 at the clearest alpha the ramp allows. Worst case on a light
365
+ page is black behind the panel; composited through it, --text holds 10.76:1,
366
+ --text-2 6.68, the dim step 4.72 at 0.500 (re-measured twice 2026-09-02, independently) and --danger 4.81. The shipped
367
+ --text-4 value 0.515 measures 4.44 there, so it joins the dim step. 0.90 is
368
+ 25.5 sRGB levels black to white. --accent-text stays bound to its opaque
369
+ quiet fill (4.89:1, unchanged, because the fill is opaque). */
370
+ --glass-bar-tint: oklch(0.955 0.006 275 / 0.92);
371
+ --glass-tint: oklch(0.995 0.003 275 / 0.90);
372
+ --glass-dim: oklch(0.500 0.018 275);
373
+ --glass-danger: var(--danger);
374
+ /* light needs no lift here: --text-disabled already measures 3.37:1 on the
375
+ panel worst case, where dark's falls to 2.49 */
376
+ --glass-disabled: var(--text-disabled);
377
+ --glass-quiet: color-mix(in oklab, var(--accent) 12%, var(--surface-1));
378
+ --glass-edge: oklch(0.45 0.03 275 / 0.12);
147
379
 
148
- --accent: #2E93B5;
149
- --accent-hover:#1F7F9F;
150
- --accent-press:#166D8A;
380
+ /* A light page has less room under a dim than a dark one: at the 0.7 the dark
381
+ block ships, --text-4 measures 2.79:1 on --bg and 2.98 on --surface-1 here,
382
+ both under the 3:1 graphics floor, and --text-3 2.90 / 3.10. Lifted to the
383
+ same 0.90 the glass block uses, which takes the worst of those to 4.04, and
384
+ means an icon does not change when it crosses onto a glass panel. */
385
+ --icon-dim: 0.90;
151
386
 
152
- --success: #40A02B;
153
- --warning: #DF8E1D;
154
- --danger: #D20F39;
155
- --info: #209FB5;
387
+ /* Chart series on the light surface-1: 5.81 / 5.80 / 5.12 / 5.55:1. Slot 3 is
388
+ mauve here too: against sky the 1-3 pair measured 0.0790 in oklab and now
389
+ measures 0.1556.
156
390
 
157
- --shadow-float: 0 1px 2px rgb(0 0 0 / 0.04), 0 8px 24px rgb(0 0 0 / 0.12);
391
+ SLOTS 1 AND 4 ARE HELD AT THE sRGB BOUNDARY, her call 2026-09-02. At their
392
+ old chromas the browser clipped them (slot 1's linear red measured -0.0182,
393
+ slot 4's -0.0020), so the declared values named colours nobody saw and every
394
+ distance taken from a declaration overstated the gap. The gamut runs out at
395
+ C 0.1110 for slot 1 and 0.1378 for slot 4 at these lightnesses and hues;
396
+ both sit under that with a little margin. `tools/cvd.mjs` reds on any slot
397
+ outside sRGB, on every run. */
398
+ --chart-series-1: oklch(0.50 0.110 238);
399
+ --chart-series-2: oklch(0.52 0.150 42);
400
+ --chart-series-3: oklch(0.55 0.150 305);
401
+ --chart-series-4: oklch(0.50 0.135 150);
158
402
  }
159
403
 
160
- /* ---- Optional categorical palette (full Catppuccin accent ramp) ----
404
+ /* ---- Optional categorical palette (Catppuccin accent ramp) ----
161
405
  For category coding only: per-language dots, syntax, tags, multi-series charts.
162
- NOT for status (use --success / --warning / --danger / --info) and NOT for
163
- chrome. Opt-in — drop this block only when two brand accents aren't enough. */
164
- [data-theme="dark"], :root {
165
- --cat-rosewater:#F5E0DC; --cat-flamingo:#F2CDCD; --cat-pink:#F5C2E7;
166
- --cat-mauve:#CBA6F7; --cat-red:#F38BA8; --cat-maroon:#EBA0AC;
167
- --cat-peach:#FAB387; --cat-yellow:#F9E2AF; --cat-green:#A6E3A1;
168
- --cat-teal:#94E2D5; --cat-sky:#89DCEB; --cat-sapphire:#74C7EC;
169
- --cat-blue:#89B4FA; --cat-lavender:#B4BEFE;
406
+ NOT for status (use --success / --warning / --danger) and NOT for chrome.
407
+ Converted from the v1 hex ramp, computed not estimated. */
408
+ :root {
409
+ --cat-rosewater: oklch(0.923 0.024 30); --cat-flamingo: oklch(0.880 0.042 18);
410
+ --cat-pink: oklch(0.870 0.075 336); --cat-mauve: oklch(0.787 0.119 305);
411
+ --cat-red: oklch(0.756 0.130 3); --cat-maroon: oklch(0.782 0.090 9);
412
+ --cat-peach: oklch(0.824 0.101 53); --cat-yellow: oklch(0.919 0.070 87);
413
+ --cat-green: oklch(0.858 0.109 143); --cat-teal: oklch(0.858 0.079 183);
414
+ --cat-sky: oklch(0.847 0.083 210); --cat-sapphire: oklch(0.791 0.096 229);
415
+ --cat-blue: oklch(0.766 0.111 260); --cat-lavender: oklch(0.817 0.091 277);
170
416
  }
171
417
  [data-theme="light"] {
172
- --cat-rosewater:#DC8A78; --cat-flamingo:#DD7878; --cat-pink:#EA76CB;
173
- --cat-mauve:#8839EF; --cat-red:#D20F39; --cat-maroon:#E64553;
174
- --cat-peach:#FE640B; --cat-yellow:#DF8E1D; --cat-green:#40A02B;
175
- --cat-teal:#179299; --cat-sky:#04A5E5; --cat-sapphire:#209FB5;
176
- --cat-blue:#1E66F5; --cat-lavender:#7287FD;
418
+ --cat-rosewater: oklch(0.714 0.105 33); --cat-flamingo: oklch(0.686 0.126 21);
419
+ --cat-pink: oklch(0.726 0.174 338); --cat-mauve: oklch(0.555 0.250 297);
420
+ --cat-red: oklch(0.550 0.216 20); --cat-maroon: oklch(0.625 0.197 20);
421
+ --cat-peach: oklch(0.692 0.204 42); --cat-yellow: oklch(0.714 0.149 68);
422
+ --cat-green: oklch(0.625 0.177 140); --cat-teal: oklch(0.602 0.098 201);
423
+ --cat-sky: oklch(0.682 0.145 235); --cat-sapphire: oklch(0.648 0.107 213);
424
+ --cat-blue: oklch(0.559 0.226 262); --cat-lavender: oklch(0.664 0.175 273);
177
425
  }
178
426
 
179
427
  /* ---- Base reset ---- */
@@ -181,33 +429,96 @@
181
429
  html { font-size: 16px; }
182
430
  body {
183
431
  font-family: var(--font-sans);
184
- font-size: var(--text-base);
432
+ font-size: var(--t-3);
185
433
  line-height: 1.55;
186
434
  font-weight: 400;
187
435
  color: var(--text);
188
436
  background: var(--bg);
437
+ accent-color: var(--accent);
189
438
  -webkit-font-smoothing: antialiased;
190
439
  text-wrap: pretty;
191
440
  }
192
- ::selection { background: var(--accent-soft); color: var(--text); }
193
- :focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
441
+ ::selection { background: var(--accent-quiet); color: var(--text); }
442
+ /* `font-family` does not inherit into a <button>: the UA sets it, together with
443
+ a 2px outset border, ButtonFace, ButtonText and centred text, and an author
444
+ rule beats a UA rule at any specificity. Every component class that lands on a
445
+ button would otherwise have to restate all five, and the ones that do not
446
+ measured in the UA's Arial on a bare button. At (0,0,1) this loses to a
447
+ component's own costume whatever the source order. */
448
+ button {
449
+ font: inherit;
450
+ border: 0;
451
+ background: none;
452
+ color: inherit;
453
+ text-align: inherit;
454
+ }
455
+ :focus-visible {
456
+ outline: 2px solid var(--accent);
457
+ outline-offset: 3px;
458
+ box-shadow: 0 0 0 1px var(--bg);
459
+ }
460
+ /* An author rule beats a UA rule whatever the specificity, so `.btn` and every other block
461
+ here keeps painting under the `hidden` attribute without this line. A script toggling
462
+ `hidden` then silently does nothing. `!important` is load-bearing: the component rules
463
+ are the thing being overridden. */
464
+ [hidden] { display: none !important; }
465
+
466
+ /* ---- Document defaults: pages stop hand-rolling typography. ---- */
467
+ a { color: var(--accent-text); text-decoration: none; }
468
+ /* :where() zeroes the specificity, so every component class that sets
469
+ `text-decoration: none` in its base rule outranks this. At (0,1,1) it beat
470
+ them all and underlined the sidebar rows, a `.btn` written as a link and every
471
+ menu row; four components had already restated the reset on hover. */
472
+ :where(a):hover { text-decoration: underline; text-underline-offset: 2px; }
473
+ h1, h2, h3, h4 { color: var(--text); text-wrap: balance; }
474
+ h1 { font: 600 var(--t-8)/1.1 var(--font-sans); letter-spacing: -0.03em; }
475
+ h2 { font: 600 var(--t-6)/1.15 var(--font-sans); letter-spacing: -0.02em; }
476
+ h3 { font: 500 var(--t-5)/1.2 var(--font-sans); letter-spacing: -0.01em; }
477
+ h4 { font: 500 var(--t-4)/1.3 var(--font-sans); }
478
+ /* An anchored jump clears the sticky navbar instead of tucking under it. */
479
+ [id] { scroll-margin-top: calc(var(--navbar-h) + var(--space-2)); }
194
480
 
195
481
  /* ---- Utilities ---- */
196
482
  .label {
197
- font: 450 11px/1.4 var(--font-sans);
198
- letter-spacing: 0.08em;
483
+ font: 500 11px/1.4 var(--font-sans);
484
+ letter-spacing: var(--caps);
199
485
  text-transform: uppercase;
200
- color: var(--text-dim);
486
+ color: var(--text-3);
201
487
  }
202
488
  .num {
203
489
  font-variant-numeric: tabular-nums;
204
490
  font-feature-settings: "tnum";
205
491
  }
206
-
207
- /* ---- Reduced motion ---- */
208
- @media (prefers-reduced-motion: reduce) {
209
- *, *::before, *::after {
210
- animation-duration: 0.01ms !important;
211
- transition-duration: 0.01ms !important;
212
- }
492
+ /* Unbreakable machine strings wrap inside their box; they never widen a page.
493
+ The five component selectors below are the same costume, named here rather
494
+ than restated in components.css. `.copy-chip-fill > .copy-chip-value` at
495
+ (0,2,0) still turns it back off, and `.chart-ticks-band .chart-tick` keeps its
496
+ own position and translate. */
497
+ .truncate,
498
+ .select-value,
499
+ .tree-label,
500
+ .status-legend-name,
501
+ .copy-chip-value,
502
+ .chart-ticks-band .chart-tick { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
503
+ .clamp-2 {
504
+ display: -webkit-box;
505
+ -webkit-line-clamp: 2;
506
+ -webkit-box-orient: vertical;
507
+ overflow: hidden;
508
+ }
509
+ /* `.stepper-compact .step-text` is this costume under another name, so it is
510
+ named here. The navbar's own label cannot join: it hides only under
511
+ `@media (max-width: 768px)`, and this rule has no condition to carry. */
512
+ .sr-only,
513
+ .stepper-compact .step-text {
514
+ position: absolute;
515
+ width: 1px;
516
+ height: 1px;
517
+ padding: 0;
518
+ margin: -1px;
519
+ overflow: hidden;
520
+ clip: rect(0 0 0 0);
521
+ clip-path: inset(50%);
522
+ white-space: nowrap;
523
+ border: 0;
213
524
  }