@x-tech-laboratories/halon 0.0.0-stage → 0.1.1

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/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # Halon for the web
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An implementation of [THEME-DESIGN-GUIDE.md](../THEME-DESIGN-GUIDE.md) for web pages, the one the X-apps draw their
4
+ dashboards with. Published as `@x-tech-laboratories/halon`.
5
+
6
+ ```
7
+ web/
8
+ _tokens.css Layer 1 — SOURCE. The token sheet, naming GTK's colours as @name.
9
+ halon-tokens.css GENERATED. _tokens.css with every @name filled in, light and dark.
10
+ halon.css Layer 2 — SOURCE. Every component rule. No colours.
11
+ ```
12
+
13
+ `halon-tokens.css` is written by `scripts/build-web.mjs` from `_tokens.css` and the same
14
+ `gtk/Halon/shared/_tokens-*.css` that drives the other ports. The build also fails if a colour literal appears in
15
+ `halon.css`.
16
+
17
+ ```sh
18
+ node scripts/build-web.mjs # regenerate halon-tokens.css
19
+ node scripts/build-web.mjs --check # fail if the output is stale
20
+ ```
21
+
22
+ The colours only the web uses, the chart series, the info status and the solid fills, are literal in `_tokens.css`.
23
+
24
+ ## Use
25
+
26
+ ```ts
27
+ import "@x-tech-laboratories/halon/halon-tokens.css";
28
+ import "@x-tech-laboratories/halon/halon.css";
29
+ ```
30
+
31
+ The dark scheme follows the system, and `data-theme="light"` or `data-theme="dark"` on `<html>` overrides it. The
32
+ widgets in `@x-tech-laboratories/x-libs` build the markup these rules style.
33
+
34
+ ## Publish
35
+
36
+ ```sh
37
+ cd halon/web && npm publish --access public
38
+ ```
@@ -0,0 +1,200 @@
1
+ /* Halon tokens for the web: GENERATED by scripts/build-web.mjs from web/_tokens.css and gtk/Halon/shared/_tokens-*.css; edit those, not this. */
2
+
3
+ :root {
4
+ --font-family-interface:
5
+ system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
6
+ "Noto Sans", Cantarell, "Helvetica Neue", Arial, sans-serif,
7
+ "Apple Color Emoji", "Segoe UI Emoji";
8
+ --font-family-monospace:
9
+ ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
10
+ "DejaVu Sans Mono", monospace;
11
+
12
+ /* Spacing — a 4px scale. Nothing may use a value that is not on it. */
13
+ --space-1: 2px;
14
+ --space-2: 4px;
15
+ --space-3: 6px;
16
+ --space-4: 8px;
17
+ --space-5: 12px;
18
+ --space-6: 16px;
19
+ --space-7: 24px;
20
+ --space-8: 32px;
21
+
22
+ /* Geometry — one control height, so everything on a row aligns */
23
+ --control-height: 32px;
24
+ --control-padding-x: 14px;
25
+ --icon-button-size: 32px;
26
+ --row-height: 28px;
27
+ --frame-height: 34px;
28
+ --icon-size-small: 16px;
29
+ --icon-size-large: 20px;
30
+ --sidebar-width: 248px;
31
+ --border-width: 1px;
32
+ --focus-ring-width: 3px;
33
+
34
+ /* Two radii: small for anything you act on, default for any surface that holds them */
35
+ --radius-small: 4px;
36
+ --radius-default: 8px;
37
+
38
+ --text-label: 11px;
39
+ --text-caption: 12px;
40
+ --text-control: 13px;
41
+ --text-prose: 14px;
42
+ --text-h3: 15px;
43
+ --text-h2: 19px;
44
+ --text-h1: 26px;
45
+
46
+ --letter-spacing-caps: .04em;
47
+ --letter-spacing-display: -.01em;
48
+
49
+ --line-height-ui: 16px;
50
+ --line-height-body: 1.55;
51
+ --measure: 68ch;
52
+
53
+ --layer-sticky: 100;
54
+ --layer-menu: 200;
55
+ --layer-modal: 300;
56
+ --layer-toast: 400;
57
+ --layer-tooltip: 500;
58
+
59
+ --duration-fast: .12s;
60
+ --duration-slow: .3s;
61
+
62
+ /* Surfaces */
63
+ --surface-root: #f1f5f9;
64
+ --surface-default: #ffffff;
65
+ --surface-secondary: #f1f5f9;
66
+ --surface-navigation: var(--surface-secondary);
67
+ --surface-navigation-hover: #e2e8f0;
68
+ --surface-overlay: rgba(15, 23, 42, 0.45);
69
+
70
+ /* Text on surfaces */
71
+ --text-heading: #0f172a;
72
+ --text-body: #1a1a2e;
73
+ --text-secondary: #475569;
74
+ --text-tertiary: #64748b;
75
+ --text-on-navigation: #475569;
76
+
77
+ /* Text on fills */
78
+ --text-on-fill: #ffffff;
79
+ --text-on-light: #0f172a;
80
+
81
+ /* Lines */
82
+ --border-default: #e2e8f0;
83
+ --border-hover: #cbd5e1;
84
+ --border-control: #8792a3;
85
+ --border-focus: var(--accent);
86
+
87
+ /* Interaction */
88
+ --accent: #2563eb;
89
+ --focus-ring: rgba(37, 99, 235, 0.15);
90
+
91
+ /* Data series — the lines of one chart, told apart by lightness as well as hue */
92
+ --series-1: var(--accent);
93
+ --series-2: #7c3aed;
94
+ --series-3: #64748b;
95
+
96
+ /* Status */
97
+ --status-success: #047857;
98
+ --status-info: #0369a1;
99
+ --status-warning: #f59e0b;
100
+ --status-warning-text: #c2410c;
101
+ --status-danger: #dc2626;
102
+ --badge-experimental: #7c3aed;
103
+
104
+ /* Solid fills that keep their lightness in both schemes, for Quasar's fixed white or black text. */
105
+ --fill-accent: #2563eb;
106
+ --fill-success: #047857;
107
+ --fill-danger: #dc2626;
108
+ --fill-warning: #f59e0b;
109
+
110
+ /* Elevation — slate-900 at low alpha, never neutral black */
111
+ --shadow-item: 0 1px 2px rgba(15, 23, 42, 0.12);
112
+ --shadow-card: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.06);
113
+ --shadow-tab: 0 1px 3px rgba(15, 23, 42, 0.15);
114
+ --shadow-floating: 0 2px 8px rgba(15, 23, 42, 0.20);
115
+ --shadow-modal: 0 8px 32px rgba(15, 23, 42, 0.25);
116
+
117
+ color-scheme: light;
118
+ }
119
+
120
+ @media (prefers-color-scheme: dark) {
121
+ :root:not([data-theme="light"]) {
122
+ --surface-root: #060b14;
123
+ --surface-default: #16213a;
124
+ --surface-secondary: #060b14;
125
+ --surface-navigation-hover: #101b2d;
126
+ --surface-overlay: rgba(0, 0, 0, 0.6);
127
+
128
+ --text-heading: #f8fafc;
129
+ --text-body: #e2e8f0;
130
+ --text-secondary: #cbd5e1;
131
+ --text-tertiary: #94a3b8;
132
+ --text-on-navigation: #ffffff;
133
+
134
+ --text-on-fill: #0b1220;
135
+
136
+ --border-default: #334155;
137
+ --border-hover: #475569;
138
+ --border-control: #64748b;
139
+
140
+ --accent: #60a5fa;
141
+ --focus-ring: rgba(96, 165, 250, 0.28);
142
+
143
+ --series-2: #c4b5fd;
144
+ --series-3: #94a3b8;
145
+
146
+ --status-success: #10b981;
147
+ --status-info: #38bdf8;
148
+ --status-warning-text: #f59e0b;
149
+ --status-danger: #f87171;
150
+ --badge-experimental: #a78bfa;
151
+
152
+ --shadow-item: 0 1px 2px rgba(15, 23, 42, 0.50);
153
+ --shadow-card: 0 1px 3px rgba(15, 23, 42, 0.27), 0 1px 2px rgba(15, 23, 42, 0.20);
154
+ --shadow-tab: 0 1px 3px rgba(15, 23, 42, 0.50);
155
+ --shadow-floating: 0 2px 8px rgba(15, 23, 42, 0.60);
156
+ --shadow-modal: 0 8px 32px rgba(15, 23, 42, 0.70);
157
+
158
+ color-scheme: dark;
159
+ }
160
+ }
161
+
162
+ :root[data-theme="dark"] {
163
+ --surface-root: #060b14;
164
+ --surface-default: #16213a;
165
+ --surface-secondary: #060b14;
166
+ --surface-navigation-hover: #101b2d;
167
+ --surface-overlay: rgba(0, 0, 0, 0.6);
168
+
169
+ --text-heading: #f8fafc;
170
+ --text-body: #e2e8f0;
171
+ --text-secondary: #cbd5e1;
172
+ --text-tertiary: #94a3b8;
173
+ --text-on-navigation: #ffffff;
174
+
175
+ --text-on-fill: #0b1220;
176
+
177
+ --border-default: #334155;
178
+ --border-hover: #475569;
179
+ --border-control: #64748b;
180
+
181
+ --accent: #60a5fa;
182
+ --focus-ring: rgba(96, 165, 250, 0.28);
183
+
184
+ --series-2: #c4b5fd;
185
+ --series-3: #94a3b8;
186
+
187
+ --status-success: #10b981;
188
+ --status-info: #38bdf8;
189
+ --status-warning-text: #f59e0b;
190
+ --status-danger: #f87171;
191
+ --badge-experimental: #a78bfa;
192
+
193
+ --shadow-item: 0 1px 2px rgba(15, 23, 42, 0.50);
194
+ --shadow-card: 0 1px 3px rgba(15, 23, 42, 0.27), 0 1px 2px rgba(15, 23, 42, 0.20);
195
+ --shadow-tab: 0 1px 3px rgba(15, 23, 42, 0.50);
196
+ --shadow-floating: 0 2px 8px rgba(15, 23, 42, 0.60);
197
+ --shadow-modal: 0 8px 32px rgba(15, 23, 42, 0.70);
198
+
199
+ color-scheme: dark;
200
+ }
package/halon.css ADDED
@@ -0,0 +1,826 @@
1
+ /* Halon components: every rule is drawn from halon-tokens.css, so a literal color here is a defect.
2
+ * The top bar and sidebar are the recessed frame, main is the one raised pane inset in it, and panels sit on that pane. */
3
+
4
+ /* ── Base ─────────────────────────────────────────────────────────────────── */
5
+
6
+ * { box-sizing: border-box; }
7
+ /* An element hidden by its attribute stays hidden whatever display its class gives it. */
8
+ [hidden] { display: none !important; }
9
+
10
+ body {
11
+ margin: 0;
12
+ background-color: var(--surface-navigation);
13
+ color: var(--text-body);
14
+ font-family: var(--font-family-interface);
15
+ font-size: var(--text-prose);
16
+ line-height: var(--line-height-body);
17
+ }
18
+
19
+ a { color: var(--accent); text-decoration-color: var(--accent); }
20
+ h1, h2, h3, h4, h5 { color: var(--text-heading); margin: 0; }
21
+ h1 { font-size: var(--text-h1); font-weight: 700; line-height: 1.25; letter-spacing: var(--letter-spacing-display); }
22
+ h2 { font-size: var(--text-h3); font-weight: 600; line-height: var(--line-height-ui); }
23
+ code, pre { font-family: var(--font-family-monospace); font-size: var(--text-caption); }
24
+ ::selection { background: var(--focus-ring); color: var(--text-heading); }
25
+ :focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: 1px; }
26
+
27
+ .muted { color: var(--text-secondary); }
28
+ .small { font-size: var(--text-caption); }
29
+ .visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
30
+
31
+ .skip {
32
+ position: absolute;
33
+ left: var(--space-4);
34
+ top: -48px;
35
+ z-index: var(--layer-tooltip);
36
+ padding: var(--space-3) var(--space-4);
37
+ border-radius: var(--radius-small);
38
+ background-color: var(--surface-navigation);
39
+ color: var(--text-heading);
40
+ box-shadow: var(--shadow-floating);
41
+ }
42
+ .skip:focus { top: var(--space-4); }
43
+
44
+ @media (prefers-reduced-motion: reduce) {
45
+ *, *::before, *::after {
46
+ animation-duration: 1ms !important;
47
+ animation-iteration-count: 1 !important;
48
+ transition-duration: 1ms !important;
49
+ scroll-behavior: auto !important;
50
+ }
51
+ }
52
+
53
+ /* ── Buttons — three weights, the accent is the scarcest (§6.1) ───────────── */
54
+
55
+ /* Default weight: a neutral bordered ghost. */
56
+ button, .button {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: var(--space-2);
60
+ height: var(--control-height);
61
+ padding: 7px var(--control-padding-x);
62
+ border: var(--border-width) solid var(--border-default);
63
+ border-radius: var(--radius-small);
64
+ background-color: transparent;
65
+ color: var(--text-body);
66
+ font-family: inherit;
67
+ font-size: var(--text-control);
68
+ font-weight: 500;
69
+ line-height: var(--line-height-ui);
70
+ cursor: pointer;
71
+ text-decoration: none;
72
+ white-space: nowrap;
73
+ transition: border-color var(--duration-fast), color var(--duration-fast), opacity var(--duration-fast);
74
+ }
75
+ button:hover:not(:disabled), .button:hover { border-color: var(--accent); }
76
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
77
+ .button[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
78
+
79
+ /* Filled weight: the one primary action on a view, hovering by opacity only. */
80
+ button.accent.solid, .button.accent.solid {
81
+ background-color: var(--accent);
82
+ border-color: var(--accent);
83
+ color: var(--text-on-fill);
84
+ }
85
+ button.accent.solid:hover:not(:disabled), .button.accent.solid:hover { border-color: var(--accent); opacity: 0.85; }
86
+
87
+ /* Destructive actions take the danger color at whichever weight they sit. */
88
+ button.danger { border-color: var(--status-danger); color: var(--status-danger); }
89
+ button.danger:hover:not(:disabled) { border-color: var(--status-danger); opacity: 0.85; }
90
+ button.danger.solid { background-color: var(--status-danger); color: var(--text-on-fill); }
91
+
92
+ /* Flat weight: repeated row actions, with no border until hover. */
93
+ .row-actions button, .row-actions .button, .toast button {
94
+ height: var(--row-height);
95
+ padding: var(--space-3) var(--space-4);
96
+ border-color: transparent;
97
+ color: var(--text-secondary);
98
+ }
99
+ .row-actions button:hover:not(:disabled), .row-actions .button:hover { border-color: var(--border-default); color: var(--text-heading); }
100
+ .row-actions button.danger { color: var(--status-danger); }
101
+ .row-actions button.danger:hover:not(:disabled) { border-color: var(--status-danger); color: var(--status-danger); }
102
+
103
+ button.link {
104
+ height: auto;
105
+ padding: 0;
106
+ border: none;
107
+ background: none;
108
+ color: var(--accent);
109
+ font-weight: 400;
110
+ text-decoration: underline;
111
+ text-underline-offset: 2px;
112
+ }
113
+
114
+ /* An icon button is a square control holding a glyph. */
115
+ button.icon {
116
+ justify-content: center;
117
+ width: var(--icon-button-size);
118
+ padding: 0;
119
+ font-size: var(--icon-size-small);
120
+ }
121
+
122
+ /* ── Fields — hairline at rest, accent on interaction (§6.2) ──────────────── */
123
+
124
+ input:not([type="checkbox"]):not([type="radio"]), select, textarea {
125
+ height: var(--control-height);
126
+ padding: 7px 11px;
127
+ border: var(--border-width) solid var(--border-default);
128
+ border-radius: var(--radius-small);
129
+ background-color: var(--surface-default);
130
+ color: var(--text-body);
131
+ font-family: inherit;
132
+ font-size: var(--text-control);
133
+ line-height: var(--line-height-ui);
134
+ transition: border-color var(--duration-fast), opacity var(--duration-fast);
135
+ }
136
+ input:not([type="checkbox"]):not([type="radio"]):hover, textarea:hover { border-color: var(--border-focus); }
137
+ input:not([type="checkbox"]):not([type="radio"]):focus, textarea:focus {
138
+ border-color: var(--border-focus);
139
+ outline: var(--focus-ring-width) solid var(--focus-ring);
140
+ outline-offset: 0;
141
+ }
142
+ input::placeholder, textarea::placeholder { color: var(--text-tertiary); opacity: 1; }
143
+ input[type="checkbox"], input[type="radio"] { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
144
+ input.filter { min-width: 220px; }
145
+ input[readonly] { background-color: var(--surface-secondary); color: var(--text-secondary); }
146
+ textarea {
147
+ height: auto;
148
+ min-height: 80px;
149
+ font-family: var(--font-family-monospace);
150
+ line-height: var(--line-height-body);
151
+ resize: vertical;
152
+ }
153
+
154
+ /* A closed select is an action, so it reads as a ghost with accent text (§6.3). */
155
+ select {
156
+ padding: 7px var(--space-4) 7px var(--space-5);
157
+ background-color: transparent;
158
+ color: var(--accent);
159
+ font-weight: 500;
160
+ cursor: pointer;
161
+ }
162
+ select:hover { opacity: 0.85; }
163
+ select:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: 0; }
164
+ select option { background-color: var(--surface-default); color: var(--text-body); font-weight: 400; }
165
+
166
+ .field { display: inline-flex; flex-direction: column; gap: var(--space-2); }
167
+ .field-label { font-size: var(--text-caption); font-weight: 500; color: var(--text-secondary); line-height: var(--line-height-ui); }
168
+ .check { display: inline-flex; align-items: center; gap: var(--space-3); font-size: var(--text-control); }
169
+
170
+ /* A segmented switch: the group owns the border and the chosen segment takes the accent. */
171
+ .segmented {
172
+ display: inline-flex;
173
+ border: var(--border-width) solid var(--border-default);
174
+ border-radius: var(--radius-small);
175
+ overflow: hidden;
176
+ }
177
+ .segmented button {
178
+ height: calc(var(--control-height) - 2 * var(--border-width));
179
+ border: none;
180
+ border-radius: 0;
181
+ color: var(--text-secondary);
182
+ }
183
+ .segmented button:hover:not(:disabled) { color: var(--text-heading); }
184
+ .segmented button + button { border-left: var(--border-width) solid var(--border-default); }
185
+ .segmented button[aria-pressed="true"], .segmented button[aria-checked="true"] { background-color: var(--accent); color: var(--text-on-fill); }
186
+
187
+ /* A switch is a checkbox drawn as a track whose knob slides to the accent side when on. */
188
+ input[type="checkbox"][role="switch"] {
189
+ position: relative;
190
+ flex: none;
191
+ width: 32px;
192
+ height: 18px;
193
+ border: var(--border-width) solid var(--border-control);
194
+ border-radius: 9px;
195
+ background-color: var(--surface-secondary);
196
+ appearance: none;
197
+ cursor: pointer;
198
+ transition: background-color var(--duration-fast), border-color var(--duration-fast);
199
+ }
200
+ input[type="checkbox"][role="switch"]::before {
201
+ content: "";
202
+ position: absolute;
203
+ top: 2px;
204
+ left: 2px;
205
+ width: 12px;
206
+ height: 12px;
207
+ border-radius: 50%;
208
+ background-color: var(--border-control);
209
+ transition: transform var(--duration-fast), background-color var(--duration-fast);
210
+ }
211
+ input[type="checkbox"][role="switch"]:checked { border-color: var(--accent); background-color: var(--accent); }
212
+ input[type="checkbox"][role="switch"]:checked::before { transform: translateX(14px); background-color: var(--text-on-fill); }
213
+ input[type="checkbox"][role="switch"]:disabled { opacity: 0.5; cursor: not-allowed; }
214
+
215
+ /* ── The frame (§6.4): top bar and sidebar are one recessed surface ───────── */
216
+
217
+ .topbar {
218
+ position: sticky;
219
+ top: 0;
220
+ z-index: calc(var(--layer-sticky) + 2);
221
+ display: flex;
222
+ align-items: center;
223
+ gap: var(--space-4);
224
+ height: var(--frame-height);
225
+ padding: 0 var(--space-2);
226
+ background-color: var(--surface-navigation);
227
+ color: var(--text-on-navigation);
228
+ }
229
+ .brand {
230
+ padding: 0 var(--space-6);
231
+ color: var(--text-heading);
232
+ font-size: var(--text-h3);
233
+ font-weight: 600;
234
+ line-height: var(--line-height-ui);
235
+ text-decoration: none;
236
+ }
237
+
238
+ /* Frame buttons hover with the frame's own fill and keep no border. */
239
+ .topbar button {
240
+ border-color: transparent;
241
+ color: var(--text-on-navigation);
242
+ transition: background-color var(--duration-fast), color var(--duration-fast);
243
+ }
244
+ .topbar button:hover:not(:disabled) { border-color: transparent; background-color: var(--surface-navigation-hover); color: var(--text-heading); }
245
+
246
+ .menu-button { display: none; }
247
+ #theme { margin-left: auto; }
248
+
249
+ .account-link {
250
+ padding: 0 var(--space-4);
251
+ color: var(--text-on-navigation);
252
+ font-size: var(--text-control);
253
+ text-decoration: none;
254
+ white-space: nowrap;
255
+ transition: color var(--duration-fast);
256
+ }
257
+ .account-link:hover { color: var(--text-heading); }
258
+
259
+ .layout { display: grid; grid-template-columns: var(--sidebar-width) minmax(0, 1fr); min-height: calc(100vh - var(--frame-height)); }
260
+ .sidebar {
261
+ position: sticky;
262
+ top: var(--frame-height);
263
+ align-self: start;
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: var(--border-width);
267
+ height: calc(100vh - var(--frame-height));
268
+ padding: var(--space-4);
269
+ background-color: var(--surface-navigation);
270
+ }
271
+ .sidebar a {
272
+ min-height: var(--row-height);
273
+ padding: var(--space-3) var(--space-4);
274
+ border-radius: var(--radius-small);
275
+ color: var(--text-body);
276
+ font-size: var(--text-control);
277
+ line-height: var(--line-height-ui);
278
+ text-decoration: none;
279
+ transition: background-color var(--duration-fast), color var(--duration-fast);
280
+ }
281
+ .sidebar a:hover { background-color: var(--surface-navigation-hover); color: var(--text-heading); }
282
+
283
+ /* A place you are, not a row you picked: elevation, not fill. */
284
+ .sidebar a.active { background-color: var(--surface-default); color: var(--text-heading); box-shadow: var(--shadow-item); font-weight: 500; }
285
+ .scrim { position: fixed; inset: 0; z-index: calc(var(--layer-sticky) + 1); background-color: var(--surface-overlay); }
286
+
287
+ /* The content pane: the one raised surface, inset in the frame. */
288
+ main {
289
+ min-width: 0;
290
+ min-height: calc(100vh - var(--frame-height) - var(--space-2));
291
+ margin: 0 var(--space-2) var(--space-2) 0;
292
+ padding: 28px var(--space-8) 80px;
293
+ border-radius: var(--radius-default);
294
+ background-color: var(--surface-default);
295
+ outline: none;
296
+ }
297
+ .page { max-width: 1280px; margin: 0 auto; display: grid; gap: var(--space-6); }
298
+
299
+ body.logged-out .topbar > :not(.brand), body.logged-out .sidebar { display: none; }
300
+ body.logged-out .layout { grid-template-columns: minmax(0, 1fr); }
301
+ body.logged-out main { margin-left: var(--space-2); }
302
+
303
+ .login { max-width: 320px; margin: calc(2 * var(--space-8)) auto; display: grid; gap: var(--space-5); }
304
+ .login .field, .login input { width: 100%; }
305
+ .login button, .login .button { justify-content: center; }
306
+ .login-brand { display: grid; justify-items: center; gap: var(--space-4); }
307
+ .login-brand img { width: 96px; height: 96px; }
308
+
309
+ .user-name { color: var(--text-on-navigation); font-size: var(--text-control); }
310
+
311
+ /* ── Panels, tabs and headers ─────────────────────────────────────────────── */
312
+
313
+ /* A panel and a stack space their children with their gap, so no block inside needs a margin of its own. */
314
+ .panel {
315
+ display: grid;
316
+ grid-template-columns: minmax(0, 1fr);
317
+ gap: var(--space-5);
318
+ align-content: start;
319
+ min-width: 0;
320
+ padding: 14px var(--space-6);
321
+ border: var(--border-width) solid var(--border-default);
322
+ border-radius: var(--radius-default);
323
+ background-color: var(--surface-default);
324
+ box-shadow: var(--shadow-card);
325
+ }
326
+ .panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); flex-wrap: wrap; min-height: var(--control-height); }
327
+ .panel-tools { display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap; }
328
+ .columns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-6); }
329
+ .stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-6); align-content: start; }
330
+ .stack.tight { gap: var(--space-5); }
331
+
332
+ .page-head { display: grid; gap: var(--space-4); }
333
+ .page-title { display: flex; align-items: center; gap: var(--space-5); flex-wrap: wrap; }
334
+ .page-title h1 { word-break: break-all; }
335
+ .page-title.spread { justify-content: space-between; }
336
+ .page-head p { margin: 0; }
337
+ .head-actions { display: flex; gap: var(--space-4); flex-wrap: wrap; }
338
+
339
+ .crumbs { font-size: var(--text-control); color: var(--text-secondary); overflow-wrap: anywhere; }
340
+
341
+ /* A frame tab bar: the strip is recessed and the open tab is a raised card with an accent line. */
342
+ .tabs {
343
+ display: flex;
344
+ align-items: center;
345
+ gap: var(--space-1);
346
+ height: var(--frame-height);
347
+ padding: 0 var(--space-2);
348
+ border-radius: var(--radius-default);
349
+ background-color: var(--surface-navigation);
350
+ overflow-x: auto;
351
+ scrollbar-width: none;
352
+ }
353
+ .tabs::-webkit-scrollbar { display: none; }
354
+ .tab {
355
+ display: flex;
356
+ align-items: center;
357
+ height: var(--row-height);
358
+ padding: var(--space-3) 10px;
359
+ border-radius: var(--radius-default);
360
+ color: var(--text-on-navigation);
361
+ font-size: var(--text-control);
362
+ line-height: var(--line-height-ui);
363
+ text-decoration: none;
364
+ white-space: nowrap;
365
+ transition: background-color var(--duration-fast), color var(--duration-fast);
366
+ }
367
+ /* A tab that switches in place rather than linking takes the tab's look, not a button's. */
368
+ button.tab { border: none; background-color: transparent; font-weight: 400; }
369
+ .tab:hover { background-color: var(--surface-navigation-hover); color: var(--text-heading); }
370
+ .tab.active { background-color: var(--surface-default); color: var(--text-heading); box-shadow: inset 0 -2px 0 var(--accent), var(--shadow-tab); font-weight: 500; }
371
+
372
+ .toolbar { display: flex; gap: var(--space-5); align-items: end; flex-wrap: wrap; }
373
+ .stepper { display: flex; gap: var(--space-4); align-items: center; }
374
+
375
+ /* Status regions are tinted, and the text in them stays ordinary body text (§6.7). */
376
+ .banner {
377
+ padding: var(--space-5) var(--space-6);
378
+ border-radius: var(--radius-default);
379
+ background-color: color-mix(in srgb, var(--accent) 15%, transparent);
380
+ color: var(--text-body);
381
+ font-size: var(--text-control);
382
+ }
383
+
384
+ .facts { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-5) var(--space-6); margin: 0; }
385
+ .facts > div { display: grid; gap: var(--space-1); align-content: start; }
386
+ .facts dt { color: var(--text-secondary); font-size: var(--text-caption); }
387
+ .facts dd { margin: 0; overflow-wrap: anywhere; }
388
+ .facts-rows { grid-template-columns: 1fr; gap: 0; }
389
+ .facts-rows > div { grid-template-columns: minmax(140px, 220px) 1fr; gap: var(--space-6); padding: var(--space-3) 0; border-bottom: var(--border-width) solid var(--border-default); }
390
+ .facts-rows > div:last-child { border-bottom: none; }
391
+ .facts-rows dt { font-family: var(--font-family-monospace); }
392
+
393
+ /* ── Tones ────────────────────────────────────────────────────────────────── */
394
+
395
+ .tone-good { color: var(--status-success); }
396
+ .tone-warn { color: var(--status-warning-text); }
397
+ .tone-bad { color: var(--status-danger); }
398
+ .tone-info { color: var(--status-info); }
399
+ .tone-none { color: var(--text-tertiary); }
400
+ .tone-fill-good { background-color: color-mix(in srgb, var(--status-success) 15%, transparent); color: var(--text-body); }
401
+ .tone-fill-warn { background-color: color-mix(in srgb, var(--status-warning) 15%, transparent); color: var(--text-body); }
402
+ .tone-fill-bad { background-color: color-mix(in srgb, var(--status-danger) 15%, transparent); color: var(--text-body); }
403
+ .tone-fill-info { background-color: color-mix(in srgb, var(--status-info) 15%, transparent); color: var(--text-body); }
404
+ .tone-fill-none { background-color: var(--surface-secondary); color: var(--text-body); }
405
+
406
+ /* Badges are chips: solid status fills under their on-color, neutral otherwise. */
407
+ .badge, .chip {
408
+ display: inline-flex;
409
+ align-items: center;
410
+ padding: var(--space-1) var(--space-4);
411
+ border-radius: var(--radius-small);
412
+ background-color: var(--surface-secondary);
413
+ color: var(--text-secondary);
414
+ font-size: var(--text-label);
415
+ font-weight: 600;
416
+ line-height: var(--line-height-ui);
417
+ letter-spacing: var(--letter-spacing-caps);
418
+ white-space: nowrap;
419
+ }
420
+ .badge.tone-good { background-color: var(--status-success); color: var(--text-on-fill); }
421
+ .badge.tone-bad { background-color: var(--status-danger); color: var(--text-on-fill); }
422
+ .badge.tone-info, .chip-info { background-color: var(--status-info); color: var(--text-on-fill); }
423
+ /* Warning is light in both schemes and takes near-black text (§3.5). */
424
+ .badge.tone-warn { background-color: var(--fill-warning); color: var(--text-on-light); }
425
+ .badge.tone-none { background-color: var(--surface-secondary); color: var(--text-secondary); }
426
+ .chips { display: flex; gap: var(--space-3); flex-wrap: wrap; }
427
+ .chips.inline-chips { gap: var(--space-2); }
428
+ /* A chip that picks what it names, such as a tag on the Hosts page. */
429
+ button.chip { height: auto; border: var(--border-width) solid transparent; cursor: pointer; }
430
+ button.chip:hover { border-color: var(--border-default); color: var(--text-heading); }
431
+ .verdicts { display: flex; gap: var(--space-5); flex-wrap: wrap; font-size: var(--text-caption); margin-top: var(--space-4); align-items: center; }
432
+
433
+ /* ── Tiles, cards and lists ───────────────────────────────────────────────── */
434
+
435
+ .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-5); }
436
+ .tile {
437
+ display: grid;
438
+ gap: var(--space-2);
439
+ padding: var(--space-5) var(--space-6);
440
+ border: var(--border-width) solid var(--border-default);
441
+ border-left: 3px solid var(--border-default);
442
+ border-radius: var(--radius-default);
443
+ background-color: var(--surface-default);
444
+ box-shadow: var(--shadow-card);
445
+ color: var(--text-body);
446
+ text-decoration: none;
447
+ transition: border-color var(--duration-fast);
448
+ }
449
+ a.tile:hover { border-top-color: var(--border-hover); border-right-color: var(--border-hover); border-bottom-color: var(--border-hover); }
450
+ .tile-value { font-size: var(--text-h2); font-weight: 600; line-height: 1.25; color: var(--text-heading); }
451
+ .tile-label { color: var(--text-secondary); font-size: var(--text-label); font-weight: 600; letter-spacing: var(--letter-spacing-caps); text-transform: uppercase; line-height: var(--line-height-ui); }
452
+ .tile-bad { border-left-color: var(--status-danger); }
453
+ .tile-bad .tile-value { color: var(--status-danger); }
454
+ .tile-info { border-left-color: var(--status-info); }
455
+ .tile-warn { border-left-color: var(--status-warning); }
456
+ .tile-good { border-left-color: var(--status-success); }
457
+
458
+ .attention { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-3); }
459
+ .attention li {
460
+ display: flex;
461
+ gap: var(--space-5);
462
+ align-items: baseline;
463
+ flex-wrap: wrap;
464
+ padding: var(--space-3) var(--space-5);
465
+ border-left: 3px solid var(--border-default);
466
+ border-radius: 0 var(--radius-small) var(--radius-small) 0;
467
+ background-color: var(--surface-secondary);
468
+ font-size: var(--text-control);
469
+ }
470
+ .attention.compact li { padding: var(--space-2) var(--space-4); }
471
+ .attention li.tone-edge-bad { background-color: color-mix(in srgb, var(--status-danger) 15%, transparent); }
472
+ .attention li.tone-edge-warn { background-color: color-mix(in srgb, var(--status-warning) 15%, transparent); }
473
+ .tone-edge-bad { border-left-color: var(--status-danger) !important; }
474
+ .tone-edge-warn { border-left-color: var(--status-warning) !important; }
475
+ .tone-edge-good { border-left-color: var(--status-success) !important; }
476
+ .all-good { color: var(--status-success); padding: var(--space-5) 0; }
477
+
478
+ .plain-list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-4); }
479
+
480
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-5); }
481
+ .card {
482
+ display: block;
483
+ padding: var(--space-5) var(--space-6);
484
+ border: var(--border-width) solid var(--border-default);
485
+ border-left: 3px solid var(--border-default);
486
+ border-radius: var(--radius-default);
487
+ background-color: var(--surface-default);
488
+ box-shadow: var(--shadow-card);
489
+ color: var(--text-body);
490
+ font-size: var(--text-control);
491
+ text-decoration: none;
492
+ transition: border-color var(--duration-fast);
493
+ }
494
+ .card:hover { border-color: var(--border-hover); }
495
+ /* A card that opens something in place takes the card's look, not a button's. */
496
+ button.card { width: 100%; height: auto; font-weight: 400; line-height: var(--line-height-body); text-align: left; white-space: normal; }
497
+ .card-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-4); color: var(--text-heading); }
498
+ .card-host { font-size: var(--text-caption); }
499
+ .card dl { display: grid; grid-template-columns: auto 1fr; gap: var(--space-1) var(--space-5); margin: var(--space-4) 0 0; }
500
+ .card dt { color: var(--text-secondary); }
501
+ .card dd { margin: 0; }
502
+
503
+ /* ── Trees ────────────────────────────────────────────────────────────────── */
504
+
505
+ /* A long path breaks anywhere rather than widening its column. */
506
+ .path { overflow-wrap: anywhere; }
507
+ .change-summary { display: flex; gap: var(--space-4); flex-wrap: wrap; }
508
+ .tree { display: grid; gap: var(--space-1); font-size: var(--text-control); }
509
+ .tree-folder > summary { cursor: pointer; padding: var(--space-1) 0; }
510
+ .tree-children { display: grid; gap: var(--space-1); margin-left: var(--space-2); padding-left: var(--space-6); border-left: var(--border-width) solid var(--border-default); }
511
+ .tree-file { display: flex; gap: var(--space-4); align-items: baseline; padding: var(--space-1) 0 var(--space-1) var(--space-6); flex-wrap: wrap; }
512
+ .tree-name { font-family: var(--font-family-monospace); font-size: var(--text-caption); overflow-wrap: anywhere; }
513
+
514
+ /* ── Forms and settings ───────────────────────────────────────────────────── */
515
+
516
+ .form-group > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: var(--space-4); }
517
+ .form-group > summary::-webkit-details-marker { display: none; }
518
+ .form-group > summary::before { content: "▸"; color: var(--text-secondary); }
519
+ .form-group[open] > summary::before { content: "▾"; }
520
+ .form-group[open] > summary { margin-bottom: var(--space-5); }
521
+ .form-rows { display: grid; }
522
+ .form-row {
523
+ display: grid;
524
+ grid-template-columns: minmax(140px, 220px) 1fr;
525
+ gap: var(--space-3) var(--space-6);
526
+ align-items: start;
527
+ padding: var(--space-4) 0;
528
+ border-bottom: var(--border-width) solid var(--border-default);
529
+ font-size: var(--text-control);
530
+ }
531
+ .form-row:last-child { border-bottom: none; }
532
+ .form-row label { padding-top: var(--space-3); }
533
+ .form-row input:not([type="checkbox"]):not([type="radio"]), .form-row select, .form-row textarea { width: 100%; max-width: 560px; }
534
+ .form-row p { margin: var(--space-2) 0 0; }
535
+ .field-error { font-weight: 600; }
536
+ .form-actions {
537
+ position: sticky;
538
+ bottom: 0;
539
+ z-index: var(--layer-sticky);
540
+ display: flex;
541
+ gap: var(--space-4);
542
+ flex-wrap: wrap;
543
+ padding: var(--space-5) 0;
544
+ border-top: var(--border-width) solid var(--border-default);
545
+ background-color: var(--surface-default);
546
+ }
547
+ .form-actions .danger { margin-left: auto; }
548
+ .setting-value { display: flex; gap: var(--space-4); align-items: center; }
549
+ .setting-value input { max-width: 240px; }
550
+
551
+ /* A settings page groups rows under small headings, each row a label and hint beside its control. */
552
+ .settings-group { display: grid; gap: var(--space-4); }
553
+ .settings-heading {
554
+ margin: 0;
555
+ color: var(--text-secondary);
556
+ font-size: var(--text-label);
557
+ font-weight: 600;
558
+ letter-spacing: var(--letter-spacing-caps);
559
+ text-transform: uppercase;
560
+ }
561
+ .setting-row {
562
+ display: flex;
563
+ align-items: center;
564
+ justify-content: space-between;
565
+ gap: var(--space-6);
566
+ padding: var(--space-5) var(--space-6);
567
+ border: var(--border-width) solid var(--border-default);
568
+ border-radius: var(--radius-default);
569
+ background-color: var(--surface-default);
570
+ box-shadow: var(--shadow-card);
571
+ }
572
+ label.setting-row { cursor: pointer; }
573
+ .setting-text { display: flex; flex: 1; flex-direction: column; gap: var(--space-1); min-width: 0; }
574
+ .setting-label { color: var(--text-heading); font-size: var(--text-control); font-weight: 500; }
575
+ .setting-hint { color: var(--text-tertiary); font-size: var(--text-caption); }
576
+ .setting-row > button, .setting-row > select, .setting-row > .segmented { flex: none; }
577
+
578
+ /* ── Check lists, output and events ───────────────────────────────────────── */
579
+
580
+ .check-list { display: grid; gap: var(--space-2); margin-top: var(--space-3); max-height: 220px; overflow: auto; }
581
+ .check-group { display: grid; gap: var(--space-2); margin: 0; padding: 0; border: 0; min-width: 0; }
582
+ .check-group legend { padding: 0; margin-bottom: var(--space-1); color: var(--text-secondary); font-size: var(--text-caption); font-weight: 600; }
583
+
584
+ .command summary { cursor: pointer; color: var(--text-secondary); }
585
+
586
+ .output {
587
+ margin: 0;
588
+ max-height: 60vh;
589
+ overflow: auto;
590
+ padding: var(--space-5) var(--space-6);
591
+ border: var(--border-width) solid var(--border-default);
592
+ border-radius: var(--radius-default);
593
+ background-color: var(--surface-secondary);
594
+ white-space: pre-wrap;
595
+ word-break: break-word;
596
+ }
597
+ .run-output { min-height: 120px; }
598
+
599
+ .events { list-style: none; padding: 0; margin: 0; }
600
+ .events li { display: flex; gap: var(--space-5); align-items: baseline; flex-wrap: wrap; padding: var(--space-3) 0; border-bottom: var(--border-width) solid var(--border-default); font-size: var(--text-control); }
601
+ .event-time { min-width: 4.5em; }
602
+ .event-message { flex: 1; min-width: 200px; overflow-wrap: anywhere; }
603
+
604
+ /* ── Tables ───────────────────────────────────────────────────────────────── */
605
+
606
+ .table-wrap { overflow-x: auto; border: var(--border-width) solid var(--border-default); border-radius: var(--radius-default); }
607
+ table { width: 100%; border-collapse: collapse; font-size: var(--text-control); }
608
+ th, td { padding: 9px var(--space-5); text-align: left; vertical-align: middle; border-bottom: var(--border-width) solid var(--border-default); }
609
+ th {
610
+ background-color: var(--surface-secondary);
611
+ color: var(--text-secondary);
612
+ font-size: var(--text-label);
613
+ font-weight: 600;
614
+ line-height: var(--line-height-ui);
615
+ letter-spacing: var(--letter-spacing-caps);
616
+ text-transform: uppercase;
617
+ white-space: nowrap;
618
+ }
619
+ tbody tr:last-child td { border-bottom: none; }
620
+ tbody tr:hover td { background-color: var(--surface-secondary); }
621
+ th .sort {
622
+ height: auto;
623
+ padding: 0;
624
+ border: none;
625
+ background: none;
626
+ color: inherit;
627
+ font: inherit;
628
+ letter-spacing: inherit;
629
+ text-transform: inherit;
630
+ }
631
+ th[aria-sort="ascending"], th[aria-sort="descending"] { color: var(--text-heading); }
632
+ .number { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
633
+ .actions-cell { text-align: right; }
634
+ .row-actions { display: inline-flex; gap: var(--space-1); justify-content: flex-end; flex-wrap: wrap; }
635
+ .pager { display: flex; gap: var(--space-5); align-items: center; justify-content: center; margin-top: var(--space-5); font-size: var(--text-control); }
636
+
637
+
638
+
639
+ /* ── States ───────────────────────────────────────────────────────────────── */
640
+
641
+ .skeleton { display: grid; gap: var(--space-4); padding: var(--space-2) 0; }
642
+ .skeleton:empty { height: 48px; border-radius: var(--radius-default); background-color: var(--border-default); }
643
+ .skeleton-line { height: 14px; border-radius: var(--radius-small); background-color: var(--border-default); animation: pulse 1.4s ease-in-out infinite; }
644
+ @keyframes pulse { 50% { opacity: 0.45; } }
645
+
646
+ .empty { padding: var(--space-7) var(--space-4); text-align: center; font-size: var(--text-control); }
647
+ .empty p { margin: var(--space-2) 0; }
648
+ .empty-title { font-weight: 600; color: var(--text-heading); }
649
+ .failure {
650
+ display: flex;
651
+ gap: var(--space-5);
652
+ align-items: center;
653
+ justify-content: space-between;
654
+ flex-wrap: wrap;
655
+ padding: var(--space-5) var(--space-6);
656
+ border-radius: var(--radius-default);
657
+ background-color: color-mix(in srgb, var(--status-danger) 15%, transparent);
658
+ color: var(--text-body);
659
+ }
660
+ .failure p { margin: 0; }
661
+
662
+ /* ── Dialogs and toasts (§6.6) ────────────────────────────────────────────── */
663
+
664
+ .dialog {
665
+ width: min(520px, calc(100vw - 2 * var(--space-6)));
666
+ max-height: calc(100vh - 2 * var(--space-7));
667
+ padding: 0;
668
+ border: var(--border-width) solid var(--border-default);
669
+ border-radius: var(--radius-default);
670
+ background-color: var(--surface-default);
671
+ color: var(--text-body);
672
+ box-shadow: var(--shadow-modal);
673
+ font-size: var(--text-control);
674
+ }
675
+ .dialog.wide { width: min(900px, calc(100vw - 2 * var(--space-6))); }
676
+ .dialog::backdrop { background-color: var(--surface-overlay); }
677
+ .dialog h2 { padding: var(--space-5) var(--space-6); border-bottom: var(--border-width) solid var(--border-default); }
678
+ .dialog-body { padding: var(--space-6); }
679
+ .dialog-body p { margin: 0 0 var(--space-4); }
680
+ .dialog-buttons {
681
+ display: flex;
682
+ gap: var(--space-4);
683
+ justify-content: flex-end;
684
+ padding: var(--space-5) var(--space-6);
685
+ border-top: var(--border-width) solid var(--border-default);
686
+ background-color: var(--surface-secondary);
687
+ }
688
+ .type-confirm { display: grid; gap: var(--space-3); margin-top: var(--space-5); }
689
+
690
+ /* Transient overlays belong to the frame and float above the page. */
691
+ .toasts { position: fixed; right: var(--space-6); bottom: var(--space-6); z-index: var(--layer-toast); display: grid; gap: var(--space-4); max-width: min(420px, calc(100vw - 2 * var(--space-6))); }
692
+ .toast {
693
+ display: flex;
694
+ gap: var(--space-5);
695
+ align-items: center;
696
+ padding: var(--space-5);
697
+ border-left: 3px solid var(--status-info);
698
+ border-radius: var(--radius-default);
699
+ background-color: var(--surface-navigation);
700
+ color: var(--text-heading);
701
+ box-shadow: var(--shadow-floating);
702
+ font-size: var(--text-control);
703
+ animation: toast-in var(--duration-slow) ease-out;
704
+ }
705
+ @keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }
706
+ .toast span { flex: 1; overflow-wrap: anywhere; }
707
+ .toast.tone-good { border-left-color: var(--status-success); }
708
+ .toast.tone-warn { border-left-color: var(--status-warning); }
709
+ .toast.tone-bad { border-left-color: var(--status-danger); }
710
+ .toast button.icon { width: var(--row-height); padding: 0; color: inherit; }
711
+ .toast button.toast-action { flex: none; color: var(--accent); font-weight: 600; }
712
+
713
+ /* ── Menus and popovers ───────────────────────────────────────────────────── */
714
+
715
+ .menu, .popover {
716
+ position: fixed;
717
+ z-index: var(--layer-menu);
718
+ max-height: calc(100vh - 2 * var(--space-4));
719
+ overflow-y: auto;
720
+ padding: var(--space-1);
721
+ border: var(--border-width) solid var(--border-default);
722
+ border-radius: var(--radius-default);
723
+ background-color: var(--surface-default);
724
+ box-shadow: var(--shadow-floating);
725
+ font-size: var(--text-control);
726
+ }
727
+ .menu { min-width: 180px; margin: 0; list-style: none; outline: none; }
728
+ /* A native popover is centred by default, so a menu or popover opened at a point drops that and takes its own place. */
729
+ .menu[popover], .popover[popover] { inset: auto; margin: 0; color: var(--text-body); }
730
+ /* An anchored popover hangs below its anchor, flipping above or across where there is no room. */
731
+ .popover.anchored { top: anchor(bottom); left: anchor(left); margin-top: var(--space-2); position-try-fallbacks: flip-block, flip-inline; }
732
+ .popover { padding: var(--space-4); }
733
+ .menu-item {
734
+ display: flex;
735
+ align-items: center;
736
+ justify-content: space-between;
737
+ gap: var(--space-5);
738
+ width: 100%;
739
+ height: var(--row-height);
740
+ padding: 0 var(--space-4);
741
+ border: none;
742
+ color: var(--text-body);
743
+ font-weight: 400;
744
+ text-align: left;
745
+ }
746
+ .menu-item:hover:not(:disabled), .menu-item:focus-visible { background-color: var(--surface-navigation-hover); color: var(--text-heading); }
747
+ .menu-item.danger { color: var(--status-danger); }
748
+ .menu-item[aria-checked="true"] { color: var(--accent); font-weight: 600; }
749
+ .menu-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
750
+ .menu-hint { flex: none; color: var(--text-tertiary); font-size: var(--text-label); }
751
+ .menu-separator { height: var(--border-width); margin: var(--space-1) var(--space-2); background-color: var(--border-default); }
752
+
753
+ /* ── Resize handles and splits ────────────────────────────────────────────── */
754
+
755
+ /* A handle is a thin grab bar that lights with the accent while hovered, focused or dragged. */
756
+ .resize-handle { position: relative; flex: none; width: 7px; cursor: col-resize; touch-action: none; }
757
+ .resize-handle.vertical { width: auto; height: 7px; cursor: row-resize; }
758
+ .resize-handle::after { content: ""; position: absolute; inset: 0 3px; transition: background-color var(--duration-fast); }
759
+ .resize-handle.vertical::after { inset: 3px 0; }
760
+ .resize-handle:hover::after, .resize-handle:focus-visible::after, .resize-handle.dragging::after { background-color: var(--accent); }
761
+ .resize-handle:focus-visible { outline: none; }
762
+ body.resizing { cursor: col-resize; user-select: none; }
763
+ body.resizing.vertical { cursor: row-resize; }
764
+
765
+ .split { display: grid; min-width: 0; min-height: 0; }
766
+ /* A pane's content must not grow its own track, which the split would then measure. */
767
+ .split-pane { display: grid; grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr); min-width: 0; min-height: 0; overflow: hidden; }
768
+ .split > .resize-handle { width: 5px; background-color: var(--border-default); }
769
+ .split > .resize-handle.vertical { width: auto; height: 5px; }
770
+ .split > .resize-handle::after { inset: 0 -3px; }
771
+ .split > .resize-handle.vertical::after { inset: -3px 0; }
772
+
773
+ /* ── Scrollbars (§6.8) ────────────────────────────────────────────────────── */
774
+
775
+ * { scrollbar-color: var(--border-hover) transparent; scrollbar-width: thin; }
776
+ *::-webkit-scrollbar { width: 10px; height: 10px; }
777
+ *::-webkit-scrollbar-track { background: transparent; }
778
+ *::-webkit-scrollbar-thumb { background-color: var(--border-hover); border: 2px solid transparent; background-clip: padding-box; border-radius: 3px; }
779
+ *::-webkit-scrollbar-thumb:hover { background-color: var(--text-tertiary); }
780
+
781
+
782
+ /* ── Phones and narrow windows ────────────────────────────────────────────── */
783
+
784
+ @media (max-width: 900px) {
785
+ .columns { grid-template-columns: 1fr; }
786
+ }
787
+
788
+ @media (max-width: 768px) {
789
+ .menu-button { display: inline-flex; }
790
+ .layout { grid-template-columns: minmax(0, 1fr); }
791
+ .sidebar {
792
+ position: fixed;
793
+ top: 0;
794
+ left: 0;
795
+ bottom: 0;
796
+ z-index: var(--layer-menu);
797
+ width: var(--sidebar-width);
798
+ height: 100vh;
799
+ transform: translateX(-100%);
800
+ transition: transform var(--duration-slow) ease-out;
801
+ visibility: hidden;
802
+ }
803
+ body.menu-open .sidebar { transform: none; visibility: visible; box-shadow: var(--shadow-floating); }
804
+ .account-link { display: none; }
805
+ main { margin-left: var(--space-2); padding: var(--space-6) var(--space-6) var(--space-8); }
806
+ .topbar { height: auto; min-height: var(--frame-height); gap: var(--space-2); padding: var(--space-1) var(--space-2) var(--space-2); flex-wrap: wrap; }
807
+ .brand { padding: 0 var(--space-4); }
808
+ #theme { margin-left: auto; }
809
+ }
810
+
811
+ @media (max-width: 640px) {
812
+ input.filter { min-width: 0; width: 100%; }
813
+ .toolbar > * { flex: 1 1 140px; }
814
+ table.responsive thead { display: none; }
815
+ table.responsive, table.responsive tbody, table.responsive tr, table.responsive td { display: block; width: 100%; }
816
+ table.responsive tr { padding: var(--space-4) var(--space-5); border-bottom: var(--border-width) solid var(--border-default); }
817
+ table.responsive tr:last-child { border-bottom: none; }
818
+ table.responsive td { display: flex; justify-content: space-between; gap: var(--space-5); padding: var(--space-1) 0; border: none; text-align: right; }
819
+ table.responsive tbody tr:hover td { background-color: transparent; }
820
+ table.responsive td::before { content: attr(data-label); color: var(--text-secondary); text-align: left; }
821
+ table.responsive td[data-label=""]::before { content: none; }
822
+ table.responsive td.actions-cell { justify-content: flex-end; }
823
+ .form-row { grid-template-columns: 1fr; }
824
+ .form-row label { padding-top: 0; }
825
+ }
826
+
package/package.json CHANGED
@@ -1,6 +1,20 @@
1
1
  {
2
2
  "name": "@x-tech-laboratories/halon",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Halon for the web: the token sheet and the component styles the X-apps' pages are drawn with",
5
+ "license": "GPL-3.0-or-later",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/x-tech-laboratories/X-Themes.git",
9
+ "directory": "halon/web"
10
+ },
11
+ "files": [
12
+ "halon-tokens.css",
13
+ "halon.css"
14
+ ],
15
+ "exports": {
16
+ "./halon-tokens.css": "./halon-tokens.css",
17
+ "./halon.css": "./halon.css"
18
+ },
19
+ "style": "./halon.css"
20
+ }