penthera 1.7.0 → 1.8.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 (163) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/README.md +139 -76
  3. package/SECURITY.md +8 -6
  4. package/docs/examples/pr-dependency-review.yml +1 -1
  5. package/lib/adaptive-probes.js +3 -3
  6. package/lib/advisories/evidence.js +248 -0
  7. package/lib/advisories/index.js +228 -0
  8. package/lib/advisories/products.js +83 -0
  9. package/lib/advisories/versions.js +65 -0
  10. package/lib/agents/hidden-content.js +122 -11
  11. package/lib/agents/index.js +26 -6
  12. package/lib/agents/lock.js +33 -5
  13. package/lib/agents/rules.js +33 -4
  14. package/lib/compliance/index.js +4 -4
  15. package/lib/crawler.js +12 -15
  16. package/lib/deps/capabilities.js +21 -5
  17. package/lib/deps/findings.js +309 -0
  18. package/lib/deps/index.js +13 -272
  19. package/lib/deps/iocs.js +74 -0
  20. package/lib/deps/lockfile.js +3 -8
  21. package/lib/deps/online.js +8 -3
  22. package/lib/deps/popular-packages.json.gz +0 -0
  23. package/lib/deps/popular-pypi.json.gz +0 -0
  24. package/lib/deps/python/capabilities.js +16 -3
  25. package/lib/deps/python/index.js +5 -1
  26. package/lib/deps/python/lockfiles.js +11 -5
  27. package/lib/deps/python/source.js +75 -26
  28. package/lib/deps/python/toml.js +15 -3
  29. package/lib/deps/source.js +28 -9
  30. package/lib/deps/typosquat.js +30 -5
  31. package/lib/fix/apply.js +253 -34
  32. package/lib/fix/index.js +7 -4
  33. package/lib/fix/model.js +25 -5
  34. package/lib/fix/util.js +6 -2
  35. package/lib/fuzzer.js +3 -3
  36. package/lib/git.js +65 -0
  37. package/lib/hook/index.js +157 -73
  38. package/lib/hook/policy.js +10 -4
  39. package/lib/install/index.js +4 -7
  40. package/lib/machine.js +0 -13
  41. package/lib/memory/store.js +11 -4
  42. package/lib/nuclei-loader.js +0 -25
  43. package/lib/owasp-wstg.js +4 -0
  44. package/lib/plugins.js +1 -1
  45. package/lib/provenance.js +1 -0
  46. package/lib/recon.js +12 -31
  47. package/lib/references.json +224 -0
  48. package/lib/retirejs.js +4 -4
  49. package/lib/rules/advisories/advisories.json +186 -0
  50. package/lib/rules/advisories/eol.json +413 -0
  51. package/lib/rules/advisories/pack.json +20 -0
  52. package/lib/rules/advisories/pack.json.sig +6 -0
  53. package/lib/rules/allowed_signers +13 -0
  54. package/lib/rules/core/agent-exec.json +29 -0
  55. package/lib/rules/core/agent-instructions.json +17 -0
  56. package/lib/rules/core/pack.json +30 -0
  57. package/lib/rules/core/pack.json.sig +6 -0
  58. package/lib/rules/core/secrets.json +161 -0
  59. package/lib/rules/core/templates.json +52 -0
  60. package/lib/rules/index.js +107 -0
  61. package/lib/rules/ioc-2025-2026/files.json +43 -0
  62. package/lib/rules/ioc-2025-2026/hosts.json +35 -0
  63. package/lib/rules/ioc-2025-2026/pack.json +25 -0
  64. package/lib/rules/ioc-2025-2026/pack.json.sig +6 -0
  65. package/lib/rules/ioc-2025-2026/packages.json +1048 -0
  66. package/lib/rules/pack.js +452 -0
  67. package/lib/rules/regex-guard.js +81 -9
  68. package/lib/rules/sshsig.js +205 -0
  69. package/lib/templates.js +24 -20
  70. package/lib/tmp.js +21 -0
  71. package/lib/whitebox/frameworks.js +1 -1
  72. package/lib/whitebox/secrets.js +117 -41
  73. package/package.json +12 -4
  74. package/scripts/rules/build-pack.mjs +80 -0
  75. package/scripts/rules/check-bundled.mjs +30 -0
  76. package/scripts/rules/sign-pack.sh +32 -0
  77. package/skills/penthera/SKILL.md +16 -15
  78. package/src/agent/git.js +147 -14
  79. package/src/agent/loop.js +165 -46
  80. package/src/cli/agent.js +11 -5
  81. package/src/cli/deps.js +0 -2
  82. package/src/cli/detect.js +14 -7
  83. package/src/cli/fix.js +26 -8
  84. package/src/cli/hook.js +2 -1
  85. package/src/cli/onboarding.js +14 -6
  86. package/src/cli/report-outputs.js +1 -0
  87. package/src/cli/rules.js +144 -0
  88. package/src/cli/ui.js +43 -10
  89. package/src/cli.js +24 -1
  90. package/src/report/evidence.js +105 -21
  91. package/src/report/footer.js +1 -1
  92. package/src/report/md-escape.js +8 -0
  93. package/src/scan-repo.js +6 -0
  94. package/src/scan-url.js +11 -3
  95. package/src/ui/api/data.js +39 -6
  96. package/src/ui/api/routes.js +44 -11
  97. package/src/ui/api/runs.js +32 -7
  98. package/src/ui/security.js +20 -23
  99. package/src/ui/server.js +52 -27
  100. package/src/ui/static/app.css +583 -213
  101. package/src/ui/static/favicon.svg +1 -1
  102. package/src/ui/static/fonts/DepartureMono-LICENSE.txt +93 -0
  103. package/src/ui/static/fonts/DepartureMono-Regular.woff2 +0 -0
  104. package/src/ui/static/index.html +5 -4
  105. package/src/ui/static/js/api.js +114 -6
  106. package/src/ui/static/js/app.js +81 -42
  107. package/src/ui/static/js/center.js +246 -133
  108. package/src/ui/static/js/charts.js +99 -18
  109. package/src/ui/static/js/copy.js +68 -9
  110. package/src/ui/static/js/dialog.js +152 -0
  111. package/src/ui/static/js/left.js +23 -60
  112. package/src/ui/static/js/menubar.js +170 -0
  113. package/src/ui/static/js/onboarding.js +104 -0
  114. package/src/ui/static/js/panels.js +186 -0
  115. package/src/ui/static/js/right.js +69 -188
  116. package/src/ui/static/js/state.js +36 -5
  117. package/src/ui/static/logo.svg +10 -0
  118. package/src/ui/static/mark.svg +3 -0
  119. package/src/utils/http.js +162 -13
  120. package/docs/METHODOLOGY.md +0 -375
  121. package/docs/commands/README.md +0 -24
  122. package/docs/commands/action.md +0 -21
  123. package/docs/commands/agents.md +0 -38
  124. package/docs/commands/deps.md +0 -115
  125. package/docs/commands/fix.md +0 -53
  126. package/docs/commands/hook.md +0 -69
  127. package/docs/commands/init.md +0 -114
  128. package/docs/commands/install.md +0 -53
  129. package/docs/commands/scan.md +0 -100
  130. package/docs/commands/skill.md +0 -58
  131. package/docs/compare/README.md +0 -25
  132. package/docs/compare/github.md +0 -60
  133. package/docs/compare/semgrep.md +0 -54
  134. package/docs/compare/snyk.md +0 -62
  135. package/docs/compare/socket.md +0 -78
  136. package/docs/compare/zap.md +0 -59
  137. package/docs/compliance-cra.md +0 -141
  138. package/docs/compliance-nis2.md +0 -168
  139. package/docs/dashboard.md +0 -187
  140. package/docs/evidence.md +0 -162
  141. package/docs/funding/due-diligence-checklist.md +0 -79
  142. package/docs/funding/nlnet-ngi-zero-draft.md +0 -273
  143. package/docs/funding/partner-one-pager.md +0 -84
  144. package/docs/funding/sponsor-tiers.md +0 -81
  145. package/docs/index.md +0 -81
  146. package/docs/internal-audit.md +0 -83
  147. package/docs/local-loop.md +0 -276
  148. package/docs/marketing.md +0 -151
  149. package/docs/owasp-wstg-coverage.md +0 -79
  150. package/docs/plan.md +0 -102
  151. package/docs/privacy.md +0 -38
  152. package/docs/references.bib +0 -559
  153. package/docs/references.md +0 -119
  154. package/docs/release-plan.md +0 -228
  155. package/docs/research/socket-positioning-notes.md +0 -183
  156. package/docs/use-cases/README.md +0 -14
  157. package/docs/use-cases/agencies.md +0 -71
  158. package/docs/use-cases/ai-coding-teams.md +0 -81
  159. package/docs/use-cases/eu-sme-compliance.md +0 -68
  160. package/docs/use-cases/open-source-maintainers.md +0 -75
  161. package/docs/use-cases/solo-founder.md +0 -65
  162. package/lib/deps/popular-packages.json +0 -1
  163. package/lib/deps/popular-pypi.json +0 -1
@@ -1,29 +1,45 @@
1
1
  /*
2
- * penthera ui :: one view, three columns, a terminal inside.
2
+ * penthera ui :: System 1-bit, blue ink.
3
3
  *
4
- * Layout: like the Claude Code desktop app. A left sidebar of projects and
5
- * runs, one scrolling centre (a project, or a run as a transcript), and a
6
- * right panel of stacked cards. Chrome is calm: hairlines, not boxes. The 1-bit
7
- * register stays in the details: pattern swatches for severity, shade bars,
8
- * the pinstriped approval dialog, the #0000ed wordmark on the empty state.
9
- * Black, white and patterns do the work; #0000ed is the one accent; green is
10
- * kept for the cursor, the prompt and "OK".
4
+ * A menu bar over three tiled windows on a dotted desktop: projects on the
5
+ * left, the document (a project's five tabs, or a run) in the centre, the
6
+ * inspector on the right. Every pane is a
7
+ * window: a 1px ink frame and a title bar. The window that holds focus gets
8
+ * the solid ink title bar; the others are outlined, so you can always see
9
+ * which pane j / k / Esc will act on. Only dialogs float (hard 2px shadow),
10
+ * and the approval dialog alone is pinstriped: pinstripes mean "waiting on you".
11
+ *
12
+ * Colour: ink, paper and #0000ed. No flat greys: shades are dither patterns
13
+ * (solid, hatched, half, sparse, dotted, empty). Blue has four jobs only ::
14
+ * selection, the default button, the focus ring, chart lines and links.
15
+ * Green is retired: OK is a ✓ and a word.
16
+ *
17
+ * Type, three roles:
18
+ * --px Departure Mono (OFL, self-hosted) at 11 / 22px :: chrome, labels, numbers
19
+ * --mono ui-monospace :: data, paths, diffs, evidence, transcripts
20
+ * --sans system-ui :: sentences for people (In plain words, notes, empty states)
11
21
  *
12
22
  * One theming system: [data-theme] on <html>, set before paint by
13
- * /js/theme.js (localStorage, else prefers-color-scheme). There is no
14
- * prefers-color-scheme block here, so the two can never drift.
23
+ * /js/theme.js (localStorage, else prefers-color-scheme). Both themes are
24
+ * first-class; every token has a paper and an ink value.
15
25
  *
16
26
  * Contrast, measured (WCAG 2.x, on the ground):
17
- * dark #000: text-1 21.0 · text-2 (ink .72) 10.3 · text-3 (ink .52) 5.6 · accent-ink #7a7aff 6.06
18
- * green #55ff00 15.7 · white on #0000ed 9.45 · focus #7a7aff 6.06 (>=3 non-text)
27
+ * dark #000: text-1 21.0 · text-2 (ink .72) 10.3 · text-3 (ink .56) 6.4 · accent-ink #7a7aff 6.06
28
+ * white on #0000ed 9.45 · focus #7a7aff 6.06 (>=3 non-text)
19
29
  * light #fdfdfa: text-1 19.4 · text-2 (ink .72) 8.0 · text-3 (ink .6) 5.24 · #0000ed 9.27
20
- * green #137300 5.92 · white on #0000ed 9.27
21
- * #0000ed itself is 2.22:1 on black, so in the dark theme it is only ever a
22
- * fill (with white on it), a frame or a glow: never text, never a 1px line.
23
- * The canonical ink ladder's 0.5 step measures 3.6:1 on the light ground, so
24
- * light text-3 is 0.6, not 0.5. Same ladder, measured per polarity.
30
+ * white on #0000ed 9.27
31
+ * #0000ed is 2.22:1 on black, so in the dark theme it is only ever a fill
32
+ * (with white on it), never text and never a 1px line.
25
33
  */
26
34
 
35
+ @font-face {
36
+ font-family: "Departure Mono";
37
+ src: url("/fonts/DepartureMono-Regular.woff2") format("woff2");
38
+ font-weight: 400;
39
+ font-style: normal;
40
+ font-display: swap;
41
+ }
42
+
27
43
  :root,
28
44
  :root[data-theme="dark"] {
29
45
  color-scheme: dark;
@@ -31,23 +47,19 @@
31
47
  --ink-rgb: 255, 255, 255;
32
48
  --text-1: rgb(var(--ink-rgb));
33
49
  --text-2: rgba(var(--ink-rgb), 0.72);
34
- --text-3: rgba(var(--ink-rgb), 0.52);
50
+ --text-3: rgba(var(--ink-rgb), 0.56);
35
51
  --text-4: rgba(var(--ink-rgb), 0.3); /* ornament only, never text you must read */
36
- --rule: rgba(var(--ink-rgb), 0.3);
37
- --hair: rgba(var(--ink-rgb), 0.14);
52
+ --rule: rgba(var(--ink-rgb), 0.34);
53
+ --hair: rgba(var(--ink-rgb), 0.16);
38
54
  --frame: rgb(var(--ink-rgb));
39
- --wash: rgba(var(--ink-rgb), 0.06);
40
55
  --accent: #0000ed;
41
56
  --accent-hover: #2a2aff;
42
57
  --accent-ink: #7a7aff; /* same hue, 6.06:1 on black */
43
58
  --on-accent: #ffffff;
44
59
  --focus: #7a7aff;
45
60
  --series: #7a7aff;
46
- --ok: #55ff00;
47
- --dot: rgba(var(--ink-rgb), 0.26); /* desktop pattern */
48
- --glow: 0 0 6px rgba(85, 255, 0, 0.6);
49
- --logo-glow: 0 0 10px rgba(0, 0, 237, 0.95), 0 0 26px rgba(0, 0, 237, 0.6);
50
- --scan: rgba(255, 255, 255, 0.035);
61
+ --desk: rgba(var(--ink-rgb), 0.42); /* desktop dots */
62
+ --scrim: rgba(0, 0, 0, 0.6);
51
63
  }
52
64
 
53
65
  :root[data-theme="light"] {
@@ -58,31 +70,40 @@
58
70
  --text-2: rgba(var(--ink-rgb), 0.72);
59
71
  --text-3: rgba(var(--ink-rgb), 0.6);
60
72
  --text-4: rgba(var(--ink-rgb), 0.3);
61
- --rule: rgba(var(--ink-rgb), 0.36);
62
- --hair: rgba(var(--ink-rgb), 0.14);
73
+ --rule: rgba(var(--ink-rgb), 0.4);
74
+ --hair: rgba(var(--ink-rgb), 0.16);
63
75
  --frame: rgb(var(--ink-rgb));
64
- --wash: rgba(var(--ink-rgb), 0.045);
65
76
  --accent: #0000ed;
66
77
  --accent-hover: #2a2aff;
67
78
  --accent-ink: #0000ed;
68
79
  --on-accent: #ffffff;
69
80
  --focus: #0000ed;
70
81
  --series: #0000ed;
71
- --ok: #137300;
72
- --dot: rgba(var(--ink-rgb), 0.42);
73
- --glow: none; /* no glow on paper */
74
- --logo-glow: none;
75
- --scan: transparent; /* no scanlines on paper */
82
+ --desk: rgba(var(--ink-rgb), 0.55);
83
+ --scrim: rgba(10, 10, 10, 0.35);
76
84
  }
77
85
 
78
86
  :root {
87
+ --px: "Departure Mono", ui-monospace, "SF Mono", Menlo, monospace;
79
88
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, "Liberation Mono", monospace;
89
+ --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
80
90
  --fs: 13px;
81
- --gap: 16px;
82
- --radius-btn: 6px; /* the one radius: push buttons and the prompt bar. Panels stay square */
91
+ --bar-h: 28px; /* window title bars */
92
+ --desk-gap: 6px; /* desktop showing between windows */
83
93
  --left-w: 288px;
84
- --right-w: 380px;
94
+ --right-w: 392px;
85
95
  --target: 28px; /* WCAG 2.5.8 floor is 24; coarse pointers get 44 below */
96
+ --press: 100ms;
97
+ }
98
+
99
+ /* ── Dither patterns: the only greys ───────────────────────────────────── */
100
+ /* Each is a background on currentColor-free tokens, so they invert with the theme. */
101
+ :root {
102
+ --pt-solid: linear-gradient(var(--frame), var(--frame));
103
+ --pt-hatch: repeating-linear-gradient(45deg, var(--frame) 0 1.5px, transparent 1.5px 3px);
104
+ --pt-half: conic-gradient(from 90deg at 1px 1px, transparent 50%, var(--frame) 0 75%, transparent 0) 0 0 / 2px 2px, conic-gradient(var(--frame) 25%, transparent 0) 0 0 / 2px 2px;
105
+ --pt-sparse: conic-gradient(from 90deg at 1px 1px, transparent 75%, var(--frame) 0) 0 0 / 3px 3px;
106
+ --pt-desk: conic-gradient(from 90deg at 1px 1px, transparent 75%, var(--desk) 0) 0 0 / 4px 4px;
86
107
  }
87
108
 
88
109
  * { box-sizing: border-box; }
@@ -97,6 +118,8 @@ body {
97
118
  font-variant-numeric: tabular-nums lining-nums;
98
119
  -webkit-font-smoothing: antialiased;
99
120
  overflow: hidden;
121
+ display: flex;
122
+ flex-direction: column;
100
123
  }
101
124
  ::selection { background: var(--accent); color: var(--on-accent); }
102
125
  a { color: var(--accent-ink); text-underline-offset: 3px; }
@@ -106,32 +129,201 @@ a:hover { background: var(--accent); color: var(--on-accent); text-decoration: n
106
129
  .skip { position: absolute; left: 8px; top: -40px; z-index: 100; background: var(--accent); color: var(--on-accent); padding: 4px 8px; }
107
130
  .skip:focus { top: 8px; }
108
131
  .pad { padding: 16px; }
132
+ code { font: inherit; }
109
133
 
110
- /* ── The three columns ─────────────────────────────────────────────────── */
134
+ /* ── The desktop and its three windows ─────────────────────────────────── */
111
135
 
112
136
  .app {
113
137
  display: grid;
114
138
  grid-template-columns: var(--left-w) minmax(0, 1fr) var(--right-w);
115
- height: 100dvh;
116
- }
117
- .app.no-left { grid-template-columns: 0 minmax(0, 1fr) var(--right-w); }
118
- .app.no-right { grid-template-columns: var(--left-w) minmax(0, 1fr) 0; }
119
- .app.no-left.no-right { grid-template-columns: 0 minmax(0, 1fr) 0; }
120
- .app.no-left .left, .app.no-right .right { visibility: hidden; }
121
- .left, .right { min-width: 0; overflow-y: auto; overflow-x: hidden; background: var(--ground); }
122
- .left { border-right: 1px solid var(--rule); display: flex; flex-direction: column; }
123
- .right { border-left: 1px solid var(--rule); }
124
- .center { min-width: 0; display: flex; flex-direction: column; outline: none; overflow: hidden; }
139
+ grid-template-areas: "left center right";
140
+ gap: var(--desk-gap);
141
+ padding: var(--desk-gap);
142
+ flex: 1;
143
+ min-height: 0;
144
+ background: var(--pt-desk), var(--ground);
145
+ }
146
+ .app.no-left { grid-template-columns: minmax(0, 1fr) var(--right-w); grid-template-areas: "center right"; }
147
+ .app.no-right { grid-template-columns: var(--left-w) minmax(0, 1fr); grid-template-areas: "left center"; }
148
+ .app.no-left.no-right { grid-template-columns: minmax(0, 1fr); grid-template-areas: "center"; }
149
+ .app.no-left .left, .app.no-right .right { display: none; }
150
+ .left { grid-area: left; }
151
+ .center { grid-area: center; }
152
+ .right { grid-area: right; }
153
+
154
+ .pane {
155
+ min-width: 0;
156
+ min-height: 0;
157
+ display: flex;
158
+ flex-direction: column;
159
+ background: var(--ground);
160
+ border: 1px solid var(--frame);
161
+ overflow: hidden;
162
+ }
163
+ .pane-body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; }
164
+ .left .pane-body { display: flex; flex-direction: column; }
165
+ .center { outline: none; }
125
166
  .scrim { display: none; }
126
167
 
127
- /* ── Left sidebar ──────────────────────────────────────────────────────── */
168
+ /* Title bar. Outlined at rest; solid ink on the window that holds focus (the
169
+ centre one when nothing does). */
170
+ .win-bar {
171
+ flex: none;
172
+ display: flex;
173
+ align-items: center;
174
+ gap: 6px;
175
+ height: var(--bar-h);
176
+ padding: 0 4px 0 8px;
177
+ border-bottom: 1px solid var(--frame);
178
+ background: var(--ground);
179
+ color: var(--text-1);
180
+ font: 11px / 1 var(--px);
181
+ white-space: nowrap;
182
+ }
183
+ .pane:focus-within > .win-bar,
184
+ .app:not(:focus-within) .center > .win-bar {
185
+ background: var(--frame);
186
+ color: var(--ground);
187
+ }
188
+ .win-bar .title { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
189
+ .win-bar .grow { flex: 1; }
190
+ .win-bar .icon { color: inherit; min-width: 22px; min-height: 22px; }
191
+ .win-bar .icon:hover { background: var(--ground); color: var(--text-1); box-shadow: inset 0 0 0 1px var(--frame); }
192
+ .pane:focus-within > .win-bar .icon:hover,
193
+ .app:not(:focus-within) .center > .win-bar .icon:hover { background: var(--ground); color: var(--text-1); box-shadow: none; }
194
+ .win-bar input[type="search"] {
195
+ height: 20px;
196
+ min-height: 20px;
197
+ width: 200px;
198
+ padding: 0 6px;
199
+ font: 11px / 1 var(--px);
200
+ border: 1px solid var(--frame);
201
+ background: var(--ground);
202
+ color: var(--text-1);
203
+ }
204
+
205
+ /* The logo: the serif mark and wordmark as masks, so they take the text colour
206
+ (paper on an active title bar, ink everywhere else). */
207
+ .mark, .logotype {
208
+ display: inline-block;
209
+ flex: none;
210
+ background: currentColor;
211
+ -webkit-mask: var(--src) center / contain no-repeat;
212
+ mask: var(--src) center / contain no-repeat;
213
+ }
214
+ .mark { --src: url("/mark.svg"); width: 7px; height: 14px; }
215
+ .logotype { --src: url("/logo.svg"); width: 58px; height: 13px; }
216
+ .logotype.xl { width: 223px; height: 50px; }
217
+
218
+ /*
219
+ * Which logo where. One appearance per surface, never the mark and the
220
+ * wordmark side by side:
221
+ * menu bar, top left the horizontal wordmark; the P mark below 760px
222
+ * favicon / tab the P mark on a blue tile (favicon.svg)
223
+ * About window the wordmark, large
224
+ * first run (empty) the wordmark, large (phase 4)
225
+ * window title bars never: they name the window, not the product
226
+ */
227
+
228
+ /* ── Menu bar ──────────────────────────────────────────────────────────── */
128
229
 
129
- .brand { display: flex; align-items: center; gap: 8px; padding: 12px 12px 10px 14px; color: var(--text-1); font-weight: 700; }
130
- .brand svg { display: block; flex: none; }
131
- .brand .px { fill: var(--accent-ink); }
132
- .brand .grow { flex: 1; }
133
- .new-scan { margin: 2px 12px 12px; justify-content: center; }
134
- .side-label { margin: 14px 14px 6px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-3); display: flex; justify-content: space-between; }
230
+ .menubar {
231
+ flex: none;
232
+ position: relative;
233
+ z-index: 40;
234
+ display: flex;
235
+ align-items: stretch;
236
+ height: 26px;
237
+ padding: 0 6px;
238
+ border-bottom: 1px solid var(--frame);
239
+ background: var(--ground);
240
+ color: var(--text-1);
241
+ font: 11px / 1 var(--px);
242
+ }
243
+ .menubar .grow { flex: 1; }
244
+ .menus { display: flex; list-style: none; margin: 0; padding: 0; }
245
+ .menu-wrap { position: relative; }
246
+ .menu-title {
247
+ height: 100%;
248
+ display: inline-flex;
249
+ align-items: center;
250
+ padding: 0 10px;
251
+ border: 0;
252
+ background: none;
253
+ color: var(--text-1);
254
+ font: inherit;
255
+ cursor: default;
256
+ }
257
+ .menu-title:hover, .menu-title[aria-expanded="true"] { background: var(--frame); color: var(--ground); }
258
+ .menu-title:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
259
+ .menu-title.logo { padding: 0 12px 0 8px; }
260
+ .menu-title.logo .logotype { width: 54px; height: 12px; }
261
+ .menu-title.logo .mark { display: none; width: 6px; height: 13px; }
262
+ .menu {
263
+ position: absolute;
264
+ top: 100%;
265
+ left: 0;
266
+ min-width: 232px;
267
+ margin: 0;
268
+ padding: 3px 0;
269
+ list-style: none;
270
+ background: var(--ground);
271
+ border: 1px solid var(--frame);
272
+ box-shadow: 3px 3px 0 var(--frame);
273
+ }
274
+ .menu-item {
275
+ display: flex;
276
+ align-items: center;
277
+ gap: 4px;
278
+ width: 100%;
279
+ min-height: 24px;
280
+ padding: 0 10px 0 4px;
281
+ border: 0;
282
+ background: none;
283
+ color: var(--text-1);
284
+ font: inherit;
285
+ text-align: left;
286
+ text-decoration: none;
287
+ white-space: nowrap;
288
+ cursor: default;
289
+ }
290
+ .menu-item .check { width: 14px; text-align: center; }
291
+ .menu-item .grow { flex: 1; }
292
+ .menu-item kbd { border: 0; padding: 0; color: var(--text-3); margin-left: 16px; }
293
+ .menu-item:hover, .menu-item:focus-visible { background: var(--frame); color: var(--ground); outline: none; }
294
+ .menu-item:hover kbd, .menu-item:focus-visible kbd { color: var(--ground); }
295
+ .menu-item[disabled] { color: var(--text-4); background: none; }
296
+ .menu-item[disabled] kbd { color: var(--text-4); }
297
+ .menu .sep { margin: 3px 0; border-top: 1px dotted var(--rule); }
298
+
299
+ .statuses { display: flex; align-items: stretch; gap: 2px; }
300
+ .status {
301
+ display: inline-flex;
302
+ align-items: center;
303
+ gap: 7px;
304
+ padding: 0 10px;
305
+ border: 0;
306
+ background: none;
307
+ color: var(--text-1);
308
+ font: inherit;
309
+ cursor: default;
310
+ white-space: nowrap;
311
+ }
312
+ button.status:hover { background: var(--frame); color: var(--ground); }
313
+ button.status:hover .lamp { border-color: var(--ground); }
314
+ .status.dim { color: var(--text-3); }
315
+ .status.waiting { background: var(--accent); color: var(--on-accent); }
316
+ .status.waiting .lamp { border-color: var(--on-accent); }
317
+ .lamp { width: 8px; height: 8px; border: 1px solid var(--frame); flex: none; }
318
+ .lamp.on { background: var(--frame); }
319
+ .lamp.live { background: var(--pt-half); animation: blink 1s steps(1, end) infinite; }
320
+ .status.waiting .lamp.live { background: var(--on-accent); }
321
+
322
+ /* ── Left: projects and runs ───────────────────────────────────────────── */
323
+
324
+ .new-scan { margin: 10px 10px 8px; justify-content: space-between; }
325
+ .new-scan kbd { border-color: currentColor; }
326
+ .side-label { margin: 14px 12px 6px; font: 11px / 1 var(--px); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3); display: flex; justify-content: space-between; }
135
327
  .side-list { list-style: none; margin: 0; padding: 0 6px; display: grid; gap: 1px; }
136
328
  .item {
137
329
  all: unset;
@@ -143,143 +335,159 @@ a:hover { background: var(--accent); color: var(--on-accent); text-decoration: n
143
335
  width: 100%;
144
336
  min-height: 40px;
145
337
  padding: 6px 8px;
146
- border-left: 2px solid transparent;
147
- cursor: pointer;
338
+ cursor: default;
148
339
  color: var(--text-1);
149
340
  }
150
- .item:hover { background: var(--wash); }
341
+ .item:hover { box-shadow: inset 0 0 0 1px var(--rule); }
151
342
  .item:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
152
- .item[aria-current="true"] { background: var(--wash); border-left-color: var(--accent-ink); }
343
+ .item[aria-current="true"] { background: var(--accent); color: var(--on-accent); box-shadow: none; }
344
+ .item[aria-current="true"] .sub, .item[aria-current="true"] .count { color: var(--on-accent); }
345
+ .item[aria-current="true"] .grade { border-color: var(--on-accent); color: var(--on-accent); background: none; }
346
+ .item[aria-current="true"] .grade-F, .item[aria-current="true"] .grade-D { background: var(--on-accent); color: var(--accent); }
347
+ .item[aria-current="true"] .dot { border-color: var(--on-accent); }
153
348
  .item .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
154
349
  .item .sub { grid-column: 2 / -1; font-size: 11px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
155
- .item .count { font-size: 12px; color: var(--text-2); }
156
- .item .dot { width: 8px; height: 8px; border: 1px solid var(--frame); }
157
- .item .dot.live { background: var(--ok); border-color: var(--ok); box-shadow: var(--glow); }
350
+ .item .count { font: 11px / 1 var(--px); color: var(--text-2); }
351
+ .item .dot { width: 9px; height: 9px; border: 1px solid var(--frame); }
352
+ .item .dot.live { background: var(--pt-half); animation: blink 1s steps(1, end) infinite; }
158
353
  .item .dot.done { background: var(--frame); }
159
- .group-h { padding: 8px 10px 2px; font-size: 11px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
160
- .side-foot { margin-top: auto; display: flex; align-items: center; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--rule); font-size: 11px; color: var(--text-3); }
161
- .side-foot .grow { flex: 1; }
354
+ .item[aria-current="true"] .dot.done { background: var(--on-accent); }
355
+ .group-h { padding: 8px 10px 2px; font: 11px / 1.3 var(--px); color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
162
356
 
163
- /* ── Small controls ────────────────────────────────────────────────────── */
357
+ /* ── Controls ──────────────────────────────────────────────────────────── */
164
358
 
165
359
  .icon {
166
- font: inherit;
360
+ font: 13px / 1 var(--mono);
167
361
  display: inline-grid;
168
362
  place-items: center;
169
363
  min-width: 28px;
170
364
  min-height: 28px;
171
- padding: 0 6px;
172
- border: 1px solid transparent;
173
- border-radius: var(--radius-btn);
365
+ padding: 0 5px;
366
+ border: 0;
367
+ border-radius: 0;
174
368
  background: none;
175
369
  color: var(--text-2);
176
- cursor: pointer;
370
+ cursor: default;
177
371
  }
178
- .icon:hover { background: var(--wash); color: var(--text-1); border-color: var(--rule); }
179
- .icon[aria-pressed="true"] { color: var(--text-1); border-color: var(--rule); }
372
+ .icon:hover { color: var(--text-1); box-shadow: inset 0 0 0 1px var(--frame); }
373
+ .icon:active { background: var(--frame); color: var(--ground); }
374
+ .icon[aria-pressed="true"] { color: var(--text-1); box-shadow: inset 0 0 0 1px var(--frame); }
375
+ /* The close box: an empty square at the left of a title bar. */
376
+ .icon.close-box { min-width: 22px; min-height: 22px; padding: 0; }
377
+ .icon.close-box::before { content: ""; width: 11px; height: 11px; border: 1px solid currentColor; background: var(--ground); }
378
+ .icon.close-box:hover::before { background: currentColor; }
180
379
 
380
+ /* Push buttons: square, 1px ink. Pressed inverts, instantly. The default
381
+ button is blue with the classic outer ring. */
181
382
  .btn {
182
- font: inherit;
383
+ font: 11px / 1 var(--px);
384
+ letter-spacing: 0.02em;
183
385
  display: inline-flex;
184
386
  align-items: center;
185
- gap: 6px;
387
+ gap: 8px;
186
388
  min-height: var(--target);
187
- padding: 3px 12px;
389
+ padding: 0 12px;
188
390
  color: var(--text-1);
189
391
  background: var(--ground);
190
- border: 1px solid var(--rule);
191
- border-radius: var(--radius-btn);
192
- cursor: pointer;
392
+ border: 1px solid var(--frame);
393
+ border-radius: 0;
394
+ cursor: default;
193
395
  white-space: nowrap;
194
396
  text-decoration: none;
195
397
  }
196
- .btn:hover { background: var(--wash); color: var(--text-1); border-color: var(--frame); }
398
+ .btn:hover { box-shadow: inset 0 0 0 1px var(--frame); background: var(--ground); color: var(--text-1); }
197
399
  .btn:active { background: var(--frame); color: var(--ground); }
198
- .btn[disabled] { color: var(--text-4); border-color: var(--hair); cursor: not-allowed; background: var(--ground); }
199
- .btn.primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
400
+ .btn[disabled] { color: var(--text-4); border-color: var(--text-4); box-shadow: none; background: var(--ground); }
401
+ .btn.primary {
402
+ background: var(--accent);
403
+ color: var(--on-accent);
404
+ border-color: var(--frame);
405
+ box-shadow: 0 0 0 2px var(--ground), 0 0 0 3px var(--frame);
406
+ margin: 3px;
407
+ }
200
408
  .btn.primary:hover { background: var(--accent-hover); color: var(--on-accent); }
201
409
  .btn.primary:active { background: var(--frame); color: var(--ground); }
202
- .btn.primary[disabled] { background: var(--ground); color: var(--text-4); border-color: var(--hair); }
203
- .btn.small { min-height: 24px; padding: 0 8px; font-size: 12px; }
204
- .btn .k { opacity: 0.8; font-size: 11px; }
410
+ .btn.primary[disabled] { background: var(--ground); color: var(--text-4); border-color: var(--text-4); box-shadow: 0 0 0 2px var(--ground), 0 0 0 3px var(--text-4); }
411
+ .btn.small { min-height: 24px; padding: 0 8px; }
412
+ .btn .k { opacity: 0.75; }
205
413
 
206
- .chip { display: inline-block; border: 1px solid var(--rule); padding: 0 6px; font-size: 11px; color: var(--text-2); white-space: nowrap; }
207
- .chip.on { color: var(--ok); border-color: currentColor; }
414
+ .chip { display: inline-block; border: 1px solid var(--frame); padding: 1px 5px; font: 11px / 1.2 var(--px); color: var(--text-1); white-space: nowrap; }
415
+ .chip.on { background: var(--frame); color: var(--ground); }
208
416
  .chip.inv { background: var(--frame); color: var(--ground); border-color: var(--frame); }
209
- .chip.blue { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
417
+ .chip.blue { background: var(--accent); color: var(--on-accent); border-color: var(--frame); }
210
418
 
211
419
  input[type="text"], input[type="search"], select {
212
420
  font: inherit;
213
421
  color: var(--text-1);
214
422
  background: var(--ground);
215
- border: 1px solid var(--rule);
216
- border-radius: var(--radius-btn);
423
+ border: 1px solid var(--frame);
424
+ border-radius: 0;
217
425
  padding: 4px 8px;
218
426
  min-height: var(--target);
219
427
  min-width: 0;
220
428
  }
221
429
  input::placeholder { color: var(--text-3); }
222
- input:focus-visible, select:focus-visible { outline: 2px solid var(--focus); outline-offset: 0; border-color: transparent; }
430
+ input:focus-visible, select:focus-visible { outline: 2px solid var(--focus); outline-offset: 0; }
223
431
  input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); width: 14px; height: 14px; margin: 0 6px 0 0; vertical-align: -2px; }
224
432
 
225
- /* ── Severity, grade, bars (the 1-bit details) ─────────────────────────── */
433
+ /* ── Severity, grade, bars ─────────────────────────────────────────────── */
226
434
 
227
- .sev { white-space: nowrap; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; display: inline-flex; align-items: center; gap: 6px; color: var(--text-1); }
435
+ .sev { white-space: nowrap; font: 11px / 1 var(--px); text-transform: uppercase; letter-spacing: 0.05em; display: inline-flex; align-items: center; gap: 6px; color: var(--text-1); }
228
436
  .sev::before { content: ""; display: inline-block; width: 9px; height: 9px; border: 1px solid var(--frame); flex: none; }
229
437
  .sev-critical::before { background: var(--frame); }
230
- .sev-high::before { background: repeating-linear-gradient(45deg, var(--frame) 0 1.5px, transparent 1.5px 3px); }
231
- .sev-medium::before { background: conic-gradient(from 90deg at 1px 1px, transparent 50%, var(--frame) 0 75%, transparent 0) 0 0 / 2px 2px, conic-gradient(var(--frame) 25%, transparent 0) 0 0 / 2px 2px; }
232
- .sev-low::before { background: conic-gradient(from 90deg at 1px 1px, transparent 75%, var(--frame) 0) 0 0 / 3px 3px; }
438
+ .sev-high::before { background: var(--pt-hatch); }
439
+ .sev-medium::before { background: var(--pt-half); }
440
+ .sev-low::before { background: var(--pt-sparse); }
233
441
  .sev-info::before { background: transparent; }
234
- .sev-critical { font-weight: 700; }
235
442
  .sev-low, .sev-info { color: var(--text-2); }
236
- .sw { display: inline-block; width: 9px; height: 9px; border: 1px solid var(--frame); }
443
+ .sw { display: inline-block; width: 9px; height: 9px; border: 1px solid var(--frame); flex: none; }
237
444
  .sw.sev-critical { background: var(--frame); }
445
+ .sw.sev-high { background: var(--pt-hatch); }
446
+ .sw.sev-medium { background: var(--pt-half); }
447
+ .sw.sev-low { background: var(--pt-sparse); }
238
448
 
239
- .grade { display: inline-grid; place-items: center; min-width: 22px; height: 20px; padding: 0 3px; border: 1px solid var(--frame); color: var(--text-1); font-weight: 700; font-size: 12px; }
449
+ .grade { display: inline-grid; place-items: center; min-width: 22px; height: 20px; padding: 0 3px; border: 1px solid var(--frame); color: var(--text-1); font: 11px / 1 var(--px); }
240
450
  .grade-F, .grade-D { background: var(--frame); color: var(--ground); }
241
- .grade.lg { min-width: 40px; height: 36px; font-size: 20px; }
451
+ .grade.lg { min-width: 44px; height: 44px; font-size: 22px; }
242
452
 
243
453
  .shade { color: var(--text-1); }
244
454
  .shade.track { color: var(--text-4); }
245
455
  .bars { display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content; gap: 3px 10px; align-items: baseline; }
246
- .bars .bar { white-space: pre; overflow: hidden; letter-spacing: -0.04em; }
456
+ .bars .bar { white-space: pre; overflow: hidden; letter-spacing: -0.04em; line-height: 1.3; max-height: 1.3em; }
457
+ .bars .num { font: 11px / 1 var(--px); }
247
458
  .stackbar { white-space: pre; overflow: hidden; letter-spacing: -0.04em; color: var(--text-1); line-height: 1.2; }
248
459
  svg.spark { display: block; width: 140px; max-width: 100%; height: 26px; }
249
460
  .spark .s-line { fill: none; stroke: var(--series); stroke-width: 1.5; }
250
461
  .spark .s-dot { fill: var(--series); }
462
+ svg.chart { display: block; width: 100%; height: auto; }
251
463
  .chart .grid-line { stroke: var(--hair); stroke-width: 1; }
464
+ .chart .axis { stroke: var(--frame); stroke-width: 1; }
465
+ .chart .tick { fill: var(--text-3); font: 11px var(--px); }
466
+ .chart .area-total { fill: url(#pt-hatch); }
467
+ .chart .line-total { fill: none; stroke: var(--series); stroke-width: 1.5; }
468
+ .chart .line-severe { fill: none; stroke: var(--frame); stroke-width: 1; stroke-dasharray: 3 3; }
469
+ .chart .dot { fill: var(--ground); stroke: var(--series); stroke-width: 1.5; }
252
470
  .hatch-line { stroke: var(--series); stroke-width: 1; opacity: 0.55; }
253
471
  .dither-dot { fill: var(--frame); }
254
472
  .hatch-ink { stroke: var(--frame); stroke-width: 1; }
255
473
 
256
- /* ── Centre ────────────────────────────────────────────────────────────── */
474
+ /* ── Centre: the document ──────────────────────────────────────────────── */
257
475
 
258
- .toolbar {
259
- display: flex;
260
- align-items: center;
261
- gap: 8px;
262
- min-height: 48px;
263
- padding: 8px 14px;
264
- border-bottom: 1px solid var(--rule);
265
- }
266
- .toolbar .title { color: var(--text-1); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
267
- .toolbar .grow { flex: 1; }
268
- .toolbar input[type="search"] { width: 220px; }
269
- .stream { flex: 1; overflow-y: auto; }
476
+ .stream { flex: 1; min-height: 0; overflow-y: auto; }
270
477
  .col { max-width: 920px; margin: 0 auto; padding: 22px 24px 40px; }
271
478
 
272
479
  .head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 6px 16px; align-items: center; margin-bottom: 18px; }
273
- .head h1 { margin: 0; font-size: 16px; color: var(--text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
480
+ .head h1 { margin: 0; font: 22px / 1.1 var(--px); color: var(--text-1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
274
481
  .head .meta { grid-column: 2; color: var(--text-3); font-size: 12px; }
275
482
  .head .right-bits { grid-row: 1 / span 2; grid-column: 3; display: grid; justify-items: end; gap: 4px; }
276
- .head .counts { color: var(--text-2); font-size: 12px; }
277
- .head .counts b { color: var(--text-1); }
483
+ .head .counts { color: var(--text-2); font: 11px / 1 var(--px); }
484
+ .head .counts b { color: var(--text-1); font-weight: 400; }
278
485
 
279
- .section { margin-top: 22px; }
280
- .section > h2 { margin: 0 0 8px; font-size: 11px; font-weight: 400; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-3); display: flex; justify-content: space-between; gap: 12px; }
281
- .rows { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hair); }
486
+ .section { margin-top: 24px; }
487
+ .section > h2 { margin: 0 0 8px; font: 11px / 1 var(--px); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3); display: flex; justify-content: space-between; gap: 12px; }
488
+ .rows { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--frame); }
282
489
  .rows > li { border-bottom: 1px solid var(--hair); }
490
+ .rows > li:last-child { border-bottom-color: var(--frame); }
283
491
  .row {
284
492
  all: unset;
285
493
  box-sizing: border-box;
@@ -289,37 +497,74 @@ svg.spark { display: block; width: 140px; max-width: 100%; height: 26px; }
289
497
  align-items: baseline;
290
498
  width: 100%;
291
499
  padding: 8px 8px;
292
- cursor: pointer;
500
+ cursor: default;
293
501
  color: var(--text-1);
294
502
  }
295
- .row:hover { background: var(--wash); }
503
+ .row:hover { box-shadow: inset 0 0 0 1px var(--rule); }
296
504
  .row:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
297
- .row[aria-current="true"] { background: var(--accent); color: var(--on-accent); }
505
+ .row[aria-current="true"] { background: var(--accent); color: var(--on-accent); box-shadow: none; }
298
506
  .row[aria-current="true"] * { color: var(--on-accent) !important; }
299
507
  .row[aria-current="true"] .sev::before { border-color: var(--on-accent); }
508
+ .row[aria-current="true"] .sev-critical::before { background: var(--on-accent); }
509
+ .row[aria-current="true"] .sev-high::before { background: repeating-linear-gradient(45deg, var(--on-accent) 0 1.5px, transparent 1.5px 3px); }
300
510
  .row .where { grid-column: 2 / -1; color: var(--text-3); font-size: 12px; overflow-wrap: anywhere; }
301
- .row .tag { color: var(--text-3); font-size: 11px; }
511
+ .row .tag { color: var(--text-3); font: 11px / 1 var(--px); }
302
512
  .row.b .t { font-weight: 700; }
513
+ .row.static { cursor: auto; }
514
+ .row.static:hover { box-shadow: none; }
515
+
516
+ /* Folder tabs: the selected one opens onto the page below it. */
517
+ .tabs { display: flex; align-items: flex-end; margin: 6px 0 4px; overflow-x: auto; scrollbar-width: none; }
518
+ .tab { flex: none; }
519
+ .tab {
520
+ font: 11px / 1 var(--px);
521
+ min-height: 28px;
522
+ padding: 0 14px;
523
+ margin-right: -1px;
524
+ border: 1px solid var(--frame);
525
+ background: var(--ground);
526
+ color: var(--text-2);
527
+ cursor: default;
528
+ white-space: nowrap;
529
+ }
530
+ .tab:hover { color: var(--text-1); box-shadow: inset 0 0 0 1px var(--frame); }
531
+ .tab[aria-selected="true"] { color: var(--ground); background: var(--frame); box-shadow: none; }
532
+ .tab:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; position: relative; z-index: 1; }
533
+ .tab-rest { flex: 1; border-bottom: 1px solid var(--frame); align-self: stretch; }
534
+ .tab-panel { min-height: 200px; }
535
+ .section .label { font: 11px / 1 var(--px); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3); margin: 14px 0 6px; }
536
+ .tab-panel > .section:first-child { margin-top: 18px; }
537
+ .actions.start { justify-content: flex-start; }
303
538
  .cols2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 24px; }
304
539
  .mini { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
305
540
  .mini li { display: flex; gap: 8px; align-items: baseline; min-width: 0; color: var(--text-1); }
306
541
  .mini li .grow { flex: 1; min-width: 0; overflow-wrap: anywhere; }
542
+ .mini li.stacked { display: grid; gap: 3px; }
543
+ .chips { display: flex; flex-wrap: wrap; gap: 3px; }
307
544
  .muted { color: var(--text-3); }
308
545
  .t1 { color: var(--text-1); }
309
- .ok { color: var(--ok); }
546
+ .ok { color: var(--text-1); }
310
547
  .bad { color: var(--text-1); font-weight: 700; }
311
548
  .blue { color: var(--accent-ink); }
312
549
  .empty { color: var(--text-3); margin: 6px 0; }
313
550
 
314
- /* The bold move: the CLI's blue wordmark, on the empty state only. */
315
- .wordmark { margin: 0 0 12px; font-size: clamp(5px, 1.1vw, 11px); line-height: 1.08; white-space: pre; user-select: none; overflow: hidden; }
316
- .wordmark .fill { color: var(--accent); text-shadow: var(--logo-glow); }
317
- .wordmark .edge { color: var(--accent-ink); }
318
- :root[data-theme="light"] .wordmark .edge { color: var(--text-1); }
319
- .start h1 { margin: 0 0 6px; font-size: 16px; color: var(--text-1); }
320
- .start .steps { display: grid; gap: 14px; margin-top: 18px; }
321
- .start .step { display: grid; grid-template-columns: 3ch minmax(0, 1fr) auto; gap: 2px 12px; align-items: baseline; padding: 12px 0; border-top: 1px solid var(--hair); }
322
- .start .step .n { color: var(--text-3); }
551
+ /* Sentences for people get the sans. */
552
+ .prose, .empty, .plain p, .start > p, .start .step p, .detail section > p, .dialog-body p.prose {
553
+ font-family: var(--sans);
554
+ font-size: 14px;
555
+ line-height: 1.45;
556
+ }
557
+ .empty { font-size: 13px; }
558
+
559
+ /* First run: the wordmark large, a quiet field, one primary action. */
560
+ .start { display: grid; gap: 14px; }
561
+ .start > .logotype.xl { width: 279px; height: 63px; color: var(--text-1); margin-top: 12px; }
562
+ .start > .halftone-wrap { height: 120px; border-block: 1px solid var(--frame); }
563
+ .start > p { margin: 0; }
564
+ .start h1 { margin: 6px 0 0; font: 22px / 1.1 var(--px); color: var(--text-1); }
565
+ .start .steps { display: grid; gap: 0; margin-top: 18px; border-top: 1px solid var(--frame); }
566
+ .start .step { display: grid; grid-template-columns: 3ch minmax(0, 1fr) auto; gap: 2px 12px; align-items: baseline; padding: 12px 0; border-bottom: 1px solid var(--hair); }
567
+ .start .step .n { color: var(--text-3); font-family: var(--px); }
323
568
  .start .step h3 { margin: 0; font-size: 13px; color: var(--text-1); }
324
569
  .start .step p { grid-column: 2; margin: 0; color: var(--text-3); }
325
570
 
@@ -327,123 +572,248 @@ svg.spark { display: block; width: 140px; max-width: 100%; height: 26px; }
327
572
  .transcript { font-size: 12.5px; line-height: 1.55; }
328
573
  .transcript .ln { white-space: pre-wrap; overflow-wrap: anywhere; min-height: 1.55em; color: var(--text-2); }
329
574
  .transcript .ln.cmd { color: var(--text-1); font-weight: 700; margin-top: 4px; }
330
- .transcript .ln.cmd::before { content: "$ "; color: var(--ok); }
575
+ .transcript .ln.cmd::before { content: "$ "; color: var(--text-3); }
331
576
  .transcript .ln.dim::before { content: "▸ "; color: var(--text-3); }
332
- .transcript .ln.ok { color: var(--ok); }
577
+ .transcript .ln.ok { color: var(--text-1); }
578
+ .transcript .ln.ok::before { content: "✓ "; }
333
579
  .transcript .ln.bad { color: var(--text-1); font-weight: 700; }
334
580
  .transcript .ln.bad::before { content: "[!] "; }
335
581
  .transcript .ln.hl { color: var(--text-1); font-weight: 700; margin-top: 10px; }
336
- .cursor { display: inline-block; width: 1ch; height: 1.15em; vertical-align: text-bottom; background: var(--ok); box-shadow: var(--glow); margin-left: 1px; animation: blink 1s steps(1, end) infinite; }
582
+ .cursor { display: inline-block; width: 1ch; height: 1.15em; vertical-align: text-bottom; background: var(--accent-ink); margin-left: 1px; animation: blink 1s steps(1, end) infinite; }
337
583
  @keyframes blink { 50% { opacity: 0; } }
338
584
  .run-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
339
- .run-head h1 { margin: 0; font-size: 15px; color: var(--text-1); }
340
-
341
- /* The approval: the one pinstriped window left, inline in the transcript. */
342
- .dialog { margin: 14px 0; border: 1px solid var(--frame); box-shadow: 2px 2px 0 var(--frame); background: var(--ground); }
343
- .dialog-bar { height: 18px; border-bottom: 1px solid var(--frame); display: flex; justify-content: center; align-items: center;
344
- background: repeating-linear-gradient(to bottom, var(--frame) 0 1px, transparent 1px 2px) 0 3px / 100% 11px no-repeat, var(--ground); }
345
- .dialog-bar span { padding: 0 8px; background: var(--ground); color: var(--text-1); font-size: 11px; font-weight: 700; }
346
- .dialog-body { padding: 12px 14px; display: grid; gap: 8px; }
585
+ .run-head h1 { margin: 0; font: 22px / 1.1 var(--px); color: var(--text-1); }
586
+ .run-head .grow, .actions .grow { flex: 1; }
587
+
588
+ /* The approval: a floating dialog, pinstriped because it waits on you. */
589
+ .dialog { margin: 14px 3px 14px 0; border: 1px solid var(--frame); box-shadow: 3px 3px 0 var(--frame); background: var(--ground); }
590
+ .dialog-bar { height: 22px; border-bottom: 1px solid var(--frame); display: flex; justify-content: center; align-items: center;
591
+ background: repeating-linear-gradient(to bottom, var(--frame) 0 1px, transparent 1px 2px) 0 5px / 100% 11px no-repeat, var(--ground); }
592
+ .dialog-bar span { padding: 0 10px; background: var(--ground); color: var(--text-1); font: 11px / 1 var(--px); }
593
+ .dialog-body { padding: 12px 14px; display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); }
594
+ .dialog-body > * { min-width: 0; }
347
595
  .dialog-body h3 { margin: 0; font-size: 13px; color: var(--text-1); }
348
596
  .dialog-body p { margin: 0; }
349
- .actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
597
+ .actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; align-items: center; }
350
598
  .kv { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 3px 14px; margin: 0; }
351
- .kv dt { color: var(--text-3); }
599
+ .kv dt { color: var(--text-3); font: 11px / 1.6 var(--px); }
352
600
  .kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; color: var(--text-1); }
353
- pre.block { margin: 6px 0 0; padding: 10px 12px; border: 1px solid var(--hair); background: var(--wash); white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-1); max-height: 320px; overflow: auto; font: inherit; font-size: 12px; }
601
+ pre.block { margin: 6px 0 0; padding: 10px 12px; border: 1px solid var(--frame); background: var(--ground); white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text-1); max-height: 320px; overflow: auto; font: 12px / 1.5 var(--mono); }
354
602
  pre.diff { white-space: pre; overflow-x: auto; }
355
603
  .diff .add { color: var(--text-1); font-weight: 700; }
356
604
  .diff .del { color: var(--text-3); text-decoration: line-through; text-decoration-color: var(--text-4); }
357
605
  .diff .hunk { color: var(--accent-ink); }
358
606
  .diff .meta { color: var(--text-3); }
359
607
 
360
- /* The prompt bar: one primary action. */
361
- .bar { border-top: 1px solid var(--rule); padding: 10px 14px 12px; background: var(--ground); }
362
- .bar-inner { max-width: 920px; margin: 0 auto; }
363
- .bar form { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; border: 1px solid var(--frame); border-radius: var(--radius-btn); padding: 6px 6px 6px 10px; }
364
- .bar form:focus-within { outline: 2px solid var(--focus); outline-offset: 1px; }
365
- .bar input[type="text"] { border: 0; padding: 4px 0; min-height: 28px; width: 100%; background: transparent; }
366
- .bar input[type="text"]:focus-visible { outline: none; }
367
- .bar .modes { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 8px; align-items: center; }
368
- .mode { font: inherit; font-size: 12px; min-height: 26px; padding: 0 10px; border: 1px solid var(--rule); border-radius: var(--radius-btn); background: var(--ground); color: var(--text-2); cursor: pointer; }
369
- .mode:hover { color: var(--text-1); border-color: var(--frame); }
370
- .mode[aria-pressed="true"] { background: var(--frame); color: var(--ground); border-color: var(--frame); }
371
- .bar .note { font-size: 11px; color: var(--text-3); margin-left: auto; }
372
- .bar .confirm { display: flex; gap: 8px; align-items: center; margin-top: 8px; font-size: 12px; color: var(--text-1); flex-wrap: wrap; }
373
- .bar .confirm input { flex: 1; min-width: 160px; }
374
- .bar .err { margin: 8px 0 0; color: var(--text-1); font-size: 12px; }
375
- .bar .err::before { content: "[!] "; font-weight: 700; }
376
-
377
- /* ── Right panel: stacked cards ────────────────────────────────────────── */
378
-
379
- .cards { display: grid; }
380
- .card { border-bottom: 1px solid var(--rule); }
381
- .card > summary { list-style: none; display: flex; align-items: center; gap: 8px; padding: 12px 14px; cursor: pointer; color: var(--text-1); font-weight: 700; font-size: 12px; }
382
- .card > summary::-webkit-details-marker { display: none; }
383
- .card > summary::before { content: "▸"; color: var(--text-3); font-weight: 400; }
384
- .card[open] > summary::before { content: "▾"; }
385
- .card > summary .aside { margin-left: auto; font-weight: 400; color: var(--text-3); font-size: 11px; }
386
- .card > summary:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
387
- .card-body { padding: 0 14px 14px; display: grid; gap: 8px; min-width: 0; }
388
- .card-body .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-3); margin: 6px 0 0; }
389
- .detail-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--rule); }
390
- .detail-head .grow { flex: 1; }
391
- .detail { padding: 14px; display: grid; gap: 12px; }
608
+ /* ── Right: the inspector ──────────────────────────────────────────────── */
609
+
610
+ .detail { padding: 14px; display: grid; gap: 14px; grid-template-columns: minmax(0, 1fr); align-content: start; }
611
+ .detail > * { min-width: 0; }
392
612
  .detail h2 { margin: 0; font-size: 14px; color: var(--text-1); }
393
- .detail section > .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-3); margin: 0 0 4px; }
394
- .plain { border: 1px solid var(--frame); padding: 10px 12px; display: grid; gap: 6px; }
395
- .plain p { margin: 0; }
613
+ .detail section > .label { font: 11px / 1 var(--px); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3); margin: 0 0 6px; }
614
+ .detail section > p { margin: 0; color: var(--text-1); }
615
+ .plain { border: 1px solid var(--frame); box-shadow: 3px 3px 0 var(--frame); padding: 10px 12px; display: grid; gap: 8px; margin-right: 3px; }
616
+ .plain p { margin: 0; color: var(--text-1); }
396
617
  .plain b { color: var(--text-1); }
397
- .seg { display: inline-flex; border: 1px solid var(--rule); border-radius: var(--radius-btn); overflow: hidden; }
398
- .seg label { position: relative; cursor: pointer; }
399
- .seg label + label { border-left: 1px solid var(--rule); }
618
+ .seg { display: inline-flex; border: 1px solid var(--frame); }
619
+ .seg label { position: relative; cursor: default; }
620
+ .seg label + label { border-left: 1px solid var(--frame); }
400
621
  .seg input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
401
- .seg span { display: flex; align-items: center; min-height: 28px; padding: 0 12px; color: var(--text-1); }
622
+ .seg span { display: flex; align-items: center; min-height: 28px; padding: 0 12px; color: var(--text-1); font: 11px / 1 var(--px); }
402
623
  .seg input:checked + span { background: var(--frame); color: var(--ground); }
403
624
  .seg input:focus-visible + span { outline: 2px solid var(--focus); outline-offset: -4px; }
404
- .switch { font: inherit; display: inline-flex; gap: 10px; align-items: center; min-height: 28px; padding: 0 12px; background: var(--ground); color: var(--text-1); border: 1px solid var(--rule); border-radius: var(--radius-btn); cursor: pointer; }
405
- .switch[aria-checked="true"] .state { color: var(--ok); font-weight: 700; }
406
- kbd { font: inherit; font-size: 11px; border: 1px solid var(--rule); border-radius: 3px; padding: 0 4px; color: var(--text-1); }
625
+ .switch { font: 11px / 1 var(--px); display: inline-flex; gap: 10px; align-items: center; min-height: 28px; padding: 0 4px 0 12px; background: var(--ground); color: var(--text-1); border: 1px solid var(--frame); border-radius: 0; cursor: default; }
626
+ .switch .state { padding: 4px 8px; border: 1px solid var(--frame); }
627
+ .switch[aria-checked="true"] .state { background: var(--frame); color: var(--ground); }
628
+ kbd { font: 11px / 1 var(--px); border: 1px solid var(--frame); padding: 1px 4px; color: var(--text-1); }
407
629
 
408
- * { scrollbar-color: var(--rule) var(--ground); scrollbar-width: thin; }
630
+ * { scrollbar-color: var(--frame) var(--ground); scrollbar-width: thin; }
409
631
 
410
- /* ── Narrow: side panels become drawers ────────────────────────────────── */
632
+ /* ── Narrow: side windows become drawers ───────────────────────────────── */
411
633
 
412
634
  @media (max-width: 1180px) {
413
- .app, .app.no-right { grid-template-columns: var(--left-w) minmax(0, 1fr); }
414
- .app.no-left, .app.no-left.no-right { grid-template-columns: 0 minmax(0, 1fr); }
415
- .right { position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 92vw); z-index: 30; box-shadow: -2px 0 0 var(--frame); }
416
- .app.no-right .right { display: none; }
417
- .app:not(.no-right) .scrim { display: block; position: fixed; inset: 0; z-index: 25; background: rgba(0, 0, 0, 0.45); }
635
+ .app, .app.no-right { grid-template-columns: var(--left-w) minmax(0, 1fr); grid-template-areas: "left center"; }
636
+ .app.no-left, .app.no-left.no-right { grid-template-columns: minmax(0, 1fr); grid-template-areas: "center"; }
637
+ .right { position: fixed; top: calc(27px + var(--desk-gap)); right: var(--desk-gap); bottom: var(--desk-gap); width: min(420px, 92vw); z-index: 30; box-shadow: 3px 3px 0 var(--frame); }
638
+ .app:not(.no-right) .scrim { display: block; position: fixed; inset: 0; z-index: 25; background: var(--scrim); }
639
+ }
640
+ /* ── Floating windows: New scan, About ─────────────────────────────────── */
641
+
642
+ dialog.window {
643
+ width: min(560px, calc(100vw - 32px));
644
+ max-height: calc(100dvh - 64px);
645
+ padding: 0;
646
+ border: 1px solid var(--frame);
647
+ background: var(--ground);
648
+ color: var(--text-2);
649
+ box-shadow: 3px 3px 0 var(--frame);
650
+ overflow: hidden;
651
+ display: flex;
652
+ flex-direction: column;
418
653
  }
654
+ dialog.window:not([open]) { display: none; }
655
+ /* A heavy blur behind every floating window: the page is there, but out of reach. */
656
+ dialog.window::backdrop {
657
+ background: rgba(0, 0, 0, 0.35);
658
+ -webkit-backdrop-filter: blur(48px) saturate(0.6);
659
+ backdrop-filter: blur(48px) saturate(0.6);
660
+ }
661
+ :root[data-theme="light"] dialog.window::backdrop { background: rgba(253, 253, 250, 0.35); }
662
+ @media (prefers-reduced-transparency: reduce) {
663
+ dialog.window::backdrop { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(0, 0, 0, 0.7); }
664
+ }
665
+ dialog.window > .win-bar { background: var(--frame); color: var(--ground); padding-left: 4px; }
666
+ dialog.window > .win-bar .icon.close-box::before { background: var(--frame); border-color: var(--ground); }
667
+ dialog.window > .win-bar .icon.close-box:hover::before { background: var(--ground); }
668
+ .window-body { padding: 14px 16px 16px; overflow-y: auto; }
669
+ .form fieldset { margin: 0; padding: 0; border: 0; min-width: 0; }
670
+ .form legend, .form .field > label { display: block; margin: 0 0 6px; padding: 0; font: 11px / 1 var(--px); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-3); }
671
+ .form .field > label .muted { text-transform: none; letter-spacing: 0; }
672
+ .form .field.confirm > label { text-transform: none; letter-spacing: 0; font: 14px / 1.45 var(--sans); color: var(--text-1); }
673
+ .choices { display: grid; border: 1px solid var(--frame); }
674
+ .choice { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 2px 8px; align-items: baseline; padding: 8px 10px; cursor: default; }
675
+ .choice + .choice { border-top: 1px solid var(--hair); }
676
+ .choice:hover { box-shadow: inset 0 0 0 1px var(--rule); }
677
+ .choice:has(input:checked) { box-shadow: inset 0 0 0 1px var(--frame); }
678
+ .choice .t1, .check .t1 { font-weight: 700; }
679
+ .note { display: block; color: var(--text-3); font-size: 13px; }
680
+ .field { display: grid; gap: 4px; margin-top: 14px; }
681
+ .field select, .field input { width: 100%; }
682
+ .field p { margin: 0; }
683
+ .check { display: flex; align-items: baseline; gap: 2px; margin-top: 14px; }
684
+ .check .note { display: inline; }
685
+ .form .actions { margin-top: 18px; }
686
+ .err { margin: 12px 0 0; color: var(--text-1); }
687
+ .err::before { content: "[!] "; font-weight: 700; }
688
+ .about { display: grid; gap: 14px; justify-items: start; }
689
+ .about .logotype.xl { color: var(--text-1); margin: 6px 0 2px; }
690
+ .about p { margin: 0; }
691
+ .about .actions { justify-self: stretch; }
692
+
419
693
  @media (max-width: 760px) {
420
- .app, .app.no-left, .app.no-right, .app.no-left.no-right { grid-template-columns: minmax(0, 1fr); }
421
- .left { position: fixed; top: 0; left: 0; bottom: 0; width: min(320px, 88vw); z-index: 30; box-shadow: 2px 0 0 var(--frame); }
422
- .app.no-left .left { display: none; }
423
- .app:not(.no-left) .scrim { display: block; position: fixed; inset: 0; z-index: 25; background: rgba(0, 0, 0, 0.45); }
694
+ .app, .app.no-left, .app.no-right, .app.no-left.no-right { grid-template-columns: minmax(0, 1fr); grid-template-areas: "center"; }
695
+ .left { position: fixed; top: calc(27px + var(--desk-gap)); left: var(--desk-gap); bottom: var(--desk-gap); width: min(320px, 88vw); z-index: 30; box-shadow: 3px 3px 0 var(--frame); }
696
+ .app:not(.no-left) .scrim { display: block; position: fixed; inset: 0; z-index: 25; background: var(--scrim); }
424
697
  .col { padding: 16px 14px 28px; }
425
- .toolbar { padding: 8px 10px; }
426
- .toolbar input[type="search"] { width: 120px; }
698
+ .win-bar input[type="search"] { width: 110px; }
427
699
  .row { grid-template-columns: minmax(0, 1fr) auto; }
428
700
  .row .sev { grid-column: 1 / -1; }
429
701
  .row .where { grid-column: 1 / -1; }
430
702
  .cols2 { grid-template-columns: minmax(0, 1fr); }
431
703
  .head { grid-template-columns: auto minmax(0, 1fr); }
432
704
  .head .right-bits { grid-row: auto; grid-column: 1 / -1; justify-items: start; }
433
- .bar .note { display: none; }
705
+ .menu-title.logo .logotype { display: none; }
706
+ .menu-title.logo .mark { display: inline-block; }
707
+ .menu-title { padding: 0 7px; }
708
+ .status.dim { display: none; }
434
709
  }
435
710
  @media (pointer: coarse) {
436
- :root { --target: 44px; }
711
+ :root { --target: 44px; --bar-h: 44px; }
437
712
  .item, .row { min-height: 44px; }
438
713
  }
439
714
  @media (prefers-reduced-motion: reduce) {
440
715
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
441
- .cursor { opacity: 1; }
716
+ .cursor, .item .dot.live { opacity: 1; }
442
717
  }
443
- .run-head .grow, .actions .grow { flex: 1; }
444
- .card-body .head { margin: 0; grid-template-columns: auto minmax(0, 1fr); }
445
- .card-body, .detail, .dialog-body { grid-template-columns: minmax(0, 1fr); }
446
- .card-body > *, .detail > *, .dialog-body > * { min-width: 0; }
447
- .mini li.stacked { display: grid; gap: 3px; }
448
- .chips { display: flex; flex-wrap: wrap; gap: 3px; }
449
- .bars .bar { line-height: 1.3; max-height: 1.3em; }
718
+
719
+ /* ── Phase 3: verdict, simple list, triage matrix, setup, approval ────── */
720
+
721
+ /* The verdict: one sentence and one button. Critical inverts the box, so the
722
+ loudest state is the one with the most at stake. */
723
+ .verdict { margin: 0 3px 8px 0; padding: 14px 16px; border: 1px solid var(--frame); box-shadow: 3px 3px 0 var(--frame); display: grid; gap: 6px; }
724
+ .verdict p { margin: 0; }
725
+ .verdict-text { font: 600 20px / 1.3 var(--sans); color: var(--text-1); }
726
+ .verdict-sub { font: 13px / 1.4 var(--sans); }
727
+ .verdict .actions { margin-top: 6px; }
728
+ .verdict.v-stop { background: var(--frame); }
729
+ .verdict.v-stop .verdict-text { color: var(--ground); }
730
+ .verdict.v-stop .verdict-sub { color: var(--ground); opacity: 0.75; }
731
+ .verdict.v-stop .btn:not(.primary) { border-color: var(--ground); }
732
+ .verdict.v-stop .btn.primary { box-shadow: 0 0 0 2px var(--frame), 0 0 0 3px var(--ground); }
733
+
734
+ .rows.simple .row { grid-template-columns: 9em minmax(0, 1fr) auto; padding: 12px 8px; align-items: center; }
735
+ .rows.simple .row .t { font: 15px / 1.35 var(--sans); }
736
+ .rows.simple .row.b .t { font-weight: 600; }
737
+ p.more { margin-top: 28px; font-size: 13px; }
738
+ details.more { display: grid; gap: 10px; }
739
+ details.more > summary { cursor: default; font: 11px / 1 var(--px); text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-1); padding: 8px 0; list-style: none; }
740
+ details.more > summary::-webkit-details-marker { display: none; }
741
+ details.more > summary::before { content: "▸ "; }
742
+ details.more[open] > summary::before { content: "▾ "; }
743
+ details.more > summary:hover { text-decoration: underline; }
744
+ details.more[open] > * + * { margin-top: 10px; }
745
+ .linkish { font: inherit; text-transform: inherit; letter-spacing: inherit; color: var(--text-1); background: none; border: 0; padding: 0; text-decoration: underline; cursor: default; }
746
+
747
+ /* Triage matrix: severity down, how sure across. */
748
+ .matrix { border-collapse: collapse; width: 100%; max-width: 560px; }
749
+ .matrix th { font: 11px / 1 var(--px); color: var(--text-3); font-weight: 400; text-align: left; padding: 0 8px 6px 0; }
750
+ .matrix thead th { text-align: center; }
751
+ .matrix tbody th { padding: 0 12px 0 0; white-space: nowrap; }
752
+ .matrix td { padding: 0; border: 1px solid var(--frame); }
753
+ .matrix .cell { display: grid; place-items: center; width: 100%; height: 36px; border: 0; background: var(--ground); color: var(--text-1); cursor: default; }
754
+ .matrix .cell[disabled] { color: var(--text-4); }
755
+ .matrix .cell .n { font: 11px / 1 var(--px); padding: 3px 6px; background: var(--ground); }
756
+ .matrix .cell.fill-critical { background: var(--pt-solid); }
757
+ .matrix .cell.fill-high { background: var(--pt-hatch); }
758
+ .matrix .cell.fill-medium { background: var(--pt-half); }
759
+ .matrix .cell.fill-low { background: var(--pt-sparse); }
760
+ .matrix .cell:not([disabled]):hover .n { background: var(--frame); color: var(--ground); }
761
+ .matrix .cell[aria-pressed="true"] { box-shadow: inset 0 0 0 3px var(--accent); }
762
+ .matrix .cell[aria-pressed="true"] .n { background: var(--accent); color: var(--on-accent); }
763
+ .matrix .cell:focus-visible { outline: 2px solid var(--focus); outline-offset: -4px; }
764
+
765
+ /* Setup: three screens in one window. */
766
+ dialog.window.wide { width: min(640px, calc(100vw - 32px)); }
767
+ .setup { display: grid; gap: 14px; justify-items: stretch; }
768
+ .setup > .logotype.xl { justify-self: start; color: var(--text-1); margin: 4px 0 2px; }
769
+ .setup h2 { margin: 0; font: 600 20px / 1.3 var(--sans); color: var(--text-1); }
770
+ .setup p { margin: 0; }
771
+ .setup .gate { border-left: 3px solid var(--frame); padding-left: 10px; color: var(--text-1); }
772
+ .setup .form .field:first-child { margin-top: 0; }
773
+ .choice.big { padding: 14px 12px; }
774
+ .choice.big .t1 { font: 600 16px / 1.3 var(--sans); }
775
+ .win-bar .progress { font: 11px / 1 var(--px); padding-right: 6px; }
776
+
777
+ /* The approval window: pinstripes mean it waits on you. */
778
+ .win-bar.striped,
779
+ dialog.window > .win-bar.striped {
780
+ background: repeating-linear-gradient(to bottom, var(--frame) 0 1px, transparent 1px 2px) 0 8px / 100% 11px no-repeat, var(--ground);
781
+ color: var(--text-1);
782
+ }
783
+ .win-bar.striped .title { background: var(--ground); padding: 0 8px; margin: 0 auto; }
784
+ dialog.window > .win-bar.striped .icon.close-box::before { background: var(--ground); border-color: var(--frame); }
785
+ dialog.window > .win-bar.striped .icon.close-box { background: var(--ground); }
786
+ .approval { display: grid; gap: 10px; grid-template-columns: minmax(0, 1fr); }
787
+ .approval > * { min-width: 0; }
788
+ .approval h3 { margin: 0; font: 600 16px / 1.35 var(--sans); color: var(--text-1); }
789
+ .approval p { margin: 0; }
790
+ .approval .actions { margin-top: 6px; }
791
+ .approval kbd, .btn kbd { border-color: currentColor; }
792
+ .wait-note { display: flex; align-items: center; gap: 10px; margin: 14px 3px 14px 0; padding: 10px 12px; border: 1px solid var(--frame); box-shadow: 3px 3px 0 var(--frame); color: var(--text-1); flex-wrap: wrap; }
793
+
794
+ /* Step gutter: every line done gets a filled square; the line being worked on is hollow. */
795
+ .transcript.gutter { padding-left: 20px; }
796
+ .transcript.gutter .ln { position: relative; }
797
+ .transcript.gutter .ln::after { content: ""; position: absolute; left: -20px; top: 0.4em; width: 9px; height: 9px; border: 1px solid var(--frame); background: var(--frame); }
798
+ .transcript.gutter:not(.done) .ln:last-child::after { background: none; animation: blink 1s steps(1, end) infinite; }
799
+
800
+ @media (max-width: 760px) {
801
+ .rows.simple .row { grid-template-columns: minmax(0, 1fr) auto; }
802
+ .verdict-text { font-size: 18px; }
803
+ }
804
+
805
+ /* ── Phase 4: the halftone field ───────────────────────────────────────── */
806
+
807
+ .halftone-wrap { position: relative; min-height: 0; overflow: hidden; }
808
+ .halftone { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--frame); }
809
+ .inspector-empty { display: flex; flex-direction: column; gap: 14px; }
810
+ .inspector-empty > .halftone-wrap { flex: 1; min-height: 160px; margin: 0 -14px; border-block: 1px solid var(--frame); }
811
+ .field-note { margin: 0; font-size: 12px; }
812
+
813
+ /* The desktop between the windows: denser dots for more serious open risk,
814
+ plain paper when nothing is open. */
815
+ .app[data-risk="0"] { background: var(--ground); }
816
+ .app[data-risk="1"] { background: conic-gradient(from 90deg at 1px 1px, transparent 75%, var(--desk) 0) 0 0 / 6px 6px, var(--ground); }
817
+ .app[data-risk="2"] { background: conic-gradient(from 90deg at 1px 1px, transparent 75%, var(--desk) 0) 0 0 / 4px 4px, var(--ground); }
818
+ .app[data-risk="3"] { background: conic-gradient(from 90deg at 1px 1px, transparent 75%, var(--desk) 0) 0 0 / 3px 3px, var(--ground); }
819
+ .app[data-risk="4"] { background: conic-gradient(from 90deg at 1px 1px, transparent 50%, var(--desk) 0 75%, transparent 0) 0 0 / 2px 2px, conic-gradient(var(--desk) 25%, transparent 0) 0 0 / 2px 2px, var(--ground); }