great-cto 3.2.0 → 3.4.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.
@@ -123,17 +123,106 @@
123
123
  --shadow-card-hover: 0 1px 3px rgba(0, 0, 0, 0.12), 0 8px 24px rgba(0, 168, 98, 0.08);
124
124
  --drawer-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
125
125
  }
126
+ /* ── Type scale ──────────────────────────────────────────────────────────────
127
+ The board had 22 raw font sizes (9px…52px, including 11.5/12.5/13.5) and no
128
+ rule for h1/h2/h3 at all — so Budgets, Docs, Sessions and Activity rendered
129
+ their titles in the browser's default bold 700, a weight used nowhere else in
130
+ the design. One role, six renderings.
131
+
132
+ Two ramps, because the board has two kinds of text. Text steps by +1px in the
133
+ dense band (this is a single-operator console; density is the point) and by
134
+ ~1.2x in the title band. Live numbers get their own ramp: they are read as
135
+ quantities, not prose, so they take --serif at 500 with tabular-nums.
136
+
137
+ --serif is NOT a serif face — it resolves to the same Geist as --sans. The
138
+ name is legacy; the role is "display numerals". Reach for it only at >=24px.
139
+
140
+ Legal 10px: --fs-eyebrow, and only for mono uppercase labels with tracking.
141
+ 10px in sans body text is not a size, it is a mistake. */
142
+ :root {
143
+ --fs-eyebrow: 10px; /* mono uppercase label, tracking 0.08–0.1em */
144
+ --fs-caption: 11px; /* metadata, counts, timestamps */
145
+ --fs-small: 12px; /* table cells, secondary meta, chips */
146
+ --fs-body: 13px; /* dense body: cards, list rows, form fields */
147
+ --fs-base: 14px; /* document base / longform prose */
148
+ --fs-title-s: 15px; /* card or section heading (h3 role) */
149
+ --fs-title-m: 18px; /* region heading (h2 role) */
150
+ --fs-title-l: 22px; /* page heading (h1 role) */
151
+
152
+ --fs-num-s: 24px; /* inline stat */
153
+ --fs-num-m: 30px; /* secondary metric card */
154
+ --fs-num-l: 36px; /* second-tier hero KPI */
155
+ --fs-num-xl: 52px; /* primary hero KPI */
156
+ }
157
+
126
158
  * { box-sizing: border-box; margin: 0; padding: 0; }
127
159
  html, body { height: 100%; }
128
160
  body {
129
161
  font-family: var(--sans);
130
162
  color: var(--text);
131
- font-size: 14px;
163
+ font-size: var(--fs-base);
132
164
  line-height: 1.5;
133
165
  -webkit-font-smoothing: antialiased;
134
166
  background: var(--bg-card);
135
167
  }
136
- button { font-family: inherit; cursor: pointer; }
168
+ /* Form controls do not inherit type from the page — they start at the browser's
169
+ own font, and `font-family: inherit` alone fixed only half of that. The size
170
+ stayed at the UA default 13.333px: off every step of the scale, on nine inputs
171
+ and four buttons, including the fields the CTO actually types into. A scale
172
+ that the text you enter does not follow is not the page's scale.
173
+
174
+ Bare-element specificity (0,0,1), so every component rule below still wins —
175
+ this only supplies a floor where nothing else speaks. */
176
+ button, input, select, textarea {
177
+ font-family: inherit;
178
+ font-size: var(--fs-body);
179
+ }
180
+ button { cursor: pointer; }
181
+
182
+ /* Headings had no base rule, so every unstyled h1/h2/h3 fell to the browser's
183
+ bold 700. Three weights exist in this design — 400 body, 500 value, 600 title
184
+ — and 700 is not one of them. This removes it everywhere at once. Bare-element
185
+ specificity (0,0,1), so every component rule below still wins. */
186
+ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
187
+
188
+ /* The page's own title. Present on every panel, one size, one weight. Before
189
+ this, two panels had no title element at all, four rendered the browser
190
+ default, and one rendered a section heading at title size. */
191
+ .page-title {
192
+ font-size: var(--fs-title-l);
193
+ font-weight: 600;
194
+ line-height: 1.25;
195
+ letter-spacing: -0.02em;
196
+ color: var(--text);
197
+ margin: 0 0 14px;
198
+ }
199
+
200
+ /* A heading INSIDE a page. Notifications rendered its three sections through
201
+ .share-card h2 — the showcase hero — so "Email alerts" was set at 28px, the
202
+ size of a page title, three times on one screen. */
203
+ .section-title {
204
+ font-size: var(--fs-title-m);
205
+ font-weight: 600;
206
+ line-height: 1.3;
207
+ letter-spacing: -0.01em;
208
+ color: var(--text);
209
+ margin: 0 0 6px;
210
+ }
211
+
212
+ /* Sessions had a .panel-head element with no rule of any kind: its h2 fell to
213
+ the browser default of 1.5em bold-700 and the search box beside it did not
214
+ align to anything. */
215
+ .panel-head {
216
+ display: flex; align-items: center; gap: 12px;
217
+ margin: 0 0 14px; flex-wrap: wrap;
218
+ }
219
+ .panel-head h2 {
220
+ font-size: var(--fs-title-l);
221
+ letter-spacing: -0.02em;
222
+ line-height: 1.25;
223
+ margin: 0; flex: 1 1 auto;
224
+ }
225
+
137
226
 
138
227
  .board {
139
228
  position: relative;
@@ -159,16 +248,16 @@ button { font-family: inherit; cursor: pointer; }
159
248
  background: rgba(180, 83, 9, 0.10);
160
249
  border: 1px solid var(--status-progress);
161
250
  border-radius: 8px;
162
- font-size: 13px; color: var(--text);
251
+ font-size: var(--fs-body); color: var(--text);
163
252
  }
164
- .stale-board code { font-family: var(--mono); font-size: 12px; }
253
+ .stale-board code { font-family: var(--mono); font-size: var(--fs-small); }
165
254
 
166
255
  /* Per-agent budget. Four states, and they must not look alike: `exceeded` holds
167
256
  a dispatch, `unmeasured` deliberately does not. A shared grey chip for
168
257
  "under the cap" and "we never measured it" would be the same silent zero this
169
258
  board spends its time refusing to render. */
170
259
  .budget-chip {
171
- font-family: var(--mono); font-size: 10px;
260
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
172
261
  padding: 1px 6px; border-radius: 3px;
173
262
  border: 1px solid var(--border);
174
263
  white-space: nowrap;
@@ -187,11 +276,11 @@ button { font-family: inherit; cursor: pointer; }
187
276
  .ab-row:first-of-type { border-top: 0; }
188
277
  .ab-name { font-family: var(--mono); flex: 1 1 auto; min-width: 0; }
189
278
  .ab-cap { font-family: var(--mono); color: var(--text2); }
190
- .ab-spent { font-family: var(--mono); font-size: 11px; color: var(--text3); }
191
- .ab-note { margin-top: 8px; font-size: 11px; color: var(--status-progress); }
279
+ .ab-spent { font-family: var(--mono); font-size: var(--fs-caption); color: var(--text3); }
280
+ .ab-note { margin-top: 8px; font-size: var(--fs-caption); color: var(--status-progress); }
192
281
  .ab-note.ab-bad { color: var(--status-blocked); }
193
282
  .ab-btn {
194
- font-family: inherit; font-size: 11px;
283
+ font-family: inherit; font-size: var(--fs-caption);
195
284
  min-height: 24px; padding: 0 8px;
196
285
  border: 1px solid var(--border); border-radius: 5px;
197
286
  background: var(--bg-card); color: var(--text2);
@@ -203,7 +292,7 @@ button { font-family: inherit; cursor: pointer; }
203
292
 
204
293
  .budget-line {
205
294
  margin: -18px 0 24px; padding: 8px 12px;
206
- border-radius: 8px; font-size: 12px;
295
+ border-radius: 8px; font-size: var(--fs-small);
207
296
  border: 1px solid var(--border);
208
297
  }
209
298
  .budget-line.exceeded { color: var(--status-blocked); border-color: var(--status-blocked); background: var(--p0-bg); font-weight: 500; }
@@ -263,7 +352,7 @@ button { font-family: inherit; cursor: pointer; }
263
352
  .sidebar-brand .brand-mark svg { width: 22px; height: 22px; }
264
353
  .sidebar-brand .brand-name {
265
354
  font-family: var(--serif); font-weight: 600;
266
- font-size: 15px; letter-spacing: -0.01em;
355
+ font-size: var(--fs-title-s); letter-spacing: -0.01em;
267
356
  color: var(--text);
268
357
  }
269
358
  .proj-switch {
@@ -276,9 +365,9 @@ button { font-family: inherit; cursor: pointer; }
276
365
  }
277
366
  .proj-switch:hover { background: var(--bg-muted); }
278
367
  .proj-switch .star { color: var(--accent); font-size: 16px; }
279
- .proj-switch .name { font-weight: 600; font-size: 14px; }
368
+ .proj-switch .name { font-weight: 600; font-size: var(--fs-base); }
280
369
  .proj-switch .sw-meta { flex: 1; display: flex; flex-direction: column; gap: 1px; }
281
- .proj-switch .sw-arch { font-size: 10px; color: var(--text3); font-weight: 400; letter-spacing: 0.03em; }
370
+ .proj-switch .sw-arch { font-size: var(--fs-eyebrow); color: var(--text3); font-weight: 400; letter-spacing: 0.03em; }
282
371
  .proj-switch .chev {
283
372
  color: var(--text3); width: 12px; height: 12px;
284
373
  transition: transform 0.15s;
@@ -306,7 +395,7 @@ button { font-family: inherit; cursor: pointer; }
306
395
  }
307
396
  .proj-menu-search input {
308
397
  flex: 1; border: none; outline: none;
309
- font-family: inherit; font-size: 12px;
398
+ font-family: inherit; font-size: var(--fs-small);
310
399
  color: var(--text); background: transparent;
311
400
  }
312
401
  .proj-item {
@@ -314,11 +403,11 @@ button { font-family: inherit; cursor: pointer; }
314
403
  padding: 8px 10px;
315
404
  border-radius: 5px;
316
405
  cursor: pointer;
317
- font-size: 13px;
406
+ font-size: var(--fs-body);
318
407
  }
319
408
  .proj-item:hover { background: var(--bg-muted); }
320
409
  .proj-item.active { background: var(--bg-strong); font-weight: 500; }
321
- .proj-item .star { color: var(--accent); font-size: 14px; }
410
+ .proj-item .star { color: var(--accent); font-size: var(--fs-base); }
322
411
  .proj-icon, .proj-switch .proj-icon, .crumbs .proj-icon {
323
412
  display: inline-flex; align-items: center; justify-content: center;
324
413
  flex-shrink: 0;
@@ -327,9 +416,9 @@ button { font-family: inherit; cursor: pointer; }
327
416
  .proj-item .proj-icon { width: 22px; height: 22px; }
328
417
  .crumbs .proj-icon { width: 14px; height: 14px; margin-right: 2px; }
329
418
  .proj-item-info { flex: 1; min-width: 0; }
330
- .proj-item-info .name { font-weight: 500; font-size: 13px; }
419
+ .proj-item-info .name { font-weight: 500; font-size: var(--fs-body); }
331
420
  .proj-item-info .arch {
332
- font-family: var(--mono); font-size: 10px;
421
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
333
422
  color: var(--text3);
334
423
  letter-spacing: 0.04em;
335
424
  }
@@ -341,7 +430,7 @@ button { font-family: inherit; cursor: pointer; }
341
430
  .proj-menu-empty {
342
431
  padding: 14px 10px;
343
432
  text-align: center;
344
- font-size: 12px;
433
+ font-size: var(--fs-small);
345
434
  color: var(--text3);
346
435
  }
347
436
 
@@ -350,7 +439,7 @@ button { font-family: inherit; cursor: pointer; }
350
439
  display: flex; align-items: center; gap: 10px;
351
440
  height: 32px; padding: 0 10px;
352
441
  border-radius: 6px;
353
- font-size: 13px;
442
+ font-size: var(--fs-body);
354
443
  color: var(--text);
355
444
  cursor: pointer;
356
445
  position: relative;
@@ -368,14 +457,14 @@ button { font-family: inherit; cursor: pointer; }
368
457
  }
369
458
  .nav-item .icon { width: 16px; height: 16px; color: var(--text2); flex-shrink: 0; }
370
459
  .nav-item.active .icon { color: var(--accent); }
371
- .nav-item .count { margin-left: auto; font-size: 11px; color: var(--text3); font-family: var(--mono); }
460
+ .nav-item .count { margin-left: auto; font-size: var(--fs-caption); color: var(--text3); font-family: var(--mono); }
372
461
  .nav-divider { height: 1px; background: var(--border); margin: 12px 8px; }
373
462
 
374
463
  .sidebar-footer {
375
464
  padding: 12px 16px;
376
465
  border-top: 1px solid var(--border);
377
466
  font-family: var(--mono);
378
- font-size: 11px;
467
+ font-size: var(--fs-caption);
379
468
  color: var(--text3);
380
469
  letter-spacing: 0.04em;
381
470
  display: flex; align-items: center; gap: 6px;
@@ -395,7 +484,7 @@ button { font-family: inherit; cursor: pointer; }
395
484
  border-bottom: 1px solid var(--border);
396
485
  display: flex; align-items: center; justify-content: space-between;
397
486
  padding: 0 20px;
398
- font-size: 13px;
487
+ font-size: var(--fs-body);
399
488
  }
400
489
  .crumbs { display: flex; align-items: center; gap: 8px; color: var(--text2); }
401
490
  .crumbs .star { color: var(--accent); }
@@ -416,12 +505,12 @@ button { font-family: inherit; cursor: pointer; }
416
505
  .search-box svg { color: var(--text3); flex-shrink: 0; }
417
506
  .search-box input {
418
507
  flex: 1; border: none; outline: none; background: transparent;
419
- font-family: inherit; font-size: 13px;
508
+ font-family: inherit; font-size: var(--fs-body);
420
509
  color: var(--text); padding: 3px 0;
421
510
  }
422
511
  .search-box input::placeholder { color: var(--text3); }
423
512
  .search-box kbd {
424
- font-family: var(--mono); font-size: 10px;
513
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
425
514
  color: var(--text3);
426
515
  background: var(--bg-strong);
427
516
  border-radius: 3px; padding: 1px 5px;
@@ -438,7 +527,7 @@ button { font-family: inherit; cursor: pointer; }
438
527
  .btn-bell-badge {
439
528
  position: absolute; top: -5px; right: -5px;
440
529
  background: var(--accent); color: #04140d;
441
- font-size: 10px; font-weight: 700; line-height: 1;
530
+ font-size: var(--fs-eyebrow); font-weight: 700; line-height: 1;
442
531
  min-width: 16px; height: 16px; border-radius: 8px;
443
532
  display: none; align-items: center; justify-content: center;
444
533
  padding: 0 3px;
@@ -448,7 +537,7 @@ button { font-family: inherit; cursor: pointer; }
448
537
  .tab-btn {
449
538
  background: transparent; border: none;
450
539
  padding: 6px 12px; border-radius: 6px;
451
- font-size: 13px; color: var(--text2);
540
+ font-size: var(--fs-body); color: var(--text2);
452
541
  }
453
542
  .tab-btn:hover { background: var(--bg-muted); color: var(--text); }
454
543
  .tab-btn.active { background: var(--bg-strong); color: var(--text); font-weight: 500; }
@@ -469,13 +558,13 @@ button { font-family: inherit; cursor: pointer; }
469
558
  background: #0f1115; color: #fff; border: none;
470
559
  height: 30px; padding: 0 14px;
471
560
  border-radius: 999px;
472
- font-size: 13px; font-weight: 500;
561
+ font-size: var(--fs-body); font-weight: 500;
473
562
  display: inline-flex; align-items: center; gap: 6px;
474
563
  }
475
564
  .btn-new:hover { background: #2a2d33; }
476
565
 
477
566
  .task-count {
478
- font-family: var(--mono); font-size: 11px;
567
+ font-family: var(--mono); font-size: var(--fs-caption);
479
568
  color: var(--text3); letter-spacing: 0.04em;
480
569
  margin-right: 6px;
481
570
  }
@@ -505,7 +594,7 @@ button { font-family: inherit; cursor: pointer; }
505
594
  .col-head {
506
595
  display: flex; align-items: center; gap: 8px;
507
596
  padding: 4px 6px 8px;
508
- font-size: 13px; font-weight: 600;
597
+ font-size: var(--fs-body); font-weight: 600;
509
598
  }
510
599
  .col-dot {
511
600
  width: 10px; height: 10px; border-radius: 50%;
@@ -515,7 +604,7 @@ button { font-family: inherit; cursor: pointer; }
515
604
  .col-dot.filled { background: currentColor; }
516
605
  .col-dot.half { background: conic-gradient(currentColor 50%, transparent 50%); }
517
606
  .col-count {
518
- font-family: var(--mono); font-size: 11px;
607
+ font-family: var(--mono); font-size: var(--fs-caption);
519
608
  color: var(--text3); margin-left: 2px; font-weight: 500;
520
609
  }
521
610
  .col-title { color: var(--text); }
@@ -537,7 +626,7 @@ button { font-family: inherit; cursor: pointer; }
537
626
  overflow: hidden;
538
627
  }
539
628
  .card-desc {
540
- font-size: 12px;
629
+ font-size: var(--fs-small);
541
630
  line-height: 1.4;
542
631
  color: var(--text2);
543
632
  display: -webkit-box;
@@ -549,12 +638,12 @@ button { font-family: inherit; cursor: pointer; }
549
638
  .card:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-1px); }
550
639
  .card.selected { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
551
640
  .card .id {
552
- font-family: var(--mono); font-size: 10px;
641
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
553
642
  letter-spacing: 0.04em; color: var(--text3);
554
643
  flex-shrink: 0;
555
644
  }
556
645
  .card .title {
557
- font-weight: 600; font-size: 13px;
646
+ font-weight: 600; font-size: var(--fs-body);
558
647
  line-height: 1.35;
559
648
  color: var(--text);
560
649
  display: -webkit-box;
@@ -577,7 +666,7 @@ button { font-family: inherit; cursor: pointer; }
577
666
  .card-footer > * { flex-shrink: 0; }
578
667
  .card-time {
579
668
  margin-left: auto;
580
- font-family: var(--mono); font-size: 11px;
669
+ font-family: var(--mono); font-size: var(--fs-caption);
581
670
  color: var(--text3);
582
671
  }
583
672
 
@@ -585,7 +674,7 @@ button { font-family: inherit; cursor: pointer; }
585
674
  display: inline-flex; align-items: center; gap: 4px;
586
675
  height: 20px; padding: 0 8px;
587
676
  border-radius: 999px;
588
- font-size: 11px; font-weight: 500;
677
+ font-size: var(--fs-caption); font-weight: 500;
589
678
  background: var(--bg-strong); color: var(--text2);
590
679
  }
591
680
  .chip-p0 { background: var(--p0-bg); color: var(--p0-fg); }
@@ -609,7 +698,7 @@ button { font-family: inherit; cursor: pointer; }
609
698
  height: 24px;
610
699
  border: 1px solid var(--border);
611
700
  border-radius: 5px;
612
- font-family: inherit; font-size: 11px; font-weight: 500;
701
+ font-family: inherit; font-size: var(--fs-caption); font-weight: 500;
613
702
  cursor: pointer;
614
703
  background: var(--bg-card);
615
704
  transition: background 0.12s, border-color 0.12s;
@@ -627,14 +716,14 @@ button { font-family: inherit; cursor: pointer; }
627
716
  #agent-panel { position: fixed; right: 16px; bottom: 16px; width: min(560px, 94vw); max-height: 70vh; display: none; flex-direction: column; background: var(--bg-card, #11161a); border: 1px solid var(--border, #1f2a26); border-radius: 12px; box-shadow: 0 24px 70px rgba(0,0,0,.5); z-index: 60; overflow: hidden; }
628
717
  #agent-panel.open { display: flex; }
629
718
  #agent-panel header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--border, #1f2a26); background: transparent; }
630
- #agent-panel header b { font-size: 13px; }
631
- #agent-panel .ap-x { margin-left: auto; cursor: pointer; border: 0; background: none; color: var(--text-muted, #8aa); font-size: 16px; }
719
+ #agent-panel header b { font-size: var(--fs-body); }
720
+ #agent-panel .ap-x { margin-left: auto; cursor: pointer; border: 0; background: none; color: var(--text3); font-size: 16px; }
632
721
  #agent-log { flex: 1; overflow: auto; padding: 10px 14px; font: 12px/1.55 ui-monospace, Menlo, monospace; }
633
722
  #agent-log .al { margin: 2px 0; white-space: pre-wrap; word-break: break-word; }
634
- #agent-log .al.tool { color: var(--status-review); } #agent-log .al.system { color: var(--text-muted, #8aa); } #agent-log .al.error { color: var(--status-blocked, #ff5a5a); } #agent-log .al.done { color: var(--text-muted, #8aa); font-weight: 600; }
723
+ #agent-log .al.tool { color: var(--status-review); } #agent-log .al.system { color: var(--text3); } #agent-log .al.error { color: var(--status-blocked, #ff5a5a); } #agent-log .al.done { color: var(--text3); font-weight: 600; }
635
724
  #agent-panel .ap-foot { display: flex; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--border, #1f2a26); }
636
- #agent-panel input { flex: 1; background: var(--bg, #0a0e0c); color: var(--text, #ecf2ee); border: 1px solid var(--border, #1f2a26); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 13px; }
637
- #agent-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-muted, #556); }
725
+ #agent-panel input { flex: 1; background: var(--bg-muted); color: var(--text, #ecf2ee); border: 1px solid var(--border, #1f2a26); border-radius: 8px; padding: 7px 10px; font: inherit; font-size: var(--fs-body); }
726
+ #agent-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text3); }
638
727
  #agent-dot.live { background: var(--status-review); animation: apulse 1.2s infinite; }
639
728
  @keyframes apulse { 0%,100%{opacity:1} 50%{opacity:.4} }
640
729
  .gate-reject { color: var(--status-blocked); border-color: rgba(220, 38, 38, 0.30); }
@@ -657,7 +746,7 @@ button { font-family: inherit; cursor: pointer; }
657
746
 
658
747
  .empty {
659
748
  padding: 20px 8px;
660
- font-size: 12px;
749
+ font-size: var(--fs-small);
661
750
  color: var(--text3);
662
751
  text-align: center;
663
752
  }
@@ -674,10 +763,10 @@ button { font-family: inherit; cursor: pointer; }
674
763
  border-left-width: 3px;
675
764
  border-radius: 6px;
676
765
  background: rgba(240, 180, 41, 0.08);
677
- font-size: 12px;
766
+ font-size: var(--fs-small);
678
767
  line-height: 1.45;
679
768
  }
680
- .degraded-banner strong { color: var(--text1); font-weight: 600; }
769
+ .degraded-banner strong { color: var(--text); font-weight: 600; }
681
770
  .degraded-banner span { color: var(--text2); font-family: var(--mono, ui-monospace, monospace); word-break: break-word; }
682
771
 
683
772
  /* ── Side panel ───────────────────────────────────────────────────────────── */
@@ -718,7 +807,7 @@ button { font-family: inherit; cursor: pointer; }
718
807
  border-bottom: 1px solid var(--border);
719
808
  }
720
809
  .side-id {
721
- font-family: var(--mono); font-size: 11px; font-weight: 500;
810
+ font-family: var(--mono); font-size: var(--fs-caption); font-weight: 500;
722
811
  background: var(--bg-strong); color: var(--text);
723
812
  padding: 4px 8px; border-radius: 4px;
724
813
  letter-spacing: 0.04em;
@@ -733,14 +822,14 @@ button { font-family: inherit; cursor: pointer; }
733
822
  .side-close:hover { background: var(--bg-muted); color: var(--text); }
734
823
  .side-body { flex: 1; overflow: auto; padding: 18px; }
735
824
  .side-title {
736
- font-family: var(--serif); font-weight: 600;
737
- font-size: 20px;
738
- letter-spacing: -0.02em;
825
+ font-family: var(--sans); font-weight: 600;
826
+ font-size: var(--fs-title-m);
827
+ letter-spacing: -0.01em;
739
828
  margin-bottom: 12px;
740
829
  color: var(--text);
741
830
  }
742
831
  .side-desc {
743
- font-size: 14px; line-height: 1.6;
832
+ font-size: var(--fs-base); line-height: 1.6;
744
833
  color: var(--text2);
745
834
  margin-bottom: 16px;
746
835
  padding-bottom: 14px;
@@ -751,30 +840,30 @@ button { font-family: inherit; cursor: pointer; }
751
840
  .side-desc-empty {
752
841
  color: var(--text3, #9ca3af);
753
842
  font-style: italic;
754
- font-size: 13px;
843
+ font-size: var(--fs-body);
755
844
  }
756
845
  .side-desc-empty code {
757
846
  font-style: normal;
758
847
  background: var(--bg-muted);
759
848
  padding: 1px 5px;
760
849
  border-radius: 3px;
761
- font-size: 12px;
850
+ font-size: var(--fs-small);
762
851
  }
763
852
  .side-desc.md { white-space: normal; }
764
853
  .side-desc.md h1 { font-size: 16px; margin: 8px 0 6px; font-weight: 600; }
765
- .side-desc.md h2 { font-size: 14px; margin: 10px 0 4px; font-weight: 600; color: var(--text); }
766
- .side-desc.md h3 { font-size: 13px; margin: 8px 0 3px; font-weight: 600; color: var(--text); }
767
- .side-desc.md h4 { font-size: 12px; margin: 6px 0 2px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text2); }
854
+ .side-desc.md h2 { font-size: var(--fs-base); margin: 10px 0 4px; font-weight: 600; color: var(--text); }
855
+ .side-desc.md h3 { font-size: var(--fs-body); margin: 8px 0 3px; font-weight: 600; color: var(--text); }
856
+ .side-desc.md h4 { font-size: var(--fs-small); margin: 6px 0 2px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text2); }
768
857
  .side-desc.md p { margin: 0 0 8px; }
769
858
  .side-desc.md ul { padding-left: 18px; margin: 4px 0 8px; }
770
859
  .side-desc.md li { margin: 2px 0; }
771
860
  .side-desc.md code {
772
861
  background: var(--bg-muted); padding: 1px 4px;
773
- border-radius: 3px; font-size: 12px; font-family: ui-monospace, monospace;
862
+ border-radius: 3px; font-size: var(--fs-small); font-family: ui-monospace, monospace;
774
863
  }
775
864
  .side-desc.md pre.md-pre {
776
865
  background: var(--bg-muted); padding: 8px 10px;
777
- border-radius: 4px; overflow-x: auto; font-size: 12px;
866
+ border-radius: 4px; overflow-x: auto; font-size: var(--fs-small);
778
867
  margin: 6px 0;
779
868
  }
780
869
  .side-desc.md pre.md-pre code { background: none; padding: 0; }
@@ -796,7 +885,7 @@ button { font-family: inherit; cursor: pointer; }
796
885
  .toast {
797
886
  background: #1f2937; color: #fff;
798
887
  padding: 8px 14px; border-radius: 6px;
799
- font-size: 13px; min-width: 200px; max-width: 360px;
888
+ font-size: var(--fs-body); min-width: 200px; max-width: 360px;
800
889
  box-shadow: 0 4px 16px rgba(0,0,0,0.35);
801
890
  opacity: 0; transform: translateY(8px);
802
891
  transition: opacity 0.2s, transform 0.2s;
@@ -826,7 +915,7 @@ button { font-family: inherit; cursor: pointer; }
826
915
  #cmdk-input {
827
916
  border: 0; border-bottom: 1px solid var(--border);
828
917
  background: transparent; color: var(--text);
829
- font-family: inherit; font-size: 15px; padding: 14px 16px; outline: none;
918
+ font-family: inherit; font-size: var(--fs-title-s); padding: 14px 16px; outline: none;
830
919
  }
831
920
  #cmdk-list { overflow-y: auto; padding: 6px; }
832
921
  .cmdk-item {
@@ -836,14 +925,14 @@ button { font-family: inherit; cursor: pointer; }
836
925
  }
837
926
  .cmdk-item.on { background: var(--bg-muted); }
838
927
  .cmdk-group {
839
- font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em;
928
+ font-family: var(--mono); font-size: var(--fs-eyebrow); letter-spacing: 0.06em;
840
929
  text-transform: uppercase; color: var(--text3);
841
930
  min-width: 34px; flex-shrink: 0;
842
931
  }
843
- .cmdk-label { font-size: 13.5px; color: var(--text); flex: 1; min-width: 0;
932
+ .cmdk-label { font-size: var(--fs-body); color: var(--text); flex: 1; min-width: 0;
844
933
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
845
- .cmdk-hint { font-family: var(--mono); font-size: 11px; color: var(--text3); flex-shrink: 0; }
846
- .cmdk-empty { padding: 18px 14px; font-size: 13px; color: var(--text2); }
934
+ .cmdk-hint { font-family: var(--mono); font-size: var(--fs-caption); color: var(--text3); flex-shrink: 0; }
935
+ .cmdk-empty { padding: 18px 14px; font-size: var(--fs-body); color: var(--text2); }
847
936
 
848
937
  #hotkey-modal {
849
938
  position: fixed; inset: 0; z-index: 10000;
@@ -853,21 +942,21 @@ button { font-family: inherit; cursor: pointer; }
853
942
  position: absolute; inset: 0; background: rgba(0,0,0,0.5);
854
943
  }
855
944
  .hk-card {
856
- position: relative; background: var(--bg, #0f172a);
945
+ position: relative; background: var(--bg-elevated);
857
946
  color: var(--text); border: 1px solid var(--border);
858
947
  border-radius: 8px; padding: 20px 24px; min-width: 360px;
859
948
  box-shadow: 0 12px 32px rgba(0,0,0,0.5);
860
949
  }
861
- .hk-title { font-size: 15px; font-weight: 600; margin-bottom: 12px; }
950
+ .hk-title { font-size: var(--fs-title-s); font-weight: 600; margin-bottom: 12px; }
862
951
  .hk-table { border-collapse: collapse; width: 100%; }
863
- .hk-table td { padding: 4px 8px; font-size: 13px; }
952
+ .hk-table td { padding: 4px 8px; font-size: var(--fs-body); }
864
953
  .hk-table td:first-child { text-align: right; white-space: nowrap; color: var(--text2); }
865
954
  .hk-table kbd {
866
955
  background: var(--bg-muted); border: 1px solid var(--border);
867
- border-radius: 3px; padding: 1px 5px; font-size: 11px;
956
+ border-radius: 3px; padding: 1px 5px; font-size: var(--fs-caption);
868
957
  font-family: ui-monospace, monospace; color: var(--text);
869
958
  }
870
- .hk-foot { margin-top: 12px; font-size: 11px; color: var(--text3, #9ca3af); text-align: center; }
959
+ .hk-foot { margin-top: 12px; font-size: var(--fs-caption); color: var(--text3, #9ca3af); text-align: center; }
871
960
 
872
961
  /* ── Resume detail modal (clickable verdicts + decisions) ───────────── */
873
962
  #resume-detail-modal {
@@ -876,7 +965,7 @@ button { font-family: inherit; cursor: pointer; }
876
965
  }
877
966
  .rdm-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
878
967
  .rdm-card {
879
- position: relative; background: var(--bg, #0f172a);
968
+ position: relative; background: var(--bg-elevated);
880
969
  color: var(--text); border: 1px solid var(--border);
881
970
  border-radius: 8px; padding: 18px 22px; min-width: 480px; max-width: 720px;
882
971
  box-shadow: 0 12px 32px rgba(0,0,0,0.5);
@@ -885,30 +974,30 @@ button { font-family: inherit; cursor: pointer; }
885
974
  .rdm-head .rdm-agent { font-weight: 600; }
886
975
  .rdm-head .rdm-verdict.ok { color: #4ade80; }
887
976
  .rdm-head .rdm-verdict.fail { color: #f87171; }
888
- .rdm-head .rdm-ts { color: var(--text2); font-size: 11px; margin-left: auto; }
977
+ .rdm-head .rdm-ts { color: var(--text2); font-size: var(--fs-caption); margin-left: auto; }
889
978
  .rdm-raw {
890
979
  background: var(--bg-muted); padding: 8px 10px; border-radius: 4px;
891
- font-family: ui-monospace, monospace; font-size: 12px;
980
+ font-family: ui-monospace, monospace; font-size: var(--fs-small);
892
981
  white-space: pre-wrap; word-break: break-all;
893
982
  border: 1px solid var(--border);
894
983
  max-height: 200px; overflow-y: auto;
895
984
  }
896
985
  .rdm-artefacts { margin-top: 12px; }
897
- .rdm-artefacts-title { font-size: 11px; color: var(--text2); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
986
+ .rdm-artefacts-title { font-size: var(--fs-caption); color: var(--text2); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
898
987
  .rdm-artefact {
899
988
  display: inline-flex; align-items: center; gap: 4px;
900
989
  margin: 2px 4px 2px 0; padding: 4px 8px;
901
990
  background: var(--bg-muted); border: 1px solid var(--border);
902
- border-radius: 4px; font-size: 12px; cursor: pointer;
991
+ border-radius: 4px; font-size: var(--fs-small); cursor: pointer;
903
992
  text-decoration: none; color: var(--text);
904
993
  font-family: ui-monospace, monospace;
905
994
  }
906
- .rdm-artefact:hover { background: var(--bg-hover, rgba(255,255,255,0.08)); }
995
+ .rdm-artefact:hover { background: var(--bg-muted); }
907
996
  .rdm-artefact .rdm-art-kind {
908
- font-size: 10px; padding: 1px 4px; border-radius: 3px;
909
- background: var(--accent-bg, rgba(59,130,246,0.2)); color: var(--accent, #3b82f6);
997
+ font-size: var(--fs-eyebrow); padding: 1px 4px; border-radius: 3px;
998
+ background: var(--accent-soft); color: var(--accent-text);
910
999
  }
911
- .rdm-foot { margin-top: 12px; font-size: 11px; color: var(--text3, #9ca3af); }
1000
+ .rdm-foot { margin-top: 12px; font-size: var(--fs-caption); color: var(--text3, #9ca3af); }
912
1001
  .resume-item { cursor: pointer; }
913
1002
  .resume-item:hover { background: var(--bg-muted); border-radius: 4px; }
914
1003
 
@@ -937,9 +1026,9 @@ button { font-family: inherit; cursor: pointer; }
937
1026
  display: flex; align-items: center; justify-content: space-between;
938
1027
  padding: 14px 18px; border-bottom: 1px solid var(--border);
939
1028
  }
940
- .ni-header h3 { font-size: 14px; font-weight: 600; margin: 0; }
1029
+ .ni-header h3 { font-size: var(--fs-title-s); font-weight: 600; margin: 0; }
941
1030
  .ni-close {
942
- background: transparent; border: 0; color: var(--text-3, #9ca3af);
1031
+ background: transparent; border: 0; color: var(--text3);
943
1032
  cursor: pointer; padding: 4px; display: flex; border-radius: 4px;
944
1033
  }
945
1034
  .ni-close:hover { background: var(--bg-muted, rgba(255,255,255,0.06)); color: var(--text); }
@@ -952,8 +1041,8 @@ button { font-family: inherit; cursor: pointer; }
952
1041
  flex: 1;
953
1042
  }
954
1043
  .ni-label {
955
- font-size: 11px; color: var(--text-3, #9ca3af);
956
- text-transform: uppercase; letter-spacing: 0.04em; font-weight: 500;
1044
+ font-family: var(--mono); font-size: var(--fs-eyebrow); color: var(--text3);
1045
+ text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500;
957
1046
  }
958
1047
  .ni-field input[type="text"],
959
1048
  .ni-field textarea,
@@ -962,7 +1051,7 @@ button { font-family: inherit; cursor: pointer; }
962
1051
  border: 1px solid var(--border, #2a2d33);
963
1052
  border-radius: 6px;
964
1053
  color: var(--text);
965
- font: inherit; font-size: 13px;
1054
+ font: inherit; font-size: var(--fs-body);
966
1055
  padding: 8px 10px;
967
1056
  outline: none;
968
1057
  transition: border-color 120ms;
@@ -978,21 +1067,21 @@ button { font-family: inherit; cursor: pointer; }
978
1067
  .ni-row { display: flex; gap: 12px; }
979
1068
  .ni-checkbox {
980
1069
  flex-direction: row; align-items: center; gap: 8px;
981
- font-size: 13px; cursor: pointer;
1070
+ font-size: var(--fs-body); cursor: pointer;
982
1071
  }
983
1072
  .ni-checkbox input { margin: 0; cursor: pointer; }
984
- .ni-checkbox span { color: var(--text-2); }
1073
+ .ni-checkbox span { color: var(--text2); }
985
1074
  .ni-actions {
986
1075
  display: flex; justify-content: flex-end; gap: 8px;
987
1076
  margin-top: 4px;
988
1077
  }
989
1078
  .ni-btn {
990
1079
  border: 1px solid transparent; border-radius: 6px;
991
- padding: 7px 14px; font: inherit; font-size: 13px;
1080
+ padding: 7px 14px; font: inherit; font-size: var(--fs-body);
992
1081
  cursor: pointer; transition: all 120ms;
993
1082
  }
994
1083
  .ni-btn-ghost {
995
- background: transparent; color: var(--text-2);
1084
+ background: transparent; color: var(--text2);
996
1085
  border-color: var(--border);
997
1086
  }
998
1087
  .ni-btn-ghost:hover { background: var(--bg-muted, rgba(255,255,255,0.05)); color: var(--text); }
@@ -1012,19 +1101,19 @@ button { font-family: inherit; cursor: pointer; }
1012
1101
  .side-gate-actions .gate-btn {
1013
1102
  flex: 1;
1014
1103
  height: 36px;
1015
- font-size: 13px; font-weight: 500;
1104
+ font-size: var(--fs-body); font-weight: 500;
1016
1105
  padding: 0 14px;
1017
1106
  }
1018
1107
  .side-section {
1019
1108
  margin-top: 18px;
1020
1109
  }
1021
1110
  .side-section-head {
1022
- font-family: var(--mono); font-size: 10px;
1111
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1023
1112
  letter-spacing: 0.1em; text-transform: uppercase;
1024
1113
  color: var(--text3); margin-bottom: 6px;
1025
1114
  }
1026
1115
  .side-section-body {
1027
- font-size: 13.5px; line-height: 1.55;
1116
+ font-size: var(--fs-body); line-height: 1.5;
1028
1117
  color: var(--text2);
1029
1118
  white-space: pre-wrap;
1030
1119
  word-break: break-word;
@@ -1036,12 +1125,12 @@ button { font-family: inherit; cursor: pointer; }
1036
1125
  border-top: 1px solid var(--border);
1037
1126
  padding-top: 14px;
1038
1127
  }
1039
- .prop-key { color: var(--text3); font-size: 12px; padding: 6px 0; font-family: var(--mono); letter-spacing: 0.04em; }
1040
- .prop-val { font-size: 13px; padding: 6px 0; }
1128
+ .prop-key { color: var(--text3); font-size: var(--fs-small); padding: 6px 0; font-family: var(--mono); letter-spacing: 0.04em; }
1129
+ .prop-val { font-size: var(--fs-body); padding: 6px 0; }
1041
1130
  .pill {
1042
1131
  display: inline-flex; align-items: center; gap: 6px;
1043
1132
  padding: 4px 10px; border-radius: 999px;
1044
- font-size: 12px; font-weight: 500;
1133
+ font-size: var(--fs-small); font-weight: 500;
1045
1134
  background: var(--bg-strong); color: var(--text2);
1046
1135
  }
1047
1136
  .pill-gate { background: rgba(124, 58, 237, 0.12); color: var(--status-gate); }
@@ -1055,7 +1144,7 @@ button { font-family: inherit; cursor: pointer; }
1055
1144
  border: 1px solid var(--border);
1056
1145
  border-radius: 8px;
1057
1146
  padding: 12px 14px;
1058
- font-size: 13px;
1147
+ font-size: var(--fs-body);
1059
1148
  color: var(--text2);
1060
1149
  white-space: pre-wrap;
1061
1150
  line-height: 1.55;
@@ -1076,7 +1165,7 @@ button { font-family: inherit; cursor: pointer; }
1076
1165
  overflow-y: auto;
1077
1166
  }
1078
1167
  .memory-side-head {
1079
- font-family: var(--mono); font-size: 10px;
1168
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1080
1169
  letter-spacing: 0.1em; text-transform: uppercase;
1081
1170
  color: var(--text3);
1082
1171
  padding: 0 10px 8px;
@@ -1092,27 +1181,27 @@ button { font-family: inherit; cursor: pointer; }
1092
1181
  .mem-item:hover { background: var(--bg-muted); }
1093
1182
  .mem-item.active { background: var(--accent-soft); border-color: rgba(37, 64, 212, 0.16); }
1094
1183
  .mem-item .layer-tag {
1095
- font-family: var(--mono); font-size: 10px;
1184
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1096
1185
  color: var(--accent); letter-spacing: 0.06em;
1097
1186
  font-weight: 600;
1098
1187
  }
1099
1188
  .mem-item .name {
1100
- font-family: var(--mono); font-size: 12px;
1189
+ font-family: var(--mono); font-size: var(--fs-small);
1101
1190
  color: var(--text);
1102
1191
  }
1103
1192
  .mem-item .desc {
1104
- font-size: 11.5px; color: var(--text2);
1193
+ font-size: var(--fs-caption); color: var(--text2);
1105
1194
  margin-top: 2px;
1106
1195
  }
1107
1196
  .mem-item .meta {
1108
- font-family: var(--mono); font-size: 10px;
1197
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1109
1198
  color: var(--text3); margin-top: 2px;
1110
1199
  }
1111
1200
  .mem-item.missing { opacity: 0.55; }
1112
1201
  .mem-item.missing .layer-tag { color: var(--text3); }
1113
1202
  .mem-group-label {
1114
1203
  font-family: var(--mono);
1115
- font-size: 10px;
1204
+ font-size: var(--fs-eyebrow);
1116
1205
  letter-spacing: 0.08em;
1117
1206
  color: var(--text3);
1118
1207
  padding: 14px 12px 6px;
@@ -1132,18 +1221,18 @@ button { font-family: inherit; cursor: pointer; }
1132
1221
  }
1133
1222
  .memory-doc h1 { font-family: var(--serif); font-weight: 600; font-size: 28px; letter-spacing: -0.02em; margin: 0 0 14px; color: var(--text); }
1134
1223
  .memory-doc h2 { font-family: var(--serif); font-weight: 600; font-size: 20px; margin: 28px 0 10px; color: var(--text); }
1135
- .memory-doc h3 { font-family: var(--sans); font-weight: 600; font-size: 15px; margin: 18px 0 8px; color: var(--text); }
1136
- .memory-doc p { font-size: 14px; line-height: 1.65; color: var(--text2); margin: 0 0 10px; }
1224
+ .memory-doc h3 { font-family: var(--sans); font-weight: 600; font-size: var(--fs-title-s); margin: 18px 0 8px; color: var(--text); }
1225
+ .memory-doc p { font-size: var(--fs-base); line-height: 1.65; color: var(--text2); margin: 0 0 10px; }
1137
1226
  .memory-doc ul, .memory-doc ol { padding-left: 22px; margin: 0 0 12px; color: var(--text2); }
1138
- .memory-doc li { line-height: 1.6; font-size: 14px; }
1227
+ .memory-doc li { line-height: 1.6; font-size: var(--fs-base); }
1139
1228
  .memory-doc code {
1140
- font-family: var(--mono); font-size: 12px;
1229
+ font-family: var(--mono); font-size: var(--fs-small);
1141
1230
  background: var(--bg-muted);
1142
1231
  padding: 1px 6px; border-radius: 3px;
1143
1232
  color: var(--text);
1144
1233
  }
1145
1234
  .memory-doc pre {
1146
- font-family: var(--mono); font-size: 12px;
1235
+ font-family: var(--mono); font-size: var(--fs-small);
1147
1236
  background: var(--bg-strong);
1148
1237
  padding: 14px 16px;
1149
1238
  border-radius: 8px;
@@ -1156,7 +1245,7 @@ button { font-family: inherit; cursor: pointer; }
1156
1245
  .memory-doc table {
1157
1246
  border-collapse: collapse;
1158
1247
  margin: 12px 0;
1159
- font-size: 13px;
1248
+ font-size: var(--fs-body);
1160
1249
  width: 100%;
1161
1250
  }
1162
1251
  .memory-doc th, .memory-doc td {
@@ -1172,27 +1261,27 @@ button { font-family: inherit; cursor: pointer; }
1172
1261
  border-bottom: 1px solid var(--border);
1173
1262
  }
1174
1263
  .memory-doc-head .layer-tag {
1175
- font-family: var(--mono); font-size: 11px;
1264
+ font-family: var(--mono); font-size: var(--fs-caption);
1176
1265
  background: var(--accent-soft); color: var(--accent);
1177
1266
  padding: 4px 10px; border-radius: 4px;
1178
1267
  letter-spacing: 0.06em; font-weight: 600;
1179
1268
  }
1180
1269
  .memory-doc-head .filename {
1181
- font-family: var(--mono); font-size: 13px;
1270
+ font-family: var(--mono); font-size: var(--fs-body);
1182
1271
  color: var(--text);
1183
1272
  }
1184
1273
  .memory-doc-head .filesize {
1185
1274
  margin-left: auto;
1186
- font-family: var(--mono); font-size: 11px;
1275
+ font-family: var(--mono); font-size: var(--fs-caption);
1187
1276
  color: var(--text3);
1188
1277
  }
1189
1278
  .memory-empty {
1190
1279
  text-align: center; padding: 60px 24px;
1191
- color: var(--text3); font-size: 13px;
1280
+ color: var(--text3); font-size: var(--fs-body);
1192
1281
  }
1193
1282
 
1194
1283
  /* ── change_tier badge ────────────────────────────────────────────────────── */
1195
- .tier-badge { font-size: 11px; padding: 1px 8px; border-radius: 999px; margin-left: 6px;
1284
+ .tier-badge { font-size: var(--fs-caption); padding: 1px 8px; border-radius: 999px; margin-left: 6px;
1196
1285
  font-family: var(--mono); white-space: nowrap; }
1197
1286
  .tier-badge:empty { display: none; }
1198
1287
  .tier-badge.tier-T0 { background: var(--status-review); color: #fff; }
@@ -1229,12 +1318,12 @@ button { font-family: inherit; cursor: pointer; }
1229
1318
  min-height: 26px; padding: 0 8px;
1230
1319
  border: 1px solid var(--border); border-radius: 5px;
1231
1320
  background: var(--bg-card); color: var(--text2);
1232
- font-family: var(--mono); font-size: 10px; cursor: pointer;
1321
+ font-family: var(--mono); font-size: var(--fs-eyebrow); cursor: pointer;
1233
1322
  }
1234
1323
  .pl-recover-btn:hover { border-color: var(--status-blocked); color: var(--status-blocked); }
1235
1324
  .pl-recover-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
1236
1325
  .pl-stage .pl-name {
1237
- font-family: var(--mono); font-size: 11px;
1326
+ font-family: var(--mono); font-size: var(--fs-caption);
1238
1327
  letter-spacing: 0.04em; color: var(--text);
1239
1328
  display: flex; align-items: center; gap: 6px;
1240
1329
  }
@@ -1248,7 +1337,7 @@ button { font-family: inherit; cursor: pointer; }
1248
1337
  .pl-stage.failed .pl-name .dot { background: var(--status-blocked); }
1249
1338
  @keyframes pl-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4); } 50% { box-shadow: 0 0 0 5px rgba(245, 158, 11, 0); } }
1250
1339
  .pl-stage .pl-msg {
1251
- font-size: 11px; color: var(--text2);
1340
+ font-size: var(--fs-caption); color: var(--text2);
1252
1341
  line-height: 1.35;
1253
1342
  display: -webkit-box;
1254
1343
  -webkit-line-clamp: 2;
@@ -1257,7 +1346,7 @@ button { font-family: inherit; cursor: pointer; }
1257
1346
  text-overflow: ellipsis;
1258
1347
  }
1259
1348
  .pl-stage .pl-meta {
1260
- font-family: var(--mono); font-size: 10px;
1349
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1261
1350
  color: var(--text3);
1262
1351
  }
1263
1352
  .pl-stage.idle .pl-msg, .pl-stage.idle .pl-meta { opacity: 0.55; }
@@ -1278,11 +1367,11 @@ button { font-family: inherit; cursor: pointer; }
1278
1367
  border-radius: 50%; background: rgba(0, 217, 126, 0.12);
1279
1368
  color: var(--accent, #00d97e); font-size: 16px; font-weight: 600;
1280
1369
  }
1281
- .inbox-allclear .ac-text { flex: 1; font-size: 14px; color: var(--text2); }
1370
+ .inbox-allclear .ac-text { flex: 1; font-size: var(--fs-base); color: var(--text2); }
1282
1371
  .inbox-allclear .ac-text b { color: var(--text); font-weight: 600; }
1283
1372
  .inbox-allclear .ac-actions { display: flex; gap: 8px; }
1284
1373
  .inbox-allclear .ac-btn {
1285
- font-size: 13px; padding: 7px 14px; border-radius: 8px;
1374
+ font-size: var(--fs-body); padding: 7px 14px; border-radius: 8px;
1286
1375
  border: 1px solid var(--border-strong); background: transparent;
1287
1376
  color: var(--text); cursor: pointer; transition: background 0.12s, border-color 0.12s;
1288
1377
  }
@@ -1299,7 +1388,7 @@ button { font-family: inherit; cursor: pointer; }
1299
1388
  margin: -4px 2px;
1300
1389
  padding: 12px 12px 10px;
1301
1390
  }
1302
- .pl-gate .gate-glyph { font-size: 12px; line-height: 1; flex-shrink: 0; filter: grayscale(1); opacity: 0.6; }
1391
+ .pl-gate .gate-glyph { font-size: var(--fs-small); line-height: 1; flex-shrink: 0; filter: grayscale(1); opacity: 0.6; }
1303
1392
  .pl-gate .pl-name { font-weight: 600; }
1304
1393
  .pl-gate.active {
1305
1394
  border-left-color: var(--status-blocked);
@@ -1310,7 +1399,7 @@ button { font-family: inherit; cursor: pointer; }
1310
1399
  .pl-gate .gate-badge {
1311
1400
  margin-left: auto;
1312
1401
  background: var(--status-blocked); color: #fff;
1313
- font-size: 10px; font-weight: 700;
1402
+ font-size: var(--fs-eyebrow); font-weight: 700;
1314
1403
  min-width: 16px; height: 16px; padding: 0 4px;
1315
1404
  border-radius: 8px;
1316
1405
  display: inline-flex; align-items: center; justify-content: center;
@@ -1331,18 +1420,18 @@ button { font-family: inherit; cursor: pointer; }
1331
1420
  display: flex; flex-direction: column; gap: 2px;
1332
1421
  }
1333
1422
  .cost-cell .lbl {
1334
- font-family: var(--mono); font-size: 10px;
1423
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1335
1424
  letter-spacing: 0.08em; text-transform: uppercase;
1336
1425
  color: var(--text2);
1337
1426
  }
1338
1427
  .cost-cell .v {
1339
1428
  font-family: var(--serif); font-weight: 500;
1340
1429
  font-variant-numeric: tabular-nums;
1341
- font-size: 26px; letter-spacing: -0.02em;
1430
+ font-size: var(--fs-num-s); letter-spacing: -0.02em;
1342
1431
  color: var(--text); line-height: 1.1;
1343
1432
  }
1344
- .cost-cell .v small { font-size: 14px; color: var(--text2); margin-left: 2px; font-weight: 400; }
1345
- .cost-cell .sub { font-size: 11.5px; color: var(--text2); }
1433
+ .cost-cell .v small { font-size: var(--fs-base); color: var(--text2); margin-left: 2px; font-weight: 400; }
1434
+ .cost-cell .sub { font-size: var(--fs-caption); color: var(--text2); }
1346
1435
  .cost-cell.warn .v { color: var(--status-blocked); }
1347
1436
  .cost-cell.good .v { color: var(--status-review); }
1348
1437
 
@@ -1374,7 +1463,7 @@ button { font-family: inherit; cursor: pointer; }
1374
1463
  transform: translateX(-50%);
1375
1464
  background: #0f1115; color: #fff;
1376
1465
  padding: 4px 8px; border-radius: 4px;
1377
- font-family: var(--mono); font-size: 10px;
1466
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1378
1467
  white-space: nowrap;
1379
1468
  display: none;
1380
1469
  pointer-events: none;
@@ -1387,7 +1476,7 @@ button { font-family: inherit; cursor: pointer; }
1387
1476
  chart is the primary reading and this is the exact one. */
1388
1477
  .cost-table { margin-top: 10px; }
1389
1478
  .cost-table summary {
1390
- cursor: pointer; font-size: 12px; color: var(--text2);
1479
+ cursor: pointer; font-size: var(--fs-small); color: var(--text2);
1391
1480
  padding: 4px 0; list-style-position: inside;
1392
1481
  }
1393
1482
  .cost-table summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 4px; }
@@ -1395,13 +1484,13 @@ button { font-family: inherit; cursor: pointer; }
1395
1484
  .cost-table th, .cost-table td {
1396
1485
  text-align: left; padding: 5px 10px 5px 0;
1397
1486
  border-bottom: 1px solid var(--border);
1398
- font-size: 12px; white-space: nowrap;
1487
+ font-size: var(--fs-small); white-space: nowrap;
1399
1488
  }
1400
- .cost-table th { font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text2); }
1489
+ .cost-table th { font-family: var(--mono); font-size: var(--fs-eyebrow); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text2); }
1401
1490
  .cost-table td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
1402
1491
  .cost-axis {
1403
1492
  display: flex; justify-content: space-between;
1404
- font-family: var(--mono); font-size: 10px;
1493
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1405
1494
  color: var(--text3);
1406
1495
  margin-top: 4px;
1407
1496
  }
@@ -1414,13 +1503,13 @@ button { font-family: inherit; cursor: pointer; }
1414
1503
  }
1415
1504
  .cost-budget-line .label {
1416
1505
  position: absolute; right: 4px; top: -16px;
1417
- font-family: var(--mono); font-size: 10px;
1506
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1418
1507
  color: var(--status-progress);
1419
1508
  background: var(--bg-card); padding: 0 4px;
1420
1509
  }
1421
1510
  .cost-empty {
1422
1511
  text-align: center; padding: 30px 16px;
1423
- font-size: 13px; color: var(--text3);
1512
+ font-size: var(--fs-body); color: var(--text3);
1424
1513
  }
1425
1514
 
1426
1515
  /* ── Cost per feature ────────────────────────────────────────────────────── */
@@ -1430,7 +1519,7 @@ button { font-family: inherit; cursor: pointer; }
1430
1519
  padding-top: 16px;
1431
1520
  }
1432
1521
  .feat-title {
1433
- font-size: 11px; font-weight: 600; text-transform: uppercase;
1522
+ font-size: var(--fs-caption); font-weight: 600; text-transform: uppercase;
1434
1523
  letter-spacing: .06em; color: var(--text3);
1435
1524
  margin-bottom: 10px;
1436
1525
  }
@@ -1440,16 +1529,16 @@ button { font-family: inherit; cursor: pointer; }
1440
1529
  grid-template-columns: 160px 1fr 56px 32px;
1441
1530
  align-items: center;
1442
1531
  gap: 8px;
1443
- font-size: 12px;
1532
+ font-size: var(--fs-small);
1444
1533
  }
1445
1534
  .feat-name {
1446
- font-family: var(--mono); color: var(--text1);
1535
+ font-family: var(--mono); color: var(--text);
1447
1536
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1448
1537
  }
1449
- .feat-bar-wrap { background: var(--bg); border-radius: 3px; height: 6px; }
1538
+ .feat-bar-wrap { background: var(--bg-strong); border-radius: 3px; height: 6px; }
1450
1539
  .feat-bar { height: 6px; border-radius: 3px; background: var(--accent); transition: width .3s; }
1451
- .feat-cost { font-family: var(--mono); color: var(--text1); text-align: right; }
1452
- .feat-runs { font-size: 11px; color: var(--text3); text-align: right; }
1540
+ .feat-cost { font-family: var(--mono); color: var(--text); text-align: right; }
1541
+ .feat-runs { font-size: var(--fs-caption); color: var(--text3); text-align: right; }
1453
1542
 
1454
1543
  /* ── Inbox page ───────────────────────────────────────────────────────────── */
1455
1544
  .inbox-page {
@@ -1476,11 +1565,13 @@ button { font-family: inherit; cursor: pointer; }
1476
1565
  display: flex; align-items: center; gap: 10px;
1477
1566
  }
1478
1567
  .inbox-summary .pill-stat .num {
1479
- font-family: var(--mono); font-weight: 600;
1480
- font-size: 24px; color: var(--text);
1568
+ font-family: var(--serif); font-weight: 500;
1569
+ font-variant-numeric: tabular-nums;
1570
+ font-size: var(--fs-num-s); letter-spacing: -0.02em;
1571
+ color: var(--text);
1481
1572
  }
1482
1573
  .inbox-summary .pill-stat .lbl {
1483
- font-family: var(--mono); font-size: 10px;
1574
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1484
1575
  letter-spacing: 0.08em; text-transform: uppercase;
1485
1576
  color: var(--text2);
1486
1577
  }
@@ -1507,11 +1598,11 @@ button { font-family: inherit; cursor: pointer; }
1507
1598
  }
1508
1599
  .resume-title {
1509
1600
  font-family: var(--sans); font-weight: 600;
1510
- font-size: 13px; color: var(--text);
1601
+ font-size: var(--fs-body); color: var(--text);
1511
1602
  }
1512
1603
  .resume-meta {
1513
1604
  margin-left: auto;
1514
- font-family: var(--mono); font-size: 10px;
1605
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1515
1606
  letter-spacing: 0.08em; text-transform: uppercase;
1516
1607
  color: var(--text3);
1517
1608
  }
@@ -1520,7 +1611,7 @@ button { font-family: inherit; cursor: pointer; }
1520
1611
  gap: 16px;
1521
1612
  }
1522
1613
  .resume-col-head {
1523
- font-family: var(--mono); font-size: 10px;
1614
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1524
1615
  letter-spacing: 0.08em; text-transform: uppercase;
1525
1616
  color: var(--text3);
1526
1617
  margin-bottom: 8px;
@@ -1531,7 +1622,7 @@ button { font-family: inherit; cursor: pointer; }
1531
1622
  display: flex; flex-direction: column; gap: 8px;
1532
1623
  }
1533
1624
  .resume-list .resume-item {
1534
- font-size: 12px;
1625
+ font-size: var(--fs-small);
1535
1626
  line-height: 1.4;
1536
1627
  color: var(--text2);
1537
1628
  cursor: pointer;
@@ -1542,7 +1633,7 @@ button { font-family: inherit; cursor: pointer; }
1542
1633
  .resume-list .resume-item:hover { color: var(--text); }
1543
1634
  .resume-list .resume-item .ri-tag {
1544
1635
  flex-shrink: 0;
1545
- font-family: var(--mono); font-size: 9px;
1636
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1546
1637
  padding: 2px 5px; border-radius: 3px;
1547
1638
  letter-spacing: 0.04em;
1548
1639
  background: rgba(255,255,255,0.05);
@@ -1550,13 +1641,13 @@ button { font-family: inherit; cursor: pointer; }
1550
1641
  margin-top: 1px;
1551
1642
  }
1552
1643
  .resume-list .resume-item .ri-tag.ok { color: var(--accent); background: rgba(0,217,126,0.08); }
1553
- .resume-list .resume-item .ri-tag.fail { color: var(--p1); background: rgba(255,80,80,0.08); }
1644
+ .resume-list .resume-item .ri-tag.fail { color: var(--p1-fg); background: rgba(255,80,80,0.08); }
1554
1645
  .resume-list .resume-item .ri-text {
1555
1646
  flex: 1; min-width: 0;
1556
1647
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1557
1648
  }
1558
1649
  .resume-list .empty {
1559
- font-size: 12px; color: var(--text3); font-style: italic;
1650
+ font-size: var(--fs-small); color: var(--text3); font-style: italic;
1560
1651
  }
1561
1652
  @media (max-width: 880px) {
1562
1653
  .resume-grid { grid-template-columns: 1fr; gap: 12px; }
@@ -1572,10 +1663,10 @@ button { font-family: inherit; cursor: pointer; }
1572
1663
  }
1573
1664
  .inbox-section-head h3 {
1574
1665
  font-family: var(--sans); font-weight: 600;
1575
- font-size: 14px; margin: 0; color: var(--text);
1666
+ font-size: var(--fs-title-s); margin: 0; color: var(--text);
1576
1667
  }
1577
1668
  .inbox-section-head .inbox-count {
1578
- font-family: var(--mono); font-size: 11px;
1669
+ font-family: var(--mono); font-size: var(--fs-caption);
1579
1670
  color: var(--text3); margin-left: 2px;
1580
1671
  }
1581
1672
 
@@ -1601,13 +1692,13 @@ button { font-family: inherit; cursor: pointer; }
1601
1692
  .inbox-row:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-1px); }
1602
1693
  .inbox-row .id {
1603
1694
  padding-top: 2px;
1604
- font-family: var(--mono); font-size: 11px;
1695
+ font-family: var(--mono); font-size: var(--fs-caption);
1605
1696
  color: var(--text3); letter-spacing: 0.04em;
1606
1697
  /* keep a long project-prefixed id inside its own column — never overlap the title */
1607
1698
  min-width: 0; overflow-wrap: anywhere; word-break: break-word;
1608
1699
  }
1609
1700
  .inbox-row .ttl {
1610
- font-size: 13.5px; color: var(--text);
1701
+ font-size: var(--fs-body); color: var(--text);
1611
1702
  font-weight: 500;
1612
1703
  display: flex;
1613
1704
  flex-direction: column;
@@ -1623,7 +1714,7 @@ button { font-family: inherit; cursor: pointer; }
1623
1714
  }
1624
1715
  .inbox-row .ttl .ttl-desc {
1625
1716
  font-weight: 400;
1626
- font-size: 12.5px;
1717
+ font-size: var(--fs-small);
1627
1718
  color: var(--text2);
1628
1719
  display: -webkit-box;
1629
1720
  -webkit-line-clamp: 2;
@@ -1633,39 +1724,43 @@ button { font-family: inherit; cursor: pointer; }
1633
1724
  margin-top: 2px;
1634
1725
  }
1635
1726
  .inbox-row .ttl .meta {
1636
- font-family: var(--mono); font-size: 11px;
1727
+ font-family: var(--mono); font-size: var(--fs-caption);
1637
1728
  color: var(--text3); margin-top: 4px;
1638
1729
  }
1639
1730
  .inbox-row .actions {
1640
1731
  display: flex; gap: 4px; align-items: center;
1641
1732
  }
1642
- .inbox-row .actions .gate-btn { height: 26px; padding: 0 10px; font-size: 11px; }
1643
- .budgets-table { width: 100%; border-collapse: collapse; font-size: 13px; }
1733
+ .inbox-row .actions .gate-btn { height: 26px; padding: 0 10px; font-size: var(--fs-caption); }
1734
+ .budgets-failed { padding: 20px 0; max-width: 560px; }
1735
+ .budgets-failed .bf-head { font-size: var(--fs-title-s); font-weight: 600; margin-bottom: 6px; }
1736
+ .budgets-failed .bf-why { font-family: var(--mono); font-size: var(--fs-small); color: var(--status-blocked); margin-bottom: 10px; word-break: break-word; }
1737
+ .budgets-failed .bf-note { font-size: var(--fs-body); color: var(--text2); line-height: 1.5; margin-bottom: 14px; }
1738
+ .budgets-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
1644
1739
  .budgets-table th {
1645
- text-align: left; font-family: var(--mono); font-size: 10px;
1740
+ text-align: left; font-family: var(--mono); font-size: var(--fs-eyebrow);
1646
1741
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text2);
1647
1742
  padding: 0 12px 8px 0; border-bottom: 1px solid var(--border);
1648
1743
  }
1649
1744
  .budgets-table td { padding: 10px 12px 10px 0; border-bottom: 1px solid var(--border); vertical-align: middle; }
1650
1745
  /* A row has to answer three questions: what is this agent, what does it cost,
1651
1746
  over what period. The slug alone answered none of them. */
1652
- .budgets-table .bt-slug { font-family: var(--mono); font-size: 12.5px; }
1747
+ .budgets-table .bt-slug { font-family: var(--mono); font-size: var(--fs-small); }
1653
1748
  .budgets-table .bt-desc {
1654
- font-size: 12px; color: var(--text2); margin-top: 2px;
1749
+ font-size: var(--fs-small); color: var(--text2); margin-top: 2px;
1655
1750
  max-width: 52ch; text-wrap: pretty;
1656
1751
  }
1657
1752
  .budgets-table .bt-runs,
1658
1753
  .budgets-table .bt-cap { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
1659
- .budgets-table .bt-spend { font-family: var(--mono); font-size: 12px; white-space: nowrap; }
1754
+ .budgets-table .bt-spend { font-family: var(--mono); font-size: var(--fs-small); white-space: nowrap; }
1660
1755
  .budgets-table .bt-usd { font-variant-numeric: tabular-nums; color: var(--text); }
1661
1756
  .budgets-table th[title] { cursor: help; }
1662
- .budgets-idle summary { cursor: pointer; font-size: 13px; color: var(--text2); }
1757
+ .budgets-idle summary { cursor: pointer; font-size: var(--fs-body); color: var(--text2); }
1663
1758
  .budgets-idle summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
1664
1759
  .budgets-table .bt-actions { text-align: right; white-space: nowrap; }
1665
1760
  .budgets-table .bt-actions .ab-btn + .ab-btn { margin-left: 6px; }
1666
1761
  .budgets-empty {
1667
1762
  padding: 16px; border: 1px dashed var(--border-strong); border-radius: 8px;
1668
- color: var(--text2); font-size: 13px;
1763
+ color: var(--text2); font-size: var(--fs-body);
1669
1764
  }
1670
1765
  .budgets-uncapped { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
1671
1766
  .docs-search { display: flex; align-items: center; gap: 8px; margin: 10px 0 16px; }
@@ -1673,18 +1768,18 @@ button { font-family: inherit; cursor: pointer; }
1673
1768
  flex: 1 1 auto; min-width: 0; max-width: 460px;
1674
1769
  height: 32px; padding: 0 10px;
1675
1770
  border: 1px solid var(--border); border-radius: 8px;
1676
- background: var(--bg-card); color: var(--text); font: inherit; font-size: 13px;
1771
+ background: var(--bg-card); color: var(--text); font: inherit; font-size: var(--fs-body);
1677
1772
  }
1678
1773
  .docs-search input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
1679
- .docs-search-hint { font-family: var(--mono); font-size: 10px; color: var(--text3); }
1680
- .docs-search-meta { font-size: 12px; color: var(--text2); margin-bottom: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
1774
+ .docs-search-hint { font-family: var(--mono); font-size: var(--fs-eyebrow); color: var(--text3); }
1775
+ .docs-search-meta { font-size: var(--fs-small); color: var(--text2); margin-bottom: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
1681
1776
  .docs-hit { padding: 10px 0; border-top: 1px solid var(--border); }
1682
- .docs-hit-line { display: flex; gap: 10px; font-family: var(--mono); font-size: 11px; color: var(--text2); margin-top: 4px; }
1777
+ .docs-hit-line { display: flex; gap: 10px; font-family: var(--mono); font-size: var(--fs-caption); color: var(--text2); margin-top: 4px; }
1683
1778
  .docs-hit-no { color: var(--text3); min-width: 34px; text-align: right; }
1684
1779
  .col-more {
1685
1780
  display: block; width: 100%;
1686
1781
  margin-top: 6px; padding: 8px;
1687
- font-family: var(--mono); font-size: 11px;
1782
+ font-family: var(--mono); font-size: var(--fs-caption);
1688
1783
  border: 1px dashed var(--border-strong); border-radius: 8px;
1689
1784
  background: transparent; color: var(--text2);
1690
1785
  }
@@ -1694,14 +1789,14 @@ button { font-family: inherit; cursor: pointer; }
1694
1789
  padding: 14px 16px;
1695
1790
  background: var(--bg-muted);
1696
1791
  border-radius: 8px;
1697
- font-size: 12px; color: var(--text3);
1792
+ font-size: var(--fs-small); color: var(--text3);
1698
1793
  text-align: center;
1699
1794
  }
1700
1795
 
1701
1796
  /* ── Metrics page ─────────────────────────────────────────────────────────── */
1702
1797
  .metrics-page { padding: 24px 32px; }
1703
1798
  .mp-section-label {
1704
- font-family: var(--mono); font-size: 11px;
1799
+ font-family: var(--mono); font-size: var(--fs-caption);
1705
1800
  letter-spacing: 0.1em; text-transform: uppercase;
1706
1801
  color: var(--text3); margin: 0 0 12px;
1707
1802
  }
@@ -1714,7 +1809,7 @@ button { font-family: inherit; cursor: pointer; }
1714
1809
  margin-bottom: 16px;
1715
1810
  }
1716
1811
  .period-label {
1717
- font-family: var(--mono); font-size: 10px;
1812
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1718
1813
  letter-spacing: 0.12em; color: var(--text3);
1719
1814
  }
1720
1815
  .period-chips {
@@ -1725,7 +1820,7 @@ button { font-family: inherit; cursor: pointer; }
1725
1820
  .period-chip {
1726
1821
  background: transparent; border: none; color: var(--text2);
1727
1822
  padding: 5px 12px; border-radius: 5px;
1728
- font-family: var(--mono); font-size: 11px; font-weight: 500;
1823
+ font-family: var(--mono); font-size: var(--fs-caption); font-weight: 500;
1729
1824
  letter-spacing: 0.05em; cursor: pointer;
1730
1825
  transition: background .12s, color .12s;
1731
1826
  }
@@ -1753,7 +1848,7 @@ button { font-family: inherit; cursor: pointer; }
1753
1848
  so a figure that ticks over re-flows its own width and the number appears to
1754
1849
  breathe. Tabular figures cost nothing here and stop it. */
1755
1850
  font-variant-numeric: tabular-nums;
1756
- font-size: 52px; line-height: 1;
1851
+ font-size: var(--fs-num-xl); line-height: 1;
1757
1852
  letter-spacing: -0.02em;
1758
1853
  }
1759
1854
  /* An absence is rendered quietly and never silently — the glyph stays out of the
@@ -1766,23 +1861,23 @@ button { font-family: inherit; cursor: pointer; }
1766
1861
  }
1767
1862
  .delta {
1768
1863
  margin-left: 6px;
1769
- font-family: var(--mono); font-size: 10px; font-weight: 600;
1864
+ font-family: var(--mono); font-size: var(--fs-eyebrow); font-weight: 600;
1770
1865
  letter-spacing: 0.02em; white-space: nowrap;
1771
1866
  color: var(--text3); cursor: help;
1772
1867
  }
1773
1868
  .delta.up { color: var(--status-review); }
1774
1869
  .delta.down { color: var(--status-progress); }
1775
1870
  .metric-num small {
1776
- font-size: 22px; color: var(--text2);
1871
+ font-size: var(--fs-title-l); color: var(--text2);
1777
1872
  margin-left: 4px; font-weight: 400;
1778
1873
  }
1779
1874
  .metric-label {
1780
- font-family: var(--mono); font-size: 11px;
1875
+ font-family: var(--mono); font-size: var(--fs-caption);
1781
1876
  letter-spacing: 0.08em; text-transform: uppercase;
1782
1877
  color: var(--text2); margin-top: 14px;
1783
1878
  }
1784
1879
  .metric-trend {
1785
- margin-top: 6px; font-size: 12px; color: var(--text2);
1880
+ margin-top: 6px; font-size: var(--fs-small); color: var(--text2);
1786
1881
  }
1787
1882
 
1788
1883
  .mp-secondary {
@@ -1790,7 +1885,7 @@ button { font-family: inherit; cursor: pointer; }
1790
1885
  gap: 12px; margin-bottom: 28px;
1791
1886
  }
1792
1887
  .mp-secondary .metric-card { padding: 18px; }
1793
- .mp-secondary .metric-num { font-size: 30px; }
1888
+ .mp-secondary .metric-num { font-size: var(--fs-num-m); }
1794
1889
 
1795
1890
  .mp-row {
1796
1891
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
@@ -1801,17 +1896,17 @@ button { font-family: inherit; cursor: pointer; }
1801
1896
  }
1802
1897
  .mp-card h3 {
1803
1898
  font-family: var(--sans); font-weight: 600;
1804
- font-size: 15px; margin: 0 0 16px;
1899
+ font-size: var(--fs-title-s); margin: 0 0 16px;
1805
1900
  }
1806
1901
  .mp-card-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 16px; }
1807
1902
  .mp-card-head h3 { margin: 0; }
1808
1903
  .mp-card-meta {
1809
- font-family: var(--mono); font-size: 10px;
1904
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1810
1905
  color: var(--text3); letter-spacing: 0.06em;
1811
1906
  text-transform: uppercase;
1812
1907
  }
1813
1908
  .feed-cost {
1814
- font-family: var(--mono); font-size: 10px;
1909
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
1815
1910
  color: var(--accent);
1816
1911
  background: var(--accent-soft);
1817
1912
  padding: 2px 6px; border-radius: 4px;
@@ -1821,7 +1916,7 @@ button { font-family: inherit; cursor: pointer; }
1821
1916
  .bar-row {
1822
1917
  display: grid; grid-template-columns: 130px 1fr 50px;
1823
1918
  align-items: center; gap: 12px;
1824
- margin-bottom: 10px; font-size: 12.5px;
1919
+ margin-bottom: 10px; font-size: var(--fs-small);
1825
1920
  }
1826
1921
  .bar-row .name { font-family: var(--mono); color: var(--text); }
1827
1922
  .bar-row .rail {
@@ -1836,7 +1931,7 @@ button { font-family: inherit; cursor: pointer; }
1836
1931
  }
1837
1932
  .bar-row .pct {
1838
1933
  text-align: right; color: var(--text2);
1839
- font-family: var(--mono); font-size: 11px;
1934
+ font-family: var(--mono); font-size: var(--fs-caption);
1840
1935
  }
1841
1936
 
1842
1937
  .feed-row {
@@ -1845,7 +1940,7 @@ button { font-family: inherit; cursor: pointer; }
1845
1940
  gap: 12px; align-items: baseline;
1846
1941
  padding: 10px 0;
1847
1942
  border-top: 1px solid var(--border);
1848
- font-size: 13px;
1943
+ font-size: var(--fs-body);
1849
1944
  }
1850
1945
  .feed-row:first-child { border-top: none; }
1851
1946
  .feed-dot {
@@ -1855,16 +1950,16 @@ button { font-family: inherit; cursor: pointer; }
1855
1950
  .feed-dot.ok { background: var(--status-review); }
1856
1951
  .feed-dot.fail { background: var(--status-blocked); }
1857
1952
  .feed-dot.def { background: var(--text3); }
1858
- .feed-row .agent { font-family: var(--mono); color: var(--text2); font-size: 12px; }
1953
+ .feed-row .agent { font-family: var(--mono); color: var(--text2); font-size: var(--fs-small); }
1859
1954
  .feed-row .msg { color: var(--text); }
1860
- .feed-row .time { font-family: var(--mono); font-size: 11px; color: var(--text3); }
1955
+ .feed-row .time { font-family: var(--mono); font-size: var(--fs-caption); color: var(--text3); }
1861
1956
 
1862
1957
  /* ── Share page ───────────────────────────────────────────────────────────── */
1863
1958
  /* ── Logs tab ───────────────────────────────────────────── */
1864
1959
  .logs-page { display: flex; flex-direction: column; height: 100%; }
1865
1960
  .logs-header { padding: 20px 24px 12px; border-bottom: 1px solid var(--border); display: flex; align-items: baseline; gap: 16px; }
1866
- .logs-header h2 { margin: 0; font-size: 18px; }
1867
- .logs-hint { font-size: 12px; color: var(--text3); }
1961
+ .logs-header h2 { margin: 0; font-size: var(--fs-title-l); letter-spacing: -0.02em; }
1962
+ .logs-hint { font-size: var(--fs-small); color: var(--text3); }
1868
1963
  .logs-hint code { font-family: var(--mono); background: var(--bg-strong); padding: 1px 5px; border-radius: 3px; }
1869
1964
  .logs-layout { display: flex; flex: 1; overflow: hidden; }
1870
1965
  .logs-list { width: 280px; min-width: 220px; border-right: 1px solid var(--border); overflow-y: auto; padding: 8px 0; flex-shrink: 0; }
@@ -1877,7 +1972,7 @@ button { font-family: inherit; cursor: pointer; }
1877
1972
  min-height: 28px; padding: 0 10px;
1878
1973
  border: 1px solid var(--border); border-radius: 999px;
1879
1974
  background: transparent; color: var(--text2);
1880
- font-family: var(--mono); font-size: 11px; cursor: pointer;
1975
+ font-family: var(--mono); font-size: var(--fs-caption); cursor: pointer;
1881
1976
  }
1882
1977
  .log-facet:hover { border-color: var(--border-strong); }
1883
1978
  .log-facet.on { background: var(--accent-soft); border-color: var(--accent-2); color: var(--accent-text); }
@@ -1886,18 +1981,18 @@ button { font-family: inherit; cursor: pointer; }
1886
1981
  .log-item { padding: 10px 16px; cursor: pointer; border-bottom: 1px solid var(--border); }
1887
1982
  .log-item:hover { background: var(--bg-muted); }
1888
1983
  .log-item.active { background: var(--bg-strong); border-left: 2px solid var(--accent); }
1889
- .log-item .log-date { font-size: 11px; color: var(--text3); font-family: var(--mono); }
1890
- .log-item .log-title { font-size: 13px; font-weight: 500; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1891
- .log-item .log-meta { font-size: 11px; color: var(--text3); margin-top: 2px; }
1984
+ .log-item .log-date { font-size: var(--fs-caption); color: var(--text3); font-family: var(--mono); }
1985
+ .log-item .log-title { font-size: var(--fs-body); font-weight: 500; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1986
+ .log-item .log-meta { font-size: var(--fs-caption); color: var(--text3); margin-top: 2px; }
1892
1987
  .log-detail-head { margin-bottom: 20px; padding-bottom: 14px; border-bottom: 1px solid var(--border); }
1893
- .log-detail-head h3 { margin: 0 0 4px; font-size: 17px; }
1894
- .log-detail-head .log-detail-meta { font-size: 12px; color: var(--text3); }
1988
+ .log-detail-head h3 { margin: 0 0 4px; font-size: var(--fs-title-m); letter-spacing: -0.01em; }
1989
+ .log-detail-head .log-detail-meta { font-size: var(--fs-small); color: var(--text3); }
1895
1990
  .log-section { margin-bottom: 18px; }
1896
- .log-section-label { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; }
1991
+ .log-section-label { font-size: var(--fs-caption); color: var(--text3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; }
1897
1992
  .log-bullets { list-style: none; padding: 0; margin: 0; }
1898
- .log-bullets li { padding: 3px 0 3px 12px; position: relative; font-size: 13px; color: var(--text2); }
1993
+ .log-bullets li { padding: 3px 0 3px 12px; position: relative; font-size: var(--fs-body); color: var(--text2); }
1899
1994
  .log-bullets li::before { content: '·'; position: absolute; left: 0; color: var(--accent); }
1900
- .log-empty { font-size: 13px; color: var(--text3); font-style: italic; }
1995
+ .log-empty { font-size: var(--fs-body); color: var(--text3); font-style: italic; }
1901
1996
 
1902
1997
  /* ── Agents installed list (legacy — kept for fallback) ───────────────── */
1903
1998
  .agents-installed { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; margin-top: 4px; }
@@ -1907,9 +2002,9 @@ button { font-family: inherit; cursor: pointer; }
1907
2002
  .ab-name { font-family: var(--mono, monospace); color: var(--accent); }
1908
2003
  .ab-cap { color: var(--text3); }
1909
2004
  .agent-card { background: var(--bg-muted); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
1910
- .agent-card .ac-name { font-size: 12px; font-weight: 600; font-family: var(--mono); color: var(--accent); }
1911
- .agent-card .ac-desc { font-size: 11px; color: var(--text3); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1912
- .agent-card .ac-meta { font-size: 10px; color: var(--text3); margin-top: 5px; display: flex; gap: 8px; }
2005
+ .agent-card .ac-name { font-size: var(--fs-small); font-weight: 600; font-family: var(--mono); color: var(--accent); }
2006
+ .agent-card .ac-desc { font-size: var(--fs-caption); color: var(--text3); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2007
+ .agent-card .ac-meta { font-size: var(--fs-eyebrow); color: var(--text3); margin-top: 5px; display: flex; gap: 8px; }
1913
2008
  .agent-card .ac-model { background: var(--bg-strong); padding: 1px 5px; border-radius: 3px; }
1914
2009
  .agent-card .ac-used { color: var(--status-review); }
1915
2010
  .agent-card .ac-never { color: var(--text3); font-style: italic; }
@@ -1939,17 +2034,17 @@ button { font-family: inherit; cursor: pointer; }
1939
2034
  .fleet-search input[type="search"] {
1940
2035
  flex: 1; max-width: 320px;
1941
2036
  background: var(--bg-muted); border: 1px solid var(--border);
1942
- color: var(--text); font-family: var(--mono); font-size: 13px;
2037
+ color: var(--text); font-family: var(--mono); font-size: var(--fs-body);
1943
2038
  padding: 8px 12px; border-radius: 6px;
1944
2039
  }
1945
2040
  .fleet-search input[type="search"]:focus {
1946
2041
  outline: 2px solid var(--focus-ring); outline-offset: 1px;
1947
2042
  border-color: transparent;
1948
2043
  }
1949
- .fleet-search .search-hint { font-size: 11px; color: var(--text3); font-family: var(--mono); }
2044
+ .fleet-search .search-hint { font-size: var(--fs-caption); color: var(--text3); font-family: var(--mono); }
1950
2045
  .fleet-search .search-hint kbd {
1951
2046
  background: var(--bg-muted); border: 1px solid var(--border);
1952
- border-radius: 3px; padding: 1px 5px; font-size: 10px;
2047
+ border-radius: 3px; padding: 1px 5px; font-size: var(--fs-eyebrow);
1953
2048
  }
1954
2049
 
1955
2050
  .fleet-facets { display: flex; flex-direction: column; gap: 6px; }
@@ -1958,12 +2053,12 @@ button { font-family: inherit; cursor: pointer; }
1958
2053
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
1959
2054
  }
1960
2055
  .fleet-facets legend {
1961
- font-size: 11px; color: var(--text3); text-transform: uppercase;
2056
+ font-size: var(--fs-caption); color: var(--text3); text-transform: uppercase;
1962
2057
  letter-spacing: 0.05em; margin-right: 6px; min-width: 60px;
1963
2058
  }
1964
2059
  .facet-chip {
1965
2060
  background: var(--bg-muted); border: 1px solid var(--border);
1966
- color: var(--text2); font-size: 12px; font-family: var(--mono);
2061
+ color: var(--text2); font-size: var(--fs-small); font-family: var(--mono);
1967
2062
  padding: 4px 10px; border-radius: 12px; cursor: pointer;
1968
2063
  transition: background-color 0.15s, color 0.15s, border-color 0.15s;
1969
2064
  }
@@ -1977,16 +2072,16 @@ button { font-family: inherit; cursor: pointer; }
1977
2072
 
1978
2073
  .fleet-sort {
1979
2074
  display: flex; gap: 8px; align-items: center;
1980
- font-size: 11px; color: var(--text3);
2075
+ font-size: var(--fs-caption); color: var(--text3);
1981
2076
  }
1982
2077
  .fleet-sort select {
1983
2078
  background: var(--bg-muted); border: 1px solid var(--border);
1984
- color: var(--text); font-family: var(--mono); font-size: 12px;
2079
+ color: var(--text); font-family: var(--mono); font-size: var(--fs-small);
1985
2080
  padding: 4px 8px; border-radius: 6px;
1986
2081
  }
1987
2082
  .fleet-sort button {
1988
2083
  background: transparent; border: 1px solid var(--border);
1989
- color: var(--text2); font-size: 11px; font-family: var(--mono);
2084
+ color: var(--text2); font-size: var(--fs-caption); font-family: var(--mono);
1990
2085
  padding: 4px 8px; border-radius: 6px;
1991
2086
  }
1992
2087
  .fleet-sort button:hover { color: var(--text); border-color: var(--border-strong); }
@@ -2023,11 +2118,11 @@ button { font-family: inherit; cursor: pointer; }
2023
2118
  .agent-row.status-unused .ar-status { background: var(--agent-unused); }
2024
2119
  .agent-row.status-retired .ar-status { background: var(--agent-retired); }
2025
2120
  .agent-row .ar-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
2026
- .agent-row .ar-name { font-family: var(--mono); font-size: 13px; color: var(--text); }
2027
- .agent-row .ar-meta { font-size: 11px; color: var(--text3); display: flex; gap: 10px; flex-wrap: wrap; }
2121
+ .agent-row .ar-name { font-family: var(--mono); font-size: var(--fs-body); color: var(--text); }
2122
+ .agent-row .ar-meta { font-size: var(--fs-caption); color: var(--text3); display: flex; gap: 10px; flex-wrap: wrap; }
2028
2123
  .agent-row .ar-meta .ar-domain {
2029
2124
  background: var(--bg-muted); padding: 1px 6px; border-radius: 3px;
2030
- color: var(--text2); font-family: var(--mono); font-size: 10px;
2125
+ color: var(--text2); font-family: var(--mono); font-size: var(--fs-eyebrow);
2031
2126
  }
2032
2127
  .agent-row .ar-rail {
2033
2128
  width: 80px; height: 4px;
@@ -2040,13 +2135,13 @@ button { font-family: inherit; cursor: pointer; }
2040
2135
  }
2041
2136
  .agent-row.status-failing .ar-rail-fill { background: var(--agent-failing); }
2042
2137
  .agent-row .ar-chev {
2043
- color: var(--text3); font-size: 14px; padding: 0 4px;
2138
+ color: var(--text3); font-size: var(--fs-base); padding: 0 4px;
2044
2139
  }
2045
2140
 
2046
2141
  /* Status pill — used in drawer header */
2047
2142
  .status-pill {
2048
2143
  display: inline-flex; align-items: center; gap: 6px;
2049
- font-size: 11px; font-family: var(--mono);
2144
+ font-size: var(--fs-caption); font-family: var(--mono);
2050
2145
  padding: 2px 8px; border-radius: 10px;
2051
2146
  background: var(--bg-muted); border: 1px solid var(--border);
2052
2147
  }
@@ -2086,12 +2181,13 @@ button { font-family: inherit; cursor: pointer; }
2086
2181
  padding: 16px 20px; border-bottom: 1px solid var(--border);
2087
2182
  }
2088
2183
  .agent-drawer-header h3 {
2089
- font-family: var(--mono); font-size: 14px;
2184
+ font-family: var(--sans); font-weight: 600;
2185
+ font-size: var(--fs-title-s);
2090
2186
  color: var(--text); flex: 1; margin: 0;
2091
2187
  }
2092
2188
  .agent-drawer-close {
2093
2189
  background: transparent; border: 1px solid var(--border);
2094
- color: var(--text2); font-size: 18px; line-height: 1;
2190
+ color: var(--text2); font-size: var(--fs-title-m); line-height: 1;
2095
2191
  width: 28px; height: 28px; border-radius: 6px;
2096
2192
  display: flex; align-items: center; justify-content: center;
2097
2193
  }
@@ -2103,20 +2199,20 @@ button { font-family: inherit; cursor: pointer; }
2103
2199
  }
2104
2200
  .agent-drawer-body .mp-card { padding: 12px 14px; }
2105
2201
  .agent-drawer-body h4 {
2106
- font-size: 11px; text-transform: uppercase;
2202
+ font-size: var(--fs-caption); text-transform: uppercase;
2107
2203
  color: var(--text3); letter-spacing: 0.05em;
2108
2204
  margin: 0 0 8px;
2109
2205
  }
2110
2206
  .agent-summary-grid {
2111
2207
  display: grid; grid-template-columns: auto 1fr; gap: 4px 12px;
2112
- font-size: 12px;
2208
+ font-size: var(--fs-small);
2113
2209
  }
2114
2210
  .agent-summary-grid dt { color: var(--text3); font-family: var(--mono); }
2115
2211
  .agent-summary-grid dd { color: var(--text); font-family: var(--mono); margin: 0; }
2116
2212
  .run-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
2117
2213
  .run-row {
2118
2214
  display: grid; grid-template-columns: auto auto 1fr auto; gap: 10px;
2119
- font-size: 11px; font-family: var(--mono); color: var(--text2);
2215
+ font-size: var(--fs-caption); font-family: var(--mono); color: var(--text2);
2120
2216
  padding: 4px 0;
2121
2217
  }
2122
2218
  .run-row .rr-verdict.ok { color: var(--agent-ok); }
@@ -2126,15 +2222,15 @@ button { font-family: inherit; cursor: pointer; }
2126
2222
  .failure-list { list-style: none; margin: 0; padding: 0; }
2127
2223
  .failure-list li {
2128
2224
  display: flex; gap: 10px; padding: 4px 0;
2129
- font-family: var(--mono); font-size: 12px;
2225
+ font-family: var(--mono); font-size: var(--fs-small);
2130
2226
  }
2131
2227
  .failure-list .fm-key { flex: 1; color: var(--text); }
2132
2228
  .failure-list .fm-count { color: var(--agent-failing); }
2133
- .failure-list .fm-last { color: var(--text3); font-size: 11px; }
2229
+ .failure-list .fm-last { color: var(--text3); font-size: var(--fs-caption); }
2134
2230
  .agent-actions { display: flex; gap: 8px; flex-wrap: wrap; }
2135
2231
  .agent-actions button {
2136
2232
  background: var(--bg-muted); border: 1px solid var(--border);
2137
- color: var(--text); font-family: var(--mono); font-size: 12px;
2233
+ color: var(--text); font-family: var(--mono); font-size: var(--fs-small);
2138
2234
  padding: 6px 12px; border-radius: 6px;
2139
2235
  }
2140
2236
  .agent-actions button:hover { border-color: var(--border-strong); }
@@ -2169,7 +2265,7 @@ button { font-family: inherit; cursor: pointer; }
2169
2265
  border-radius: 16px;
2170
2266
  padding: 32px;
2171
2267
  }
2172
- .share-card h2 {
2268
+ .share-card h2:not(.section-title) {
2173
2269
  font-family: var(--serif); font-weight: 500;
2174
2270
  font-size: 28px; letter-spacing: -0.01em;
2175
2271
  margin: 0 0 6px;
@@ -2184,13 +2280,13 @@ button { font-family: inherit; cursor: pointer; }
2184
2280
  margin-bottom: 16px;
2185
2281
  }
2186
2282
  .share-toggle-row .label-grp { flex: 1; }
2187
- .share-toggle-row .label-grp .l { font-weight: 600; font-size: 14px; }
2188
- .share-toggle-row .label-grp .h { color: var(--text2); font-size: 12px; }
2283
+ .share-toggle-row .label-grp .l { font-weight: 600; font-size: var(--fs-base); }
2284
+ .share-toggle-row .label-grp .h { color: var(--text2); font-size: var(--fs-small); }
2189
2285
 
2190
- .notif-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; transition: border-color 0.15s; }
2286
+ .notif-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--bg-muted); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; transition: border-color 0.15s; }
2191
2287
  .notif-row:hover { border-color: var(--accent); }
2192
2288
  .notif-row .emoji { font-size: 16px; flex-shrink: 0; }
2193
- .notif-row strong { color: var(--text1); margin-right: 4px; }
2289
+ .notif-row strong { color: var(--text); margin-right: 4px; }
2194
2290
  .notif-row input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
2195
2291
 
2196
2292
  .toggle {
@@ -2224,24 +2320,24 @@ button { font-family: inherit; cursor: pointer; }
2224
2320
  }
2225
2321
  .url-block .url-text {
2226
2322
  flex: 1; font-family: var(--mono);
2227
- font-size: 12.5px; color: var(--text);
2323
+ font-size: var(--fs-small); color: var(--text);
2228
2324
  white-space: nowrap; overflow: hidden;
2229
2325
  text-overflow: ellipsis;
2230
2326
  }
2231
2327
  .btn-black {
2232
2328
  background: #0f1115; color: #fff; border: none;
2233
2329
  padding: 8px 14px; border-radius: 999px;
2234
- font-size: 12px; font-weight: 500;
2330
+ font-size: var(--fs-small); font-weight: 500;
2235
2331
  }
2236
2332
  .btn-ghost-sm {
2237
2333
  background: transparent;
2238
2334
  border: 1px solid var(--border-strong);
2239
2335
  color: var(--text);
2240
2336
  padding: 8px 12px; border-radius: 999px;
2241
- font-size: 12px;
2337
+ font-size: var(--fs-small);
2242
2338
  }
2243
2339
  .share-note {
2244
- font-size: 13px; color: var(--text2);
2340
+ font-size: var(--fs-body); color: var(--text2);
2245
2341
  margin-top: 16px; line-height: 1.55;
2246
2342
  }
2247
2343
  .share-note strong { color: var(--text); }
@@ -2251,7 +2347,7 @@ button { font-family: inherit; cursor: pointer; }
2251
2347
  padding: 12px 16px;
2252
2348
  background: var(--bg-muted);
2253
2349
  border-radius: 10px;
2254
- font-size: 13px; color: var(--text2);
2350
+ font-size: var(--fs-body); color: var(--text2);
2255
2351
  margin-top: 12px;
2256
2352
  }
2257
2353
  .spinner {
@@ -2265,7 +2361,7 @@ button { font-family: inherit; cursor: pointer; }
2265
2361
 
2266
2362
  .share-badge {
2267
2363
  display: inline-flex; align-items: center; gap: 4px;
2268
- font-size: 10px;
2364
+ font-size: var(--fs-eyebrow);
2269
2365
  padding: 2px 6px; border-radius: 999px;
2270
2366
  font-family: var(--mono);
2271
2367
  text-transform: uppercase;
@@ -2292,7 +2388,7 @@ button { font-family: inherit; cursor: pointer; }
2292
2388
  display: inline-flex; align-items: center; gap: 4px;
2293
2389
  height: 22px; padding: 0 9px;
2294
2390
  border-radius: 999px;
2295
- font-size: 11px; font-weight: 500;
2391
+ font-size: var(--fs-caption); font-weight: 500;
2296
2392
  background: var(--bg-strong); color: var(--text2);
2297
2393
  border: 1px solid var(--border);
2298
2394
  cursor: pointer;
@@ -2306,7 +2402,7 @@ button { font-family: inherit; cursor: pointer; }
2306
2402
  .fchip-clear { background: transparent; color: var(--text3); border-color: transparent; }
2307
2403
  .fchip-clear:hover { background: var(--bg-strong); color: var(--status-blocked); }
2308
2404
  .filter-section-label {
2309
- font-family: var(--mono); font-size: 10px;
2405
+ font-family: var(--mono); font-size: var(--fs-eyebrow);
2310
2406
  letter-spacing: 0.06em; text-transform: uppercase;
2311
2407
  color: var(--text3); padding: 0 4px;
2312
2408
  }
@@ -2317,7 +2413,7 @@ button { font-family: inherit; cursor: pointer; }
2317
2413
  cursor: pointer;
2318
2414
  position: relative;
2319
2415
  }
2320
- .card-status-edit .pill { font-size: 10px; height: 18px; padding: 0 7px; }
2416
+ .card-status-edit .pill { font-size: var(--fs-eyebrow); height: 18px; padding: 0 7px; }
2321
2417
  .card-status-chev {
2322
2418
  color: var(--text3); transition: color 0.12s;
2323
2419
  display: inline-flex; align-items: center;
@@ -2338,7 +2434,7 @@ button { font-family: inherit; cursor: pointer; }
2338
2434
  display: flex; align-items: center; gap: 8px;
2339
2435
  padding: 6px 10px;
2340
2436
  border-radius: 4px;
2341
- font-size: 12px; font-weight: 500;
2437
+ font-size: var(--fs-small); font-weight: 500;
2342
2438
  color: var(--text);
2343
2439
  background: transparent; border: none;
2344
2440
  width: 100%; text-align: left;
@@ -2357,19 +2453,19 @@ button { font-family: inherit; cursor: pointer; }
2357
2453
  display: grid;
2358
2454
  grid-template-columns: 130px 1fr 70px;
2359
2455
  align-items: center; gap: 12px;
2360
- margin-bottom: 10px; font-size: 12.5px;
2456
+ margin-bottom: 10px; font-size: var(--fs-small);
2361
2457
  }
2362
2458
  .agent-cost-row .name { font-family: var(--mono); color: var(--text); }
2363
2459
  .agent-cost-row .rail { display: block; height: 6px; background: var(--bg-strong); border-radius: 999px; overflow: hidden; }
2364
2460
  .agent-cost-row .fill { display: block; height: 100%; background: var(--accent); border-radius: 999px; min-width: 2px; }
2365
- .agent-cost-row .amt { text-align: right; color: var(--text2); font-family: var(--mono); font-size: 11px; }
2461
+ .agent-cost-row .amt { text-align: right; color: var(--text2); font-family: var(--mono); font-size: var(--fs-caption); }
2366
2462
 
2367
2463
  /* ── Task timeline ────────────────────────────────────────────────────────── */
2368
2464
  .tl-event {
2369
2465
  display: flex; gap: 10px;
2370
2466
  padding: 6px 0;
2371
2467
  border-top: 1px solid var(--border);
2372
- font-size: 12px;
2468
+ font-size: var(--fs-small);
2373
2469
  }
2374
2470
  .tl-event:first-child { border-top: none; }
2375
2471
  .tl-dot {
@@ -2379,22 +2475,26 @@ button { font-family: inherit; cursor: pointer; }
2379
2475
  margin-top: 4px; flex-shrink: 0;
2380
2476
  }
2381
2477
  .tl-content { flex: 1; display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px; }
2382
- .tl-actor { font-family: var(--mono); font-size: 11px; font-weight: 600; color: var(--accent); }
2478
+ .tl-actor { font-family: var(--mono); font-size: var(--fs-caption); font-weight: 600; color: var(--accent); }
2383
2479
  .tl-action { color: var(--text2); }
2384
2480
  .tl-change {
2385
- font-family: var(--mono); font-size: 11px;
2481
+ font-family: var(--mono); font-size: var(--fs-caption);
2386
2482
  background: var(--bg-strong); color: var(--text);
2387
2483
  padding: 1px 6px; border-radius: 3px;
2388
2484
  }
2389
- .tl-ts { margin-left: auto; font-family: var(--mono); font-size: 10px; color: var(--text3); }
2390
- .side-section-head { font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text3); margin-bottom: 8px; margin-top: 16px; }
2485
+ .tl-ts { margin-left: auto; font-family: var(--mono); font-size: var(--fs-eyebrow); color: var(--text3); }
2486
+ .side-section-head { font-family: var(--mono); font-size: var(--fs-eyebrow); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text3); margin-bottom: 8px; margin-top: 16px; }
2391
2487
 
2392
2488
  /* ── Security tab: hero KPI tile + activity feed ──────────────────────────── */
2393
- .kpi-hero .kpi-value-hero { font-size: 36px; line-height: 1.1; font-weight: 500; }
2394
- .kpi-hero .kpi-label { font-size: 13px; font-weight: 500; }
2395
- .kpi-empty .kpi-value { color: var(--accent); font-size: 22px; }
2489
+ .kpi-hero .kpi-value-hero {
2490
+ font-family: var(--serif); font-weight: 500;
2491
+ font-variant-numeric: tabular-nums;
2492
+ font-size: var(--fs-num-l); line-height: 1.1; letter-spacing: -0.02em;
2493
+ }
2494
+ .kpi-hero .kpi-label { font-size: var(--fs-body); font-weight: 500; }
2495
+ .kpi-empty .kpi-value { color: var(--accent); font-size: var(--fs-title-l); }
2396
2496
  .kpi-empty .kpi-value::after {
2397
- content: ' all clear'; font-size: 11px; color: var(--text3); font-weight: 400;
2497
+ content: ' all clear'; font-size: var(--fs-caption); color: var(--text3); font-weight: 400;
2398
2498
  }
2399
2499
  @media (max-width: 900px) {
2400
2500
  .kpi-hero { grid-column: span 2; }
@@ -2463,7 +2563,7 @@ button { font-family: inherit; cursor: pointer; }
2463
2563
  display: flex; flex-wrap: wrap;
2464
2564
  gap: 8px 10px; align-items: baseline;
2465
2565
  }
2466
- .inbox-row .ttl { order: 1; flex: 1 1 100%; font-size: 14px; }
2566
+ .inbox-row .ttl { order: 1; flex: 1 1 100%; font-size: var(--fs-base); }
2467
2567
  /* Two lines, wrapped — a phone has the vertical room it lacks sideways. */
2468
2568
  .inbox-row .ttl > :first-child { -webkit-line-clamp: 2; white-space: normal; }
2469
2569
  /* The id and the status share a line: stacked, each took a full row of its
@@ -2510,7 +2610,7 @@ button { font-family: inherit; cursor: pointer; }
2510
2610
  their own size say it after. A button added tomorrow is covered by default;
2511
2611
  one that must stay small has to opt out in writing. */
2512
2612
  button, [role="button"], .nav-item, .toggle, summary { min-height: 44px; }
2513
- .gate-btn, .inbox-row .actions .gate-btn { height: 44px; font-size: 13px; }
2613
+ .gate-btn, .inbox-row .actions .gate-btn { height: 44px; font-size: var(--fs-body); }
2514
2614
  .menu-btn { width: 44px; height: 44px; }
2515
2615
  .btn-bell { min-width: 44px; justify-content: center; }
2516
2616
  .gate-actions, .side-gate-actions, .inbox-row .actions { gap: 12px; }
@@ -2540,7 +2640,7 @@ button { font-family: inherit; cursor: pointer; }
2540
2640
  </svg>
2541
2641
  </span>
2542
2642
  <span class="brand-name">greatcto</span>
2543
- <span id="build-version" title="great_cto build version" style="margin-left:6px;font-size:10px;color:var(--text3);font-family:var(--mono);align-self:center">—</span>
2643
+ <span id="build-version" title="great_cto build version" style="margin-left:6px;font-size:var(--fs-eyebrow);color:var(--text3);font-family:var(--mono);align-self:center">—</span>
2544
2644
  </div>
2545
2645
  <div class="proj-switch" id="proj-switch" onclick="toggleProjMenu(event)">
2546
2646
  <span class="proj-icon" id="proj-switch-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg></span>
@@ -2625,7 +2725,7 @@ button { font-family: inherit; cursor: pointer; }
2625
2725
  <span class="task-count" id="topbar-task-count">0 ISSUES</span>
2626
2726
  <button class="btn-bell" data-tab="share" onclick="switchTab('share', this)" title="Share a public project report" aria-label="Share" style="gap:6px;padding:6px 10px;">
2627
2727
  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
2628
- <span class="btn-label" style="font-size:12px;font-weight:500">Share</span>
2728
+ <span class="btn-label" style="font-size:var(--fs-small);font-weight:500">Share</span>
2629
2729
  <span id="share-nav-badge" class="share-badge off"><span class="share-badge-dot"></span>off</span>
2630
2730
  </button>
2631
2731
  <button class="btn-bell" id="board-theme-btn" onclick="toggleBoardTheme()" title="Toggle light / dark theme" aria-label="Toggle theme">🌙</button>
@@ -2647,7 +2747,7 @@ button { font-family: inherit; cursor: pointer; }
2647
2747
  <div class="inbox-summary" id="inbox-summary"></div>
2648
2748
  <!-- Top rung of the evidence ladder: are the bytes that were reviewed
2649
2749
  still the bytes in the tree? Hidden while they match. -->
2650
- <div id="inbox-receipt" style="display:none;margin:6px 0 2px;font-size:12px"></div>
2750
+ <div id="inbox-receipt" style="display:none;margin:6px 0 2px;font-size:var(--fs-small)"></div>
2651
2751
 
2652
2752
  <!-- Resume — pick up where you left off -->
2653
2753
  <div class="resume-card" id="resume-card" style="display:none">
@@ -2675,9 +2775,9 @@ button { font-family: inherit; cursor: pointer; }
2675
2775
  </div>
2676
2776
 
2677
2777
  <!-- Goal ancestry banner (Paperclip pattern) — shows archetype+compliance+phase -->
2678
- <div id="goal-ancestry-banner" style="display:none;font-family:var(--mono,monospace);font-size:11px;color:var(--text3,#6b7280);background:var(--bg-muted);padding:6px 10px;border-radius:6px;margin-bottom:10px;letter-spacing:0.3px;"></div>
2778
+ <div id="goal-ancestry-banner" style="display:none;font-family:var(--mono,monospace);font-size:var(--fs-caption);color:var(--text3,#6b7280);background:var(--bg-muted);padding:6px 10px;border-radius:6px;margin-bottom:10px;letter-spacing:0.3px;"></div>
2679
2779
  <!-- Tool failure rate watchdog warning -->
2680
- <div id="tool-failure-rate" style="display:none;font-size:12px;color:var(--status-blocked,#f87171);background:rgba(248,113,113,0.08);padding:6px 10px;border-radius:6px;margin-bottom:10px;"></div>
2780
+ <div id="tool-failure-rate" style="display:none;font-size:var(--fs-small);color:var(--status-blocked,#f87171);background:rgba(248,113,113,0.08);padding:6px 10px;border-radius:6px;margin-bottom:10px;"></div>
2681
2781
 
2682
2782
  <div class="inbox-section">
2683
2783
  <div class="inbox-section-head">
@@ -2745,6 +2845,7 @@ button { font-family: inherit; cursor: pointer; }
2745
2845
 
2746
2846
  <!-- Kanban -->
2747
2847
  <div class="panel" id="panel-kanban">
2848
+ <h1 class="page-title">Tasks</h1>
2748
2849
  <div id="filter-bar" class="filter-bar"></div>
2749
2850
  <div class="kanban" id="kanban-board"></div>
2750
2851
  </div>
@@ -2753,6 +2854,7 @@ button { font-family: inherit; cursor: pointer; }
2753
2854
  <!-- Metrics -->
2754
2855
  <div class="panel" id="panel-dashboard">
2755
2856
  <div class="metrics-page">
2857
+ <h1 class="page-title">Metrics</h1>
2756
2858
  <div class="period-toolbar">
2757
2859
  <span class="period-label">PERIOD</span>
2758
2860
  <div class="period-chips" id="period-chips">
@@ -2773,7 +2875,7 @@ button { font-family: inherit; cursor: pointer; }
2773
2875
  project had declared no caps, which is exactly the project that
2774
2876
  needs the button to declare one. -->
2775
2877
  <div class="mp-section-label" style="margin-top:24px">Agent budgets</div>
2776
- <div id="agent-budgets" class="mp-card" style="padding:12px 16px;font-size:12px;margin-bottom:4px;"></div>
2878
+ <div id="agent-budgets" class="mp-card" style="padding:12px 16px;font-size:var(--fs-small);margin-bottom:4px;"></div>
2777
2879
 
2778
2880
  <div class="mp-section-label" id="mp-cost-label" style="margin-top:24px">Cost · last 30 days</div>
2779
2881
  <div class="mp-card cost-panel" id="cost-panel">
@@ -2853,10 +2955,10 @@ button { font-family: inherit; cursor: pointer; }
2853
2955
  <section aria-label="Legacy agents" id="legacy-agents-section" style="margin-top:20px" hidden>
2854
2956
  <div class="mp-card">
2855
2957
  <div class="mp-card-head">
2856
- <h3 style="font-size:13px">Legacy / non-canonical sources</h3>
2958
+ <h3 style="font-size:var(--fs-body)">Legacy / non-canonical sources</h3>
2857
2959
  <span class="mp-card-meta" id="legacy-agents-count"></span>
2858
2960
  </div>
2859
- <div style="font-size:12px;color:var(--text3);margin-top:6px">
2961
+ <div style="font-size:var(--fs-small);color:var(--text3);margin-top:6px">
2860
2962
  Verdicts logged under agent names not in <code>~/.claude/agents/great_cto-*.md</code>
2861
2963
  (older log files, custom scripts). Listed in <code>m.legacy_agent_runs</code>; cleanup is a
2862
2964
  one-time housekeeping task.
@@ -2898,7 +3000,7 @@ button { font-family: inherit; cursor: pointer; }
2898
3000
 
2899
3001
  <div class="panel" id="panel-budgets" role="tabpanel">
2900
3002
  <div class="metrics-page">
2901
- <h1>Agent budgets</h1>
3003
+ <h1 class="page-title">Agent budgets</h1>
2902
3004
  <p class="muted" style="max-width:70ch;margin:6px 0 20px">
2903
3005
  A cap on what one agent may spend. When measured spend passes it, the pipeline
2904
3006
  refuses to dispatch that agent and says so — it does not fail quietly.
@@ -2910,7 +3012,7 @@ button { font-family: inherit; cursor: pointer; }
2910
3012
  </div>
2911
3013
 
2912
3014
  <div class="panel" id="panel-docs">
2913
- <h1>Docs</h1>
3015
+ <h1 class="page-title">Docs</h1>
2914
3016
  <div class="docs-search">
2915
3017
  <input type="search" id="docs-q" placeholder="Search inside documents…"
2916
3018
  aria-label="Search inside documents"
@@ -2961,7 +3063,7 @@ button { font-family: inherit; cursor: pointer; }
2961
3063
  <button class="btn-black" onclick="copyUrl(event)">Copy</button>
2962
3064
 
2963
3065
  </div>
2964
- <div class="share-meta" id="share-meta" style="font-size:11px;color:var(--text3);margin-top:8px;font-family:var(--mono);"></div>
3066
+ <div class="share-meta" id="share-meta" style="font-size:var(--fs-caption);color:var(--text3);margin-top:8px;font-family:var(--mono);"></div>
2965
3067
  <div class="share-note">
2966
3068
  The report includes shipped tasks, recent activity, agent utilization, and the six headline metrics.
2967
3069
  Open issues, code, and credentials are <strong>never</strong> exposed.
@@ -2979,8 +3081,9 @@ button { font-family: inherit; cursor: pointer; }
2979
3081
  <!-- Notifications -->
2980
3082
  <div class="panel" id="panel-notifications">
2981
3083
  <div class="share-page">
3084
+ <h1 class="page-title">Notifications</h1>
2982
3085
  <div class="share-card">
2983
- <h2>Email alerts</h2>
3086
+ <h2 class="section-title">Email alerts</h2>
2984
3087
  <p class="sub">Get notified when something needs your attention — even when you're away from the board. Verify your email once, pick what to receive, done. No API keys, no extra signup.</p>
2985
3088
 
2986
3089
  <div class="share-toggle-row">
@@ -2995,34 +3098,34 @@ button { font-family: inherit; cursor: pointer; }
2995
3098
 
2996
3099
  <!-- Step 1: enter email + send code -->
2997
3100
  <div id="notif-step-email">
2998
- <div style="font-size:11px;color:var(--text3);margin-bottom:4px;font-family:var(--mono);letter-spacing:.05em">RECIPIENT EMAIL</div>
3101
+ <div style="font-size:var(--fs-caption);color:var(--text3);margin-bottom:4px;font-family:var(--mono);letter-spacing:.05em">RECIPIENT EMAIL</div>
2999
3102
  <div style="display:flex;gap:8px;flex-wrap:wrap">
3000
- <input id="notif-to" type="email" placeholder="you@example.com" style="flex:1 1 280px;min-width:240px;padding:8px 10px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;color:var(--text1);font-size:14px" />
3103
+ <input id="notif-to" type="email" placeholder="you@example.com" style="flex:1 1 280px;min-width:240px;padding:8px 10px;background:var(--bg-muted);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:var(--fs-base)" />
3001
3104
  <button class="btn-black" onclick="sendVerifyCode()">Send code</button>
3002
3105
  </div>
3003
3106
  </div>
3004
3107
 
3005
3108
  <!-- Step 2: enter code -->
3006
3109
  <div id="notif-step-code" style="display:none;margin-top:14px">
3007
- <div style="font-size:11px;color:var(--text3);margin-bottom:4px;font-family:var(--mono);letter-spacing:.05em">VERIFICATION CODE (CHECK <span id="notif-code-target">YOUR INBOX</span>)</div>
3110
+ <div style="font-size:var(--fs-caption);color:var(--text3);margin-bottom:4px;font-family:var(--mono);letter-spacing:.05em">VERIFICATION CODE (CHECK <span id="notif-code-target">YOUR INBOX</span>)</div>
3008
3111
  <div style="display:flex;gap:8px;flex-wrap:wrap">
3009
- <input id="notif-code" type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" placeholder="123456" style="flex:1 1 200px;min-width:140px;padding:8px 10px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;color:var(--text1);font-size:18px;font-family:var(--mono);letter-spacing:.2em;text-align:center" />
3112
+ <input id="notif-code" type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" placeholder="123456" style="flex:1 1 200px;min-width:140px;padding:8px 10px;background:var(--bg-muted);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:var(--fs-title-m);font-family:var(--mono);letter-spacing:.2em;text-align:center" />
3010
3113
  <button class="btn-black" onclick="confirmVerifyCode()">Verify</button>
3011
3114
  <button class="btn-ghost-sm" onclick="sendVerifyCode()">Resend code</button>
3012
3115
  </div>
3013
3116
  </div>
3014
3117
 
3015
3118
  <!-- Step 3: verified -->
3016
- <div id="notif-step-verified" style="display:none;margin-top:14px;padding:10px 14px;background:rgba(0,217,126,0.08);border:1px solid rgba(0,217,126,0.30);border-radius:8px;display:none;align-items:center;gap:10px;font-size:13.5px">
3017
- <span style="color:var(--accent);font-size:18px">✓</span>
3119
+ <div id="notif-step-verified" style="display:none;margin-top:14px;padding:10px 14px;background:rgba(0,217,126,0.08);border:1px solid rgba(0,217,126,0.30);border-radius:8px;display:none;align-items:center;gap:10px;font-size: var(--fs-body)">
3120
+ <span style="color:var(--accent);font-size:var(--fs-title-m)">✓</span>
3018
3121
  <span><strong id="notif-verified-email">—</strong> verified · alerts delivered through <code>greatcto.systems/notify</code></span>
3019
3122
  <button class="btn-ghost-sm" style="margin-left:auto" onclick="changeEmail()">Change</button>
3020
3123
  </div>
3021
3124
 
3022
3125
  <div class="share-note" style="margin-top:24px">
3023
- <strong style="color:var(--text1)">Which alerts to send</strong>
3126
+ <strong style="color:var(--text)">Which alerts to send</strong>
3024
3127
  </div>
3025
- <div id="notif-triggers" style="display:flex;flex-direction:column;gap:10px;margin-top:10px;font-size:13.5px">
3128
+ <div id="notif-triggers" style="display:flex;flex-direction:column;gap:10px;margin-top:10px;font-size: var(--fs-body)">
3026
3129
  <label class="notif-row"><input type="checkbox" data-trigger="incident.p0"> <span class="emoji">🚨</span><strong>P0 incident</strong> — production failure, ack-grade</label>
3027
3130
  <label class="notif-row"><input type="checkbox" data-trigger="gate.stale"> <span class="emoji">⏸️</span><strong>Gate stale &gt; 2h</strong> — ship gate waiting for your approval</label>
3028
3131
  <label class="notif-row"><input type="checkbox" data-trigger="gate.blocked"> <span class="emoji">🛡️</span><strong>Security BLOCKED</strong> — security-officer rejected a merge</label>
@@ -3035,7 +3138,7 @@ button { font-family: inherit; cursor: pointer; }
3035
3138
  <button class="btn-ghost-sm" onclick="testNotifications()">Send test email</button>
3036
3139
  </div>
3037
3140
 
3038
- <div id="notif-status" style="margin-top:14px;font-size:13px;font-family:var(--mono)"></div>
3141
+ <div id="notif-status" style="margin-top:14px;font-size:var(--fs-body);font-family:var(--mono)"></div>
3039
3142
 
3040
3143
  <div class="share-note" style="margin-top:24px">
3041
3144
  Free — up to 100 emails per day per verified email. Delivered through our <code>greatcto.systems/notify</code> relay; your inbox address never leaves your machine without your verification. Local state in <code>~/.great_cto/notifications.json</code>.
@@ -3044,7 +3147,7 @@ button { font-family: inherit; cursor: pointer; }
3044
3147
 
3045
3148
  <!-- ── Browser push card ───────────────────────────────────────── -->
3046
3149
  <div class="share-card" style="margin-top:16px" id="push-card">
3047
- <h2>Browser push</h2>
3150
+ <h2 class="section-title">Browser push</h2>
3048
3151
  <p class="sub">Native desktop notifications — works even when the board tab is in the background. No email required. Uses Web Push (VAPID) — keys stay local.</p>
3049
3152
  <div class="share-toggle-row">
3050
3153
  <div class="label-grp">
@@ -3055,7 +3158,7 @@ button { font-family: inherit; cursor: pointer; }
3055
3158
  <input type="checkbox" id="push-toggle" onclick="event.stopPropagation()">
3056
3159
  </div>
3057
3160
  </div>
3058
- <div id="push-status" style="margin-top:10px;font-size:12.5px;font-family:var(--mono);color:var(--text3)"></div>
3161
+ <div id="push-status" style="margin-top:10px;font-size: var(--fs-small);font-family:var(--mono);color:var(--text3)"></div>
3059
3162
  <div class="share-note" style="margin-top:16px">
3060
3163
  Push subscriptions are stored locally in <code>~/.great_cto/push-subscriptions.json</code>. Notifications fire for the same 5 triggers as email alerts.
3061
3164
  </div>
@@ -3063,10 +3166,10 @@ button { font-family: inherit; cursor: pointer; }
3063
3166
 
3064
3167
  <!-- ── In-app notification history ─────────────────────────────── -->
3065
3168
  <div class="share-card" style="margin-top:16px">
3066
- <h2>Recent notifications</h2>
3169
+ <h2 class="section-title">Recent notifications</h2>
3067
3170
  <p class="sub">Last 100 alerts fired this session. Also accessible via the notification drawer (bell icon).</p>
3068
3171
  <div id="notif-history-inline" style="display:flex;flex-direction:column;gap:0;margin-top:12px;
3069
- border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:13px">
3172
+ border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:var(--fs-body)">
3070
3173
  <div id="notif-history-empty" style="padding:20px 16px;color:var(--text3);text-align:center">
3071
3174
  No notifications yet this session.
3072
3175
  </div>
@@ -3250,7 +3353,7 @@ function freshnessBadge(doc) {
3250
3353
  const unreadable = doc.freshnessBasis === 'unreadable';
3251
3354
  const colour = v === 'stale' ? 'var(--status-gate)' : 'var(--text3)';
3252
3355
  const label = v === 'stale' ? 'stale' : (unreadable ? 'could not read' : 'unknown');
3253
- return `<span title="${esc(doc.freshnessWhy || '')}" style="font-family:var(--mono);font-size:10px;margin-left:6px;padding:0 4px;border:1px solid var(--border);border-radius:3px;color:${colour}">${esc(label)}</span>`;
3356
+ return `<span title="${esc(doc.freshnessWhy || '')}" style="font-family:var(--mono);font-size:var(--fs-eyebrow);margin-left:6px;padding:0 4px;border:1px solid var(--border);border-radius:3px;color:${colour}">${esc(label)}</span>`;
3254
3357
  }
3255
3358
 
3256
3359
  // Will this gate wait for you, or did the pipeline already go past it?
@@ -3284,7 +3387,7 @@ function gateTierNote(t) {
3284
3387
  // conclusively, which says nothing about the rest of what this agent is for.
3285
3388
  // It is the reading most worth a second look, so it reads differently.
3286
3389
  const caveat = thin ? ' — one eval, coverage unmeasured' : '';
3287
- return `<div style="margin-top:3px;font-size:11px;color:${colour}">
3390
+ return `<div style="margin-top:3px;font-size:var(--fs-caption);color:${colour}">
3288
3391
  <span style="font-family:var(--mono);border:1px solid var(--border);border-radius:3px;padding:0 4px">${esc(info.tier)}</span>
3289
3392
  the pipeline did not wait for this${esc(caveat)} · <span title="${esc(info.why || '')}">${esc((info.why || '').slice(0, 90))}</span>
3290
3393
  </div>`;
@@ -3324,7 +3427,7 @@ async function refreshStandDowns() {
3324
3427
  // unmeasured coverage. That is the row most worth a second look, so it is
3325
3428
  // the row that looks different.
3326
3429
  const thin = r.tier === 'notify-thin';
3327
- const tierTag = `<span style="font-family:var(--mono);font-size:10px;padding:1px 5px;border-radius:3px;border:1px solid var(--border);color:${thin ? 'var(--status-gate)' : 'var(--text3)'}">${esc(r.tier || 'unknown')}</span>`;
3430
+ const tierTag = `<span style="font-family:var(--mono);font-size:var(--fs-eyebrow);padding:1px 5px;border-radius:3px;border:1px solid var(--border);color:${thin ? 'var(--status-gate)' : 'var(--text3)'}">${esc(r.tier || 'unknown')}</span>`;
3328
3431
  return `
3329
3432
  <div class="inbox-row">
3330
3433
  <span class="id">gate:${esc(r.gate || '?')}</span>
@@ -3369,7 +3472,7 @@ function renderInboxList(rootId, items, countId, opts = {}) {
3369
3472
  <button class="gate-btn gate-approve" onclick="event.stopPropagation(); gateAction('${esc(t.id)}', 'approve')">Approve</button>
3370
3473
  <button class="gate-btn gate-reject" onclick="event.stopPropagation(); gateAction('${esc(t.id)}', 'reject')">Reject</button>
3371
3474
  </div>`
3372
- : `<div class="actions"><span style="font-family:var(--mono);font-size:11px;color:var(--text3)">${t.status || ''}</span></div>`;
3475
+ : `<div class="actions"><span style="font-family:var(--mono);font-size:var(--fs-caption);color:var(--text3)">${t.status || ''}</span></div>`;
3373
3476
  const desc = t.description ? `<span class="ttl-desc">${esc(t.description.slice(0, 140))}${t.description.length > 140 ? '…' : ''}</span>` : '';
3374
3477
  // Only gate rows can stand down, so only they carry the note.
3375
3478
  const tierNote = opts.showApprove ? gateTierNote(t) : '';
@@ -3554,11 +3657,32 @@ async function loadBudgetsPage() {
3554
3657
  if (!body) return;
3555
3658
  body.innerHTML = '<div class="muted">loading…</div>';
3556
3659
 
3557
- const [h] = await Promise.all([
3558
- api(`/api/heartbeat${pqs()}`),
3559
- (typeof refreshAgentsInstalled === 'function' && !(fleetState?.agents || []).length)
3560
- ? refreshAgentsInstalled() : Promise.resolve(),
3561
- ]);
3660
+ // Three states, not two: loading | loaded | failed.
3661
+ //
3662
+ // Without this catch the two reads below were awaited bare, so a rejection
3663
+ // returned from the function and left `loading…` on screen permanently. Caught
3664
+ // once in the wild: opening Budgets during startup raced the agent list, the
3665
+ // promise rejected, and the page sat there looking busy. A page that failed
3666
+ // and a page still working are the same picture, and the one you are looking
3667
+ // at is the one where nothing more is coming.
3668
+ let h;
3669
+ try {
3670
+ [h] = await Promise.all([
3671
+ api(`/api/heartbeat${pqs()}`),
3672
+ (typeof refreshAgentsInstalled === 'function' && !(fleetState?.agents || []).length)
3673
+ ? refreshAgentsInstalled() : Promise.resolve(),
3674
+ ]);
3675
+ } catch (err) {
3676
+ body.innerHTML = `
3677
+ <div class="budgets-failed">
3678
+ <div class="bf-head">Could not read the caps.</div>
3679
+ <div class="bf-why">${esc(String(err && err.message || err))}</div>
3680
+ <div class="bf-note">Caps already set are unaffected — this failed to
3681
+ <em>read</em> them, and the pipeline reads them itself, not from here.</div>
3682
+ <button class="btn" onclick="loadBudgetsPage()">Try again</button>
3683
+ </div>`;
3684
+ return;
3685
+ }
3562
3686
  const caps = h?.budgets || {};
3563
3687
  const agents = (fleetState?.agents || []).filter((a) => !a.retired);
3564
3688
 
@@ -3902,7 +4026,7 @@ function showVerdictDetail(idx) {
3902
4026
  <span>${esc(a.path)}</span>
3903
4027
  </a>`;
3904
4028
  }).join('')
3905
- : '<span style="color: var(--text3); font-size: 12px;">No artefacts referenced in this verdict</span>';
4029
+ : '<span style="color: var(--text3); font-size: var(--fs-small);">No artefacts referenced in this verdict</span>';
3906
4030
 
3907
4031
  // Format additional metadata (cost, feature, etc.) as small chips
3908
4032
  const metaKeys = ['feature', 'task', 'cost'];
@@ -3930,7 +4054,7 @@ function showVerdictDetail(idx) {
3930
4054
  <div class="rdm-artefacts-title">Artefacts (click to open in editor)</div>
3931
4055
  ${artefactsHtml}
3932
4056
  </div>
3933
- <div class="rdm-foot">Press <kbd style="background:var(--bg-muted);border:1px solid var(--border);border-radius:3px;padding:1px 5px;font-size:10px;">Esc</kbd> or click outside to close</div>
4057
+ <div class="rdm-foot">Press <kbd style="background:var(--bg-muted);border:1px solid var(--border);border-radius:3px;padding:1px 5px;font-size:var(--fs-eyebrow);">Esc</kbd> or click outside to close</div>
3934
4058
  </div>
3935
4059
  `;
3936
4060
  document.body.appendChild(modal);
@@ -4410,7 +4534,7 @@ async function loadDocs() {
4410
4534
  ? `<div class="card"><b>Pipeline</b> <span class="muted">— how a feature moves, drawn from shared/pipeline.toml</span>
4411
4535
  ${renderDeclaredPipeline(d.pipeline)}
4412
4536
  <details style="margin-top:8px"><summary class="muted">Mermaid source</summary>
4413
- <pre style="white-space:pre-wrap;font-size:11px">${esc(d.pipeline)}</pre></details></div>`
4537
+ <pre style="white-space:pre-wrap;font-size:var(--fs-caption)">${esc(d.pipeline)}</pre></details></div>`
4414
4538
  : '';
4415
4539
 
4416
4540
  const map = d.map ? `<div class="card"><b>Components</b>
@@ -4418,19 +4542,19 @@ async function loadDocs() {
4418
4542
  <div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:10px">
4419
4543
  ${d.map.nodes.map(n => `<div style="border:1px solid var(--border,#ddd);border-radius:6px;padding:8px 10px;min-width:120px">
4420
4544
  <div><b>${esc(n.label)}</b> <span class="muted">${n.count}</span></div>
4421
- <div class="muted" style="font-size:11px">${esc(n.what || '')}</div></div>`).join('')}
4545
+ <div class="muted" style="font-size:var(--fs-caption)">${esc(n.what || '')}</div></div>`).join('')}
4422
4546
  </div>
4423
- ${d.map.edges.length ? `<div class="muted" style="margin-top:8px;font-size:11px">
4547
+ ${d.map.edges.length ? `<div class="muted" style="margin-top:8px;font-size:var(--fs-caption)">
4424
4548
  ${d.map.edges.map(e => `${esc(e.from)}→${esc(e.to)} (${e.count})`).join(' · ')}</div>` : ''}
4425
4549
  <details style="margin-top:8px"><summary class="muted">Mermaid source</summary>
4426
- <pre style="white-space:pre-wrap;font-size:11px">${esc(d.map.mermaid)}</pre></details></div>` : '';
4550
+ <pre style="white-space:pre-wrap;font-size:var(--fs-caption)">${esc(d.map.mermaid)}</pre></details></div>` : '';
4427
4551
 
4428
4552
  const groups = (d.groups || []).map(g => `<div class="card">
4429
4553
  <b>${esc(g.label)}</b> <span class="muted">${g.docs.length} — ${esc(g.why || '')}</span>
4430
4554
  <div style="margin-top:8px">${g.docs.map(doc => `
4431
4555
  <div style="padding:4px 0;border-bottom:1px solid var(--border,#eee)">
4432
4556
  <a href="#" onclick="openDoc('${esc(doc.path)}');return false">${esc(doc.title)}</a>
4433
- <span class="muted" style="font-size:11px"> · ${esc(doc.path)} · ${ago(doc.modified)}</span>
4557
+ <span class="muted" style="font-size:var(--fs-caption)"> · ${esc(doc.path)} · ${ago(doc.modified)}</span>
4434
4558
  ${freshnessBadge(doc)}
4435
4559
  </div>`).join('')}</div>
4436
4560
  </div>`).join('');
@@ -4513,11 +4637,11 @@ async function loadContextLayers() {
4513
4637
 
4514
4638
  const layers = (m.layers || []).map(l => {
4515
4639
  const global = String(l.path || '').includes('/.great_cto/') && !String(l.path || '').includes(projectRootHint());
4516
- const where = global ? ' <span class="muted" style="font-size:11px">shared by every project</span>' : '';
4640
+ const where = global ? ' <span class="muted" style="font-size:var(--fs-caption)">shared by every project</span>' : '';
4517
4641
  return l.exists
4518
4642
  ? `<div style="padding:4px 0;border-bottom:1px solid var(--border,#eee)">
4519
4643
  <a href="#" onclick="openDoc('${esc(l.path)}');return false">${esc(l.id)}</a>${where}
4520
- <span class="muted" style="font-size:11px"> · ${esc(l.path)}</span></div>`
4644
+ <span class="muted" style="font-size:var(--fs-caption)"> · ${esc(l.path)}</span></div>`
4521
4645
  : `<div style="padding:4px 0;border-bottom:1px solid var(--border,#eee)" class="muted">
4522
4646
  ${esc(l.id)} — not written yet${where}</div>`;
4523
4647
  }).join('');
@@ -4561,7 +4685,7 @@ function renderDeclaredPipeline(mermaid) {
4561
4685
  }
4562
4686
  if (!edges.length) return '<div class="muted">no transitions declared</div>';
4563
4687
  const name = (id) => id.replace(/_/g, '-');
4564
- return `<div style="margin-top:10px;font-size:12px">${edges.map(e => `
4688
+ return `<div style="margin-top:10px;font-size:var(--fs-small)">${edges.map(e => `
4565
4689
  <div style="display:flex;align-items:center;gap:8px;padding:3px 0">
4566
4690
  <span style="border:1px solid var(--border,#ddd);border-radius:4px;padding:2px 8px">${esc(name(e.from))}</span>
4567
4691
  <span class="muted">${e.join ? '⋯ join ⋯' : (e.gate ? `→ ${esc(e.gate)} →` : '→')}</span>
@@ -4596,7 +4720,7 @@ async function openDoc(p) {
4596
4720
  const d = await api(`/api/doc?path=${encodeURIComponent(p)}${q ? '&' + q.slice(1) : ''}`);
4597
4721
  if (!d || d.error) throw new Error(d?.error || 'doc not found');
4598
4722
  idEl.textContent = d.name || p;
4599
- body.innerHTML = `<div class="muted" style="font-size:11px;margin-bottom:10px">${esc(d.path)}</div>
4723
+ body.innerHTML = `<div class="muted" style="font-size:var(--fs-caption);margin-bottom:10px">${esc(d.path)}</div>
4600
4724
  <div class="side-desc md">${mdToHtml(d.content)}</div>`;
4601
4725
  } catch (e) {
4602
4726
  body.innerHTML = `<div class="side-desc"><b>Could not open ${esc(p)}</b>
@@ -5462,7 +5586,7 @@ function cardHTML(t) {
5462
5586
  ${t.agent ? `<span class="chip chip-agent">${esc(t.agent)}</span>` : ''}
5463
5587
  ${labels.map(l => `<span class="chip">${esc(l)}</span>`).join('')}
5464
5588
  <div class="card-status-edit" onclick="event.stopPropagation(); openStatusPicker('${esc(t.id)}', this)">
5465
- <span class="pill pill-${t.status || 'backlog'}" style="font-size:10px;height:18px;padding:0 7px">${(t.status || 'backlog').replace('_',' ')}</span>
5589
+ <span class="pill pill-${t.status || 'backlog'}" style="font-size:var(--fs-eyebrow);height:18px;padding:0 7px">${(t.status || 'backlog').replace('_',' ')}</span>
5466
5590
  <span class="card-status-chev"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg></span>
5467
5591
  </div>
5468
5592
  ${elapsed}
@@ -6095,7 +6219,7 @@ function openSide(task) {
6095
6219
  const docsSec = docRefs.length ? `
6096
6220
  <div class="side-section"><div class="side-section-head">Documents</div>
6097
6221
  ${docRefs.map(p => `
6098
- <button onclick="openDocInSide('${esc(p)}')" style="display:flex;width:100%;gap:8px;align-items:center;text-align:left;background:var(--bg2,transparent);border:1px solid var(--line,#ddd);border-radius:6px;padding:6px 10px;margin:4px 0;cursor:pointer;font-family:var(--mono);font-size:12px;color:inherit">
6222
+ <button onclick="openDocInSide('${esc(p)}')" style="display:flex;width:100%;gap:8px;align-items:center;text-align:left;background:var(--bg-muted);border:1px solid var(--border);border-radius:6px;padding:6px 10px;margin:4px 0;cursor:pointer;font-family:var(--mono);font-size:var(--fs-small);color:inherit">
6099
6223
  <span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(p)}</span>
6100
6224
  <span style="color:var(--text3)">open →</span>
6101
6225
  </button>`).join('')}
@@ -6197,15 +6321,22 @@ document.addEventListener('keydown', e => { if (e.key === 'Escape') closeSide();
6197
6321
  */
6198
6322
  async function checkBoardFreshness() {
6199
6323
  const v = await api('/api/version');
6200
- if (!v || v.stale !== 'yes') return;
6324
+ // `behind` and `ahead` are different problems with different fixes; `no` and
6325
+ // `unknown` are both silent, because a comparison we could not make must not
6326
+ // render as a mismatch.
6327
+ if (!v || (v.stale !== 'behind' && v.stale !== 'ahead')) return;
6201
6328
  const host = document.querySelector('.workspace');
6202
6329
  if (!host || document.getElementById('stale-board')) return;
6203
6330
  const el = document.createElement('div');
6204
6331
  el.id = 'stale-board';
6205
6332
  el.className = 'stale-board';
6206
6333
  el.setAttribute('role', 'status');
6207
- el.innerHTML = `<span>This board is running <code>v${esc(v.version)}</code> `
6208
- + `<code>v${esc(v.installed)}</code> is installed. Restart it to pick up the new version.</span>`;
6334
+ el.innerHTML = v.stale === 'behind'
6335
+ ? `<span>This board is running <code>v${esc(v.version)}</code> `
6336
+ + `<code>v${esc(v.installed)}</code> is installed. Restart it to pick up the new version.</span>`
6337
+ : `<span>This board is running <code>v${esc(v.version)}</code>, which is newer than the `
6338
+ + `installed <code>v${esc(v.installed)}</code>. Restarting would go back to the older build — `
6339
+ + `install this version first.</span>`;
6209
6340
  host.insertBefore(el, host.firstChild);
6210
6341
  }
6211
6342
 
@@ -6491,7 +6622,7 @@ function renderLogsList() {
6491
6622
 
6492
6623
  el.innerHTML = bar + shown.map(({ log, i }) => `
6493
6624
  <div class="log-item ${i === 0 ? 'active' : ''}" onclick="selectLog(${i}, this)">
6494
- <div class="log-date">${esc(log.date)}${log.time ? ' · ' + esc(log.time) : ''}${log.source === 'verdicts' || log.source === 'auto' ? ' <span style="color:var(--text3);font-size:10px">· auto</span>' : ''}</div>
6625
+ <div class="log-date">${esc(log.date)}${log.time ? ' · ' + esc(log.time) : ''}${log.source === 'verdicts' || log.source === 'auto' ? ' <span style="color:var(--text3);font-size:var(--fs-eyebrow)">· auto</span>' : ''}</div>
6495
6626
  <div class="log-title">${esc(log.title)}</div>
6496
6627
  <div class="log-meta">${log.done.length} done · ${log.pending.length} pending${log.duration ? ' · ' + esc(log.duration) : ''}</div>
6497
6628
  </div>`).join('');
@@ -6854,14 +6985,14 @@ function renderAgentProfile(p) {
6854
6985
  <dt>domain</dt> <dd>${esc(p.domain)}</dd>
6855
6986
  <dt>model</dt> <dd>${esc(p.model)}</dd>
6856
6987
  ${p.applies_to.length ? `<dt>applies_to</dt><dd>${esc(p.applies_to.join(', '))}</dd>` : ''}
6857
- ${(p.tools && p.tools.length) ? `<dt>tools</dt><dd>${p.tools.map(t => `<span class="chip" style="font-size:11px">${esc(t)}</span>`).join(' ')}</dd>` : '<dt>tools</dt><dd style="color:var(--text3)">none declared</dd>'}
6858
- ${(p.skills && p.skills.length) ? `<dt>skills</dt><dd>${p.skills.map(s => `<span class="chip chip-agent" style="font-size:11px">${esc(s)}</span>`).join(' ')}</dd>` : ''}
6988
+ ${(p.tools && p.tools.length) ? `<dt>tools</dt><dd>${p.tools.map(t => `<span class="chip" style="font-size:var(--fs-caption)">${esc(t)}</span>`).join(' ')}</dd>` : '<dt>tools</dt><dd style="color:var(--text3)">none declared</dd>'}
6989
+ ${(p.skills && p.skills.length) ? `<dt>skills</dt><dd>${p.skills.map(s => `<span class="chip chip-agent" style="font-size:var(--fs-caption)">${esc(s)}</span>`).join(' ')}</dd>` : ''}
6859
6990
  <dt>runs total</dt> <dd>${p.runs_total} · ${p.runs_30d} in 30d</dd>
6860
6991
  <dt>success</dt> <dd>${p.success_rate != null ? `${p.success_rate}% (${p.ok_30d}/${p.ok_30d + p.fail_30d})` : 'no verdicts in 30d'}</dd>
6861
6992
  <dt>last run</dt> <dd>${p.last_run ? esc(p.last_run.slice(0, 16).replace('T', ' ') + ' · ' + agoStr(p.last_run)) : 'never'}</dd>
6862
6993
  <dt>savings</dt> <dd>${esc(savings)} ${esc(realCost)}</dd>
6863
6994
  </dl>
6864
- ${p.description ? `<div style="font-size:11px;color:var(--text3);margin-top:8px;line-height:1.5">${esc(p.description)}</div>` : ''}
6995
+ ${p.description ? `<div style="font-size:var(--fs-caption);color:var(--text3);margin-top:8px;line-height:1.5">${esc(p.description)}</div>` : ''}
6865
6996
  </div>
6866
6997
 
6867
6998
  <div class="mp-card">
@@ -6899,7 +7030,7 @@ function renderAgentProfile(p) {
6899
7030
  ? `<button type="button" onclick="restoreAgentAction('${esc(p.slug)}')">Restore agent</button>`
6900
7031
  : `<button type="button" class="danger" onclick="retireAgentAction('${esc(p.slug)}')">Retire agent</button>`}
6901
7032
  </div>
6902
- <div style="font-size:11px;color:var(--text3);margin-top:8px">
7033
+ <div style="font-size:var(--fs-caption);color:var(--text3);margin-top:8px">
6903
7034
  File: <code>${esc(p.file_path)}</code>
6904
7035
  </div>
6905
7036
  </div>`;
@@ -7234,12 +7365,12 @@ function renderNotifDrawer() {
7234
7365
  style="padding:12px 16px;border-bottom:1px solid var(--border);
7235
7366
  ${n.read ? '' : 'background:rgba(59,130,246,0.06);'}">
7236
7367
  <div style="display:flex;align-items:flex-start;gap:10px">
7237
- <span style="font-size:15px;flex-shrink:0;margin-top:1px">${_notifLevelIcon(n.level)}</span>
7368
+ <span style="font-size:var(--fs-title-s);flex-shrink:0;margin-top:1px">${_notifLevelIcon(n.level)}</span>
7238
7369
  <div style="flex:1;min-width:0">
7239
- <div style="font-size:13px;font-weight:${n.read ? '400' : '600'};color:var(--text1);
7370
+ <div style="font-size:var(--fs-body);font-weight:${n.read ? '400' : '600'};color:var(--text);
7240
7371
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${esc(n.title)}</div>
7241
- <div style="font-size:12px;color:var(--text3);margin-top:2px;line-height:1.4">${esc(n.body)}</div>
7242
- <div style="font-size:11px;color:var(--text3);margin-top:4px;font-family:var(--mono)">
7372
+ <div style="font-size:var(--fs-small);color:var(--text3);margin-top:2px;line-height:1.4">${esc(n.body)}</div>
7373
+ <div style="font-size:var(--fs-caption);color:var(--text3);margin-top:4px;font-family:var(--mono)">
7243
7374
  ${n.project ? `<span style="color:var(--accent);margin-right:6px">${esc(n.project)}</span>` : ''}
7244
7375
  ${new Date(n.ts).toLocaleString()}
7245
7376
  </div>
@@ -7260,12 +7391,12 @@ function renderNotifHistoryInline() {
7260
7391
  el.innerHTML = _notifHistory.slice(0, 20).map(n => `
7261
7392
  <div class="notif-inline-row" style="padding:10px 14px;border-bottom:1px solid var(--border);
7262
7393
  display:flex;align-items:center;gap:10px;${n.read ? '' : 'background:rgba(59,130,246,0.05);'}">
7263
- <span style="font-size:14px">${_notifLevelIcon(n.level)}</span>
7394
+ <span style="font-size:var(--fs-base)">${_notifLevelIcon(n.level)}</span>
7264
7395
  <div style="flex:1;min-width:0">
7265
- <span style="font-size:13px;font-weight:${n.read?'400':'600'}">${esc(n.title)}</span>
7266
- <span style="font-size:12px;color:var(--text3);margin-left:8px">${esc(n.body)}</span>
7396
+ <span style="font-size:var(--fs-body);font-weight:${n.read?'400':'600'}">${esc(n.title)}</span>
7397
+ <span style="font-size:var(--fs-small);color:var(--text3);margin-left:8px">${esc(n.body)}</span>
7267
7398
  </div>
7268
- <span style="font-size:11px;color:var(--text3);font-family:var(--mono);flex-shrink:0">
7399
+ <span style="font-size:var(--fs-caption);color:var(--text3);font-family:var(--mono);flex-shrink:0">
7269
7400
  ${new Date(n.ts).toLocaleTimeString()}
7270
7401
  </span>
7271
7402
  </div>`).join('');
@@ -7416,7 +7547,7 @@ if (_costLbl) _costLbl.textContent = `Cost · last ${periodLabel(currentPeriod)}
7416
7547
 
7417
7548
  <!-- ── Notification drawer ──────────────────────────────────────────────── -->
7418
7549
  <div id="notif-drawer" style="position:fixed;top:0;right:-400px;width:380px;height:100vh;
7419
- background:var(--bg1);border-left:1px solid var(--border);z-index:9999;
7550
+ background:var(--bg-elevated);border-left:1px solid var(--border);z-index:9999;
7420
7551
  transition:right .25s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;
7421
7552
  box-shadow:-4px 0 32px rgba(0,0,0,.5)">
7422
7553
  <div style="padding:14px 16px;border-bottom:1px solid var(--border);
@@ -7425,16 +7556,16 @@ if (_costLbl) _costLbl.textContent = `Cost · last ${periodLabel(currentPeriod)}
7425
7556
  style="width:16px;height:16px;color:var(--text2)">
7426
7557
  <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9 M10.3 21a1.94 1.94 0 0 0 3.4 0"/>
7427
7558
  </svg>
7428
- <span style="font-weight:600;font-size:14px;flex:1;color:var(--text1)">Notifications</span>
7559
+ <span style="font-weight:600;font-size:var(--fs-base);flex:1;color:var(--text)">Notifications</span>
7429
7560
  <button class="btn-ghost-sm" onclick="markAllNotifsRead()"
7430
- style="font-size:12px">Mark all read</button>
7561
+ style="font-size:var(--fs-small)">Mark all read</button>
7431
7562
  <button onclick="closeNotifDrawer()" style="background:none;border:none;cursor:pointer;
7432
- color:var(--text3);font-size:22px;line-height:1;padding:0 4px;margin-left:4px">&times;</button>
7563
+ color:var(--text3);font-size:var(--fs-title-l);line-height:1;padding:0 4px;margin-left:4px">&times;</button>
7433
7564
  </div>
7434
7565
  <div id="notif-drawer-list" tabindex="0" role="region" aria-label="Notifications list" style="flex:1;overflow-y:auto"></div>
7435
7566
  <div id="notif-drawer-empty" style="display:flex;flex:1;align-items:center;
7436
- justify-content:center;flex-direction:column;gap:8px;color:var(--text3);font-size:13px">
7437
- <span style="font-size:36px">🔔</span>No notifications yet
7567
+ justify-content:center;flex-direction:column;gap:8px;color:var(--text3);font-size:var(--fs-body)">
7568
+ <span style="font-size:var(--fs-num-l)">🔔</span>No notifications yet
7438
7569
  </div>
7439
7570
  </div>
7440
7571
  <div id="notif-drawer-overlay" onclick="closeNotifDrawer()"