segreant 0.1.0 → 0.2.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.
Files changed (142) hide show
  1. package/LICENSING.md +5 -3
  2. package/README.md +57 -19
  3. package/bin/segreant-statusline.mjs +20 -0
  4. package/bin/segreant.mjs +19 -8
  5. package/dist/alerts/detect.js +13 -0
  6. package/dist/billing/epistemic.js +4 -0
  7. package/dist/billing/importer.js +29 -10
  8. package/dist/billing/reconcile.js +122 -32
  9. package/dist/billing/scope.js +7 -0
  10. package/dist/budget/guard.js +33 -4
  11. package/dist/cli/allocCmd.js +73 -4
  12. package/dist/cli/askModelPrices.js +59 -0
  13. package/dist/cli/backupCmd.js +2 -1
  14. package/dist/cli/basisLines.js +45 -0
  15. package/dist/cli/billingCmd.js +68 -9
  16. package/dist/cli/capitalCmd.js +2 -1
  17. package/dist/cli/connectCmd.js +50 -9
  18. package/dist/cli/egressCmd.js +15 -3
  19. package/dist/cli/evidenceCmd.js +6 -3
  20. package/dist/cli/flags.js +68 -0
  21. package/dist/cli/homeCmd.js +117 -0
  22. package/dist/cli/importCmd.js +210 -54
  23. package/dist/cli/launchCmd.js +2 -1
  24. package/dist/cli/opsCmd.js +49 -2
  25. package/dist/cli/planCmd.js +181 -0
  26. package/dist/cli/pricingDeclareCmd.js +178 -0
  27. package/dist/cli/quotaCmd.js +190 -0
  28. package/dist/cli/runCmd.js +203 -51
  29. package/dist/cli/showCmd.js +37 -11
  30. package/dist/cli/teamCmd.js +5 -5
  31. package/dist/cli/ui.js +24 -8
  32. package/dist/cli/valueCmd.js +189 -14
  33. package/dist/cli.js +102 -21
  34. package/dist/config.js +95 -6
  35. package/dist/connect/antigravity.js +310 -0
  36. package/dist/connect/claudeCode.js +43 -3
  37. package/dist/connect/codex.js +38 -1
  38. package/dist/connect/importShared.js +58 -5
  39. package/dist/connect/opencode.js +37 -9
  40. package/dist/connect/protobuf.js +126 -0
  41. package/dist/connect/registry.js +46 -0
  42. package/dist/connect/repoRoot.js +112 -0
  43. package/dist/cost/basis.js +168 -0
  44. package/dist/cost/declarations.js +37 -0
  45. package/dist/cost/exactPricing.js +4 -1
  46. package/dist/cost/firstSight.js +98 -0
  47. package/dist/cost/overrides.js +230 -0
  48. package/dist/cost/pricing.js +228 -15
  49. package/dist/cost/reprice.js +130 -0
  50. package/dist/dashboard/claim-support.js +11 -8
  51. package/dist/dashboard/contracts.js +23 -3
  52. package/dist/dashboard/routes.js +980 -112
  53. package/dist/dashboard/sessions.js +345 -0
  54. package/dist/dashboard/settings.js +2 -1
  55. package/dist/dashboard/value-cache.js +105 -0
  56. package/dist/dashboard/web/app/components/claimInspector.js +6 -5
  57. package/dist/dashboard/web/app/components/drawer.js +105 -35
  58. package/dist/dashboard/web/app/components/ruleFields.js +144 -0
  59. package/dist/dashboard/web/app/components/spine.js +54 -24
  60. package/dist/dashboard/web/app/components/workflow.js +82 -0
  61. package/dist/dashboard/web/app/core/actions.js +734 -23
  62. package/dist/dashboard/web/app/core/api.js +37 -1
  63. package/dist/dashboard/web/app/core/chain.js +11 -4
  64. package/dist/dashboard/web/app/core/claimLayers.js +179 -51
  65. package/dist/dashboard/web/app/core/claimTypes.js +30 -0
  66. package/dist/dashboard/web/app/core/fmt.js +19 -2
  67. package/dist/dashboard/web/app/core/generated-contract.js +23 -3
  68. package/dist/dashboard/web/app/core/generated-payload-contract.js +1944 -465
  69. package/dist/dashboard/web/app/core/generated-types.js +1 -1
  70. package/dist/dashboard/web/app/core/griffin.js +10 -0
  71. package/dist/dashboard/web/app/core/jobs.js +12 -0
  72. package/dist/dashboard/web/app/core/period.js +35 -0
  73. package/dist/dashboard/web/app/core/periodWords.js +10 -0
  74. package/dist/dashboard/web/app/core/periods.js +32 -0
  75. package/dist/dashboard/web/app/core/pricingActions.js +264 -0
  76. package/dist/dashboard/web/app/core/privacyText.js +78 -0
  77. package/dist/dashboard/web/app/core/reconcileWords.js +63 -0
  78. package/dist/dashboard/web/app/core/registry.js +79 -46
  79. package/dist/dashboard/web/app/core/slug.js +9 -0
  80. package/dist/dashboard/web/app/core/theme.js +48 -0
  81. package/dist/dashboard/web/app/core/view.js +69 -0
  82. package/dist/dashboard/web/app/main.js +197 -15
  83. package/dist/dashboard/web/app/views/allocation.js +249 -61
  84. package/dist/dashboard/web/app/views/control.js +57 -16
  85. package/dist/dashboard/web/app/views/data.js +4 -4
  86. package/dist/dashboard/web/app/views/evidence.js +336 -87
  87. package/dist/dashboard/web/app/views/privacy.js +13 -0
  88. package/dist/dashboard/web/app/views/spend.js +30 -15
  89. package/dist/dashboard/web/app/views/statement.js +189 -0
  90. package/dist/dashboard/web/app/views/system.js +44 -4
  91. package/dist/dashboard/web/app/views/value.js +105 -59
  92. package/dist/dashboard/web/app/views/workbench.js +312 -0
  93. package/dist/dashboard/web/classic.html +16 -4
  94. package/dist/dashboard/web/index.html +1 -1
  95. package/dist/dashboard/web/styles/app.css +536 -23
  96. package/dist/demo/seed.js +153 -14
  97. package/dist/economics/ledger.js +1 -1
  98. package/dist/egress/policy.js +129 -5
  99. package/dist/export/allocationCsv.js +70 -0
  100. package/dist/export/billingCsv.js +6 -2
  101. package/dist/export/csv.js +10 -2
  102. package/dist/export/economic.js +6 -2
  103. package/dist/export/statementCsv.js +104 -0
  104. package/dist/git/correlate.js +86 -9
  105. package/dist/git/quality.js +107 -24
  106. package/dist/git/repoScope.js +207 -0
  107. package/dist/git/survivalStore.js +186 -0
  108. package/dist/guide.js +9 -4
  109. package/dist/plans/detect.js +147 -0
  110. package/dist/proxy/server.js +134 -14
  111. package/dist/proxy/usage.js +9 -4
  112. package/dist/quota/limits.js +138 -0
  113. package/dist/quota/statusline.js +68 -0
  114. package/dist/quota/view.js +122 -0
  115. package/dist/scan/knownApps.js +6 -2
  116. package/dist/scan/scan.js +9 -0
  117. package/dist/store/allocation.js +14 -0
  118. package/dist/store/db.js +259 -34
  119. package/dist/store/realization.js +78 -14
  120. package/dist/store/schema.js +59 -1
  121. package/dist/store/scopeEvidence.js +67 -0
  122. package/dist/util/pool.js +19 -0
  123. package/dist/util/time.js +7 -0
  124. package/dist/value/realization.js +321 -30
  125. package/dist/value/report.js +28 -8
  126. package/dist/value/selfReported.js +10 -9
  127. package/docs/ARCHITECTURE.md +8 -4
  128. package/docs/CAPABILITY-EVIDENCE-CONTRACT.md +33 -11
  129. package/docs/CLI-JSON-CONTRACT.md +38 -0
  130. package/docs/DATA-BOUNDARIES.md +66 -6
  131. package/docs/FAQ.md +43 -9
  132. package/docs/GETTING-STARTED.md +19 -12
  133. package/docs/GUIDE.md +81 -18
  134. package/docs/INTEGRATIONS.md +9 -9
  135. package/docs/METHODOLOGY.md +3 -2
  136. package/docs/NEUTRALITY.md +1 -1
  137. package/docs/PRIVACY.md +51 -0
  138. package/docs/README.md +1 -0
  139. package/docs/RELEASE-GATE.md +52 -7
  140. package/docs/THREAT-MODEL.md +2 -1
  141. package/package.json +3 -2
  142. package/pricing/models.json +1366 -60
@@ -2,7 +2,7 @@
2
2
  Segreant web GUI — the Night Vault system.
3
3
 
4
4
  Inherited from the incumbent dashboard rather than replaced: dark ledger
5
- ground, gold as the single accent, serif display, mono for anything that is a
5
+ ground, gold for the mark, money and caution, ember for what you act on, serif display, mono for anything that is a
6
6
  quantity or an identifier. What changes here is scope — this is a console for
7
7
  operating the product, not a page for reading five cards.
8
8
 
@@ -38,8 +38,26 @@
38
38
  the light theme has to be much darker to stay legible on paper. */
39
39
  --gold-text: #e8b33c;
40
40
  --gold-soft: rgba(232, 179, 60, 0.14);
41
+ /* The griffin's own gold, apart from the caution gold above. Beside the ember
42
+ accent a bright yellow read as a second, competing highlight; this antique
43
+ gold shares ember's warmth and still reads as gold (7.3:1 on the page).
44
+ Chosen from six candidates rendered in the live header (2026-10-16). */
45
+ --brand: #c9963a;
46
+ /* Text on a gold fill. Dark in both themes: the light theme's gold is a
47
+ mid-tone, and its paper-coloured --ink on it fell to about 3:1. */
48
+ --on-gold: #14161d;
49
+ /* Ember is what you act on: buttons, the current job, selection, focus and
50
+ links. Gold keeps three jobs: the griffin, money, and "look at this".
51
+ Orange (hue 25) sits beside the griffin's gold (hue 42) as one warm family
52
+ against the cool ink, without ever repainting the mark. */
53
+ --accent: #f0883e;
54
+ --accent-text: #f39a57;
55
+ --accent-soft: rgba(240, 136, 62, 0.14);
56
+ --on-accent: #14161d;
41
57
  --mint: #63c593;
42
- --coral: #e25d4a;
58
+ /* Waste red moved off orange's hue (8 -> 352) so "not kept" never reads
59
+ as "press here". */
60
+ --coral: #f0566a;
43
61
  --sky: #7ea6e0;
44
62
  --violet: #9ba4d6;
45
63
 
@@ -60,6 +78,16 @@
60
78
  single place to turn it off. */
61
79
  --t: 160ms;
62
80
  --ease: cubic-bezier(0.2, 0, 0, 1);
81
+ /* Scrollbars are part of the page, not the operating system's chrome: a
82
+ default grey bar inside a gold-on-ink table read as unfinished. */
83
+ --scroll-thumb: #2b3142;
84
+ --scroll-thumb-hover: #3b4357;
85
+ /* Chart series, in fixed order: the five costliest models, then the rest.
86
+ Checked for colour-blind separation against each theme's panel; identity
87
+ never rests on the hue alone (a legend and a readout name every one). */
88
+ --series-1: #3987e5; --series-2: #199e70; --series-3: #9085e9; --series-4: #d55181; --series-5: #008300;
89
+ --series-other: #5b6275;
90
+ color-scheme: dark;
63
91
  }
64
92
 
65
93
  :root[data-theme='light'] {
@@ -75,13 +103,29 @@
75
103
  --gold: #b8842a;
76
104
  --gold-text: #846013;
77
105
  --gold-soft: rgba(184, 132, 42, 0.12);
106
+ --brand: #b8842a;
107
+ --accent: #b04a14;
108
+ --accent-text: #a8440f;
109
+ --accent-soft: rgba(176, 74, 20, 0.10);
110
+ --on-accent: #ffffff;
78
111
  --mint: #206d4c;
79
- --coral: #a83625;
112
+ --coral: #b3243f;
80
113
  --sky: #3f68b0;
81
114
  --violet: #5c62a8;
82
115
  --shadow: 0 18px 48px rgba(60, 50, 30, 0.14);
116
+ --scroll-thumb: #cfc8b6;
117
+ --scroll-thumb-hover: #b9b19c;
118
+ --series-1: #2a78d6; --series-2: #1baf7a; --series-3: #6250d6; --series-4: #e87ba4; --series-5: #008300;
119
+ --series-other: #aaa595;
120
+ color-scheme: light;
83
121
  }
84
122
 
123
+ /* Compact: the same page with tighter spacing, for readers who live in it. */
124
+ :root[data-density='compact'] {
125
+ --s3: 9px; --s4: 12px; --s5: 15px; --s6: 18px; --s8: 24px; --s10: 28px; --s12: 34px;
126
+ }
127
+ :root[data-intros='off'] .view-head .view-plain { display: none; }
128
+
85
129
  @media (prefers-reduced-motion: reduce) {
86
130
  :root { --t: 0ms; }
87
131
  *, *::before, *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; transition-duration: 1ms !important; }
@@ -91,6 +135,16 @@
91
135
 
92
136
  html { overflow-x: clip; }
93
137
 
138
+ /* Every scrolling area (page, tables, drawers, code) inherits these. The
139
+ standard properties cover Chromium 121+ and Firefox; the -webkit- rules
140
+ cover older Chromium and Safari. */
141
+ html { scrollbar-color: var(--scroll-thumb) transparent; scrollbar-width: thin; }
142
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
143
+ ::-webkit-scrollbar-track { background: transparent; }
144
+ ::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box; }
145
+ ::-webkit-scrollbar-thumb:hover { background-color: var(--scroll-thumb-hover); }
146
+ ::-webkit-scrollbar-corner { background: transparent; }
147
+
94
148
  body {
95
149
  background: var(--ink);
96
150
  color: var(--text);
@@ -106,8 +160,11 @@ body {
106
160
 
107
161
  [hidden] { display: none !important; }
108
162
 
163
+ /* A heading focused for screen readers after a page change is not a control. */
164
+ h1[tabindex='-1']:focus { outline: none; }
165
+
109
166
  :focus-visible {
110
- outline: 2px solid var(--gold);
167
+ outline: 2px solid var(--accent);
111
168
  outline-offset: 2px;
112
169
  border-radius: 4px;
113
170
  }
@@ -132,10 +189,25 @@ body {
132
189
  }
133
190
 
134
191
  .brand { display: flex; align-items: center; gap: var(--s3); text-decoration: none; color: inherit; }
135
- .brand-mark { width: 26px; height: 30px; flex: none; filter: drop-shadow(0 2px 10px rgba(232, 179, 60, 0.3)); }
192
+ .brand-mark { width: 34px; height: 34px; flex: none; }
193
+ .firstrun-mark .brand-mark { width: 56px; height: 56px; }
136
194
  .brand-text { display: flex; flex-direction: column; line-height: 1.15; }
137
195
  .brand-name { font-family: var(--serif); font-weight: 500; font-size: 18px; letter-spacing: -0.01em; }
138
- .brand-tag { font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint); }
196
+ .brand-tag { font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint); white-space: nowrap; }
197
+ .brand, .register-switch, .topbar-demo { flex: none; }
198
+
199
+ /* Laptop widths: one row cannot hold the brand, eight jobs and the register
200
+ switch, so at 980 px the tag broke into three lines and the switch was cut
201
+ off. The jobs move to a second row instead; below 821 px the phone grid
202
+ further down takes over. */
203
+ @media (min-width: 821px) and (max-width: 1240px) {
204
+ .topbar { flex-wrap: wrap; row-gap: var(--s2); column-gap: var(--s4); padding: var(--s3) var(--s6); }
205
+ .register-switch { order: 1; margin-left: auto; }
206
+ /* A zero-height, full-width item: everything ordered after it starts row two. */
207
+ .topbar::after { content: ''; order: 2; flex-basis: 100%; height: 0; }
208
+ .ops { order: 3; margin-left: auto; }
209
+ .ops-util { order: 4; margin-left: 0; }
210
+ }
139
211
 
140
212
  .ops { display: flex; gap: var(--s1); margin-left: auto; }
141
213
  .op {
@@ -144,7 +216,12 @@ body {
144
216
  transition: color var(--t) var(--ease), background var(--t) var(--ease);
145
217
  }
146
218
  .op:hover { color: var(--text); background: var(--panel); }
147
- .op[aria-current='page'] { color: var(--text); background: var(--panel-2); }
219
+ .op[aria-current='page'] { color: var(--text); background: var(--panel-2); box-shadow: inset 0 -2px 0 var(--accent); }
220
+ /* Setup sits apart from the jobs: quieter, and after a hairline. */
221
+ .card-action { margin-left: auto; margin-right: var(--s3); padding: var(--s1) var(--s3); border-radius: var(--r-sm); font: inherit; font-size: 13px; cursor: pointer; color: var(--text); }
222
+ .card-action:hover { border-color: var(--accent); }
223
+ .ops-util { margin-left: 0; padding-left: var(--s3); border-left: 1px solid var(--line-soft); }
224
+ .op-util { color: var(--faint); font-size: 13.5px; }
148
225
 
149
226
  .register-switch { display: flex; gap: 2px; padding: 3px; background: var(--panel); border: 1px solid var(--line-soft); border-radius: var(--r-sm); }
150
227
  .register-switch button {
@@ -152,7 +229,7 @@ body {
152
229
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
153
230
  padding: 5px 10px; border-radius: 6px; transition: all var(--t) var(--ease);
154
231
  }
155
- .register-switch button[aria-pressed='true'] { background: var(--panel-2); color: var(--gold-text); }
232
+ .register-switch button[aria-pressed='true'] { background: var(--panel-2); color: var(--accent-text); }
156
233
 
157
234
  .sheet { max-width: 1180px; margin: 0 auto; padding: 0 var(--s8); width: 100%; }
158
235
 
@@ -163,6 +240,19 @@ body {
163
240
  font-size: 12.5px; color: var(--faint);
164
241
  }
165
242
  .shellfoot a { color: var(--muted); }
243
+ .shellfoot-links { display: flex; gap: var(--s5); }
244
+
245
+ /* The privacy statement: prose, so it reads at a book's line length rather
246
+ than the dashboard's full width. */
247
+ .privacy { max-width: 68ch; }
248
+ /* The promise itself: full text colour, and not a page explanation, so hiding
249
+ explanations in "What you see" never hides it. */
250
+ .privacy-lead { color: var(--text); font-size: 15px; line-height: 1.6; margin-top: var(--s2); }
251
+ .privacy-section p, .privacy-section li { line-height: 1.6; color: var(--text); }
252
+ .privacy-section p + p { margin-top: var(--s3); }
253
+ .privacy-section ul { margin: var(--s3) 0 0; padding-left: var(--s5); display: grid; gap: var(--s2); }
254
+ /* A command inside running prose: marked, not boxed, so lines keep their spacing. */
255
+ .inline-code { font-family: var(--mono); font-size: 0.88em; color: var(--text); background: var(--line-soft); border-radius: 4px; padding: 0.08em 0.36em; white-space: nowrap; }
166
256
 
167
257
  /* ---------- The ledger spine: the thesis, as the layout ------------------ */
168
258
 
@@ -205,13 +295,13 @@ body {
205
295
  color: var(--faint); cursor: pointer;
206
296
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease);
207
297
  }
208
- .band-inspect:hover { color: var(--gold-text); border-bottom-color: var(--gold-text); }
209
- .band-active { border-top-color: var(--gold); background: var(--panel); }
298
+ .band-inspect:hover { color: var(--accent-text); border-bottom-color: var(--accent-text); }
299
+ .band-active { border-top-color: var(--accent); background: var(--panel); }
210
300
 
211
301
  /* An unestablished layer is drawn as an open socket, not a filled card. The
212
302
  dashed top edge is the visual half of the same statement its words make. */
213
303
  .band-open { border-top-style: dashed; border-top-color: var(--line); }
214
- .band-open.band-active { border-top-color: var(--gold-text); }
304
+ .band-open.band-active { border-top-color: var(--accent-text); }
215
305
 
216
306
  .band-label {
217
307
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em;
@@ -225,8 +315,16 @@ body {
225
315
  font-family: var(--sans); font-size: 15px; letter-spacing: 0;
226
316
  color: var(--faint); font-style: italic;
227
317
  }
318
+ .band-period { letter-spacing: 0.06em; text-transform: none; }
319
+ .band-pending .band-value { opacity: 0.7; }
228
320
  .band-basis { font-size: 12.5px; color: var(--muted); max-width: 34ch; }
229
- .band-next { font-size: 12px; color: var(--gold-text); max-width: 34ch; }
321
+ .band-next { font-size: 12px; color: var(--accent-text); max-width: 34ch; }
322
+ /* An estimate: present, but never at a measured figure's size or brightness. */
323
+ .band-estimate { display: flex; flex-direction: column; gap: 4px; font-size: clamp(16px, 1.45vw, 20px); color: var(--muted); }
324
+ .band-chip {
325
+ align-self: flex-start; font-family: var(--sans); font-size: 11.5px; letter-spacing: 0.01em;
326
+ color: var(--sky); border: 1px solid currentColor; border-radius: 999px; padding: 1px 8px;
327
+ }
230
328
 
231
329
  /* The separator IS the thesis: metered != billed != allocated != realized.
232
330
  Four claims, four evidence standards, never interchangeable. */
@@ -274,7 +372,7 @@ body {
274
372
  cursor: pointer; transition: all var(--t) var(--ease);
275
373
  }
276
374
  .chip:hover { color: var(--text); border-color: var(--line); }
277
- .chip[aria-pressed='true'] { color: var(--ink); background: var(--gold); border-color: var(--gold); }
375
+ .chip[aria-pressed='true'] { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
278
376
 
279
377
  .grid { display: grid; gap: var(--s4); grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
280
378
  .grid-2 { display: grid; gap: var(--s4); grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
@@ -313,8 +411,12 @@ body {
313
411
  padding: 3px 8px; border-radius: 999px; border: 1px solid currentColor;
314
412
  }
315
413
  .pill-demo { color: var(--violet); }
414
+ .topbar-demo { flex-shrink: 0; }
316
415
  .pill-estimate { color: var(--sky); }
317
416
  .pill-unverified { color: var(--gold-text); }
417
+ /* Nothing to show yet is a state, not a warning: neutral and dashed, so the
418
+ caution gold stays rare enough to mean "look at this". */
419
+ .pill-empty { color: var(--muted); border-style: dashed; }
318
420
  .pill-ok { color: var(--mint); }
319
421
  .pill-warn { color: var(--coral); }
320
422
 
@@ -329,7 +431,14 @@ th {
329
431
  }
330
432
  td { padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line-soft); vertical-align: top; }
331
433
  tr:last-child td { border-bottom: 0; }
434
+ /* Phone tables: 12px a side on every cell cost a four-column table its last
435
+ column at 375px (Saved splits measured 353px in a 343px wrapper). */
436
+ @media (max-width: 480px) { th, td { padding-left: var(--s2); padding-right: var(--s2); } }
332
437
  .num { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
438
+ /* A name the user chose (project, model, cost centre) may be one unbroken
439
+ token. Without a break point it sets the column's minimum width and pushes
440
+ the amount beside it off a phone screen, so names break anywhere. */
441
+ .name-cell { overflow-wrap: anywhere; min-width: 7rem; }
333
442
 
334
443
  /* ---------- Actions ----------------------------------------------------- */
335
444
 
@@ -407,6 +516,8 @@ tr:last-child td { border-bottom: 0; }
407
516
 
408
517
  .drawer-h3 { font-family: var(--mono); font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase; color: var(--faint); margin-bottom: var(--s3); display: block; }
409
518
  .drawer-summary { margin-bottom: var(--s3); }
519
+ /* A preview being recomputed for edited fields: still readable, visibly not current. */
520
+ .drawer-preview.is-refreshing > :not(h3) { opacity: 0.5; transition: opacity 120ms; }
410
521
  .drawer-muted { color: var(--faint); }
411
522
  .drawer-error { color: var(--coral); }
412
523
  .drawer-done { color: var(--mint); }
@@ -429,7 +540,7 @@ tr:last-child td { border-bottom: 0; }
429
540
  border: 1px solid var(--line-soft); background: none; color: var(--muted);
430
541
  padding: 0 var(--s3); border-radius: var(--r-sm); cursor: pointer; transition: all var(--t) var(--ease);
431
542
  }
432
- .cmd-copy:hover { color: var(--gold-text); border-color: var(--gold); }
543
+ .cmd-copy:hover { color: var(--accent-text); border-color: var(--accent); }
433
544
 
434
545
  .drawer-input {
435
546
  width: 100%; font-family: var(--mono); font-size: 13px;
@@ -437,8 +548,13 @@ tr:last-child td { border-bottom: 0; }
437
548
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: var(--s2) var(--s3);
438
549
  }
439
550
 
551
+ .pricing-model { padding-block: var(--s3); border-bottom: 1px solid var(--line-soft); }
552
+ .pricing-model .drawer-h3 { overflow-wrap: anywhere; }
553
+
440
554
  .drawer-foot { margin-top: auto; padding-top: var(--s4); border-top: 1px solid var(--line-soft); display: flex; flex-direction: column; gap: var(--s3); }
441
555
  .drawer-actions { display: flex; gap: var(--s2); justify-content: flex-end; }
556
+ /* Why Apply is unavailable: read before the buttons, at body size, not as fine print. */
557
+ .drawer-blocked { font-size: 13px; color: var(--muted); text-align: right; text-wrap: pretty; }
442
558
 
443
559
  .btn-ghost, .btn-commit {
444
560
  font: inherit; font-size: 13px; padding: var(--s2) var(--s5);
@@ -447,11 +563,11 @@ tr:last-child td { border-bottom: 0; }
447
563
  .btn-ghost { background: none; border: 1px solid var(--line); color: var(--muted); }
448
564
  .btn-ghost:hover { color: var(--text); border-color: var(--muted); }
449
565
 
450
- .btn-commit { border: 1px solid var(--gold); background: var(--gold); color: #14161d; font-weight: 500; }
566
+ .btn-commit { border: 1px solid var(--accent); background: var(--accent); color: var(--on-accent); font-weight: 500; }
451
567
  .btn-commit:hover:not(:disabled) { filter: brightness(1.08); }
452
568
  .btn-commit:disabled { opacity: 0.4; cursor: not-allowed; }
453
569
  .btn-commit.tone-danger { background: var(--coral); border-color: var(--coral); color: #fff; }
454
- .btn-commit.tone-warn { background: var(--gold); border-color: var(--gold); }
570
+ .btn-commit.tone-warn { background: var(--gold); border-color: var(--gold); color: var(--on-gold); }
455
571
 
456
572
  body.drawer-open { overflow: hidden; }
457
573
  body.claim-open { overflow: hidden; }
@@ -539,7 +655,7 @@ body.claim-open { overflow: hidden; }
539
655
  .claim-missing { border-left-color: var(--coral); }
540
656
  .claim-missing ul { color: var(--text); }
541
657
  .claim-complete { font-size: 13px; color: var(--mint); }
542
- .claim-next { font-size: 13px; color: var(--gold-text); }
658
+ .claim-next { font-size: 13px; color: var(--accent-text); }
543
659
 
544
660
  @media (max-width: 900px) {
545
661
  .claim-panel { width: 100%; padding: var(--s5); }
@@ -560,7 +676,7 @@ body.claim-open { overflow: hidden; }
560
676
  border: 1px solid var(--line); background: var(--panel); color: var(--text);
561
677
  font: inherit; cursor: pointer; transition: all var(--t) var(--ease);
562
678
  }
563
- .choice:hover { border-color: var(--gold); }
679
+ .choice:hover { border-color: var(--accent); }
564
680
  .choice strong { display: block; font-size: 15px; margin-bottom: var(--s2); }
565
681
  .choice span { color: var(--muted); font-size: 13px; }
566
682
  .firstrun .fine { color: var(--faint); font-size: 13px; margin-top: var(--s6); max-width: 58ch; }
@@ -578,6 +694,12 @@ body.claim-open { overflow: hidden; }
578
694
  padding: var(--s3) var(--s4); margin-bottom: var(--s5); background: var(--panel-2);
579
695
  }
580
696
  .banner-demo { border-color: var(--violet); }
697
+ .banner-stale { border-color: var(--coral); align-items: center; justify-content: space-between; }
698
+ .banner.banner-stale strong { color: var(--coral); }
699
+ .banner-retry { flex: none; padding: var(--s2) var(--s4); border-radius: var(--r-sm); font: inherit; font-size: 13px; color: var(--text); cursor: pointer; }
700
+ .banner-retry:hover { border-color: var(--muted); }
701
+ /* Figures read before the server stopped: still legible, visibly not current. */
702
+ .sheet.is-stale { opacity: 0.55; filter: saturate(0.35); transition: opacity var(--t) var(--ease); }
581
703
  .banner p { font-size: 13px; color: var(--muted); max-width: 70ch; }
582
704
  .banner strong { color: var(--text); }
583
705
 
@@ -601,7 +723,7 @@ body.claim-open { overflow: hidden; }
601
723
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease);
602
724
  }
603
725
  .axis:hover { color: var(--text); }
604
- .axis[aria-pressed='true'] { color: var(--text); border-bottom-color: var(--gold); }
726
+ .axis[aria-pressed='true'] { color: var(--text); border-bottom-color: var(--accent); }
605
727
  .axisbar-note { font-size: 12.5px; color: var(--faint); margin-left: auto; }
606
728
 
607
729
  /* ---------- Ledger -------------------------------------------------------- */
@@ -628,6 +750,19 @@ body.claim-open { overflow: hidden; }
628
750
  border-bottom: 1px solid var(--line-soft);
629
751
  }
630
752
 
753
+ .ledger-gates .ledger-head, .ledger-gates .ledger-row { grid-template-columns: minmax(0, 1fr) 6rem 6rem 7rem; }
754
+ .ledger-gates .ledger-key { font-family: var(--sans); font-size: 14px; }
755
+ .ledger-gates .ledger-bar { background: var(--mint); }
756
+ .gate-pass { color: var(--mint); }
757
+ .gate-fail { color: var(--coral); }
758
+ .gate-unchecked { color: var(--faint); }
759
+ @media (max-width: 820px) {
760
+ /* Three bare numbers mean nothing without their headers, so this table
761
+ keeps its header row where the other ledgers fold it away. */
762
+ .ledger-gates .ledger-head { position: static; width: auto; height: auto; margin: 0; padding: var(--s3) 0; overflow: visible; clip: auto; white-space: normal; border-bottom: 1px solid var(--line-soft); }
763
+ .ledger-gates .ledger-head, .ledger-gates .ledger-row { grid-template-columns: minmax(0, 1fr) 3.5rem 3.5rem 4.5rem; grid-template-areas: none; gap: var(--s2); }
764
+ }
765
+
631
766
  .ledger-key {
632
767
  font-family: var(--mono); font-size: 13px;
633
768
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
@@ -665,6 +800,9 @@ body.claim-open { overflow: hidden; }
665
800
  /* Governance alerts. Severity is carried by a word and a rule, never by colour
666
801
  alone -- the same standard the pills follow. */
667
802
  .alerts { display: flex; flex-direction: column; gap: var(--s3); margin-bottom: var(--s5); }
803
+ /* Sits above the alert list and the cap card in a plain block; without its own
804
+ margin its border touched the cap card's. */
805
+ .alert-coverage { margin-bottom: var(--s5); }
668
806
  .alert {
669
807
  border: 1px solid var(--line); border-left: 2px solid var(--line);
670
808
  border-radius: var(--r); padding: var(--s3) var(--s4); background: var(--panel);
@@ -713,6 +851,9 @@ body.claim-open { overflow: hidden; }
713
851
  .fact-key {
714
852
  display: flex; align-items: center; gap: var(--s2);
715
853
  font-size: 13px; color: var(--text); min-width: 0;
854
+ /* Keys quote names the user chose ("Spend where project <name>"); one
855
+ unbroken name otherwise sets the row's minimum width and widens the page. */
856
+ overflow-wrap: anywhere;
716
857
  }
717
858
  .fact-val {
718
859
  font-family: var(--mono); font-size: 12px; color: var(--faint);
@@ -735,7 +876,7 @@ body.claim-open { overflow: hidden; }
735
876
  width: 6px; height: 6px; border-radius: 50%; flex: none;
736
877
  border: 1px solid var(--line); background: transparent;
737
878
  }
738
- .dot-on { background: var(--gold); border-color: var(--gold); }
879
+ .dot-on { background: var(--accent); border-color: var(--accent); }
739
880
 
740
881
  /* Spend against a cap. Deliberately thin: it is a reading, not a gauge to watch. */
741
882
  .meter {
@@ -771,11 +912,21 @@ body.claim-open { overflow: hidden; }
771
912
  color: var(--faint); padding: var(--s2) 0;
772
913
  }
773
914
  .more summary:hover { color: var(--text); }
774
- .more summary:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
915
+ .more summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
775
916
 
776
917
  @media (max-width: 820px) {
777
- .topbar { padding: var(--s3) var(--s4); gap: var(--s3); flex-wrap: wrap; }
778
- .ops { margin-left: 0; order: 3; width: 100%; overflow-x: auto; }
918
+ /* On a phone the header wraps to several rows, and a sticky one that tall
919
+ would hold a quarter of the screen. A sideways-scrolling job strip hid
920
+ Value past the right edge with nothing saying it was there, so every job
921
+ sits in a grid instead, each a full-size target, all visible at once. */
922
+ .topbar { position: static; padding: var(--s3) var(--s4); gap: var(--s3); flex-wrap: wrap; }
923
+ .ops { margin-left: 0; order: 3; width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s1); }
924
+ .ops-util { order: 4; width: 100%; grid-template-columns: repeat(2, minmax(0, 1fr)); border-left: 0; padding-left: 0; }
925
+ .op { min-height: 44px; text-align: center; background: var(--panel); }
926
+ /* Every grid cell has a fill here, so the current page needs more than a
927
+ slightly different fill to stand out. */
928
+ .op[aria-current='page'] { color: var(--text); background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
929
+ .register-switch button { min-height: 38px; }
779
930
  .register-switch { margin-left: auto; }
780
931
  .sheet { padding: 0 var(--s4); }
781
932
  .shellfoot { padding: var(--s5) var(--s4) var(--s8); }
@@ -821,3 +972,365 @@ body.claim-open { overflow: hidden; }
821
972
  .kernel-link { background: none; border: 0; padding: 0; color: var(--sky); font-family: var(--mono); font-size: 12px; cursor: pointer; text-align: left; word-break: break-all; }
822
973
  .kernel-link:hover, .kernel-link:focus-visible { text-decoration: underline; }
823
974
  .kernel-record { font-family: var(--mono); font-size: 11px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; max-height: 24rem; overflow: auto; padding: var(--s3); border: 1px solid var(--line); border-radius: 6px; margin: var(--s2) 0 0; }
975
+
976
+ /* ---------- Workflow steps (Allocate) ------------------------------------- */
977
+ /* A job done in order: each step says what it is for, shows what exists, and
978
+ carries the form that adds to it. The number is the order the engine needs,
979
+ not decoration. */
980
+
981
+ .step { margin-top: var(--s8); }
982
+ .step-title {
983
+ display: flex; align-items: center; gap: var(--s3);
984
+ font-family: var(--serif); font-size: 19px; font-weight: 500; color: var(--text);
985
+ }
986
+ .step-n {
987
+ display: inline-grid; place-items: center; flex: none;
988
+ width: 26px; height: 26px; border-radius: 50%;
989
+ border: 1px solid var(--accent); color: var(--accent-text);
990
+ font-family: var(--mono); font-size: 12px;
991
+ }
992
+ .step-lede { color: var(--muted); max-width: 70ch; margin: var(--s1) 0 var(--s3) calc(26px + var(--s3)); }
993
+ .step > .facts, .step > .inline-form, .step > .run-panel, .step > p.drawer-muted, .step > .form-hint, .step > .drawer-warning, .step > .step-aside { margin-left: calc(26px + var(--s3)); }
994
+ .step-aside { margin-top: var(--s3); max-width: 420px; }
995
+
996
+ .basis-strip .card-head { flex-wrap: wrap; gap: var(--s2); }
997
+ .basis-strip .card-title { margin-right: auto; }
998
+
999
+ .fact-sub { color: var(--faint); font-size: 12px; margin-left: var(--s2); }
1000
+ .fact-val { display: inline-flex; align-items: baseline; gap: var(--s3); }
1001
+ .link-btn {
1002
+ background: none; border: 0; padding: 0; font: inherit; font-family: var(--mono); font-size: 12px;
1003
+ color: var(--muted); text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
1004
+ }
1005
+ .link-btn:hover, .link-btn:focus-visible { color: var(--coral); }
1006
+
1007
+ .inline-form {
1008
+ margin-top: var(--s3); padding: var(--s4); border: 1px solid var(--line-soft);
1009
+ border-radius: var(--r-sm); background: var(--panel); display: grid; gap: var(--s3);
1010
+ }
1011
+ .form-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s3); }
1012
+ .form-field { display: grid; gap: var(--s1); flex: 1 1 200px; min-width: 0; }
1013
+ .form-field label, .shares legend { font-size: 12px; color: var(--muted); }
1014
+ .form-hint { font-size: 12.5px; color: var(--faint); min-height: 1em; }
1015
+ .form-hint:empty { display: none; }
1016
+ .form-actions { display: flex; justify-content: flex-end; gap: var(--s2); align-items: center; }
1017
+ select.drawer-input { font-family: inherit; }
1018
+
1019
+ .shares { border: 0; padding: 0; margin: 0; display: grid; gap: var(--s2); }
1020
+ .share { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
1021
+ .share .drawer-input { width: 8rem; text-align: right; }
1022
+
1023
+ .run-panel { display: grid; gap: var(--s4); }
1024
+ .run-row .form-field { flex: 0 1 300px; }
1025
+ /* Allocate: the rule form's further conditions stay folded until wanted, so the
1026
+ common rule (one project to one centre) is still one row. */
1027
+ .rule-more { display: grid; gap: var(--s3); }
1028
+ .rule-more > summary { cursor: pointer; list-style: none; min-height: 44px; display: flex; align-items: center; font-size: 13px; color: var(--muted); }
1029
+ .rule-more > summary::-webkit-details-marker { display: none; }
1030
+ .rule-more > summary::before { content: '\25B8'; margin-right: var(--s2); }
1031
+ .rule-more[open] > summary::before { content: '\25BE'; }
1032
+ .rule-more > summary:hover { color: var(--text); }
1033
+ .rule-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1034
+ .rule-more-row .form-field { flex: 1 1 150px; }
1035
+ /* The rule form inside a drawer: the same fields, spaced as blocks, since the
1036
+ drawer has no inline-form grid around them. */
1037
+ .rule-drawer-fields { display: grid; gap: var(--s4); margin-top: var(--s3); }
1038
+ .run-dates { flex: 1 1 100%; }
1039
+ .run-dates .form-field { flex: 0 1 200px; }
1040
+ .run-result { display: grid; gap: var(--s3); padding: var(--s4); border: 1px solid var(--line-soft); border-radius: var(--r-sm); background: var(--panel); }
1041
+ .row-unalloc td { color: var(--gold-text); }
1042
+ .row-unalloc .fact-sub { display: block; margin: 2px 0 0; }
1043
+ tfoot td { border-top: 1px solid var(--line); font-weight: 500; }
1044
+
1045
+ .statement-kept { display: grid; gap: var(--s1); margin: var(--s2) 0; }
1046
+ .statement-kept-row { display: flex; justify-content: space-between; gap: var(--s4); font-size: 14px; }
1047
+ .statement-kept-val { font-family: var(--mono); font-size: 13px; color: var(--text); white-space: nowrap; }
1048
+
1049
+ .view-prefs { margin-top: var(--s5); }
1050
+ .view-prefs .pref-group { border: 0; padding: 0; margin: var(--s4) 0 0; display: grid; gap: var(--s2); }
1051
+ .view-prefs legend { font-size: 12px; color: var(--muted); padding: 0; margin-bottom: var(--s1); }
1052
+ .pref { display: flex; align-items: center; gap: var(--s2); font-size: 14px; cursor: pointer; }
1053
+ .pref input { accent-color: var(--accent); width: 16px; height: 16px; }
1054
+ .pref-links { display: flex; flex-wrap: wrap; gap: var(--s3); }
1055
+ .pref-links a { color: var(--accent-text); font-size: 13px; }
1056
+
1057
+ /* The page-level result of a save: fixed in view, because the form that caused
1058
+ it may be far down a long page. Errors stay; confirmations clear. */
1059
+ .page-notice {
1060
+ position: fixed; left: 50%; bottom: calc(var(--s6) + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);
1061
+ z-index: 40; max-width: min(640px, calc(100vw - 2 * var(--s4)));
1062
+ padding: var(--s3) var(--s5); border-radius: var(--r-sm);
1063
+ background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
1064
+ box-shadow: 0 8px 28px rgb(0 0 0 / 0.28);
1065
+ width: max-content; overflow-wrap: anywhere;
1066
+ }
1067
+ .page-notice.drawer-error { border-color: var(--coral); color: var(--coral); }
1068
+ /* Idle: present for screen readers, invisible on screen. */
1069
+ .page-notice:empty { padding: 0; border: 0; box-shadow: none; background: none; }
1070
+
1071
+ /* A step the ledger already satisfies: its number becomes a check in mint. */
1072
+ .step-done .step-n { border-color: var(--mint); color: var(--mint); }
1073
+
1074
+ /* Label and value pairs in a preview: what will be written, before it is. */
1075
+ .preview-rows { display: grid; gap: var(--s1); margin: 0; }
1076
+ .preview-row { display: grid; grid-template-columns: minmax(9rem, 14rem) minmax(0, 1fr); gap: var(--s3); padding: var(--s1) 0; border-bottom: 1px solid var(--line-soft); }
1077
+ .preview-row:last-child { border-bottom: 0; }
1078
+ .preview-row dt { color: var(--muted); font-size: 13px; }
1079
+ .preview-row dd { margin: 0; font-size: 13px; overflow-wrap: anywhere; }
1080
+
1081
+ .fact-stack { flex-direction: column; align-items: stretch; gap: var(--s2); }
1082
+ .fact-line { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4); }
1083
+ .fact-stack .fact-sub { display: block; margin: 2px 0 0; }
1084
+ .fact-stack .fact-key { flex-direction: column; align-items: flex-start; gap: 0; }
1085
+
1086
+ .close-details { margin-top: var(--s10); }
1087
+ .close-details > summary { cursor: pointer; list-style: revert; }
1088
+ .close-details[open] > summary { margin-bottom: var(--s2); }
1089
+
1090
+ .sr-only {
1091
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
1092
+ overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
1093
+ }
1094
+
1095
+ @media (max-width: 820px) {
1096
+ .step-lede, .step > .facts, .step > .inline-form, .step > .run-panel, .step > p.drawer-muted { margin-left: 0; }
1097
+ .btn-commit, .btn-ghost { min-height: 44px; }
1098
+ .form-row > .btn-commit, .form-row > .btn-ghost, .form-actions > .btn-commit { width: 100%; }
1099
+ .preview-row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
1100
+ .fact-line { flex-direction: column; gap: var(--s1); }
1101
+ .step > .facts, .step > .form-hint { margin-left: 0; }
1102
+ }
1103
+
1104
+ /* ---------- Statement ------------------------------------------------------ */
1105
+ .month-bar { display: flex; align-items: center; gap: var(--s3); margin: var(--s4) 0 var(--s5); flex-wrap: wrap; }
1106
+ .month-bar .btn-ghost { padding: var(--s1) var(--s3); font-size: 16px; line-height: 1; }
1107
+ .month-label { font-family: var(--serif); font-size: 20px; min-width: 10ch; text-align: center; }
1108
+ .month-state { color: var(--muted); font-size: 13px; }
1109
+ .appearance { margin-top: var(--s5); display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s5); }
1110
+ .appearance .card-head { margin: 0; }
1111
+ .appearance .basis { margin: 0; }
1112
+ .theme-switch button { text-transform: none; letter-spacing: 0; font-family: var(--sans); font-size: 13px; min-height: 32px; }
1113
+ .statement-download { margin-left: auto; color: var(--accent-text); font-size: 14px; padding: var(--s2) 0; }
1114
+ .statement-download:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1115
+ /* One recorded split's CSV, in the Saved splits table: the same accent link, sized for a thumb on phones. */
1116
+ .run-period { display: block; }
1117
+ .run-download { display: inline-block; margin-top: 2px; color: var(--accent-text); font-size: 12px; white-space: nowrap; }
1118
+ .run-download:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1119
+ @media (max-width: 820px) { .run-download { display: inline-flex; align-items: center; min-height: 44px; margin-top: 0; } }
1120
+ @media (max-width: 820px) { .statement-download { margin-left: 0; width: 100%; min-height: 44px; display: flex; align-items: center; } }
1121
+ .statement-grid { display: grid; gap: var(--s4); grid-template-columns: repeat(2, minmax(0, 1fr)); }
1122
+ @media (max-width: 820px) { .statement-grid { grid-template-columns: minmax(0, 1fr); } }
1123
+ .statement-card .pill { white-space: nowrap; }
1124
+ .statement-card { display: flex; flex-direction: column; gap: var(--s2); }
1125
+ .statement-card .stat { margin: var(--s1) 0; }
1126
+ .statement-none { color: var(--muted); }
1127
+ .statement-run { display: grid; gap: 2px; padding: var(--s2) 0; border-bottom: 1px solid var(--line-soft); }
1128
+ .statement-run:last-of-type { border-bottom: 0; }
1129
+ .statement-shares { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
1130
+ .statement-shares li { display: flex; justify-content: space-between; gap: var(--s3); font-size: 14px; }
1131
+ .statement-link { margin-top: auto; padding-top: var(--s2); color: var(--accent-text); font-size: 13px; text-underline-offset: 3px; }
1132
+ .statement-rule { margin-top: var(--s4); max-width: 80ch; }
1133
+
1134
+ /* ---------- Folds (long evidence, closed by default) ------------------------ */
1135
+ .fold { margin-top: var(--s3); border-top: 1px solid var(--line-soft); }
1136
+ .fold:last-of-type { border-bottom: 1px solid var(--line-soft); }
1137
+ .fold-summary, .parity-summary { cursor: pointer; padding: var(--s3) 0; list-style: revert; color: var(--text); font-size: 14px; }
1138
+ .fold-summary:hover, .parity-summary:hover { color: var(--accent-text); }
1139
+ .fold[open] > .fold-summary { margin-bottom: var(--s2); }
1140
+ .fold-head { margin-top: var(--s10); }
1141
+ .parity-summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s4); }
1142
+ .parity-summary .section-title { margin: 0; }
1143
+ /* A flex summary loses the native disclosure triangle; without one, nothing says
1144
+ this line opens. */
1145
+ .parity-summary { list-style: none; }
1146
+ .parity-summary::-webkit-details-marker { display: none; }
1147
+ .parity-summary::before { content: '\25B8'; color: var(--muted); }
1148
+ .parity-details[open] > .parity-summary::before { content: '\25BE'; }
1149
+ /* Close: an adopted export says it is in use rather than offering itself again. */
1150
+ .in-use { font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: nowrap; }
1151
+ /* Close: each recorded comparison opens to what was recorded. The summary is a
1152
+ flex row, so it draws its own disclosure marker, as the parity table does. */
1153
+ .recorded-run > summary { cursor: pointer; list-style: none; min-height: 44px; display: flex; align-items: baseline; padding: var(--s2) 0; }
1154
+ .recorded-run > summary::-webkit-details-marker { display: none; }
1155
+ .recorded-run > summary .fact-line { flex: 1; min-width: 0; }
1156
+ .recorded-run > summary::before { content: '\25B8'; color: var(--muted); margin-right: var(--s2); }
1157
+ .recorded-run[open] > summary::before { content: '\25BE'; }
1158
+ .recorded-run > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1159
+ .recorded-run[open] > summary { margin-bottom: var(--s2); }
1160
+ .declared-scope { margin-bottom: var(--s3); }
1161
+ .parity-counts { color: var(--muted); font-size: 13px; }
1162
+ /* The capability and its plain description are what this table is for; the
1163
+ command column had taken the width and wrapped the descriptions a word or
1164
+ two per line. Status tags stay on one line. */
1165
+ .parity-table td:first-child { width: 44%; min-width: 260px; }
1166
+ .parity-table .tag { white-space: nowrap; }
1167
+ /* Opens the capability's drawer. An action, so it takes the action colour, not
1168
+ the muted link-btn grey (whose coral hover means "archive"), and a 24px target. */
1169
+ .parity-table .parity-open {
1170
+ display: inline-flex; align-items: center; min-height: 24px; margin-top: var(--s1);
1171
+ color: var(--accent-text);
1172
+ }
1173
+ .parity-table .parity-open:hover, .parity-table .parity-open:focus-visible { color: var(--text); }
1174
+ .parity-table td:last-child code { white-space: nowrap; }
1175
+
1176
+ /* ---------- Workbench (Spend) -------------------------------------------- */
1177
+
1178
+ /* Dense by design: the reader lives on this page. Ember marks what can be
1179
+ chosen; gold stays for the brand and for "look at this". */
1180
+ .wb { margin-bottom: var(--s8); }
1181
+ .wb-body { display: flex; flex-direction: column; gap: var(--s4); }
1182
+ .wb-pair { display: grid; gap: var(--s4); grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); align-items: start; }
1183
+ .wb-pair-sessions { grid-template-columns: minmax(0, 1fr) minmax(300px, 340px); }
1184
+ @media (max-width: 1180px) { .wb-pair, .wb-pair-sessions { grid-template-columns: minmax(0, 1fr); } }
1185
+ .wb-head-note { font-size: 12px; color: var(--faint); }
1186
+
1187
+ .wb-tiles {
1188
+ display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
1189
+ background: var(--panel); border: 1px solid var(--line-soft); border-radius: var(--r);
1190
+ }
1191
+ .wb-tile { display: flex; flex-direction: column; gap: 3px; padding: var(--s3) var(--s4); min-width: 0; border-left: 1px solid var(--line-soft); }
1192
+ .wb-tile:first-child { border-left: 0; }
1193
+ .wb-tile-label { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); }
1194
+ .wb-tile-value { font-family: var(--mono); font-size: 20px; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; color: var(--text); }
1195
+ .wb-tile-sub { font-size: 11.5px; color: var(--muted); overflow-wrap: anywhere; }
1196
+ .wb-tile-caution .wb-tile-value { color: var(--gold-text); }
1197
+ @media (max-width: 1000px) {
1198
+ .wb-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
1199
+ .wb-tile:nth-child(3n + 1) { border-left: 0; }
1200
+ .wb-tile:nth-child(n + 4) { border-top: 1px solid var(--line-soft); }
1201
+ }
1202
+ @media (max-width: 560px) {
1203
+ .wb-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1204
+ .wb-tile { border-left: 0; border-top: 1px solid var(--line-soft); }
1205
+ .wb-tile:nth-child(even) { border-left: 1px solid var(--line-soft); }
1206
+ .wb-tile:nth-child(-n + 2) { border-top: 0; }
1207
+ }
1208
+
1209
+ /* The chart. HTML rather than SVG, so labels stay crisp at any width. One
1210
+ scale: every gridline is a value the axis really reaches. */
1211
+ .wb-legend { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin-bottom: var(--s2); }
1212
+ .wb-legend-item { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
1213
+ .wb-legend-value { color: var(--text); }
1214
+ .wb-swatch { width: 9px; height: 9px; border-radius: 2px; flex: none; display: inline-block; }
1215
+ .wb-readout { font-size: 12.5px; color: var(--muted); min-height: 3em; margin-bottom: var(--s3); font-variant-numeric: tabular-nums; }
1216
+ .wb-plot { display: grid; grid-template-columns: 3.6rem minmax(0, 1fr); height: 210px; padding-bottom: 20px; }
1217
+ .wb-yaxis { position: relative; }
1218
+ .wb-ytick {
1219
+ position: absolute; right: var(--s2); transform: translateY(50%);
1220
+ font-family: var(--mono); font-size: 10.5px; color: var(--faint); font-variant-numeric: tabular-nums; white-space: nowrap;
1221
+ }
1222
+ .wb-bars {
1223
+ position: relative; display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr));
1224
+ gap: clamp(1px, 0.4vw, 5px); align-items: end; border-bottom: 1px solid var(--line);
1225
+ }
1226
+ .wb-grid { position: absolute; left: 0; right: 0; height: 0; border-top: 1px dashed var(--line-soft); pointer-events: none; }
1227
+ .wb-bar {
1228
+ position: relative; height: 100%; display: flex; flex-direction: column; justify-content: flex-end;
1229
+ background: none; border: 0; padding: 0; cursor: pointer; min-width: 0;
1230
+ }
1231
+ .wb-bar:disabled { cursor: default; }
1232
+ .wb-bar:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
1233
+ .wb-stack { display: flex; flex-direction: column; width: 100%; max-width: 34px; margin: 0 auto; border-radius: 3px 3px 0 0; overflow: hidden; }
1234
+ .wb-seg { display: block; min-height: 1px; flex-basis: 0; }
1235
+ .wb-seg + .wb-seg { border-top: 1px solid var(--panel); }
1236
+ .wb-bars:has(.wb-bar.is-focus) .wb-bar:not(.is-focus) .wb-stack { opacity: 0.45; }
1237
+ .wb-xtick {
1238
+ position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 4px;
1239
+ font-family: var(--mono); font-size: 10.5px; color: var(--faint); white-space: nowrap;
1240
+ }
1241
+
1242
+ /* Outcomes. Words carry the state; colour only repeats it. */
1243
+ .wb-split { display: flex; gap: 2px; height: 10px; border-radius: 3px; overflow: hidden; margin-bottom: var(--s3); }
1244
+ .wb-split-seg { display: block; flex-basis: 0; background: currentColor; }
1245
+ .wb-oc-rows { display: flex; flex-direction: column; }
1246
+ .wb-oc-row {
1247
+ display: grid; grid-template-columns: minmax(0, 1fr) 6.5rem 5.5rem 3rem; gap: var(--s3); align-items: baseline;
1248
+ padding: 6px 0; border-bottom: 1px solid var(--line-soft); font-size: 13px;
1249
+ }
1250
+ .wb-oc-key { display: inline-flex; align-items: center; gap: var(--s2); }
1251
+ .wb-oc-key > span:last-child { color: var(--text); }
1252
+ .wb-oc-dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
1253
+ .wb-oc-n { color: var(--muted); font-size: 12px; }
1254
+ .wb-oc-share { color: var(--faint); font-size: 12px; }
1255
+ .oc-kept { color: var(--mint); }
1256
+ .oc-notkept { color: var(--coral); }
1257
+ .oc-contested { color: var(--gold-text); }
1258
+ .oc-unknown { color: var(--faint); }
1259
+ .oc-maturing { color: var(--sky); }
1260
+ .oc-unmeasured { color: var(--violet); }
1261
+ .oc-none { color: var(--faint); }
1262
+ .wb-split-seg.oc-none { background: repeating-linear-gradient(135deg, var(--line) 0 3px, transparent 3px 6px); }
1263
+ .wb-oc-key.oc-none .wb-oc-dot { background: none; border: 1px solid var(--faint); }
1264
+
1265
+ /* Sessions. */
1266
+ .wb-table { font-size: 12.5px; }
1267
+ .wb-table td { padding-top: 6px; padding-bottom: 6px; vertical-align: middle; }
1268
+ .wb-row { cursor: pointer; }
1269
+ .wb-row:hover td { background: var(--panel-2); }
1270
+ .wb-row.is-selected td { background: var(--accent-soft); }
1271
+ .wb-row.is-selected td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
1272
+ .wb-when { text-align: left; }
1273
+ .wb-open { color: var(--accent-text); text-decoration: none; font-size: 12px; }
1274
+ .wb-open:hover, .wb-open:focus-visible { color: var(--text); text-decoration: underline; }
1275
+ .wb-who { min-width: 0; }
1276
+ .wb-tool { display: block; color: var(--text); }
1277
+ .wb-model { display: inline-flex; align-items: center; gap: 5px; font-family: var(--mono); font-size: 11px; color: var(--muted); white-space: nowrap; }
1278
+ .wb-cost { color: var(--text); }
1279
+ .wb-ctx { white-space: nowrap; }
1280
+ .wb-ctx .wb-dumbbell { display: block; margin: 0 0 2px; }
1281
+ .wb-ctx-text { display: block; text-align: left; }
1282
+ .wb-when-day, .wb-when-time { display: block; }
1283
+ .wb-when-time { color: var(--muted); font-size: 11px; }
1284
+ .wb-dumbbell { position: relative; display: inline-block; width: 5.5rem; height: 12px; vertical-align: middle; margin-right: var(--s2); }
1285
+ .wb-db-track { position: absolute; left: 0; right: 0; top: 50%; border-top: 1px solid var(--line); }
1286
+ .wb-db-span { position: absolute; top: calc(50% - 1px); height: 2px; background: var(--accent); }
1287
+ .wb-db-span.is-down { background: var(--sky); }
1288
+ .wb-db-start, .wb-db-end {
1289
+ position: absolute; top: 50%; width: 8px; height: 8px; border-radius: 50%; transform: translate(-50%, -50%);
1290
+ }
1291
+ .wb-db-start { background: var(--panel); border: 1.5px solid var(--accent); }
1292
+ .wb-db-end { background: var(--accent); }
1293
+ .wb-db-peak { position: absolute; top: 1px; bottom: 1px; width: 0; border-left: 1px solid var(--faint); }
1294
+ .wb-ctx-text { font-size: 11.5px; color: var(--muted); }
1295
+ .wb-chips { display: inline-flex; flex-wrap: wrap; gap: 4px; }
1296
+ .wb-chip {
1297
+ display: inline-flex; align-items: center; padding: 1px 6px; border-radius: 999px; border: 1px solid currentColor;
1298
+ font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.02em; white-space: nowrap;
1299
+ }
1300
+ .wb-none { font-size: 11.5px; color: var(--muted); }
1301
+ .wb-more { margin-top: var(--s3); color: var(--accent-text); }
1302
+ .wb-more:hover, .wb-more:focus-visible { color: var(--text); }
1303
+
1304
+ /* Inside one session. */
1305
+ .wb-detail { position: sticky; top: var(--s4); }
1306
+ .wb-detail-who { font-size: 13px; color: var(--muted); margin-bottom: var(--s3); }
1307
+ .wb-detail-who strong { color: var(--text); font-weight: 600; }
1308
+ .wb-profile { display: flex; align-items: flex-end; gap: 2px; height: 84px; border-bottom: 1px solid var(--line); padding-top: 10px; }
1309
+ .wb-pbar { position: relative; flex: 1 1 0; min-width: 2px; background: var(--accent); opacity: 0.85; border-radius: 2px 2px 0 0; }
1310
+ .wb-pbar.has-mark::after {
1311
+ content: ""; position: absolute; left: 50%; top: -9px; width: 6px; height: 6px; transform: translateX(-50%) rotate(45deg);
1312
+ }
1313
+ .wb-pbar.mark-refused::after { background: var(--gold); }
1314
+ .wb-pbar.mark-failed::after, .wb-pbar.mark-both::after { background: var(--coral); }
1315
+ .wb-profile-axis { display: flex; justify-content: space-between; margin-top: 4px; font-family: var(--mono); font-size: 10.5px; color: var(--faint); }
1316
+ .wb-profile-key { font-size: 11.5px; color: var(--muted); margin-top: var(--s2); }
1317
+ .wb-mark-key { display: inline-block; width: 6px; height: 6px; transform: rotate(45deg); margin: 0 2px 1px 0; }
1318
+ .wb-mark-key.mark-refused { background: var(--gold); }
1319
+ .wb-mark-key.mark-failed { background: var(--coral); }
1320
+ .wb-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px var(--s4); margin: var(--s4) 0; }
1321
+ .wb-fact { display: flex; justify-content: space-between; gap: var(--s2); border-bottom: 1px solid var(--line-soft); padding: 3px 0; font-size: 12.5px; min-width: 0; }
1322
+ .wb-fact dt { color: var(--muted); }
1323
+ .wb-fact dd { color: var(--text); overflow: hidden; text-overflow: ellipsis; }
1324
+ .wb-sub { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); margin: var(--s4) 0 var(--s2); font-weight: 400; }
1325
+ .wb-commit-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
1326
+ .wb-commit { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--s2); font-size: 12.5px; }
1327
+ .wb-sha { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }
1328
+ .wb-subject { color: var(--text); flex: 1 1 10rem; min-width: 0; }
1329
+ .wb-check { font-size: 11px; color: var(--faint); }
1330
+ .wb-hints { margin: 0; padding-left: 1.1em; display: flex; flex-direction: column; gap: 6px; font-size: 12.5px; color: var(--muted); max-width: 60ch; }
1331
+ .wb-hints li::marker { color: var(--accent); }
1332
+ @media (max-width: 1180px) { .wb-detail { position: static; } }
1333
+
1334
+ .wb-req { display: block; }
1335
+ .wb-req-note { display: block; font-size: 10.5px; color: var(--gold-text); }
1336
+ .wb-table th, .wb-table td { padding-left: var(--s2); padding-right: var(--s2); }