great-cto 3.20.0 → 3.22.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.
@@ -31,14 +31,29 @@
31
31
  <script src="/assets/vendor/purify.min.js"></script>
32
32
  <style>
33
33
  :root {
34
+ /* The <meta name="color-scheme"> above is static, so it says "dark" in BOTH
35
+ themes. Every form control that never set its own `color` then takes the
36
+ dark scheme's UA defaults — `buttontext` is white — and in the light theme
37
+ six "Copy re-run" buttons rendered white on white, 1.00:1. The token audit
38
+ could not see it: no declared pair was wrong; the wrong colour was simply
39
+ never declared. This property wins over the meta, and the light block
40
+ below flips it. tests/lib/rendered-contrast.test.mjs reads the pixels. */
41
+ color-scheme: dark;
34
42
  /* Colours that used to be written straight into rules.
35
43
  Twenty-nine declarations bypassed the token system, so the contrast audit —
36
44
  which reads `:root` — could not see any of them. Values are carried across
37
45
  unchanged: this is a rename, not a redesign, and the rendered-layout check
38
46
  proves it by comparing the colours the page actually paints. */
39
- --on-solid: #fff; /* text on a solid coloured chip or button */
47
+ /* Three tokens, because "text on a solid chip" was three different questions
48
+ wearing one name. --on-solid covered the near-black button (#0f1115), the
49
+ green accent and the status colours at once, and #fff is right for exactly
50
+ one of them: white on the accent measures 1.87:1 on dark and 3.82:1 on
51
+ light. Naming the bed each one sits on is what makes the value checkable. */
52
+ --on-solid: #fff; /* on --bg-solid (#0f1115) — 18.90:1 */
53
+ --on-status: #0d1a14; /* on --status-* — bright on dark; the light theme flips it */
40
54
  --on-warm: #000; /* text on amber, where white fails contrast */
41
- --on-accent: #04140d; /* text on the green accent */
55
+ --on-accent: #04140d; /* on --accent: 10.10 dark, 4.95 light. White fails both
56
+ (1.87 / 3.82) — the accent is a mid-green, not a dark bed. */
42
57
  --bg-solid: #0f1115; /* opaque button and tooltip surface */
43
58
  --bg-solid-hover: #2a2d33;
44
59
  --accent-hover: #00ee8a;
@@ -66,7 +81,12 @@
66
81
 
67
82
  --text: #ecf2ee;
68
83
  --text2: #8a9a92;
69
- --text3: #8b958c;
84
+ /* FIND-3. The widest step this ladder allows — 4.53 against the lightest
85
+ surface, where the floor is 4.5. A five-step dark ladder cannot carry
86
+ three clearly distinct ink steps; the light ground can. Two-and-a-bit
87
+ steps is what this surface has, and calling it three would be the same
88
+ defect one layer up. See the commit for the measurements. */
89
+ --text3: #848f88;
70
90
 
71
91
 
72
92
  /* Focus ring — a separate token from --accent on purpose.
@@ -78,7 +98,6 @@
78
98
  --rail-ok-bg: rgba(0, 217, 126, 0.12);
79
99
  --rail-fail-bg: rgba(255, 84, 102, 0.12);
80
100
  --surface-drawer: #0f1411;
81
- --drawer-shadow: 0 16px 32px rgba(0, 0, 0, 0.5);
82
101
 
83
102
  --accent: #00d97e;
84
103
  --accent-2: #34e69b;
@@ -89,6 +108,7 @@
89
108
  --status-todo: #9ca3af;
90
109
  --status-progress: #f59e0b;
91
110
  --status-review: #00d97e;
111
+ --status-review-text: #00d97e; /* bright on dark: the bed and the ink can be one colour */
92
112
  --status-done: #00d97e;
93
113
  --status-blocked: #ff5466;
94
114
  --status-gate: #a78bfa;
@@ -98,17 +118,25 @@
98
118
  --p2-bg: rgba(240, 210, 78, 0.14); --p2-fg: #fde68a;
99
119
  --p3-bg: rgba(255, 255, 255, 0.05); --p3-fg: #9ca3af;
100
120
 
101
- --serif: "Geist", "Space Grotesk", "Inter", -apple-system, sans-serif;
121
+ /* Was `--serif`, and there is no serif anywhere in the stack — it is the
122
+ DISPLAY face, doing display work correctly under a name that promised a
123
+ contrast this page has never had. greatcto.systems already calls the same
124
+ stack `--display`; this is that name arriving here. */
125
+ /* Direction ①: one voice. The display face IS the mono face — rule 4 already
126
+ said mono means machine-truth, and a board whose every figure is a number
127
+ you would paste has no second voice to speak in. Weight caps at 500: the
128
+ vendored axis is 400-500 and hierarchy is size and ink, never bold. */
129
+ --display: "Geist Mono", "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
102
130
  --sans: "Geist", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
103
131
  --mono: "Geist Mono", "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
104
-
105
- --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.30);
106
- --shadow-card-hover: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 217, 126, 0.06);
107
132
  --accent-text: #00d97e; /* contrast-safe accent for TEXT (bright on dark) */
108
133
  --surface-translucent: rgba(17, 22, 26, 0.7); /* translucent card surface (themable) */
109
134
  }
110
135
  /* Light theme — same emerald system, AA-contrast on white. Toggle via data-theme. */
111
136
  [data-theme="light"] {
137
+ /* Flip the UA scheme with the tokens, or every unstyled control stays on the
138
+ dark scheme's defaults (see :root). */
139
+ color-scheme: light;
112
140
  /* Four values that had never been measured.
113
141
  The contrast audit read `:root` and nothing else, so this theme — 39 token
114
142
  overrides, half the board's surface area — was unchecked for months. When it
@@ -129,17 +157,33 @@
129
157
  --border-strong: rgba(0, 0, 0, 0.18);
130
158
  --text: #0d1a14;
131
159
  --text2: #56655e;
132
- --text3: #55625b;
160
+ /* Below --text2, which it was not: #55625b measured 6.39 on --bg-card against
161
+ text2's 6.14 — the quietest step was the louder one, so a caption read with
162
+ more emphasis than the secondary line above it. Both cleared AA, so no
163
+ contrast guard could see it; it is a hierarchy defect, not a readability one.
164
+ #5b6b64 is the quietest this ladder allows: 5.62 on --bg-card and 4.52 on
165
+ --bg-strong, the hardest surface here. The step is 0.52 where the dark theme
166
+ gets 0.74 — --bg-strong is what runs out first, the same way the dark
167
+ theme's five surfaces limit its own third step. */
168
+ --text3: #5b6b64;
133
169
  --focus-ring: #047857;
134
170
  --accent: #009657;
135
- --accent-2: #0a7d4d;
171
+ --accent-2: #097448; /* 4.06 on the approve tint, floor 4.5 → 4.55 */
136
172
  --accent-soft: rgba(0, 217, 126, 0.12);
137
173
  --accent-strong: rgba(0, 168, 98, 0.20);
138
- --accent-text: #097648;
174
+ --accent-text: #097547;
175
+ /* The light theme's status colours are dark (#0a7d4d, #b45309, #c43030),
176
+ so the ink that reads on the dark theme's bright ones fails here: 3.45,
177
+ 3.56, 3.24. White gives 5.18, 5.02, 5.51. */
178
+ --on-status: #fff;
139
179
  --status-backlog: #6b7280;
140
180
  --status-todo: #6b7280;
141
181
  --status-progress: #b45309;
142
182
  --status-review: #0a7d4d;
183
+ /* Same split as --accent / --accent-text: this colour is a BED for tier
184
+ badges and a dot, and INK for approve buttons and budget chips. As ink it
185
+ measures 4.06 on the .12 approve tint it most often sits on. */
186
+ --status-review-text: #097448;
143
187
  --status-done: #0a7d4d;
144
188
  --status-blocked: #c43030;
145
189
  --status-gate: #7c3aed;
@@ -219,6 +263,10 @@ body {
219
263
  button, input, select, textarea {
220
264
  font-family: inherit;
221
265
  font-size: var(--fs-body);
266
+ /* Controls do not inherit `color` by default; they take the UA scheme's.
267
+ Belt to the color-scheme braces above: authored tokens govern text here
268
+ regardless of what scheme the browser thinks it is in. */
269
+ color: inherit;
222
270
  }
223
271
  button { cursor: pointer; }
224
272
 
@@ -290,7 +338,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
290
338
  margin: 0 0 12px; padding: 10px 14px;
291
339
  background: rgba(180, 83, 9, 0.10);
292
340
  border: 1px solid var(--status-progress);
293
- border-radius: 8px;
341
+ border-radius: 0;
294
342
  font-size: var(--fs-body); color: var(--text);
295
343
  }
296
344
  .stale-board code { font-family: var(--mono); font-size: var(--fs-small); }
@@ -301,11 +349,11 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
301
349
  board spends its time refusing to render. */
302
350
  .budget-chip {
303
351
  font-family: var(--mono); font-size: var(--fs-eyebrow);
304
- padding: 1px 6px; border-radius: 3px;
352
+ padding: 1px 6px; border-radius: 0;
305
353
  border: 1px solid var(--border);
306
354
  white-space: nowrap;
307
355
  }
308
- .budget-chip.within { color: var(--status-review); border-color: rgba(22, 163, 74, 0.35); }
356
+ .budget-chip.within { color: var(--status-review-text); border-color: rgba(22, 163, 74, 0.35); }
309
357
  .budget-chip.exceeded { color: var(--status-blocked); border-color: rgba(220, 38, 38, 0.35); background: var(--p0-bg); font-weight: 600; }
310
358
  .budget-chip.unmeasured { color: var(--status-progress); border-color: rgba(180, 83, 9, 0.35); cursor: help; }
311
359
 
@@ -325,7 +373,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
325
373
  .ab-btn {
326
374
  font-family: inherit; font-size: var(--fs-caption);
327
375
  min-height: 24px; padding: 0 8px;
328
- border: 1px solid var(--border); border-radius: 5px;
376
+ border: 1px solid var(--border); border-radius: 0;
329
377
  background: var(--bg-card); color: var(--text2);
330
378
  }
331
379
  .ab-btn:hover { border-color: var(--focus-ring); color: var(--text); }
@@ -335,7 +383,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
335
383
 
336
384
  .budget-line {
337
385
  margin: -18px 0 24px; padding: 8px 12px;
338
- border-radius: 8px; font-size: var(--fs-small);
386
+ border-radius: 0; font-size: var(--fs-small);
339
387
  border: 1px solid var(--border);
340
388
  }
341
389
  .budget-line.exceeded { color: var(--status-blocked); border-color: var(--status-blocked); background: var(--p0-bg); font-weight: 500; }
@@ -350,6 +398,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
350
398
  -webkit-backdrop-filter: blur(20px);
351
399
  border-right: 1px solid var(--border);
352
400
  display: flex; flex-direction: column;
401
+ border: 1px solid var(--border);
353
402
  }
354
403
  /* Below this width the fixed 240px sidebar left 135px of workspace on a 375px
355
404
  screen — narrower than a single kanban column (264px) — so every visit from a
@@ -371,7 +420,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
371
420
  display: none;
372
421
  align-items: center; justify-content: center;
373
422
  width: 40px; height: 40px; flex-shrink: 0;
374
- border: 1px solid var(--border); border-radius: 8px;
423
+ border: 1px solid var(--border); border-radius: 0;
375
424
  background: var(--bg-card); color: var(--text2);
376
425
  }
377
426
  .menu-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
@@ -387,14 +436,20 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
387
436
  .sidebar-brand .brand-mark {
388
437
  width: 24px; height: 24px;
389
438
  background: var(--bg-page);
390
- border-radius: 5px;
439
+ border-radius: 0;
391
440
  display: flex; align-items: center; justify-content: center;
392
441
  flex-shrink: 0;
393
442
  border: 1px solid var(--border-strong);
394
443
  }
395
444
  .sidebar-brand .brand-mark svg { width: 22px; height: 22px; }
396
445
  .sidebar-brand .brand-name {
397
- font-family: var(--serif); font-weight: 600;
446
+ /* 500, not 600: --display is the mono stack, and Geist Mono's weight axis
447
+ ends at 500. Asking for 600 gets a SYNTHETIC bold — the browser strokes
448
+ around the glyph — and 600 and 700 render identically because of it
449
+ (measured: 1926 ink pixels for both, against a smooth 1473/1598 for
450
+ 400/500). The brand already outranks its neighbours by size, 16px against
451
+ 12-14, which is the hierarchy this direction asks for. */
452
+ font-family: var(--display); font-weight: 500;
398
453
  font-size: var(--fs-title-s); letter-spacing: -0.01em;
399
454
  color: var(--text);
400
455
  }
@@ -407,7 +462,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
407
462
  user-select: none;
408
463
  }
409
464
  .proj-switch:hover { background: var(--bg-muted); }
410
- .proj-switch .star { color: var(--accent); font-size: 16px; }
465
+ .proj-switch .star { color: var(--accent-text); font-size: 16px; }
411
466
  .proj-switch .name { font-weight: 600; font-size: var(--fs-base); }
412
467
  .proj-switch .sw-meta { flex: 1; display: flex; flex-direction: column; gap: 1px; }
413
468
  .sw-ancestry[hidden] { display: none; }
@@ -430,8 +485,8 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
430
485
  top: calc(100% - 4px); left: 8px; right: 8px;
431
486
  background: var(--bg-card);
432
487
  border: 1px solid var(--border-strong);
433
- border-radius: 8px;
434
- box-shadow: 0 8px 24px rgba(15, 17, 21, 0.10);
488
+ border-radius: 0;
489
+ box-shadow: none;
435
490
  padding: 4px;
436
491
  z-index: 100;
437
492
  display: none;
@@ -452,13 +507,13 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
452
507
  .proj-item {
453
508
  display: flex; align-items: center; gap: 8px;
454
509
  padding: 8px 10px;
455
- border-radius: 5px;
510
+ border-radius: 0;
456
511
  cursor: pointer;
457
512
  font-size: var(--fs-body);
458
513
  }
459
514
  .proj-item:hover { background: var(--bg-muted); }
460
515
  .proj-item.active { background: var(--bg-strong); font-weight: 500; }
461
- .proj-item .star { color: var(--accent); font-size: var(--fs-base); }
516
+ .proj-item .star { color: var(--accent-text); font-size: var(--fs-base); }
462
517
  .proj-icon, .proj-switch .proj-icon, .crumbs .proj-icon {
463
518
  display: inline-flex; align-items: center; justify-content: center;
464
519
  flex-shrink: 0;
@@ -475,7 +530,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
475
530
  }
476
531
  .proj-item .check {
477
532
  width: 14px; height: 14px;
478
- color: var(--accent);
533
+ color: var(--accent-text);
479
534
  }
480
535
  .proj-item:not(.active) .check { display: none; }
481
536
  .proj-menu-empty {
@@ -489,7 +544,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
489
544
  .nav-item {
490
545
  display: flex; align-items: center; gap: 10px;
491
546
  height: 32px; padding: 0 10px;
492
- border-radius: 6px;
547
+ border-radius: 0;
493
548
  font-size: var(--fs-body);
494
549
  color: var(--text);
495
550
  cursor: pointer;
@@ -504,10 +559,10 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
504
559
  left: -8px; top: 6px; bottom: 6px;
505
560
  width: 3px;
506
561
  background: var(--accent);
507
- border-radius: 0 2px 2px 0;
562
+ border-radius: 0;
508
563
  }
509
564
  .nav-item .icon { width: 16px; height: 16px; color: var(--text2); flex-shrink: 0; }
510
- .nav-item.active .icon { color: var(--accent); }
565
+ .nav-item.active .icon { color: var(--accent-text); }
511
566
  .nav-item .count { margin-left: auto; font-size: var(--fs-caption); color: var(--text3); font-family: var(--mono); }
512
567
  .nav-divider { height: 1px; background: var(--border); margin: 12px 8px; }
513
568
 
@@ -538,7 +593,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
538
593
  font-size: var(--fs-body);
539
594
  }
540
595
  .crumbs { display: flex; align-items: center; gap: 8px; color: var(--text2); }
541
- .crumbs .star { color: var(--accent); }
596
+ .crumbs .star { color: var(--accent-text); }
542
597
  .crumbs .sep { color: var(--text3); }
543
598
  .crumbs .here { color: var(--text); font-weight: 500; }
544
599
 
@@ -546,7 +601,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
546
601
  display: flex; align-items: center; gap: 6px;
547
602
  background: var(--bg-muted);
548
603
  border: 1px solid var(--border);
549
- border-radius: 6px;
604
+ border-radius: 0;
550
605
  padding: 4px 10px;
551
606
  width: 280px;
552
607
  margin: 0 16px;
@@ -564,13 +619,13 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
564
619
  font-family: var(--mono); font-size: var(--fs-eyebrow);
565
620
  color: var(--text3);
566
621
  background: var(--bg-strong);
567
- border-radius: 3px; padding: 1px 5px;
622
+ border-radius: 0; padding: 1px 5px;
568
623
  }
569
624
 
570
625
  .topbar-actions { display: flex; align-items: center; gap: 6px; }
571
626
  .btn-bell {
572
627
  position: relative; background: transparent;
573
- border: 1px solid var(--border); border-radius: 8px;
628
+ border: 1px solid var(--border); border-radius: 0;
574
629
  padding: 6px 8px; cursor: pointer; color: var(--text2);
575
630
  display: flex; align-items: center; transition: border-color .15s, color .15s;
576
631
  }
@@ -579,7 +634,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
579
634
  position: absolute; top: -5px; right: -5px;
580
635
  background: var(--accent); color: var(--on-accent);
581
636
  font-size: var(--fs-eyebrow); font-weight: 700; line-height: 1;
582
- min-width: 16px; height: 16px; border-radius: 8px;
637
+ min-width: 16px; height: 16px; border-radius: 0;
583
638
  display: none; align-items: center; justify-content: center;
584
639
  padding: 0 3px;
585
640
  }
@@ -587,7 +642,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
587
642
  .tab-row { display: flex; align-items: center; gap: 4px; margin-right: 12px; }
588
643
  .tab-btn {
589
644
  background: transparent; border: none;
590
- padding: 6px 12px; border-radius: 6px;
645
+ padding: 6px 12px; border-radius: 0;
591
646
  font-size: var(--fs-body); color: var(--text2);
592
647
  }
593
648
  .tab-btn:hover { background: var(--bg-muted); color: var(--text); }
@@ -599,7 +654,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
599
654
  .icon-btn {
600
655
  background: transparent; border: 1px solid transparent;
601
656
  width: 30px; height: 30px;
602
- border-radius: 6px;
657
+ border-radius: 0;
603
658
  display: inline-flex; align-items: center; justify-content: center;
604
659
  color: var(--text2);
605
660
  }
@@ -637,7 +692,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
637
692
  backdrop-filter: blur(12px);
638
693
  -webkit-backdrop-filter: blur(12px);
639
694
  border: 1px solid var(--border);
640
- border-radius: 10px;
695
+ border-radius: 0;
641
696
  padding: 10px;
642
697
  display: flex; flex-direction: column; gap: 8px;
643
698
  max-height: 100%;
@@ -666,7 +721,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
666
721
  .card {
667
722
  background: var(--bg-card);
668
723
  border: 1px solid var(--border);
669
- border-radius: 8px;
724
+ border-radius: 0;
670
725
  padding: 10px 12px;
671
726
  cursor: pointer;
672
727
  transition: box-shadow 0.15s, transform 0.15s;
@@ -686,7 +741,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
686
741
  overflow: hidden;
687
742
  text-overflow: ellipsis;
688
743
  }
689
- .card:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-1px); }
744
+ .card:hover { box-shadow: none; transform: translateY(-1px); }
690
745
  .card.selected { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
691
746
  .card .id {
692
747
  font-family: var(--mono); font-size: var(--fs-eyebrow);
@@ -748,7 +803,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
748
803
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
749
804
  height: 24px;
750
805
  border: 1px solid var(--border);
751
- border-radius: 5px;
806
+ border-radius: 0;
752
807
  font-family: inherit; font-size: var(--fs-caption); font-weight: 500;
753
808
  cursor: pointer;
754
809
  background: var(--bg-card);
@@ -761,26 +816,26 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
761
816
  makes a long gate list scannable is left exactly as it is. 44px is the iOS
762
817
  minimum; Android asks for 48dp, and the gap does the rest. */
763
818
 
764
- .gate-approve { color: var(--status-review); border-color: rgba(22, 163, 74, 0.35); }
819
+ .gate-approve { color: var(--status-review-text); border-color: rgba(22, 163, 74, 0.35); }
765
820
  .gate-approve:hover { background: rgba(22, 163, 74, 0.08); border-color: var(--status-review); }
766
821
  /* Agent runner (launch control) */
767
- #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; }
822
+ #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: 0; box-shadow: none; z-index: 60; overflow: hidden; }
768
823
  #agent-panel.open { display: flex; }
769
824
  #agent-panel header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--border, #1f2a26); background: transparent; }
770
825
  #agent-panel header b { font-size: var(--fs-body); }
771
826
  #agent-panel .ap-x { margin-left: auto; cursor: pointer; border: 0; background: none; color: var(--text3); font-size: 16px; }
772
827
  #agent-log { flex: 1; overflow: auto; padding: 10px 14px; font: 12px/1.55 ui-monospace, Menlo, monospace; }
773
828
  #agent-log .al { margin: 2px 0; white-space: pre-wrap; word-break: break-word; }
774
- #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; }
829
+ #agent-log .al.tool { color: var(--status-review-text); } #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; }
775
830
  #agent-panel .ap-foot { display: flex; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--border, #1f2a26); }
776
- #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); }
831
+ #agent-panel input { flex: 1; background: var(--bg-muted); color: var(--text, #ecf2ee); border: 1px solid var(--border, #1f2a26); border-radius: 0; padding: 7px 10px; font: inherit; font-size: var(--fs-body); }
777
832
  #agent-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text3); }
778
833
  #agent-dot.live { background: var(--status-review); animation: apulse 1.2s infinite; }
779
834
  @keyframes apulse { 0%,100%{opacity:1} 50%{opacity:.4} }
780
835
  .gate-reject { color: var(--status-blocked); border-color: rgba(220, 38, 38, 0.30); }
781
836
  .gate-reject:hover { background: rgba(220, 38, 38, 0.06); border-color: var(--status-blocked); }
782
837
  .chip-agent {
783
- background: var(--accent-soft); color: var(--accent);
838
+ background: var(--accent-soft); color: var(--accent-text);
784
839
  font-family: var(--mono);
785
840
  }
786
841
 
@@ -812,7 +867,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
812
867
  padding: 10px 12px;
813
868
  border: 1px solid #f0b429;
814
869
  border-left-width: 3px;
815
- border-radius: 6px;
870
+ border-radius: 0;
816
871
  background: rgba(240, 180, 41, 0.08);
817
872
  font-size: var(--fs-small);
818
873
  line-height: 1.45;
@@ -820,6 +875,21 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
820
875
  .degraded-banner strong { color: var(--text); font-weight: 600; }
821
876
  .degraded-banner span { color: var(--text2); font-family: var(--mono, ui-monospace, monospace); word-break: break-word; }
822
877
 
878
+ /* Louder than .degraded-banner, and after it, so the shared properties are
879
+ inherited and only the severity is overridden — at equal specificity the later
880
+ rule wins, which is the whole reason this sits below rather than above.
881
+
882
+ The two banners report different failures and must not look alike: amber says
883
+ a panel could not be read and is showing empty; red says every panel WAS read,
884
+ correctly, for a different project. The second is the more dangerous, because
885
+ nothing else on the screen looks wrong. */
886
+ .project-fallback-banner {
887
+ border-color: var(--status-blocked);
888
+ background: var(--rail-fail-bg);
889
+ }
890
+ .project-fallback-banner strong { color: var(--status-blocked); }
891
+ .project-fallback-banner span { color: var(--text); font-family: var(--sans); }
892
+
823
893
  /* ── Side panel ───────────────────────────────────────────────────────────── */
824
894
  .side {
825
895
  position: absolute;
@@ -827,7 +897,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
827
897
  width: 480px;
828
898
  background: var(--bg-card);
829
899
  border-left: 1px solid var(--border);
830
- box-shadow: -12px 0 32px rgba(15, 17, 21, 0.08);
900
+ box-shadow: none;
831
901
  z-index: 10;
832
902
  display: flex; flex-direction: column;
833
903
  transform: translateX(100%);
@@ -860,14 +930,14 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
860
930
  .side-id {
861
931
  font-family: var(--mono); font-size: var(--fs-caption); font-weight: 500;
862
932
  background: var(--bg-strong); color: var(--text);
863
- padding: 4px 8px; border-radius: 4px;
933
+ padding: 4px 8px; border-radius: 0;
864
934
  letter-spacing: 0.04em;
865
935
  }
866
936
  .side-close {
867
937
  margin-left: auto; cursor: pointer;
868
938
  width: 28px; height: 28px;
869
939
  display: flex; align-items: center; justify-content: center;
870
- border-radius: 6px;
940
+ border-radius: 0;
871
941
  color: var(--text3);
872
942
  }
873
943
  .side-close:hover { background: var(--bg-muted); color: var(--text); }
@@ -897,7 +967,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
897
967
  font-style: normal;
898
968
  background: var(--bg-muted);
899
969
  padding: 1px 5px;
900
- border-radius: 3px;
970
+ border-radius: 0;
901
971
  font-size: var(--fs-small);
902
972
  }
903
973
  .side-desc.md { white-space: normal; }
@@ -910,15 +980,15 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
910
980
  .side-desc.md li { margin: 2px 0; }
911
981
  .side-desc.md code {
912
982
  background: var(--bg-muted); padding: 1px 4px;
913
- border-radius: 3px; font-size: var(--fs-small); font-family: ui-monospace, monospace;
983
+ border-radius: 0; font-size: var(--fs-small); font-family: ui-monospace, monospace;
914
984
  }
915
985
  .side-desc.md pre.md-pre {
916
986
  background: var(--bg-muted); padding: 8px 10px;
917
- border-radius: 4px; overflow-x: auto; font-size: var(--fs-small);
987
+ border-radius: 0; overflow-x: auto; font-size: var(--fs-small);
918
988
  margin: 6px 0;
919
989
  }
920
990
  .side-desc.md pre.md-pre code { background: none; padding: 0; }
921
- .side-desc.md a { color: var(--accent); text-decoration: none; }
991
+ .side-desc.md a { color: var(--accent-text); text-decoration: none; }
922
992
  .side-desc.md a:hover { text-decoration: underline; }
923
993
  .side-desc.md strong { color: var(--text); font-weight: 600; }
924
994
  .side-id-row { display: inline-flex; align-items: center; gap: 6px; }
@@ -935,20 +1005,21 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
935
1005
  }
936
1006
  .toast {
937
1007
  background: var(--toast-bg); color: var(--on-solid);
938
- padding: 8px 14px; border-radius: 6px;
1008
+ padding: 8px 14px; border-radius: 0;
939
1009
  font-size: var(--fs-body); min-width: 200px; max-width: 360px;
940
- box-shadow: 0 4px 16px rgba(0,0,0,0.35);
1010
+ box-shadow: none;
941
1011
  opacity: 0; transform: translateY(8px);
942
1012
  transition: opacity 0.2s, transform 0.2s;
943
1013
  pointer-events: auto;
1014
+ border: 1px solid var(--border-strong);
944
1015
  }
945
1016
  .toast.show { opacity: 1; transform: translateY(0); }
946
1017
  .toast-success { background: var(--toast-ok); border-left: 3px solid #22c55e; }
947
1018
  .toast-error { background: var(--toast-err); border-left: 3px solid #ef4444; }
948
1019
  .toast-info { background: var(--toast-info); border-left: 3px solid #3b82f6; }
949
- .toast code { background: rgba(255,255,255,0.1); padding: 1px 4px; border-radius: 3px; }
1020
+ .toast code { background: rgba(255,255,255,0.1); padding: 1px 4px; border-radius: 0; }
950
1021
  .pl-stage { cursor: pointer; transition: background 0.12s; }
951
- .pl-stage:hover { background: var(--bg-muted); border-radius: 4px; }
1022
+ .pl-stage:hover { background: var(--bg-muted); border-radius: 0; }
952
1023
  .card-selected { outline: 2px solid var(--focus-ring); outline-offset: -1px; }
953
1024
  #cmdk {
954
1025
  position: fixed; inset: 0; z-index: 10001;
@@ -961,7 +1032,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
961
1032
  width: min(620px, 92vw); max-height: 62vh;
962
1033
  display: flex; flex-direction: column;
963
1034
  background: var(--surface-drawer); border: 1px solid var(--border-strong);
964
- border-radius: 12px; box-shadow: var(--drawer-shadow); overflow: hidden;
1035
+ border-radius: 0; box-shadow: none; overflow: hidden;
965
1036
  }
966
1037
  #cmdk-input {
967
1038
  border: 0; border-bottom: 1px solid var(--border);
@@ -971,7 +1042,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
971
1042
  #cmdk-list { overflow-y: auto; padding: 6px; }
972
1043
  .cmdk-item {
973
1044
  display: flex; align-items: center; gap: 10px;
974
- padding: 9px 10px; border-radius: 7px; cursor: pointer;
1045
+ padding: 9px 10px; border-radius: 0; cursor: pointer;
975
1046
  min-height: 40px;
976
1047
  }
977
1048
  .cmdk-item.on { background: var(--bg-muted); }
@@ -995,8 +1066,8 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
995
1066
  .hk-card {
996
1067
  position: relative; background: var(--bg-elevated);
997
1068
  color: var(--text); border: 1px solid var(--border);
998
- border-radius: 8px; padding: 20px 24px; min-width: 360px;
999
- box-shadow: 0 12px 32px rgba(0,0,0,0.5);
1069
+ border-radius: 0; padding: 20px 24px; min-width: 360px;
1070
+ box-shadow: none;
1000
1071
  }
1001
1072
  .hk-title { font-size: var(--fs-title-s); font-weight: 600; margin-bottom: 12px; }
1002
1073
  .hk-table { border-collapse: collapse; width: 100%; }
@@ -1004,7 +1075,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1004
1075
  .hk-table td:first-child { text-align: right; white-space: nowrap; color: var(--text2); }
1005
1076
  .hk-table kbd {
1006
1077
  background: var(--bg-muted); border: 1px solid var(--border);
1007
- border-radius: 3px; padding: 1px 5px; font-size: var(--fs-caption);
1078
+ border-radius: 0; padding: 1px 5px; font-size: var(--fs-caption);
1008
1079
  font-family: ui-monospace, monospace; color: var(--text);
1009
1080
  }
1010
1081
  .hk-foot { margin-top: 12px; font-size: var(--fs-caption); color: var(--text3, #9ca3af); text-align: center; }
@@ -1018,8 +1089,8 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1018
1089
  .rdm-card {
1019
1090
  position: relative; background: var(--bg-elevated);
1020
1091
  color: var(--text); border: 1px solid var(--border);
1021
- border-radius: 8px; padding: 18px 22px; min-width: 480px; max-width: 720px;
1022
- box-shadow: 0 12px 32px rgba(0,0,0,0.5);
1092
+ border-radius: 0; padding: 18px 22px; min-width: 480px; max-width: 720px;
1093
+ box-shadow: none;
1023
1094
  }
1024
1095
  .rdm-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
1025
1096
  .rdm-head .rdm-agent { font-weight: 600; }
@@ -1027,7 +1098,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1027
1098
  .rdm-head .rdm-verdict.fail { color: var(--verdict-fail); }
1028
1099
  .rdm-head .rdm-ts { color: var(--text2); font-size: var(--fs-caption); margin-left: auto; }
1029
1100
  .rdm-raw {
1030
- background: var(--bg-muted); padding: 8px 10px; border-radius: 4px;
1101
+ background: var(--bg-muted); padding: 8px 10px; border-radius: 0;
1031
1102
  font-family: ui-monospace, monospace; font-size: var(--fs-small);
1032
1103
  white-space: pre-wrap; word-break: break-all;
1033
1104
  border: 1px solid var(--border);
@@ -1039,18 +1110,18 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1039
1110
  display: inline-flex; align-items: center; gap: 4px;
1040
1111
  margin: 2px 4px 2px 0; padding: 4px 8px;
1041
1112
  background: var(--bg-muted); border: 1px solid var(--border);
1042
- border-radius: 4px; font-size: var(--fs-small); cursor: pointer;
1113
+ border-radius: 0; font-size: var(--fs-small); cursor: pointer;
1043
1114
  text-decoration: none; color: var(--text);
1044
1115
  font-family: ui-monospace, monospace;
1045
1116
  }
1046
1117
  .rdm-artefact:hover { background: var(--bg-muted); }
1047
1118
  .rdm-artefact .rdm-art-kind {
1048
- font-size: var(--fs-eyebrow); padding: 1px 4px; border-radius: 3px;
1119
+ font-size: var(--fs-eyebrow); padding: 1px 4px; border-radius: 0;
1049
1120
  background: var(--accent-soft); color: var(--accent-text);
1050
1121
  }
1051
1122
  .rdm-foot { margin-top: 12px; font-size: var(--fs-caption); color: var(--text3, #9ca3af); }
1052
1123
  .resume-item { cursor: pointer; }
1053
- .resume-item:hover { background: var(--bg-muted); border-radius: 4px; }
1124
+ .resume-item:hover { background: var(--bg-muted); border-radius: 0; }
1054
1125
 
1055
1126
  /* ── New issue modal ──────────────────────────────────────────────────── */
1056
1127
  #new-issue-modal {
@@ -1064,9 +1135,9 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1064
1135
  .ni-card {
1065
1136
  position: relative; background: var(--bg-elevated, #161a1f);
1066
1137
  color: var(--text); border: 1px solid var(--border-strong, #2a2d33);
1067
- border-radius: 10px; width: 480px; max-width: calc(100vw - 32px);
1138
+ border-radius: 0; width: 480px; max-width: calc(100vw - 32px);
1068
1139
  max-height: calc(100vh - 64px); overflow: auto;
1069
- box-shadow: 0 24px 48px rgba(0,0,0,0.6);
1140
+ box-shadow: none;
1070
1141
  animation: ni-pop 140ms ease-out;
1071
1142
  }
1072
1143
  @keyframes ni-pop {
@@ -1080,7 +1151,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1080
1151
  .ni-header h3 { font-size: var(--fs-title-s); font-weight: 600; margin: 0; }
1081
1152
  .ni-close {
1082
1153
  background: transparent; border: 0; color: var(--text3);
1083
- cursor: pointer; padding: 4px; display: flex; border-radius: 4px;
1154
+ cursor: pointer; padding: 4px; display: flex; border-radius: 0;
1084
1155
  }
1085
1156
  .ni-close:hover { background: var(--bg-muted, rgba(255,255,255,0.06)); color: var(--text); }
1086
1157
  .ni-form {
@@ -1100,7 +1171,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1100
1171
  .ni-field select {
1101
1172
  background: var(--bg-page, #0a0e0c);
1102
1173
  border: 1px solid var(--border, #2a2d33);
1103
- border-radius: 6px;
1174
+ border-radius: 0;
1104
1175
  color: var(--text);
1105
1176
  font: inherit; font-size: var(--fs-body);
1106
1177
  padding: 8px 10px;
@@ -1127,7 +1198,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1127
1198
  margin-top: 4px;
1128
1199
  }
1129
1200
  .ni-btn {
1130
- border: 1px solid transparent; border-radius: 6px;
1201
+ border: 1px solid transparent; border-radius: 0;
1131
1202
  padding: 7px 14px; font: inherit; font-size: var(--fs-body);
1132
1203
  cursor: pointer; transition: all 120ms;
1133
1204
  }
@@ -1138,7 +1209,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1138
1209
  .ni-btn-ghost:hover { background: var(--bg-muted, rgba(255,255,255,0.05)); color: var(--text); }
1139
1210
  .ni-btn-primary {
1140
1211
  background: var(--accent, #00d97e);
1141
- color: var(--bg-page); font-weight: 500;
1212
+ color: var(--on-accent); font-weight: 500;
1142
1213
  }
1143
1214
  .ni-btn-primary:hover { background: var(--accent-hover); }
1144
1215
  .ni-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
@@ -1193,7 +1264,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1193
1264
  margin-top: 14px;
1194
1265
  background: var(--bg-muted);
1195
1266
  border: 1px solid var(--border);
1196
- border-radius: 8px;
1267
+ border-radius: 0;
1197
1268
  padding: 12px 14px;
1198
1269
  font-size: var(--fs-body);
1199
1270
  color: var(--text2);
@@ -1224,7 +1295,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1224
1295
  .mem-item {
1225
1296
  display: flex; flex-direction: column; gap: 2px;
1226
1297
  padding: 10px 12px;
1227
- border-radius: 6px;
1298
+ border-radius: 0;
1228
1299
  cursor: pointer;
1229
1300
  margin-bottom: 2px;
1230
1301
  border: 1px solid transparent;
@@ -1233,8 +1304,8 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1233
1304
  .mem-item.active { background: var(--accent-soft); border-color: rgba(37, 64, 212, 0.16); }
1234
1305
  .mem-item .layer-tag {
1235
1306
  font-family: var(--mono); font-size: var(--fs-eyebrow);
1236
- color: var(--accent); letter-spacing: 0.06em;
1237
- font-weight: 600;
1307
+ color: var(--accent-text); letter-spacing: 0.06em;
1308
+ font-weight: 500;
1238
1309
  }
1239
1310
  .mem-item .name {
1240
1311
  font-family: var(--mono); font-size: var(--fs-small);
@@ -1270,8 +1341,8 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1270
1341
  max-width: 880px;
1271
1342
  margin: 0 auto;
1272
1343
  }
1273
- .memory-doc h1 { font-family: var(--serif); font-weight: 600; font-size: 28px; letter-spacing: -0.02em; margin: 0 0 14px; color: var(--text); }
1274
- .memory-doc h2 { font-family: var(--serif); font-weight: 600; font-size: 20px; margin: 28px 0 10px; color: var(--text); }
1344
+ .memory-doc h1 { font-family: var(--display); font-weight: 600; font-size: 28px; letter-spacing: -0.02em; margin: 0 0 14px; color: var(--text); }
1345
+ .memory-doc h2 { font-family: var(--display); font-weight: 600; font-size: 20px; margin: 28px 0 10px; color: var(--text); }
1275
1346
  .memory-doc h3 { font-family: var(--sans); font-weight: 600; font-size: var(--fs-title-s); margin: 18px 0 8px; color: var(--text); }
1276
1347
  .memory-doc p { font-size: var(--fs-base); line-height: 1.65; color: var(--text2); margin: 0 0 10px; }
1277
1348
  .memory-doc ul, .memory-doc ol { padding-left: 22px; margin: 0 0 12px; color: var(--text2); }
@@ -1279,14 +1350,14 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1279
1350
  .memory-doc code {
1280
1351
  font-family: var(--mono); font-size: var(--fs-small);
1281
1352
  background: var(--bg-muted);
1282
- padding: 1px 6px; border-radius: 3px;
1353
+ padding: 1px 6px; border-radius: 0;
1283
1354
  color: var(--text);
1284
1355
  }
1285
1356
  .memory-doc pre {
1286
1357
  font-family: var(--mono); font-size: var(--fs-small);
1287
1358
  background: var(--bg-strong);
1288
1359
  padding: 14px 16px;
1289
- border-radius: 8px;
1360
+ border-radius: 0;
1290
1361
  overflow-x: auto;
1291
1362
  margin: 12px 0;
1292
1363
  line-height: 1.55;
@@ -1313,9 +1384,9 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1313
1384
  }
1314
1385
  .memory-doc-head .layer-tag {
1315
1386
  font-family: var(--mono); font-size: var(--fs-caption);
1316
- background: var(--accent-soft); color: var(--accent);
1317
- padding: 4px 10px; border-radius: 4px;
1318
- letter-spacing: 0.06em; font-weight: 600;
1387
+ background: var(--accent-soft); color: var(--accent-text);
1388
+ padding: 4px 10px; border-radius: 0;
1389
+ letter-spacing: 0.06em; font-weight: 500;
1319
1390
  }
1320
1391
  .memory-doc-head .filename {
1321
1392
  font-family: var(--mono); font-size: var(--fs-body);
@@ -1335,9 +1406,9 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1335
1406
  .tier-badge { font-size: var(--fs-caption); padding: 1px 8px; border-radius: 999px; margin-left: 6px;
1336
1407
  font-family: var(--mono); white-space: nowrap; }
1337
1408
  .tier-badge:empty { display: none; }
1338
- .tier-badge.tier-T0 { background: var(--status-review); color: var(--on-solid); }
1339
- .tier-badge.tier-T1 { background: var(--status-progress); color: var(--on-solid); }
1340
- .tier-badge.tier-T2 { background: var(--status-blocked); color: var(--on-solid); }
1409
+ .tier-badge.tier-T0 { background: var(--status-review); color: var(--on-status); }
1410
+ .tier-badge.tier-T1 { background: var(--status-progress); color: var(--on-status); }
1411
+ .tier-badge.tier-T2 { background: var(--status-blocked); color: var(--on-status); }
1341
1412
 
1342
1413
  /* ── Pipeline track ───────────────────────────────────────────────────────── */
1343
1414
  .pipeline-track {
@@ -1346,7 +1417,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1346
1417
  padding: 14px 16px;
1347
1418
  background: var(--surface-translucent);
1348
1419
  border: 1px solid var(--border);
1349
- border-radius: 10px;
1420
+ border-radius: 0;
1350
1421
  overflow-x: auto;
1351
1422
  }
1352
1423
  .pl-stage {
@@ -1376,7 +1447,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1376
1447
  .pl-recover { display: flex; gap: 6px; margin-top: 6px; }
1377
1448
  .pl-recover-btn {
1378
1449
  min-height: 26px; padding: 0 8px;
1379
- border: 1px solid var(--border); border-radius: 5px;
1450
+ border: 1px solid var(--border); border-radius: 0;
1380
1451
  background: var(--bg-card); color: var(--text2);
1381
1452
  font-family: var(--mono); font-size: var(--fs-eyebrow); cursor: pointer;
1382
1453
  }
@@ -1445,7 +1516,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1445
1516
  display: flex; align-items: center; gap: 14px;
1446
1517
  padding: 22px 22px; margin-top: 4px;
1447
1518
  background: var(--surface-translucent); border: 1px solid var(--border);
1448
- border-radius: 12px;
1519
+ border-radius: 0;
1449
1520
  }
1450
1521
  .inbox-allclear .ac-mark {
1451
1522
  width: 34px; height: 34px; flex-shrink: 0;
@@ -1457,7 +1528,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1457
1528
  .inbox-allclear .ac-text b { color: var(--text); font-weight: 600; }
1458
1529
  .inbox-allclear .ac-actions { display: flex; gap: 8px; }
1459
1530
  .inbox-allclear .ac-btn {
1460
- font-size: var(--fs-body); padding: 7px 14px; border-radius: 8px;
1531
+ font-size: var(--fs-body); padding: 7px 14px; border-radius: 0;
1461
1532
  border: 1px solid var(--border-strong); background: transparent;
1462
1533
  color: var(--text); cursor: pointer; transition: background 0.12s, border-color 0.12s;
1463
1534
  }
@@ -1470,7 +1541,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1470
1541
  .pl-gate {
1471
1542
  background: var(--surface-translucent);
1472
1543
  border-left: 2px solid var(--border-strong);
1473
- border-radius: 6px;
1544
+ border-radius: 0;
1474
1545
  margin: -4px 2px;
1475
1546
  padding: 12px 12px 10px;
1476
1547
  }
@@ -1487,7 +1558,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1487
1558
  background: var(--status-blocked); color: var(--on-solid);
1488
1559
  font-size: var(--fs-eyebrow); font-weight: 700;
1489
1560
  min-width: 16px; height: 16px; padding: 0 4px;
1490
- border-radius: 8px;
1561
+ border-radius: 0;
1491
1562
  display: inline-flex; align-items: center; justify-content: center;
1492
1563
  }
1493
1564
  .pl-gate.active.fresh .pl-name .gate-glyph { animation: pl-pulse 1.4s infinite; }
@@ -1532,7 +1603,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1532
1603
  color: var(--text2);
1533
1604
  }
1534
1605
  .cost-cell .v {
1535
- font-family: var(--serif); font-weight: 500;
1606
+ font-family: var(--display); font-weight: 500;
1536
1607
  font-variant-numeric: tabular-nums;
1537
1608
  font-size: var(--fs-num-s); letter-spacing: -0.02em;
1538
1609
  color: var(--text); line-height: 1.1;
@@ -1540,7 +1611,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1540
1611
  .cost-cell .v small { font-size: var(--fs-base); color: var(--text2); margin-left: 2px; font-weight: 400; }
1541
1612
  .cost-cell .sub { font-size: var(--fs-caption); color: var(--text2); }
1542
1613
  .cost-cell.warn .v { color: var(--status-blocked); }
1543
- .cost-cell.good .v { color: var(--status-review); }
1614
+ .cost-cell.good .v { color: var(--status-review-text); }
1544
1615
 
1545
1616
  .cost-chart {
1546
1617
  position: relative;
@@ -1556,7 +1627,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1556
1627
  .cost-bar {
1557
1628
  flex: 1; min-width: 4px;
1558
1629
  background: var(--accent-soft);
1559
- border-radius: 2px 2px 0 0;
1630
+ border-radius: 0;
1560
1631
  position: relative;
1561
1632
  cursor: pointer;
1562
1633
  transition: background 0.12s;
@@ -1569,7 +1640,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1569
1640
  bottom: calc(100% + 4px); left: 50%;
1570
1641
  transform: translateX(-50%);
1571
1642
  background: var(--bg-solid); color: var(--on-solid);
1572
- padding: 4px 8px; border-radius: 4px;
1643
+ padding: 4px 8px; border-radius: 0;
1573
1644
  font-family: var(--mono); font-size: var(--fs-eyebrow);
1574
1645
  white-space: nowrap;
1575
1646
  display: none;
@@ -1586,7 +1657,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1586
1657
  cursor: pointer; font-size: var(--fs-small); color: var(--text2);
1587
1658
  padding: 4px 0; list-style-position: inside;
1588
1659
  }
1589
- .cost-table summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 4px; }
1660
+ .cost-table summary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: 0; }
1590
1661
  .cost-table table { border-collapse: collapse; width: 100%; margin-top: 6px; }
1591
1662
  .cost-table th, .cost-table td {
1592
1663
  text-align: left; padding: 5px 10px 5px 0;
@@ -1642,8 +1713,8 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1642
1713
  font-family: var(--mono); color: var(--text);
1643
1714
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1644
1715
  }
1645
- .feat-bar-wrap { background: var(--bg-strong); border-radius: 3px; height: 6px; }
1646
- .feat-bar { height: 6px; border-radius: 3px; background: var(--accent); transition: width .3s; }
1716
+ .feat-bar-wrap { background: var(--bg-strong); border-radius: 0; height: 6px; }
1717
+ .feat-bar { height: 6px; border-radius: 0; background: var(--accent); transition: width .3s; }
1647
1718
  .feat-cost { font-family: var(--mono); color: var(--text); text-align: right; }
1648
1719
  .feat-runs { font-size: var(--fs-caption); color: var(--text3); text-align: right; }
1649
1720
 
@@ -1654,7 +1725,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1654
1725
  margin: 0 auto;
1655
1726
  }
1656
1727
  .inbox-greet {
1657
- font-family: var(--serif); font-weight: 600;
1728
+ font-family: var(--display); font-weight: 500;
1658
1729
  font-size: clamp(22px, 2.5vw, 30px);
1659
1730
  letter-spacing: -0.02em;
1660
1731
  margin-bottom: 24px;
@@ -1667,12 +1738,12 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1667
1738
  background: var(--surface-translucent);
1668
1739
  backdrop-filter: blur(8px);
1669
1740
  border: 1px solid var(--border);
1670
- border-radius: 10px;
1741
+ border-radius: 0;
1671
1742
  padding: 14px 16px;
1672
1743
  display: flex; align-items: center; gap: 10px;
1673
1744
  }
1674
1745
  .inbox-summary .pill-stat .num {
1675
- font-family: var(--serif); font-weight: 500;
1746
+ font-family: var(--display); font-weight: 500;
1676
1747
  font-variant-numeric: tabular-nums;
1677
1748
  font-size: var(--fs-num-s); letter-spacing: -0.02em;
1678
1749
  color: var(--text);
@@ -1702,7 +1773,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1702
1773
  .ac-primary:focus-visible {
1703
1774
  outline: 2px solid var(--focus-ring);
1704
1775
  outline-offset: 2px;
1705
- border-radius: 8px;
1776
+ border-radius: 0;
1706
1777
  }
1707
1778
 
1708
1779
  /* Reason chip — why a row is in the queue, carried by the row itself. */
@@ -1713,19 +1784,27 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1713
1784
  letter-spacing: 0.04em;
1714
1785
  padding: 1px 7px;
1715
1786
  margin-right: 8px;
1716
- border-radius: 5px;
1787
+ border-radius: 0;
1717
1788
  border: 1px solid var(--border-strong);
1718
1789
  color: var(--text2);
1719
1790
  vertical-align: 1px;
1720
1791
  }
1721
1792
  .why-gate { color: var(--status-gate); border-color: var(--status-gate); }
1793
+ /* An expensive-to-undo gate must not wear the same chip as a routine one — that
1794
+ equivalence is the finding this rule exists to remove. Colour AND a glyph:
1795
+ colour alone fails the reader who cannot see it. */
1796
+ /* An expensive-to-undo gate must not wear the same chip as a routine one — that
1797
+ equivalence is the whole finding. And `cost not classified` gets a third look,
1798
+ because a gate nobody has judged is not a cheap gate. */
1799
+ .why-gate-expensive { color: var(--status-blocked); border-color: var(--status-blocked); }
1800
+ .why-gate-unclassified { color: var(--p1-fg); border-color: var(--p1-fg); }
1722
1801
  .why-p0 { color: var(--status-blocked); border-color: var(--status-blocked); }
1723
1802
  .why-blocked { color: var(--status-progress); border-color: var(--status-progress); }
1724
1803
  .why-stale { color: var(--text3); }
1725
1804
 
1726
1805
  .proj-status {
1727
1806
  border: 1px solid var(--border);
1728
- border-radius: 12px;
1807
+ border-radius: 0;
1729
1808
  background: var(--bg-card);
1730
1809
  margin: 10px 0 18px;
1731
1810
  }
@@ -1748,14 +1827,14 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1748
1827
  .proj-status-summary:focus-visible {
1749
1828
  outline: 2px solid var(--focus-ring);
1750
1829
  outline-offset: 2px;
1751
- border-radius: 8px;
1830
+ border-radius: 0;
1752
1831
  }
1753
1832
  /* A link with no colour rule gets the browser's default, which is on nobody's
1754
1833
  palette. This one — "stale, listed below" in the empty resume column — was
1755
1834
  added today without one, and the rendered-layout check found it on both themes
1756
1835
  within the hour. That is the check working on its author. */
1757
1836
  .resume-list a, .empty a { color: var(--accent-text); text-decoration: underline; }
1758
- .resume-list a:hover, .empty a:hover { color: var(--accent); }
1837
+ .resume-list a:hover, .empty a:hover { color: var(--accent-text); }
1759
1838
 
1760
1839
  .proj-status-verdict { color: var(--text3); font-family: var(--mono); font-size: var(--fs-eyebrow); }
1761
1840
  .proj-status-body { padding: 0 16px 14px; }
@@ -1764,7 +1843,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1764
1843
  .resume-card {
1765
1844
  background: linear-gradient(135deg, rgba(0, 217, 126, 0.04), var(--surface-translucent));
1766
1845
  border: 1px solid rgba(0, 217, 126, 0.18);
1767
- border-radius: 12px;
1846
+ border-radius: 0;
1768
1847
  padding: 18px 20px;
1769
1848
  margin-bottom: 28px;
1770
1849
  backdrop-filter: blur(8px);
@@ -1774,7 +1853,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1774
1853
  margin-bottom: 14px;
1775
1854
  }
1776
1855
  .resume-icon {
1777
- display: inline-flex; color: var(--accent);
1856
+ display: inline-flex; color: var(--accent-text);
1778
1857
  }
1779
1858
  .resume-title {
1780
1859
  font-family: var(--sans); font-weight: 600;
@@ -1814,13 +1893,13 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1814
1893
  .resume-list .resume-item .ri-tag {
1815
1894
  flex-shrink: 0;
1816
1895
  font-family: var(--mono); font-size: var(--fs-eyebrow);
1817
- padding: 2px 5px; border-radius: 3px;
1896
+ padding: 2px 5px; border-radius: 0;
1818
1897
  letter-spacing: 0.04em;
1819
1898
  background: rgba(255,255,255,0.05);
1820
1899
  color: var(--text3);
1821
1900
  margin-top: 1px;
1822
1901
  }
1823
- .resume-list .resume-item .ri-tag.ok { color: var(--accent); background: rgba(0,217,126,0.08); }
1902
+ .resume-list .resume-item .ri-tag.ok { color: var(--accent-text); background: rgba(0,217,126,0.08); }
1824
1903
  .resume-list .resume-item .ri-tag.fail { color: var(--p1-fg); background: rgba(255,80,80,0.08); }
1825
1904
  .resume-list .resume-item .ri-text {
1826
1905
  flex: 1; min-width: 0;
@@ -1863,13 +1942,13 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1863
1942
  padding: 12px 14px;
1864
1943
  background: var(--bg-card);
1865
1944
  border: 1px solid var(--border);
1866
- border-radius: 8px;
1945
+ border-radius: 0;
1867
1946
  margin-bottom: 6px;
1868
1947
  transition: box-shadow .12s, transform .12s;
1869
1948
  cursor: pointer;
1870
1949
  min-width: 0;
1871
1950
  }
1872
- .inbox-row:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-1px); }
1951
+ .inbox-row:hover { box-shadow: none; transform: translateY(-1px); }
1873
1952
  .inbox-row .id {
1874
1953
  padding-top: 2px;
1875
1954
  font-family: var(--mono); font-size: var(--fs-caption);
@@ -1938,7 +2017,9 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1938
2017
  blocked colour — so "sat here longest" is legible at a glance instead of
1939
2018
  requiring five timestamps to be compared by eye. */
1940
2019
  .inbox-row.row-aged { border-left: 3px solid var(--status-blocked); }
1941
- .inbox-row.row-aged .actions.age-col .age { color: var(--status-blocked); font-weight: 600; }
2020
+ /* Colour carries this, not weight: the age is mono, whose axis stops at 500,
2021
+ so 600 only buys a synthetic stroke. */
2022
+ .inbox-row.row-aged .actions.age-col .age { color: var(--status-blocked); font-weight: 500; }
1942
2023
 
1943
2024
  /* Quiet page footer for conditions that report but do not ask. */
1944
2025
  .inbox-foot-note {
@@ -1952,7 +2033,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1952
2033
  .budgets-failed .bf-note { font-size: var(--fs-body); color: var(--text2); line-height: 1.5; margin-bottom: 14px; }
1953
2034
  .judge-status {
1954
2035
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
1955
- padding: 10px 12px; margin-bottom: 16px; border-radius: 8px;
2036
+ padding: 10px 12px; margin-bottom: 16px; border-radius: 0;
1956
2037
  background: var(--bg-muted); border: 1px solid var(--border);
1957
2038
  font-size: var(--fs-body);
1958
2039
  }
@@ -1965,11 +2046,11 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1965
2046
  .jscores[hidden] { display: none; }
1966
2047
  .jscores-head {
1967
2048
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
1968
- padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px 8px 0 0;
2049
+ padding: 10px 12px; border: 1px solid var(--border); border-radius: 0;
1969
2050
  background: var(--bg-muted); font-size: var(--fs-body);
1970
2051
  }
1971
2052
  .jscores-head .rate { font-size: var(--fs-num-s); font-weight: 600; }
1972
- .jscores-rows { border: 1px solid var(--border); border-top: 0; border-radius: 0 0 8px 8px; }
2053
+ .jscores-rows { border: 1px solid var(--border); border-top: 0; border-radius: 0; }
1973
2054
  .jscore {
1974
2055
  display: grid; grid-template-columns: minmax(9rem, 12rem) 7.5rem 1fr auto;
1975
2056
  gap: 12px; align-items: baseline;
@@ -1997,7 +2078,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
1997
2078
  .judge-status .js-form { display: flex; gap: 8px; align-items: center; margin-left: auto; }
1998
2079
  .judge-status input {
1999
2080
  font-family: var(--mono); font-size: var(--fs-small); padding: 5px 8px;
2000
- border: 1px solid var(--border-strong); border-radius: 6px;
2081
+ border: 1px solid var(--border-strong); border-radius: 0;
2001
2082
  background: var(--bg-card); color: var(--text); min-width: 26ch;
2002
2083
  }
2003
2084
  .budgets-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
@@ -2024,7 +2105,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2024
2105
  .budgets-table .bt-actions { text-align: right; white-space: nowrap; }
2025
2106
  .budgets-table .bt-actions .ab-btn + .ab-btn { margin-left: 6px; }
2026
2107
  .budgets-empty {
2027
- padding: 16px; border: 1px dashed var(--border-strong); border-radius: 8px;
2108
+ padding: 16px; border: 1px dashed var(--border-strong); border-radius: 0;
2028
2109
  color: var(--text2); font-size: var(--fs-body);
2029
2110
  }
2030
2111
  .budgets-uncapped { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
@@ -2032,7 +2113,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2032
2113
  .docs-search input {
2033
2114
  flex: 1 1 auto; min-width: 0; max-width: 460px;
2034
2115
  height: 32px; padding: 0 10px;
2035
- border: 1px solid var(--border); border-radius: 8px;
2116
+ border: 1px solid var(--border); border-radius: 0;
2036
2117
  background: var(--bg-card); color: var(--text); font: inherit; font-size: var(--fs-body);
2037
2118
  }
2038
2119
  .docs-search input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
@@ -2045,7 +2126,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2045
2126
  display: block; width: 100%;
2046
2127
  margin-top: 6px; padding: 8px;
2047
2128
  font-family: var(--mono); font-size: var(--fs-caption);
2048
- border: 1px dashed var(--border-strong); border-radius: 8px;
2129
+ border: 1px dashed var(--border-strong); border-radius: 0;
2049
2130
  background: transparent; color: var(--text2);
2050
2131
  }
2051
2132
  .col-more:hover { border-style: solid; color: var(--text); }
@@ -2053,7 +2134,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2053
2134
  .inbox-empty {
2054
2135
  padding: 14px 16px;
2055
2136
  background: var(--bg-muted);
2056
- border-radius: 8px;
2137
+ border-radius: 0;
2057
2138
  font-size: var(--fs-small); color: var(--text3);
2058
2139
  text-align: center;
2059
2140
  }
@@ -2071,7 +2152,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2071
2152
  }
2072
2153
  .window-fill {
2073
2154
  font-size: var(--fs-body); color: var(--text2); line-height: 1.5;
2074
- padding: 8px 12px; margin-bottom: 12px; border-radius: 8px;
2155
+ padding: 8px 12px; margin-bottom: 12px; border-radius: 0;
2075
2156
  background: var(--bg-muted); border: 1px solid var(--border);
2076
2157
  }
2077
2158
  .window-fill[hidden] { display: none; }
@@ -2086,22 +2167,22 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2086
2167
  .period-chips {
2087
2168
  display: flex; gap: 4px;
2088
2169
  background: var(--bg-muted); border: 1px solid var(--border);
2089
- border-radius: 8px; padding: 3px;
2170
+ border-radius: 0; padding: 3px;
2090
2171
  }
2091
2172
  .period-chip {
2092
2173
  background: transparent; border: none; color: var(--text2);
2093
- padding: 5px 12px; border-radius: 5px;
2174
+ padding: 5px 12px; border-radius: 0;
2094
2175
  font-family: var(--mono); font-size: var(--fs-caption); font-weight: 500;
2095
2176
  letter-spacing: 0.05em; cursor: pointer;
2096
2177
  transition: background .12s, color .12s;
2097
2178
  }
2098
2179
  .period-chip:hover { color: var(--text); }
2099
2180
  .period-chip.active {
2100
- background: var(--accent); color: var(--bg-page);
2181
+ background: var(--accent); color: var(--on-accent);
2101
2182
  }
2102
2183
  .metric-card {
2103
2184
  background: var(--bg-card); border: 1px solid var(--border);
2104
- border-radius: 12px; padding: 26px 24px;
2185
+ border-radius: 0; padding: 26px 24px;
2105
2186
  position: relative; overflow: hidden;
2106
2187
  }
2107
2188
  .metric-card::before {
@@ -2114,7 +2195,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2114
2195
  .metric-card.red::before { background: var(--status-blocked); }
2115
2196
  .metric-card.purple::before { background: var(--metric-purple); }
2116
2197
  .metric-num {
2117
- font-family: var(--serif); font-weight: 500;
2198
+ font-family: var(--display); font-weight: 500;
2118
2199
  /* The board is live (SSE). In a proportional face a 1 is narrower than an 8,
2119
2200
  so a figure that ticks over re-flows its own width and the number appears to
2120
2201
  breathe. Tabular figures cost nothing here and stop it. */
@@ -2136,11 +2217,11 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2136
2217
  }
2137
2218
  .delta {
2138
2219
  margin-left: 6px;
2139
- font-family: var(--mono); font-size: var(--fs-eyebrow); font-weight: 600;
2220
+ font-family: var(--mono); font-size: var(--fs-eyebrow); font-weight: 500;
2140
2221
  letter-spacing: 0.02em; white-space: nowrap;
2141
2222
  color: var(--text3); cursor: help;
2142
2223
  }
2143
- .delta.up { color: var(--status-review); }
2224
+ .delta.up { color: var(--status-review-text); }
2144
2225
  .delta.down { color: var(--status-progress); }
2145
2226
  .metric-num small {
2146
2227
  font-size: var(--fs-title-l); color: var(--text2);
@@ -2167,7 +2248,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2167
2248
  }
2168
2249
  .mp-card {
2169
2250
  background: var(--bg-card); border: 1px solid var(--border);
2170
- border-radius: 12px; padding: 22px;
2251
+ border-radius: 0; padding: 22px;
2171
2252
  }
2172
2253
  .mp-card h3 {
2173
2254
  font-family: var(--sans); font-weight: 600;
@@ -2182,9 +2263,9 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2182
2263
  }
2183
2264
  .feed-cost {
2184
2265
  font-family: var(--mono); font-size: var(--fs-eyebrow);
2185
- color: var(--accent);
2266
+ color: var(--accent-text);
2186
2267
  background: var(--accent-soft);
2187
- padding: 2px 6px; border-radius: 4px;
2268
+ padding: 2px 6px; border-radius: 0;
2188
2269
  margin-right: 6px;
2189
2270
  }
2190
2271
 
@@ -2235,7 +2316,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2235
2316
  .logs-header { padding: 20px 24px 12px; border-bottom: 1px solid var(--border); display: flex; align-items: baseline; gap: 16px; }
2236
2317
  .logs-header h2 { margin: 0; font-size: var(--fs-title-l); letter-spacing: -0.02em; }
2237
2318
  .logs-hint { font-size: var(--fs-small); color: var(--text3); }
2238
- .logs-hint code { font-family: var(--mono); background: var(--bg-strong); padding: 1px 5px; border-radius: 3px; }
2319
+ .logs-hint code { font-family: var(--mono); background: var(--bg-strong); padding: 1px 5px; border-radius: 0; }
2239
2320
  .logs-layout { display: flex; flex: 1; overflow: hidden; }
2240
2321
  .logs-list { width: 280px; min-width: 220px; border-right: 1px solid var(--border); overflow-y: auto; padding: 8px 0; flex-shrink: 0; }
2241
2322
  .logs-detail { flex: 1; overflow-y: auto; padding: 24px 28px; }
@@ -2266,7 +2347,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2266
2347
  .log-section-label { font-size: var(--fs-caption); color: var(--text3); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 6px; }
2267
2348
  .log-bullets { list-style: none; padding: 0; margin: 0; }
2268
2349
  .log-bullets li { padding: 3px 0 3px 12px; position: relative; font-size: var(--fs-body); color: var(--text2); }
2269
- .log-bullets li::before { content: '·'; position: absolute; left: 0; color: var(--accent); }
2350
+ .log-bullets li::before { content: '·'; position: absolute; left: 0; color: var(--accent-text); }
2270
2351
  .log-empty { font-size: var(--fs-body); color: var(--text3); font-style: italic; }
2271
2352
 
2272
2353
  /* ── Agents installed list (legacy — kept for fallback) ───────────────── */
@@ -2274,14 +2355,14 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2274
2355
  /* Agent budget bars (Paperclip cascading budget pattern) */
2275
2356
  .ab-title { font-weight: 600; color: var(--text2); margin-bottom: 6px; }
2276
2357
  .ab-row { display: flex; justify-content: space-between; padding: 2px 0; }
2277
- .ab-name { font-family: var(--mono, monospace); color: var(--accent); }
2358
+ .ab-name { font-family: var(--mono, monospace); color: var(--accent-text); }
2278
2359
  .ab-cap { color: var(--text3); }
2279
- .agent-card { background: var(--bg-muted); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
2280
- .agent-card .ac-name { font-size: var(--fs-small); font-weight: 600; font-family: var(--mono); color: var(--accent); }
2360
+ .agent-card { background: var(--bg-muted); border: 1px solid var(--border); border-radius: 0; padding: 10px 12px; }
2361
+ .agent-card .ac-name { font-size: var(--fs-small); font-weight: 500; font-family: var(--mono); color: var(--accent-text); }
2281
2362
  .agent-card .ac-desc { font-size: var(--fs-caption); color: var(--text3); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2282
2363
  .agent-card .ac-meta { font-size: var(--fs-eyebrow); color: var(--text3); margin-top: 5px; display: flex; gap: 8px; }
2283
- .agent-card .ac-model { background: var(--bg-strong); padding: 1px 5px; border-radius: 3px; }
2284
- .agent-card .ac-used { color: var(--status-review); }
2364
+ .agent-card .ac-model { background: var(--bg-strong); padding: 1px 5px; border-radius: 0; }
2365
+ .agent-card .ac-used { color: var(--status-review-text); }
2285
2366
  .agent-card .ac-never { color: var(--text3); font-style: italic; }
2286
2367
 
2287
2368
  /* ── Agents fleet view (DESIGN-agents-fleet-view §3) ───────────────────
@@ -2310,7 +2391,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2310
2391
  flex: 1; max-width: 320px;
2311
2392
  background: var(--bg-muted); border: 1px solid var(--border);
2312
2393
  color: var(--text); font-family: var(--mono); font-size: var(--fs-body);
2313
- padding: 8px 12px; border-radius: 6px;
2394
+ padding: 8px 12px; border-radius: 0;
2314
2395
  }
2315
2396
  .fleet-search input[type="search"]:focus {
2316
2397
  outline: 2px solid var(--focus-ring); outline-offset: 1px;
@@ -2319,7 +2400,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2319
2400
  .fleet-search .search-hint { font-size: var(--fs-caption); color: var(--text3); font-family: var(--mono); }
2320
2401
  .fleet-search .search-hint kbd {
2321
2402
  background: var(--bg-muted); border: 1px solid var(--border);
2322
- border-radius: 3px; padding: 1px 5px; font-size: var(--fs-eyebrow);
2403
+ border-radius: 0; padding: 1px 5px; font-size: var(--fs-eyebrow);
2323
2404
  }
2324
2405
 
2325
2406
  .fleet-facets { display: flex; flex-direction: column; gap: 6px; }
@@ -2334,13 +2415,13 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2334
2415
  .facet-chip {
2335
2416
  background: var(--bg-muted); border: 1px solid var(--border);
2336
2417
  color: var(--text2); font-size: var(--fs-small); font-family: var(--mono);
2337
- padding: 4px 10px; border-radius: 12px; cursor: pointer;
2418
+ padding: 4px 10px; border-radius: 0; cursor: pointer;
2338
2419
  transition: background-color 0.15s, color 0.15s, border-color 0.15s;
2339
2420
  }
2340
2421
  .facet-chip:hover { color: var(--text); border-color: var(--border-strong); }
2341
2422
  .facet-chip:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
2342
2423
  .facet-chip[aria-checked="true"] {
2343
- background: var(--accent-soft); color: var(--accent);
2424
+ background: var(--accent-soft); color: var(--accent-text);
2344
2425
  border-color: var(--accent-strong);
2345
2426
  }
2346
2427
  .facet-chip[disabled] { opacity: 0.4; cursor: not-allowed; }
@@ -2352,12 +2433,12 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2352
2433
  .fleet-sort select {
2353
2434
  background: var(--bg-muted); border: 1px solid var(--border);
2354
2435
  color: var(--text); font-family: var(--mono); font-size: var(--fs-small);
2355
- padding: 4px 8px; border-radius: 6px;
2436
+ padding: 4px 8px; border-radius: 0;
2356
2437
  }
2357
2438
  .fleet-sort button {
2358
2439
  background: transparent; border: 1px solid var(--border);
2359
2440
  color: var(--text2); font-size: var(--fs-caption); font-family: var(--mono);
2360
- padding: 4px 8px; border-radius: 6px;
2441
+ padding: 4px 8px; border-radius: 0;
2361
2442
  }
2362
2443
  .fleet-sort button:hover { color: var(--text); border-color: var(--border-strong); }
2363
2444
 
@@ -2373,7 +2454,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2373
2454
  width: 100%; min-height: 44px;
2374
2455
  background: transparent; border: 1px solid transparent;
2375
2456
  color: var(--text); text-align: left; font: inherit;
2376
- padding: 10px 12px; border-radius: 6px; cursor: pointer;
2457
+ padding: 10px 12px; border-radius: 0; cursor: pointer;
2377
2458
  transition: background-color 0.15s, border-color 0.15s;
2378
2459
  }
2379
2460
  .agent-row:hover { background: var(--bg-muted); border-color: var(--border); }
@@ -2396,12 +2477,12 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2396
2477
  .agent-row .ar-name { font-family: var(--mono); font-size: var(--fs-body); color: var(--text); }
2397
2478
  .agent-row .ar-meta { font-size: var(--fs-caption); color: var(--text3); display: flex; gap: 10px; flex-wrap: wrap; }
2398
2479
  .agent-row .ar-meta .ar-domain {
2399
- background: var(--bg-muted); padding: 1px 6px; border-radius: 3px;
2480
+ background: var(--bg-muted); padding: 1px 6px; border-radius: 0;
2400
2481
  color: var(--text2); font-family: var(--mono); font-size: var(--fs-eyebrow);
2401
2482
  }
2402
2483
  .agent-row .ar-rail {
2403
2484
  width: 80px; height: 4px;
2404
- background: var(--rail-ok-bg); border-radius: 2px; overflow: hidden;
2485
+ background: var(--rail-ok-bg); border-radius: 0; overflow: hidden;
2405
2486
  }
2406
2487
  .agent-row.status-failing .ar-rail { background: var(--rail-fail-bg); }
2407
2488
  .agent-row .ar-rail-fill {
@@ -2417,7 +2498,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2417
2498
  .status-pill {
2418
2499
  display: inline-flex; align-items: center; gap: 6px;
2419
2500
  font-size: var(--fs-caption); font-family: var(--mono);
2420
- padding: 2px 8px; border-radius: 10px;
2501
+ padding: 2px 8px; border-radius: 0;
2421
2502
  background: var(--bg-muted); border: 1px solid var(--border);
2422
2503
  }
2423
2504
  .status-pill .pill-dot {
@@ -2441,11 +2522,12 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2441
2522
  width: 480px; max-width: 90vw;
2442
2523
  background: var(--surface-drawer);
2443
2524
  border-left: 1px solid var(--border-strong);
2444
- box-shadow: var(--drawer-shadow);
2525
+ box-shadow: none;
2445
2526
  display: flex; flex-direction: column;
2446
2527
  z-index: 10;
2447
2528
  transform: translateX(100%);
2448
2529
  transition: transform 200ms ease-out;
2530
+ border: 1px solid var(--border-strong);
2449
2531
  }
2450
2532
  .agent-drawer[open] { transform: translateX(0); }
2451
2533
  @media (prefers-reduced-motion: reduce) {
@@ -2463,7 +2545,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2463
2545
  .agent-drawer-close {
2464
2546
  background: transparent; border: 1px solid var(--border);
2465
2547
  color: var(--text2); font-size: var(--fs-title-m); line-height: 1;
2466
- width: 28px; height: 28px; border-radius: 6px;
2548
+ width: 28px; height: 28px; border-radius: 0;
2467
2549
  display: flex; align-items: center; justify-content: center;
2468
2550
  }
2469
2551
  .agent-drawer-close:hover { color: var(--text); border-color: var(--border-strong); }
@@ -2506,7 +2588,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2506
2588
  .agent-actions button {
2507
2589
  background: var(--bg-muted); border: 1px solid var(--border);
2508
2590
  color: var(--text); font-family: var(--mono); font-size: var(--fs-small);
2509
- padding: 6px 12px; border-radius: 6px;
2591
+ padding: 6px 12px; border-radius: 0;
2510
2592
  }
2511
2593
  .agent-actions button:hover { border-color: var(--border-strong); }
2512
2594
  .agent-actions button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
@@ -2537,11 +2619,11 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2537
2619
  width: 100%; max-width: 640px;
2538
2620
  background: var(--bg-card);
2539
2621
  border: 1px solid var(--border);
2540
- border-radius: 16px;
2622
+ border-radius: 0;
2541
2623
  padding: 32px;
2542
2624
  }
2543
2625
  .share-card h2:not(.section-title) {
2544
- font-family: var(--serif); font-weight: 500;
2626
+ font-family: var(--display); font-weight: 500;
2545
2627
  font-size: 28px; letter-spacing: -0.01em;
2546
2628
  margin: 0 0 6px;
2547
2629
  }
@@ -2551,14 +2633,14 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2551
2633
  padding: 16px 18px;
2552
2634
  background: var(--bg-muted);
2553
2635
  border: 1px solid var(--border);
2554
- border-radius: 10px;
2636
+ border-radius: 0;
2555
2637
  margin-bottom: 16px;
2556
2638
  }
2557
2639
  .share-toggle-row .label-grp { flex: 1; }
2558
2640
  .share-toggle-row .label-grp .l { font-weight: 600; font-size: var(--fs-base); }
2559
2641
  .share-toggle-row .label-grp .h { color: var(--text2); font-size: var(--fs-small); }
2560
2642
 
2561
- .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; }
2643
+ .notif-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--bg-muted); border: 1px solid var(--border); border-radius: 0; cursor: pointer; transition: border-color 0.15s; }
2562
2644
  .notif-row:hover { border-color: var(--accent); }
2563
2645
  .notif-row .emoji { font-size: 16px; flex-shrink: 0; }
2564
2646
  .notif-row strong { color: var(--text); margin-right: 4px; }
@@ -2579,7 +2661,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2579
2661
  background: var(--bg-card);
2580
2662
  border-radius: 50%;
2581
2663
  transition: transform 0.15s;
2582
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
2664
+ box-shadow: none;
2583
2665
  }
2584
2666
  .toggle.on { background: var(--accent); }
2585
2667
  .toggle.on::after { transform: translateX(16px); }
@@ -2590,7 +2672,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2590
2672
  padding: 10px 12px;
2591
2673
  background: var(--bg-card);
2592
2674
  border: 1px solid var(--border-strong);
2593
- border-radius: 10px;
2675
+ border-radius: 0;
2594
2676
  margin-top: 16px;
2595
2677
  }
2596
2678
  .url-block .url-text {
@@ -2621,7 +2703,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2621
2703
  display: flex; align-items: center; gap: 10px;
2622
2704
  padding: 12px 16px;
2623
2705
  background: var(--bg-muted);
2624
- border-radius: 10px;
2706
+ border-radius: 0;
2625
2707
  font-size: var(--fs-body); color: var(--text2);
2626
2708
  margin-top: 12px;
2627
2709
  }
@@ -2642,7 +2724,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2642
2724
  text-transform: uppercase;
2643
2725
  letter-spacing: 0.04em;
2644
2726
  }
2645
- .share-badge.on { background: rgba(22, 163, 74, 0.12); color: var(--status-review); }
2727
+ .share-badge.on { background: rgba(22, 163, 74, 0.12); color: var(--status-review-text); }
2646
2728
  .share-badge.off { background: var(--bg-strong); color: var(--text3); }
2647
2729
  .share-badge-dot {
2648
2730
  width: 5px; height: 5px; border-radius: 50%;
@@ -2670,7 +2752,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2670
2752
  transition: background 0.12s, color 0.12s, border-color 0.12s;
2671
2753
  }
2672
2754
  .fchip:hover { background: var(--bg-elevated); color: var(--text); border-color: var(--border-strong); }
2673
- .fchip.active { background: var(--accent); color: var(--on-solid); border-color: transparent; }
2755
+ .fchip.active { background: var(--accent); color: var(--on-accent); border-color: transparent; }
2674
2756
  .fchip-agent.active { background: var(--chip-agent); }
2675
2757
  .fchip-priority.active { background: var(--status-progress); color: var(--on-warm); }
2676
2758
  .fchip-label.active { background: var(--accent); }
@@ -2699,8 +2781,8 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2699
2781
  bottom: calc(100% + 4px); left: 0;
2700
2782
  background: var(--bg-card);
2701
2783
  border: 1px solid var(--border-strong);
2702
- border-radius: 6px;
2703
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
2784
+ border-radius: 0;
2785
+ box-shadow: none;
2704
2786
  z-index: 500;
2705
2787
  padding: 4px;
2706
2788
  min-width: 130px;
@@ -2708,7 +2790,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2708
2790
  .sp-item {
2709
2791
  display: flex; align-items: center; gap: 8px;
2710
2792
  padding: 6px 10px;
2711
- border-radius: 4px;
2793
+ border-radius: 0;
2712
2794
  font-size: var(--fs-small); font-weight: 500;
2713
2795
  color: var(--text);
2714
2796
  background: transparent; border: none;
@@ -2750,24 +2832,24 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2750
2832
  margin-top: 4px; flex-shrink: 0;
2751
2833
  }
2752
2834
  .tl-content { flex: 1; display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px; }
2753
- .tl-actor { font-family: var(--mono); font-size: var(--fs-caption); font-weight: 600; color: var(--accent); }
2835
+ .tl-actor { font-family: var(--mono); font-size: var(--fs-caption); font-weight: 500; color: var(--accent-text); }
2754
2836
  .tl-action { color: var(--text2); }
2755
2837
  .tl-change {
2756
2838
  font-family: var(--mono); font-size: var(--fs-caption);
2757
2839
  background: var(--bg-strong); color: var(--text);
2758
- padding: 1px 6px; border-radius: 3px;
2840
+ padding: 1px 6px; border-radius: 0;
2759
2841
  }
2760
2842
  .tl-ts { margin-left: auto; font-family: var(--mono); font-size: var(--fs-eyebrow); color: var(--text3); }
2761
2843
  .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; }
2762
2844
 
2763
2845
  /* ── Security tab: hero KPI tile + activity feed ──────────────────────────── */
2764
2846
  .kpi-hero .kpi-value-hero {
2765
- font-family: var(--serif); font-weight: 500;
2847
+ font-family: var(--display); font-weight: 500;
2766
2848
  font-variant-numeric: tabular-nums;
2767
2849
  font-size: var(--fs-num-l); line-height: 1.1; letter-spacing: -0.02em;
2768
2850
  }
2769
2851
  .kpi-hero .kpi-label { font-size: var(--fs-body); font-weight: 500; }
2770
- .kpi-empty .kpi-value { color: var(--accent); font-size: var(--fs-title-l); }
2852
+ .kpi-empty .kpi-value { color: var(--accent-text); font-size: var(--fs-title-l); }
2771
2853
  .kpi-empty .kpi-value::after {
2772
2854
  content: ' all clear'; font-size: var(--fs-caption); color: var(--text3); font-weight: 400;
2773
2855
  }
@@ -2793,7 +2875,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
2793
2875
  it waits for the slide to finish (`0s linear 200ms`), so the panel does
2794
2876
  not vanish mid-animation. */
2795
2877
  transition: transform 200ms ease-out, visibility 0s linear 200ms;
2796
- box-shadow: var(--drawer-shadow);
2878
+ box-shadow: none;
2797
2879
  }
2798
2880
  .sidebar.open {
2799
2881
  transform: none; visibility: visible;
@@ -3064,7 +3146,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
3064
3146
  </div>
3065
3147
  </div>
3066
3148
  </div>
3067
- <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>
3149
+ <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:0;margin-bottom:10px;"></div>
3068
3150
 
3069
3151
  <div class="inbox-section">
3070
3152
  <div class="inbox-section-head">
@@ -3410,7 +3492,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
3410
3492
  <div id="notif-step-email">
3411
3493
  <div style="font-size:var(--fs-caption);color:var(--text3);margin-bottom:4px;font-family:var(--mono);letter-spacing:.05em">RECIPIENT EMAIL</div>
3412
3494
  <div style="display:flex;gap:8px;flex-wrap:wrap">
3413
- <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)" />
3495
+ <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:0;color:var(--text);font-size:var(--fs-base)" />
3414
3496
  <button class="btn-black" onclick="sendVerifyCode()">Send code</button>
3415
3497
  </div>
3416
3498
  </div>
@@ -3419,15 +3501,15 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
3419
3501
  <div id="notif-step-code" style="display:none;margin-top:14px">
3420
3502
  <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>
3421
3503
  <div style="display:flex;gap:8px;flex-wrap:wrap">
3422
- <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" />
3504
+ <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:0;color:var(--text);font-size:var(--fs-title-m);font-family:var(--mono);letter-spacing:.2em;text-align:center" />
3423
3505
  <button class="btn-black" onclick="confirmVerifyCode()">Verify</button>
3424
3506
  <button class="btn-ghost-sm" onclick="sendVerifyCode()">Resend code</button>
3425
3507
  </div>
3426
3508
  </div>
3427
3509
 
3428
3510
  <!-- Step 3: verified -->
3429
- <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)">
3430
- <span style="color:var(--accent);font-size:var(--fs-title-m)">✓</span>
3511
+ <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:0;display:none;align-items:center;gap:10px;font-size: var(--fs-body)">
3512
+ <span style="color:var(--accent-text);font-size:var(--fs-title-m)">✓</span>
3431
3513
  <span><strong id="notif-verified-email">—</strong> verified · alerts delivered through <code>greatcto.systems/notify</code></span>
3432
3514
  <button class="btn-ghost-sm" style="margin-left:auto" onclick="changeEmail()">Change</button>
3433
3515
  </div>
@@ -3479,7 +3561,7 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
3479
3561
  <h2 class="section-title">Recent notifications</h2>
3480
3562
  <p class="sub">Last 100 alerts fired this session. Also accessible via the notification drawer (bell icon).</p>
3481
3563
  <div id="notif-history-inline" style="display:flex;flex-direction:column;gap:0;margin-top:12px;
3482
- border:1px solid var(--border);border-radius:8px;overflow:hidden;font-size:var(--fs-body)">
3564
+ border:1px solid var(--border);border-radius:0;overflow:hidden;font-size:var(--fs-body)">
3483
3565
  <div id="notif-history-empty" style="padding:20px 16px;color:var(--text3);text-align:center">
3484
3566
  No notifications yet this session.
3485
3567
  </div>
@@ -3563,6 +3645,30 @@ function greetByHour() {
3563
3645
  return 'Good evening.';
3564
3646
  }
3565
3647
 
3648
+ // What is waiting on the person in the projects this screen is not showing.
3649
+ //
3650
+ // "Nothing is waiting on you" was written above a project inbox while two P0s
3651
+ // sat open in another project of the same operator's. True of the project,
3652
+ // false of the person — and the person is who reads it. The server now sends
3653
+ // `elsewhere` with the inbox; this turns it into the clause the headline needs.
3654
+ //
3655
+ // Three states. A payload without `elsewhere` (an older server) or one marked
3656
+ // unreadable says NOTHING about other projects, so the headline must not claim
3657
+ // "nothing elsewhere" — `known` is false and the clause is empty. Known and
3658
+ // empty is an honest zero. Known and non-empty is the sentence.
3659
+ function elsewhereClause(e) {
3660
+ if (!e || e.state === 'unreadable') return { known: false, text: '' };
3661
+ const p0 = Number(e.p0) || 0;
3662
+ const gates = Number(e.gates) || 0;
3663
+ const m = (e.projects || []).length;
3664
+ if (!p0 && !gates) return { known: true, text: '' };
3665
+ const parts = [];
3666
+ if (p0) parts.push(`${p0} P0`);
3667
+ if (gates) parts.push(`${gates} gate${gates > 1 ? 's' : ''}`);
3668
+ const verb = p0 + gates > 1 ? 'are' : 'is';
3669
+ return { known: true, text: `${parts.join(' and ')} in ${m} other project${m > 1 ? 's' : ''} ${verb} waiting on you.` };
3670
+ }
3671
+
3566
3672
  function renderInbox(d) {
3567
3673
  // Kept so renderResume can ask what this section is already showing, rather
3568
3674
  // than the two of them rendering the same task independently.
@@ -3573,8 +3679,20 @@ function renderInbox(d) {
3573
3679
  // anything that matched two of them — a P0 that is also blocked read as two things
3574
3680
  // needing attention when it was one. Older payloads have no `needs_you`, so the sum
3575
3681
  // stays as the fallback rather than rendering nothing.
3576
- document.getElementById('nav-inbox-count').textContent =
3577
- Number.isFinite(s.needs_you) ? s.needs_you : s.gates + s.p0 + s.blocked;
3682
+ // The badge counts the PERSON: this project's distinct needs, plus the P0s
3683
+ // and gates waiting in their other projects. A badge reading 0 beside two
3684
+ // open P0s one project over is the same lie as the headline was. When
3685
+ // `elsewhere` is unknown the badge shows what it knows and says so in its
3686
+ // title rather than adding a zero it never measured.
3687
+ const here = Number.isFinite(s.needs_you) ? s.needs_you : s.gates + s.p0 + s.blocked;
3688
+ const ew = d?.elsewhere;
3689
+ const ewKnown = ew && ew.state !== 'unreadable';
3690
+ const away = ewKnown ? (Number(ew.p0) || 0) + (Number(ew.gates) || 0) : 0;
3691
+ const badge = document.getElementById('nav-inbox-count');
3692
+ badge.textContent = here + away;
3693
+ badge.title = ewKnown
3694
+ ? `${here} here · ${away} in other projects`
3695
+ : `${here} here · other projects not read`;
3578
3696
  // Same rule as the all-clear card: "nothing urgent" is a claim about data we
3579
3697
  // may not have. When a read failed, say that instead of reassuring the user.
3580
3698
  // The headline states the SITUATION, not the time of day.
@@ -3609,9 +3727,14 @@ function renderInbox(d) {
3609
3727
  greetEl.textContent = 'Some data could not be read — treat the counts below as incomplete.';
3610
3728
  } else if (newestEventAgeH != null && newestEventAgeH > STALL_H) {
3611
3729
  const days = Math.floor(newestEventAgeH / 24);
3730
+ // "Here", because the count above is this project's. What the person has
3731
+ // waiting in other projects is appended — or, when it could not be read,
3732
+ // nothing is claimed about them at all.
3733
+ const ew = elsewhereClause(d?.elsewhere);
3734
+ const tail = ew.text ? ` ${ew.text}` : (ew.known ? ' Nothing elsewhere either.' : '');
3612
3735
  greetEl.textContent = needs
3613
- ? `Stalled — nothing has moved in ${days} days, and ${needs} item${needs > 1 ? 's' : ''} ${needs > 1 ? 'are' : 'is'} waiting on you.`
3614
- : `Stalled — nothing has moved in ${days} days, and nothing is waiting on you either.`;
3736
+ ? `Stalled — nothing has moved in ${days} days, and ${needs} item${needs > 1 ? 's' : ''} ${needs > 1 ? 'are' : 'is'} waiting on you here.${tail}`
3737
+ : `Stalled — nothing has moved in ${days} days, and nothing is waiting on you here.${tail}`;
3615
3738
  } else if (needs) {
3616
3739
  greetEl.textContent = "Here's what needs your decision.";
3617
3740
  } else {
@@ -3653,14 +3776,22 @@ function renderInbox(d) {
3653
3776
  const stalled = newestEventAgeH != null && newestEventAgeH > STALL_H;
3654
3777
  const txt = ac.querySelector('.ac-text');
3655
3778
  const mark = ac.querySelector('.ac-mark');
3779
+ // The card is about the person, so it may not say "all clear" while the
3780
+ // same person has a P0 open one project over. `elsewhere` arrives with the
3781
+ // inbox payload; unknown (older server, unreadable) claims nothing.
3782
+ const ew = elsewhereClause(window.__inboxData?.elsewhere);
3783
+ const elsewhereHtml = ew.text ? ` <b>${ew.text}</b>` : (ew.known ? ' Nothing elsewhere either.' : '');
3656
3784
  if (stalled) {
3657
3785
  const days = Math.floor(newestEventAgeH / 24);
3658
- if (mark) mark.textContent = '·';
3659
- if (txt) txt.innerHTML = `<b>Nothing is waiting on you.</b> Nothing has run in `
3660
- + `${days} days either — this project is idle, not finished.`;
3786
+ if (mark) mark.textContent = ew.text ? '!' : '\u00b7';
3787
+ if (txt) txt.innerHTML = `<b>Nothing is waiting on you here.</b> Nothing has run in `
3788
+ + `${days} days either — this project is idle, not finished.${elsewhereHtml}`;
3789
+ } else if (ew.text) {
3790
+ if (mark) mark.textContent = '!';
3791
+ if (txt) txt.innerHTML = `<b>Nothing needs your decision here.</b>${elsewhereHtml}`;
3661
3792
  } else {
3662
3793
  if (mark) mark.textContent = '\u2713';
3663
- if (txt) txt.innerHTML = '<b>All clear.</b> Nothing needs your decision — back to deep work.';
3794
+ if (txt) txt.innerHTML = `<b>All clear.</b> Nothing needs your decision here — back to deep work.${elsewhereHtml}`;
3664
3795
  }
3665
3796
  }
3666
3797
  }
@@ -3717,8 +3848,18 @@ function renderQueue(d) {
3717
3848
  const queue = [
3718
3849
  // An expired gate is still a gate, and saying so is the whole point: the row
3719
3850
  // that lost its buttons for 699 hours looked like an ordinary P0.
3720
- ...withReason(d?.pending_gates, (t) =>
3721
- (t.raw_status === 'blocked' ? 'expired gate' : 'gate')),
3851
+ // ADR-009 puts gates on cost-of-undo, and the map obeys it — but until now
3852
+ // every gate reached this row as the same word. `gate:ship` to production and
3853
+ // a brief approval read identically at the moment somebody clicks, which is
3854
+ // the one place the doctrine had never been applied. The three states come
3855
+ // from the API; `unclassified` deliberately does NOT fall back to `gate`.
3856
+ ...withReason(d?.pending_gates, (t) => {
3857
+ if (t.raw_status === 'blocked') return 'expired gate';
3858
+ const st = t.reversibility?.state;
3859
+ if (st === 'expensive') return 'gate · expensive to undo';
3860
+ if (st === 'unclassified') return 'gate · cost not classified';
3861
+ return 'gate';
3862
+ }),
3722
3863
  ...withReason(d?.p0_open, (t) => (t.is_gate ? 'expired gate' : 'P0')),
3723
3864
  ...withReason(d?.blocked, 'blocked'),
3724
3865
  ...withReason(d?.stale_in_progress, (t) => {
@@ -3865,7 +4006,7 @@ function freshnessBadge(doc) {
3865
4006
  const unreadable = doc.freshnessBasis === 'unreadable';
3866
4007
  const colour = v === 'stale' ? 'var(--status-gate)' : 'var(--text3)';
3867
4008
  const label = v === 'stale' ? 'stale' : (unreadable ? 'could not read' : 'unknown');
3868
- 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>`;
4009
+ 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:0;color:${colour}">${esc(label)}</span>`;
3869
4010
  }
3870
4011
 
3871
4012
  // Will this gate wait for you, or did the pipeline already go past it?
@@ -3900,7 +4041,7 @@ function gateTierNote(t) {
3900
4041
  // It is the reading most worth a second look, so it reads differently.
3901
4042
  const caveat = thin ? ' — one eval, coverage unmeasured' : '';
3902
4043
  return `<div style="margin-top:3px;font-size:var(--fs-caption);color:${colour}">
3903
- <span style="font-family:var(--mono);border:1px solid var(--border);border-radius:3px;padding:0 4px">${esc(info.tier)}</span>
4044
+ <span style="font-family:var(--mono);border:1px solid var(--border);border-radius:0;padding:0 4px">${esc(info.tier)}</span>
3904
4045
  the pipeline did not wait for this${esc(caveat)} · <span title="${esc(info.why || '')}">${esc((info.why || '').slice(0, 90))}</span>
3905
4046
  </div>`;
3906
4047
  }
@@ -3939,7 +4080,7 @@ async function refreshStandDowns() {
3939
4080
  // unmeasured coverage. That is the row most worth a second look, so it is
3940
4081
  // the row that looks different.
3941
4082
  const thin = r.tier === 'notify-thin';
3942
- 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>`;
4083
+ const tierTag = `<span style="font-family:var(--mono);font-size:var(--fs-eyebrow);padding:1px 5px;border-radius:0;border:1px solid var(--border);color:${thin ? 'var(--status-gate)' : 'var(--text3)'}">${esc(r.tier || 'unknown')}</span>`;
3943
4084
  return `
3944
4085
  <div class="inbox-row">
3945
4086
  <span class="id">gate:${esc(r.gate || '?')}</span>
@@ -4129,7 +4270,14 @@ function renderInboxList(rootId, items, countId, opts = {}) {
4129
4270
  // carry this fact in their headings, where it was lost the moment an item moved
4130
4271
  // between them — which is how an expired gate became indistinguishable from an
4131
4272
  // ordinary P0 for 699 hours.
4132
- const REASON_TONE = { 'expired gate': 'gate', gate: 'gate', P0: 'p0', blocked: 'blocked' };
4273
+ const REASON_TONE = {
4274
+ 'expired gate': 'gate', gate: 'gate', P0: 'p0', blocked: 'blocked',
4275
+ // The class is looked up HERE, not derived from the string — a reason with
4276
+ // no entry renders `class="why why-"` and is styled by nothing. Adding the
4277
+ // reason without adding the tone is how a new chip ships invisible.
4278
+ 'gate · expensive to undo': 'gate-expensive',
4279
+ 'gate · cost not classified': 'gate-unclassified',
4280
+ };
4133
4281
  const tone = REASON_TONE[t.__reason] || (String(t.__reason || '').startsWith('stale') ? 'stale' : '');
4134
4282
  const reasonChip = (opts.reasons && t.__reason)
4135
4283
  ? `<span class="why why-${tone}">${esc(t.__reason)}</span>`
@@ -4885,7 +5033,7 @@ function showVerdictDetail(idx) {
4885
5033
  <div class="rdm-artefacts-title">Artefacts (click to open in editor)</div>
4886
5034
  ${artefactsHtml}
4887
5035
  </div>
4888
- <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>
5036
+ <div class="rdm-foot">Press <kbd style="background:var(--bg-muted);border:1px solid var(--border);border-radius:0;padding:1px 5px;font-size:var(--fs-eyebrow);">Esc</kbd> or click outside to close</div>
4889
5037
  </div>
4890
5038
  `;
4891
5039
  document.body.appendChild(modal);
@@ -5476,7 +5624,7 @@ async function loadDocs() {
5476
5624
  const map = d.map ? `<div class="card"><b>Components</b>
5477
5625
  <span class="muted">— derived ${ago(d.map.generatedAt)}, so it cannot disagree with the code</span>
5478
5626
  <div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:10px">
5479
- ${d.map.nodes.map(n => `<div style="border:1px solid var(--border,#ddd);border-radius:6px;padding:8px 10px;min-width:120px">
5627
+ ${d.map.nodes.map(n => `<div style="border:1px solid var(--border,#ddd);border-radius:0;padding:8px 10px;min-width:120px">
5480
5628
  <div><b>${esc(n.label)}</b> <span class="muted">${n.count}</span></div>
5481
5629
  <div class="muted" style="font-size:var(--fs-caption)">${esc(n.what || '')}</div></div>`).join('')}
5482
5630
  </div>
@@ -5615,9 +5763,9 @@ function renderDeclaredPipeline(mermaid) {
5615
5763
  const name = (id) => id.replace(/_/g, '-');
5616
5764
  return `<div style="margin-top:10px;font-size:var(--fs-small)">${edges.map(e => `
5617
5765
  <div style="display:flex;align-items:center;gap:8px;padding:3px 0">
5618
- <span style="border:1px solid var(--border,#ddd);border-radius:4px;padding:2px 8px">${esc(name(e.from))}</span>
5766
+ <span style="border:1px solid var(--border,#ddd);border-radius:0;padding:2px 8px">${esc(name(e.from))}</span>
5619
5767
  <span class="muted">${e.join ? '⋯ join ⋯' : (e.gate ? `→ ${esc(e.gate)} →` : '→')}</span>
5620
- <span style="border:1px solid var(--border,#ddd);border-radius:4px;padding:2px 8px">${esc(name(e.to))}</span>
5768
+ <span style="border:1px solid var(--border,#ddd);border-radius:0;padding:2px 8px">${esc(name(e.to))}</span>
5621
5769
  </div>`).join('')}</div>`;
5622
5770
  }
5623
5771
 
@@ -6242,12 +6390,71 @@ function renderDegradedBanner(id, reason, headline) {
6242
6390
  /** True when any project-scoped read failed — used to suppress "all clear" claims. */
6243
6391
  function anyDegraded() { return Object.keys(BOARD_DEGRADED).length > 0; }
6244
6392
 
6393
+ /**
6394
+ * The project the server actually served, when it is not the one that was asked
6395
+ * for. Null the rest of the time.
6396
+ *
6397
+ * `?project=<unknown>` falls back to the server's own directory and returns that
6398
+ * project's numbers — 242 tasks, $2.70, eighteen scores — byte-identical to a
6399
+ * real answer. The server has announced this since the BH-5 fix, in
6400
+ * `X-Project-Fallback`, and until now NOTHING IN THIS FILE READ THAT HEADER:
6401
+ * the announcement went to a channel with no listener, which is the same defect
6402
+ * as not announcing it, one layer further from where anyone would look.
6403
+ *
6404
+ * This is worse than the degraded case beside it. Degraded means a panel could
6405
+ * not be read and shows empty. This means every panel was read correctly, for
6406
+ * somebody else. For an operator running eighteen projects a mistyped or stale
6407
+ * slug — a bookmark, a shared link, a project dropped from the registry — puts
6408
+ * another client's spend on screen with nothing to distinguish it.
6409
+ */
6410
+ let PROJECT_FALLBACK = null;
6411
+
6412
+ /** Announce, or clear, "these are not the numbers you asked for". */
6413
+ function renderProjectFallbackBanner() {
6414
+ const id = 'project-fallback-banner';
6415
+ const existing = document.getElementById(id);
6416
+ if (!PROJECT_FALLBACK) { if (existing) existing.remove(); return; }
6417
+ const { requested, serving } = PROJECT_FALLBACK;
6418
+ const el = existing || document.createElement('div');
6419
+ el.id = id;
6420
+ el.className = 'degraded-banner project-fallback-banner';
6421
+ el.setAttribute('role', 'alert');
6422
+ // Both names, always. "Project not found" would send the operator looking for
6423
+ // a missing project; the thing they need to know is whose figures they have
6424
+ // been reading while they looked.
6425
+ el.innerHTML = `<strong>${esc(requested)} was not found</strong>`
6426
+ + `<span>Every panel below is showing <strong>${esc(serving || 'another project')}</strong>`
6427
+ + ` — the board fell back to its own directory. Pick a project from the list to fix this.</span>`;
6428
+ if (!existing) {
6429
+ const host = document.querySelector('.workspace')
6430
+ || (document.getElementById('kanban-board') || {}).parentNode;
6431
+ if (host) host.insertBefore(el, host.firstChild);
6432
+ }
6433
+ }
6434
+
6245
6435
  function api(url, opts) {
6246
6436
  const key = String(url).split('?')[0];
6247
6437
  return fetch(url, opts).then(r => {
6248
6438
  const d = r.headers && r.headers.get('X-Board-Degraded');
6249
6439
  if (d) { try { BOARD_DEGRADED[key] = decodeURIComponent(d); } catch { BOARD_DEGRADED[key] = d; } }
6250
6440
  else delete BOARD_DEGRADED[key];
6441
+ // Read on EVERY response, not once at boot: the operator switches projects
6442
+ // without a reload, so a banner raised by one slug has to come down when the
6443
+ // next one resolves. Driven by `X-Project-Resolved`, which is present on
6444
+ // every response — keying off the absence of X-Project-Fallback alone would
6445
+ // leave the banner up forever against a server too old to send either.
6446
+ if (r.headers) {
6447
+ const resolved = r.headers.get('X-Project-Resolved');
6448
+ if (resolved === 'fallback') {
6449
+ PROJECT_FALLBACK = {
6450
+ requested: (new URL(String(url), location.origin)).searchParams.get('project') || '(unnamed)',
6451
+ serving: r.headers.get('X-Project-Serving') || '',
6452
+ };
6453
+ } else if (resolved) {
6454
+ PROJECT_FALLBACK = null;
6455
+ }
6456
+ try { renderProjectFallbackBanner(); } catch { /* pre-DOM call */ }
6457
+ }
6251
6458
  return r.json();
6252
6459
  }).catch(() => {
6253
6460
  // A request that never completed is the one case the degradation mechanism
@@ -7151,7 +7358,7 @@ function openSide(task) {
7151
7358
  const docsSec = docRefs.length ? `
7152
7359
  <div class="side-section"><div class="side-section-head">Documents</div>
7153
7360
  ${docRefs.map(p => `
7154
- <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">
7361
+ <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:0;padding:6px 10px;margin:4px 0;cursor:pointer;font-family:var(--mono);font-size:var(--fs-small);color:inherit">
7155
7362
  <span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(p)}</span>
7156
7363
  <span style="color:var(--text3)">open →</span>
7157
7364
  </button>`).join('')}
@@ -7572,7 +7779,7 @@ function renderLogDetail(log) {
7572
7779
  el.innerHTML = `
7573
7780
  <div class="log-detail-head">
7574
7781
  <h3>${esc(log.title)}</h3>
7575
- <div class="log-detail-meta">${esc(log.date)}${log.time ? ' at ' + esc(log.time) : ''}${log.duration ? ' · ' + esc(log.duration) : ''}${log.source === 'verdicts' ? ' · <span style="color:var(--accent)">auto-synthesized from verdicts</span>' : log.source === 'auto' ? ' · <span style="color:var(--accent)">auto-captured by SessionEnd hook</span>' : ''}</div>
7782
+ <div class="log-detail-meta">${esc(log.date)}${log.time ? ' at ' + esc(log.time) : ''}${log.duration ? ' · ' + esc(log.duration) : ''}${log.source === 'verdicts' ? ' · <span style="color:var(--accent-text)">auto-synthesized from verdicts</span>' : log.source === 'auto' ? ' · <span style="color:var(--accent-text)">auto-captured by SessionEnd hook</span>' : ''}</div>
7576
7783
  </div>
7577
7784
  <div class="log-section">
7578
7785
  <div class="log-section-label">Done</div>
@@ -8303,7 +8510,7 @@ function renderNotifDrawer() {
8303
8510
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${esc(n.title)}</div>
8304
8511
  <div style="font-size:var(--fs-small);color:var(--text3);margin-top:2px;line-height:1.4">${esc(n.body)}</div>
8305
8512
  <div style="font-size:var(--fs-caption);color:var(--text3);margin-top:4px;font-family:var(--mono)">
8306
- ${n.project ? `<span style="color:var(--accent);margin-right:6px">${esc(n.project)}</span>` : ''}
8513
+ ${n.project ? `<span style="color:var(--accent-text);margin-right:6px">${esc(n.project)}</span>` : ''}
8307
8514
  ${new Date(n.ts).toLocaleString()}
8308
8515
  </div>
8309
8516
  </div>
@@ -8481,8 +8688,7 @@ if (_costLbl) _costLbl.textContent = `Cost · last ${periodLabel(currentPeriod)}
8481
8688
  <div id="notif-drawer" style="position:fixed;top:0;right:-400px;width:380px;height:100vh;
8482
8689
  background:var(--bg-elevated);border-left:1px solid var(--border);z-index:9999;
8483
8690
  transition:right .25s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;
8484
- box-shadow:-4px 0 32px rgba(0,0,0,.5)">
8485
- <div style="padding:14px 16px;border-bottom:1px solid var(--border);
8691
+ box-shadow: none;border-bottom:1px solid var(--border);
8486
8692
  display:flex;align-items:center;gap:8px;flex-shrink:0">
8487
8693
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
8488
8694
  style="width:16px;height:16px;color:var(--text2)">