@fasl-work/caos-app-shell 0.7.2 → 0.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.
package/styles.css CHANGED
@@ -1,9 +1,26 @@
1
1
  @import "katex/dist/katex.min.css";
2
2
 
3
- /* @fasl-work/caos-app-shell, shared design system (ADR-0016 + visual-standards palette).
4
- * Theming via CSS custom properties keyed on data-theme on <html> (set pre-paint by an inline
5
- * script in index.html, kept in sync by the shell theme store). Import once per app:
6
- * import "@fasl-work/caos-app-shell/styles.css"; */
3
+ /* @fasl-work/caos-app-shell: the design system of every CAOS / Faena app (ADR-0016, ADR-0017, ADR-0071, ADR-0078).
4
+ * Import once per app: import "@fasl-work/caos-app-shell/styles.css";
5
+ *
6
+ * The stylesheet is written in ordered layers, and each class is defined in ONE place (layer 6 once defined
7
+ * `.tablist` twice and `.subtablist` wrapping and then forced to one row; the later rule always won):
8
+ * 1 tokens colours per theme, fonts, type, space, radii, layout sizes, icon sizes, z-order
9
+ * 2 base reset, document, links, focus, reduced motion
10
+ * 3 frame .app-shell, header, main, footer, skip link; the modes default, fixed and contain
11
+ * 4 routes the three route bodies: document (.page-body), workbench (.caos-wb), surface (.caos-surface)
12
+ * 5 navigation every row of links or tabs: one line, never shrinking, faded ends when it scrolls
13
+ * 6 content prose, page head, reading measure, grids, callouts, equations, figures, references, diagrams
14
+ * 7 controls buttons, selects, ranges, chips, knobs, native controls, icons
15
+ * 8 case the case selector
16
+ * 9 workbench rail, instrument, groups, variants, readouts, gauges, verdicts, plot cards, stages, tables
17
+ * 10 frames the focus frame (ADR-0070), the authenticated console frame, the architecture modal
18
+ *
19
+ * Breakpoints are one set, used everywhere: 480 px (phone), 760 px (compact header, single columns), 900 px (the
20
+ * workbench stacks and the document scrolls). The gate's large screen is 1280 px (ADR-0071 Verification). A product
21
+ * composes these classes and never redefines them (reserved-classes.json, ADR-0078). */
22
+
23
+ /* == 1 tokens =================================================================================================== */
7
24
 
8
25
  :root,
9
26
  [data-theme="dark"] {
@@ -13,7 +30,7 @@
13
30
  --color-border: #30363d;
14
31
  --color-fg: #c9d1d9;
15
32
  --color-fg-subtle: #9aa6b2;
16
- --color-fg-faint: #6c7785;
33
+ --color-fg-faint: #828b97;
17
34
  --color-accent: #58a6ff;
18
35
  --color-accent-fg: #0d1117;
19
36
  --color-accent-soft: #132036;
@@ -23,10 +40,6 @@
23
40
  --color-warn: #d29922;
24
41
  --color-bad: #f85149;
25
42
  --color-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 6px 16px rgba(0, 0, 0, 0.45);
26
- --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
27
- --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
28
- --maxw: 1200px; /* reading width, for PROSE pages */
29
- --maxw-wide: 100%; /* instrument width, for workbench pages */
30
43
  color-scheme: dark;
31
44
  }
32
45
 
@@ -38,22 +51,73 @@
38
51
  --color-border: #d0d7de;
39
52
  --color-fg: #1f2328;
40
53
  --color-fg-subtle: #57606a;
41
- --color-fg-faint: #6e7781;
54
+ --color-fg-faint: #656d77;
42
55
  --color-accent: #0969da;
43
56
  --color-accent-fg: #ffffff;
44
57
  --color-accent-soft: #ddf4ff;
45
- --color-accent-2: #0a7c8a;
58
+ --color-accent-2: #0a7886;
46
59
  --color-magenta: #bf3989;
47
60
  --color-good: #1a7f37;
48
- --color-warn: #9a6700;
61
+ --color-warn: #946300;
49
62
  --color-bad: #cf222e;
50
63
  --color-shadow: 0 1px 2px rgba(31, 35, 40, 0.06), 0 4px 10px rgba(31, 35, 40, 0.04);
51
64
  }
52
65
 
66
+ /* Theme-independent tokens. A product reads them (`var(--rail-w)`) instead of writing the numbers again. */
67
+ :root {
68
+ --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
69
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
70
+ /* type scale */
71
+ --text-xs: 0.74rem;
72
+ --text-sm: 0.84rem;
73
+ --text-md: 0.92rem;
74
+ --text-base: 0.98rem;
75
+ --text-lg: 1.08rem;
76
+ --text-xl: 1.3rem;
77
+ --text-2xl: 1.7rem;
78
+ /* spacing */
79
+ --space-1: 0.25rem;
80
+ --space-2: 0.5rem;
81
+ --space-3: 0.75rem;
82
+ --space-4: 1rem;
83
+ --space-5: 1.5rem;
84
+ --space-6: 2.25rem;
85
+ /* radii */
86
+ --radius-sm: 6px;
87
+ --radius-md: 8px;
88
+ --radius-lg: 10px;
89
+ --radius-pill: 999px;
90
+ /* layout */
91
+ --maxw: 1200px; /* the document width (ADR-0017 s1) */
92
+ --maxw-wide: 100%; /* the workbench and surface width (ADR-0071 rule 2) */
93
+ --measure: 70ch; /* the narrow column (.measure) and the lede */
94
+ --measure-text: 78ch; /* the longest line of a text block inside a document */
95
+ --header-h: 56px;
96
+ --rail-w: clamp(248px, 19vw, 340px);
97
+ --fade: 28px; /* the faded end of a scrolling row (lib/overflow.ts reveals an item this far inside) */
98
+ /* icons: lucide's svg.lucide is sized by its context, so a product never passes a size */
99
+ --icon-sm: 14px;
100
+ --icon-md: 16px;
101
+ --icon-lg: 18px;
102
+ /* z-order */
103
+ --z-header: 50;
104
+ --z-focus: 100;
105
+ --z-modal: 1000;
106
+ }
107
+
108
+ /* == 2 base ===================================================================================================== */
109
+
53
110
  * { box-sizing: border-box; }
54
111
  /* Known shell defect 1 (fixed 0.7.0): `height: 100%` with `overflow-x: hidden` made html and body scroll containers
55
112
  pinned to the viewport, so a tall page could not scroll by script or anchor. Content-height documents. */
56
113
  html, body, #root { height: auto; min-height: 100%; }
114
+ /* The contained layout: a `nowrap` row sizes its container to its intrinsic width unless every ancestor may shrink;
115
+ the document never scrolls sideways (measured on ChargeCascade: scrollWidth 1817 on a 1600 viewport).
116
+ `clip`, not `hidden` (known shell defect 26, 0.8.0): `overflow-x: hidden` on body computes its overflow-y to auto,
117
+ so body became a scroll container that never scrolls, and nothing in a scrolling document could stick to the
118
+ viewport: not the site header, not the vertical sub-tab list (found by G12 at 768 px). `hidden` stays as the
119
+ fallback for a browser without `clip`. */
120
+ html, body { overflow-x: hidden; overflow-x: clip; }
57
121
  /* Known shell defect 2 (fixed 0.7.0): any author `display` un-hid a `[hidden]` panel. */
58
122
  [hidden] { display: none !important; }
59
123
  body {
@@ -70,168 +134,272 @@ a:hover { text-decoration: underline; }
70
134
  button { font: inherit; cursor: pointer; color: inherit; }
71
135
  h1, h2, h3, h4 { line-height: 1.2; font-weight: 650; letter-spacing: -0.01em; }
72
136
  code, kbd { font-family: var(--font-mono); }
137
+ :where(a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])):focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: 4px; }
138
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }
73
139
 
74
- /* ── Layout primitives ── */
75
- .app-shell { min-height: 100vh; display: flex; flex-direction: column; background: var(--color-bg); color: var(--color-fg); }
76
- .container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: 1.5rem; }
77
- .page { flex: 1 1 auto; width: 100%; }
78
- .page-body { width: 100%; max-width: var(--maxw); margin-inline: auto; padding: 2.25rem 1.5rem 3rem; }
79
- /* A WORKBENCH is not prose. Capping an instrument at a 1200px reading measure wastes every pixel beyond
80
- it, which is why the App surfaces across the line render their visualization as a thumbnail on a wide
81
- display. Prose pages keep the reading width; the App route opts into the full viewport. */
82
- .page-body.wide { max-width: var(--maxw-wide); padding-inline: 1.25rem; }
140
+ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
83
141
  .muted { color: var(--color-fg-subtle); }
84
142
  .faint { color: var(--color-fg-faint); }
85
143
  .mono { font-family: var(--font-mono); font-size: 0.9em; }
86
- .small { font-size: 0.84rem; }
87
- .card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 10px; padding: 1.25rem 1.5rem; box-shadow: var(--color-shadow); }
88
- .badge { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; padding: 0.15rem 0.55rem; border-radius: 999px; border: 1px solid var(--color-border); background: var(--color-surface-2); color: var(--color-fg-subtle); white-space: nowrap; }
144
+ .small { font-size: var(--text-sm); }
145
+ .container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--space-5); }
146
+ .card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 1.25rem 1.5rem; box-shadow: var(--color-shadow); }
147
+ .badge { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; padding: 0.15rem 0.55rem; border-radius: var(--radius-pill); border: 1px solid var(--color-border); background: var(--color-surface-2); color: var(--color-fg-subtle); white-space: nowrap; }
89
148
  .badge.live { color: var(--color-good); border-color: color-mix(in oklab, var(--color-good) 45%, transparent); }
90
149
  .badge.precomputed, .badge.accent { color: var(--color-accent); border-color: color-mix(in oklab, var(--color-accent) 45%, transparent); }
91
150
  .badge.warn { color: var(--color-warn); border-color: color-mix(in oklab, var(--color-warn) 45%, transparent); }
92
151
 
93
- /* ── Header ── */
94
- .site-header { position: sticky; top: 0; z-index: 50; border-bottom: 1px solid var(--color-border); background: color-mix(in oklab, var(--color-bg) 82%, transparent); backdrop-filter: blur(8px); }
95
- .header-inner { max-width: var(--maxw); margin-inline: auto; height: 56px; padding-inline: 1.5rem; display: flex; align-items: center; gap: 1rem; }
96
- .brand { display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 700; letter-spacing: -0.02em; color: var(--color-fg); }
152
+ /* == 3 frame ==================================================================================================== */
153
+
154
+ /* Three modes. Default: the document scrolls. `fixed` (ShellConfig.fixed or fixedRoutes): the route is the viewport.
155
+ `contain` (the template's configuration): every route is the viewport; a workbench or a surface fills it, a
156
+ document scrolls inside it, so the header and the footer stay in view (ADR-0071 rule 1, amended 2026-10-04). */
157
+ .app-shell { min-height: 100vh; display: flex; flex-direction: column; background: var(--color-bg); color: var(--color-fg); }
158
+ .page { flex: 1 1 auto; width: 100%; }
159
+ .page:focus { outline: none; }
160
+
161
+ /* The skip link: the first stop of the keyboard, visible only when focused. */
162
+ .skip-link { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
163
+ .skip-link:focus { position: fixed; top: var(--space-2); left: var(--space-2); z-index: var(--z-modal); width: auto; height: auto; margin: 0; padding: 0.4rem 0.8rem; clip: auto; overflow: visible; background: var(--color-surface); color: var(--color-accent); border: 1px solid var(--color-accent); border-radius: var(--radius-md); }
164
+
165
+ /* Header: brand, the route row, icon links, language and theme on ONE row at every width. */
166
+ .site-header { position: sticky; top: 0; z-index: var(--z-header); border-bottom: 1px solid var(--color-border); background: color-mix(in oklab, var(--color-bg) 82%, transparent); backdrop-filter: blur(8px); }
167
+ .header-inner { max-width: var(--maxw); margin-inline: auto; height: var(--header-h); padding-inline: var(--space-5); display: flex; align-items: center; gap: var(--space-4); }
168
+ .brand { display: inline-flex; align-items: center; gap: var(--space-2); font-weight: 700; letter-spacing: -0.02em; color: var(--color-fg); flex: none; }
97
169
  .brand:hover { text-decoration: none; }
98
170
  .brand .brand-mark { color: var(--color-accent); display: inline-flex; flex: none; }
99
- .main-nav { display: flex; align-items: center; gap: 0.15rem; flex: 1 1 auto; min-width: 0; overflow-x: auto; overflow-y: hidden; white-space: nowrap; scrollbar-width: none; }
100
- .main-nav::-webkit-scrollbar { display: none; }
101
- .main-nav .nav-link { flex: none; }
102
- .brand, .header-actions { flex: none; }
103
- .nav-link { padding: 0.4rem 0.7rem; border-radius: 6px; font-size: 0.9rem; color: var(--color-fg-subtle); transition: color 0.15s, background-color 0.15s; }
104
- .nav-link:hover { text-decoration: none; color: var(--color-fg); }
105
- .nav-link.active { color: var(--color-accent); background: var(--color-accent-soft); font-weight: 600; }
106
- .header-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 0.15rem; }
107
- .icon-btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem; padding: 0.4rem 0.5rem; border-radius: 6px; background: transparent; border: none; color: var(--color-fg-subtle); opacity: 0.85; transition: opacity 0.15s, color 0.15s; }
171
+ .header-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 0.15rem; flex: none; }
172
+ .icon-btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.3rem; padding: 0.4rem 0.5rem; border-radius: var(--radius-sm); background: transparent; border: none; color: var(--color-fg-subtle); opacity: 0.85; transition: opacity 0.15s, color 0.15s; }
108
173
  .icon-btn:hover { opacity: 1; color: var(--color-fg); text-decoration: none; }
109
174
  .icon-btn svg { display: block; flex: none; }
110
175
  .lang-code { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; }
111
176
  .header-sep { width: 1px; height: 20px; margin-inline: 0.4rem; background: var(--color-border); }
112
177
 
113
- /* ── Footer ── */
178
+ /* Footer: ADR-0016 s2, one compact line of provenance and honesty. */
114
179
  .site-footer { border-top: 1px solid var(--color-border); margin-top: 3rem; background: var(--color-surface); }
115
- .footer-inner { max-width: var(--maxw); margin-inline: auto; padding: 1.5rem; display: flex; flex-direction: column; gap: 0.9rem; font-size: 0.85rem; color: var(--color-fg-faint); }
180
+ .footer-inner { max-width: var(--maxw); margin-inline: auto; padding: var(--space-5); display: flex; flex-direction: column; gap: 0.9rem; font-size: 0.85rem; color: var(--color-fg-faint); }
116
181
  .footer-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; }
117
182
  .footer-build { margin-left: auto; font-family: var(--font-mono); font-size: 0.72rem; color: var(--color-fg-faint); display: inline-flex; gap: 0.6rem; flex-wrap: wrap; }
118
183
 
119
- /* ── Misc ── */
120
- .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
121
- :where(a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])):focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: 4px; }
184
+ /* NO MAGIC CHROME NUMBER. A surface that fills the viewport lets flex distribute the height, never a guess at the
185
+ header and footer (measured on ChargeCascade: guess 150px, real 175px, so 25px of scroll and a 48px gap). */
186
+ .app-shell.fixed { height: 100dvh; min-height: 100dvh; overflow: hidden; }
187
+ .app-shell.fixed > .page { flex: 1 1 auto; min-height: 0; display: flex; }
188
+ /* A viewport-filling app carries no prose footer margin (measured on ChargeCascade as exactly 48px of dead space),
189
+ and its footer is compact: with provenance and a disclaimer it wrapped into 178px at 1600x900 and the instrument
190
+ fell from 56 to 42 percent of the viewport. */
191
+ .app-shell.fixed .site-footer { margin-top: 0; }
192
+ .app-shell.fixed .site-footer .footer-inner { padding: 0.45rem 1.5rem; gap: 0.4rem; font-size: 0.74rem; line-height: 1.3; }
193
+ .app-shell.fixed.contain > .page { display: flex; flex-direction: column; overflow: hidden; }
194
+ /* A document route scrolls inside the main container; a workbench or a surface fills it. */
195
+ .app-shell.fixed.contain > .page:has(> .page-body:not(.caos-wb):not(.caos-surface)) { display: block; overflow-y: auto; overscroll-behavior: contain; }
196
+ /* The chrome of a contained app spans the instrument's width with its inset, so at 2560 px the header and footer do
197
+ not float in a 1200 px column above a full-width workbench. */
198
+ .app-shell.fixed.contain .header-inner, .app-shell.fixed.contain .site-footer .footer-inner { max-width: var(--maxw-wide); padding-inline: var(--space-4); }
199
+
200
+ @media (max-width: 900px) {
201
+ /* A phone or a tablet stacks the rail over the instrument and lets the document scroll: the viewport rule is
202
+ measured at 1280x800 and above (ADR-0071 Verification). */
203
+ .app-shell.fixed.contain { height: auto; min-height: 100dvh; overflow: visible; }
204
+ /* Both forms of the rule above are released: the `:has()` form outranked a plain `> .page` and kept a document's
205
+ `.page` a scroll container that never scrolls, so nothing in it could be sticky (G12, 0.8.0). */
206
+ .app-shell.fixed.contain > .page, .app-shell.fixed.contain > .page:has(> .page-body:not(.caos-wb):not(.caos-surface)) { display: block; overflow: visible; }
207
+ }
122
208
  @media (max-width: 760px) {
123
- /* ADR-0071: route navigation shares the header row; it owns the horizontal
124
- scroll instead of adding a permanent second row above every instrument. */
209
+ /* ADR-0071: the route row shares the header row and owns the horizontal scroll; no second row above the
210
+ instrument. */
125
211
  .header-inner { height: 52px; min-height: 0; flex-wrap: nowrap; gap: 0.3rem; padding-inline: 0.55rem; }
126
- .brand, .header-actions { flex: none; white-space: nowrap; }
127
- .brand { gap: 0.25rem; font-size: 0.8rem; }
128
- .header-actions { margin-left: 0; gap: 0; }
129
- .header-actions .icon-btn { padding: 0.25rem; }
212
+ .brand { gap: var(--space-1); font-size: 0.8rem; white-space: nowrap; }
213
+ .header-actions { margin-left: 0; gap: 0; white-space: nowrap; }
214
+ .header-actions .icon-btn { padding: var(--space-1); }
130
215
  .header-sep { margin-inline: 0.15rem; }
131
- .main-nav { flex: 1 1 0; min-width: 0; overflow-x: auto; overflow-y: hidden; white-space: nowrap; padding: 2px 0; scrollbar-width: none; }
132
- .main-nav::-webkit-scrollbar { display: none; }
133
- .main-nav .nav-link { flex: none; padding: 0.3rem 0.3rem; font-size: 0.7rem; }
216
+ /* The phone footer of a contained surface stays one row and says that it scrolls (useOverflowFade). */
217
+ .app-shell.fixed .site-footer .footer-inner { padding: 0.35rem 0.55rem; }
218
+ .app-shell.fixed .footer-meta { flex-wrap: nowrap; overflow-x: auto; white-space: nowrap; scrollbar-width: none; }
219
+ .app-shell.fixed .footer-meta::-webkit-scrollbar { display: none; }
220
+ .app-shell.fixed .footer-meta > * { flex: none; }
134
221
  }
135
222
  @media (max-width: 480px) {
223
+ /* The route row needs the room on every phone: on Linux fonts "Implementation" is about 100 px, and the row was
224
+ about 90 px at 320 px. The brand keeps its mark and its accessible name; the toggle keeps its icon and label. */
136
225
  .brand span:last-child { display: none; }
137
- /* The route row needs the room on every phone: on Linux fonts "Implementation" is about 100 px, and the row was about
138
- 90 px at 320 px and narrower than the link at 390 px, so a route could not be shown whole (shell CI and its gate
139
- self-test, 2026-10-05). The brand keeps its mark and its accessible name; the toggle keeps its icon and its label. */
140
226
  .header-sep, .lang-code { display: none; }
141
227
  .header-actions .icon-btn { padding: 0.2rem; }
142
228
  }
143
- @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }
144
229
 
145
- /* ── Prose / content pages ── (fill the page-body width with grids/figures/sub-tab rails, not a narrow column) */
146
- .prose { font-size: 0.98rem; }
230
+ /* == 4 routes =================================================================================================== */
231
+
232
+ /* Document: a centred reading width (ADR-0017 s1). The width is defined HERE ONLY: a later unscoped `.page-body`
233
+ rule once removed the 1200px cap from every product (test/layout-primitives.test.ts guards it). */
234
+ .page-body { width: 100%; max-width: var(--maxw); margin-inline: auto; padding: 2.25rem 1.5rem 3rem; min-width: 0; }
235
+ /* A WORKBENCH or a SURFACE is not prose: capping an instrument at the reading width wastes every pixel beyond it. */
236
+ .page-body.wide { max-width: var(--maxw-wide); padding-inline: 1.25rem; }
237
+ .app-shell.fixed .page-body.wide { flex: 1 1 auto; min-height: 0; padding-top: 0.75rem; padding-bottom: 0.75rem; }
238
+
239
+ /* Workbench (WorkbenchLayout): a sized rail beside the instrument, on the full viewport. */
240
+ .caos-wb { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); gap: 0.9rem; align-items: stretch; min-width: 0; padding-top: 0.75rem; padding-bottom: 0.75rem; }
241
+ .page-body.wide.caos-wb { padding-inline: var(--space-4); }
242
+ .caos-wb > * { min-width: 0; }
243
+ .app-shell.fixed.contain > .page > .caos-wb { flex: 1 1 auto; min-height: 0; }
244
+
245
+ /* Surface (SurfacePage): a tab App that fills the viewport; its head and tab row hold their height and the open
246
+ panel scrolls. The containment 15 to 19 products wrote for themselves (audit 2026-10-07, section 3). */
247
+ .caos-surface { display: flex; flex-direction: column; min-width: 0; min-height: 0; gap: var(--space-3); }
248
+ .page-body.wide.caos-surface { padding-inline: var(--space-4); }
249
+ .app-shell.fixed.contain > .page > .caos-surface { flex: 1 1 auto; min-height: 0; }
250
+ .caos-surface-head { flex: none; display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-2) var(--space-4); min-width: 0; }
251
+ .caos-surface-heading { min-width: 0; }
252
+ .caos-surface-title { margin: 0; font-size: var(--text-xl); }
253
+ .caos-surface-actions { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
254
+ .caos-surface-lede { margin: 0; color: var(--color-fg-subtle); font-size: var(--text-md); max-width: var(--measure-text); }
255
+ .caos-surface-body { flex: 1 1 auto; min-height: 0; min-width: 0; display: flex; flex-direction: column; }
256
+ .caos-surface-body > .tabs { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
257
+ .caos-surface-body > .tabs > .tabpanel:not([hidden]) { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 0.6rem; }
258
+ @media (max-width: 900px) {
259
+ .caos-wb { grid-template-columns: minmax(0, 1fr); }
260
+ .caos-surface-body > .tabs > .tabpanel:not([hidden]) { overflow: visible; }
261
+ }
262
+
263
+ /* == 5 navigation =============================================================================================== */
264
+
265
+ /* Every row of links or tabs is ONE line that scrolls when it does not fit (ADR-0071 rule 4) and never shrinks in a
266
+ flex column (known shell defect 16, generalised: 16 to 24 products had to say so themselves). Its scrollbar is
267
+ hidden; wheel, trackpad, drag and keyboard still scroll it, and a hidden end fades (known shell defect 11). */
268
+ .main-nav { display: flex; align-items: center; gap: 0.15rem; flex: 1 1 auto; min-width: 0; overflow-x: auto; overflow-y: hidden; white-space: nowrap; scrollbar-width: none; }
269
+ .main-nav::-webkit-scrollbar { display: none; }
270
+ .main-nav .nav-link { flex: none; }
271
+ .nav-link { padding: 0.4rem 0.7rem; border-radius: var(--radius-sm); font-size: 0.9rem; color: var(--color-fg-subtle); transition: color 0.15s, background-color 0.15s; }
272
+ .nav-link:hover { text-decoration: none; color: var(--color-fg); }
273
+ .nav-link.active { color: var(--color-accent); background: var(--color-accent-soft); font-weight: 600; }
274
+ @media (max-width: 760px) {
275
+ .main-nav { flex: 1 1 0; padding: 2px 0; }
276
+ .main-nav .nav-link { padding: 0.3rem 0.3rem; font-size: 0.7rem; }
277
+ }
278
+
279
+ /* Tabs: the first level. */
280
+ .tabs, .tabpanel, .subtabs, .subtabpanels, .subtabpanel { min-width: 0; max-width: 100%; }
281
+ .tablist { display: flex; gap: 0.35rem; flex: none; flex-wrap: nowrap !important; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -ms-overflow-style: none; scroll-behavior: smooth; width: 100%; min-width: 0; max-width: 100%; padding-bottom: 0.4rem; margin-bottom: 0.9rem; border-bottom: 1px solid var(--color-border); }
282
+ .tablist::-webkit-scrollbar { display: none; }
283
+ .tablist .tab, .tablist button { flex: 0 0 auto; white-space: nowrap; }
284
+ .tab { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.5rem 1rem; border: 1px solid transparent; border-radius: var(--radius-md); background: transparent; color: var(--color-fg-subtle); font-size: var(--text-md); font-weight: 550; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
285
+ .tab:hover { color: var(--color-fg); background: var(--color-surface-2); }
286
+ .tab.active { color: var(--color-accent); background: var(--color-accent-soft); border-color: color-mix(in oklab, var(--color-accent) 35%, transparent); font-weight: 650; }
287
+ .tabpanel:focus, .subtabpanel:focus { outline: none; }
288
+
289
+ /* Sub-tabs: the second level, lighter pills; or a vertical list beside the panel for a deep document. */
290
+ .subtabs { margin-block: 1rem; }
291
+ .subtablist { display: flex; gap: 0.3rem; flex: none; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; min-width: 0; margin-bottom: 1.1rem; }
292
+ .subtablist::-webkit-scrollbar { display: none; }
293
+ .subtablist .subtab { flex: none; white-space: nowrap; }
294
+ .subtab { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.32rem 0.8rem; border: 1px solid var(--color-border); border-radius: var(--radius-pill); background: var(--color-surface); color: var(--color-fg-subtle); font-size: var(--text-sm); font-weight: 500; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
295
+ .subtab:hover { color: var(--color-fg); border-color: var(--color-fg-faint); }
296
+ .subtab.active { color: var(--color-accent); background: var(--color-accent-soft); border-color: color-mix(in oklab, var(--color-accent) 40%, transparent); font-weight: 600; }
297
+ /* `minmax(0, 1fr)`, NOT `1fr`: a bare `1fr` track is `minmax(auto, 1fr)`, so a wide table in a sub-tab panel sized
298
+ the whole grid to the table (measured on Porvenir's Datasets tab at 1280x800: 1380px on a 1280px viewport). */
299
+ .subtabs-vertical { display: grid; grid-template-columns: minmax(180px, 240px) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
300
+ /* The vertical list stays in view while a long section scrolls (known shell defect 24, #55). Under contain the
301
+ header is outside the scroll container, so `top: 0`; in a scrolling document it clears the sticky header. */
302
+ .subtabs-vertical > .subtablist { flex-direction: column; overflow: visible; margin-bottom: 0; position: sticky; top: calc(var(--header-h) + var(--space-3)); align-self: start; }
303
+ .app-shell.fixed.contain .subtabs-vertical > .subtablist { top: var(--space-3); }
304
+ .subtabs-vertical .subtab { text-align: left; border-radius: var(--radius-md); white-space: normal; }
305
+ @media (max-width: 900px) {
306
+ /* Here the contained document scrolls as a whole under the sticky header: the list clears the header (G12 found
307
+ it hidden under the header at 768 px). */
308
+ .app-shell.fixed.contain .subtabs-vertical > .subtablist { top: calc(var(--header-h) + var(--space-3)); }
309
+ }
310
+ @media (max-width: 760px) {
311
+ .subtabs-vertical { grid-template-columns: minmax(0, 1fr); }
312
+ .subtabs-vertical > .subtablist { flex-direction: row; overflow-x: auto; position: static; }
313
+ .subtabs-vertical .subtab { white-space: nowrap; }
314
+ }
315
+
316
+ /* Chip rows: a one-of-N choice on one row (ADR-0071 rule 4). */
317
+ .caos-chip-row { display: flex; flex-wrap: nowrap; gap: 0.35rem; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: none; padding-bottom: 2px; }
318
+ .caos-chip-row::-webkit-scrollbar { display: none; }
319
+ .caos-chip-row > * { flex: 0 0 auto; }
320
+
321
+ /* The rail's section row (WorkbenchLayout): one row like every other; a hidden end fades (found on CAOS_Fragmenta
322
+ and CAOS_Contraste, whose labels were shortened by hand to fit). */
323
+ .caos-wb-rail-sections { display: flex; flex-wrap: nowrap; gap: 0.3rem; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; flex: none; min-width: 0; }
324
+ .caos-wb-rail-sections::-webkit-scrollbar { display: none; }
325
+ .caos-wb-rail-sections > * { flex: none; }
326
+
327
+ /* A scrolling row says that it scrolls (known shell defect 11): the hidden end fades. */
328
+ :is(.main-nav, .tablist, .subtablist, .caos-chip-row, .caos-wb-rail-sections, .footer-meta)[data-fade-end="1"] { mask-image: linear-gradient(to right, #000 calc(100% - var(--fade)), transparent); }
329
+ :is(.main-nav, .tablist, .subtablist, .caos-chip-row, .caos-wb-rail-sections, .footer-meta)[data-fade-start="1"] { mask-image: linear-gradient(to left, #000 calc(100% - var(--fade)), transparent); }
330
+ :is(.main-nav, .tablist, .subtablist, .caos-chip-row, .caos-wb-rail-sections, .footer-meta)[data-fade-start="1"][data-fade-end="1"] { mask-image: linear-gradient(to right, transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent); }
331
+
332
+ /* == 6 content ================================================================================================== */
333
+
334
+ /* Fill a document's width with grids, figure rows, definition grids and sub-tab lists, never with stretched text:
335
+ every text block keeps a readable line (--measure-text), a figure or a table takes the width (ADR-0017 s1). */
336
+ .prose { font-size: var(--text-base); }
147
337
  .prose section { margin-block: 1.75rem; }
148
- .measure { max-width: 70ch; }
338
+ .prose h2 { font-size: var(--text-xl); margin-block: 1.4rem 0.6rem; }
339
+ .prose h3 { font-size: 1.06rem; margin-block: 1.1rem 0.4rem; }
340
+ .prose p { margin-block: 0.6rem; }
341
+ .prose ul, .prose ol { margin-block: 0.5rem; padding-left: 1.3rem; }
342
+ .prose li { margin-block: 0.35rem; }
343
+ .prose :where(p, li, blockquote, dd, .callout) { max-width: var(--measure-text); }
344
+ .prose :where(.equation-caption, .figure-caption, .fig-cap) { max-width: var(--measure-text); margin-inline: auto; }
345
+ .measure { max-width: var(--measure); }
346
+ .page-head { border-bottom: 1px solid var(--color-border); padding-bottom: 1rem; margin-bottom: 1.25rem; }
347
+ .page-head h1 { font-size: var(--text-2xl); margin: 0 0 0.4rem; }
348
+ .lede { color: var(--color-fg-subtle); font-size: var(--text-lg); margin: 0; max-width: var(--measure); }
349
+ .caos-doc-section { margin-block: 1.75rem; }
350
+ /* Wide content in a document scrolls inside its own box; the contained page never cuts it (G5). */
351
+ .prose table { display: block; max-width: 100%; overflow-x: auto; }
352
+ .prose pre { max-width: 100%; overflow-x: auto; }
353
+ .prose img, .prose svg, .prose video { max-width: 100%; height: auto; }
354
+
149
355
  .fig-row { display: grid; grid-template-columns: 1.1fr 1fr; gap: 1.5rem; align-items: center; margin-block: 1.5rem; }
150
356
  .fig-row.rev { grid-template-columns: 1fr 1.1fr; }
151
- @media (max-width: 820px) { .fig-row, .fig-row.rev { grid-template-columns: 1fr; } }
357
+ .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; margin-block: 1.5rem; }
152
358
  .def-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; margin-block: 1.25rem; }
153
- .def { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 10px; padding: 0.9rem 1.1rem; }
359
+ .def { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 0.9rem 1.1rem; }
154
360
  .def h4 { margin: 0 0 0.3rem; color: var(--color-accent); font-size: 0.95rem; }
155
361
  .def p { margin: 0; font-size: 0.9rem; color: var(--color-fg-subtle); }
156
- .assume { margin-block: 1rem; padding: 0.85rem 1rem; background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: 8px; }
362
+ .assume { margin-block: 1rem; padding: 0.85rem 1rem; background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-md); }
157
363
  .assume-title { margin: 0 0 0.4rem; font-size: 0.76rem; font-weight: 650; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-fg-faint); }
158
364
  .assume ul { margin: 0; padding-left: 1.2rem; color: var(--color-fg-subtle); }
159
365
  .assume li { margin-block: 0.3rem; font-size: 0.92rem; }
160
- .references { margin-top: 2.5rem; border-top: 1px solid var(--color-border); padding-top: 1rem; }
161
- .reference-list { font-size: 0.85rem; color: var(--color-fg-subtle); line-height: 1.5; padding-left: 1.4rem; }
162
- .reference-list li { margin-block: 0.5rem; }
163
- .cite-inline { font-style: normal; font-size: 0.92em; color: var(--color-fg-subtle); }
164
- .th-refs { margin-top: 1.25rem; padding-top: 0.6rem; border-top: 1px dashed var(--color-border); font-size: 0.85rem; color: var(--color-fg-subtle); }
165
- .th-refs-label { font-weight: 650; color: var(--color-fg-faint); }
166
- .prose h2 { font-size: 1.3rem; margin-block: 1.4rem 0.6rem; }
167
- .prose h3 { font-size: 1.06rem; margin-block: 1.1rem 0.4rem; }
168
- .prose p { margin-block: 0.6rem; }
169
- .prose ul, .prose ol { margin-block: 0.5rem; padding-left: 1.3rem; }
170
- .prose li { margin-block: 0.35rem; }
171
- .page-head { border-bottom: 1px solid var(--color-border); padding-bottom: 1rem; margin-bottom: 1.25rem; }
172
- .page-head h1 { font-size: 1.7rem; margin: 0 0 0.4rem; }
173
- .lede { color: var(--color-fg-subtle); font-size: 1.08rem; margin: 0; max-width: 70ch; }
174
- .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; margin-block: 1.5rem; }
175
- @media (max-width: 760px) { .two-col { grid-template-columns: 1fr; } }
176
366
  .tick-list, .cross-list { list-style: none; padding-left: 0; margin: 0.4rem 0 0; }
177
367
  .tick-list li, .cross-list li { position: relative; padding-left: 1.5rem; margin-block: 0.5rem; }
178
368
  .tick-list li::before { content: "✓"; position: absolute; left: 0; color: var(--color-good); font-weight: 700; }
179
369
  .cross-list li::before { content: "✕"; position: absolute; left: 0; color: var(--color-fg-faint); font-weight: 700; }
370
+ @media (max-width: 900px) { .fig-row, .fig-row.rev { grid-template-columns: 1fr; } }
371
+ @media (max-width: 760px) { .two-col { grid-template-columns: 1fr; } }
180
372
 
181
- /* ── Callouts ── */
182
- .callout { margin-block: 1rem; padding: 0.8rem 1.1rem; border-left: 3px solid var(--color-border); background: var(--color-surface-2); border-radius: 0 8px 8px 0; }
373
+ /* Callouts. */
374
+ .callout { margin-block: 1rem; padding: 0.8rem 1.1rem; border-left: 3px solid var(--color-border); background: var(--color-surface-2); border-radius: 0 var(--radius-md) var(--radius-md) 0; }
183
375
  .callout-honest { border-left-color: var(--color-warn); }
184
376
  .callout-strong { border-left-color: var(--color-accent); background: var(--color-accent-soft); }
185
377
  .callout-note { border-left-color: var(--color-fg-faint); }
186
- .callout-title { font-weight: 650; margin: 0 0 0.4rem; color: var(--color-fg); }
378
+ .callout-title { display: flex; align-items: center; gap: 0.4rem; font-weight: 650; margin: 0 0 0.4rem; color: var(--color-fg); }
187
379
  .callout-body { color: var(--color-fg-subtle); }
188
380
  .callout-body p:first-child { margin-top: 0; }
189
381
  .callout-body p:last-child { margin-bottom: 0; }
382
+ .banner { padding: 0.6rem 0.9rem; border-radius: var(--radius-md); font-size: 0.85rem; margin-block: 0.6rem; border-left: 3px solid var(--color-border); background: var(--color-surface-2); }
383
+ .banner.warn { border-left-color: var(--color-warn); color: var(--color-warn); }
384
+ .banner.error { border-left-color: var(--color-bad); color: var(--color-bad); }
385
+ .hint { font-size: 0.8rem; color: var(--color-fg-faint); margin: 0; }
190
386
 
191
- /* ── Tabs ── */
192
- .tablist { display: flex; gap: 0.35rem; padding-bottom: 0.6rem; margin-bottom: 1.5rem; border-bottom: 1px solid var(--color-border); flex-wrap: nowrap; }
193
- /* SINGLE-LINE TAB BAR.
194
- `flex-wrap: wrap` lets a 14-to-18 tab bar spill onto a second and third row, and every extra row is
195
- vertical space taken from the instrument below it, permanently, on every render. A tab bar is
196
- navigation chrome: it gets ONE row and scrolls horizontally when it does not fit.
197
- Scrollbar is hidden but the row stays scrollable by wheel, trackpad, drag and keyboard. */
198
- .tablist { flex-wrap: nowrap !important; overflow-x: auto; overflow-y: hidden;
199
- scrollbar-width: none; -ms-overflow-style: none; padding-bottom: 0.4rem; margin-bottom: 0.9rem;
200
- scroll-behavior: smooth; }
201
- .tablist::-webkit-scrollbar { display: none; }
202
- .tablist .tab, .tablist button { flex: 0 0 auto; white-space: nowrap; }
203
-
204
- .tab { padding: 0.5rem 1rem; border: 1px solid transparent; border-radius: 8px; background: transparent; color: var(--color-fg-subtle); font-size: 0.92rem; font-weight: 550; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
205
- .tab:hover { color: var(--color-fg); background: var(--color-surface-2); }
206
- .tab.active { color: var(--color-accent); background: var(--color-accent-soft); border-color: color-mix(in oklab, var(--color-accent) 35%, transparent); font-weight: 650; }
207
- .tabpanel:focus, .subtabpanel:focus { outline: none; }
208
- .subtabs { margin-block: 1rem; }
209
- .subtablist { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
210
- .subtab { padding: 0.32rem 0.8rem; border: 1px solid var(--color-border); border-radius: 999px; background: var(--color-surface); color: var(--color-fg-subtle); font-size: 0.84rem; font-weight: 500; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
211
- .subtab:hover { color: var(--color-fg); border-color: var(--color-fg-faint); }
212
- .subtab.active { color: var(--color-accent); background: var(--color-accent-soft); border-color: color-mix(in oklab, var(--color-accent) 40%, transparent); font-weight: 600; }
213
- /* `minmax(0, 1fr)`, NOT `1fr`. A bare `1fr` track is `minmax(auto, 1fr)`, and `auto` is the
214
- min-content width of the panel, so a wide table in a sub-tab panel sized the whole grid to
215
- the table and pushed the page past the viewport instead of scrolling inside its wrapper
216
- (ADR-0071 rule 3). Measured on Porvenir's Datasets tab at 1280x800: the panel column
217
- rendered 1380px wide on a 1280px viewport. */
218
- .subtabs-vertical { display: grid; grid-template-columns: minmax(180px, 240px) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
219
- .subtabs-vertical .subtablist { flex-direction: column; margin-bottom: 0; }
220
- .subtabs-vertical .subtab { text-align: left; border-radius: 8px; }
221
- @media (max-width: 760px) { .subtabs-vertical { grid-template-columns: 1fr; } .subtabs-vertical .subtablist { flex-direction: row; } }
222
-
223
- /* ── Equations / figures ── */
224
- .equation { margin: 1rem 0; padding: 0.9rem 1rem; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 8px; overflow-x: auto; }
225
- /* Wide content in a document scrolls inside its own box; the contained page never cuts it (G5). */
226
- .prose table { display: block; max-width: 100%; overflow-x: auto; }
227
- .prose pre { max-width: 100%; overflow-x: auto; }
228
- .prose img, .prose svg, .prose video { max-width: 100%; height: auto; }
387
+ /* Equations and figures. */
388
+ .equation { margin: 1rem 0; padding: 0.9rem 1rem; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow-x: auto; }
229
389
  .equation-caption { margin-top: 0.5rem; font-size: 0.82rem; color: var(--color-fg-faint); text-align: center; }
230
390
  .katex { color: var(--color-fg); }
231
391
  .figure { margin: 1.5rem 0; }
232
- .figure-caption, .fig-cap { text-align: center; font-size: 0.84rem; color: var(--color-fg-faint); margin-top: 0.6rem; font-style: italic; }
392
+ .figure-caption, .fig-cap { text-align: center; font-size: var(--text-sm); color: var(--color-fg-faint); margin-top: 0.6rem; font-style: italic; }
233
393
 
234
- /* ── Theme-aware inline SVG figures (teaching diagrams) ── */
394
+ /* References: at the end of each section (ADR-0017 s4). */
395
+ .references { margin-top: 2.5rem; border-top: 1px solid var(--color-border); padding-top: 1rem; }
396
+ .reference-list { font-size: 0.85rem; color: var(--color-fg-subtle); line-height: 1.5; padding-left: 1.4rem; }
397
+ .reference-list li { margin-block: 0.5rem; }
398
+ .cite-inline { font-style: normal; font-size: 0.92em; color: var(--color-fg-subtle); }
399
+ .th-refs { margin-top: 1.25rem; padding-top: 0.6rem; border-top: 1px dashed var(--color-border); font-size: 0.85rem; color: var(--color-fg-subtle); }
400
+ .th-refs-label { font-weight: 650; color: var(--color-fg-faint); }
401
+
402
+ /* Theme-aware inline SVG diagrams (teaching figures). An <img> of an SVG cannot read these tokens. */
235
403
  .fig-svg { width: 100%; max-width: 560px; height: auto; display: block; margin-inline: auto; }
236
404
  .fig-svg.wide { max-width: 760px; }
237
405
  .dg-axis { stroke: var(--color-fg-faint); stroke-width: 1.3; }
@@ -262,19 +430,40 @@ code, kbd { font-family: var(--font-mono); }
262
430
  .dg-bar-2 { fill: var(--color-magenta); }
263
431
  .dg-bar-label { fill: var(--color-fg-subtle); font: 11px var(--font-sans); }
264
432
 
265
- /* ── Generic controls ── */
266
- .btn { border: 1px solid var(--color-border); background: var(--color-surface-2); color: var(--color-fg); padding: 0.4rem 0.9rem; border-radius: 8px; font-weight: 600; font-size: 0.88rem; display: inline-flex; align-items: center; gap: 0.35rem; }
433
+ /* == 7 controls ================================================================================================= */
434
+
435
+ .btn { border: 1px solid var(--color-border); background: var(--color-surface-2); color: var(--color-fg); padding: 0.4rem 0.9rem; border-radius: var(--radius-md); font-weight: 600; font-size: 0.88rem; display: inline-flex; align-items: center; gap: 0.35rem; }
267
436
  .btn:hover { border-color: var(--color-fg-faint); }
268
437
  .btn.primary { background: var(--color-accent); color: var(--color-accent-fg); border-color: var(--color-accent); }
269
438
  .btn:disabled { opacity: 0.5; cursor: default; }
270
- .select { background: var(--color-surface-2); color: var(--color-fg); border: 1px solid var(--color-border); border-radius: 8px; padding: 0.35rem 0.5rem; font-size: 0.85rem; }
271
- .hint { font-size: 0.8rem; color: var(--color-fg-faint); margin: 0; }
272
- .banner { padding: 0.6rem 0.9rem; border-radius: 8px; font-size: 0.85rem; margin-block: 0.6rem; border-left: 3px solid var(--color-border); background: var(--color-surface-2); }
273
- .banner.warn { border-left-color: var(--color-warn); color: var(--color-warn); }
274
- .banner.error { border-left-color: var(--color-bad); color: var(--color-bad); }
439
+ .select { background: var(--color-surface-2); color: var(--color-fg); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 0.35rem 0.5rem; font-size: 0.85rem; }
275
440
  .range { width: 100%; accent-color: var(--color-accent); }
441
+ /* Native controls follow the theme (S2; known shell defect 13). */
442
+ .app-shell select, .app-shell textarea, .app-shell input:not([type="range"]):not([type="checkbox"]):not([type="radio"]) { background: var(--color-surface-2); color: var(--color-fg); border: 1px solid var(--color-border); border-radius: var(--radius-sm); }
443
+ .app-shell button { color: inherit; }
444
+
445
+ /* Chips: a one-of-N choice rendered as pills; `.on` is the selected state. */
446
+ .chip { display: inline-flex; align-items: center; gap: 0.35rem; background: var(--color-surface); border: 1px solid var(--color-border); color: var(--color-fg-subtle); border-radius: var(--radius-pill); padding: 0.3rem 0.72rem; font-size: 0.82rem; font-weight: 500; white-space: nowrap; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
447
+ .chip:hover:not(:disabled) { color: var(--color-fg); border-color: var(--color-fg-faint); }
448
+ .chip.on { background: var(--color-accent); color: var(--color-accent-fg); border-color: transparent; font-weight: 600; }
449
+ .chip:disabled { opacity: 0.45; cursor: default; }
450
+
451
+ /* Registered controls (Knob, ChipGroup). */
452
+ .caos-knob { display: flex; flex-direction: column; gap: var(--space-1); }
453
+ .caos-knob-head { display: flex; justify-content: space-between; gap: var(--space-2); font-size: 0.8rem; color: var(--color-fg-subtle); }
454
+ .caos-knob-value { font-family: var(--font-mono); color: var(--color-fg); font-weight: 600; white-space: nowrap; }
455
+ .caos-chipgroup { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
456
+ .caos-chipgroup-label { font-size: 0.8rem; color: var(--color-fg-subtle); }
457
+ .caos-field { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.8rem; color: var(--color-fg-subtle); }
458
+
459
+ /* Icons: lucide renders `svg.lucide`; its size follows the place it is in, so products pass no size and every app
460
+ draws the same icon at the same size. An icon beside text takes the text's size. */
461
+ svg.lucide { flex: none; }
462
+ .icon-btn svg.lucide, .brand-mark svg.lucide { width: var(--icon-lg); height: var(--icon-lg); }
463
+ :is(.tab, .subtab, .chip, .btn, .nav-link, .badge, .callout-title, .caos-plot-head, .cs-chip, .caos-wb-rail-sections) svg.lucide { width: 1.15em; height: 1.15em; }
464
+
465
+ /* == 8 case selector ============================================================================================ */
276
466
 
277
- /* ── CaseSelector v2 (shared source + case picker) ── */
278
467
  .cs { display: flex; flex-direction: column; gap: 0.7rem; }
279
468
  .cs-source { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
280
469
  .cs-source-label { font-size: 0.78rem; font-weight: 600; color: var(--color-fg-subtle); text-transform: uppercase; letter-spacing: 0.03em; }
@@ -283,166 +472,64 @@ code, kbd { font-family: var(--font-mono); }
283
472
  .cs-group { display: flex; flex-direction: column; gap: 0.35rem; }
284
473
  .cs-group-label { font-size: 0.76rem; font-weight: 600; color: var(--color-fg-subtle); text-transform: uppercase; letter-spacing: 0.03em; }
285
474
  .cs-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
286
- .cs-chip { display: inline-flex; align-items: baseline; gap: 0.4rem; padding: 0.32rem 0.6rem; border: 1px solid var(--color-border); border-radius: 999px; background: var(--color-surface); color: var(--color-fg-subtle); font-size: 0.84rem; font-weight: 500; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
475
+ .cs-chip { display: inline-flex; align-items: baseline; gap: 0.4rem; padding: 0.32rem 0.6rem; border: 1px solid var(--color-border); border-radius: var(--radius-pill); background: var(--color-surface); color: var(--color-fg-subtle); font-size: var(--text-sm); font-weight: 500; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
287
476
  .cs-chip:hover:not(:disabled) { color: var(--color-fg); background: var(--color-surface-2); }
288
477
  .cs-chip.on { color: var(--color-accent); background: var(--color-accent-soft); border-color: color-mix(in oklab, var(--color-accent) 40%, transparent); font-weight: 600; }
289
478
  .cs-chip:disabled { opacity: 0.45; cursor: default; }
290
479
  .cs-chip-id { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.76rem; font-weight: 600; opacity: 0.85; }
291
- .cs-chip-name { }
292
480
  .cs-kind { font-size: 0.62rem; font-weight: 700; line-height: 1; padding: 0.12rem 0.28rem; border-radius: 4px; border: 1px solid var(--color-border); color: var(--color-fg-faint); background: var(--color-surface-2); align-self: center; }
293
481
  .cs-kind-real { color: var(--color-accent-2); border-color: color-mix(in oklab, var(--color-accent-2) 40%, transparent); }
294
482
  .cs-kind-uploaded { color: var(--color-warn); border-color: color-mix(in oklab, var(--color-warn) 40%, transparent); }
295
483
  .cs-modified { display: flex; align-items: center; gap: 0.35rem 0.5rem; flex-wrap: wrap; min-width: 0; }
296
484
  .cs-modified .badge { white-space: normal; max-width: 100%; }
297
485
  .cs-reset { border: none; background: transparent; color: var(--color-accent); font-size: 0.8rem; font-weight: 600; padding: 0.1rem 0.2rem; text-decoration: underline; text-underline-offset: 2px; }
298
-
299
- /* Authenticated workbench frame. Product styles may refine tokens and controls, but the package owns
300
- the responsive navigation/layout contract and keeps route/content landmarks consistent. */
301
- .caos-workbench-shell { min-height: 100vh; display: grid; grid-template-columns: 238px minmax(0, 1fr); }
302
- .caos-workbench-sidebar { position: fixed; inset: 0 auto 0 0; z-index: 20; width: 238px; display: flex; flex-direction: column; padding: 1.25rem 0.875rem; border-right: 1px solid var(--color-border); background: var(--color-surface); }
303
- .caos-workbench-sidebar nav { display: grid; gap: 0.25rem; }
304
- .caos-workbench-sidebar nav a { display: flex; align-items: center; gap: 0.75rem; padding: 0.625rem 0.7rem; border: 1px solid transparent; border-radius: 0.7rem; color: var(--color-fg-subtle); font-size: 0.9rem; font-weight: 650; }
305
- .caos-workbench-sidebar nav a:hover { color: var(--color-fg); background: var(--color-surface-2); text-decoration: none; }
306
- .caos-workbench-sidebar nav a.active { color: var(--color-accent); border-color: color-mix(in oklab, var(--color-accent) 24%, transparent); background: var(--color-accent-soft); }
307
- .caos-workbench-main { grid-column: 2; min-width: 0; }
308
- .caos-workbench-header { position: sticky; top: 0; z-index: 15; min-height: 64px; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0 2rem; border-bottom: 1px solid var(--color-border); background: color-mix(in oklab, var(--color-bg) 88%, transparent); backdrop-filter: blur(12px); }
309
- .caos-workbench-content { width: min(1540px, 100%); margin: 0 auto; padding: 2.25rem 2rem 4rem; }
310
-
311
- @media (max-width: 760px) {
312
- .caos-workbench-shell { display: block; padding-bottom: 66px; }
313
- .caos-workbench-sidebar { inset: auto 0 0; width: auto; height: 62px; display: block; border: 0; border-top: 1px solid var(--color-border); padding: 5px 8px; }
314
- .caos-workbench-sidebar > :not(nav) { display: none; }
315
- .caos-workbench-sidebar nav { grid-template-columns: repeat(var(--caos-workbench-route-count, 5), minmax(0, 1fr)); }
316
- .caos-workbench-sidebar nav a { display: grid; justify-items: center; gap: 2px; overflow: hidden; padding: 5px; font-size: 0.7rem; text-overflow: ellipsis; white-space: nowrap; }
317
- .caos-workbench-main { min-width: 0; }
318
- .caos-workbench-header { min-height: 58px; padding: 0 1rem; }
319
- .caos-workbench-content { padding: 1.5rem 1rem 2.5rem; }
320
- }
321
-
322
- /* CONTAINED LAYOUT. A `nowrap` tab bar sizes its container to its intrinsic width unless every
323
- ancestor is allowed to shrink: a flex/grid item defaults to `min-width: auto`. Without this, making
324
- the tab bar a single scrollable row pushes the whole page wider than the viewport and the user has to
325
- drag sideways to reach the right edge. Measured on ChargeCascade: scrollWidth 1817 on a 1600 viewport. */
326
- html, body { overflow-x: hidden; }
327
- /* `min-width: 0`, NOT `max-width: 100%`. Containment is about letting a box SHRINK below its
328
- content; capping it at 100% does nothing for that and, because this rule comes after the
329
- `.page-body` definition above, it silently OVERRODE `max-width: var(--maxw)` and removed the
330
- 1200px reading cap from every prose page in every product on this shell. Measured on Porvenir at
331
- 1600x900: doc routes rendered 1600px wide instead of 1200px centered, so body text ran the full
332
- width of the display, which is the opposite of what ADR-0017 s1.1 specifies. The wide workbench
333
- opt-in is `.page-body.wide`; a prose page must keep the measure. */
334
- .page-body { min-width: 0; }
335
- .tabs, .tabpanel, .subtabs, .subtabpanels, .subtabpanel { min-width: 0; max-width: 100%; }
336
- .tablist { min-width: 0; max-width: 100%; width: 100%; }
337
-
338
- /* NO MAGIC CHROME NUMBER. An app surface must not compute its height from a hardcoded guess at the
339
- header and footer: the guess drifts the moment either changes, leaving either a dead gap above the
340
- footer or a few pixels of scroll. Measured on ChargeCascade: guess 150px, real 175px (header 57 +
341
- footer 70 + 48px body padding), so 25px of scroll and a 48px gap. Apps that fill the viewport add
342
- `.app-shell.fixed` and let flex distribute the height. */
343
- .app-shell.fixed { height: 100dvh; min-height: 100dvh; overflow: hidden; }
344
- .app-shell.fixed > .page { flex: 1 1 auto; min-height: 0; display: flex; }
345
- .app-shell.fixed .page-body.wide { flex: 1 1 auto; min-height: 0; padding-top: 0.75rem; padding-bottom: 0.75rem; }
346
-
347
- /* A viewport-filling app must not carry the prose footer margin: it becomes dead space between the
348
- content and the chrome. Measured on ChargeCascade as exactly 48px (3rem). */
349
- .app-shell.fixed .site-footer { margin-top: 0; }
350
- /* On a contained surface the footer competes with the instrument for a fixed height. With a
351
- product's provenance and disclaimer it wrapped into 178px at 1600x900 (five lines), and the
352
- instrument fell from 56 to 42 percent of the viewport, under the ADR-0071 rule 8 floor. The
353
- text stays (ADR-0016 s2: provenance and the honest disclaimer are the footer's job); it is set
354
- compact, so it takes two to three lines, not five. */
355
- .app-shell.fixed .site-footer .footer-inner { padding: 0.45rem 1.5rem; gap: 0.4rem; font-size: 0.74rem; line-height: 1.3; }
356
- @media (max-width: 760px) {
357
- /* A long, correctly attributed provenance must not consume the instrument.
358
- Footer metadata remains accessible by horizontal scroll and keyboard focus. */
359
- .app-shell.fixed .site-footer .footer-inner { padding: 0.35rem 0.55rem; }
360
- .app-shell.fixed .footer-meta { flex-wrap: nowrap; overflow-x: auto; white-space: nowrap; scrollbar-width: none; }
361
- .app-shell.fixed .footer-meta::-webkit-scrollbar { display: none; }
362
- .app-shell.fixed .footer-meta > * { flex: none; }
363
- }
364
-
365
- /* ADR-0058 diagram i18n. A hand-authored architecture SVG carries BOTH languages inside ONE file,
366
- and the modal wrapper picks one. Two files would be two things to keep in step, and the one not
367
- on screen is the one that goes stale. The rules are scoped to [data-arch-lang] so the short class
368
- names cannot collide with an app's own styles. */
369
- [data-arch-lang] .l-es { display: none; }
370
- [data-arch-lang="es"] .l-en { display: none; }
371
- [data-arch-lang="es"] .l-es { display: inline; }
372
- .caos-architecture-diagram > svg { display: block; width: 100%; height: auto; max-width: none; }
373
-
374
- /* The compact one-of-N case picker (ADR-0071 rule 7). Ten cases in four categories
375
- cost a 308px chip block, 21% of a 2560x1440 viewport, and one row as a dropdown. */
486
+ /* The compact one-of-N case picker (ADR-0071 rule 7): ten cases in four categories cost a 308px chip block, 21% of a
487
+ 2560x1440 viewport, and one row as a dropdown. */
376
488
  .cs-select { width: 100%; max-width: 460px; }
377
489
 
378
- /* ADR-0070: additive, shell-owned, full-viewport scenario focus frame. */
379
- .caos-focus-shell { position: fixed; inset: 0; z-index: 100; display: grid; grid-template-columns: minmax(0,1fr) clamp(248px,19vw,340px); min-width: 0; height: 100dvh; background: var(--color-bg); color: var(--color-fg); }
380
- .caos-focus-stage { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: var(--color-bg); }
381
- .caos-focus-instrument { position: absolute; inset: 0; min-width: 0; min-height: 0; }
382
- .caos-focus-label, .caos-focus-hud > div { background: color-mix(in oklab,var(--color-surface) 82%,transparent); backdrop-filter: blur(6px); border: 1px solid var(--color-border); border-radius: 6px; }
383
- .caos-focus-label { position: absolute; top: 14px; left: 14px; max-width: min(46ch,calc(100% - 230px)); display: grid; gap: 4px; padding: 10px 12px; pointer-events: none; }
384
- .caos-focus-label strong { font-size: 0.96rem; line-height: 1.2; }
385
- .caos-focus-label span { font-size: 0.78rem; line-height: 1.4; color: var(--color-fg-subtle); }
386
- .caos-focus-hud { position: absolute; left: 14px; top: 104px; display: grid; gap: 6px; pointer-events: none; }
387
- .caos-focus-hud > div { display: grid; gap: 1px; padding: 7px 10px; min-width: 120px; }
388
- .caos-focus-hud strong { font: 700 1rem var(--font-mono,monospace); }
389
- .caos-focus-hud span { color: var(--color-fg-subtle); font-size: 0.61rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; }
390
- .caos-focus-actions { position: absolute; top: 12px; right: 12px; display: flex; gap: 6px; align-items: center; padding: 4px; border: 1px solid var(--color-border); border-radius: 6px; background: color-mix(in oklab,var(--color-surface) 90%,transparent); backdrop-filter: blur(6px); }
391
- .caos-focus-actions button { border: 0; border-radius: 4px; padding: 6px 9px; background: var(--color-surface-2); color: var(--color-fg); font-size: 0.75rem; font-weight: 700; cursor: pointer; }
392
- .caos-focus-rail { min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain; border-left: 1px solid var(--color-border); background: var(--color-surface); }
393
- @media (max-width: 860px) {
394
- .caos-focus-shell { grid-template-columns: minmax(0,1fr); grid-template-rows: minmax(0,54dvh) minmax(0,46dvh); }
395
- .caos-focus-rail { border-left: 0; border-top: 1px solid var(--color-border); }
396
- .caos-focus-label { max-width: calc(100% - 16px); top: 49px; left: 8px; padding: 6px 8px; }
397
- .caos-focus-hud { top: auto; bottom: 8px; left: 8px; right: 8px; display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 4px; }
398
- .caos-focus-hud > div { min-width: 0; padding: 5px; }
399
- .caos-focus-hud strong { font-size: 0.72rem; white-space: nowrap; }
400
- .caos-focus-hud span { font-size: 0.48rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
401
- .caos-focus-actions { top: 7px; right: 7px; }
402
- }
403
-
404
- /* == Workbench primitives (0.7.0) ==============================================================================
405
- The App route of every product: WorkbenchLayout, CaseWorkbench, VariantBar, LaneBadge, Readout, Gauge, Verdict,
406
- PlotCard; and DocPage/DocSection for documentation routes. Owned here so products compose them and never
407
- redefine them (ADR-0016 s9, ADR-0017, ADR-0071, as amended 2026-10-04). */
490
+ /* == 9 workbench ================================================================================================ */
408
491
 
409
- /* Chips: a one-of-N choice rendered as pills; `.on` is the selected state. */
410
- .chip { display: inline-flex; align-items: center; gap: 0.35rem; background: var(--color-surface); border: 1px solid var(--color-border); color: var(--color-fg-subtle); border-radius: 999px; padding: 0.3rem 0.72rem; font-size: 0.82rem; font-weight: 500; white-space: nowrap; transition: color 0.15s, background-color 0.15s, border-color 0.15s; }
411
- .chip:hover:not(:disabled) { color: var(--color-fg); border-color: var(--color-fg-faint); }
412
- .chip.on { background: var(--color-accent); color: var(--color-accent-fg); border-color: transparent; font-weight: 600; }
413
- .chip:disabled { opacity: 0.45; cursor: default; }
414
- /* ONE row of chips (ADR-0071 rule 4): it scrolls horizontally instead of wrapping into more rows. */
415
- .caos-chip-row { display: flex; flex-wrap: nowrap; gap: 0.35rem; overflow-x: auto; overflow-y: hidden; min-width: 0; scrollbar-width: none; padding-bottom: 2px; }
416
- .caos-chip-row::-webkit-scrollbar { display: none; }
417
- .caos-chip-row > * { flex: 0 0 auto; }
418
-
419
- /* WorkbenchLayout: a sized rail beside the instrument, on the full viewport. */
420
- .caos-wb { display: grid; grid-template-columns: clamp(248px, 19vw, 340px) minmax(0, 1fr); gap: 0.9rem; align-items: stretch; min-width: 0; padding-top: 0.75rem; padding-bottom: 0.75rem; }
421
- .caos-wb > * { min-width: 0; }
422
- .caos-wb-rail { display: flex; flex-direction: column; gap: 0.75rem; min-height: 0; overflow-y: auto; overflow-x: hidden; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 10px; padding: 0.85rem; }
423
- .caos-wb-rail-sections { display: flex; flex-wrap: nowrap; gap: 0.3rem; overflow-x: auto; scrollbar-width: none; flex: 0 0 auto; }
424
- .caos-wb-rail-sections::-webkit-scrollbar { display: none; }
492
+ /* The rail: sized, never scrolled when sections are used (ADR-0071 rule 6). */
493
+ .caos-wb-rail { display: flex; flex-direction: column; gap: var(--space-3); min-height: 0; overflow-y: auto; overflow-x: hidden; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 0.85rem; }
494
+ .caos-wb-rail-head { display: flex; flex-direction: column; gap: var(--space-3); padding-bottom: var(--space-3); border-bottom: 1px solid var(--color-border); }
425
495
  .caos-wb-rail-panel { display: flex; flex-direction: column; gap: 0.8rem; min-height: 0; }
426
- .caos-wb-rail label, .caos-field { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.8rem; color: var(--color-fg-subtle); }
496
+ .caos-wb-rail label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.8rem; color: var(--color-fg-subtle); }
427
497
  .caos-wb-rail .select, .caos-wb-rail .range { width: 100%; }
498
+ /* The variants wrap in the rail instead of scrolling. */
499
+ .caos-wb-rail .caos-variant-bar { margin-bottom: 0; }
500
+ .caos-wb-rail .caos-chip-row { flex-wrap: wrap; overflow: visible; }
501
+ .caos-wb-rail .caos-variant-note { font-size: 0.8rem; }
428
502
  .caos-wb-main { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
429
503
 
430
- /* CaseWorkbench: the variant bar, then ONE row of at most six question groups; the panel owns its scroll. */
504
+ /* CaseWorkbench: ONE row of at most six question groups; the open panel takes the height the row leaves and owns its
505
+ scroll (failure class 4: drawings at zero or at a guessed size). */
431
506
  .caos-cw { display: flex; flex-direction: column; min-height: 0; min-width: 0; flex: 1 1 auto; }
432
507
  .caos-cw > .tabs { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
433
- .caos-cw > .tabs > .tabpanel:not([hidden]) { flex: 1 1 auto; min-height: 0; overflow: auto; }
434
- .caos-cw-panel { display: flex; flex-direction: column; gap: 0.6rem; min-width: 0; }
508
+ .caos-cw > .tabs > .tablist { margin-bottom: 0.5rem; padding-bottom: var(--space-1); }
509
+ .caos-cw > .tabs > .tablist .tab { padding: 0.38rem 0.85rem; }
510
+ .caos-cw > .tabs > .tabpanel:not([hidden]) { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; }
511
+ .caos-cw-panel { display: flex; flex-direction: column; gap: 0.6rem; min-width: 0; flex: 1 1 auto; min-height: 0; }
435
512
  .caos-cw-panel-head { display: flex; justify-content: flex-end; }
513
+ .caos-cw-panel > .subtabs { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; margin-block: 0; }
514
+ .caos-cw-panel > .subtabs > .subtablist { margin-bottom: 0.45rem; }
515
+ .caos-cw-panel > .subtabs > .subtabpanels, .caos-cw-panel > .subtabs > .subtabpanels > .subtabpanel { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
516
+ .caos-cw-panel > .subtabs > .subtabpanels > .subtabpanel { gap: 0.6rem; }
436
517
  .caos-cw-context { max-width: 92ch; }
518
+
519
+ /* VariantBar. */
437
520
  .caos-variant-bar { display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 0.7rem; min-width: 0; }
438
521
  .caos-variant-head { display: flex; align-items: center; gap: 0.4rem 0.6rem; flex-wrap: wrap; }
439
- .caos-variant-title { font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-fg-faint); font-weight: 600; }
522
+ .caos-variant-title { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-fg-faint); font-weight: 600; }
440
523
  .caos-variant-note { margin: 0; font-size: 0.88rem; color: var(--color-fg-subtle); font-style: italic; }
441
524
 
442
- /* LaneBadge: which lane produced the values on screen. */
525
+ /* LaneBadge and provenance badges: which lane produced the values on screen, and on what data. */
526
+ .caos-badges { display: inline-flex; gap: 0.3rem; flex-wrap: wrap; }
443
527
  .caos-lane-live { color: var(--color-good); border-color: color-mix(in oklab, var(--color-good) 45%, transparent); }
444
528
  .caos-lane-replay { color: var(--color-accent); border-color: color-mix(in oklab, var(--color-accent) 45%, transparent); }
445
529
  .caos-lane-offline { color: var(--color-warn); border-color: color-mix(in oklab, var(--color-warn) 45%, transparent); }
530
+ .caos-prov-real { color: var(--color-accent-2); border-color: color-mix(in oklab, var(--color-accent-2) 45%, transparent); }
531
+ .caos-prov-synthetic { color: var(--color-magenta); border-color: color-mix(in oklab, var(--color-magenta) 45%, transparent); }
532
+ .caos-prov-published { color: var(--color-fg-subtle); }
446
533
 
447
534
  /* Tones shared by readouts, gauges and verdicts. */
448
535
  .tone-good { --tone: var(--color-good); }
@@ -453,9 +540,10 @@ html, body { overflow-x: hidden; }
453
540
 
454
541
  /* Readout: labelled live values with units. */
455
542
  .caos-readout { display: flex; flex-direction: column; gap: 0.35rem; }
456
- .caos-readout-title, .caos-gauge-title, .caos-verdict-title { margin: 0; font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-fg-faint); font-weight: 600; }
457
- .caos-readout dl { margin: 0; display: grid; gap: 0.25rem; }
458
- .caos-readout-row { display: flex; justify-content: space-between; gap: 0.75rem; font-size: 0.84rem; border-left: 3px solid var(--tone); padding-left: 0.5rem; }
543
+ .caos-readout-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; }
544
+ .caos-readout-title, .caos-gauge-title, .caos-verdict-title { margin: 0; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-fg-faint); font-weight: 600; }
545
+ .caos-readout dl { margin: 0; display: grid; gap: var(--space-1); }
546
+ .caos-readout-row { display: flex; justify-content: space-between; gap: var(--space-3); font-size: var(--text-sm); border-left: 3px solid var(--tone); padding-left: var(--space-2); }
459
547
  .caos-readout-row dt { color: var(--color-fg-subtle); }
460
548
  .caos-readout-row dd { margin: 0; text-align: right; }
461
549
  .caos-readout-value { font-family: var(--font-mono); font-weight: 650; color: var(--color-fg); }
@@ -467,133 +555,123 @@ html, body { overflow-x: hidden; }
467
555
  .caos-gauge-needle { position: absolute; top: 0; width: 3px; height: 100%; background: var(--color-fg); transform: translateX(-1px); }
468
556
  .caos-gauge-scale { display: flex; justify-content: space-between; font-size: 0.72rem; color: var(--color-fg-faint); margin-top: 0.2rem; }
469
557
  .caos-gauge-value { color: var(--color-fg); font-weight: 700; font-family: var(--font-mono); }
470
- .caos-gauge-legend { display: flex; flex-wrap: wrap; gap: 0.5rem; font-size: 0.72rem; color: var(--color-fg-subtle); margin-top: 0.25rem; }
471
- .caos-gauge-legend .dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 0.25rem; background: var(--tone); }
558
+ .caos-gauge-legend { display: flex; flex-wrap: wrap; gap: var(--space-2); font-size: 0.72rem; color: var(--color-fg-subtle); margin-top: var(--space-1); }
559
+ .caos-gauge-dot { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: var(--space-1); background: var(--tone); }
472
560
 
473
561
  /* Verdict: the current diagnosis, coloured by its tone. */
474
- .caos-verdict { border: 1px solid var(--color-border); border-left: 4px solid var(--tone); border-radius: 8px; padding: 0.7rem 0.85rem; background: var(--color-surface-2); }
562
+ .caos-verdict { border: 1px solid var(--color-border); border-left: 4px solid var(--tone); border-radius: var(--radius-md); padding: 0.7rem 0.85rem; background: var(--color-surface-2); }
475
563
  .caos-verdict-text { margin: 0.2rem 0 0; font-weight: 650; color: var(--color-fg); }
476
564
  .caos-verdict.compact { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.6rem; padding: 0.4rem 0.7rem; }
477
565
  .caos-verdict.compact .caos-verdict-text { margin: 0; }
478
566
 
479
567
  /* PlotCard: the frame every chart or table sits in. */
480
- .caos-plot { margin: 0; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 10px; padding: 0.45rem 0.6rem 0.4rem; min-width: 0; }
481
- .caos-plot-head { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; margin-bottom: 0.2rem; font-style: normal; text-align: left; min-height: 1.5rem; }
568
+ .caos-plot { margin: 0; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 0.45rem 0.6rem 0.4rem; min-width: 0; }
569
+ .caos-plot-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); margin-bottom: 0.2rem; font-style: normal; text-align: left; min-height: 1.5rem; }
482
570
  .caos-plot-head .badge { font-size: 0.68rem; padding: 0.05rem 0.45rem; }
483
- .caos-plot-title { font-size: 0.84rem; color: var(--color-fg-subtle); font-weight: 600; }
571
+ .caos-plot-title { font-size: var(--text-sm); color: var(--color-fg-subtle); font-weight: 600; }
484
572
  .caos-plot-actions { display: inline-flex; gap: 0.3rem; }
485
573
  .caos-plot-body { position: relative; min-width: 0; overflow-x: auto; }
486
574
  .caos-plot-note { margin: 0.4rem 0 0; font-size: 0.78rem; color: var(--color-fg-faint); }
575
+ .caos-plot.fill { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
576
+ .caos-plot.fill > .caos-plot-body { flex: 1 1 0; min-height: 0; overflow: hidden; }
487
577
 
488
- /* DocPage / DocSection. */
489
- .caos-doc-section { margin-block: 1.75rem; }
490
-
491
- /* contain: every route is the viewport. The workbench fills the main container; a documentation route scrolls
492
- inside it, so the header and footer stay in view and the document never scrolls (ADR-0071 rule 1). */
493
- .app-shell.fixed.contain > .page { display: flex; flex-direction: column; overflow: hidden; }
494
- .app-shell.fixed.contain > .page > .caos-wb { flex: 1 1 auto; min-height: 0; }
495
- /* The chrome of a contained app spans the instrument's width with its inset, so at 2560 px the header and footer
496
- do not float in a 1200 px column above a full-width workbench (found by the template capture review). */
497
- .app-shell.fixed.contain .header-inner, .app-shell.fixed.contain .site-footer .footer-inner { max-width: var(--maxw-wide); padding-inline: 1rem; }
498
- .app-shell.fixed.contain > .page:has(> .page-body:not(.caos-wb)) { display: block; overflow-y: auto; overscroll-behavior: contain; }
499
- @media (max-width: 900px) {
500
- /* A phone stacks the rail over the instrument and lets the document scroll: the viewport rule is measured at
501
- 1280x800 and above (ADR-0071 Verification). */
502
- .caos-wb { grid-template-columns: minmax(0, 1fr); }
503
- .app-shell.fixed.contain { height: auto; min-height: 100dvh; overflow: visible; }
504
- .app-shell.fixed.contain > .page { display: block; overflow: visible; }
505
- }
506
-
507
- /* == 0.7.0: closed known defects and the new primitives ======================================================== */
508
-
509
- /* Native controls follow the theme (S2; known shell defect 13). */
510
- .app-shell select, .app-shell textarea, .app-shell input:not([type="range"]):not([type="checkbox"]):not([type="radio"]) { background: var(--color-surface-2); color: var(--color-fg); border: 1px solid var(--color-border); border-radius: 6px; }
511
- .app-shell button { color: inherit; }
512
- .caos-arch-toggle { margin: 0 0 8px; font-size: 12px; padding: 5px 10px; color: var(--color-fg); background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: 6px; cursor: pointer; }
513
-
514
- /* A scrolling row says that it scrolls (known shell defect 11): the hidden end fades. */
515
- .main-nav[data-fade-end="1"], .tablist[data-fade-end="1"], .subtablist[data-fade-end="1"] { mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); }
516
- .main-nav[data-fade-start="1"], .tablist[data-fade-start="1"], .subtablist[data-fade-start="1"] { mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); }
517
- .main-nav[data-fade-start="1"][data-fade-end="1"], .tablist[data-fade-start="1"][data-fade-end="1"], .subtablist[data-fade-start="1"][data-fade-end="1"] { mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); }
518
- .subtablist:not([aria-orientation="vertical"]) { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
519
- .subtablist::-webkit-scrollbar { display: none; }
520
- .subtablist .subtab { flex: none; white-space: nowrap; }
521
- /* The phone footer of a contained surface stays one row (the instrument keeps the viewport) and says that it
522
- scrolls: its hidden end fades, and a focused link is scrolled into view (useOverflowFade). */
523
- .app-shell.fixed .footer-meta[data-fade-end="1"] { mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); }
524
- .app-shell.fixed .footer-meta[data-fade-start="1"] { mask-image: linear-gradient(to left, #000 calc(100% - 28px), transparent); }
525
- .app-shell.fixed .footer-meta[data-fade-start="1"][data-fade-end="1"] { mask-image: linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent); }
526
-
527
- /* A failed view (PanelBoundary): an inline message in its own frame, never a blank page. */
528
- .caos-panel-error { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: baseline; padding: 0.7rem 0.9rem; border: 1px solid color-mix(in oklab, var(--color-bad) 45%, transparent); border-left: 4px solid var(--color-bad); border-radius: 8px; background: var(--color-surface-2); color: var(--color-fg); font-size: 0.86rem; }
529
- .caos-panel-error code { flex-basis: 100%; font-size: 0.78rem; color: var(--color-fg-subtle); }
530
-
531
- /* Lane and provenance badges. */
532
- .caos-badges { display: inline-flex; gap: 0.3rem; flex-wrap: wrap; }
533
- .caos-prov-real { color: var(--color-accent-2); border-color: color-mix(in oklab, var(--color-accent-2) 45%, transparent); }
534
- .caos-prov-synthetic { color: var(--color-magenta); border-color: color-mix(in oklab, var(--color-magenta) 45%, transparent); }
535
- .caos-prov-published { color: var(--color-fg-subtle); }
578
+ /* A row of views side by side. Every view of the row takes an equal share, a filling card included (known shell
579
+ defect 23, #54: `.caos-plot.fill` outranked the row's zero basis and took the row). */
580
+ .caos-views-row { display: flex; gap: 0.9rem; flex: 1 1 auto; min-height: 0; min-width: 0; }
581
+ .caos-views-row > *, .caos-views-row > .caos-plot.fill { flex: 1 1 0; min-width: 0; }
536
582
 
537
583
  /* Staleness: a view showing data for an earlier selection says so. */
538
584
  .caos-stale .caos-plot-body > :not(.caos-stale-overlay), .caos-readout.caos-stale dl { opacity: 0.45; }
539
- .caos-stale-overlay { position: absolute; inset: auto 0.5rem 0.5rem auto; padding: 0.25rem 0.55rem; border-radius: 6px; background: var(--color-surface-2); border: 1px solid var(--color-border); font-size: 0.76rem; color: var(--color-warn); }
585
+ .caos-stale-overlay { position: absolute; inset: auto 0.5rem 0.5rem auto; padding: 0.25rem 0.55rem; border-radius: var(--radius-sm); background: var(--color-surface-2); border: 1px solid var(--color-border); font-size: 0.76rem; color: var(--color-warn); }
540
586
  .caos-stale-note { margin: 0.2rem 0 0; font-size: 0.76rem; color: var(--color-warn); }
541
- .caos-readout-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
542
587
 
543
- /* Registered controls. */
544
- .caos-knob { display: flex; flex-direction: column; gap: 0.25rem; }
545
- .caos-knob-head { display: flex; justify-content: space-between; gap: 0.5rem; font-size: 0.8rem; color: var(--color-fg-subtle); }
546
- .caos-knob-value { font-family: var(--font-mono); color: var(--color-fg); font-weight: 600; }
547
- .caos-chipgroup { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
548
- .caos-chipgroup-label { font-size: 0.8rem; color: var(--color-fg-subtle); }
549
-
550
- /* The height chain of the instrument (failure class 4: drawings at zero or at a guessed size). The open group's panel
551
- fills the workbench; a filling view (PlotCard fill, or a .caos-views-row of them) takes the height the panel leaves,
552
- so a Stage or a filling chart inside it has a real size. Sub-tabs inside a panel pass the height down. Below 900 px
553
- the document scrolls, and a filling view gets a fixed readable height instead. */
554
- .caos-cw > .tabs > .tabpanel:not([hidden]) { display: flex; flex-direction: column; }
555
- .caos-cw-panel { flex: 1 1 auto; min-height: 0; }
556
- /* In the workbench the group row is a compact header of the instrument, not a page section break. */
557
- .caos-cw > .tabs > .tablist { margin-bottom: 0.5rem; padding-bottom: 0.25rem; }
558
- .caos-cw > .tabs > .tablist .tab { padding: 0.38rem 0.85rem; }
559
- /* The rail head: the case and variant pickers; the variants wrap in the rail instead of scrolling. */
560
- .caos-wb-rail-head { display: flex; flex-direction: column; gap: 0.75rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--color-border); }
561
- .caos-wb-rail .caos-variant-bar { margin-bottom: 0; }
562
- .caos-wb-rail .caos-chip-row { flex-wrap: wrap; overflow: visible; }
563
- .caos-wb-rail .caos-variant-note { font-size: 0.8rem; }
564
- .caos-cw-panel > .subtabs { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; margin-block: 0; }
565
- .caos-cw-panel > .subtabs > .subtablist { margin-bottom: 0.45rem; }
566
- /* Known shell defect 16: the group row and the sub-tab row are scroll containers (overflow-x auto), so their
567
- automatic minimum height is zero, and in this column flex chain they shrank with a panel taller than the
568
- instrument: the rows were clipped and the panel's top covered the sub-tabs (CAOS_Contraste, 2026-10-05). A tab
569
- row keeps its height; the panel below it scrolls. */
570
- .caos-cw > .tabs > .tablist, .caos-cw-panel > .subtabs > .subtablist { flex: none; }
571
- .page-body.wide.caos-wb { padding-inline: 1rem; }
572
- .caos-cw-panel > .subtabs > .subtabpanels, .caos-cw-panel > .subtabs > .subtabpanels > .subtabpanel { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
573
- .caos-cw-panel > .subtabs > .subtabpanels > .subtabpanel { gap: 0.6rem; }
574
- .caos-views-row { display: flex; gap: 0.9rem; flex: 1 1 auto; min-height: 0; min-width: 0; }
575
- .caos-views-row > * { flex: 1 1 0; min-width: 0; }
576
- .caos-plot.fill { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
577
- .caos-plot.fill > .caos-plot-body { flex: 1 1 0; min-height: 0; overflow: hidden; }
578
- @media (max-width: 900px) {
579
- .caos-views-row { flex-direction: column; }
580
- .caos-plot.fill > .caos-plot-body { flex: none; height: clamp(260px, 48vh, 440px); }
581
- }
588
+ /* Stage: the drawing surface of an instrument, sized by its container. */
589
+ .caos-stage { position: relative; width: 100%; height: 100%; min-width: 0; min-height: 0; }
590
+ /* BarChart: as wide as its box; a filling chart takes the box's height and scrolls rows that do not fit. */
591
+ .caos-bars { position: relative; width: 100%; min-width: 0; }
592
+ .caos-bars.fill { height: 100%; min-height: 0; overflow-y: auto; }
593
+ .caos-bars > svg { display: block; }
594
+ /* A view whose data is not there yet says so (and declares data-state="loading" for the gate). */
595
+ .caos-pending { margin: auto; padding: var(--space-5); color: var(--color-fg-faint); font-size: 0.86rem; text-align: center; }
596
+ /* A failed view (PanelBoundary): an inline message in its own frame, never a blank page. */
597
+ .caos-panel-error { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: baseline; padding: 0.7rem 0.9rem; border: 1px solid color-mix(in oklab, var(--color-bad) 45%, transparent); border-left: 4px solid var(--color-bad); border-radius: var(--radius-md); background: var(--color-surface-2); color: var(--color-fg); font-size: 0.86rem; }
598
+ .caos-panel-error code { flex-basis: 100%; font-size: 0.78rem; color: var(--color-fg-subtle); }
582
599
 
583
600
  /* Tables in views and documents: tabular figures, compact rows, a header that stays in view while the body scrolls.
584
- Products use .caos-table instead of styling their own (S17: shell classes are reserved). */
585
- .caos-table { width: 100%; border-collapse: collapse; font-size: 0.84rem; font-variant-numeric: tabular-nums; color: var(--color-fg); }
601
+ A column of words reads left-aligned and wraps; numbers stay right-aligned. */
602
+ .caos-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); font-variant-numeric: tabular-nums; color: var(--color-fg); }
586
603
  .caos-table th, .caos-table td { padding: 0.35rem 0.6rem; border-bottom: 1px solid var(--color-border); text-align: right; white-space: nowrap; }
587
604
  .caos-table th:first-child, .caos-table td:first-child { text-align: left; }
588
- /* A column of words (a description, an expected band) reads left-aligned and wraps; numbers stay right-aligned. */
589
605
  .caos-table th.caos-col-text, .caos-table td.caos-col-text { text-align: left; white-space: normal; min-width: 16ch; }
590
606
  .caos-table thead th { position: sticky; top: 0; background: var(--color-surface); color: var(--color-fg-subtle); font-weight: 600; border-bottom-color: var(--color-fg-faint); }
591
607
  .caos-table tbody tr:hover { background: var(--color-surface-2); }
592
- /* A phone wraps the cells instead of pushing the table past the page (found by the gate on the template, 390 px). */
593
- @media (max-width: 600px) { .caos-table th, .caos-table td { white-space: normal; padding: 0.3rem 0.4rem; } }
594
608
 
595
- /* A view whose data is not there yet: it says so (and declares data-state="loading" for the gate). */
596
- .caos-pending { margin: auto; padding: 1.5rem; color: var(--color-fg-faint); font-size: 0.86rem; text-align: center; }
609
+ @media (max-width: 900px) {
610
+ /* The views of a row stack; a filling view gets a fixed readable height instead of the panel's. */
611
+ .caos-views-row { flex-direction: column; }
612
+ .caos-views-row > *, .caos-views-row > .caos-plot.fill { flex: none; }
613
+ .caos-plot.fill > .caos-plot-body { flex: none; height: clamp(260px, 48vh, 440px); }
614
+ }
615
+ @media (max-width: 760px) {
616
+ /* A phone wraps the cells instead of pushing the table past the page (found by the gate on the template). */
617
+ .caos-table th, .caos-table td { white-space: normal; padding: 0.3rem 0.4rem; }
618
+ }
597
619
 
598
- /* Stage: the drawing surface of an instrument, sized by its container. */
599
- .caos-stage { position: relative; width: 100%; height: 100%; min-width: 0; min-height: 0; }
620
+ /* == 10 frames ================================================================================================== */
621
+
622
+ /* ADR-0070: the shell-owned, full-viewport scenario focus frame. */
623
+ .caos-focus-shell { position: fixed; inset: 0; z-index: var(--z-focus); display: grid; grid-template-columns: minmax(0,1fr) var(--rail-w); min-width: 0; height: 100dvh; background: var(--color-bg); color: var(--color-fg); }
624
+ .caos-focus-stage { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: var(--color-bg); }
625
+ .caos-focus-instrument { position: absolute; inset: 0; min-width: 0; min-height: 0; }
626
+ .caos-focus-label, .caos-focus-hud > div { background: color-mix(in oklab,var(--color-surface) 82%,transparent); backdrop-filter: blur(6px); border: 1px solid var(--color-border); border-radius: var(--radius-sm); }
627
+ .caos-focus-label { position: absolute; top: 14px; left: 14px; max-width: min(46ch,calc(100% - 230px)); display: grid; gap: 4px; padding: 10px 12px; pointer-events: none; }
628
+ .caos-focus-label strong { font-size: 0.96rem; line-height: 1.2; }
629
+ .caos-focus-label span { font-size: 0.78rem; line-height: 1.4; color: var(--color-fg-subtle); }
630
+ .caos-focus-hud { position: absolute; left: 14px; top: 104px; display: grid; gap: 6px; pointer-events: none; }
631
+ .caos-focus-hud > div { display: grid; gap: 1px; padding: 7px 10px; min-width: 120px; }
632
+ .caos-focus-hud strong { font: 700 1rem var(--font-mono,monospace); }
633
+ .caos-focus-hud span { color: var(--color-fg-subtle); font-size: 0.61rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; }
634
+ .caos-focus-actions { position: absolute; top: 12px; right: 12px; display: flex; gap: 6px; align-items: center; padding: 4px; border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: color-mix(in oklab,var(--color-surface) 90%,transparent); backdrop-filter: blur(6px); }
635
+ .caos-focus-actions button { border: 0; border-radius: 4px; padding: 6px 9px; background: var(--color-surface-2); color: var(--color-fg); font-size: 0.75rem; font-weight: 700; cursor: pointer; }
636
+ .caos-focus-rail { min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain; border-left: 1px solid var(--color-border); background: var(--color-surface); }
637
+ @media (max-width: 900px) {
638
+ .caos-focus-shell { grid-template-columns: minmax(0,1fr); grid-template-rows: minmax(0,54dvh) minmax(0,46dvh); }
639
+ .caos-focus-rail { border-left: 0; border-top: 1px solid var(--color-border); }
640
+ .caos-focus-label { max-width: calc(100% - 16px); top: 49px; left: 8px; padding: 6px 8px; }
641
+ .caos-focus-hud { top: auto; bottom: 8px; left: 8px; right: 8px; display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 4px; }
642
+ .caos-focus-hud > div { min-width: 0; padding: 5px; }
643
+ .caos-focus-hud strong { font-size: 0.72rem; white-space: nowrap; }
644
+ .caos-focus-hud span { font-size: 0.48rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
645
+ .caos-focus-actions { top: 7px; right: 7px; }
646
+ }
647
+
648
+ /* The authenticated console frame (WorkbenchShell): a sidebar on a desktop, a bottom bar on a phone. Products supply
649
+ their slots; the package owns the frame, the navigation semantics and the main landmark. */
650
+ .caos-workbench-shell { min-height: 100vh; display: grid; grid-template-columns: 238px minmax(0, 1fr); }
651
+ .caos-workbench-sidebar { position: fixed; inset: 0 auto 0 0; z-index: 20; width: 238px; display: flex; flex-direction: column; padding: 1.25rem 0.875rem; border-right: 1px solid var(--color-border); background: var(--color-surface); }
652
+ .caos-workbench-sidebar nav { display: grid; gap: var(--space-1); }
653
+ .caos-workbench-sidebar nav a { display: flex; align-items: center; gap: var(--space-3); padding: 0.625rem 0.7rem; border: 1px solid transparent; border-radius: 0.7rem; color: var(--color-fg-subtle); font-size: 0.9rem; font-weight: 650; }
654
+ .caos-workbench-sidebar nav a:hover { color: var(--color-fg); background: var(--color-surface-2); text-decoration: none; }
655
+ .caos-workbench-sidebar nav a.active { color: var(--color-accent); border-color: color-mix(in oklab, var(--color-accent) 24%, transparent); background: var(--color-accent-soft); }
656
+ .caos-workbench-sidebar nav a svg.lucide { width: var(--icon-lg); height: var(--icon-lg); }
657
+ .caos-workbench-main { grid-column: 2; min-width: 0; }
658
+ .caos-workbench-header { position: sticky; top: 0; z-index: 15; min-height: 64px; display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: 0 2rem; border-bottom: 1px solid var(--color-border); background: color-mix(in oklab, var(--color-bg) 88%, transparent); backdrop-filter: blur(12px); }
659
+ .caos-workbench-content { width: min(1540px, 100%); margin: 0 auto; padding: 2.25rem 2rem 4rem; }
660
+ @media (max-width: 760px) {
661
+ .caos-workbench-shell { display: block; padding-bottom: 66px; }
662
+ .caos-workbench-sidebar { inset: auto 0 0; width: auto; height: 62px; display: block; border: 0; border-top: 1px solid var(--color-border); padding: 5px 8px; }
663
+ .caos-workbench-sidebar > :not(nav) { display: none; }
664
+ .caos-workbench-sidebar nav { grid-template-columns: repeat(var(--caos-workbench-route-count, 5), minmax(0, 1fr)); }
665
+ .caos-workbench-sidebar nav a { display: grid; justify-items: center; gap: 2px; overflow: hidden; padding: 5px; font-size: 0.7rem; text-overflow: ellipsis; white-space: nowrap; }
666
+ .caos-workbench-main { min-width: 0; }
667
+ .caos-workbench-header { min-height: 58px; padding: 0 1rem; }
668
+ .caos-workbench-content { padding: 1.5rem 1rem 2.5rem; }
669
+ }
670
+
671
+ /* ADR-0058: the architecture modal. A hand-authored architecture SVG carries BOTH languages in ONE file and the
672
+ wrapper picks one; the rules are scoped to [data-arch-lang] so the short class names cannot collide. */
673
+ [data-arch-lang] .l-es { display: none; }
674
+ [data-arch-lang="es"] .l-en { display: none; }
675
+ [data-arch-lang="es"] .l-es { display: inline; }
676
+ .caos-architecture-diagram > svg { display: block; width: 100%; height: auto; max-width: none; }
677
+ .caos-arch-toggle { margin: 0 0 8px; font-size: 12px; padding: 5px 10px; color: var(--color-fg); background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-sm); cursor: pointer; }