@bongos/core 1.19.444 → 1.19.446

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.
Files changed (63) hide show
  1. package/.bongos-core.json +154 -49
  2. package/docs/adr/0054-session-token-reward-cost-plus.md +17 -0
  3. package/docs/copy-inventory.md +353 -352
  4. package/docs/copy-registry.json +397 -388
  5. package/docs/design/hall-direction-v2.md +295 -0
  6. package/docs/design/mocks/hall/_tokens.css +370 -0
  7. package/docs/design/mocks/hall/board/v1.html +846 -0
  8. package/docs/design/mocks/hall/board/v2.html +858 -0
  9. package/docs/design/mocks/hall/board/v3.html +884 -0
  10. package/docs/design/mocks/hall/goals/v1.html +1212 -0
  11. package/docs/design/mocks/hall/goals/v2.html +1192 -0
  12. package/docs/design/mocks/hall/goals/v3.html +1205 -0
  13. package/docs/design/mocks/hall/hall-sheet.html +244 -0
  14. package/docs/design/mocks/hall/reading/v1.html +1125 -0
  15. package/docs/design/mocks/hall/reading/v2.html +1177 -0
  16. package/docs/design/mocks/hall/reading/v3.html +1150 -0
  17. package/docs/design/mocks/hall/settings/v1.html +1238 -0
  18. package/docs/design/mocks/hall/settings/v2.html +1235 -0
  19. package/docs/design/mocks/hall/settings/v3.html +1299 -0
  20. package/docs/design/mocks/hall/task/v1.html +637 -0
  21. package/docs/design/mocks/hall/task/v2.html +646 -0
  22. package/docs/design/mocks/hall/task/v3.html +651 -0
  23. package/docs/design/vanilla-hall-ui-redesign-scope.md +2 -0
  24. package/docs/module-api-changelog.md +4 -0
  25. package/modules/economy/credits.js +46 -13
  26. package/modules/hall-ui/public/atlas.html +1 -2
  27. package/modules/hall-ui/public/collab.html +1 -2
  28. package/modules/hall-ui/public/copy-desk.css +11 -11
  29. package/modules/hall-ui/public/copy-desk.html +1 -2
  30. package/modules/hall-ui/public/diagrams.html +1 -2
  31. package/modules/hall-ui/public/drachmae.html +1 -2
  32. package/modules/hall-ui/public/gate.html +1 -2
  33. package/modules/hall-ui/public/goals.html +1 -2
  34. package/modules/hall-ui/public/government.html +1 -2
  35. package/modules/hall-ui/public/harbor.html +1 -2
  36. package/modules/hall-ui/public/idea.html +1 -2
  37. package/modules/hall-ui/public/ideas.css +6 -8
  38. package/modules/hall-ui/public/ideas.html +1 -2
  39. package/modules/hall-ui/public/index.html +3 -4
  40. package/modules/hall-ui/public/modules.html +1 -2
  41. package/modules/hall-ui/public/my-projects.html +1 -2
  42. package/modules/hall-ui/public/not-ready.html +1 -2
  43. package/modules/hall-ui/public/pair.html +1 -2
  44. package/modules/hall-ui/public/people.html +1 -2
  45. package/modules/hall-ui/public/primer.html +1 -2
  46. package/modules/hall-ui/public/profile.html +1 -2
  47. package/modules/hall-ui/public/ranks.html +1 -2
  48. package/modules/hall-ui/public/roadmap.html +1 -2
  49. package/modules/hall-ui/public/scouting.html +1 -2
  50. package/modules/hall-ui/public/sessions.html +1 -2
  51. package/modules/hall-ui/public/settings.html +1 -2
  52. package/modules/hall-ui/public/shell.js +35 -2
  53. package/modules/hall-ui/public/style.css +818 -392
  54. package/modules/hall-ui/public/task.html +1 -2
  55. package/modules/hall-ui/public/watch.css +1 -1
  56. package/modules/hall-ui/public/watch.html +1 -2
  57. package/modules/hall-ui/public/work.html +1 -2
  58. package/package-lock.json +2 -2
  59. package/package.json +1 -1
  60. package/scripts/gds/backfill-subagent-rewards.js +485 -0
  61. package/src/module-api.js +1 -1
  62. package/tests/backfill_subagent_rewards.mjs +308 -0
  63. package/tests/hall_tokens.mjs +371 -0
@@ -1,132 +1,323 @@
1
- /* builders hall — the vanilla Cloud Bongos dashboard shell (task 1723).
2
- Clean, neutral, calm: app shell (fixed sidebar + sticky top bar + one
3
- window-scrolled content column) + a small reused component kit.
4
- Scope: docs/design/vanilla-hall-ui-redesign-scope.md.
5
-
6
- THEMING CONTRACT (do not rename): the 13 color vars + 2 font vars below are
7
- the instance-pack API — serve-internal.js themeMarkup() re-declares them from
8
- config/branding.*.json AFTER this file, so an instance's palette/fonts win.
9
- Everything else derives from them (color-mix) so any pack reskins the whole
10
- kit. The values here are the neutral vanilla defaults. */
1
+ /* builders hall — the vanilla Cloud Bongos dashboard shell (task 1723), rebuilt
2
+ into the chrome world by task 1003303 (goal 1000081).
3
+
4
+ THE WORLD. DESIGN.md at the repo root is this surface's world; the tier below
5
+ is its "Reuse: the hall and the status ledger" recipe followed literally, and
6
+ it is the tier the composition round authored and verified as
7
+ docs/design/mocks/hall/_tokens.css (task 1003302). The round's pick is v3
8
+ "the glass hall" — a 64px rail at every width, glass chrome, content on
9
+ --bg-card panels at the full radius with tone doing the separation. Record:
10
+ docs/design/hall-direction-v2.md.
11
+
12
+ THEMING CONTRACT (do not rename): the 13 color vars + 2 font vars at the head
13
+ of :root are the instance-pack API — serve-internal.js themeMarkup()
14
+ re-declares exactly these from config/branding.*.json AFTER this file, so an
15
+ instance's palette/fonts win. Their values here are branding.neutral.json's
16
+ theme.ui, byte for byte, so the served page and this sheet agree. Everything
17
+ else derives from them (var / color-mix), so any pack reskins the whole kit.
18
+
19
+ FOUR THINGS THIS TIER DOES THAT world.css DOES NOT, each a hall problem the
20
+ landing never had:
21
+
22
+ a. THE STATUS VOCABULARY IS FIXED, AND IT IS THE ONE SANCTIONED EXCEPTION TO
23
+ THE FIFTEEN RULE. DESIGN.md §Reuse: a surface that renders VERIFICATION
24
+ state — the hall's chips, CI marks — keeps its own green/amber/red and
25
+ must not adopt the world's warm accent-derived ok/warn/err. A grade that
26
+ failed must not be salmon on a page whose claim button is also salmon.
27
+ Three of the four values MOVED, and that is a measurement, not a taste:
28
+ the shipped values were tuned against the hall's retired ground
29
+ (--bg #f5f6f8), and against the world's darker paper (--bg #edecee) they
30
+ fail AA — ok 4.27:1, warn 4.13:1, info 3.85:1 and 4.36:1, and 4.19:1 on
31
+ the dark card. Re-solved here so all eight pairs clear 4.5:1 on both
32
+ grounds in both modes with ~9% of headroom. tests/hall_tokens.mjs measures
33
+ them; do not hand-tune one without re-running it.
34
+
35
+ b. THE HALL KEEPS BOTH MODES, ON ITS OWN SWITCH. The apex went dark only
36
+ (ADR 0204), so light is the hall's alone. shell.js writes
37
+ html[data-theme] — NOT the world's html[data-mode] — so the dark tier is
38
+ written twice on THAT pair: html[data-theme="dark"] and, for first paint
39
+ before the script runs, @media (prefers-color-scheme: dark)
40
+ html:not([data-theme]). Both bodies are byte-identical; the twins rule is
41
+ what keeps them so. html[data-theme="dark"] is (0,1,1) and outranks the
42
+ pack's injected :root (0,1,0), which is what lets dark win over the pack
43
+ while the instance's accent survives.
44
+
45
+ c. --line IS A WIDTH HERE, NOT A COLOUR (DESIGN.md warns about exactly this
46
+ name). The hairline's COLOUR is --rule / --rule-soft.
47
+
48
+ d. THE COMPAT ALIASES ARE LOAD-BEARING. ~450 rules across 20 page sheets
49
+ derive from --surface / --surface-2 / --border / --border-strong /
50
+ --accent-weak / --accent-text, so they are re-derived in the world's terms
51
+ rather than deleted: a page this task does not re-skin degrades into the
52
+ new palette instead of breaking. They retire page family by page family as
53
+ tasks 1003304-1003308 land.
54
+
55
+ Composition — rail/drawer breakpoints, panel padding, page layout — is NOT in
56
+ here. It lives on the shell rules below and on a page's own body, because a
57
+ body declaration beats an inherited :root value at every width and a token
58
+ tier that carries a breakpoint silently defeats its own media query. */
11
59
 
12
60
  :root {
13
- /* ---- pack-driven vars (the theming API) ---- */
14
- --bg: #f5f6f8;
15
- --bg-card: #ffffff;
16
- --bg-deep: #eceef2;
17
- --bg-frame: #e7e9ee;
18
-
19
- --ink: #1a1d23;
20
- --ink-soft: #5b6372;
21
- --ink-faint: #8b93a1;
22
- --rule: #d4d8e0;
23
- --rule-soft: #e6e8ee;
24
- --accent: #4f63e7;
25
- --accent-deep: #3b4dc4;
26
- --sea: #175cd3;
27
- --terracotta: #b45309;
28
-
29
- --font-display: "Inter", -apple-system, "Segoe UI", system-ui, sans-serif;
30
- --font-body: "Inter", -apple-system, "Segoe UI", system-ui, sans-serif;
31
-
32
- /* ---- derived tokens (recompute from the pack vars) ---- */
33
- --surface: var(--bg-card);
34
- --surface-2: color-mix(in srgb, var(--bg-card) 45%, var(--bg));
35
- --border: color-mix(in srgb, var(--ink) 11%, var(--bg));
36
- --border-strong: color-mix(in srgb, var(--ink) 22%, var(--bg));
37
- --accent-weak: color-mix(in srgb, var(--accent) 9%, var(--bg-card));
38
- --accent-text: color-mix(in srgb, var(--accent) 82%, var(--ink));
39
-
40
- /* semantic status colors (deliberately NOT pack-driven) */
41
- --ok: #1a7f4b;
42
- --ok-weak: color-mix(in srgb, #1a7f4b 12%, var(--bg-card));
43
- --warn: #9a6700;
44
- --warn-weak: color-mix(in srgb, #d99a00 14%, var(--bg-card));
61
+ color-scheme: light;
62
+
63
+ /* ---- the contract tier: the thirteen colours + two faces a pack overrides ---- */
64
+ --bg: #edecee;
65
+ --bg-card: #fbfafc;
66
+ --bg-deep: #e2e1e4;
67
+ --bg-frame: #f6f5f7;
68
+ --ink: #17181a;
69
+ --ink-soft: #55565a;
70
+ --ink-faint: #65656a;
71
+ --rule: #c6c6c8;
72
+ --rule-soft: #dcdbdd;
73
+ --accent: #fb9569;
74
+ --accent-deep: #a3481a;
75
+ --sea: #5b6fd0;
76
+ --terracotta: #c9512c;
77
+
78
+ --font-display: "Manrope", "Segoe UI", system-ui, -apple-system, sans-serif;
79
+ --font-body: "Manrope", "Segoe UI", system-ui, -apple-system, sans-serif;
80
+
81
+ /* ---- derived: the third face. Mono is where the hall quotes the machine —
82
+ task ids, column heads, eyebrows, a credit figure's unit. ---- */
83
+ --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;
84
+
85
+ /* ---- derived: glass. The blur is a token because light glass over paper is
86
+ a tint, not a frost — light sets it to 0px and stays paper. ---- */
87
+ --glass: rgba(23, 24, 26, .05);
88
+ --glass-strong: rgba(23, 24, 26, .08);
89
+ --glass-edge: rgba(23, 24, 26, .12);
90
+ --glass-blur: 0px;
91
+
92
+ /* ---- derived: the void. The hall has no hero; the void is what a control
93
+ needs when it sits over a plate, and the seam for one. ---- */
94
+ --void: #050508;
95
+ --void-ink: #ffffff;
96
+ --void-edge: rgba(23, 24, 26, .12);
97
+
98
+ /* ---- derived: the skeleton tone. --bg-deep is the obvious choice and it is
99
+ WRONG: DESIGN.md collapses deep back to black in dark, so --bg-deep
100
+ and --bg are the same value there and a skeleton painted with it is
101
+ invisible. This mix reads in both modes and needs no dark twin,
102
+ because both of its terms already flip. ---- */
103
+ --skeleton: color-mix(in srgb, var(--ink) 10%, var(--bg-card));
104
+
105
+ /* ---- derived: the reading measure. A `ch` is the width of the font's ZERO
106
+ glyph, not a character, and Manrope's zero is much wider than its
107
+ average lowercase — primer.css's `max-width: 72ch` renders about 90
108
+ characters under a comment claiming 60-72. This token is MEASURED:
109
+ 48ch renders ~60 characters at the hall's body size. Re-measure rather
110
+ than doing the ch maths. ---- */
111
+ --measure: 48ch;
112
+
113
+ /* ---- derived: accent behaviours. The One Warm Thing Rule holds in the hall
114
+ too — the accent lights only what is pressable or live (the claim
115
+ button, the current nav item, the active tab, a focus ring). A credit
116
+ figure is data, not live, so it is ink. --accent-on is the DARK ink
117
+ that goes on an accent fill: white on this salmon fails AA. ---- */
118
+ --accent-ink: color-mix(in srgb, var(--accent) 45%, #240e00);
119
+ --accent-lift: color-mix(in srgb, var(--accent) 58%, #ffffff);
120
+ --accent-tint: color-mix(in srgb, var(--accent) 30%, #fffdf9);
121
+ --accent-on: #2a1305;
122
+ --accent-glow: 0 0 0 0 transparent;
123
+
124
+ /* ---- the status vocabulary: fixed, not pack-driven, not accent-derived.
125
+ See note (a) — these values are measured, not chosen. ---- */
126
+ --ok: #187444;
127
+ --ok-weak: color-mix(in srgb, #187444 12%, var(--bg-card));
128
+ --warn: #895c00;
129
+ --warn-weak: color-mix(in srgb, #895c00 14%, var(--bg-card));
45
130
  --danger: #b42318;
46
131
  --danger-weak: color-mix(in srgb, #b42318 10%, var(--bg-card));
47
- --info: var(--sea);
48
- --info-weak: color-mix(in srgb, var(--sea) 11%, var(--bg-card));
49
-
50
- /* layout metrics (scope §10) */
51
- --sidebar-w: 240px;
52
- --sidebar-w-rail: 64px;
53
- --topbar-h: 56px;
54
- --content-max: 1120px;
55
- --radius: 10px;
56
- --radius-sm: 8px;
57
- --shadow-pop: 0 8px 24px color-mix(in srgb, var(--ink) 14%, transparent);
58
-
59
- /* z ladder — content overlays (rank tips z-50, modals z-50, toast z-100,
132
+ /* --info is the one state that is NOT a bright colour, because it derives
133
+ from the contract's --sea rather than a chosen hex, and that makes its
134
+ own weak fill the binding pair rather than the grounds. The round
135
+ measured info against --bg and --bg-card only; measured against
136
+ --info-weak too (tests/hall_tokens.mjs), dark's 91% sea fell to 4.12:1,
137
+ so dark lightens to 80% for 5.00:1 with the ~9% headroom the rest of
138
+ the vocabulary holds. Both weak fills mix the STATE colour into the
139
+ card, which is the shape ok/warn/danger already use — mixing the raw
140
+ --sea instead is what made the two drift apart. ---- */
141
+ --info: color-mix(in srgb, var(--sea) 86%, #000000);
142
+ --info-weak: color-mix(in srgb, var(--info) 9%, var(--bg-card));
143
+
144
+ /* ---- derived: focus. The world's two-tone ring — a 2px accent-derived
145
+ outline at 3px offset inside an 8px --bg halo, so the indicator keeps
146
+ its own edge over whatever it lands on. ---- */
147
+ --focus: color-mix(in srgb, var(--accent-deep) 82%, #3a1400);
148
+
149
+ /* ---- derived: the label face flips. Mono caps read as precise on paper and
150
+ thin on black, so light takes the mono and dark takes the body face.
151
+ Take the token, never the family. ---- */
152
+ --label-face: var(--font-mono);
153
+ --label-size: 11px;
154
+ --label-space: .12em;
155
+ --label-case: uppercase;
156
+
157
+ /* ---- derived: the type halo. Only type over a plate needs one, and in light
158
+ there is nothing behind it. Never a coloured glow. ---- */
159
+ --type-halo: none;
160
+
161
+ /* ---- structure. --line is a WIDTH (note c). ---- */
162
+ --line: 1px;
163
+ --radius: 16px;
164
+ --radius-sm: 10px;
165
+ --radius-pill: 999px;
166
+
167
+ /* ---- the shell's metrics. --topbar-h stays under the kit's 80px top-bar
168
+ floor; --sidebar-w IS the rail, because v3 pins the rail at every
169
+ width and the drawer is the phone's disclosure. ---- */
170
+ --top-h: 56px;
171
+ --topbar-h: 56px;
172
+ --sidebar-w-rail: 64px;
173
+ --sidebar-w: var(--sidebar-w-rail);
174
+ --rail-open: 246px;
175
+ --content-max: 1120px;
176
+
177
+ /* ---- spacing: DESIGN.md's rhythm (6/10/14/22/34/52). The four-step
178
+ --space-* scale task 2179 named aliases onto it, so no ported rule
179
+ breaks; hairlines separate, padding does not. ---- */
180
+ --sp-hair: 6px;
181
+ --sp-xs: 10px;
182
+ --sp-sm: 14px;
183
+ --sp-md: 22px;
184
+ --sp-lg: 34px;
185
+ --sp-xl: 52px;
186
+ --space-1: var(--sp-hair);
187
+ --space-2: var(--sp-xs);
188
+ --space-3: var(--sp-sm);
189
+ --space-4: var(--sp-md);
190
+
191
+ /* ---- the compat aliases (note d) ---- */
192
+ --surface: var(--bg-card);
193
+ --surface-2: color-mix(in srgb, var(--ink) 5%, var(--bg-card));
194
+ --border: var(--rule-soft);
195
+ --border-strong: var(--rule);
196
+ --accent-weak: var(--accent-tint);
197
+ --accent-text: var(--accent-ink);
198
+ /* The world has no shadow vocabulary for UI, so this is spent only where an
199
+ element floats over a scrim (the phone drawer, the deep-link card). Pure
200
+ black with alpha is the one literal the Fifteen Rule allows, as a scrim. */
201
+ --shadow-pop: 0 18px 40px rgba(0, 0, 0, .28);
202
+
203
+ /* ---- z ladder — content overlays (rank tips z-50, modals z-50, toast z-100,
60
204
  copy-toast z-1000, deeplink z-2000, diagram fullscreen z-9999) must beat
61
- the chrome; the mobile drawer sits above chrome but below dialogs. */
205
+ the chrome; the mobile drawer sits above chrome but below dialogs. ---- */
62
206
  --z-topbar: 30;
63
207
  --z-sidebar: 40;
64
208
  --z-scrim: 140;
65
209
  --z-drawer: 150;
66
- }
67
-
68
- /* ---------- spacing scale (task 2179) ----------
69
- Four steps, deliberately matching the values the sheet had already converged
70
- on (a census found 6/10/16/20 dominant), so this NAMES the existing rhythm
71
- rather than restyling the hall. It governs the three things that decide
72
- density — card padding, card gap, section gap — and is not a mandate to
73
- rewrite every inset; chip and control padding stay as authored.
74
210
 
75
- Kept in its OWN block rather than added to :root above: :root is the
76
- pack-driven theming contract an instance overrides, and the program's audit
77
- requires it unchanged. Spacing is structure, not theme.
78
-
79
- 1 6px tight — inside a row or chip
80
- 2 10px default gap between siblings
81
- 3 16px comfortable — secondary card padding
82
- 4 20px card padding, card gap, section gap */
83
- :root {
84
- --space-1: 6px;
85
- --space-2: 10px;
86
- --space-3: 16px;
87
- --space-4: 20px;
211
+ /* ---- the hall's own plate: the declared seam, deferred by the owner
212
+ ("decide after the shell lands", 2026-08-30). The hall's plate was
213
+ never shot, so it resolves to none and the shell is a complete
214
+ composition without one — which is exactly what light does anyway.
215
+ Adding one later is a change to these three values, not a re-layout. */
216
+ --plate: none;
217
+ --plate-blend: lighten;
218
+ --plate-veil: none;
88
219
  }
89
220
 
90
221
  /* ---------- dark theme ----------
91
- Only the NEUTRALS flip; the instance pack's accent/sea/terracotta + fonts
92
- survive, and every derived token (color-mix on these vars) re-derives
93
- automatically. html[data-theme] outweighs the pack's injected :root override
94
- (specificity 0,1,1 vs 0,1,0), so dark wins over the pack's light values.
95
- shell.js owns the toggle + persistence (localStorage 'hall-theme'); the
96
- media block below covers first paint for OS-dark users before it runs. */
222
+ Only the NEUTRALS and the mode-dependent derivations flip; the pack's
223
+ accent/sea/terracotta + fonts survive (the Unpinned Accent Rule — a dark
224
+ block may pin no accent literal, or a re-themed instance cannot reach dark),
225
+ and every color-mix on them re-derives for free. Written twice, byte-
226
+ identical: the [data-theme] switch shell.js owns, and the media block that
227
+ covers first paint for OS-dark users before the script runs. */
97
228
 
98
229
  html[data-theme="dark"] {
99
230
  color-scheme: dark;
100
- --bg: #101216;
101
- --bg-card: #181b21;
102
- --bg-deep: #0c0e11;
103
- --bg-frame: #1e222a;
104
- --ink: #e7e9ee;
105
- --ink-soft: #9aa2b1;
106
- --ink-faint: #6b7280;
107
- --rule: #333945;
108
- --rule-soft: #242933;
109
- --shadow-pop: 0 8px 24px rgba(0, 0, 0, .5);
231
+ --bg: #000000;
232
+ --bg-card: #101012;
233
+ --bg-deep: #000000;
234
+ --bg-frame: #08080a;
235
+ --ink: #ffffff;
236
+ --ink-soft: #c0c0c0;
237
+ --ink-faint: #a8a8a8;
238
+ --rule: rgba(255, 255, 255, .16);
239
+ --rule-soft: rgba(255, 255, 255, .08);
240
+ --glass: rgba(255, 255, 255, .11);
241
+ --glass-strong: rgba(255, 255, 255, .17);
242
+ --glass-edge: rgba(255, 255, 255, .13);
243
+ --glass-blur: 18px;
244
+ --void: #000000;
245
+ --void-ink: var(--ink);
246
+ --void-edge: rgba(255, 255, 255, .16);
247
+ --accent-ink: var(--accent);
248
+ --accent-tint: color-mix(in srgb, var(--accent) 22%, #0b0a09);
249
+ --accent-glow: 0 8px 28px color-mix(in srgb, var(--accent) 38%, transparent);
250
+ --ok: #4ade80;
251
+ --ok-weak: color-mix(in srgb, #4ade80 14%, var(--bg-card));
252
+ --warn: #fbbf24;
253
+ --warn-weak: color-mix(in srgb, #fbbf24 14%, var(--bg-card));
254
+ --danger: #f87171;
255
+ --danger-weak: color-mix(in srgb, #f87171 14%, var(--bg-card));
256
+ --info: color-mix(in srgb, var(--sea) 80%, #ffffff);
257
+ --info-weak: color-mix(in srgb, var(--info) 14%, var(--bg-card));
258
+ --focus: var(--accent);
259
+ --label-face: var(--font-body);
260
+ --label-size: 11.5px;
261
+ --label-space: .1em;
262
+ --type-halo: 0 2px 18px rgba(0, 0, 0, .94), 0 0 54px rgba(0, 0, 0, .72);
263
+ --surface-2: color-mix(in srgb, var(--ink) 6%, var(--bg-card));
264
+ --shadow-pop: 0 18px 40px rgba(0, 0, 0, .62);
265
+ --plate-veil: linear-gradient(to bottom, rgba(0, 0, 0, .82), rgba(0, 0, 0, .93));
110
266
  }
111
267
  html[data-theme="dark"] body { background: var(--bg); }
112
268
 
113
269
  @media (prefers-color-scheme: dark) {
114
270
  html:not([data-theme]) {
115
271
  color-scheme: dark;
116
- --bg: #101216;
117
- --bg-card: #181b21;
118
- --bg-deep: #0c0e11;
119
- --bg-frame: #1e222a;
120
- --ink: #e7e9ee;
121
- --ink-soft: #9aa2b1;
122
- --ink-faint: #6b7280;
123
- --rule: #333945;
124
- --rule-soft: #242933;
125
- --shadow-pop: 0 8px 24px rgba(0, 0, 0, .5);
272
+ --bg: #000000;
273
+ --bg-card: #101012;
274
+ --bg-deep: #000000;
275
+ --bg-frame: #08080a;
276
+ --ink: #ffffff;
277
+ --ink-soft: #c0c0c0;
278
+ --ink-faint: #a8a8a8;
279
+ --rule: rgba(255, 255, 255, .16);
280
+ --rule-soft: rgba(255, 255, 255, .08);
281
+ --glass: rgba(255, 255, 255, .11);
282
+ --glass-strong: rgba(255, 255, 255, .17);
283
+ --glass-edge: rgba(255, 255, 255, .13);
284
+ --glass-blur: 18px;
285
+ --void: #000000;
286
+ --void-ink: var(--ink);
287
+ --void-edge: rgba(255, 255, 255, .16);
288
+ --accent-ink: var(--accent);
289
+ --accent-tint: color-mix(in srgb, var(--accent) 22%, #0b0a09);
290
+ --accent-glow: 0 8px 28px color-mix(in srgb, var(--accent) 38%, transparent);
291
+ --ok: #4ade80;
292
+ --ok-weak: color-mix(in srgb, #4ade80 14%, var(--bg-card));
293
+ --warn: #fbbf24;
294
+ --warn-weak: color-mix(in srgb, #fbbf24 14%, var(--bg-card));
295
+ --danger: #f87171;
296
+ --danger-weak: color-mix(in srgb, #f87171 14%, var(--bg-card));
297
+ --info: color-mix(in srgb, var(--sea) 80%, #ffffff);
298
+ --info-weak: color-mix(in srgb, var(--info) 14%, var(--bg-card));
299
+ --focus: var(--accent);
300
+ --label-face: var(--font-body);
301
+ --label-size: 11.5px;
302
+ --label-space: .1em;
303
+ --type-halo: 0 2px 18px rgba(0, 0, 0, .94), 0 0 54px rgba(0, 0, 0, .72);
304
+ --surface-2: color-mix(in srgb, var(--ink) 6%, var(--bg-card));
305
+ --shadow-pop: 0 18px 40px rgba(0, 0, 0, .62);
306
+ --plate-veil: linear-gradient(to bottom, rgba(0, 0, 0, .82), rgba(0, 0, 0, .93));
126
307
  }
127
308
  html:not([data-theme]) body { background: var(--bg); }
128
309
  }
129
310
 
311
+ /* The Kill Switch Rule (DESIGN.md). Nothing escapes it, and no motion is
312
+ authored in JavaScript, because JavaScript motion escapes it. */
313
+ @media (prefers-reduced-motion: reduce) {
314
+ *, *::before, *::after {
315
+ animation: none !important;
316
+ transition: none !important;
317
+ scroll-behavior: auto !important;
318
+ }
319
+ }
320
+
130
321
  /* ---------- table of contents (task 2166) ----------
131
322
  Section banners in file order — grep "----------" to jump:
132
323
  tokens + dark theme (ABOVE — the theming contract; never move/rename)
@@ -158,12 +349,12 @@ html {
158
349
 
159
350
  * {
160
351
  scrollbar-width: thin;
161
- scrollbar-color: var(--border-strong) transparent;
352
+ scrollbar-color: var(--rule) transparent;
162
353
  }
163
354
  ::-webkit-scrollbar { width: 10px; height: 10px; }
164
355
  ::-webkit-scrollbar-track { background: transparent; }
165
356
  ::-webkit-scrollbar-thumb {
166
- background: var(--border-strong);
357
+ background: var(--rule);
167
358
  border-radius: 5px;
168
359
  border: 2px solid transparent;
169
360
  background-clip: content-box;
@@ -182,33 +373,45 @@ body {
182
373
  }
183
374
 
184
375
  a {
185
- color: var(--accent-text);
376
+ color: var(--accent-ink);
186
377
  text-decoration: none;
187
378
  }
188
- a:hover { text-decoration: underline; text-underline-offset: 2px; }
379
+ a:hover { text-decoration: underline; text-underline-offset: 3px; }
189
380
 
190
381
  code, kbd, pre {
191
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
382
+ font-family: var(--font-mono);
192
383
  font-size: 0.92em;
193
384
  }
194
385
 
386
+ /* The label voice. Mono in light, the body face in dark — take the token, never
387
+ the family (DESIGN.md, The label face flips). */
388
+ .lbl {
389
+ font-family: var(--label-face);
390
+ font-size: var(--label-size);
391
+ letter-spacing: var(--label-space);
392
+ text-transform: var(--label-case);
393
+ color: var(--ink-faint);
394
+ line-height: 1.2;
395
+ }
396
+
397
+ /* Every number the ledger produced is tabular, so a live refresh cannot reflow
398
+ the composition around it. */
399
+ .num, .tabular { font-variant-numeric: tabular-nums; }
400
+
195
401
  select, input[type="text"], input[type="search"], textarea {
196
402
  font-family: var(--font-body);
197
403
  font-size: 14px;
198
404
  color: var(--ink);
199
- background: var(--surface);
200
- border: 1px solid var(--border-strong);
201
- border-radius: var(--radius-sm);
202
- padding: 6px 10px;
405
+ background: none;
406
+ border: 1px solid var(--rule);
407
+ border-radius: var(--radius-pill);
408
+ min-height: 30px;
409
+ padding: 5px 12px;
203
410
  }
411
+ select:hover, input[type="text"]:hover, input[type="search"]:hover { border-color: var(--ink-faint); }
412
+ textarea { border-radius: var(--radius-sm); }
204
413
  select:disabled { opacity: 0.55; cursor: not-allowed; }
205
- select option { background: var(--surface); color: var(--ink); }
206
-
207
- :focus-visible {
208
- outline: 2px solid var(--accent);
209
- outline-offset: 2px;
210
- border-radius: 4px;
211
- }
414
+ select option { background: var(--bg-card); color: var(--ink); }
212
415
 
213
416
  button { font-family: var(--font-body); }
214
417
 
@@ -221,48 +424,118 @@ button { font-family: var(--font-body); }
221
424
  white-space: nowrap;
222
425
  }
223
426
 
224
- /* ---------- app shell ---------- */
427
+ /* ---------- app shell ----------
428
+ v3 "the glass hall" (the owner's pick, 2026-08-30): the sidebar is the 64px
429
+ rail at EVERY width, opening on hover or focus-within over the page; the
430
+ topbar and sidebar are glass; content sits on --bg-card panels at the full
431
+ --radius with tone doing the separation and no hairline edge.
432
+
433
+ Two rules the round found the hard way and this section obeys:
434
+ - a rail label must LEAVE the layout, not fade to opacity 0. A faded label
435
+ still occupies width and pushes its own icon out of the 64px strip.
436
+ - the rail's breakpoints are composition, so they live here on the shell
437
+ rules and never in the token tier: a page that declares --sidebar-w on
438
+ `body` would beat an inherited :root value and silently defeat the query. */
225
439
 
226
440
  .app { min-height: 100vh; }
227
441
 
442
+ /* The ground and its veil. Both are tokens; the hall's plate is not shot yet,
443
+ so both resolve to none in both modes and the shell is a complete composition
444
+ without one. Adding one later is a token change, not a re-layout. */
445
+ body::before {
446
+ content: "";
447
+ position: fixed;
448
+ inset: 0;
449
+ z-index: 0;
450
+ pointer-events: none;
451
+ background-image: var(--plate);
452
+ background-size: cover;
453
+ background-position: center;
454
+ mix-blend-mode: var(--plate-blend);
455
+ }
456
+ body::after {
457
+ content: "";
458
+ position: fixed;
459
+ inset: 0;
460
+ z-index: 0;
461
+ pointer-events: none;
462
+ background-image: var(--plate-veil);
463
+ }
464
+
228
465
  .app-sidebar {
229
466
  position: fixed;
230
467
  inset: 0 auto 0 0;
231
- width: var(--sidebar-w);
468
+ width: var(--sidebar-w-rail);
232
469
  z-index: var(--z-sidebar);
233
470
  display: flex;
234
471
  flex-direction: column;
235
- background: var(--surface);
236
- border-right: 1px solid var(--border);
472
+ background: var(--glass);
473
+ border-right: 1px solid var(--glass-edge);
474
+ backdrop-filter: blur(var(--glass-blur));
475
+ -webkit-backdrop-filter: blur(var(--glass-blur));
237
476
  overflow: hidden;
238
477
  }
478
+ /* The rail opens over the content — no transition, because the world animates
479
+ transform and filter only. `:focus-within` is what makes it keyboard-reachable
480
+ at all; without it a tab into the rail lands on unlabelled icons. */
481
+ .app-sidebar:hover,
482
+ .app-sidebar:focus-within { width: var(--rail-open); }
239
483
 
240
484
  .app-sidebar__brand {
241
485
  display: flex;
242
486
  align-items: center;
487
+ justify-content: center;
243
488
  min-height: var(--topbar-h);
244
- padding: 0 16px;
245
- border-bottom: 1px solid var(--border);
489
+ padding: 0;
490
+ border-bottom: 1px solid var(--glass-edge);
246
491
  flex-shrink: 0;
247
492
  }
493
+ /* The skip link — shell.js renders it as the first focusable thing in the
494
+ document and points it at a focusable, never-ringed <main>. Off-screen until
495
+ focused, then a pill in the top-left over everything. */
496
+ .skip {
497
+ position: absolute;
498
+ left: -9999px;
499
+ top: 8px;
500
+ z-index: 200;
501
+ background: var(--bg-card);
502
+ color: var(--ink);
503
+ border: 1px solid var(--rule);
504
+ border-radius: var(--radius-pill);
505
+ padding: 11px 18px;
506
+ font-size: 13.5px;
507
+ text-decoration: none;
508
+ }
509
+ .skip:focus { left: 12px; }
510
+ main:focus { outline: none; }
511
+
248
512
  .brand-mark {
249
513
  display: flex;
250
514
  align-items: center;
515
+ justify-content: center;
251
516
  gap: 10px;
517
+ /* The label leaves the layout in the closed rail, so the dot alone would be
518
+ an 11px target — the mark still has to clear the 24px control floor. */
519
+ min-width: 32px;
520
+ min-height: 32px;
252
521
  font-family: var(--font-display);
253
- font-weight: 700;
522
+ font-weight: 500;
254
523
  font-size: 15px;
524
+ letter-spacing: -0.015em;
255
525
  color: var(--ink);
256
- min-width: 0;
257
526
  }
258
527
  .brand-mark:hover { text-decoration: none; }
528
+ /* An outlined dot, not a filled one: the accent lights only what is pressable
529
+ or live, and a wordmark is neither. */
259
530
  .brand-mark__dot {
260
- width: 10px; height: 10px;
261
- border-radius: 3px;
262
- background: var(--accent);
531
+ width: 11px; height: 11px;
532
+ border-radius: 50%;
533
+ border: 1px solid var(--ink);
534
+ background: none;
263
535
  flex-shrink: 0;
264
536
  }
265
537
  .brand-mark__name {
538
+ display: none;
266
539
  white-space: nowrap;
267
540
  overflow: hidden;
268
541
  text-overflow: ellipsis;
@@ -270,7 +543,7 @@ button { font-family: var(--font-body); }
270
543
 
271
544
  .app-sidebar__nav {
272
545
  flex: 1;
273
- padding: 12px 10px;
546
+ padding: 14px 0 22px;
274
547
  display: flex;
275
548
  flex-direction: column;
276
549
  gap: 2px;
@@ -290,28 +563,39 @@ button { font-family: var(--font-body); }
290
563
  }
291
564
  .nav-group { display: flex; flex-direction: column; gap: 2px; }
292
565
  .nav-group + .nav-group { margin-top: 14px; }
566
+ /* task 1003303: the items live in their own wrapper so the group head can carry
567
+ aria-controls (the frozen markup had aria-expanded pointing at nothing, which
568
+ fails the disclosure rule). The class deliberately does NOT start with
569
+ "nav-group" — tests/hall_nav.mjs splits the sidebar on that string to find
570
+ groups, and a wrapper that matched it would parse as a phantom sixth group. */
571
+ .nav-items { display: flex; flex-direction: column; gap: 2px; }
293
572
  .nav-group__head {
294
- font-size: 11px;
295
- font-weight: 600;
296
- letter-spacing: 0.06em;
297
- text-transform: uppercase;
298
- color: var(--ink-faint);
299
- padding: 4px 10px 2px;
300
- }
301
- .nav-group--collapsible .nav-group__head {
302
- display: flex;
573
+ display: none;
303
574
  align-items: center;
304
575
  justify-content: space-between;
576
+ gap: 8px;
305
577
  width: 100%;
578
+ min-height: 26px;
579
+ padding: 8px 22px 4px;
580
+ text-align: left;
306
581
  background: none;
307
582
  border: none;
308
583
  cursor: pointer;
309
584
  }
310
- .nav-group--collapsible .nav-group__head:hover { color: var(--ink-soft); }
585
+ .nav-group__head-label {
586
+ font-family: var(--label-face);
587
+ font-size: var(--label-size);
588
+ letter-spacing: var(--label-space);
589
+ text-transform: var(--label-case);
590
+ color: var(--ink-faint);
591
+ }
592
+ .nav-group--collapsible .nav-group__head:hover .nav-group__head-label { color: var(--ink-soft); }
311
593
  .nav-group__chevron {
312
- width: 12px;
313
- height: 12px;
594
+ width: 10px;
595
+ height: 6px;
314
596
  flex-shrink: 0;
597
+ stroke: currentColor;
598
+ color: var(--ink-faint);
315
599
  transition: transform 0.15s ease;
316
600
  }
317
601
  .nav-group--collapsed .nav-group__chevron { transform: rotate(-90deg); }
@@ -321,42 +605,47 @@ button { font-family: var(--font-body); }
321
605
  position: relative;
322
606
  display: flex;
323
607
  align-items: center;
608
+ justify-content: center;
324
609
  gap: 10px;
325
- height: 36px;
326
- padding: 0 10px;
610
+ min-height: 40px;
611
+ margin: 0 8px;
612
+ padding: 0;
327
613
  border-radius: var(--radius-sm);
328
614
  color: var(--ink-soft);
329
- font-weight: 500;
330
615
  font-size: 14px;
616
+ text-transform: lowercase;
331
617
  white-space: nowrap;
332
618
  }
333
- .nav-item:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
334
- .nav-item--active {
335
- background: var(--accent-weak);
336
- color: var(--accent-text);
337
- }
619
+ .nav-item:hover { background: var(--glass); color: var(--ink); text-decoration: none; }
620
+ .nav-item--active { background: var(--glass-strong); color: var(--ink); }
621
+ /* The current item is the one live thing in the rail, so it takes the accent —
622
+ the lifecycle rail's lit-step grammar, one 2px bar on the leading edge. */
338
623
  .nav-item--active::before {
339
624
  content: "";
340
625
  position: absolute;
341
- left: -10px; top: 7px; bottom: 7px;
342
- width: 3px;
343
- border-radius: 0 2px 2px 0;
626
+ left: 0; top: 8px; bottom: 8px;
627
+ width: 2px;
628
+ border-radius: 1px;
344
629
  background: var(--accent);
345
630
  }
346
- .nav-icon { width: 18px; height: 18px; flex-shrink: 0; }
631
+ .nav-item__label { display: none; font-size: 14px; }
632
+ .nav-icon { width: 16px; height: 16px; flex-shrink: 0; }
347
633
 
348
634
  .app-sidebar__foot {
349
- padding: 12px 10px 14px;
350
- border-top: 1px solid var(--border);
635
+ padding: 10px 0;
636
+ border-top: 1px solid var(--glass-edge);
351
637
  display: flex;
352
638
  flex-direction: column;
639
+ align-items: center;
353
640
  gap: 8px;
354
641
  flex-shrink: 0;
355
642
  }
356
643
  .shell-user {
357
644
  display: flex;
358
645
  align-items: center;
646
+ justify-content: center;
359
647
  gap: 10px;
648
+ min-height: 36px;
360
649
  padding: 4px 6px;
361
650
  min-width: 0;
362
651
  /* task 1831: the chip is now an <a> to /settings — strip link chrome, keep the
@@ -366,15 +655,15 @@ button { font-family: var(--font-body); }
366
655
  border-radius: var(--radius-sm);
367
656
  cursor: pointer;
368
657
  }
369
- .shell-user:hover { background: var(--surface-2); text-decoration: none; }
370
- .shell-user:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
658
+ .shell-user:hover { background: var(--glass); text-decoration: none; }
371
659
  .shell-user__avatar {
372
- width: 30px; height: 30px;
660
+ width: 26px; height: 26px;
373
661
  border-radius: 50%;
374
- background: var(--accent-weak);
375
- color: var(--accent-text);
376
- font-size: 12px;
377
- font-weight: 600;
662
+ background: var(--surface-2);
663
+ border: 1px solid var(--rule);
664
+ color: var(--ink-soft);
665
+ font-size: 11px;
666
+ font-weight: 500;
378
667
  display: flex;
379
668
  align-items: center;
380
669
  justify-content: center;
@@ -382,43 +671,75 @@ button { font-family: var(--font-body); }
382
671
  flex-shrink: 0;
383
672
  }
384
673
  .shell-user__avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
385
- .shell-user__meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
674
+ .shell-user__meta { display: none; flex-direction: column; min-width: 0; line-height: 1.3; }
386
675
  .shell-user__name {
387
- font-size: 13px;
388
- font-weight: 600;
676
+ font-size: 13.5px;
389
677
  white-space: nowrap;
390
678
  overflow: hidden;
391
679
  text-overflow: ellipsis;
392
680
  }
393
- .shell-user__rank { font-size: 11.5px; color: var(--ink-faint); }
681
+ .shell-user__rank {
682
+ font-family: var(--label-face);
683
+ font-size: 10.5px;
684
+ letter-spacing: var(--label-space);
685
+ text-transform: var(--label-case);
686
+ color: var(--ink-faint);
687
+ }
394
688
  .shell-signin,
395
689
  .shell-home,
396
690
  .shell-signout {
397
691
  display: flex;
398
692
  align-items: center;
693
+ justify-content: center;
399
694
  gap: 10px;
400
- height: 34px;
401
- padding: 0 10px;
695
+ min-height: 32px;
696
+ padding: 0 8px;
402
697
  border-radius: var(--radius-sm);
403
698
  font-size: 13.5px;
404
- font-weight: 500;
405
- color: var(--ink-soft);
699
+ color: var(--ink-faint);
406
700
  }
407
701
  .shell-home { text-decoration: none; }
408
702
  .shell-signin {
409
- justify-content: center;
410
703
  background: var(--accent);
411
- color: #fff;
704
+ color: var(--accent-on);
705
+ border-radius: var(--radius-pill);
706
+ font-weight: 500;
707
+ min-height: 34px;
412
708
  }
413
- .shell-signin:hover { background: var(--accent-deep); color: #fff; text-decoration: none; }
709
+ .shell-signin:hover { background: var(--accent-lift); color: var(--accent-on); text-decoration: none; }
414
710
  .shell-home:hover,
415
- .shell-signout:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
711
+ .shell-signout:hover { background: var(--glass); color: var(--ink); text-decoration: none; }
712
+
713
+ /* ---- the rail, open. Labels LEAVE the layout when closed and come back here;
714
+ the icon strip is what stays either way. ---- */
715
+ .app-sidebar:hover .brand-mark__name,
716
+ .app-sidebar:focus-within .brand-mark__name,
717
+ .app-sidebar:hover .nav-item__label,
718
+ .app-sidebar:focus-within .nav-item__label,
719
+ .app-sidebar:hover .shell-user__meta,
720
+ .app-sidebar:focus-within .shell-user__meta { display: block; }
721
+ .app-sidebar:hover .nav-group--collapsible .nav-group__head,
722
+ .app-sidebar:focus-within .nav-group--collapsible .nav-group__head { display: flex; }
723
+ .app-sidebar:hover .app-sidebar__brand,
724
+ .app-sidebar:focus-within .app-sidebar__brand { justify-content: flex-start; padding: 0 22px; }
725
+ .app-sidebar:hover .nav-item,
726
+ .app-sidebar:focus-within .nav-item { justify-content: flex-start; padding: 0 12px; }
727
+ .app-sidebar:hover .app-sidebar__foot,
728
+ .app-sidebar:focus-within .app-sidebar__foot { align-items: stretch; padding: 12px 16px; }
729
+ .app-sidebar:hover .shell-user,
730
+ .app-sidebar:focus-within .shell-user { justify-content: flex-start; }
731
+ .app-sidebar:hover .shell-home,
732
+ .app-sidebar:focus-within .shell-home,
733
+ .app-sidebar:hover .shell-signout,
734
+ .app-sidebar:focus-within .shell-signout { justify-content: flex-start; padding: 0 10px; }
416
735
 
417
736
  .app-body {
418
- margin-left: var(--sidebar-w);
737
+ margin-left: var(--sidebar-w-rail);
419
738
  min-height: 100vh;
420
739
  display: flex;
421
740
  flex-direction: column;
741
+ position: relative;
742
+ z-index: 1;
422
743
  }
423
744
 
424
745
  .app-topbar {
@@ -427,29 +748,36 @@ button { font-family: var(--font-body); }
427
748
  z-index: var(--z-topbar);
428
749
  display: flex;
429
750
  align-items: center;
430
- gap: 12px;
751
+ gap: 14px;
431
752
  height: var(--topbar-h);
432
- padding: 0 24px;
433
- background: color-mix(in srgb, var(--surface) 88%, transparent);
434
- backdrop-filter: blur(8px);
435
- border-bottom: 1px solid var(--border);
753
+ padding: 0 22px;
754
+ background: var(--glass);
755
+ backdrop-filter: blur(var(--glass-blur));
756
+ -webkit-backdrop-filter: blur(var(--glass-blur));
757
+ border-bottom: 1px solid var(--glass-edge);
436
758
  }
759
+ /* Sentence case, not a mono label: v3's own divergence, picked with it. */
437
760
  .topbar-title {
438
761
  margin: 0;
439
762
  font-family: var(--font-display);
440
- font-size: 16px;
441
- font-weight: 600;
442
- letter-spacing: -0.01em;
763
+ font-weight: 300;
764
+ font-size: 19px;
765
+ letter-spacing: -0.025em;
766
+ text-transform: lowercase;
767
+ color: var(--ink);
443
768
  }
444
769
  .topbar-side {
445
770
  margin-left: auto;
446
771
  display: flex;
447
772
  align-items: center;
448
- gap: 12px;
773
+ gap: 8px;
449
774
  min-width: 0;
450
775
  }
451
776
  .topbar-note {
452
- font-size: 13px;
777
+ font-family: var(--label-face);
778
+ font-size: var(--label-size);
779
+ letter-spacing: var(--label-space);
780
+ text-transform: var(--label-case);
453
781
  color: var(--ink-faint);
454
782
  white-space: nowrap;
455
783
  overflow: hidden;
@@ -461,17 +789,19 @@ button { font-family: var(--font-body); }
461
789
  display: flex;
462
790
  align-items: baseline;
463
791
  gap: 12px;
464
- padding: 10px 24px;
792
+ padding: 10px 22px;
465
793
  font-size: 13px;
466
794
  color: var(--warn);
467
795
  background: var(--warn-weak);
468
- border-bottom: 1px solid var(--border);
796
+ border-bottom: 1px solid var(--rule-soft);
469
797
  }
470
798
  .denied-notice code {
471
799
  font-size: 12px;
472
800
  }
473
801
  .denied-notice__close {
474
802
  margin-left: auto;
803
+ min-width: 24px;
804
+ min-height: 24px;
475
805
  border: 0;
476
806
  background: none;
477
807
  color: inherit;
@@ -483,31 +813,32 @@ button { font-family: var(--font-body); }
483
813
  display: none;
484
814
  align-items: center;
485
815
  justify-content: center;
486
- width: 34px; height: 34px;
487
- border: 1px solid var(--border);
488
- border-radius: var(--radius-sm);
489
- background: var(--surface);
816
+ width: 32px; height: 32px;
817
+ border: 0;
818
+ border-radius: var(--radius-pill);
819
+ background: none;
490
820
  color: var(--ink-soft);
491
821
  cursor: pointer;
492
822
  padding: 0;
493
823
  }
494
- .topbar-menu svg { width: 18px; height: 18px; }
824
+ .topbar-menu:hover { color: var(--ink); }
825
+ .topbar-menu svg { width: 16px; height: 16px; }
495
826
 
496
827
  .theme-toggle {
497
828
  display: inline-flex;
498
829
  align-items: center;
499
830
  justify-content: center;
500
831
  width: 32px; height: 32px;
501
- border: 1px solid var(--border);
502
- border-radius: var(--radius-sm);
503
- background: var(--surface);
832
+ border: 0;
833
+ border-radius: var(--radius-pill);
834
+ background: none;
504
835
  color: var(--ink-soft);
505
836
  cursor: pointer;
506
837
  padding: 0;
507
838
  flex-shrink: 0;
508
839
  }
509
- .theme-toggle:hover { background: var(--surface-2); color: var(--ink); }
510
- .theme-toggle svg { width: 17px; height: 17px; }
840
+ .theme-toggle:hover { color: var(--ink); }
841
+ .theme-toggle svg { width: 16px; height: 16px; }
511
842
 
512
843
  /* task 1475: the accessibility zoom control — sibling of the theme toggle.
513
844
  Widens to fit the "125%"/"150%" label when zoom is active. */
@@ -518,24 +849,24 @@ button { font-family: var(--font-body); }
518
849
  gap: 4px;
519
850
  min-width: 32px; height: 32px;
520
851
  padding: 0 8px;
521
- border: 1px solid var(--border);
522
- border-radius: var(--radius-sm);
523
- background: var(--surface);
852
+ border: 0;
853
+ border-radius: var(--radius-pill);
854
+ background: none;
524
855
  color: var(--ink-soft);
525
856
  cursor: pointer;
526
857
  flex-shrink: 0;
527
858
  }
528
- .zoom-toggle:hover { background: var(--surface-2); color: var(--ink); }
529
- .zoom-toggle svg { width: 17px; height: 17px; }
530
- /* Active (zoomed-in) state reads as "on" with the instance accent. */
859
+ .zoom-toggle:hover { color: var(--ink); }
860
+ .zoom-toggle svg { width: 16px; height: 16px; }
861
+ /* Active (zoomed-in) state reads as "on" — the accent, because a live control
862
+ is exactly what the accent is for. */
531
863
  .zoom-toggle--on {
532
- border-color: color-mix(in srgb, var(--accent) 45%, transparent);
533
- background: var(--accent-weak);
534
- color: var(--accent-text);
864
+ background: var(--accent-tint);
865
+ color: var(--accent-ink);
535
866
  }
536
867
  .zoom-toggle__pct {
537
868
  font-size: 12px;
538
- font-weight: 600;
869
+ font-weight: 500;
539
870
  font-variant-numeric: tabular-nums;
540
871
  line-height: 1;
541
872
  }
@@ -544,15 +875,15 @@ button { font-family: var(--font-body); }
544
875
  width: 100%;
545
876
  max-width: calc(var(--content-max) + 64px);
546
877
  margin: 0 auto;
547
- padding: 24px 32px 64px;
878
+ padding: 26px 22px 108px;
548
879
  flex: 1;
549
880
  }
550
881
 
551
- .app-views .view > .view-zone + .view-zone { margin-top: var(--space-4); }
882
+ .app-views .view > .view-zone + .view-zone { margin-top: var(--sp-md); }
552
883
  .view-zone--band {
553
884
  display: grid;
554
885
  grid-template-columns: 1fr 1fr;
555
- gap: var(--space-4);
886
+ gap: var(--sp-md);
556
887
  align-items: start;
557
888
  }
558
889
  .view-zone--band > .scroll { margin: 0; }
@@ -564,11 +895,13 @@ button { font-family: var(--font-body); }
564
895
  gap: 12px;
565
896
  padding: 18px 4px 0;
566
897
  margin-top: 24px;
567
- border-top: 1px solid var(--border);
898
+ border-top: 1px solid var(--rule-soft);
568
899
  font-size: 12.5px;
569
900
  color: var(--ink-faint);
570
901
  }
571
- .app-foot a { color: var(--ink-faint); }
902
+ /* 3px of vertical padding so a link in a row with no rhythm of its own still
903
+ clears the 24px control floor (DESIGN.md §The footer). */
904
+ .app-foot a { color: var(--ink-faint); display: inline-block; padding: 3px 0; min-height: 24px; }
572
905
  .app-foot a:hover { color: var(--ink); }
573
906
  .app-foot span { margin-right: auto; }
574
907
 
@@ -576,54 +909,67 @@ button { font-family: var(--font-body); }
576
909
  position: fixed;
577
910
  inset: 0;
578
911
  z-index: var(--z-scrim);
579
- background: color-mix(in srgb, var(--ink) 40%, transparent);
912
+ background: rgba(0, 0, 0, .55);
580
913
  }
581
914
 
582
- /* ---------- responsive shell (scope §10) ---------- */
583
-
584
- @media (max-width: 1024px) {
585
- .app-sidebar { width: var(--sidebar-w-rail); }
586
- .app-body { margin-left: var(--sidebar-w-rail); }
587
- .brand-mark__name,
588
- .nav-item__label,
589
- .nav-group__head,
590
- .shell-user__meta { display: none; }
591
- .nav-item, .shell-home, .shell-signout { justify-content: center; padding: 0; }
592
- .nav-item--active::before { left: -6px; }
593
- .shell-user { justify-content: center; padding: 2px 0; }
594
- .shell-signin { font-size: 0; padding: 0; height: 34px; }
595
- .shell-signin::after { content: "→"; font-size: 15px; }
915
+ /* Focus: the world's two-tone ring — a 2px accent-derived outline at 3px offset
916
+ inside an 8px --bg halo, so the indicator keeps its own edge over whatever it
917
+ lands on (a bare outline measured 1.1:1 over the light bar). The rail's items
918
+ ring inside themselves instead, or the halo would spill out of the 64px strip. */
919
+ :focus-visible {
920
+ outline: 2px solid var(--focus);
921
+ outline-offset: 3px;
922
+ box-shadow: 0 0 0 8px var(--bg);
923
+ border-radius: var(--radius-sm);
596
924
  }
925
+ .nav-item:focus-visible,
926
+ .shell-user:focus-visible,
927
+ .shell-home:focus-visible,
928
+ .shell-signout:focus-visible {
929
+ outline-offset: -2px;
930
+ box-shadow: none;
931
+ }
932
+
933
+ /* ---------- responsive shell (scope §10) ----------
934
+ The rail holds at every width (v3), so there is no 1024px collapse any more.
935
+ What is left is the phone: under 768 the rail becomes the drawer shell.js
936
+ already owns, and the hamburger appears to open it. */
597
937
 
598
938
  @media (max-width: 768px) {
599
939
  .app-sidebar {
600
- width: var(--sidebar-w);
940
+ width: var(--rail-open);
601
941
  transform: translateX(-100%);
602
942
  transition: transform 160ms ease;
603
943
  z-index: var(--z-drawer);
604
944
  }
945
+ /* The drawer is the rail already open — the hover expansion is off, because
946
+ there is no hover on a phone and a drawer that needed one could not open. */
947
+ .app-sidebar:hover,
948
+ .app-sidebar:focus-within { width: var(--rail-open); }
605
949
  html.drawer-open .app-sidebar { transform: translateX(0); box-shadow: var(--shadow-pop); }
606
950
  .app-body { margin-left: 0; }
607
- .brand-mark__name, .nav-item__label, .nav-group__head, .shell-user__meta { display: block; }
608
- .nav-item, .shell-home, .shell-signout { justify-content: flex-start; padding: 0 10px; }
951
+ .brand-mark__name, .nav-item__label, .shell-user__meta { display: block; }
952
+ .nav-group--collapsible .nav-group__head { display: flex; }
953
+ .app-sidebar__brand { justify-content: flex-start; padding: 0 22px; }
954
+ .nav-item, .shell-home, .shell-signout { justify-content: flex-start; padding: 0 12px; }
955
+ .app-sidebar__foot { align-items: stretch; padding: 12px 16px; }
609
956
  .shell-user { justify-content: flex-start; }
610
- .shell-signin { font-size: 13.5px; padding: 0 10px; }
611
- .shell-signin::after { content: none; }
612
957
  .topbar-menu { display: flex; }
613
958
  .topbar-note { display: none; }
614
- .app-content { padding: 16px 16px 48px; }
959
+ .app-content { padding: 18px 14px 48px; }
615
960
  .view-zone--band { grid-template-columns: 1fr; }
616
961
  }
617
962
 
618
963
  @media (max-width: 480px) {
619
- .app-content { padding: 12px 10px 40px; }
964
+ .app-content { padding: 14px 12px 40px; }
620
965
  }
621
966
 
622
- @media (prefers-reduced-motion: reduce) {
623
- * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
624
- }
625
-
626
- /* ---------- cards (the .scroll legacy name is load-bearing) ---------- */
967
+ /* ---------- cards (the .scroll legacy name is load-bearing) ----------
968
+ v3's panels: --bg-card at the full --radius, with TONE doing the separation
969
+ and no hairline edge. The world's "hairlines, not containers" rule is a
970
+ poster's rule; the owner's pick answers the container question for an
971
+ application surface with yes, and the compromise the pick makes is that a
972
+ panel is a tone, never a drawn box and never a shadow. */
627
973
 
628
974
  /* The three sanctioned card levels (task 2179). Anything that looks like a card
629
975
  is one of these — a fourth variant is a design decision, not a local override:
@@ -631,11 +977,11 @@ button { font-family: var(--font-body); }
631
977
  .scroll--quiet secondary — a card INSIDE a card, or supporting detail
632
978
  .scroll--bare none — layout-only, for stat rows and grids */
633
979
  .scroll {
634
- background: var(--surface);
635
- border: 1px solid var(--border);
980
+ background: var(--bg-card);
981
+ border: 1px solid transparent;
636
982
  border-radius: var(--radius);
637
- padding: var(--space-4);
638
- margin: 0 0 var(--space-4);
983
+ padding: var(--sp-md);
984
+ margin: 0 0 var(--sp-md);
639
985
  /* anchor jumps must clear the 56px sticky top bar */
640
986
  scroll-margin-top: calc(var(--topbar-h) + 16px);
641
987
  }
@@ -646,88 +992,114 @@ button { font-family: var(--font-body); }
646
992
  border-radius: 0;
647
993
  padding: 0;
648
994
  }
649
- /* Secondary level: recedes against a primary card via surface-2 and a quieter,
650
- smaller header. Use for supporting detail — not to fit more onto Home. */
995
+ /* Secondary level: a card inside a card recedes by tone, one step further from
996
+ the ground than its parent. Use for supporting detail — not to fit more onto
997
+ Home. */
651
998
  .scroll--quiet {
652
999
  background: var(--surface-2);
653
- padding: var(--space-3);
1000
+ padding: var(--sp-sm);
654
1001
  }
655
1002
  .scroll--quiet > .scroll__h {
656
- font-size: 13.5px;
1003
+ font-size: 14px;
657
1004
  color: var(--ink-soft);
658
1005
  }
659
1006
 
1007
+ /* Section headings take the display voice in sentence case — v3's own
1008
+ divergence from the mono label, picked with it. */
660
1009
  .scroll__h {
661
- margin: 0 0 6px;
1010
+ margin: 0 0 8px;
662
1011
  font-family: var(--font-display);
663
- font-size: 15px;
664
- font-weight: 600;
665
- letter-spacing: -0.01em;
1012
+ font-weight: 300;
1013
+ font-size: 21px;
1014
+ line-height: 1.15;
1015
+ letter-spacing: -0.03em;
1016
+ color: var(--ink);
1017
+ text-shadow: var(--type-halo);
666
1018
  }
667
1019
  .scroll__lede {
668
1020
  margin: 0 0 14px;
669
1021
  font-size: 13.5px;
670
1022
  color: var(--ink-soft);
671
- max-width: 68ch;
1023
+ max-width: var(--measure);
672
1024
  }
673
1025
  .card-foot {
674
- margin: 12px 0 0;
1026
+ margin: 14px 0 0;
675
1027
  font-size: 13px;
676
1028
  }
677
1029
 
678
1030
  .pill-link,
679
1031
  #welcome-reopen a {
680
- display: inline-block;
681
- font-size: 12.5px;
682
- font-weight: 500;
1032
+ display: inline-flex;
1033
+ align-items: center;
1034
+ min-height: 28px;
1035
+ font-size: 13px;
1036
+ text-transform: lowercase;
683
1037
  color: var(--ink-soft);
684
- background: var(--surface);
685
- border: 1px solid var(--border);
686
- border-radius: 999px;
687
- padding: 4px 12px;
1038
+ background: none;
1039
+ border: 1px solid var(--rule);
1040
+ border-radius: var(--radius-pill);
1041
+ padding: 0 12px;
688
1042
  }
689
1043
  #welcome-reopen { margin: 0; }
690
- .pill-link:hover { color: var(--ink); text-decoration: none; background: var(--surface-2); }
1044
+ .pill-link:hover { color: var(--ink); text-decoration: none; border-color: var(--ink-faint); }
691
1045
 
692
- /* ---------- stat tiles (Home) ---------- */
1046
+ /* ---------- stat tiles (Home) ----------
1047
+ v3's signature: ONE glass band of figures divided by vertical hairlines, the
1048
+ owner's dark reference exactly, rather than four separate objects. The figure
1049
+ is the display face at 300 with tabular numerals — every number the ledger
1050
+ produced holds its column when it refreshes (DESIGN.md, The Tabular Rule). */
693
1051
 
694
1052
  .stat-row {
695
1053
  display: grid;
696
1054
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
697
- gap: 16px;
1055
+ gap: 0;
1056
+ background: var(--glass);
1057
+ border: 1px solid var(--glass-edge);
1058
+ border-radius: var(--radius);
1059
+ backdrop-filter: blur(var(--glass-blur));
1060
+ -webkit-backdrop-filter: blur(var(--glass-blur));
1061
+ overflow: hidden;
698
1062
  }
699
1063
  .stat-tile {
700
- background: var(--surface);
701
- border: 1px solid var(--border);
702
- border-radius: var(--radius);
703
- padding: 14px 18px;
1064
+ background: none;
1065
+ border: 0;
1066
+ border-left: 1px solid var(--glass-edge);
1067
+ border-radius: 0;
1068
+ padding: 20px 22px;
704
1069
  min-height: 88px;
705
1070
  display: flex;
706
1071
  flex-direction: column;
707
1072
  justify-content: center;
708
- gap: 2px;
1073
+ gap: 8px;
709
1074
  }
1075
+ .stat-tile:first-child { border-left: 0; }
710
1076
  .stat-tile__label {
711
- font-size: 11px;
712
- font-weight: 600;
713
- letter-spacing: 0.06em;
714
- text-transform: uppercase;
1077
+ font-family: var(--label-face);
1078
+ font-size: var(--label-size);
1079
+ letter-spacing: var(--label-space);
1080
+ text-transform: var(--label-case);
715
1081
  color: var(--ink-faint);
716
1082
  }
717
1083
  .stat-tile__num {
718
- font-size: 26px;
719
- font-weight: 650;
720
- letter-spacing: -0.02em;
1084
+ font-family: var(--font-display);
1085
+ font-size: clamp(26px, 2.6vw, 34px);
1086
+ font-weight: 300;
1087
+ letter-spacing: -0.035em;
721
1088
  font-variant-numeric: tabular-nums;
722
- line-height: 1.15;
1089
+ line-height: 1;
1090
+ color: var(--ink);
1091
+ text-shadow: var(--type-halo);
723
1092
  }
724
- .stat-tile__num--badge { font-size: 15px; }
725
- .stat-tile__sub { font-size: 12px; color: var(--ink-faint); }
1093
+ .stat-tile__num--badge { font-size: 15px; letter-spacing: -0.02em; }
1094
+ .stat-tile__sub { font-size: 12.5px; color: var(--ink-faint); }
726
1095
 
727
1096
  @media (max-width: 480px) {
728
- .stat-row { grid-template-columns: 1fr 1fr; gap: 10px; }
729
- .stat-tile { padding: 12px 14px; min-height: 72px; }
730
- .stat-tile__num { font-size: 21px; }
1097
+ .stat-row { grid-template-columns: 1fr 1fr; }
1098
+ .stat-tile { padding: 14px 16px; min-height: 72px; gap: 6px; }
1099
+ .stat-tile__num { font-size: clamp(21px, 6.6vw, 28px); }
1100
+ /* On two columns the band still divides, but now on both axes. */
1101
+ .stat-tile:nth-child(odd) { border-left: 0; }
1102
+ .stat-tile:nth-child(n + 3) { border-top: 1px solid var(--glass-edge); }
731
1103
  }
732
1104
 
733
1105
  /* ---------- Standing layout: section rail + panels ---------- */
@@ -746,15 +1118,28 @@ button { font-family: var(--font-body); }
746
1118
  gap: 2px;
747
1119
  }
748
1120
  .rail-link {
749
- display: block;
750
- padding: 6px 10px;
1121
+ display: flex;
1122
+ align-items: center;
1123
+ min-height: 28px;
1124
+ padding: 0 10px;
751
1125
  border-radius: var(--radius-sm);
752
1126
  font-size: 13px;
753
- font-weight: 500;
1127
+ text-transform: lowercase;
754
1128
  color: var(--ink-soft);
1129
+ position: relative;
1130
+ }
1131
+ .rail-link:hover { background: var(--glass); color: var(--ink); text-decoration: none; }
1132
+ /* The current section is live, so it takes the accent — the same lit-step
1133
+ grammar the rail’s nav items use. */
1134
+ .rail-link--active { background: var(--glass-strong); color: var(--ink); }
1135
+ .rail-link--active::before {
1136
+ content: "";
1137
+ position: absolute;
1138
+ left: 0; top: 6px; bottom: 6px;
1139
+ width: 2px;
1140
+ border-radius: 1px;
1141
+ background: var(--accent);
755
1142
  }
756
- .rail-link:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
757
- .rail-link--active { background: var(--accent-weak); color: var(--accent-text); }
758
1143
 
759
1144
  /* task 2174: a Standing rail section — the scroll-spy anchor holding one or
760
1145
  more cards. The extra air between groups is what makes the grouping legible
@@ -772,11 +1157,11 @@ button { font-family: var(--font-body); }
772
1157
  overflow-x: auto;
773
1158
  gap: 6px;
774
1159
  padding: 8px 0;
775
- background: color-mix(in srgb, var(--bg) 92%, transparent);
776
- backdrop-filter: blur(6px);
1160
+ background: var(--bg);
777
1161
  }
778
- .rail-link { white-space: nowrap; border: 1px solid var(--border); background: var(--surface); border-radius: 999px; padding: 4px 12px; font-size: 12.5px; }
779
- .rail-link--active { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
1162
+ .rail-link { white-space: nowrap; border: 1px solid var(--rule); background: none; border-radius: var(--radius-pill); min-height: 26px; padding: 0 12px; font-size: 12.5px; }
1163
+ .rail-link--active { border-color: var(--accent); background: none; }
1164
+ .rail-link--active::before { content: none; }
780
1165
  }
781
1166
 
782
1167
  /* ---------- legacy page chrome compat (pre-shell pages) ---------- */
@@ -871,28 +1256,30 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
871
1256
  .ledger th {
872
1257
  position: sticky;
873
1258
  top: var(--topbar-h);
874
- background: var(--surface);
1259
+ background: var(--bg-card);
875
1260
  text-align: left;
876
- font-size: 11px;
877
- font-weight: 600;
878
- letter-spacing: 0.06em;
879
- text-transform: uppercase;
1261
+ font-family: var(--label-face);
1262
+ font-size: var(--label-size);
1263
+ font-weight: 400;
1264
+ letter-spacing: var(--label-space);
1265
+ text-transform: var(--label-case);
880
1266
  color: var(--ink-faint);
881
- padding: 8px 12px;
882
- border-bottom: 1px solid var(--border-strong);
1267
+ padding: 0 12px 8px 0;
1268
+ border-bottom: 1px solid var(--rule);
883
1269
  z-index: 1;
884
1270
  }
885
1271
  .ledger td {
886
- padding: 12px;
1272
+ padding: 14px 12px 14px 0;
887
1273
  border-bottom: 1px solid var(--rule-soft);
888
1274
  vertical-align: top;
889
1275
  }
1276
+ .ledger th:last-child, .ledger td:last-child { padding-right: 0; }
890
1277
  .ledger tr:last-child td { border-bottom: none; }
891
- .ledger tbody tr:hover td { background: var(--surface-2); }
1278
+ .ledger tbody tr:hover td { background: var(--glass); }
892
1279
  /* task 1467: a builder name that links to their profile page reads as plain
893
1280
  text in the dense leaderboard/roster, revealing itself as a link on hover. */
894
1281
  .lb-name { color: inherit; }
895
- .lb-name:hover { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }
1282
+ .lb-name:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 3px; }
896
1283
  .ledger__empty {
897
1284
  color: var(--ink-faint);
898
1285
  font-size: 13px;
@@ -956,16 +1343,17 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
956
1343
  .filter {
957
1344
  display: flex;
958
1345
  flex-direction: column;
959
- gap: 3px;
960
- font-size: 11px;
961
- font-weight: 600;
962
- letter-spacing: 0.06em;
963
- text-transform: uppercase;
1346
+ gap: 4px;
1347
+ font-family: var(--label-face);
1348
+ font-size: var(--label-size);
1349
+ letter-spacing: var(--label-space);
1350
+ text-transform: var(--label-case);
964
1351
  color: var(--ink-faint);
965
1352
  }
966
1353
  .filter select {
967
1354
  min-width: 150px;
968
- height: 32px;
1355
+ min-height: 32px;
1356
+ font-family: var(--font-body);
969
1357
  padding: 4px 8px;
970
1358
  font-size: 13.5px;
971
1359
  text-transform: none;
@@ -976,7 +1364,8 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
976
1364
  selects it sits alongside. */
977
1365
  .filter input[type="search"] {
978
1366
  min-width: 200px;
979
- height: 32px;
1367
+ min-height: 32px;
1368
+ font-family: var(--font-body);
980
1369
  padding: 4px 8px;
981
1370
  font-size: 13.5px;
982
1371
  text-transform: none;
@@ -999,24 +1388,51 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
999
1388
  gap: 12px;
1000
1389
  margin-top: 12px;
1001
1390
  }
1002
- .pager__range { font-size: 12.5px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
1391
+ .pager__range { font-family: var(--font-mono); font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
1003
1392
 
1393
+ /* Pill or nothing (DESIGN.md, Shapes). A ghost button is a hairline pill; the
1394
+ one filled shape on a page is the accent door, and only where the action is
1395
+ the live thing. 28px of box plus the row’s own air clears the 24px floor. */
1004
1396
  .btn-ghost {
1005
1397
  display: inline-flex;
1006
1398
  align-items: center;
1399
+ justify-content: center;
1007
1400
  gap: 6px;
1008
- height: 30px;
1009
- padding: 0 12px;
1401
+ min-height: 30px;
1402
+ min-width: 56px;
1403
+ padding: 0 14px;
1010
1404
  font-size: 13px;
1011
- font-weight: 500;
1405
+ text-transform: lowercase;
1012
1406
  color: var(--ink-soft);
1013
- background: var(--surface);
1014
- border: 1px solid var(--border-strong);
1015
- border-radius: var(--radius-sm);
1407
+ background: none;
1408
+ border: 1px solid var(--rule);
1409
+ border-radius: var(--radius-pill);
1410
+ cursor: pointer;
1411
+ }
1412
+ .btn-ghost:hover:not(:disabled) { border-color: var(--ink-faint); color: var(--ink); text-decoration: none; }
1413
+ .btn-ghost:disabled { color: var(--ink-faint); border-color: var(--rule-soft); cursor: default; }
1414
+
1415
+ /* The accent door: the page’s one primary action. --accent-on is the dark ink
1416
+ that goes on the salmon; white on it fails AA and is banned. */
1417
+ .btn-accent {
1418
+ display: inline-flex;
1419
+ align-items: center;
1420
+ justify-content: center;
1421
+ gap: 6px;
1422
+ min-height: 34px;
1423
+ min-width: 74px;
1424
+ padding: 0 16px;
1425
+ font-size: 13.5px;
1426
+ font-weight: 500;
1427
+ text-transform: lowercase;
1428
+ color: var(--accent-on);
1429
+ background: var(--accent);
1430
+ border: 0;
1431
+ border-radius: var(--radius-pill);
1016
1432
  cursor: pointer;
1017
1433
  }
1018
- .btn-ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); text-decoration: none; }
1019
- .btn-ghost:disabled { opacity: 0.45; cursor: not-allowed; }
1434
+ .btn-accent:hover:not(:disabled) { background: var(--accent-lift); color: var(--accent-on); text-decoration: none; }
1435
+ .btn-accent:disabled { background: var(--surface-2); color: var(--ink-faint); cursor: default; }
1020
1436
 
1021
1437
  /* ---------- profile / standing ---------- */
1022
1438
 
@@ -1139,7 +1555,7 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1139
1555
  .conn-reqs { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
1140
1556
  .conn-req {
1141
1557
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
1142
- padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-1);
1558
+ padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2);
1143
1559
  }
1144
1560
  .conn-req__note { flex: 1 1 100%; margin: 0; font-size: 13px; color: var(--ink-soft); }
1145
1561
  .conn-req__state { font-size: 13px; color: var(--ink-faint); }
@@ -1429,20 +1845,29 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1429
1845
 
1430
1846
  .setup { margin-top: 12px; }
1431
1847
  .setup__intro { font-size: 13px; color: var(--ink-soft); }
1432
- .setup__toggle { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); overflow: hidden; margin: 8px 0 12px; }
1848
+ .setup__toggle { display: flex; border: 0; border-bottom: 1px solid var(--rule); margin: 8px 0 12px; flex-wrap: wrap; }
1849
+ /* An open tab is the live thing in the strip, so it is what the accent lights:
1850
+ a 2px underline sitting on the strip’s own hairline. */
1433
1851
  .setup__tab {
1434
- border: none;
1435
- background: var(--surface);
1852
+ display: inline-flex;
1853
+ align-items: baseline;
1854
+ gap: 8px;
1855
+ min-height: 38px;
1856
+ border: 0;
1857
+ border-bottom: 2px solid transparent;
1858
+ margin-bottom: -1px;
1859
+ background: none;
1436
1860
  color: var(--ink-soft);
1437
- font-size: 13px;
1438
- font-weight: 500;
1439
- padding: 6px 14px;
1861
+ font-size: 14px;
1862
+ text-transform: lowercase;
1863
+ padding: 0 16px;
1440
1864
  cursor: pointer;
1441
1865
  }
1442
- .setup__tab + .setup__tab { border-left: 1px solid var(--border-strong); }
1443
- .setup__tab[aria-selected="true"] { background: var(--accent-weak); color: var(--accent-text); }
1444
- .setup__tab-tag { font-size: 10.5px; margin-left: 6px; color: var(--ink-faint); }
1445
- .setup__tab-tag--rec { color: var(--ok); font-weight: 600; }
1866
+ .setup__tab:first-child { padding-left: 0; }
1867
+ .setup__tab:hover { color: var(--ink); }
1868
+ .setup__tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }
1869
+ .setup__tab-tag { font-family: var(--label-face); font-size: 10.5px; letter-spacing: var(--label-space); text-transform: var(--label-case); margin-left: 6px; color: var(--ink-faint); }
1870
+ .setup__tab-tag--rec { color: var(--ok); }
1446
1871
  .setup__pane { display: none; }
1447
1872
  .setup[data-config="desktop"] .setup__pane--desktop { display: block; }
1448
1873
  .setup[data-config="online"] .setup__pane--online { display: block; }
@@ -1537,40 +1962,50 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1537
1962
 
1538
1963
  /* ---------- landing (signed-out) ---------- */
1539
1964
 
1540
- .landing { max-width: 560px; }
1965
+ .landing { max-width: var(--measure); }
1541
1966
  .landing__lede { font-size: 14px; color: var(--ink-soft); }
1967
+ /* A link inside landing prose is still a control: 3px of vertical padding is
1968
+ what clears the 24px floor in a line that has no rhythm of its own
1969
+ (DESIGN.md §The footer applies the same rule to its links). */
1970
+ .landing p a:not(.landing__btn) {
1971
+ display: inline-block;
1972
+ padding: 3px 0;
1973
+ min-height: 24px;
1974
+ line-height: 18px;
1975
+ }
1542
1976
  .landing__cta { margin: 14px 0; }
1543
1977
  .landing__btn {
1544
1978
  display: inline-flex;
1545
1979
  align-items: center;
1546
1980
  justify-content: center;
1547
- height: 36px;
1548
- padding: 0 18px;
1549
- border-radius: var(--radius-sm);
1550
- border: 1px solid var(--accent);
1981
+ min-height: 46px;
1982
+ padding: 0 22px;
1983
+ border-radius: var(--radius-pill);
1984
+ border: 0;
1551
1985
  background: var(--accent);
1552
- color: #fff;
1553
- font-size: 13.5px;
1554
- font-weight: 600;
1986
+ color: var(--accent-on);
1987
+ font-size: 14px;
1988
+ font-weight: 500;
1989
+ text-transform: lowercase;
1555
1990
  cursor: pointer;
1556
1991
  }
1557
- .landing__btn:hover { background: var(--accent-deep); text-decoration: none; color: #fff; }
1992
+ .landing__btn:hover { background: var(--accent-lift); text-decoration: none; color: var(--accent-on); }
1558
1993
  .landing__note { font-size: 12.5px; color: var(--ink-faint); }
1559
1994
  .landing__error { color: var(--danger); }
1560
1995
  .landing__error-h { color: var(--danger); margin: 0 0 4px; font-size: 14px; }
1561
1996
  .landing__request {
1562
- border: 1px solid var(--border);
1997
+ border: 0;
1563
1998
  border-radius: var(--radius);
1564
1999
  background: var(--surface-2);
1565
- padding: 16px 18px;
2000
+ padding: 18px 20px;
1566
2001
  margin: 16px 0;
1567
2002
  }
1568
- .landing__request-h { margin: 0 0 4px; font-size: 14px; font-weight: 650; }
2003
+ .landing__request-h { margin: 0 0 6px; font-family: var(--font-display); font-size: 18px; font-weight: 300; letter-spacing: -0.025em; }
1569
2004
  .landing__request-lede { margin: 0 0 12px; font-size: 13px; color: var(--ink-soft); }
1570
2005
  .landing__request-lede--soft { color: var(--ink-faint); }
1571
2006
  .landing__form { display: flex; flex-direction: column; gap: 10px; }
1572
- .landing__field { display: flex; flex-direction: column; gap: 3px; font-size: 12.5px; font-weight: 500; color: var(--ink-soft); }
1573
- .landing__field input { background: var(--surface); }
2007
+ .landing__field { display: flex; flex-direction: column; gap: 4px; font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-faint); }
2008
+ .landing__field input { font-family: var(--font-body); font-size: 14px; text-transform: none; letter-spacing: normal; }
1574
2009
  .landing__req-mark { color: var(--danger); }
1575
2010
  .landing__opt { color: var(--ink-faint); font-weight: 400; }
1576
2011
  .landing__form-hint { font-size: 12.5px; color: var(--ink-faint); margin: 0; }
@@ -1748,10 +2183,10 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1748
2183
  transform: translate(-50%, 12px);
1749
2184
  z-index: 100;
1750
2185
  background: var(--ink);
1751
- color: var(--bg-card);
2186
+ color: var(--bg);
1752
2187
  font-size: 13.5px;
1753
- padding: 10px 18px;
1754
- border-radius: var(--radius);
2188
+ padding: 11px 20px;
2189
+ border-radius: var(--radius-pill);
1755
2190
  box-shadow: var(--shadow-pop);
1756
2191
  opacity: 0;
1757
2192
  visibility: hidden;
@@ -1782,7 +2217,7 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1782
2217
  position: fixed;
1783
2218
  inset: 0;
1784
2219
  z-index: 2000;
1785
- background: color-mix(in srgb, var(--ink) 45%, transparent);
2220
+ background: rgba(0, 0, 0, .55);
1786
2221
  display: flex;
1787
2222
  align-items: center;
1788
2223
  justify-content: center;
@@ -1790,8 +2225,8 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1790
2225
  }
1791
2226
  .deeplink-card__panel {
1792
2227
  position: relative;
1793
- background: var(--surface);
1794
- border: 1px solid var(--border);
2228
+ background: var(--bg-card);
2229
+ border: 1px solid var(--rule);
1795
2230
  border-radius: var(--radius);
1796
2231
  box-shadow: var(--shadow-pop);
1797
2232
  max-width: 560px;
@@ -1804,15 +2239,15 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1804
2239
  position: absolute;
1805
2240
  top: 10px; right: 10px;
1806
2241
  width: 28px; height: 28px;
1807
- border: 1px solid var(--border);
1808
- border-radius: var(--radius-sm);
1809
- background: var(--surface);
2242
+ border: 1px solid var(--rule);
2243
+ border-radius: var(--radius-pill);
2244
+ background: none;
1810
2245
  color: var(--ink-soft);
1811
2246
  font-size: 16px;
1812
2247
  line-height: 1;
1813
2248
  cursor: pointer;
1814
2249
  }
1815
- .deeplink-card__close:hover { background: var(--surface-2); }
2250
+ .deeplink-card__close:hover { border-color: var(--ink-faint); color: var(--ink); }
1816
2251
  .deeplink-card__kind {
1817
2252
  font-size: 11px;
1818
2253
  font-weight: 600;
@@ -1821,7 +2256,7 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1821
2256
  color: var(--ink-faint);
1822
2257
  margin: 0 0 4px;
1823
2258
  }
1824
- .deeplink-card__title { font-size: 16px; font-weight: 650; margin: 0 0 8px; }
2259
+ .deeplink-card__title { font-family: var(--font-display); font-size: 21px; font-weight: 300; letter-spacing: -0.03em; margin: 0 0 8px; }
1825
2260
  .deeplink-card__meta { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12.5px; color: var(--ink-soft); margin: 0 0 10px; }
1826
2261
  .deeplink-card__summary { font-size: 13.5px; margin: 0 0 10px; }
1827
2262
  .deeplink-card__note { font-size: 12.5px; color: var(--ink-faint); margin: 0; }
@@ -1995,40 +2430,47 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1995
2430
  /* filter-bar disclosure ("Filters" button) + removable active-filter chips */
1996
2431
  .filters__more-count { margin-left: 6px; }
1997
2432
  .filters--overflow {
1998
- padding: 10px 12px;
1999
- border: 1px dashed var(--border-strong);
2433
+ padding: 12px 14px;
2434
+ border: 0;
2000
2435
  border-radius: var(--radius-sm);
2001
2436
  background: var(--surface-2);
2002
2437
  margin-bottom: 14px;
2003
2438
  }
2004
2439
  .filter-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
2440
+ /* An active filter IS live, so the chip is the one place in a filter bar the
2441
+ accent belongs. */
2005
2442
  .chip {
2006
- display: inline-flex; align-items: center; gap: 6px;
2007
- padding: 3px 10px;
2443
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
2444
+ min-height: 26px;
2445
+ padding: 0 12px;
2008
2446
  font-size: 12.5px;
2009
- color: var(--accent-text);
2010
- background: var(--accent-weak);
2011
- border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
2012
- border-radius: 999px;
2447
+ text-transform: lowercase;
2448
+ color: var(--accent-ink);
2449
+ background: var(--accent-tint);
2450
+ border: 0;
2451
+ border-radius: var(--radius-pill);
2013
2452
  cursor: pointer;
2014
2453
  }
2015
- .chip:hover { background: color-mix(in srgb, var(--accent) 16%, var(--bg-card)); }
2454
+ .chip:hover { color: var(--ink); }
2016
2455
 
2017
2456
  /* tab strip — the .setup__tab idiom in the .setup__toggle frame, promoted */
2457
+ /* A tab strip is hairlines, not a segmented box: one rule under the row and a
2458
+ 2px accent underline on the open tab, the lit-step grammar again. */
2018
2459
  .kit-tabs {
2019
- display: inline-flex;
2460
+ display: flex;
2020
2461
  max-width: 100%;
2021
2462
  overflow-x: auto;
2022
- border: 1px solid var(--border-strong);
2023
- border-radius: var(--radius-sm);
2463
+ border: 0;
2464
+ border-bottom: 1px solid var(--rule);
2465
+ border-radius: 0;
2024
2466
  margin: 0 0 14px;
2025
2467
  }
2026
2468
  .kit-tabs .setup__tab { white-space: nowrap; }
2027
2469
  .kit-tabs .setup__tab .count-pill { margin-left: 6px; }
2028
2470
 
2029
2471
  /* empty state — one short line + at most one action link */
2030
- .empty-state { padding: 6px 0; }
2031
- .empty-state p { margin: 0; font-size: 13px; color: var(--ink-faint); }
2472
+ .empty-state { padding: 8px 0; }
2473
+ .empty-state p { margin: 0; font-size: 13.5px; color: var(--ink-soft); }
2032
2474
  .empty-state__action { display: inline-block; margin-top: 6px; font-size: 13px; }
2033
2475
 
2034
2476
  /* skeleton shimmer — standard-height placeholder rows while data loads, so
@@ -2038,22 +2480,10 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
2038
2480
  width: 60%;
2039
2481
  min-width: 80px;
2040
2482
  height: 12px;
2041
- border-radius: 6px;
2042
- background: linear-gradient(90deg,
2043
- color-mix(in srgb, var(--ink) 7%, var(--bg-card)) 25%,
2044
- color-mix(in srgb, var(--ink) 13%, var(--bg-card)) 45%,
2045
- color-mix(in srgb, var(--ink) 7%, var(--bg-card)) 65%);
2046
- background-size: 200% 100%;
2047
- animation: kit-skel-shimmer 1.2s ease-in-out infinite;
2483
+ border-radius: 2px;
2484
+ background: var(--skeleton);
2048
2485
  }
2049
2486
  .kit-skel-row td { border-bottom: 1px solid var(--rule-soft); }
2050
- @keyframes kit-skel-shimmer {
2051
- from { background-position: 120% 0; }
2052
- to { background-position: -80% 0; }
2053
- }
2054
- @media (prefers-reduced-motion: reduce) {
2055
- .kit-skel { animation: none; }
2056
- }
2057
2487
 
2058
2488
  /* collapsible card body — the Work-backlog <details> idiom, generalized */
2059
2489
  .kit-collapse__summary {
@@ -2068,11 +2498,8 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
2068
2498
  /* ---- Home dashboard (task 2169) ---- */
2069
2499
  /* Stat tiles are links now — inherit ink, reveal affordance on hover. */
2070
2500
  a.stat-tile { color: inherit; }
2071
- a.stat-tile:hover {
2072
- text-decoration: none;
2073
- border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
2074
- background: color-mix(in srgb, var(--accent) 3%, var(--surface));
2075
- }
2501
+ a.stat-tile:hover { text-decoration: none; background: var(--glass); }
2502
+ a.stat-tile:hover .stat-tile__sub { color: var(--ink-soft); }
2076
2503
  /* The over-budget "More" row — one quiet line, not a card. */
2077
2504
  .home-more { margin: 0; font-size: 13px; color: var(--ink-soft); }
2078
2505
 
@@ -2096,14 +2523,13 @@ a.stat-tile:hover {
2096
2523
  display: flex;
2097
2524
  flex-direction: column;
2098
2525
  gap: 2px;
2099
- padding: 9px 12px;
2100
- border: 1px solid var(--border);
2101
- border-left: 3px solid color-mix(in srgb, var(--accent) 55%, var(--border));
2526
+ padding: 10px 14px;
2527
+ border: 0;
2102
2528
  border-radius: var(--radius-sm);
2103
2529
  background: var(--surface-2);
2104
2530
  }
2105
- .next-up__title { font-weight: 600; font-size: 13.5px; color: var(--ink); }
2106
- .next-up__title:hover { color: var(--accent-text); }
2531
+ .next-up__title { font-weight: 500; font-size: 13.5px; color: var(--ink); }
2532
+ .next-up__title:hover { color: var(--accent-ink); }
2107
2533
  .next-up__meta { font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
2108
2534
  .next-up__foot { margin: 0 0 14px; font-size: 13px; }
2109
2535