anentrypoint-design 0.0.322 → 0.0.324

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +22 -2
  2. package/app-shell.css +24 -4782
  3. package/colors_and_type.css +43 -0
  4. package/dist/247420.css +74 -3
  5. package/dist/247420.js +16 -16
  6. package/package.json +14 -8
  7. package/src/bootstrap.js +10 -1
  8. package/src/components/chat-message-parts.js +355 -0
  9. package/src/components/chat.js +23 -320
  10. package/src/components/data-density.js +15 -10
  11. package/src/components/files-modals.js +14 -2
  12. package/src/components/files.js +5 -1
  13. package/src/components/freddie/helpers.js +46 -8
  14. package/src/css/app-shell/base.css +157 -0
  15. package/src/css/app-shell/catalog-theme.css +144 -0
  16. package/src/css/app-shell/chat-basic.css +12 -0
  17. package/src/css/app-shell/chat-polish.css +459 -0
  18. package/src/css/app-shell/data-density.css +150 -0
  19. package/src/css/app-shell/files.css +709 -0
  20. package/src/css/app-shell/hero-content.css +407 -0
  21. package/src/css/app-shell/kits-appended.css +361 -0
  22. package/src/css/app-shell/loading-alerts.css +120 -0
  23. package/src/css/app-shell/panel-row.css +177 -0
  24. package/src/css/app-shell/primitives.css +197 -0
  25. package/src/css/app-shell/responsive.css +266 -0
  26. package/src/css/app-shell/responsive2-workspace.css +465 -0
  27. package/src/css/app-shell/row-print.css +75 -0
  28. package/src/css/app-shell/sidebar-misc.css +79 -0
  29. package/src/css/app-shell/states-interactions.css +696 -0
  30. package/src/css/app-shell/topbar.css +319 -0
  31. package/src/deck-stage.js +6 -4
  32. package/src/highlight.js +20 -4
  33. package/src/index.js +15 -5
  34. package/src/kits/os/freddie/pages-chat.js +1 -1
  35. package/src/kits/os/freddie/pages-core.js +1 -1
  36. package/src/kits/os/freddie/pages-os.js +1 -1
  37. package/src/kits/os/freddie/pages-tools.js +1 -1
  38. package/src/kits/os/wm.js +38 -0
  39. package/src/kits/slides/deck-stage-overlay.js +68 -0
  40. package/src/kits/slides/deck-stage-state.js +81 -0
  41. package/src/kits/slides/deck-stage-style.js +117 -0
  42. package/src/kits/slides/deck-stage.js +167 -0
  43. package/src/kits/slides/index.js +5 -0
  44. package/src/markdown.js +89 -11
  45. package/src/kits/os/freddie/helpers.js +0 -33
package/app-shell.css CHANGED
@@ -1,4782 +1,24 @@
1
- /* ============================================================
2
- 247420 design system — component sheet
3
- "Acid Editorial": system-font display + body + mono, no custom web fonts.
4
- Confident hierarchy, asymmetric grid tension, print texture over glow,
5
- one electric lead accent. Indicator rails over hairlines.
6
- ============================================================ */
7
-
8
- /* ============================================================
9
- Base
10
- ============================================================ */
11
- * { box-sizing: border-box; }
12
- html, body { margin: 0; padding: 0; }
13
- button, input, select, textarea { font: inherit; }
14
-
15
- /* ============================================================
16
- Accessibility — Skip Link
17
- ============================================================ */
18
- .skip-link {
19
- position: absolute;
20
- top: -40px;
21
- left: 0;
22
- background: var(--accent);
23
- color: var(--accent-fg);
24
- padding: 8px 16px;
25
- text-decoration: none;
26
- z-index: 100;
27
- border-radius: var(--r-pill);
28
- font-weight: 600;
29
- font-size: var(--fs-sm);
30
- }
31
- .skip-link:focus {
32
- top: 10px;
33
- outline: var(--focus-w) solid var(--focus-color);
34
- outline-offset: var(--focus-offset);
35
- }
36
- body {
37
- background: var(--bg);
38
- color: var(--fg);
39
- font-family: var(--ff-body);
40
- font-size: var(--fs-body);
41
- line-height: var(--lh-base);
42
- -webkit-font-smoothing: antialiased;
43
- text-rendering: optimizeLegibility;
44
- }
45
- /* Light (paper, the default theme) needs a stronger accent share than dark to
46
- clear a visible selection tint against --bg-2 -- the dark-theme value (24%)
47
- read too weak on paper. Dark/ink keeps its existing 24% mix, restored below
48
- in the ink/dark + auto-dark blocks (mirrors the --danger/--flame pattern of
49
- scoping a value per theme rather than sharing one across both). */
50
- ::selection { background: color-mix(in oklab, var(--accent) 46%, var(--bg-2)); color: var(--fg); }
51
-
52
- /* Every root has a CQ container so fluid type can resolve to a meaningful inline-size. */
53
- .app, .ds-stage {
54
- container-type: inline-size;
55
- overflow-x: clip;
56
- min-width: 0;
57
- }
58
- .app * { min-width: 0; }
59
-
60
- /* ============================================================
61
- Typography
62
- ============================================================ */
63
- h1, .t-h1 { font-family: var(--ff-display); font-size: var(--fs-h1); line-height: var(--lh-snug); letter-spacing: var(--tr-tight); font-weight: 600; margin: 0; }
64
- h2, .t-h2 { font-family: var(--ff-display); font-size: var(--fs-h2); line-height: var(--lh-snug); letter-spacing: var(--tr-tight); font-weight: 600; margin: 0; }
65
- h3, .t-h3 { font-family: var(--ff-display); font-size: var(--fs-h3); line-height: var(--lh-snug); letter-spacing: -0.01em; font-weight: 600; margin: 0; }
66
- h4, .t-h4 { font-family: var(--ff-display); font-size: var(--fs-h4); line-height: var(--lh-snug); font-weight: 600; margin: 0; }
67
-
68
- /* App/chrome typescale opt-in: compact heading ceiling for application UIs. */
69
- [data-typescale="app"] h1, [data-typescale="app"] .t-h1 { font-size: var(--fs-h1-app); line-height: 1.15; }
70
- [data-typescale="app"] h2, [data-typescale="app"] .t-h2 { font-size: var(--fs-h2-app); }
71
- [data-typescale="app"] h3, [data-typescale="app"] .t-h3 { font-size: var(--fs-h3-app); }
72
- [data-typescale="app"] h4, [data-typescale="app"] .t-h4 { font-size: var(--fs-h4-app); }
73
-
74
- .t-hero { font-family: var(--ff-display); font-size: var(--fs-hero); line-height: var(--lh-tight); letter-spacing: var(--tr-tighter); font-weight: 700; margin: 0; }
75
- .t-mega { font-family: var(--ff-display); font-size: var(--fs-mega); line-height: 0.92; letter-spacing: var(--tr-tighter); font-weight: 700; margin: 0; }
76
-
77
- .t-display { font-family: var(--ff-display); font-weight: 600; letter-spacing: var(--tr-tight); }
78
- .t-mono { font-family: var(--ff-mono); }
79
- .t-body { font-family: var(--ff-body); }
80
-
81
- .t-label { font-family: var(--ff-mono); font-size: var(--fs-tiny); text-transform: uppercase; letter-spacing: var(--tr-label); font-weight: 500; }
82
- .t-micro { font-family: var(--ff-mono); font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: var(--tr-label); color: var(--fg-3); }
83
- .t-meta { font-family: var(--ff-body); font-size: var(--fs-sm); color: var(--fg-3); }
84
-
85
- .lede, .t-lede {
86
- font-family: var(--ff-body); font-size: var(--fs-xl);
87
- line-height: var(--lh-tall); max-width: 38ch; font-weight: 400; color: var(--fg-2);
88
- margin: 0;
89
- }
90
-
91
- .eyebrow {
92
- font-family: var(--ff-mono);
93
- font-size: var(--fs-tiny);
94
- font-weight: 600;
95
- letter-spacing: var(--tr-label);
96
- text-transform: uppercase;
97
- color: var(--accent-ink);
98
- display: inline-flex; align-items: center; gap: 12px;
99
- }
100
- /* Editorial kicker mark — a short solid ink rule, the print-byline tell. */
101
- .eyebrow::before {
102
- content: '';
103
- width: 28px; height: 3px; background: currentColor;
104
- }
105
-
106
- .ds-prose {
107
- font-family: var(--ff-body);
108
- font-size: var(--fs-lg);
109
- line-height: var(--lh-long);
110
- max-width: var(--measure);
111
- text-wrap: pretty;
112
- }
113
- .ds-prose p { margin: 0 0 var(--space-4); }
114
- .ds-prose h1 { font-size: clamp(34px, 5.4cqi, 64px); margin-bottom: var(--space-3); }
115
- .ds-prose h2 { margin: var(--space-6) 0 var(--space-3); }
116
- .ds-prose h3 { margin: var(--space-5) 0 var(--space-2); font-size: var(--fs-lg); font-weight: 600; }
117
-
118
- .dim { color: var(--fg-3); }
119
-
120
- /* Links */
121
- a { color: inherit; text-decoration: none; }
122
- a.ds-link-accent, .ds-prose a {
123
- color: var(--accent-ink);
124
- text-decoration: underline;
125
- text-underline-offset: 4px;
126
- text-decoration-thickness: 2px;
127
- }
128
- a.ds-link-accent:hover, .ds-prose a:hover {
129
- background: var(--accent); color: var(--accent-fg); text-decoration-color: transparent;
130
- }
131
-
132
- kbd {
133
- font-family: var(--ff-mono); font-size: 0.85em;
134
- background: var(--bg-2); padding: 2px 6px; border-radius: var(--r-1);
135
- }
136
-
137
- code {
138
- font-family: var(--ff-mono); font-size: 0.92em;
139
- background: var(--bg-2); padding: 0.15em 0.5em; border-radius: var(--r-1);
140
- }
141
-
142
- pre {
143
- font-family: var(--ff-mono);
144
- background: var(--panel-bg);
145
- color: var(--panel-text, inherit);
146
- padding: var(--space-5);
147
- overflow-x: auto;
148
- line-height: 1.55;
149
- font-size: var(--fs-sm);
150
- margin: 0;
151
- border-radius: var(--r-3);
152
- }
153
- pre .k { color: var(--code-str-alt, var(--mascot)); }
154
- pre .s { color: var(--code-string, var(--green)); }
155
- pre .c { color: var(--fg-3); }
156
- pre .n { color: var(--code-num); }
157
-
158
- /* ============================================================
159
- AppShell — page frame. The topbar floats above a soft background;
160
- the body sits inside a rounded inner shell for a friendlier feel.
161
- ============================================================ */
162
- .app {
163
- display: flex; flex-direction: column;
164
- /* Offset parent for the sidebar drawer/scrim/toggle, which are absolute so
165
- they overlay THIS shell (a thebird WM window) rather than the viewport. */
166
- position: relative;
167
- min-height: 100vh;
168
- /* Definite height so the flex column resolves height:100% for descendants
169
- (e.g. a full-height chat) at every breakpoint, not just where a desktop
170
- media override happened to set explicit heights. dvh tracks mobile chrome. */
171
- min-height: 100dvh;
172
- height: 100dvh;
173
- background: var(--bg);
174
- color: var(--fg);
175
- /* Notched-device safe area padding (no-op on devices without notches) */
176
- padding-top: env(safe-area-inset-top);
177
- padding-bottom: env(safe-area-inset-bottom);
178
- padding-left: env(safe-area-inset-left);
179
- padding-right: env(safe-area-inset-right);
180
- }
181
-
182
- /* When the app shell is embedded inside a windowed surface (e.g. the freddie
183
- dashboard mounted in a WM window via .fd-root) it is NOT a full-page app:
184
- `min-height:100vh` and the absence of an explicit width make it collapse to
185
- 0 width at desktop, where the responsive single-column @media rules don't
186
- apply. Inside .fd-root it must fill its container instead. (At <=767px the
187
- grid already linearizes, which is why the bug only showed at desktop width.) */
188
- .fd-root .app { width: 100%; height: 100%; min-height: 0; flex: 1 1 auto; }
189
- .fd-root .app-body { min-width: 0; }
190
-
191
- /* Full-bleed canvas host (e.g. a game/3D viewport that fills the page behind the
192
- shell): the shell's opaque var(--bg) would paint OVER the canvas and hide it.
193
- Mark the host with `canvas-host` to make the shell surfaces transparent so the
194
- live canvas shows through. The canvas itself is the backdrop. */
195
- .canvas-host,
196
- .canvas-host .app,
197
- body.canvas-host { background: transparent !important; }
198
-
199
- :root {
200
- --app-status-h: 26px;
201
- --app-topbar-h: var(--size-lg);
202
- --app-crumb-h: var(--size-sm);
203
- }
204
-
205
- .app-topbar {
206
- position: sticky; top: 0; z-index: var(--z-header);
207
- display: flex; flex-wrap: wrap;
208
- align-items: center; gap: var(--space-4);
209
- min-height: var(--app-topbar-h);
210
- padding: 10px var(--pad-x);
211
- background: color-mix(in oklab, var(--bg) 97%, transparent);
212
- }
213
-
214
- /* Merged chrome: when AppShell gets both a topbar and a crumb it wraps them
215
- in .app-chrome and they share ONE sticky band instead of stacking as two
216
- bars. The breadcrumb provides the left identity (it already begins with the
217
- brand), so the topbar's standalone brand is hidden to avoid showing the name
218
- twice; nav and the crumb's right slot sit together on the right. */
219
- .app-chrome {
220
- position: sticky; top: 0; z-index: var(--z-header);
221
- display: flex; flex-wrap: wrap; align-items: center;
222
- gap: var(--space-2) var(--space-4);
223
- padding: 8px var(--pad-x);
224
- background: color-mix(in oklab, var(--bg) 97%, transparent);
225
- }
226
- .app-chrome > .app-topbar,
227
- .app-chrome > .app-crumb {
228
- position: static; background: none; backdrop-filter: none;
229
- -webkit-backdrop-filter: none; padding: 0; min-height: 0;
230
- flex: 1 1 auto;
231
- }
232
- .app-chrome > .app-crumb { order: 1; flex: 1 1 auto; }
233
- .app-chrome > .app-topbar { order: 2; flex: 0 0 auto; }
234
- .app-chrome > .app-topbar > .brand { display: none; }
235
- .app-chrome > .app-topbar > nav { margin-left: 0; }
236
- .app-chrome > .app-crumb > .crumb-right { margin-left: auto; }
237
- .app-topbar > .brand { flex: 0 0 auto; }
238
- .app-topbar > .app-search { flex: 1 1 auto; }
239
- .app-topbar > nav { margin-left: auto; }
240
- .app-topbar nav { display: flex; gap: 4px; font-size: var(--fs-sm); flex-wrap: wrap; }
241
- .app-topbar nav a { flex: 0 0 auto; }
242
- .app-topbar nav a {
243
- color: var(--fg-2);
244
- /* Dense desktop chrome (was a 44px pill with 12px 14px pad — marketing-scale
245
- touch target in the top bar). Matches the .app-side a treatment: ~34px,
246
- --r-1 rounded rect, tighter padding. The 44px tap target is restored under
247
- @media(pointer:coarse) (the existing .app-topbar nav a rule further down). */
248
- padding: var(--space-2) var(--space-2-5);
249
- min-height: 34px;
250
- display: flex;
251
- align-items: center;
252
- border-radius: var(--r-1);
253
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
254
- }
255
- .app-topbar nav a:hover { background: var(--bg-2); color: var(--fg); }
256
- .app-topbar nav a:focus-visible {
257
- outline: var(--focus-w) solid var(--focus-color);
258
- outline-offset: var(--focus-offset);
259
- }
260
- /* Active tab reads as a subtle tinted pill with an accent underline rather than
261
- an oversized filled oval that sits taller than its inactive siblings and reads
262
- as misaligned. Same height as the inactive links — only the tint + underline
263
- change. */
264
- .app-topbar nav a.active {
265
- color: var(--accent-ink);
266
- background: color-mix(in oklab, var(--accent) 16%, transparent);
267
- box-shadow: inset 0 -2px 0 0 var(--accent);
268
- font-weight: 600;
269
- }
270
-
271
- .brand {
272
- font-family: var(--ff-display);
273
- font-weight: 700; font-size: var(--fs-lg);
274
- letter-spacing: var(--tr-tight);
275
- }
276
- /* The wordmark separator printed in the electric lead — the brand tell. */
277
- .brand .slash { color: var(--accent-ink); font-weight: 700; margin: 0 3px; }
278
-
279
- .app-search {
280
- display: inline-flex; align-items: center; gap: var(--space-2-5);
281
- /* Snap to the single-line control ladder (--field-height == --ctl-md, 34px,
282
- density-scaled) so the topbar search aligns with sibling topbar controls and
283
- shrinks under [data-density]. Was raw 9px 16px padding off the ladder — the
284
- one front-door input the control-height port missed. */
285
- height: var(--field-height);
286
- padding: 0 var(--space-3); background: var(--bg-2);
287
- border-radius: var(--r-pill);
288
- font-size: var(--fs-sm); color: var(--fg-3);
289
- max-width: 360px;
290
- }
291
- .app-search .icon { opacity: 0.6; }
292
- .app-search input {
293
- border: 0; background: transparent;
294
- font-family: inherit; font-size: inherit; color: var(--fg);
295
- width: 100%;
296
- }
297
- .app-search:focus-within {
298
- outline: var(--focus-w) solid var(--focus-color);
299
- outline-offset: var(--focus-offset);
300
- }
301
-
302
- .app-crumb {
303
- min-height: var(--app-crumb-h);
304
- padding: 6px var(--pad-x);
305
- display: flex; align-items: center; gap: 8px;
306
- font-size: var(--fs-sm); color: var(--fg-3);
307
- }
308
- .app-crumb .sep { opacity: 0.5; }
309
- .app-crumb .leaf { color: var(--fg); font-weight: 600; }
310
- .app-crumb .crumb-right { margin-left: auto; color: var(--fg-3); }
311
-
312
- .app-side-shell { background: var(--bg); }
313
- .app-body { display: grid; grid-template-columns: var(--sidebar-width) minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); align-items: stretch; align-content: stretch; flex: 1; min-height: 0; }
314
- .app-body.no-side { grid-template-columns: minmax(0, 1fr); }
315
- /* When there is no sidebar, the empty side-shell must not occupy a grid row,
316
- otherwise main wraps to a second (collapsed) row. Remove it from layout. */
317
- .app-body.no-side .app-side-shell { display: none; }
318
-
319
- .app-side {
320
- display: flex; flex-direction: column; gap: var(--space-4);
321
- padding: var(--space-5) var(--space-3);
322
- font-size: var(--fs-sm);
323
- }
324
- .app-side .group {
325
- font-size: var(--fs-xs); font-weight: 600;
326
- color: var(--fg-3); padding: 0 var(--space-3);
327
- margin-bottom: 6px;
328
- }
329
- .app-side a {
330
- display: grid; grid-template-columns: 18px 1fr auto;
331
- gap: var(--space-2-5); align-items: center;
332
- /* Dense rounded-rect nav row (was a 44px pill with 12px 14px padding — tall
333
- and consumer-shaped). A --r-1 rounded rect at ~34px reads as compact app
334
- chrome; the full 44px touch target is restored under pointer:coarse below. */
335
- padding: var(--space-2) var(--space-2-5);
336
- min-height: 34px;
337
- border-radius: var(--r-1);
338
- color: var(--fg-2);
339
- margin-bottom: 2px;
340
- }
341
- @media (pointer: coarse) {
342
- .app-side a { min-height: 44px; padding: var(--space-2-75) var(--space-3); }
343
- }
344
- .app-side a:hover { background: var(--bg-2); color: var(--fg); }
345
- .app-side a:focus-visible {
346
- outline: var(--focus-w) solid var(--focus-color);
347
- outline-offset: var(--focus-offset);
348
- }
349
- .app-side a.active {
350
- color: var(--accent-fg);
351
- background: var(--accent);
352
- font-weight: 600;
353
- }
354
- .app-side .count {
355
- background: color-mix(in oklab, var(--fg) 10%, transparent);
356
- color: var(--fg-2);
357
- padding: 3px 10px; font-size: var(--fs-micro); font-weight: 700;
358
- border-radius: var(--r-pill);
359
- }
360
- /* The lime active pill is the sidebar's one accent moment; its own count
361
- chip rides the ink-on-accent pair instead of a second competing hue. */
362
- .app-side a.active .count {
363
- background: color-mix(in oklab, var(--accent-fg) 82%, transparent);
364
- color: var(--accent);
365
- }
366
-
367
- .app-main {
368
- padding: var(--space-5) var(--pad-x) 0;
369
- min-width: 0;
370
- /* Full-height flex column so a single flex:1 child (e.g. .chat) fills the
371
- region between crumb and status instead of floating at content height. */
372
- display: flex; flex-direction: column;
373
- min-height: 0;
374
- align-self: stretch;
375
- height: 100%;
376
- }
377
- /* Document children (hero, sections, panels) keep their natural height in the
378
- scrolling flex column. Without flex-shrink:0 the flex algorithm shrinks them
379
- toward min-height:0 once total content exceeds the fixed-height .app-main,
380
- collapsing every section to 0 and piling content on top of itself — the
381
- failure mode any long-scroll page (a marketing/landing route) hits. Grow
382
- children (.chat/.grow/etc.) re-assert flex:1 1 auto below via higher
383
- specificity, so a single full-height pane still fills the region. */
384
- .app-main > * { min-height: 0; flex: none; }
385
- /* The main region scrolls its own overflow at every breakpoint (previously
386
- only ≥901px), so a fixed-height .app never clips route content and inner
387
- panels don't fight the page scroll. */
388
- .app-main { overflow-y: auto; }
389
- /* Reserve the scrollbar track so routes with/without overflow don't shift
390
- the layout horizontally when the bar appears. */
391
- .app-main { scrollbar-gutter: stable; }
392
- /* Jump-target anchors clear the inner-scroll top edge (no chrome overlap,
393
- but a little breathing room when deep-linked within .app-main). */
394
- .app-main [id] { scroll-margin-top: var(--space-4); }
395
- /* Full-height route children fill the region instead of scrolling the page. */
396
- .app-main > .chat,
397
- .app-main > .chat-area,
398
- .app-main > .ds-file-stage,
399
- .app-main > .ds-files-stack,
400
- .app-main > .grow { flex: 1 1 auto; min-height: 0; }
401
- .app-main.narrow { max-width: var(--measure-narrow); margin: 0 auto; }
402
- /* Article-body heading rhythm and lede spacing, consumed by flatspace-rendered
403
- long-form pages (docs, papers) that drop headings/panels directly into .app-main. */
404
- .app-main > h1,
405
- .app-main > h2,
406
- .app-main > h3 { margin-top: 36px; margin-bottom: 12px; }
407
- .app-main > h1:first-child { margin-top: 8px; }
408
- .app-main > .panel,
409
- .app-main > .work-detail-chips,
410
- .app-main > .cli { margin-top: 18px; margin-bottom: 18px; }
411
- .app-main .ds-lede { margin-top: 4px; margin-bottom: 18px; max-width: 64ch; line-height: 1.6; }
412
- .work-detail-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; row-gap: 10px; }
413
- @media (max-width: 720px) {
414
- .app-main > h1,
415
- .app-main > h2,
416
- .app-main > h3 { margin-top: 28px; }
417
- }
418
-
419
- @media (min-width: 1400px) {
420
- /* Fluid ceiling, not a hard stop: scales from 1400px up to 1920px across the
421
- 1400-2200px viewport band (matches .ws-shell's 1920px ultrawide cap below)
422
- instead of freezing at 1400px and leaving >1000px dead margin on a 2560px+
423
- display. Row/prose measure caps (--measure-wide etc.) still own line-length
424
- readability, so widening the shell itself does not create unreadable rows. */
425
- .app { max-width: clamp(1400px, 92vw, 1920px); margin-left: auto; margin-right: auto; }
426
- .app-main .chat,
427
- .app-main > .chat-area,
428
- .app-main > .main-content { max-width: none; margin: 0; width: 100%; }
429
- /* Status bar inherits the .app max-width cap above and stretches edge-to-edge
430
- within it; no separate width constraint (which previously narrowed it). */
431
- .app-status { width: 100%; }
432
- /* Cap row text measure inside the wide capped main (ultrawide readability). */
433
- .row .title, .row .sub { max-width: var(--measure-wide); }
434
- }
435
- /* Beyond 1920px the shell itself stops growing (matches .ws-shell's own cap) —
436
- extra space becomes page margin rather than stretching sidebar/content grid
437
- tracks to distances a pointer/eye shouldn't have to travel. */
438
- @media (min-width: 2200px) {
439
- .app { max-width: 1920px; }
440
- }
441
-
442
- .app-status {
443
- display: flex; align-items: center; gap: var(--space-3);
444
- width: 100%;
445
- height: var(--app-status-h); min-height: var(--app-status-h);
446
- /* A thin desktop status strip, not a webpage footer band. */
447
- padding: 0 var(--space-4);
448
- /* Status chrome is prose, not code - mono is reserved for code/paths. */
449
- font-family: var(--ff-body); font-size: var(--fs-xs); line-height: 1.2;
450
- color: var(--fg-3);
451
- border-top: 1px solid var(--rule);
452
- }
453
- .app-status .item { color: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
454
- .app-status .item:first-of-type { color: var(--accent-ink); }
455
- .app-status .spread { flex: 1; }
456
- /* The status bar NEVER wraps: at narrow widths the trailing items (hints,
457
- agent target) drop in priority order instead of breaking mid-word onto a
458
- second line. */
459
- .app-status { flex-wrap: nowrap; overflow: hidden; }
460
- @media (max-width: 1100px) {
461
- .app-status .item:last-of-type:not(:only-of-type) { display: none; }
462
- }
463
-
464
- /* The "dateline" — ASCII bar used at the top of every preview/brand page.
465
- 3-4 flex slots; a .spread filler pushes the trailing slot to the far
466
- edge instead of every span running together with no gap. */
467
- .dateline {
468
- display: flex; align-items: center; gap: var(--space-3);
469
- flex-wrap: nowrap; overflow: hidden;
470
- font-family: var(--ff-mono); font-size: var(--fs-xs); line-height: 1.2;
471
- letter-spacing: 0.02em;
472
- color: var(--fg-3);
473
- }
474
- .dateline span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
475
- .dateline .spread { flex: 1 1 auto; min-width: var(--space-3); }
476
-
477
- /* ============================================================
478
- Primitives
479
- ============================================================ */
480
- .btn, .btn-primary, .btn-ghost {
481
- display: inline-flex; align-items: center; justify-content: center; gap: 6px;
482
- /* Fixed height + horizontal-only padding: the control height is exact and
483
- alignable (flex centres the label vertically), the way webgeist snaps every
484
- control to a height ladder. Default is the --ctl-md rung; --btn-sm/--btn-lg
485
- retune height + h-padding + font per size. Density scales the ladder. */
486
- height: var(--ctl-md); min-height: var(--ctl-md);
487
- padding: 0 var(--space-2-75);
488
- font-family: var(--ff-body);
489
- font-weight: 600; font-size: var(--fs-sm);
490
- text-decoration: none;
491
- white-space: nowrap;
492
- cursor: pointer;
493
- /* Rounded-rect app chrome, not a marketing-page stadium CTA.
494
- Shape vocabulary: r-1/r-2 = actionable buttons, icon-buttons, and
495
- containers; r-pill = reserved for status/input/chip/badge/progress
496
- surfaces (composer input, chips, badges, progress bars, theme-toggle
497
- segments) - never for actions. */
498
- border-radius: var(--r-1);
499
- border: var(--bw-hair) solid transparent;
500
- box-shadow: none;
501
- /* Restrained hover-lift: a 1px rise + hairline shadow reads as a crisp,
502
- confident affordance rather than a soft glow-card float. */
503
- transition: transform var(--dur-base) var(--ease-spring), background var(--dur-snap) var(--ease),
504
- color var(--dur-snap) var(--ease), box-shadow var(--dur-base) var(--ease-spring),
505
- border-color var(--dur-snap) var(--ease);
506
- }
507
- .btn { background: var(--bg-2); color: var(--fg); border-color: var(--rule); }
508
- .btn:hover { background: var(--bg-3); color: var(--fg); transform: translateY(-1px); box-shadow: var(--shadow-1); }
509
- /* Primary CTA — the electric lead as a solid editorial block; hover inverts to
510
- an ink block with the lead as text, a confident two-tone flip. */
511
- .btn-primary {
512
- background: var(--accent); color: var(--accent-fg);
513
- font-weight: 700;
514
- }
515
- .btn-primary:hover { background: var(--fg); color: var(--accent-ink); transform: translateY(-1px); box-shadow: var(--shadow-2); }
516
- .btn-ghost { background: transparent; color: var(--fg); box-shadow: inset 0 0 0 2px var(--fg); }
517
- .btn-ghost:hover { background: var(--fg); color: var(--bg); transform: translateY(-1px); }
518
- .btn:active, .btn-primary:active, .btn-ghost:active { transform: translateY(1px); box-shadow: none; }
519
- .btn:focus-visible, .btn-primary:focus-visible, .btn-ghost:focus-visible, .ds-icon-btn:focus-visible {
520
- outline: var(--focus-w) solid var(--focus-color);
521
- outline-offset: var(--focus-offset);
522
- }
523
-
524
- /* Button size ladder — the size prop on Btn() (sm | md | lg). md is the base
525
- rule above; sm/lg retune height (off the --ctl-* ladder), horizontal padding,
526
- and font-size so a size swap is one class, never inline padding overrides
527
- (the old preview faked sizes with style=). Applies to every variant. */
528
- .btn-sm, .btn-sm.btn-primary, .btn-sm.btn-ghost {
529
- height: var(--ctl-sm); min-height: var(--ctl-sm);
530
- padding: 0 var(--space-2); font-size: var(--fs-tiny); gap: var(--space-1);
531
- }
532
- .btn-lg, .btn-lg.btn-primary, .btn-lg.btn-ghost {
533
- height: var(--ctl-lg); min-height: var(--ctl-lg);
534
- padding: 0 var(--space-4); font-size: var(--fs-body); gap: var(--space-2);
535
- }
536
-
537
- /* App-mode quiet interactions — the marketing hover-lift (translateY + shadow)
538
- and the .row .meta slide read as decorative float on a dense working surface.
539
- Under [data-typescale="app"], state changes are background/border-only: crisp,
540
- no motion, no elevation. Marketing surfaces (no data-typescale) keep the lift. */
541
- [data-typescale="app"] .btn:hover,
542
- [data-typescale="app"] .btn-primary:hover,
543
- [data-typescale="app"] .btn-ghost:hover { transform: none; box-shadow: none; }
544
- [data-typescale="app"] .btn:active,
545
- [data-typescale="app"] .btn-primary:active,
546
- [data-typescale="app"] .btn-ghost:active { transform: none; }
547
- [data-typescale="app"] .panel { transition: box-shadow var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease); }
548
- [data-typescale="app"] a.row:hover .meta,
549
- [data-typescale="app"] .row[role="button"]:hover .meta { transform: none; }
550
-
551
- /* ============================================================
552
- IconButton — square icon-only button
553
- ============================================================ */
554
- .ds-icon-btn {
555
- display: inline-flex; align-items: center; justify-content: center;
556
- padding: 0; border: none; cursor: pointer;
557
- border-radius: var(--r-1, 6px);
558
- background: transparent; color: var(--fg);
559
- width: 32px; height: 32px;
560
- }
561
- .ds-icon-btn-xs { width: 22px; height: 22px; }
562
- .ds-icon-btn-sm { width: 26px; height: 26px; }
563
- .ds-icon-btn-base { width: 32px; height: 32px; }
564
- .ds-icon-btn-lg { width: 40px; height: 40px; }
565
- .ds-icon-btn-xl { width: 48px; height: 48px; }
566
- .ds-icon-btn-ghost { background: transparent; }
567
- .ds-icon-btn-ghost:hover { background: var(--fg); color: var(--bg); }
568
- .ds-icon-btn-primary { background: var(--accent); color: var(--accent-fg); }
569
- .ds-icon-btn-primary:hover { background: var(--fg); color: var(--accent-ink); }
570
- /* warn = destructive ACTION tone; flame stays exclusively error STATUS. */
571
- .ds-icon-btn-danger { background: var(--warn); color: var(--on-color); }
572
- .ds-icon-btn-danger:hover { filter: brightness(0.92); }
573
- .ds-icon-btn:active { transform: translateY(1px); }
574
- .ds-icon-btn:disabled, .ds-icon-btn.is-disabled {
575
- opacity: 0.5; cursor: not-allowed; pointer-events: none;
576
- }
577
-
578
- /* ============================================================
579
- Badge — small inline count/status pill
580
- ============================================================ */
581
- .ds-badge {
582
- display: inline-flex; align-items: center; justify-content: center;
583
- min-width: 18px; height: 18px; padding: 0 6px;
584
- font-size: var(--fs-micro); font-weight: 600; line-height: 1;
585
- border-radius: var(--r-pill);
586
- background: var(--bg-3); color: var(--fg-2);
587
- }
588
- /* Badge size ladder — sm for dense inline counts, lg for prominent markers.
589
- Base (above) stays the 18px default. */
590
- .ds-badge.ds-badge--sm { min-width: 14px; height: 14px; padding: 0 var(--space-1); font-size: 10px; }
591
- .ds-badge.ds-badge--lg { min-width: 22px; height: 22px; padding: 0 var(--space-2); font-size: var(--fs-tiny); }
592
- /* Canonical tone names are tone-success / tone-error / tone-neutral. Every
593
- other tone name grouped into the same rule below (tone-green/tone-live,
594
- tone-flame, tone-wip, tone-sun/tone-yellow, tone-ok/tone-miss) is a
595
- true-synonym legacy alias, kept working but sharing one declaration so the
596
- tone-family colors can't drift apart across names. */
597
- .ds-badge.tone-green, .ds-badge.tone-live, .ds-badge.tone-success {
598
- background: var(--green-tint); color: var(--green-deep);
599
- }
600
- .ds-badge.tone-flame, .ds-badge.tone-error {
601
- background: var(--flame); color: var(--on-color);
602
- }
603
- .ds-badge.tone-wip, .ds-badge.tone-neutral {
604
- background: var(--bg-3); color: var(--fg-2);
605
- }
606
- .ds-badge.tone-purple { background: var(--purple-tint); color: var(--purple-deep); }
607
- .ds-badge.tone-mascot { background: var(--mascot-tint); color: var(--ink); }
608
- .ds-badge.tone-sun, .ds-badge.tone-yellow {
609
- background: var(--sun); color: var(--ink);
610
- }
611
- .ds-badge.tone-blue { background: var(--accent-tint); color: var(--accent-ink); }
612
- .ds-badge.tone-orange { background: color-mix(in oklab, var(--flame) 30%, var(--sun) 70%); color: var(--ink); }
613
-
614
- .chip {
615
- display: inline-flex; align-items: center; gap: var(--space-1-75);
616
- padding: 3px 10px;
617
- background: var(--bg-2); color: var(--fg-2);
618
- font-family: var(--ff-body);
619
- font-size: var(--fs-tiny); font-weight: 600;
620
- letter-spacing: var(--tr-caps); text-transform: uppercase;
621
- border-radius: var(--r-pill);
622
- }
623
- /* Chip size ladder — sm for dense toolbars/table cells, lg for prominent
624
- status. tone-* colouring (below) is orthogonal to size. */
625
- .chip.chip--sm { padding: var(--space-half) var(--space-2); font-size: var(--fs-micro); gap: var(--space-1); }
626
- .chip.chip--lg { padding: var(--space-1-75) var(--space-2-75); font-size: var(--fs-xs); }
627
- /* Tag variant — a rectangular, sentence-case chip for dense data (table cells,
628
- inline labels) where the all-caps pill reads too shouty and space-hungry.
629
- Keeps the tone-* palette; drops the caps + pill for a tight --r-0 corner. */
630
- .chip.chip--tag {
631
- text-transform: none; letter-spacing: 0; font-weight: 500;
632
- border-radius: var(--r-0); padding: var(--space-half) var(--space-2);
633
- }
634
- /* Same canonical/legacy-alias grouping as .ds-badge above. */
635
- .chip.tone-green, .chip.tone-live, .chip.tone-success, .chip.tone-ok {
636
- background: var(--green-tint); color: var(--green-deep);
637
- }
638
- .chip.tone-flame, .chip.tone-error, .chip.tone-miss {
639
- background: var(--flame); color: var(--on-color);
640
- }
641
- .chip.tone-wip, .chip.tone-neutral {
642
- background: var(--bg-3); color: var(--fg-2);
643
- }
644
- .chip.tone-purple { background: var(--purple-tint); color: var(--purple-deep); }
645
- .chip.tone-mascot { background: var(--mascot-tint); color: var(--ink); }
646
- .chip.tone-sun, .chip.tone-yellow {
647
- background: var(--sun); color: var(--ink);
648
- }
649
- .chip.tone-disabled { background: var(--bg-3); color: var(--fg-3); }
650
- .chip.tone-blue { background: var(--accent-tint); color: var(--accent-ink); }
651
- .chip.tone-orange { background: color-mix(in oklab, var(--flame) 30%, var(--sun) 70%); color: var(--ink); }
652
- .chip.tone-yellow { background: var(--sun); color: var(--ink); }
653
-
654
- /* ds-pill — plain neutral chip, distinct from the toned .ds-badge/.chip family
655
- (gmsniff's .pill: a bare rounded label with no semantic tone). */
656
- .ds-pill {
657
- display: inline-block;
658
- padding: 1px 7px;
659
- border-radius: var(--r-pill);
660
- font-size: var(--fs-micro);
661
- background: var(--bg-3); color: var(--fg-2);
662
- margin: 1px;
663
- }
664
- .ds-pill.tone-accent { background: var(--accent-tint); color: var(--accent-ink); }
665
- .ds-pill.tone-muted { background: transparent; color: var(--fg-3); box-shadow: inset 0 0 0 1px var(--rule); }
666
-
667
- .glyph {
668
- display: inline-flex; align-items: center; justify-content: center;
669
- width: 18px; height: 18px;
670
- font-family: var(--ff-mono); font-size: var(--fs-tiny);
671
- opacity: 0.8;
672
- }
673
-
674
- /* ============================================================
675
- Panel — soft tonal container, no border decoration
676
- ============================================================ */
677
- .panel {
678
- background: var(--panel-1, var(--bg-2));
679
- border: var(--bw-hair) solid var(--rule);
680
- border-radius: var(--r-2);
681
- margin: 0 0 var(--space-4);
682
- padding: var(--space-3);
683
- position: relative;
684
- /* Crisp, restrained elevation — a hairline border does the edge definition;
685
- the shadow only adds a whisper of lift, not a soft glow-card float. */
686
- box-shadow: var(--shadow-1);
687
- transition: box-shadow var(--dur-base) var(--ease-spring), border-color var(--dur-snap) var(--ease),
688
- transform var(--dur-base) var(--ease-spring);
689
- }
690
- /* Editorial accent spine — an opt-in printed-ink rule down the left edge that
691
- reads as a magazine pull-quote bar, not a glow card. */
692
- .panel.panel-spine {
693
- border-radius: 0 var(--r-2) var(--r-2) 0;
694
- padding-left: calc(var(--space-3) + var(--bw-chunk));
695
- }
696
- .panel.panel-spine::before {
697
- content: ''; position: absolute; left: 0; top: 0; bottom: 0;
698
- width: var(--bw-chunk); background: var(--accent);
699
- border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
700
- }
701
- .panel.panel-inline { background: var(--bg-2); padding: var(--space-3) var(--space-3); }
702
- .panel.panel-wide { background: transparent; box-shadow: none; border: none; }
703
- /* Dense panel — a compact container for app surfaces (inspector sections,
704
- sidebars) that want the .panel look without the roomy 16px pad + 24px stack
705
- gap. Tighter corner + padding + head, so it sits between .panel and the
706
- flush editor .ds-ep-panel. */
707
- .panel.panel--dense {
708
- padding: var(--space-2); margin-bottom: var(--space-2-75);
709
- border-radius: var(--r-1);
710
- }
711
- .panel.panel--dense .panel-head { padding: var(--space-1-5) var(--space-2); }
712
- .panel.panel--dense .panel-body { padding: var(--space-1-5) var(--space-2) var(--space-2); }
713
- .panel.panel--dense .panel-body > * + * { margin-top: var(--space-2); }
714
- .panel-head {
715
- display: flex; align-items: baseline; justify-content: space-between;
716
- /* Shares the .panel-body 16px side padding so the caps label and the body
717
- rows sit on ONE left axis (the old 24/32 padding was authored for the
718
- pre-padding .panel-wide era and indented the label 16px past its content). */
719
- padding: var(--space-2) var(--space-3);
720
- font-size: var(--fs-tiny); font-weight: 600;
721
- color: var(--fg-3);
722
- letter-spacing: var(--tr-caps); text-transform: uppercase;
723
- }
724
- .panel.panel-wide .panel-head { padding-left: 0; padding-right: 0; }
725
- .panel-head > :last-child {
726
- font-weight: 500; color: var(--fg-3);
727
- font-family: var(--ff-mono); letter-spacing: 0;
728
- }
729
- .panel-body { padding: var(--space-2) var(--space-3) var(--space-3); }
730
- .panel-body > * { margin: 0; }
731
- .panel-body > * + * { margin-top: var(--space-3); }
732
- .panel.panel-wide .panel-body { padding: 0; }
733
-
734
- /* ============================================================
735
- Row — primary list pattern. Indicator rail on the left,
736
- no hairlines between rows.
737
- ============================================================ */
738
- .row {
739
- display: grid;
740
- grid-template-columns: minmax(80px, 12ch) minmax(0, 1fr) auto;
741
- gap: var(--space-3);
742
- align-items: baseline;
743
- /* App density is the BASE: a list row is quiet working chrome, so it defaults
744
- to the compact 12px/16px rhythm (density-scaled) rather than the old
745
- 16px/24px marketing padding. .row--lede restores the roomier marketing
746
- voice for editorial index pages. */
747
- padding: calc(var(--space-2-75) * var(--density)) var(--space-3);
748
- background: var(--bg);
749
- border-radius: var(--r-1);
750
- color: var(--fg);
751
- position: relative;
752
- /* Only background + the leading rail animate; padding/size never change on
753
- interaction, so they are deliberately absent (a padding transition here was
754
- dead and misleading). */
755
- transition: background var(--dur-snap) var(--ease), box-shadow var(--dur-base) var(--ease-spring);
756
- }
757
- .row + .row { margin-top: 2px; }
758
- /* Interactive rows (anchor/button wrappers) get a hairline lift on hover,
759
- matching the btn/panel elevation language — non-interactive rows (plain
760
- divs used as static list items) are unaffected since the selector needs a
761
- real interactive role. */
762
- a.row:hover, button.row:hover, .row[role="button"]:hover {
763
- box-shadow: var(--shadow-1);
764
- }
765
- /* `detail` drops to its own full-width line. The row is a grid, so span every
766
- track (grid-column 1/-1); flex-basis:100% covers the flex-row fallback. */
767
- .ds-row-detail {
768
- white-space: pre-wrap;
769
- font-family: var(--ff-mono);
770
- font-size: var(--fs-sm);
771
- line-height: var(--lh-snug);
772
- margin-top: var(--space-2);
773
- padding: 10px 12px;
774
- background: var(--bg);
775
- border-radius: var(--r-1);
776
- overflow-x: auto;
777
- flex-basis: 100%;
778
- grid-column: 1 / -1;
779
- }
780
- .row::before {
781
- content: '';
782
- position: absolute; left: 7px; top: 0; bottom: 0; width: 3px;
783
- margin: auto 0;
784
- height: 0; /* collapsed by default; reveals from centre */
785
- background: var(--rule-strong);
786
- border-radius: var(--r-pill);
787
- opacity: 0;
788
- transition: height var(--dur-base) var(--ease), opacity var(--dur-snap) var(--ease), background var(--dur-snap) var(--ease);
789
- }
790
- /* A clickable row (onClick -> role=button, or a link row) is interactive, so it
791
- carries a pointer cursor; without this the click affordance is invisible. */
792
- .row[role="button"], a.row { cursor: pointer; }
793
- /* Hover reveals the rail (grows from centre) only on interactive rows — a static
794
- row should not signal an affordance it does not have. */
795
- .row[role="button"]:hover, a.row:hover { background: var(--bg-2); }
796
- .row[role="button"]:hover::before, a.row:hover::before { height: 56%; opacity: 1; }
797
- /* Editorial hover-reveal — the row's meta (cta) slides toward the lead accent
798
- and nudges right, the printed-index "open ->" affordance. */
799
- .row .meta { transition: color var(--dur-base) var(--ease), transform var(--dur-base) var(--ease-spring); }
800
- a.row:hover .meta, .row[role="button"]:hover .meta { color: var(--accent-ink); transform: translateX(3px); }
801
- .row.active { background: color-mix(in oklab, var(--accent) 10%, var(--bg-2)); }
802
- .row.active::before { height: 64%; opacity: 1; background: var(--accent); }
803
- .row.active .title { color: var(--accent-ink); }
804
- .row.row-state-disabled { opacity: 0.5; pointer-events: none; }
805
- .row.row-state-error { background: color-mix(in oklab, var(--flame) 10%, var(--bg-2)); }
806
- .row.row-state-error::before { height: 64%; opacity: 1; background: var(--flame); }
807
- /* rail tones — a persistent status colour on the leading rail (the ::before
808
- bar). Always shown (a status indicator, not a hover affordance). Active/error
809
- still win via their own rules below in source order. */
810
- .row.rail-green::before,
811
- .row.rail-purple::before,
812
- .row.rail-flame::before { height: 56%; opacity: 1; }
813
- .row.rail-green::before { background: var(--accent-ink); }
814
- .row.rail-purple::before { background: var(--purple-2); }
815
- .row.rail-flame::before { background: var(--flame); }
816
- /* Differentiate the rail by SHAPE, not hue alone (color-blind safety): error
817
- reads as a taller solid bar, subagent as a dashed/gapped fill, ok stays the
818
- short solid bar. Paired with the sr-only status word emitted by Row(). */
819
- .row.rail-flame::before { height: 64%; }
820
- .row.rail-purple::before { background: repeating-linear-gradient(var(--purple-2) 0 4px, transparent 4px 7px); }
821
- .row-grid { /* explicit grid-template-columns set inline */ }
822
- /* A row with no leading code/index drops the empty first column so the title
823
- takes the full width instead of wrapping in the narrow code gutter. */
824
- .row.row-nocode { grid-template-columns: minmax(0, 1fr) auto; }
825
-
826
- .row .code { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }
827
- /* Title defaults to the quiet app voice (fs-sm/500) — the 18px/600 marketing
828
- voice is now opt-in via .row--lede. */
829
- .row .title { font-family: var(--ff-body); font-weight: 500; font-size: var(--fs-sm); line-height: var(--lh-snug); display: flex; align-items: baseline; gap: var(--space-2-5); flex-wrap: wrap; min-width: 0; }
830
- .row .title .sub { font-family: var(--ff-body); font-weight: 400; font-size: var(--fs-sm); color: var(--fg-3); }
831
- /* Marketing/editorial index row — restores the roomier padding + larger title
832
- the default carried before app-scale became the base. */
833
- .row--lede { padding: var(--space-3) var(--space-4); }
834
- .row--lede .title { font-size: var(--fs-lg); font-weight: 600; }
835
- /* App typescale: list rows are quiet working chrome (the 18px/600 default is a
836
- marketing-surface voice). One size for ALL app list rows - matches
837
- .ds-file-row/.ds-session-title at fs-sm/500; values mirror the kit's own
838
- mobile block below. Marketing pages without data-typescale keep the default. */
839
- [data-typescale="app"] .row { padding: 12px 16px; }
840
- [data-typescale="app"] .row .title { font-size: var(--fs-sm); font-weight: 500; }
841
- .row .sub { font-family: var(--ff-body); font-weight: 400; font-size: var(--fs-sm); color: var(--fg-3); }
842
- /* Context-pane cwd fact reads as a path (like .ds-dash-cwd/.ds-session-agent/
843
- .ds-dash-model) -- scoped to that one row so no other Row .sub is affected. */
844
- .ds-context-cwd-row .row .sub { font-family: var(--ff-mono); }
845
- .row .meta { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; align-self: center; }
846
- /* WorksList meta pairs a label with a disclosure chevron, inline-aligned. */
847
- .ds-works-meta { display: inline-flex; align-items: center; gap: .4em; }
848
-
849
- a.row { text-decoration: none; }
850
-
851
- /* ============================================================
852
- Hero
853
- ============================================================ */
854
- /* Signature hero — asymmetric editorial header. Title spans wide; the body +
855
- actions sit offset in a narrower right column on desktop, stacked on mobile.
856
- No centered stack. */
857
- .ds-hero {
858
- padding: var(--space-9) 0 var(--space-8);
859
- display: grid; gap: var(--space-5) var(--space-5);
860
- grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
861
- grid-template-areas: 'title title' 'body aside';
862
- align-items: start;
863
- max-width: var(--measure-wide);
864
- position: relative; isolation: isolate;
865
- }
866
- .ds-hero-head { grid-area: title; display: grid; gap: var(--space-3); }
867
- .ds-hero-body { grid-area: body; }
868
- /* The right column carries its own visual weight — a spined, halftone-
869
- textured card holding the badge/stat cluster and the CTAs — so the
870
- asymmetric grid reads as a deliberate composition, not leftover space
871
- beside the narrow body copy. Stretches to the body column's height. */
872
- .ds-hero-aside {
873
- grid-area: aside; position: relative; isolation: isolate;
874
- align-self: stretch; display: flex; flex-direction: column;
875
- justify-content: space-between; gap: var(--space-5);
876
- background: var(--bg-2);
877
- border-radius: 0 var(--r-2) var(--r-2) 0;
878
- padding: var(--space-5) var(--space-5) var(--space-5) calc(var(--space-4) + var(--bw-chunk));
879
- }
880
- .ds-hero-aside::before {
881
- content: ''; position: absolute; left: 0; top: 0; bottom: 0;
882
- width: var(--bw-chunk); background: var(--accent);
883
- border-radius: var(--bw-chunk) 0 0 var(--bw-chunk);
884
- }
885
- .ds-hero-stats { display: flex; flex-direction: column; gap: var(--space-2); }
886
- .ds-hero-stat {
887
- font-family: var(--ff-mono); font-size: var(--fs-sm);
888
- color: var(--fg-2); letter-spacing: var(--tr-tight);
889
- padding-bottom: var(--space-2); border-bottom: var(--bw-hair) solid var(--bg-3);
890
- }
891
- .ds-hero-stat:last-child { border-bottom: 0; padding-bottom: 0; }
892
- .ds-hero-title {
893
- font-family: var(--ff-display); font-weight: 700;
894
- font-size: clamp(40px, 9cqi, 116px);
895
- line-height: 0.96; letter-spacing: var(--tr-tighter);
896
- margin: 0; max-width: 16ch; text-wrap: balance;
897
- }
898
- .ds-hero-body {
899
- font-family: var(--ff-body); font-size: var(--fs-xl);
900
- line-height: 1.45; margin: 0; max-width: 46ch;
901
- color: var(--fg-2);
902
- }
903
- /* The lead phrase in the title — printed in the electric lead, not glowing. */
904
- .ds-hero-accent { color: var(--accent-ink); font-weight: 700; }
905
- .ds-hero-actions {
906
- display: flex; gap: var(--space-2, 10px); flex-wrap: wrap;
907
- align-self: start; margin-top: var(--space-hair, 4px);
908
- }
909
- /* Container-queried (the whole shell is; a 500px pane on a wide viewport
910
- would keep the two-column grid under a width @media). Left-aligned stack —
911
- no centering — preserving the asymmetric intent. */
912
- @container (max-width: 900px) {
913
- .ds-hero {
914
- grid-template-columns: minmax(0, 1fr);
915
- grid-template-areas: 'title' 'body' 'aside';
916
- align-items: start;
917
- padding: var(--space-7) 0 var(--space-6);
918
- }
919
- .ds-hero-aside {
920
- border-radius: var(--r-2); flex-direction: row; flex-wrap: wrap;
921
- justify-content: flex-start; align-items: center;
922
- }
923
- .ds-hero-stats { flex-direction: row; flex-wrap: wrap; }
924
- .ds-hero-stat { border-bottom: 0; padding-bottom: 0; }
925
- }
926
- .ds-chat-title { margin: 0; font-size: inherit; }
927
-
928
- /* ============================================================
929
- Section grouping
930
- ============================================================ */
931
- .ds-section { margin: var(--space-8) 0; }
932
- /* Compact section (e.g. a PageHeader used as a page's first element): no large
933
- leading margin, modest trailing margin — top-aligns without consumer overrides. */
934
- .ds-section-compact { margin-top: 0; margin-bottom: var(--space-4); }
935
- .ds-section > h3 {
936
- font-family: var(--ff-body); font-weight: 600; font-size: var(--fs-h3);
937
- margin: 0 0 var(--space-5);
938
- letter-spacing: -0.01em;
939
- }
940
-
941
- @media (max-width: 900px) {
942
- .ds-section { margin: var(--space-4) 0; }
943
- }
944
-
945
- /* ============================================================
946
- Print texture — the signature surface treatment. Opt-in overlay that
947
- reads as risograph/newsprint grain, layered above the surface fill but
948
- below content. Faint by default so type stays crisp; reduced-data and
949
- reduced-motion are unaffected (texture is static).
950
- ============================================================ */
951
- .ds-grain { position: relative; isolation: isolate; }
952
- .ds-grain::after {
953
- content: ''; position: absolute; inset: 0; z-index: -1;
954
- pointer-events: none;
955
- background-image: var(--grain);
956
- background-size: 160px 160px;
957
- opacity: var(--grain-opacity);
958
- mix-blend-mode: var(--grain-blend);
959
- }
960
- /* ============================================================
961
- Marquee / ticker — brand kinetic device ("always open 24/7"). The track
962
- holds two identical runs so the loop is seamless; reduced-motion freezes it
963
- to a single static run. Editorial, not a decorative flourish.
964
- ============================================================ */
965
- .ds-marquee {
966
- overflow: hidden; width: 100%;
967
- border-block: var(--bw-rule) solid var(--fg);
968
- padding: var(--space-2) 0;
969
- font-family: var(--ff-display); font-weight: 600;
970
- text-transform: uppercase; letter-spacing: var(--tr-caps);
971
- }
972
- .ds-marquee-track {
973
- display: inline-flex; gap: var(--space-5);
974
- white-space: nowrap; will-change: transform;
975
- animation: ds-marquee-run 28s linear infinite;
976
- }
977
- .ds-marquee-item { display: inline-flex; align-items: center; gap: var(--space-5); }
978
- .ds-marquee-sep { color: var(--accent-ink); }
979
- .ds-marquee:hover .ds-marquee-track,
980
- .ds-marquee:focus-within .ds-marquee-track,
981
- .ds-marquee:active .ds-marquee-track { animation-play-state: paused; }
982
- @keyframes ds-marquee-run {
983
- from { transform: translateX(0); }
984
- to { transform: translateX(-50%); }
985
- }
986
- /* Touch has no hover-pause; slow the ticker so it burns less attention. */
987
- @media (hover: none), (pointer: coarse) {
988
- .ds-marquee-track { animation-duration: 40s; }
989
- }
990
- @media (prefers-reduced-motion: reduce) {
991
- /* Static: wrap to a readable strip instead of a frozen half-offscreen run. */
992
- .ds-marquee-track { animation: none; flex-wrap: wrap; white-space: normal; }
993
- .ds-marquee-run-b { display: none; }
994
- }
995
-
996
- /* ============================================================
997
- Install — single CLI command card
998
- ============================================================ */
999
- .cli {
1000
- display: flex; align-items: center; gap: 14px;
1001
- overflow-x: auto;
1002
- padding: 20px 24px;
1003
- background: var(--ink); color: var(--paper);
1004
- border-radius: var(--r-3);
1005
- font-family: var(--ff-mono); font-size: var(--fs-lg);
1006
- }
1007
- .cli .prompt { color: var(--green-2); }
1008
- .cli .cmd { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; color: var(--paper); }
1009
- .cli .copy {
1010
- padding: 8px 16px; background: transparent; color: var(--paper);
1011
- font-size: var(--fs-tiny); text-transform: uppercase; letter-spacing: var(--tr-caps);
1012
- cursor: pointer; box-shadow: inset 0 0 0 1px var(--rule-strong);
1013
- border-radius: var(--r-pill);
1014
- }
1015
- .cli .copy:hover { background: var(--fg-3); }
1016
-
1017
- /* Multi-line CLI block: when .cli holds .ds-cli-row / .ds-cli-comment children
1018
- (quickstart scripts, multi-command snippets — see CliBlock in
1019
- src/components/content.js) it stacks as a column instead of the
1020
- single-line install row. */
1021
- .cli:has(.ds-cli-row),
1022
- .cli:has(.ds-cli-comment) {
1023
- flex-direction: column; align-items: stretch; gap: 0;
1024
- font-size: var(--fs-sm); line-height: 1.6;
1025
- }
1026
- .ds-cli-row {
1027
- display: flex; gap: 10px;
1028
- font-family: var(--ff-mono); font-size: 13px; line-height: 1.6;
1029
- padding: 3px 0;
1030
- }
1031
- .ds-cli-row .prompt { color: var(--green-2); flex: 0 0 auto; user-select: none; }
1032
- .ds-cli-row .cmd { flex: 1 1 auto; white-space: pre-wrap; word-break: break-word; color: var(--paper); }
1033
- .ds-cli-comment {
1034
- color: var(--fg-3);
1035
- font-family: var(--ff-mono); font-size: var(--fs-tiny); line-height: 1.6;
1036
- padding: 3px 0; white-space: pre-wrap; word-break: break-word;
1037
- }
1038
-
1039
- /* ============================================================
1040
- Receipt — key/value table
1041
- ============================================================ */
1042
- table.kv { width: 100%; border-collapse: collapse; }
1043
- table.kv td {
1044
- padding: 8px 0;
1045
- font-family: var(--ff-body); font-size: var(--fs-sm);
1046
- }
1047
- table.kv tr + tr td { border-top: 1px solid var(--rule); }
1048
- table.kv td:first-child {
1049
- color: var(--fg-3);
1050
- font-size: var(--fs-xs);
1051
- text-transform: uppercase; letter-spacing: var(--tr-caps);
1052
- width: 14ch;
1053
- }
1054
- table.kv td:last-child {
1055
- font-weight: 600;
1056
- overflow-wrap: anywhere; min-width: 0;
1057
- }
1058
- /* Tiny screens: stack the key over the value instead of squeezing a 14ch
1059
- label column beside long unbroken values (URLs, hashes). */
1060
- @media (max-width: 400px) {
1061
- table.kv td { display: block; width: auto; padding: 4px 0; }
1062
- table.kv td:first-child { padding-top: 8px; }
1063
- table.kv tr + tr td { border-top: 0; }
1064
- table.kv tr + tr td:first-child { border-top: 1px solid var(--rule); }
1065
- }
1066
-
1067
- /* ============================================================
1068
- Generic table
1069
- ============================================================ */
1070
- table {
1071
- width: 100%; border-collapse: collapse;
1072
- font-family: var(--ff-body); font-size: var(--fs-sm);
1073
- /* A data grid, not a card: a tight --r-0 corner (was --r-3/22px, which read
1074
- as a rounded card). Cells drop to 8px 12px for a dense, scannable row
1075
- (was 12/14 x 16 => ~43px rows). Density scales the vertical padding. */
1076
- border-radius: var(--r-0); overflow: hidden;
1077
- }
1078
- table th {
1079
- text-align: left;
1080
- padding: calc(var(--space-2) * var(--density)) var(--space-2-75);
1081
- font-size: var(--fs-tiny); font-weight: 600;
1082
- color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps);
1083
- background: var(--bg-2);
1084
- }
1085
- table td { padding: calc(var(--space-2) * var(--density)) var(--space-2-75); border-top: 1px solid var(--rule); }
1086
- table tr.clickable { cursor: pointer; }
1087
- table tr.clickable:hover td { background: var(--bg-2); }
1088
- table tr.clickable:focus-visible {
1089
- outline: var(--focus-w) solid var(--focus-color);
1090
- outline-offset: -2px;
1091
- }
1092
- table tr.clickable:focus-visible td { background: var(--bg-2); }
1093
-
1094
- /* Table() striped/compact opt-in density modifiers (webgeist g-table parity).
1095
- Scoped under .ds-table-wrap so they never leak onto an unrelated bare
1096
- <table> a consumer hand-rolls outside this component. */
1097
- .ds-table-wrap.is-striped table tr:nth-child(even) td { background: var(--bg-2); }
1098
- .ds-table-wrap.is-striped table tr.clickable:nth-child(even):hover td,
1099
- .ds-table-wrap.is-striped table tr.clickable:nth-child(even):focus-visible td { background: var(--bg-3, var(--bg-2)); }
1100
- .ds-table-wrap.is-compact table th { padding: var(--space-1-5, 5px) var(--space-2, 8px); }
1101
- .ds-table-wrap.is-compact table td { padding: var(--space-1-5, 5px) var(--space-2, 8px); }
1102
-
1103
- /* ============================================================
1104
- Changelog
1105
- ============================================================ */
1106
- .ds-changelog-row {
1107
- grid-template-columns: minmax(110px, 14ch) minmax(60px, 9ch) minmax(0, 1fr) !important;
1108
- }
1109
- .ds-changelog-ver {
1110
- font-family: var(--ff-body); font-weight: 600; font-size: var(--fs-sm);
1111
- }
1112
-
1113
- /* ============================================================
1114
- Works
1115
- ============================================================ */
1116
- .work-detail {
1117
- padding: var(--space-4) var(--space-5) var(--space-5);
1118
- margin: 4px 0 var(--space-2);
1119
- background: var(--bg-2);
1120
- border-radius: var(--r-3);
1121
- display: flex; flex-direction: column; gap: var(--space-3);
1122
- }
1123
- .ds-work-body { margin: 0; max-width: 60ch; }
1124
- .ds-work-actions { display: flex; gap: 10px; flex-wrap: wrap; }
1125
-
1126
- /* ============================================================
1127
- Manifesto / pull moment
1128
- ============================================================ */
1129
- .ds-manifesto { max-width: 60ch; }
1130
- .ds-manifesto-para {
1131
- font-family: var(--ff-body); font-size: var(--fs-lg);
1132
- line-height: var(--lh-long); margin: 0 0 var(--space-3);
1133
- }
1134
- .ds-manifesto-para.dim { color: var(--fg-3); }
1135
-
1136
- /* ============================================================
1137
- Currently shipping dots
1138
- ============================================================ */
1139
- .ds-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex-shrink: 0; vertical-align: middle; }
1140
- .ds-dot-on { color: var(--green-2); }
1141
- .ds-dot-off { color: var(--fg-3); }
1142
- .ds-dot-live { color: var(--green-2); }
1143
- .ds-dot-idle { color: var(--fg-3); }
1144
-
1145
- /* ============================================================
1146
- KPI
1147
- ============================================================ */
1148
- .kpi {
1149
- display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
1150
- gap: var(--space-2);
1151
- }
1152
- .kpi-card {
1153
- /* Was --space-5 (32px) pad + --r-3 (22px) — four cards ate a full fold. A
1154
- 16px pad on a --r-1 corner keeps them prominent but dense; the number drops
1155
- from a 48px marketing display to a 32px app-scale figure. */
1156
- padding: var(--space-3);
1157
- background: var(--bg-2);
1158
- border-radius: var(--r-1);
1159
- }
1160
- .kpi-card .num {
1161
- font-family: var(--ff-body); font-weight: 600;
1162
- font-size: clamp(22px, 3cqi, 32px); line-height: 1;
1163
- letter-spacing: var(--tr-tight);
1164
- }
1165
- .kpi-card .lbl {
1166
- font-size: var(--fs-xs); color: var(--fg-3);
1167
- font-weight: 600; margin-top: 8px;
1168
- letter-spacing: var(--tr-caps); text-transform: uppercase;
1169
- }
1170
- .kpi-foot {
1171
- display: flex; align-items: center; justify-content: space-between;
1172
- margin-top: var(--space-2);
1173
- }
1174
- .kpi-delta {
1175
- display: inline-flex; align-items: center; gap: var(--space-hair);
1176
- font-size: var(--fs-xs); font-weight: 600;
1177
- }
1178
- .kpi-delta-up { color: var(--green); }
1179
- .kpi-delta-down { color: var(--danger); }
1180
- .ds-sparkline path { stroke: currentColor; }
1181
- .ds-sparkline-up { color: var(--success); }
1182
- .ds-sparkline-down { color: var(--danger); }
1183
-
1184
- /* ============================================================
1185
- BarChart
1186
- ============================================================ */
1187
- .ds-barchart { display: flex; flex-direction: column; gap: var(--space-2); }
1188
- .ds-barchart-row {
1189
- display: grid; grid-template-columns: minmax(90px, 25%) 1fr auto;
1190
- align-items: center; gap: var(--space-2);
1191
- }
1192
- .ds-barchart-label { font-size: var(--fs-sm); color: var(--fg-2); }
1193
- .ds-barchart-track {
1194
- /* 6px unifies with the system's other bar tracks (.ds-upload-bar,
1195
- .ds-bar-bg); was --space-2 (8px), the lone thicker slab in the bar family. */
1196
- height: var(--space-1-75); border-radius: var(--r-pill);
1197
- background: var(--bg-3); overflow: hidden;
1198
- }
1199
- .ds-barchart-fill {
1200
- height: 100%; width: var(--bar-pct, 0%);
1201
- background: var(--fg-3); border-radius: var(--r-pill);
1202
- }
1203
- .ds-barchart-fill.is-active { background: var(--accent); }
1204
- .ds-barchart-value {
1205
- font-size: var(--fs-xs); font-weight: 600; color: var(--fg-3);
1206
- font-variant-numeric: tabular-nums;
1207
- }
1208
-
1209
- /* ============================================================
1210
- Empty state
1211
- ============================================================ */
1212
- .empty {
1213
- /* Was --space-8 (96px) — an empty list spent 192px of vertical space on
1214
- nothing. --space-6 (48px) still centres a message with air, without the
1215
- empty state dominating the fold. Mobile drops it further (below). */
1216
- padding: var(--space-6);
1217
- text-align: center;
1218
- color: var(--fg-3);
1219
- background: var(--bg-2);
1220
- border-radius: var(--r-1);
1221
- font-size: var(--fs-sm);
1222
- }
1223
-
1224
- /* ============================================================
1225
- Form
1226
- ============================================================ */
1227
- .row-form {
1228
- display: grid; gap: 12px;
1229
- padding: var(--space-5);
1230
- background: var(--bg-2);
1231
- border-radius: var(--r-3);
1232
- }
1233
- .row-form input,
1234
- .row-form textarea {
1235
- font-family: inherit; font-size: var(--fs-sm);
1236
- padding: 12px 16px;
1237
- background: var(--bg); color: var(--fg);
1238
- border: 0; border-radius: var(--r-2);
1239
- box-shadow: inset 0 0 0 1px var(--rule);
1240
- }
1241
- .row-form input:focus-visible,
1242
- .row-form textarea:focus-visible { box-shadow: var(--focus-ring-inset); }
1243
-
1244
- /* Field char counter (TextField maxLength) */
1245
- .ds-field-count {
1246
- font-size: var(--fs-tiny, 13px);
1247
- color: var(--fg-3, var(--fg-2));
1248
- text-align: right;
1249
- }
1250
-
1251
- /* Multi-column form layout (Form columns prop) */
1252
- .row-form[data-columns="2"] { grid-template-columns: repeat(2, 1fr); }
1253
- .row-form[data-columns="3"] { grid-template-columns: repeat(3, 1fr); }
1254
- @media (max-width: 600px) {
1255
- .row-form[data-columns="2"],
1256
- .row-form[data-columns="3"] { grid-template-columns: 1fr; }
1257
- }
1258
-
1259
- /* ============================================================
1260
- Responsive Design — Mobile (480px), Tablet (1024px), Desktop (1440px+)
1261
- ============================================================ */
1262
-
1263
- /* --------------------------------------------------------------------
1264
- Mobile Portrait Breakpoint (480px and below)
1265
- ---------------------------------------------------------------------- */
1266
- @media (max-width: 480px) {
1267
- /* App Layout: single-column + drawer is handled once in the ≤900px block;
1268
- no need to re-declare grid-template-columns here (was a redundant !important). */
1269
-
1270
- /* Topbar Navigation: stack the nav onto its own full-width row below the
1271
- brand and let it scroll horizontally. The brand stays on the first row;
1272
- nav wraps to a second row at 100% width (flex-wrap on .app-topbar). */
1273
- .app-topbar {
1274
- gap: var(--space-2); padding: 12px var(--space-3);
1275
- }
1276
- /* When the topbar is folded into .app-chrome it is a flex:0 0 auto child, so
1277
- it sizes to its nav's content and overflows the viewport instead of letting
1278
- the nav's own overflow-x:auto engage. Constrain it to the chrome width so
1279
- the horizontally-scrolling nav stays inside the screen. */
1280
- .app-chrome > .app-topbar { max-width: 100%; min-width: 0; flex-basis: 100%; }
1281
- .app-topbar > nav {
1282
- flex: 1 1 100%; margin-left: 0; order: 3; min-width: 0; max-width: 100%;
1283
- display: flex; gap: 2px; flex-wrap: nowrap;
1284
- overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
1285
- /* Fade the trailing edge so a clipped last item (e.g. "source ->" cut to
1286
- "sour") reads as scrollable content, not a broken/truncated label. */
1287
- mask-image: linear-gradient(90deg, var(--fg) 0 calc(100% - 28px), transparent 100%);
1288
- -webkit-mask-image: linear-gradient(90deg, var(--fg) 0 calc(100% - 28px), transparent 100%);
1289
- }
1290
- .app-topbar nav::-webkit-scrollbar { display: none; }
1291
- .app-topbar nav a {
1292
- flex: 0 0 auto; padding: 10px 12px; min-height: 44px; white-space: nowrap;
1293
- }
1294
- .brand { font-size: var(--fs-tiny); font-weight: 600; }
1295
-
1296
- /* Search */
1297
- .app-search {
1298
- display: none; /* Hide search on mobile unless explicitly pinned */
1299
- }
1300
-
1301
- /* Main Content */
1302
- .app-main { padding: var(--space-4) var(--space-2); }
1303
- .app-main.narrow { max-width: 100%; margin: 0; }
1304
-
1305
- /* Headings stay on the fluid base clamps (cqi) — no mobile re-clamp. */
1306
-
1307
- /* Panel & Row */
1308
- .panel { margin: 0 0 var(--space-3); }
1309
- .panel-head { padding: var(--space-3) var(--space-2); gap: var(--space-2); }
1310
- .panel-body { padding: var(--space-2); overflow-x: auto; -webkit-overflow-scrolling: touch; }
1311
- .panel-body > table { min-width: 100%; }
1312
-
1313
- .row {
1314
- grid-template-columns: minmax(0, 1fr) auto !important;
1315
- gap: var(--space-2); padding: 12px 16px;
1316
- row-gap: 4px !important;
1317
- }
1318
- .row .sub { grid-column: 1 / -1; order: 3; }
1319
- .row .title { font-size: var(--fs-sm); }
1320
- /* Changelog carries 3 children: give date+ver line one, message wraps below
1321
- (the generic 2-col .row override staggers it into an implicit row). */
1322
- .ds-changelog-row { grid-template-columns: minmax(70px, 10ch) minmax(0, 1fr) !important; }
1323
- .ds-changelog-row .title { grid-column: 1 / -1; }
1324
-
1325
- /* Buttons */
1326
- .btn, .btn-primary, .btn-ghost {
1327
- padding: 11px 16px; font-size: var(--fs-tiny);
1328
- min-height: 44px; /* Touch target minimum */
1329
- }
1330
-
1331
- /* File Grid */
1332
- .ds-file-grid {
1333
- display: flex;
1334
- flex-direction: column;
1335
- gap: 3px;
1336
- }
1337
- .ds-file-row {
1338
- gap: 8px; padding: 10px 12px;
1339
- font-size: var(--fs-xs);
1340
- }
1341
- .ds-file-open { gap: 10px; }
1342
- /* At mobile row widths, a fixed-size meta column (date/size) plus the
1343
- always-visible action buttons leave the flexible title only a few px —
1344
- not enough for even one legible character before the two collide. Drop
1345
- the metadata column here; the title + actions still fully identify and
1346
- operate on the row, and meta stays available in the file viewer. */
1347
- .ds-file-row .ds-file-meta { display: none; }
1348
- .ds-file-row .ds-file-actions { opacity: 1; }
1349
- /* H4: floor finger-sized controls so file actions/sort/filter/breadcrumb are
1350
- reachable by touch (cohesion sweep). */
1351
- .ds-file-act, .ds-file-sort-btn, .ds-crumb-seg { min-height: 44px; }
1352
- .ds-file-filter-input { min-height: 44px; }
1353
- .ds-file-check, .ds-file-selectall, .ds-density-btn { min-height: 44px; min-width: 44px; }
1354
-
1355
- /* Chat */
1356
- .chat-stack { max-width: 100%; min-width: 0; }
1357
- .chat-bubble {
1358
- padding: 10px 12px; font-size: var(--fs-sm);
1359
- }
1360
- .chat-avatar { width: 28px; height: 28px; font-size: 11px; }
1361
- .chat-msg:hover { padding: 4px 0; margin: 0; background: transparent; }
1362
- .chat-composer { padding: 8px; gap: 6px; }
1363
- .chat-composer textarea { padding: 10px 12px; font-size: var(--fs-sm); }
1364
-
1365
- /* KPI Cards */
1366
- .kpi {
1367
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
1368
- gap: var(--space-2);
1369
- }
1370
- .kpi-card { padding: var(--space-3); }
1371
- /* Container-relative like the base rule (5vw sized against the viewport and
1372
- jumped across the breakpoint). */
1373
- .kpi-card .num { font-size: clamp(22px, 8cqi, 32px); }
1374
-
1375
- /* Empty State */
1376
- .empty { padding: var(--space-5); font-size: var(--fs-xs); }
1377
-
1378
- /* Form */
1379
- .row-form { gap: 10px; padding: var(--space-3); }
1380
- .row-form input,
1381
- .row-form textarea { padding: 11px 12px; font-size: var(--fs-sm); }
1382
-
1383
- /* Hero Section — keep fluid base font-size, just unconstrain width on mobile. */
1384
- .ds-hero { padding: var(--space-6) 0 var(--space-5); }
1385
- .ds-hero-title { max-width: 100%; }
1386
- .ds-hero-body { max-width: 100%; }
1387
-
1388
- /* Table Responsiveness */
1389
- table { font-size: var(--fs-xs); }
1390
- table th,
1391
- table td { padding: 10px 12px; }
1392
-
1393
- /* CLI Block */
1394
- .cli {
1395
- flex-direction: column; align-items: flex-start; gap: 10px;
1396
- padding: 16px 12px; font-size: var(--fs-xs);
1397
- }
1398
- .cli .copy { padding: 6px 12px; font-size: var(--fs-micro); }
1399
- }
1400
-
1401
- /* --------------------------------------------------------------------
1402
- Container Query Responsive (720px and below)
1403
- ---------------------------------------------------------------------- */
1404
- @container (max-width: 760px) {
1405
- .app-body { grid-template-columns: 1fr !important; }
1406
- .app-side-shell { border-right: 0; border-bottom: 1px solid var(--rule); }
1407
- .app-topbar nav { font-size: var(--fs-xs); gap: 14px; }
1408
- .row { grid-template-columns: minmax(0, 1fr) auto !important; row-gap: 4px !important; }
1409
- .row .sub { grid-column: 1 / -1; order: 3; }
1410
- .ds-changelog-row { grid-template-columns: minmax(70px, 10ch) minmax(0, 1fr) !important; }
1411
- .ds-changelog-row .title { grid-column: 1 / -1; }
1412
- }
1413
-
1414
- /* --------------------------------------------------------------------
1415
- Tablet Landscape Breakpoint (768px to 1024px)
1416
- ---------------------------------------------------------------------- */
1417
- @media (min-width: 481px) and (max-width: 1024px) {
1418
- /* App Layout — side handled by 900px drawer block; restore for 901-1024 */
1419
- .app-side a {
1420
- padding: 12px 12px; gap: 8px;
1421
- grid-template-columns: 16px 1fr auto;
1422
- font-size: var(--fs-xs);
1423
- min-height: 44px;
1424
- }
1425
-
1426
- /* Topbar Navigation (flex container — no grid-template here) */
1427
- .app-topbar {
1428
- gap: var(--space-3); padding: 12px var(--space-4);
1429
- }
1430
- .app-topbar nav {
1431
- display: flex; gap: 8px; font-size: var(--fs-xs);
1432
- flex-wrap: wrap;
1433
- }
1434
- .app-topbar nav a {
1435
- padding: 12px 12px; font-size: var(--fs-xs);
1436
- min-height: 44px;
1437
- }
1438
- .brand { font-size: var(--fs-sm); }
1439
-
1440
- /* Search */
1441
- .app-search {
1442
- display: inline-flex;
1443
- max-width: 280px; font-size: var(--fs-xs);
1444
- padding: 8px 14px;
1445
- }
1446
-
1447
- /* Main Content */
1448
- .app-main { padding: var(--space-6) var(--space-4); }
1449
- .app-main.narrow { max-width: 100%; }
1450
-
1451
- /* Headings stay on the fluid base clamps (cqi) — no tablet re-clamp. */
1452
-
1453
- /* File list — single column rows on tablet (cards only via data-columns) */
1454
- .ds-file-row {
1455
- grid-template-columns: 24px minmax(0, 1fr) auto auto;
1456
- gap: 12px; padding: 12px 14px;
1457
- }
1458
-
1459
- /* Chat Bubbles */
1460
- .chat-stack { max-width: min(75%, 420px); }
1461
- .chat-bubble {
1462
- padding: 11px 14px; font-size: var(--fs-sm);
1463
- }
1464
- .chat-avatar { width: 32px; height: 32px; font-size: 12px; }
1465
-
1466
- /* KPI Cards */
1467
- .kpi {
1468
- grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
1469
- gap: var(--space-3);
1470
- }
1471
-
1472
- /* Buttons */
1473
- .btn, .btn-primary, .btn-ghost {
1474
- padding: 12px 18px; font-size: var(--fs-sm);
1475
- }
1476
-
1477
- /* Panel - keep the head on the body's left axis at laptop widths too. */
1478
- .panel-head { padding: var(--space-2) var(--space-3); }
1479
- .panel-body { padding: var(--space-2) var(--space-3); }
1480
-
1481
- /* Hero — fluid base font-size; only width unconstrained on tablet. */
1482
- .ds-hero { padding: var(--space-7) 0 var(--space-6); max-width: 100%; }
1483
- .ds-hero-title { max-width: 100%; }
1484
-
1485
- /* Row */
1486
- .row {
1487
- grid-template-columns: minmax(90px, 12ch) minmax(0, 1fr) auto;
1488
- gap: var(--space-2); padding: 14px 16px;
1489
- }
1490
- }
1491
-
1492
- /* --------------------------------------------------------------------
1493
- Mid Breakpoint (768px and below) — collapse sidebar + simplify row grid
1494
- ---------------------------------------------------------------------- */
1495
- @media (max-width: 768px) {
1496
- /* Sidebar stacks above main rather than sitting beside it */
1497
- .app-body { grid-template-columns: 1fr; }
1498
- .app-side { width: 100%; max-width: none; }
1499
-
1500
- /* Row drops the leading code column; title + meta share the line */
1501
- .row {
1502
- grid-template-columns: minmax(0, 1fr) auto;
1503
- gap: var(--space-2);
1504
- }
1505
- .row .code { display: none; }
1506
- /* Changelog's date lives in span.code — hiding it is data loss, not reflow. */
1507
- .ds-changelog-row .code { display: inline; font-size: var(--fs-micro); }
1508
- .row .sub { grid-column: 1 / -1; }
1509
- }
1510
-
1511
- /* --------------------------------------------------------------------
1512
- Desktop Breakpoint Enhancements (1025px and up)
1513
- ---------------------------------------------------------------------- */
1514
- @media (min-width: 1025px) {
1515
- /* Chat Bubbles */
1516
- .chat-stack { max-width: min(70%, 480px); }
1517
-
1518
- /* KPI Cards */
1519
- .kpi {
1520
- grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
1521
- gap: var(--space-3);
1522
- }
1523
- }
1524
-
1525
- /* ============================================================
1526
- Chat — minimal styles (placeholder for full chat surface)
1527
- ============================================================ */
1528
- .ds-chat { display: flex; flex-direction: column; gap: 12px; padding: var(--space-4); }
1529
- .ds-chat-msg { padding: 12px 18px; background: var(--bg-2); border-radius: var(--r-3); max-width: min(90vw, 36em); }
1530
- .ds-chat-msg.user { background: var(--accent-tint); color: var(--fg); align-self: flex-end; }
1531
- .ds-chat-md { font-size: var(--fs-sm); line-height: 1.6; }
1532
- .ds-chat-composer { display: flex; gap: 8px; padding: var(--space-4); }
1533
- .ds-chat-composer input { flex: 1; padding: 13px 18px; background: var(--bg-2); border: 0; border-radius: var(--r-pill); font-family: inherit; font-size: var(--fs-sm); color: var(--fg); }
1534
- .ds-chat-composer input:focus-visible { box-shadow: var(--focus-ring-inset); }
1535
-
1536
- /* ============================================================
1537
- File surface — responsive grid + row layouts
1538
- ============================================================ */
1539
-
1540
- /* Default file grid — a vertical list of rows. Each row lays out its own
1541
- columns internally; the container only stacks them. Card mode (multi-column)
1542
- is opt-in via [data-columns]. */
1543
- .ds-file-grid {
1544
- display: flex;
1545
- flex-direction: column;
1546
- gap: 4px;
1547
- }
1548
- .ds-file-grid:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); border-radius: var(--r-2); }
1549
-
1550
- /* Listing wrapper holds the optional filter + sort header above the grid. */
1551
- .ds-file-listing { display: flex; flex-direction: column; gap: var(--space-2); min-height: 0; }
1552
- /* The quick filter is a compact control, not a full-width form row - a giant
1553
- input above the grid reads as a search page and costs fold height. */
1554
- /* The filter is now an inline item in the single .ds-file-controls toolbar row
1555
- (the standalone right-aligned .ds-file-filter strip was removed). */
1556
- .ds-file-filter-input {
1557
- width: min(280px, 100%); padding: 6px 12px; font-size: var(--fs-sm);
1558
- background: var(--bg); color: var(--fg);
1559
- border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1);
1560
- }
1561
- .ds-file-controls .ds-file-filter-wrap { flex: 0 1 220px; display: contents; }
1562
- .ds-file-controls .ds-file-filter-wrap .ds-file-filter-input { flex: 0 1 220px; }
1563
- .ds-file-filter-input:focus-visible { outline: none; box-shadow: var(--focus-ring-inset); }
1564
- .ds-file-sort { display: flex; gap: var(--space-2); padding: 0 var(--space-2) var(--space-1); }
1565
- .ds-file-sort-btn {
1566
- font-size: var(--fs-tiny); font-family: var(--ff-mono); color: var(--fg-3);
1567
- background: none; border: none; cursor: pointer; padding: 2px 4px; border-radius: var(--r-1);
1568
- }
1569
- .ds-file-sort-btn:hover { color: var(--fg-2); background: var(--bg-2); }
1570
- .ds-file-sort-btn.active { color: var(--accent-ink); }
1571
- .ds-file-sort-btn:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
1572
-
1573
- /* Skeleton shimmer rows shown while a directory loads. */
1574
- .ds-file-row-skeleton { cursor: default; pointer-events: none; }
1575
- /* In-place refresh: dim the populated grid instead of swapping it for shimmer
1576
- rows (the rows stay; the round-trip reads as a settle, not a reload). */
1577
- .ds-file-grid.is-refreshing { opacity: .6; }
1578
- @media (prefers-reduced-motion: no-preference) {
1579
- .ds-file-grid { transition: opacity .15s var(--ease); }
1580
- }
1581
- /* EventList loading skeleton (shares the .ds-skel shimmer base above). */
1582
- .ds-event-row-skeleton { display: flex; align-items: center; gap: var(--space-3); padding: 12px 16px; pointer-events: none; }
1583
- .ds-skel-rank { height: 12px; width: 2.5ch; }
1584
- .ds-event-state { padding: var(--space-2) 16px; }
1585
- .ds-skel { display: inline-block; border-radius: var(--r-1); background: var(--bg-2); position: relative; overflow: hidden; }
1586
- .ds-skel::after {
1587
- content: ''; position: absolute; inset: 0;
1588
- background: linear-gradient(90deg, transparent, var(--bg-3), transparent);
1589
- transform: translateX(-100%); animation: ds-skel-shimmer 1.2s infinite;
1590
- }
1591
- .ds-skel-icon { width: 22px; height: 22px; }
1592
- .ds-skel-title { height: 12px; width: 60%; }
1593
- .ds-skel-meta { height: 10px; width: 64px; margin-left: auto; }
1594
- @keyframes ds-skel-shimmer { 100% { transform: translateX(100%); } }
1595
- @media (prefers-reduced-motion: reduce) { .ds-skel::after { animation: none; } }
1596
-
1597
- /* File row — linear layout (checkbox? · open-button[code · icon · title · meta] · actions).
1598
- The row is a flex strip; the open <button> is the stretching member and lays
1599
- out the entry's own columns. It needs a full reset (UA button chrome off)
1600
- because FileRow wraps the row content in a real button for native keyboard
1601
- semantics. */
1602
- .ds-file-row {
1603
- display: flex; align-items: center; gap: 10px;
1604
- padding: 11px 16px; background: var(--bg);
1605
- border: var(--bw-hair) solid transparent;
1606
- border-radius: var(--r-2); color: var(--fg);
1607
- cursor: pointer;
1608
- transition: background var(--dur-snap) var(--ease),
1609
- border-color var(--dur-snap) var(--ease),
1610
- transform var(--dur-snap) var(--ease);
1611
- }
1612
- .ds-file-open {
1613
- display: flex; align-items: center; gap: 14px;
1614
- flex: 1 1 auto; min-width: 0;
1615
- background: none; border: 0; padding: 0; margin: 0;
1616
- color: inherit; font: inherit; text-align: left; cursor: pointer;
1617
- }
1618
- .ds-file-open[disabled] { cursor: default; }
1619
- .ds-file-open:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); border-radius: var(--r-1); }
1620
- .ds-file-row:hover {
1621
- background: var(--bg-2);
1622
- border-color: var(--rule);
1623
- }
1624
- .ds-file-row:active { transform: translateY(1px); }
1625
- .ds-file-row.active { background: var(--accent-tint); border-color: var(--accent); }
1626
- .ds-file-row[data-file-type="dir"] { border-left-width: 3px; border-left-color: var(--accent); }
1627
- .ds-file-row[data-file-type="image"],
1628
- .ds-file-row[data-file-type="video"],
1629
- .ds-file-row[data-file-type="audio"],
1630
- .ds-file-row[data-file-type="code"],
1631
- .ds-file-row[data-file-type="text"],
1632
- .ds-file-row[data-file-type="archive"],
1633
- .ds-file-row[data-file-type="document"],
1634
- .ds-file-row[data-file-type="symlink"],
1635
- .ds-file-row[data-file-type="other"] { border-left-width: 3px; border-left-color: var(--rule); }
1636
-
1637
- /* Row internals — these are the classes FileRow actually renders. */
1638
- .ds-file-row .title {
1639
- font-weight: 500; font-size: var(--fs-sm);
1640
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1641
- min-width: 0; flex: 1 1 auto;
1642
- }
1643
- .ds-file-row .code {
1644
- font-family: var(--ff-mono); font-size: var(--fs-micro);
1645
- color: var(--fg-3);
1646
- }
1647
- .ds-file-row .ds-file-meta {
1648
- font-family: var(--ff-mono); font-size: var(--fs-xs);
1649
- color: var(--fg-3); white-space: nowrap;
1650
- /* Never shrink below content: a flex sibling with white-space:nowrap and
1651
- the default flex-shrink:1 silently overflows its own box (no ellipsis)
1652
- when the row runs out of width, painting the date/size over the title
1653
- and action icons at mobile widths. Fixed-size the meta column instead
1654
- and let .title (flex:1, min-width:0, ellipsis) absorb the squeeze. */
1655
- flex-shrink: 0;
1656
- }
1657
-
1658
- /* File icon — glyph chip, tinted per type. */
1659
- .ds-file-icon {
1660
- display: inline-flex; align-items: center; justify-content: center;
1661
- width: 26px; height: 26px; font-size: 15px; line-height: 1;
1662
- color: var(--fg-2);
1663
- }
1664
- .ds-file-row[data-file-type="dir"] .ds-file-icon { color: var(--accent-ink); }
1665
- .ds-file-row[data-file-type="image"] .ds-file-icon { color: var(--mascot); }
1666
- .ds-file-row[data-file-type="video"] .ds-file-icon { color: var(--purple-2); }
1667
- .ds-file-row[data-file-type="audio"] .ds-file-icon { color: var(--sky); }
1668
- .ds-file-row[data-file-type="code"] .ds-file-icon { color: var(--green-2); }
1669
- .ds-file-row[data-file-type="archive"] .ds-file-icon { color: var(--flame); }
1670
- .ds-file-row[data-file-type="document"] .ds-file-icon { color: var(--amber, var(--sun)); }
1671
-
1672
- /* Row actions — a faint baseline keeps them discoverable at rest (not fully
1673
- hidden, which left mouse/trackpad users unable to find rename/move/delete
1674
- without accidentally hovering a row), full opacity on hover/focus. */
1675
- .ds-file-actions {
1676
- display: inline-flex; gap: 2px; align-items: center;
1677
- opacity: 0.35; transition: opacity var(--dur-base) var(--ease);
1678
- }
1679
- .ds-file-row:hover .ds-file-actions,
1680
- .ds-file-row:focus-within .ds-file-actions { opacity: 1; }
1681
- .ds-file-act {
1682
- display: inline-flex; align-items: center; justify-content: center;
1683
- width: 28px; height: 28px; padding: 0;
1684
- background: transparent; border: 0; border-radius: var(--r-1);
1685
- color: var(--fg-3); font-size: 14px; cursor: pointer;
1686
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
1687
- }
1688
- .ds-file-act:hover { background: var(--bg-3); color: var(--fg); }
1689
- .ds-file-act-warn:hover { background: color-mix(in oklab, var(--warn) 18%, var(--bg)); color: var(--warn); }
1690
-
1691
- /* Multi-select — per-row checkbox, header tri-state, bulk action strip. */
1692
- /* CSS-drawn multi-select checkbox: a bordered box that fills with the accent +
1693
- a border-drawn tick when its parent button is .is-marked or aria-checked.
1694
- Shared by FileRow/FileCell (.is-marked) and the file/session select-all +
1695
- SessionCard select (aria-checked true/mixed) since the bundle concatenates
1696
- all CSS. Replaces the old [x]/[ ] bracket text - no glyphs, AT keeps the
1697
- role=checkbox/aria-checked name+state.
1698
- See also .ds-check in editor-primitives.css — a separate native-input
1699
- checkbox family for form/settings contexts; intentionally distinct
1700
- rendering (native vs CSS-drawn), do not let one drift toward the other
1701
- by accident. */
1702
- .ds-check-box {
1703
- width: 15px; height: 15px; box-sizing: border-box; flex: 0 0 auto; position: relative;
1704
- border: 1.5px solid var(--fg-3); border-radius: var(--r-0);
1705
- transition: background var(--dur-snap) var(--ease), border-color var(--dur-snap) var(--ease);
1706
- }
1707
- .is-marked > .ds-check-box,
1708
- [aria-checked="true"] > .ds-check-box { background: var(--fg); border-color: var(--fg); }
1709
- .is-marked > .ds-check-box::after,
1710
- [aria-checked="true"] > .ds-check-box::after {
1711
- content: ""; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px;
1712
- border: solid var(--bg); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg);
1713
- }
1714
- [aria-checked="mixed"] > .ds-check-box { background: var(--fg); border-color: var(--fg); }
1715
- [aria-checked="mixed"] > .ds-check-box::after {
1716
- content: ""; position: absolute; left: 3px; right: 3px; top: 6px; height: 2px; background: var(--bg); border-radius: var(--r-hair);
1717
- }
1718
-
1719
- .ds-file-check {
1720
- display: inline-flex; align-items: center; justify-content: center;
1721
- min-width: 28px; height: 28px; padding: 0 4px;
1722
- background: transparent; border: 0; border-radius: var(--r-1);
1723
- color: var(--fg-3);
1724
- cursor: pointer;
1725
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
1726
- }
1727
- .ds-file-check:hover { background: var(--bg-3); color: var(--fg); }
1728
- .ds-file-check.is-marked { color: var(--accent-ink); }
1729
- .ds-file-check:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
1730
- .ds-file-check[disabled] { opacity: 0.4; cursor: default; }
1731
- .ds-file-row.is-marked { background: var(--accent-tint); border-color: var(--accent); }
1732
- .ds-file-selectall {
1733
- display: inline-flex; align-items: center; gap: var(--space-1);
1734
- font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-2);
1735
- cursor: pointer; background: none; border: none;
1736
- padding: var(--space-1); border-radius: var(--r-1);
1737
- }
1738
- .ds-file-selectall:hover { background: var(--bg-2); color: var(--fg); }
1739
- .ds-file-selectall:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
1740
- .ds-file-controls { display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-2) var(--space-1); flex-wrap: wrap; }
1741
- .ds-file-controls .spread { flex: 1 1 auto; }
1742
- .ds-file-controls .ds-file-sort { padding: 0; }
1743
- .ds-bulkbar {
1744
- display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
1745
- padding: var(--space-1) var(--space-2);
1746
- background: var(--bg-2); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-2);
1747
- }
1748
- .ds-bulkbar-count { font-size: var(--fs-sm); font-weight: 600; color: var(--fg-2); }
1749
- /* BulkBar is a toolbar (it even declares role=toolbar): compact rect buttons,
1750
- quiet destructive outline - the app arm-confirms destructive bulk actions
1751
- via ConfirmDialog, so the strip never needs a loud CTA. */
1752
- .ds-bulkbar .btn, .ds-bulkbar .btn-primary {
1753
- padding: 5px 12px; min-height: 32px; border-radius: var(--r-1); font-weight: 500;
1754
- }
1755
- .ds-bulkbar .btn { background: transparent; border: var(--bw-hair) solid var(--rule); color: var(--fg-2); }
1756
- .ds-bulkbar .btn:hover { background: var(--bg-2); color: var(--fg); }
1757
- .ds-bulkbar .btn-primary.danger { background: transparent; color: var(--warn); border: var(--bw-hair) solid var(--warn); }
1758
- .ds-bulkbar .btn-primary.danger:hover { background: color-mix(in oklab, var(--warn) 12%, transparent); color: var(--warn); }
1759
- @media (pointer: coarse) {
1760
- .ds-bulkbar .btn, .ds-bulkbar .btn-primary { min-height: 44px; }
1761
- }
1762
-
1763
- /* Density picker — list / compact / thumbnails. */
1764
- .ds-density {
1765
- display: inline-flex; gap: 2px; padding: 2px;
1766
- background: var(--bg-2); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1);
1767
- }
1768
- .ds-density-btn {
1769
- display: inline-flex; align-items: center; justify-content: center;
1770
- width: 28px; height: 28px; color: var(--fg-2);
1771
- background: none; border: none; cursor: pointer;
1772
- padding: 0; border-radius: calc(var(--r-1) - 2px);
1773
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
1774
- }
1775
- .ds-density-btn:hover { color: var(--fg); }
1776
- .ds-density-btn.active { background: var(--accent-tint); color: var(--fg); }
1777
- .ds-density-btn:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
1778
-
1779
- /* Compact density — tighter rows for long listings. */
1780
- .ds-file-grid[data-density="compact"] { gap: 2px; }
1781
- .ds-file-grid[data-density="compact"] .ds-file-row { padding: 5px 12px; gap: 10px; }
1782
- .ds-file-grid[data-density="compact"] .ds-file-row .title { font-size: var(--fs-xs); }
1783
- .ds-file-grid[data-density="compact"] .ds-file-row .ds-file-meta { font-size: var(--fs-micro); }
1784
- .ds-file-grid[data-density="compact"] .ds-file-icon { width: 20px; height: 20px; font-size: 13px; }
1785
-
1786
- /* Thumbnail density — tile grid; image entries carry a real lazy thumbnail. */
1787
- .ds-file-grid-thumb {
1788
- display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
1789
- gap: var(--space-3); align-items: start;
1790
- }
1791
- .ds-file-cell {
1792
- position: relative;
1793
- background: var(--bg); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-2);
1794
- overflow: hidden;
1795
- transition: border-color var(--dur-snap) var(--ease), background var(--dur-snap) var(--ease);
1796
- }
1797
- .ds-file-cell:hover { border-color: var(--fg-3); }
1798
- .ds-file-cell.active { border-color: var(--accent); background: var(--accent-tint); }
1799
- .ds-file-cell.is-marked { border-color: var(--accent); background: var(--accent-tint); }
1800
- .ds-file-cell.is-locked { opacity: 0.6; }
1801
- .ds-file-row.is-locked { opacity: 0.6; }
1802
- .ds-file-row.is-restricted .title { color: var(--fg-2); }
1803
- /* A fourth, deliberately bordered/mono tag variant for file-permission tags
1804
- specifically (distinct from .ds-badge for numeric counts and .chip for
1805
- status/label words) - is-noaccess reuses the shared --flame tone token
1806
- so 'no access' reads the same red as chip.tone-error/miss elsewhere. */
1807
- .ds-file-perm-tag { font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--fg-3); padding: 1px 6px; border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1); white-space: nowrap; }
1808
- .ds-file-perm-tag.is-noaccess { color: var(--flame); border-color: var(--flame); }
1809
- .ds-file-cell-open {
1810
- display: flex; flex-direction: column; align-items: stretch; gap: var(--space-1);
1811
- width: 100%; padding: var(--space-1); margin: 0;
1812
- background: none; border: none; cursor: pointer; color: var(--fg);
1813
- }
1814
- .ds-file-cell-open[disabled] { cursor: default; }
1815
- .ds-file-cell-open:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; border-radius: var(--r-2); }
1816
- .ds-file-cell-media {
1817
- display: flex; align-items: center; justify-content: center;
1818
- width: 100%; aspect-ratio: 4 / 3;
1819
- background: var(--bg-2); border-radius: var(--r-1); overflow: hidden;
1820
- }
1821
- .ds-file-cell-media .ds-file-icon { width: 34px; height: 34px; font-size: 24px; }
1822
- .ds-file-cell-thumb { width: 100%; height: 100%; object-fit: cover; }
1823
- /* Per-type icon tints on thumb tiles so the grid keeps the colour-coding the
1824
- list rows already carry (mirrors .ds-file-row[data-file-type] .ds-file-icon). */
1825
- .ds-file-cell[data-file-type="dir"] .ds-file-icon { color: var(--accent-ink); }
1826
- .ds-file-cell[data-file-type="image"] .ds-file-icon { color: var(--mascot); }
1827
- .ds-file-cell[data-file-type="video"] .ds-file-icon { color: var(--purple-2); }
1828
- .ds-file-cell[data-file-type="audio"] .ds-file-icon { color: var(--sky); }
1829
- .ds-file-cell[data-file-type="code"] .ds-file-icon { color: var(--green-2); }
1830
- .ds-file-cell[data-file-type="archive"] .ds-file-icon { color: var(--flame); }
1831
- .ds-file-cell[data-file-type="document"] .ds-file-icon { color: var(--amber); }
1832
- .ds-file-cell[data-file-type="symlink"] .ds-file-icon { color: var(--purple); }
1833
- .ds-file-cell-name {
1834
- font-size: var(--fs-tiny); text-align: left;
1835
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1836
- }
1837
- .ds-file-cell-meta { font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--fg-3); text-align: left; }
1838
- .ds-file-cell-check {
1839
- position: absolute; top: 4px; left: 4px; z-index: 1;
1840
- background: color-mix(in oklab, var(--bg) 78%, transparent);
1841
- }
1842
-
1843
- /* ============================================================
1844
- File browser surface — stage, toolbar, breadcrumb, dropzone,
1845
- upload progress, empty state, modals, preview. Authored for
1846
- fsbrowse but lives in the shared kit under .ds-247420 scope.
1847
- ============================================================ */
1848
-
1849
- /* Stage — the scrollable content column. */
1850
- .ds-file-stage {
1851
- display: flex; flex-direction: column; gap: var(--space-3);
1852
- width: 100%; max-width: var(--measure-wide); margin: 0 auto;
1853
- padding: var(--space-2) var(--space-3) var(--space-4);
1854
- }
1855
- /* When the stage is the app-main child, the shell already supplies top
1856
- padding — collapse the stage's own so content isn't pushed down. */
1857
- .app-main > div > .ds-file-stage,
1858
- .app-main .ds-file-stage { padding-top: 0; }
1859
-
1860
- /* Full-width files stack: the stage's space-3 vertical beat without its
1861
- --measure-wide cap/auto-margins, so the roots / toolbar / bulkbar / uploads / grid
1862
- bands keep one consistent rhythm instead of touching edge-to-edge. Carries
1863
- the same side/bottom padding as .ds-file-stage (top collapses the same way
1864
- when mounted directly under .app-main, which already supplies top padding)
1865
- so switching a consumer from the capped stage to this full-width stack is a
1866
- pure width change, not a spacing regression. */
1867
- .ds-files-stack {
1868
- display: flex; flex-direction: column; gap: var(--space-3); min-height: 0;
1869
- padding: 0 var(--space-3) var(--space-4);
1870
- }
1871
- .app-main > div > .ds-files-stack,
1872
- .app-main .ds-files-stack { padding-top: 0; }
1873
-
1874
- /* Breadcrumb path */
1875
- .ds-crumb-path {
1876
- display: flex; align-items: center; flex-wrap: wrap; gap: 2px;
1877
- font-size: var(--fs-sm);
1878
- }
1879
- .ds-crumb-seg {
1880
- background: transparent; border: 0; cursor: pointer;
1881
- padding: 4px 8px; border-radius: var(--r-1);
1882
- color: var(--fg-3); font-family: inherit; font-size: var(--fs-sm);
1883
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
1884
- /* A single long unbroken segment (hash-named dir) must never force the
1885
- whole path past the viewport. */
1886
- max-width: min(40vw, 220px);
1887
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1888
- }
1889
- .ds-crumb-seg:hover { background: var(--bg-2); color: var(--fg); }
1890
- .ds-crumb-seg.leaf { color: var(--fg); font-weight: 600; }
1891
- .ds-crumb-sep { color: var(--fg-3); opacity: 0.6; user-select: none; }
1892
-
1893
- /* Toolbar */
1894
- .ds-file-toolbar {
1895
- display: flex; align-items: center; justify-content: space-between;
1896
- gap: var(--space-2); flex-wrap: wrap;
1897
- }
1898
- /* Dense page header: one row, small heading, single-line muted lede. The
1899
- working surfaces (files, live, history, settings) lead with content, not a
1900
- display H1 + paragraph that costs the first 150px of every fold. */
1901
- .ds-page-header-dense { margin: 0 0 var(--space-3); }
1902
- .ds-page-header-dense-row { display: flex; align-items: baseline; gap: var(--space-3); min-width: 0; }
1903
- .ds-page-header-dense-row h1 {
1904
- margin: 0; font-size: var(--fs-lg); line-height: 1.3; font-weight: 600;
1905
- letter-spacing: normal; flex: 0 0 auto;
1906
- }
1907
- .ds-page-header-dense-lede {
1908
- flex: 1 1 auto; min-width: 0;
1909
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1910
- font-size: var(--fs-sm); color: var(--fg-3);
1911
- }
1912
- .ds-page-header-dense .ds-page-header-right { flex: 0 0 auto; margin-left: auto; }
1913
- /* Below ~480px the single-line ellipsis truncates the majority of the lede
1914
- text with no way to recover it (no title attr, no expand) - let the row
1915
- wrap and the lede break onto its own line instead of clipping. */
1916
- @media (max-width: 480px) {
1917
- .ds-page-header-dense-row { flex-wrap: wrap; }
1918
- .ds-page-header-dense-lede {
1919
- flex-basis: 100%; overflow: visible; text-overflow: clip; white-space: normal;
1920
- }
1921
- }
1922
- .ds-file-toolbar-left,
1923
- .ds-file-toolbar-right {
1924
- display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
1925
- }
1926
- /* Toolbar-scoped buttons: quiet, compact, rectangular - a toolbar is chrome,
1927
- not a CTA row; the stadium pill stays for real page-level actions. */
1928
- .ds-file-toolbar .btn, .ds-file-toolbar .btn-ghost {
1929
- padding: 5px 12px; min-height: 32px;
1930
- border-radius: var(--r-1);
1931
- font-weight: 500;
1932
- background: transparent;
1933
- border: var(--bw-hair) solid var(--rule); color: var(--fg-2);
1934
- }
1935
- .ds-file-toolbar .btn:hover, .ds-file-toolbar .btn-ghost:hover { background: var(--bg-2); color: var(--fg); }
1936
- @media (pointer: coarse) {
1937
- .ds-file-toolbar .btn, .ds-file-toolbar .btn-ghost { min-height: 44px; }
1938
- }
1939
- .ds-meta-mono {
1940
- font-family: var(--ff-mono); font-size: var(--fs-xs);
1941
- color: var(--fg-3); letter-spacing: var(--tr-caps);
1942
- }
1943
-
1944
- /* Error banner — dismissible toast row. */
1945
- .ds-error-banner {
1946
- padding: 10px 14px; border-radius: var(--r-2);
1947
- background: color-mix(in oklab, var(--warn) 14%, var(--bg));
1948
- border: var(--bw-hair) solid color-mix(in oklab, var(--warn) 40%, transparent);
1949
- color: var(--warn); font-size: var(--fs-sm); cursor: pointer;
1950
- }
1951
- .ds-error-banner:hover { background: color-mix(in oklab, var(--warn) 20%, var(--bg)); }
1952
-
1953
- /* Dropzone */
1954
- .ds-dropzone {
1955
- border: var(--bw-rule) dashed var(--rule);
1956
- border-radius: var(--r-3); background: var(--bg);
1957
- transition: border-color var(--dur-base) var(--ease), background var(--dur-base) var(--ease);
1958
- }
1959
- .ds-dropzone-inner {
1960
- display: flex; align-items: center; justify-content: center;
1961
- gap: var(--space-2); flex-wrap: wrap;
1962
- padding: var(--space-4) var(--space-3); text-align: center;
1963
- }
1964
- .ds-dropzone-glyph { font-size: 22px; color: var(--fg-3); }
1965
- .ds-dropzone-label { font-size: var(--fs-sm); color: var(--fg-3); }
1966
- .ds-dropzone.dragover {
1967
- border-color: var(--accent);
1968
- background: var(--accent-tint);
1969
- }
1970
- @media (prefers-reduced-motion: no-preference) {
1971
- .ds-dropzone.dragover { animation: ds-dropzone-pulse 1.1s var(--ease) infinite; }
1972
- }
1973
- .ds-dropzone.dragover .ds-dropzone-glyph,
1974
- .ds-dropzone.dragover .ds-dropzone-label { color: var(--accent-ink); }
1975
- @keyframes ds-dropzone-pulse {
1976
- 0%, 100% { border-color: var(--accent); }
1977
- 50% { border-color: color-mix(in oklab, var(--accent) 45%, transparent); }
1978
- }
1979
- /* Wrapper form (has children): no permanent chrome - the content renders as if
1980
- the zone were not there, and the dashed affordance overlays it ONLY while a
1981
- drag is in flight. */
1982
- .ds-dropzone--wrap { position: relative; border: none; border-radius: 0; background: transparent; }
1983
- .ds-dropzone--wrap > .ds-dropzone-inner { display: none; }
1984
- .ds-dropzone--wrap.dragover > .ds-dropzone-inner {
1985
- display: flex; position: absolute; inset: 0; z-index: 5;
1986
- margin: 0; padding: var(--space-3);
1987
- align-items: center; justify-content: center;
1988
- background: color-mix(in srgb, var(--bg) 84%, transparent);
1989
- outline: var(--bw-rule) dashed var(--accent); outline-offset: -6px;
1990
- border-radius: var(--r-3);
1991
- }
1992
- .ds-dropzone--wrap.dragover { animation: none; }
1993
- /* Touch devices can never fire dragover — keep the pick-files affordance
1994
- visible as a compact static row so upload stays reachable in wrap mode. */
1995
- @media (hover: none) and (pointer: coarse) {
1996
- .ds-dropzone--wrap > .ds-dropzone-inner {
1997
- display: flex; position: static; padding: var(--space-2);
1998
- outline: none; background: transparent;
1999
- }
2000
- }
2001
-
2002
- /* Upload progress */
2003
- .ds-upload-progress { display: flex; flex-direction: column; gap: 6px; }
2004
- .ds-upload-item {
2005
- display: grid; grid-template-columns: minmax(0, 1fr) 120px 44px;
2006
- gap: var(--space-2); align-items: center;
2007
- padding: 8px 12px; border-radius: var(--r-2);
2008
- background: var(--bg-2); font-size: var(--fs-xs);
2009
- }
2010
- .ds-upload-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2011
- .ds-upload-bar {
2012
- height: 6px; border-radius: var(--r-pill);
2013
- background: var(--bg-3); overflow: hidden;
2014
- }
2015
- .ds-upload-fill {
2016
- display: block; height: 100%; width: 0;
2017
- background: var(--fg-3); border-radius: var(--r-pill);
2018
- transition: width var(--dur-base) var(--ease);
2019
- }
2020
- .ds-upload-fill.is-active { background: var(--accent); }
2021
- /* Indeterminate: percent is unknown mid-transfer (fetch gives no chunk-level
2022
- progress) — a sliding marquee bar reads as "working", not stuck at 0%. */
2023
- .ds-upload-bar.indeterminate .ds-upload-fill {
2024
- width: 40%; transition: none;
2025
- animation: ds-upload-marquee 1.1s ease-in-out infinite;
2026
- }
2027
- @keyframes ds-upload-marquee {
2028
- 0% { transform: translateX(-100%); }
2029
- 100% { transform: translateX(250%); }
2030
- }
2031
- @media (prefers-reduced-motion: reduce) {
2032
- .ds-upload-bar.indeterminate .ds-upload-fill { animation: none; width: 100%; opacity: 0.5; }
2033
- }
2034
- .ds-upload-pct {
2035
- font-family: var(--ff-mono); font-size: var(--fs-micro);
2036
- color: var(--fg-3); text-align: right;
2037
- }
2038
- .ds-upload-item.done .ds-upload-fill { background: var(--success); }
2039
- .ds-upload-item.done .ds-upload-pct { color: var(--green); }
2040
- .ds-upload-item.error .ds-upload-fill { background: var(--warn); }
2041
- .ds-upload-item.error .ds-upload-pct { color: var(--warn); }
2042
- /* Per-row upload recovery actions (replace / dismiss) — error rows are not dead ends. */
2043
- .ds-upload-item { grid-template-columns: minmax(0, 1fr) minmax(60px, 120px) 44px auto; }
2044
- @media (max-width: 480px) {
2045
- .ds-upload-item { grid-template-columns: minmax(0, 1fr) 44px; }
2046
- .ds-upload-bar { grid-column: 1 / -1; order: 3; }
2047
- .ds-upload-actions { grid-column: 1 / -1; justify-content: flex-end; }
2048
- }
2049
- .ds-upload-actions { display: inline-flex; gap: var(--space-1); justify-content: flex-end; }
2050
- .ds-upload-act {
2051
- padding: 2px 10px; min-height: 24px; cursor: pointer;
2052
- background: var(--bg); border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1);
2053
- color: var(--fg-2); font-family: var(--ff-body); font-size: var(--fs-micro);
2054
- }
2055
- .ds-upload-act:hover { background: var(--bg-3); color: var(--fg); }
2056
- .ds-upload-act:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
2057
- @media (hover: none), (pointer: coarse) {
2058
- .ds-upload-act { min-height: 44px; min-width: 44px; }
2059
- /* 44px floor for file controls on ANY touch device, not only narrow ones
2060
- (the width-based mobile block misses wide touch tablets). */
2061
- .ds-file-act, .ds-file-sort-btn, .ds-crumb-seg, .ds-file-filter-input { min-height: 44px; }
2062
- .ds-file-act { min-width: 44px; }
2063
- .ds-file-check, .ds-file-selectall, .ds-density-btn { min-height: 44px; min-width: 44px; }
2064
- }
2065
-
2066
- /* Empty state */
2067
- .ds-file-empty {
2068
- display: flex; flex-direction: column; align-items: center; justify-content: center;
2069
- gap: var(--space-2); padding: var(--space-6) var(--space-3);
2070
- color: var(--fg-3); text-align: center;
2071
- }
2072
- .ds-file-empty-glyph { font-size: 34px; opacity: 0.55; }
2073
- .ds-file-empty-text { font-size: var(--fs-sm); }
2074
-
2075
- /* -- Modals ------------------------------------------------- */
2076
- .ds-modal-backdrop {
2077
- position: fixed; inset: 0; z-index: var(--z-modal, 800);
2078
- display: flex; align-items: center; justify-content: center;
2079
- /* Notch/home-indicator safe: near-fullscreen modals must not slide under. */
2080
- padding: max(var(--space-3), env(safe-area-inset-top))
2081
- max(var(--space-3), env(safe-area-inset-right))
2082
- max(var(--space-3), env(safe-area-inset-bottom))
2083
- max(var(--space-3), env(safe-area-inset-left));
2084
- background: color-mix(in oklab, var(--ink) 55%, transparent);
2085
- }
2086
- @media (prefers-reduced-motion: no-preference) {
2087
- .ds-modal-backdrop { animation: ds-modal-fade var(--dur-base) var(--ease); }
2088
- }
2089
- @keyframes ds-modal-fade { from { opacity: 0; } to { opacity: 1; } }
2090
- .ds-modal {
2091
- display: flex; flex-direction: column;
2092
- width: 100%; max-width: 460px;
2093
- max-height: calc(100vh - 2 * var(--space-4));
2094
- max-height: calc(100dvh - 2 * var(--space-4));
2095
- background: var(--bg); color: var(--fg);
2096
- border: var(--bw-hair) solid var(--rule);
2097
- border-radius: var(--r-3); box-shadow: var(--shadow-3);
2098
- overflow: hidden;
2099
- }
2100
- @media (prefers-reduced-motion: no-preference) {
2101
- .ds-modal { animation: ds-modal-rise var(--dur-base) var(--ease); }
2102
- }
2103
- @keyframes ds-modal-rise {
2104
- from { opacity: 0; transform: translateY(8px) scale(0.98); }
2105
- to { opacity: 1; transform: none; }
2106
- }
2107
- .ds-modal-small { max-width: 380px; }
2108
- .ds-modal-preview { max-width: 880px; }
2109
- .ds-modal-head {
2110
- padding: var(--space-3) var(--space-4);
2111
- font-weight: 600; font-size: var(--fs-body);
2112
- border-bottom: var(--bw-hair) solid var(--rule);
2113
- }
2114
- .ds-modal-body { padding: var(--space-4); overflow: auto; font-size: var(--fs-sm); }
2115
- .ds-modal-actions {
2116
- display: flex; justify-content: flex-end; gap: var(--space-2);
2117
- padding: var(--space-3) var(--space-4);
2118
- border-top: var(--bw-hair) solid var(--rule);
2119
- }
2120
- .ds-modal-input {
2121
- width: 100%; padding: 10px 12px;
2122
- background: var(--bg-2); border: var(--bw-hair) solid var(--rule);
2123
- border-radius: var(--r-2); color: var(--fg);
2124
- font-family: inherit; font-size: var(--fs-sm);
2125
- }
2126
- .ds-modal-input:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
2127
- /* iOS auto-zooms inputs under 16px on focus, breaking the modal layout. */
2128
- @media (hover: none), (pointer: coarse) {
2129
- .ds-modal-input { font-size: 16px; min-height: 44px; }
2130
- }
2131
- /* In-body modal error (role=alert): mutation failures (409/403) surface INSIDE
2132
- the dialog, not behind the fixed overlay. */
2133
- .ds-modal-error {
2134
- margin: var(--space-2) 0 0; padding: 8px 10px;
2135
- border: var(--bw-hair) solid var(--flame); border-radius: var(--r-2);
2136
- background: color-mix(in oklab, var(--flame) 12%, var(--bg));
2137
- color: var(--flame); font-size: var(--fs-xs);
2138
- }
2139
- .btn-primary.danger {
2140
- background: var(--warn);
2141
- border-color: var(--warn);
2142
- color: var(--on-color);
2143
- }
2144
- .btn-primary.danger:hover { filter: brightness(0.92); border-color: var(--warn); }
2145
-
2146
- /* -- Preview ------------------------------------------------ */
2147
- .ds-preview-head {
2148
- display: flex; align-items: center; gap: var(--space-3);
2149
- padding: var(--space-3) var(--space-4);
2150
- border-bottom: var(--bw-hair) solid var(--rule);
2151
- }
2152
- .ds-preview-name {
2153
- font-weight: 600; font-size: var(--fs-body);
2154
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
2155
- flex: 1 1 auto;
2156
- }
2157
- .ds-preview-meta {
2158
- font-family: var(--ff-mono); font-size: var(--fs-xs);
2159
- color: var(--fg-3); white-space: nowrap;
2160
- flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
2161
- }
2162
- .ds-preview-actions { display: inline-flex; gap: 2px; margin-left: auto; }
2163
- .ds-preview-body {
2164
- flex: 1; min-height: 0; overflow: auto;
2165
- padding: var(--space-3) var(--space-4);
2166
- display: flex; flex-direction: column;
2167
- }
2168
- .ds-preview-media {
2169
- max-width: 100%;
2170
- max-height: 70vh; max-height: 70dvh;
2171
- margin: 0 auto;
2172
- border-radius: var(--r-2); object-fit: contain;
2173
- }
2174
- .ds-preview-audio { width: 100%; }
2175
- .ds-preview-code,
2176
- .ds-preview-text {
2177
- margin: 0; flex: 1; min-height: 0; overflow: auto;
2178
- padding: var(--space-3);
2179
- background: var(--ink); color: var(--paper);
2180
- border-radius: var(--r-2);
2181
- font-family: var(--ff-mono); font-size: var(--fs-xs); line-height: var(--lh-base);
2182
- white-space: pre; tab-size: 2;
2183
- }
2184
- .ds-preview-text { white-space: pre-wrap; word-break: break-word; }
2185
- .ds-preview-code code, .ds-preview-text code { font: inherit; color: inherit; background: none; }
2186
- .ds-preview-truncated {
2187
- margin-top: var(--space-2); padding-top: var(--space-2);
2188
- border-top: var(--bw-hair) solid color-mix(in oklab, var(--paper) 20%, transparent);
2189
- color: var(--paper-3); font-size: var(--fs-micro);
2190
- }
2191
- .ds-preview-fallback {
2192
- display: flex; flex-direction: column; align-items: center; justify-content: center;
2193
- gap: var(--space-2); padding: var(--space-6); color: var(--fg-3);
2194
- }
2195
- .ds-preview-glyph { font-size: 34px; opacity: 0.6; }
2196
-
2197
- /* Thin scrollbars inside preview bodies. */
2198
- .ds-preview-code, .ds-preview-text, .ds-modal-body, .ds-preview-body {
2199
- scrollbar-width: thin;
2200
- }
2201
- .ds-preview-code::-webkit-scrollbar,
2202
- .ds-preview-text::-webkit-scrollbar,
2203
- .ds-preview-body::-webkit-scrollbar,
2204
- .ds-modal-body::-webkit-scrollbar { width: 10px; height: 10px; }
2205
- .ds-preview-code::-webkit-scrollbar-thumb,
2206
- .ds-preview-text::-webkit-scrollbar-thumb {
2207
- background: color-mix(in oklab, var(--paper) 28%, transparent);
2208
- border-radius: var(--r-pill); border: 2px solid transparent; background-clip: padding-box;
2209
- }
2210
- .ds-preview-body::-webkit-scrollbar-thumb,
2211
- .ds-modal-body::-webkit-scrollbar-thumb {
2212
- background: var(--bg-3); border-radius: var(--r-pill);
2213
- }
2214
-
2215
- /* Mobile — modals go near-fullscreen, preview media shrinks. */
2216
- @media (max-width: 560px) {
2217
- .ds-modal {
2218
- max-width: 100%;
2219
- max-height: calc(100vh - 2 * var(--space-3));
2220
- max-height: calc(100dvh - 2 * var(--space-3));
2221
- }
2222
- .ds-modal-preview {
2223
- width: 100%; max-height: none;
2224
- height: calc(100vh - 2 * var(--space-3));
2225
- height: calc(100dvh - 2 * var(--space-3));
2226
- }
2227
- .ds-preview-media { max-height: 50vh; max-height: 50dvh; }
2228
- .ds-file-stage { padding: var(--space-3) var(--space-2); }
2229
- /* Preview head: let it wrap and drop the nowrap meta (duplicated in body)
2230
- so the close button can never be pushed past the modal edge. */
2231
- .ds-preview-head { flex-wrap: wrap; row-gap: 4px; }
2232
- .ds-preview-meta { display: none; }
2233
- }
2234
-
2235
- /* ============================================================
2236
- Catalog / demo shell — a shared, compact framing for the preview/*.html
2237
- component galleries, so each preview stops hand-rolling its own <style>
2238
- (body padding, section margins, demo boxes). Mirrors webgeist's dense
2239
- catalog: a 240px sticky sidebar + capped main + a hairline-bordered,
2240
- flex-wrapping .ds-demo cell that packs variants inline and reflows.
2241
- ============================================================ */
2242
- .ds-catalog { display: flex; min-height: 100vh; gap: 0; }
2243
- .ds-catalog-side {
2244
- width: 240px; flex-shrink: 0;
2245
- border-right: var(--bw-hair) solid var(--rule);
2246
- padding: var(--space-3);
2247
- position: sticky; top: 0; height: 100vh; overflow-y: auto;
2248
- }
2249
- .ds-catalog-side h1 { font-size: var(--fs-lg); margin: 0 0 var(--space-1); }
2250
- .ds-catalog-side .subtitle { font-size: var(--fs-micro); color: var(--fg-3); margin: 0 0 var(--space-4); }
2251
- .ds-catalog-side nav h4 {
2252
- font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: var(--tr-label);
2253
- color: var(--fg-3); margin: var(--space-3) 0 var(--space-1);
2254
- }
2255
- .ds-catalog-side nav a {
2256
- display: block; padding: var(--space-half) 0; font-size: var(--fs-tiny);
2257
- color: var(--fg-2); text-decoration: none;
2258
- }
2259
- .ds-catalog-side nav a:hover { color: var(--accent-ink); }
2260
- .ds-catalog-main { flex: 1; padding: var(--space-5); max-width: var(--measure-wide); min-width: 0; }
2261
- .ds-catalog-section { margin-bottom: var(--space-5); padding-top: var(--space-3); border-top: var(--bw-hair) solid var(--rule); }
2262
- .ds-catalog-section > h2 { font-size: var(--fs-h3-app); margin: 0 0 var(--space-3); }
2263
-
2264
- /* The gallery cell: a hairline-bordered, flex-wrapping bin that packs a
2265
- component's variants inline and reflows — the signature "showcase" frame. */
2266
- .ds-demo {
2267
- border: var(--bw-hair) solid var(--rule);
2268
- border-radius: var(--r-1);
2269
- padding: var(--space-3);
2270
- display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
2271
- background: var(--bg);
2272
- }
2273
- .ds-demo + .ds-demo { margin-top: var(--space-2-75); }
2274
- .ds-demo-label { font-size: var(--fs-xs); font-weight: 500; margin: 0 0 var(--space-1); }
2275
- .ds-demo-desc { font-size: var(--fs-tiny); color: var(--fg-3); margin: 0 0 var(--space-2); }
2276
-
2277
- @media (max-width: 768px) {
2278
- .ds-catalog { flex-direction: column; }
2279
- .ds-catalog-side { width: 100%; height: auto; position: static; border-right: 0; border-bottom: var(--bw-hair) solid var(--rule); }
2280
- .ds-catalog-main { padding: var(--space-3); }
2281
- }
2282
-
2283
- /* -- File browser UX affordances ---------------------------- */
2284
- /* Toolbar filter input — compact search box. */
2285
- .ds-filter-input {
2286
- width: clamp(120px, 22vw, 220px);
2287
- padding: 7px 12px;
2288
- background: var(--bg-2); border: var(--bw-hair) solid var(--rule);
2289
- border-radius: var(--r-pill); color: var(--fg);
2290
- font-family: inherit; font-size: var(--fs-xs);
2291
- }
2292
- .ds-filter-input:focus-visible { outline: none; box-shadow: var(--focus-ring-inset); }
2293
-
2294
- /* Loading skeleton: FileSkeleton renders `.ds-file-grid.ds-file-skeleton` with
2295
- `.ds-file-row.ds-file-row-skeleton` rows whose inner `.ds-skel` children shimmer
2296
- (via .ds-skel::after). The container therefore inherits the .ds-file-grid layout
2297
- and must NOT be a single 48px gradient bar (that collapsed every row and double-
2298
- shimmered). The dead .ds-file-grid-loading rule + unused ds-skeleton-shimmer
2299
- keyframe were removed; only the inner .ds-skel children animate. */
2300
-
2301
- /* Drag-to-move drop target highlight on a directory row. */
2302
- .ds-file-row.ds-drop-target {
2303
- border-color: var(--accent);
2304
- background: var(--accent-tint);
2305
- box-shadow: inset 0 0 0 1px var(--accent);
2306
- }
2307
- .ds-file-row[draggable="true"] { cursor: grab; }
2308
- .ds-file-row[draggable="true"]:active { cursor: grabbing; }
2309
-
2310
- /* (A stale duplicate multi-select block used to live here: an absolute-overlay
2311
- .ds-file-check hidden until hover, plus .ds-file-check.on / .ds-file-row.selected
2312
- / .ds-bulk-bar rules NO component ever emits. Being later in source at equal
2313
- specificity it clobbered the real in-flow checkbox at ~1243 - the shipped
2314
- multi-select rendered invisible-until-hover with clipped brackets. The live
2315
- rules are the earlier block; only the still-load-bearing bits remain below.) */
2316
- .ds-file-row { position: relative; }
2317
- /* Touch / coarse-pointer devices have no hover — keep the per-row action
2318
- buttons (rename/delete/download) visible so the file manager is fully
2319
- reachable without a pointer (e.g. an iPad in landscape). */
2320
- @media (hover: none), (pointer: coarse) {
2321
- .ds-file-actions { opacity: 1; }
2322
- }
2323
-
2324
- /* Keyboard-shortcuts hint — compact two-column legend. */
2325
- .ds-shortcuts-hint {
2326
- display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
2327
- gap: 4px var(--space-3);
2328
- padding: var(--space-3) var(--space-4);
2329
- background: var(--bg-2); border: var(--bw-hair) solid var(--rule);
2330
- border-radius: var(--r-2); font-size: var(--fs-xs);
2331
- }
2332
- .ds-shortcut-row { display: flex; align-items: center; gap: var(--space-2); }
2333
- .ds-shortcuts-hint .ds-kbd-label { flex: 1 1 auto; opacity: .85; }
2334
- .ds-shortcuts-hint .ds-kbd { white-space: normal; max-width: 100%; }
2335
- .ds-kbd-caps { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px; }
2336
- .ds-kbd-sep { color: var(--fg-3); font-size: var(--fs-micro); padding: 0 2px; }
2337
- .ds-kbd {
2338
- display: inline-block; min-width: 0;
2339
- padding: 2px 7px; border-radius: var(--r-0);
2340
- background: var(--bg); border: var(--bw-hair) solid var(--rule);
2341
- border-bottom-width: 2px;
2342
- font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--fg-2);
2343
- white-space: nowrap;
2344
- }
2345
-
2346
- /* ============================================================
2347
- Theme toggle (segmented + compact) — bound to src/theme.js
2348
- ============================================================ */
2349
- .ds-segmented {
2350
- display: inline-flex; gap: 0; padding: 2px;
2351
- background: var(--bg-2); border-radius: var(--r-pill);
2352
- font-family: var(--ff-mono); font-size: var(--fs-xs);
2353
- }
2354
- .ds-segmented .ds-seg-btn {
2355
- background: transparent; border: 0; color: var(--fg-3);
2356
- padding: 4px 12px; border-radius: var(--r-pill); cursor: pointer;
2357
- display: inline-flex; align-items: center; gap: 6px;
2358
- transition: color var(--dur-base) var(--ease), background var(--dur-base) var(--ease);
2359
- }
2360
- .ds-segmented .ds-seg-btn:hover { color: var(--fg); }
2361
- .ds-segmented .ds-seg-btn:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
2362
- .ds-segmented .ds-seg-btn.is-on {
2363
- background: var(--bg); color: var(--fg);
2364
- box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule);
2365
- }
2366
- .ds-theme-toggle.btn {
2367
- font-family: var(--ff-mono); font-size: var(--fs-xs);
2368
- display: inline-flex; align-items: center; gap: 6px;
2369
- }
2370
- /* CSS-drawn half-disc mark on the compact theme toggle: keeps the control
2371
- legible when its text label is hidden (icon-only rail strip). */
2372
- .ds-theme-disc {
2373
- flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%;
2374
- border: var(--bw-hair) solid var(--fg-3);
2375
- background: linear-gradient(90deg, var(--fg-3) 0 50%, transparent 50% 100%);
2376
- }
2377
-
2378
- /* ============================================================
2379
- Chat kit polish — styles every class emitted by Chat /
2380
- ChatMessage / ChatComposer / AICat. Before this block the
2381
- chat-* classes had no CSS and rendered as unstyled paragraphs.
2382
- ============================================================ */
2383
- .chat {
2384
- display: flex; flex-direction: column;
2385
- flex: 1; min-height: 0; gap: var(--space-3);
2386
- }
2387
- .chat-head {
2388
- display: flex; align-items: baseline; gap: 8px;
2389
- padding: var(--space-3) 0 var(--space-2);
2390
- font-family: var(--ff-mono); font-size: var(--fs-xs);
2391
- color: var(--fg-3); text-transform: lowercase; letter-spacing: 0.02em;
2392
- border-bottom: 1px solid color-mix(in oklab, var(--fg) 8%, transparent);
2393
- margin-bottom: var(--space-2);
2394
- }
2395
- .chat-head .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }
2396
- .chat-head .ds-chat-title,
2397
- .chat-head > span:nth-child(2) {
2398
- font-family: var(--ff-body); font-size: var(--fs-h4);
2399
- font-weight: 600; color: var(--fg);
2400
- text-transform: none; letter-spacing: var(--tr-tight, -0.01em); line-height: 1.2;
2401
- }
2402
- .chat-head .sub { color: var(--fg-3); }
2403
- .chat-head .spread { flex: 1; }
2404
-
2405
- .chat-thread {
2406
- display: flex; flex-direction: column; gap: var(--space-3);
2407
- flex: 1; min-height: 0; overflow-y: auto;
2408
- padding: var(--space-2) 0;
2409
- scrollbar-width: thin; scrollbar-color: var(--bg-3) transparent;
2410
- }
2411
- @media (prefers-reduced-motion: no-preference) {
2412
- .chat-thread { scroll-behavior: smooth; }
2413
- }
2414
- .chat-thread::-webkit-scrollbar { width: 8px; }
2415
- .chat-thread::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: var(--r-0); }
2416
- .chat-empty {
2417
- margin: auto;
2418
- text-align: center;
2419
- color: var(--fg-3);
2420
- font-family: var(--ff-mono);
2421
- pointer-events: none;
2422
- }
2423
- .chat-empty-title { font-size: var(--fs-sm); margin: 0 0 var(--space-1); }
2424
- .chat-empty-sub { font-size: var(--fs-tiny); margin: 0; opacity: .7; }
2425
-
2426
- .chat-msg { display: flex; gap: 12px; align-items: flex-start; padding: 6px 0; position: relative; min-width: 0; }
2427
- .chat-msg.you { flex-direction: row-reverse; }
2428
- .chat-msg.you .chat-stack { align-items: flex-end; }
2429
- .chat-msg:hover { background: color-mix(in oklab, var(--fg) 4%, transparent); padding: 6px 0; margin: 0; border-radius: var(--r-0); transition: background var(--dur-base) var(--ease); }
2430
-
2431
- .chat-avatar {
2432
- width: 36px; height: 36px; flex-shrink: 0;
2433
- border-radius: 50%; background: var(--bg-3); color: var(--fg);
2434
- display: inline-flex; align-items: center; justify-content: center;
2435
- font-family: var(--ff-mono); font-size: 12px; font-weight: 600;
2436
- text-transform: lowercase; letter-spacing: 0.02em; user-select: none;
2437
- transition: transform 0.12s ease, box-shadow 0.12s ease;
2438
- position: relative;
2439
- }
2440
- .chat-msg:hover .chat-avatar { transform: scale(1.03); box-shadow: 0 1px 4px color-mix(in oklab, var(--fg) 6%, transparent); }
2441
- .chat-msg.you .chat-avatar {
2442
- background: color-mix(in oklab, var(--accent) 18%, var(--bg-3));
2443
- color: var(--accent-ink);
2444
- }
2445
- .chat-msg.aicat .chat-avatar {
2446
- background: color-mix(in oklab, var(--mascot) 26%, var(--bg-2));
2447
- color: var(--mascot-deep);
2448
- }
2449
-
2450
- .chat-stack {
2451
- display: flex; flex-direction: column; gap: 4px;
2452
- max-width: min(70%, 56ch); min-width: 0;
2453
- flex: 0 1 auto; overflow: hidden;
2454
- }
2455
-
2456
- .chat-bubble {
2457
- padding: 10px 14px; background: var(--bg-2); color: var(--fg);
2458
- max-width: 100%;
2459
- border-radius: var(--r-2); line-height: 1.55;
2460
- word-wrap: break-word; overflow-wrap: anywhere;
2461
- font-size: var(--fs-sm);
2462
- min-width: 0;
2463
- transition: transform 0.12s ease, box-shadow 0.12s ease;
2464
- }
2465
- .chat-msg:hover .chat-bubble { transform: translateY(-1px); box-shadow: 0 2px 8px color-mix(in oklab, var(--fg) 6%, transparent); transition: transform var(--dur-base) var(--ease), box-shadow var(--dur-base) var(--ease); }
2466
- /* Own bubble — calm tinted lead, not a full electric fill (chat stays readable). */
2467
- .chat-msg.you .chat-bubble { background: var(--accent-tint); color: var(--fg); }
2468
- .chat-msg.aicat .chat-bubble {
2469
- background: color-mix(in oklab, var(--mascot) 14%, var(--bg-2));
2470
- border: 1px solid color-mix(in oklab, var(--mascot) 28%, transparent);
2471
- }
2472
- .chat-bubble code {
2473
- font-family: var(--ff-mono); font-size: 0.92em;
2474
- background: color-mix(in oklab, var(--fg) 10%, transparent);
2475
- padding: 1px 6px; border-radius: var(--r-0);
2476
- }
2477
- .chat-msg.you .chat-bubble code { background: color-mix(in oklab, var(--fg) 10%, transparent); }
2478
- .chat-bubble a {
2479
- color: inherit; text-decoration: underline;
2480
- text-underline-offset: 2px; text-decoration-thickness: 1px;
2481
- text-decoration-color: color-mix(in oklab, currentColor 50%, transparent);
2482
- }
2483
- .chat-bubble a:hover { text-decoration-color: currentColor; }
2484
- .chat-bubble strong { font-weight: 700; }
2485
- .chat-bubble em { font-style: italic; }
2486
-
2487
- .chat-bubble.chat-md { line-height: 1.6; }
2488
- .chat-bubble.chat-md > :first-child { margin-top: 0; }
2489
- .chat-bubble.chat-md > :last-child { margin-bottom: 0; }
2490
- .chat-bubble.chat-md h1,
2491
- .chat-bubble.chat-md h2,
2492
- .chat-bubble.chat-md h3,
2493
- .chat-bubble.chat-md h4 {
2494
- font-family: var(--ff-body); font-weight: 600;
2495
- margin: var(--space-2) 0 var(--space-1); line-height: var(--lh-snug);
2496
- }
2497
- .chat-bubble.chat-md h1 { font-size: 1.15em; }
2498
- .chat-bubble.chat-md h2 { font-size: 1.08em; }
2499
- .chat-bubble.chat-md h3 { font-size: 1.04em; }
2500
- .chat-bubble.chat-md p { margin: var(--space-2) 0; }
2501
- .chat-bubble.chat-md ul,
2502
- .chat-bubble.chat-md ol { margin: var(--space-2) 0; padding-left: 22px; }
2503
- .chat-bubble.chat-md li { margin: var(--space-1) 0; }
2504
- .chat-bubble.chat-md blockquote {
2505
- margin: var(--space-2) 0; padding: 4px 12px;
2506
- border-left: 3px solid color-mix(in oklab, currentColor 30%, transparent);
2507
- color: color-mix(in oklab, currentColor 75%, transparent); font-style: italic;
2508
- }
2509
- .chat-bubble.chat-md pre {
2510
- background: var(--bg);
2511
- border: 1px solid color-mix(in oklab, var(--fg) 12%, transparent);
2512
- padding: 12px 16px; border-radius: var(--r-1); overflow-x: auto;
2513
- font-family: var(--ff-mono); font-size: var(--fs-xs); line-height: 1.55;
2514
- color: var(--fg); margin: var(--space-2) 0;
2515
- }
2516
- /* When injectCodeCopy has wrapped the fence (.chat-code-block), reserve room
2517
- for the absolute .chat-code-lang tab. This selector outranks the bare
2518
- .chat-code-block pre reservation in chat.css, which the 2-class md pre
2519
- selector above would otherwise override. */
2520
- .chat-code-block .chat-bubble.chat-md pre,
2521
- .chat-bubble.chat-md .chat-code-block pre {
2522
- padding-top: calc(var(--space-3) + 4px);
2523
- }
2524
- /* Markdown tables + horizontal rules: agents routinely emit them, but they were
2525
- unstyled (bare unspaced cells, vanished rules). */
2526
- .chat-bubble.chat-md table { border-collapse: collapse; width: auto; max-width: 100%; margin: 8px 0; font-size: .95em; display: block; overflow-x: auto; }
2527
- .chat-bubble.chat-md th,
2528
- .chat-bubble.chat-md td { border: var(--bw-hair) solid var(--rule); padding: 4px 10px; text-align: left; }
2529
- .chat-bubble.chat-md th { background: color-mix(in oklab, var(--fg) 5%, transparent); font-weight: 600; }
2530
- .chat-bubble.chat-md hr { border: 0; border-top: var(--bw-hair) solid var(--rule); margin: 12px 0; }
2531
-
2532
- .chat-msg .chat-bubble.chat-code,
2533
- .chat-msg.you .chat-bubble.chat-code,
2534
- .chat-bubble.chat-code {
2535
- padding: 0; background: var(--bg);
2536
- color: var(--fg);
2537
- border: 1px solid color-mix(in oklab, var(--fg) 12%, transparent);
2538
- overflow: hidden; max-width: 100%; min-width: 0;
2539
- }
2540
- .chat-bubble.chat-code pre,
2541
- .chat-bubble pre,
2542
- .chat-msg pre {
2543
- max-width: 100%;
2544
- overflow-x: auto;
2545
- scrollbar-width: thin;
2546
- scrollbar-color: var(--bg-3) transparent;
2547
- }
2548
- .chat-bubble pre code,
2549
- .chat-msg pre code {
2550
- display: block;
2551
- white-space: pre;
2552
- min-width: 0;
2553
- }
2554
- .chat-code-head {
2555
- display: flex; align-items: center; gap: 10px;
2556
- padding: 8px 12px;
2557
- background: color-mix(in oklab, var(--fg) 6%, var(--bg-2));
2558
- border-bottom: 1px solid color-mix(in oklab, var(--fg) 12%, transparent);
2559
- font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3);
2560
- }
2561
- .chat-code-head .lang { text-transform: uppercase; font-weight: 700; color: var(--accent-ink); letter-spacing: 0.06em; }
2562
- .chat-code-head .filename,
2563
- .chat-code-head .name { color: var(--fg-2); font-weight: 500; }
2564
- .chat-code-head .spread { flex: 1; }
2565
- .chat-bubble.chat-code pre {
2566
- margin: 0; padding: 12px 16px; background: transparent;
2567
- font-family: var(--ff-mono); font-size: var(--fs-xs); line-height: 1.55;
2568
- overflow-x: auto; color: var(--fg); border-radius: 0;
2569
- }
2570
- .chat-bubble.chat-code code { background: transparent; padding: 0; font-size: inherit; }
2571
- .chat-bubble.chat-code .token.keyword,
2572
- .chat-bubble.chat-code .token.atrule,
2573
- .chat-bubble.chat-code .token.selector { color: var(--accent-ink); }
2574
- .chat-bubble.chat-code .token.string,
2575
- .chat-bubble.chat-code .token.attr-value { color: var(--code-str-alt, var(--mascot)); }
2576
- .chat-bubble.chat-code .token.comment { color: var(--fg-3); font-style: italic; }
2577
- .chat-bubble.chat-code .token.number,
2578
- .chat-bubble.chat-code .token.boolean { color: var(--code-num, var(--sun)); }
2579
- .chat-bubble.chat-code .token.punctuation { color: var(--fg-2); }
2580
- .chat-bubble.chat-code .token.property,
2581
- .chat-bubble.chat-code .token.attr-name { color: var(--code-keyword, var(--sky)); }
2582
-
2583
- .chat-msg.you .chat-image,
2584
- .chat-msg .chat-image,
2585
- .chat-image {
2586
- display: inline-block; border-radius: var(--r-1); overflow: hidden;
2587
- max-width: min(100%, 480px); background: var(--bg-2);
2588
- text-decoration: none; color: var(--fg);
2589
- border: 1px solid color-mix(in oklab, var(--fg) 10%, transparent);
2590
- transition: box-shadow 0.15s ease, border-color 0.15s ease;
2591
- }
2592
- .chat-msg:hover .chat-image,
2593
- .chat-image:hover {
2594
- border-color: var(--rule);
2595
- }
2596
- .chat-image img { display: block; width: 100%; height: auto; max-height: 360px; object-fit: cover; }
2597
- .chat-image .cap,
2598
- .chat-image .caption { display: block; padding: 8px 12px; font-size: var(--fs-xs); color: var(--fg-3); font-family: var(--ff-mono); }
2599
-
2600
- .chat-msg.you .chat-file,
2601
- .chat-msg .chat-file,
2602
- .chat-file {
2603
- display: flex; align-items: center; gap: 12px; padding: 12px 14px;
2604
- background: var(--bg-2); border: 1px solid color-mix(in oklab, var(--fg) 10%, transparent);
2605
- border-radius: var(--r-1); text-decoration: none; color: var(--fg);
2606
- max-width: min(100%, 360px);
2607
- transition: background var(--dur-snap, .15s) var(--ease, ease), border-color var(--dur-snap, .15s) var(--ease, ease);
2608
- }
2609
- .chat-file:hover { background: var(--bg-3); }
2610
- .chat-file .glyph {
2611
- font-size: 22px; width: 36px; height: 36px;
2612
- display: inline-flex; align-items: center; justify-content: center;
2613
- background: var(--bg-3);
2614
- color: var(--fg-2); border-radius: var(--r-0); flex-shrink: 0; font-family: var(--ff-mono);
2615
- }
2616
- .chat-file > div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
2617
- .chat-file .name {
2618
- font-weight: 600; font-size: var(--fs-sm);
2619
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
2620
- }
2621
- .chat-file .meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
2622
- .chat-file .size { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); }
2623
- .chat-file .go { font-family: var(--ff-mono); font-size: 16px; color: var(--fg-3); flex-shrink: 0; }
2624
- .chat-file:hover .go { color: var(--accent-ink); }
2625
-
2626
- .chat-msg.you .chat-pdf,
2627
- .chat-msg .chat-pdf,
2628
- .chat-pdf {
2629
- background: var(--bg-2);
2630
- color: var(--fg);
2631
- border: 1px solid color-mix(in oklab, var(--fg) 10%, transparent);
2632
- border-radius: var(--r-1); overflow: hidden; max-width: min(100%, 480px);
2633
- transition: box-shadow 0.12s ease, border-color 0.12s ease;
2634
- }
2635
- .chat-msg:hover .chat-pdf { border-color: var(--rule); }
2636
- .chat-pdf-head {
2637
- display: flex; align-items: center; gap: 10px; padding: 10px 14px;
2638
- background: color-mix(in oklab, var(--fg) 5%, var(--bg-2));
2639
- border-bottom: 1px solid color-mix(in oklab, var(--fg) 10%, transparent);
2640
- }
2641
- .chat-pdf-head .glyph { font-size: 18px; color: var(--mascot); flex-shrink: 0; }
2642
- .chat-pdf-head .name { flex: 1; font-weight: 600; font-size: var(--fs-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2643
- .chat-pdf-head .size { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); }
2644
- .chat-pdf-head a { color: var(--accent-ink); font-family: var(--ff-mono); font-size: var(--fs-xs); text-decoration: none; }
2645
- .chat-pdf embed,
2646
- .chat-pdf iframe,
2647
- .chat-pdf object { width: 100%; height: 360px; border: 0; display: block; }
2648
-
2649
- .chat-msg.you .chat-link,
2650
- .chat-msg .chat-link,
2651
- .chat-link {
2652
- display: grid; grid-template-columns: 80px 1fr; gap: 12px; padding: 10px;
2653
- background: var(--bg-2);
2654
- border: 1px solid color-mix(in oklab, var(--fg) 10%, transparent);
2655
- border-radius: var(--r-1); text-decoration: none; color: var(--fg);
2656
- max-width: min(100%, 480px);
2657
- transition: background var(--dur-snap, .15s) var(--ease, ease), border-color var(--dur-snap, .15s) var(--ease, ease), box-shadow 0.15s ease;
2658
- }
2659
- .chat-msg:hover .chat-link,
2660
- .chat-link:hover {
2661
- background: var(--bg-3);
2662
- }
2663
- .chat-link:not(:has(.thumb)):not(:has(img)) { grid-template-columns: 1fr; }
2664
- .chat-link .thumb,
2665
- .chat-link img.thumb { width: 80px; height: 80px; border-radius: var(--r-0); object-fit: cover; background: var(--bg-3) center/cover no-repeat; }
2666
- .chat-link .meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
2667
- .chat-link .host { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); text-transform: lowercase; }
2668
- .chat-link .title { font-weight: 600; font-size: var(--fs-sm); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
2669
- .chat-link .desc { font-size: var(--fs-xs); color: var(--fg-2); line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
2670
-
2671
- .chat-meta {
2672
- display: flex; align-items: center; gap: 6px;
2673
- font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3);
2674
- padding: 0 6px; letter-spacing: 0.02em;
2675
- }
2676
- .chat-meta .who { font-weight: 600; color: var(--fg-2); }
2677
- .chat-meta .who::after { content: ' ·'; color: var(--fg-3); font-weight: 400; }
2678
- .chat-meta .t { color: var(--fg-3); }
2679
- .chat-meta .tick { color: var(--fg-3); margin-left: 2px; letter-spacing: -2px; font-family: var(--ff-mono); }
2680
- .chat-meta .tick.read { color: var(--accent-ink); }
2681
-
2682
- .chat-reactions {
2683
- display: flex; gap: 4px; flex-wrap: wrap; margin-top: 2px;
2684
- }
2685
- .chat-reactions .rxn {
2686
- display: inline-flex; align-items: center; gap: 4px;
2687
- padding: 2px 8px; background: var(--bg-2);
2688
- border: 1px solid color-mix(in oklab, var(--fg) 10%, transparent);
2689
- border-radius: var(--r-pill); font-size: var(--fs-xs);
2690
- cursor: pointer; user-select: none;
2691
- transition: background var(--dur-snap, .12s) var(--ease, ease), border-color var(--dur-snap, .12s) var(--ease, ease), transform 0.12s ease;
2692
- position: relative;
2693
- }
2694
- .chat-reactions .rxn:hover { background: var(--bg-3); transform: scale(1.06); }
2695
- .chat-reactions .rxn.you {
2696
- background: color-mix(in oklab, var(--accent) 18%, var(--bg-2));
2697
- border-color: color-mix(in oklab, var(--accent) 55%, transparent);
2698
- color: var(--accent-ink);
2699
- }
2700
- .chat-reactions .rxn .e { font-size: 14px; line-height: 1; }
2701
- .chat-reactions .rxn .n { font-family: var(--ff-mono); font-weight: 600; font-size: 11px; color: var(--fg-2); }
2702
- .chat-reactions .rxn.you .n { color: var(--accent-ink); }
2703
-
2704
- .chat-typing { display: inline-flex; gap: 4px; align-items: center; }
2705
- .chat-typing span {
2706
- width: 6px; height: 6px; border-radius: 50%; background: var(--fg-3);
2707
- }
2708
-
2709
- @media (prefers-reduced-motion: no-preference) {
2710
- .chat-typing span {
2711
- animation: chat-typing-bounce 1.4s infinite ease-in-out;
2712
- }
2713
- .chat-typing span:nth-child(1) { animation-delay: 0s; }
2714
- .chat-typing span:nth-child(2) { animation-delay: 0.18s; }
2715
- .chat-typing span:nth-child(3) { animation-delay: 0.36s; }
2716
- @keyframes chat-typing-bounce {
2717
- 0%, 60%, 100% { transform: translateY(0); opacity: 0.55; }
2718
- 30% { transform: translateY(-5px); opacity: 1; }
2719
- }
2720
- }
2721
-
2722
- @media (prefers-reduced-motion: no-preference) {
2723
- @keyframes chat-fade-in {
2724
- from { opacity: 0; transform: translateY(8px); }
2725
- to { opacity: 1; transform: translateY(0); }
2726
- }
2727
-
2728
- .chat-msg {
2729
- animation: chat-fade-in 0.3s ease-out forwards;
2730
- }
2731
- .chat-msg:nth-child(1) { animation-delay: 0s; }
2732
- .chat-msg:nth-child(2) { animation-delay: 0.05s; }
2733
- .chat-msg:nth-child(3) { animation-delay: 0.1s; }
2734
- .chat-msg:nth-child(n+4) { animation-delay: 0.15s; }
2735
- }
2736
-
2737
- /* Calm composer (claude.ai/code): ONE bordered rounded-rect shell holding the
2738
- context line, a borderless textarea, the hint, and an inline send button -
2739
- not a stadium pill + a floating round send. The shell carries the border and
2740
- the focus ring; the textarea fills it transparently. flex-wrap lets the
2741
- context line and hint occupy their own rows above/below the input row. */
2742
- .chat-composer {
2743
- display: flex; align-items: flex-end; flex-wrap: wrap; gap: var(--space-2);
2744
- padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
2745
- background: var(--bg);
2746
- /* The composer sits bg-on-bg: the 14% --rule hairline is ~1.3:1 on paper,
2747
- leaving the primary input near-invisible at rest - use the strong rule. */
2748
- border: var(--bw-hair) solid var(--rule-strong);
2749
- border-radius: var(--r-2);
2750
- /* A flat hairline box on a flat background is the single most-repeated
2751
- "looks unfinished" tell in the chat surface - the one control the user
2752
- types into every turn had no more visual weight than a settings card.
2753
- --shadow-1 (already defined kit-wide, just unused here) lifts it a hair
2754
- off the page at rest; the existing focus-within box-shadow overrides it. */
2755
- box-shadow: var(--shadow-1);
2756
- flex-shrink: 0;
2757
- transition: border-color var(--dur-snap) var(--ease), box-shadow var(--dur-snap) var(--ease);
2758
- }
2759
- .chat-composer:focus-within {
2760
- border-color: var(--accent);
2761
- box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 18%, transparent);
2762
- }
2763
- .chat-composer.is-error,
2764
- .chat-composer:has(textarea[aria-invalid="true"]) {
2765
- border-color: var(--flame);
2766
- }
2767
- .chat-composer.is-error:focus-within,
2768
- .chat-composer:has(textarea[aria-invalid="true"]):focus-within {
2769
- box-shadow: 0 0 0 2px color-mix(in oklab, var(--flame) 22%, transparent);
2770
- }
2771
- .chat-composer textarea {
2772
- flex: 1; min-width: 0; padding: 8px 4px;
2773
- background: none; color: var(--fg);
2774
- border: none; border-radius: 0;
2775
- font-family: inherit; font-size: var(--fs-sm);
2776
- /* 1.5 is the value that actually renders (a later polish block used to
2777
- re-declare it; the sizing block now states the truth). */
2778
- line-height: 1.5; resize: none;
2779
- min-height: 28px; max-height: 200px;
2780
- box-sizing: border-box;
2781
- /* Always auto, not focus-only: a capped-height composer that loses focus
2782
- while scrolled must still show its scrollbar - otherwise once the field
2783
- blurs there is zero cue that content exists below the fold. */
2784
- overflow-y: auto;
2785
- scrollbar-width: thin;
2786
- }
2787
- .chat-composer textarea::placeholder { color: var(--fg-3); }
2788
- .chat-composer textarea:focus { background: none; border: none; box-shadow: none; outline: none; }
2789
- .chat-composer .send {
2790
- border-radius: var(--r-1);
2791
- background: var(--accent); color: var(--accent-fg);
2792
- border: 0; cursor: pointer;
2793
- display: inline-flex; align-items: center; justify-content: center;
2794
- flex-shrink: 0;
2795
- transition: background var(--dur-snap, .15s) var(--ease, ease);
2796
- }
2797
- .chat-composer .send:disabled { opacity: .5; cursor: not-allowed; }
2798
- /* Ghost icon buttons in the toolbar (attach / emoji / more). Size + radius are
2799
- owned by .chat-composer-toolbar .composer-btn in chat.css (32px / --r-1,
2800
- 44px coarse); this base rule keeps only the shared appearance. */
2801
- .composer-btn {
2802
- background: transparent; color: var(--fg-3);
2803
- border: 0; cursor: pointer;
2804
- display: inline-flex; align-items: center; justify-content: center;
2805
- flex-shrink: 0;
2806
- transition: background var(--dur-snap, .15s) var(--ease, ease), color var(--dur-snap, .15s) var(--ease, ease);
2807
- }
2808
- .composer-btn:hover { background: color-mix(in oklab, var(--fg) 8%, transparent); color: var(--fg); }
2809
- .chat-composer .send:hover { filter: brightness(0.92); }
2810
- .chat-composer .send:active { filter: brightness(0.94); }
2811
- .chat-composer .send:focus-visible,
2812
- .composer-btn:focus-visible {
2813
- outline: var(--focus-w) solid var(--focus-color);
2814
- outline-offset: var(--focus-offset);
2815
- }
2816
- .chat-composer .send:disabled {
2817
- background: var(--bg-3); color: var(--fg-3); cursor: not-allowed; transform: none;
2818
- }
2819
- @media (prefers-reduced-motion: reduce) {
2820
- .chat-composer,
2821
- .chat-composer textarea,
2822
- .chat-composer .send,
2823
- .composer-btn { transition: none; }
2824
- }
2825
-
2826
- .aicat-portrait { display: inline-flex; align-items: center; gap: 10px; padding: 4px 0; }
2827
- .aicat-face {
2828
- width: 32px; height: 32px; font-size: 24px;
2829
- display: inline-flex; align-items: center; justify-content: center;
2830
- background: color-mix(in oklab, var(--mascot) 22%, var(--bg-2));
2831
- border-radius: 50%; flex-shrink: 0;
2832
- }
2833
- .aicat-meta { display: flex; flex-direction: column; font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-3); }
2834
- .aicat-meta .name { color: var(--mascot-deep, var(--mascot)); font-weight: 600; }
2835
- [data-theme="dark"] .aicat-meta .name, [data-theme="ink"] .aicat-meta .name { color: var(--mascot); }
2836
-
2837
- /* ============================================================
2838
- Sidebar polish: hide zero-count badges; tonal active state;
2839
- monospace count chips.
2840
- ============================================================ */
2841
- .app-side .count {
2842
- padding: 1px 8px; font-size: var(--fs-micro); font-weight: 700;
2843
- font-family: var(--ff-mono); letter-spacing: 0.04em;
2844
- min-width: 18px; text-align: center;
2845
- }
2846
- .app-side a:has(> .count:empty) > .count,
2847
- .app-side .count:empty { display: none; }
2848
- .app-side a .glyph { font-family: var(--ff-mono); font-size: 13px; font-weight: 600; color: var(--fg-3); text-align: center; }
2849
- .app-side a.active .glyph { color: var(--accent-ink); }
2850
-
2851
- /* -- select primitive --------------------------------------------------- */
2852
- .ds-select {
2853
- width: 100%;
2854
- min-width: 0;
2855
- min-height: 32px; /* shared control floor with .btn/.btn-primary/.btn-ghost */
2856
- padding: 10px 30px 10px 14px; /* right gap clears the chevron */
2857
- background: var(--bg-2);
2858
- border: 0;
2859
- border-radius: var(--r-2);
2860
- font-family: inherit;
2861
- font-size: var(--fs-sm);
2862
- color: var(--fg);
2863
- /* Clip long option text gracefully - a select that cuts 'Claude Sonnet
2864
- (latest' mid-parenthesis reads as broken; ellipsis reads as truncated. */
2865
- text-overflow: ellipsis;
2866
- white-space: nowrap;
2867
- overflow: hidden;
2868
- appearance: none;
2869
- background-image: linear-gradient(45deg, transparent 50%, var(--fg-3) 50%),
2870
- linear-gradient(135deg, var(--fg-3) 50%, transparent 50%);
2871
- background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
2872
- background-size: 5px 5px, 5px 5px;
2873
- background-repeat: no-repeat;
2874
- cursor: pointer;
2875
- }
2876
- .ds-select:focus-visible { box-shadow: var(--focus-ring-inset); }
2877
- .ds-select:hover { background-color: var(--bg-3); }
2878
-
2879
- /* -- chat composer autogrow polish (sizing lives in the primary block) -- */
2880
- .chat-composer textarea {
2881
- transition: height var(--dur-snap) var(--ease);
2882
- }
2883
- .chat-composer textarea:disabled {
2884
- opacity: 0.5;
2885
- cursor: not-allowed;
2886
- }
2887
-
2888
- /* -- mobile responsive: chat -------------------------------------------- */
2889
- @media (max-width: 768px) {
2890
- .chat-stack { max-width: 100%; }
2891
- .chat-msg { padding: 4px 0; }
2892
- .chat-msg:hover { padding: 4px 0; margin: 0; border-radius: 0; background: transparent; }
2893
- .chat-bubble { font-size: var(--fs-sm); padding: 10px 14px; }
2894
- .chat-avatar { width: 28px; height: 28px; font-size: 12px; }
2895
- .chat-composer { padding: 8px; }
2896
- .chat-composer textarea { padding: 10px 12px; }
2897
- }
2898
-
2899
- /* --- landscape orientation: reduce vertical space for chrome + composer --- */
2900
- @media (max-height: 500px) and (orientation: landscape) {
2901
- /* Keep horizontal padding: 0 put the textarea/focus ring flush on the edge. */
2902
- .chat-composer { padding: 6px var(--space-2); }
2903
- .chat-composer textarea { min-height: 36px; max-height: 96px; }
2904
- .chat-composer .chat-composer-hint,
2905
- .chat-composer .chat-composer-context { display: none; }
2906
- /* Compact the shell chrome: ~114px of topbar+crumb+status is a third of a
2907
- 360px-tall landscape fold. */
2908
- :root { --app-topbar-h: 40px; --app-crumb-h: 24px; }
2909
- .app-chrome { padding: 4px var(--pad-x); }
2910
- .app-status { display: none; }
2911
- .ws-crumb { min-height: 32px; }
2912
- .ds-hero { padding: var(--space-4) 0 var(--space-3); }
2913
- .ds-hero-title { font-size: clamp(28px, 6cqi, 56px); }
2914
- }
2915
-
2916
- /* ============================================================
2917
- Enhanced UX: Component States, Drag-and-Drop, Context Menus
2918
- Comprehensive improvements for perfect UX across all surfaces
2919
- ============================================================ */
2920
-
2921
- /* ------------------------------------------------------------
2922
- Component States — Disabled, Loading, Error, Success
2923
- -------------------------------------------------------------- */
2924
-
2925
- /* Disabled state for all interactive elements. The `.is-disabled` class
2926
- variants cover Btn()'s anchor-rendered case (shell.js) — an <a> has no
2927
- :disabled pseudo-class, so a disabled link-styled button relies entirely
2928
- on this class selector for its visual treatment (aria-disabled + tabindex
2929
- are set in JS; this is the paired CSS half). */
2930
- .btn:disabled, .btn-primary:disabled, .btn-ghost:disabled,
2931
- .btn.is-disabled, .btn-primary.is-disabled, .btn-ghost.is-disabled,
2932
- button:disabled, [disabled] {
2933
- opacity: 0.6;
2934
- cursor: not-allowed;
2935
- pointer-events: none;
2936
- }
2937
-
2938
- /* Loading state — spinning indicator */
2939
- @keyframes spinner-rotate {
2940
- from { transform: rotate(0deg); }
2941
- to { transform: rotate(360deg); }
2942
- }
2943
- /* Reusable spin utility: rotates the element (e.g. a refresh Icon used as a
2944
- busy indicator) via the shared spinner-rotate keyframe. Respects reduced
2945
- motion. Use instead of a decorative rotation glyph. */
2946
- .ds-spin { animation: spinner-rotate 0.8s linear infinite; transform-origin: center; }
2947
- @media (prefers-reduced-motion: reduce) { .ds-spin { animation: none; } }
2948
-
2949
- .loading { position: relative; }
2950
- .loading::after {
2951
- content: '';
2952
- position: absolute; top: 50%; right: 12px;
2953
- width: 16px; height: 16px;
2954
- border: 2px solid var(--bg-3);
2955
- border-top-color: var(--accent);
2956
- border-radius: 50%;
2957
- animation: spinner-rotate 0.8s linear infinite;
2958
- }
2959
-
2960
- /* Error state */
2961
- .error, [data-state="error"],
2962
- .row.error, input.error,
2963
- textarea.error {
2964
- border-color: var(--warn) !important;
2965
- background-color: color-mix(in oklab, var(--warn) 6%, var(--bg));
2966
- }
2967
- .error::placeholder { color: color-mix(in oklab, var(--warn) 60%, var(--fg-3)); }
2968
-
2969
- /* Success state */
2970
- .success, [data-state="success"],
2971
- .row.success { background-color: color-mix(in oklab, var(--green-2) 6%, var(--bg)); }
2972
-
2973
- /* Readonly state */
2974
- [readonly], .readonly {
2975
- background: var(--bg-2);
2976
- cursor: default;
2977
- opacity: 0.8;
2978
- }
2979
-
2980
- /* ------------------------------------------------------------
2981
- Enhanced Button Micro-Interactions
2982
- -------------------------------------------------------------- */
2983
-
2984
- /* Button scale feedback on active */
2985
- .btn:active:not(:disabled),
2986
- .btn-primary:active:not(:disabled),
2987
- .btn-ghost:active:not(:disabled),
2988
- button:active:not(:disabled) {
2989
- transform: scale(0.98);
2990
- }
2991
-
2992
- /* Enhanced hover transitions — transition only the properties that actually
2993
- change (not `all`, which animates layout and is a perf trap), and drop the
2994
- blanket `will-change` (it forces a permanent compositor layer per element). */
2995
- .btn, .btn-primary, .btn-ghost,
2996
- button, a.row, .row,
2997
- .chip, [role="button"],
2998
- input[type="checkbox"], input[type="radio"] {
2999
- transition: background-color var(--dur-snap) var(--ease),
3000
- color var(--dur-snap) var(--ease),
3001
- box-shadow var(--dur-snap) var(--ease),
3002
- transform var(--dur-snap) var(--ease);
3003
- }
3004
-
3005
- /* Native checkbox/radio tint — browser default is UA blue, which fights the
3006
- single lime lead accent wherever a raw control lands outside a bespoke
3007
- toggle (e.g. signin's "remember me"). accent-color re-tints the native
3008
- control box without hand-drawing a replacement. */
3009
- input[type="checkbox"], input[type="radio"] {
3010
- accent-color: var(--accent-ink);
3011
- }
3012
-
3013
- /* Prevent double-tap zoom on buttons (iOS) */
3014
- .btn, .btn-primary, .btn-ghost, button {
3015
- -webkit-user-select: none;
3016
- -webkit-tap-highlight-color: transparent;
3017
- -webkit-touch-callout: none;
3018
- }
3019
-
3020
- /* ------------------------------------------------------------
3021
- Drag-and-Drop Styles
3022
- -------------------------------------------------------------- */
3023
-
3024
- /* Draggable element */
3025
- [draggable="true"], .draggable {
3026
- cursor: grab;
3027
- user-select: none;
3028
- }
3029
- [draggable="true"]:active { cursor: grabbing; }
3030
-
3031
- /* Drag-over state */
3032
- .drag-over {
3033
- border: 2px dashed var(--accent);
3034
- }
3035
-
3036
- /* Drag-ghost (semi-transparent dragging element) */
3037
- .drag-ghost {
3038
- opacity: 0.5;
3039
- transform: rotate(2deg) scale(0.95);
3040
- }
3041
-
3042
- /* Drop-zone indicator */
3043
- .drop-zone {
3044
- border: 2px dashed var(--rule-strong);
3045
- border-radius: var(--r-2);
3046
- padding: var(--space-4);
3047
- background: color-mix(in oklab, var(--accent) 4%, var(--bg));
3048
- min-height: 120px;
3049
- display: flex; align-items: center; justify-content: center;
3050
- text-align: center; color: var(--fg-3);
3051
- transition: all var(--dur-snap) var(--ease);
3052
- }
3053
- .drop-zone.active {
3054
- border-color: var(--accent);
3055
- background: color-mix(in oklab, var(--accent) 10%, var(--bg));
3056
- color: var(--accent-ink);
3057
- }
3058
-
3059
- /* List item reorder indicator */
3060
- .list-item.dragging { opacity: 0.5; }
3061
- .list-item.drag-before::before,
3062
- .list-item.drag-after::after {
3063
- content: '';
3064
- position: absolute;
3065
- left: 0; right: 0; height: 2px;
3066
- background: var(--accent);
3067
- }
3068
- .list-item.drag-before::before { top: -2px; }
3069
- .list-item.drag-after::after { bottom: -2px; }
3070
-
3071
- @keyframes pulse-line {
3072
- 0%, 100% { opacity: 0.3; }
3073
- 50% { opacity: 1; }
3074
- }
3075
- @media (prefers-reduced-motion: no-preference) {
3076
- .list-item.drag-before::before,
3077
- .list-item.drag-after::after { animation: pulse-line 0.6s ease-in-out infinite; }
3078
- }
3079
-
3080
- /* ------------------------------------------------------------
3081
- Context Menu Styles
3082
- -------------------------------------------------------------- */
3083
-
3084
- .ds-context-menu {
3085
- position: absolute;
3086
- background: var(--bg-2);
3087
- border: 1px solid var(--rule);
3088
- border-radius: var(--r-2);
3089
- box-shadow: var(--shadow-overlay);
3090
- min-width: 160px;
3091
- z-index: calc(var(--z-overlay, 1000) + 1);
3092
- padding: 4px 0;
3093
- }
3094
-
3095
- @keyframes context-menu-in {
3096
- from {
3097
- opacity: 0;
3098
- transform: scale(0.95) translateY(-4px);
3099
- }
3100
- to {
3101
- opacity: 1;
3102
- transform: scale(1) translateY(0);
3103
- }
3104
- }
3105
- @media (prefers-reduced-motion: no-preference) {
3106
- .ds-context-menu { animation: context-menu-in 0.15s ease-out; }
3107
- }
3108
-
3109
- .ds-context-menu-item {
3110
- display: flex; align-items: center; gap: 10px;
3111
- padding: 10px 14px;
3112
- cursor: pointer;
3113
- color: var(--fg);
3114
- font-size: var(--fs-sm);
3115
- transition: background var(--dur-snap) var(--ease);
3116
- }
3117
- .ds-context-menu-item:hover {
3118
- background: var(--bg-3);
3119
- color: var(--accent-ink);
3120
- }
3121
- .ds-context-menu-item:active {
3122
- background: color-mix(in oklab, var(--accent) 20%, var(--bg-3));
3123
- }
3124
- .ds-context-menu-item.danger {
3125
- color: var(--warn);
3126
- }
3127
- .ds-context-menu-item.danger:hover {
3128
- background: color-mix(in oklab, var(--warn) 12%, var(--bg));
3129
- }
3130
- .ds-context-menu-divider {
3131
- height: 1px;
3132
- background: var(--rule);
3133
- margin: 4px 0;
3134
- }
3135
- .ds-context-menu-icon {
3136
- width: 16px; height: 16px;
3137
- flex-shrink: 0;
3138
- opacity: 0.7;
3139
- }
3140
-
3141
- /* Context menu trigger indicator */
3142
- [data-has-context-menu] {
3143
- cursor: context-menu;
3144
- }
3145
-
3146
- /* ------------------------------------------------------------
3147
- Enhanced Form Inputs
3148
- -------------------------------------------------------------- */
3149
-
3150
- /* Base chrome for any BARE native control (outside .ds-field/.row-form/.input):
3151
- the same editorial tonal-fill + printed-baseline-rule as .ds-field, so a raw
3152
- <input> is never a browser-default white box in dark mode. The guards are
3153
- wrapped in :where() so they add ZERO specificity — the whole selector stays
3154
- at (0,1,1), exactly like `.ds-field input`, so the later .ds-field / .ds-field--sm
3155
- / .row-form descendant rules win by source order instead of being out-specified
3156
- (a bare :not(.input) counts as a class and would beat every .ds-field rule,
3157
- which clobbered the size variants). */
3158
- input[type="text"]:where(:not(.input):not(.ds-search-input)),
3159
- input[type="email"]:where(:not(.input):not(.ds-search-input)),
3160
- input[type="password"]:where(:not(.input):not(.ds-search-input)),
3161
- input[type="number"]:where(:not(.input):not(.ds-search-input)),
3162
- input[type="search"]:where(:not(.input):not(.ds-search-input)),
3163
- input[type="url"]:where(:not(.input):not(.ds-search-input)),
3164
- input[type="tel"]:where(:not(.input):not(.ds-search-input)),
3165
- textarea:where(:not(.input)),
3166
- select:where(:not(.ds-select)) {
3167
- font: inherit; color: var(--fg);
3168
- background: var(--bg-2);
3169
- border: 0;
3170
- box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3171
- inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
3172
- border-radius: var(--r-1);
3173
- padding: 0 var(--space-3);
3174
- transition: background var(--dur-snap) var(--ease),
3175
- border-color var(--dur-snap) var(--ease),
3176
- box-shadow var(--dur-snap) var(--ease);
3177
- }
3178
- /* Single-line bare controls snap to the field height; textarea keeps auto
3179
- height with symmetric padding (its content decides the box). */
3180
- input[type="text"]:where(:not(.input):not(.ds-search-input)),
3181
- input[type="email"]:where(:not(.input):not(.ds-search-input)),
3182
- input[type="password"]:where(:not(.input):not(.ds-search-input)),
3183
- input[type="number"]:where(:not(.input):not(.ds-search-input)),
3184
- input[type="search"]:where(:not(.input):not(.ds-search-input)),
3185
- input[type="url"]:where(:not(.input):not(.ds-search-input)),
3186
- input[type="tel"]:where(:not(.input):not(.ds-search-input)),
3187
- select:where(:not(.ds-select)) {
3188
- height: var(--field-height);
3189
- }
3190
- textarea:where(:not(.input)) { padding: var(--space-2) var(--space-3); min-height: calc(4 * 1.5em); }
3191
- input[type="text"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3192
- input[type="email"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3193
- input[type="password"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3194
- input[type="number"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3195
- input[type="search"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3196
- input[type="url"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3197
- input[type="tel"]:where(:not(.input):not(.ds-search-input)):focus-visible,
3198
- textarea:where(:not(.input)):focus-visible,
3199
- select:where(:not(.ds-select)):focus-visible {
3200
- outline: none;
3201
- background: var(--bg);
3202
- box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3203
- inset 0 calc(-1 * var(--bw-chunk)) 0 var(--accent);
3204
- }
3205
-
3206
- input[type="text"]::placeholder,
3207
- input[type="email"]::placeholder,
3208
- input[type="password"]::placeholder,
3209
- input[type="number"]::placeholder,
3210
- input[type="search"]::placeholder,
3211
- input[type="url"]::placeholder,
3212
- textarea::placeholder {
3213
- color: var(--fg-3);
3214
- }
3215
-
3216
- /* Long-form reading voice — quiet sans, generous line-height, capped to a
3217
- readable measure so a wide viewport never stretches body copy edge to
3218
- edge. Links use --accent-ink (never the bare fill, which is unreadable
3219
- as text). */
3220
- .prose {
3221
- max-width: var(--measure);
3222
- font-family: var(--ff-body); font-size: var(--fs-body); line-height: 1.6;
3223
- color: var(--fg);
3224
- }
3225
- .prose p { margin: 0 0 var(--space-4) 0; }
3226
- .prose p:last-child { margin-bottom: 0; }
3227
- .prose a { color: var(--accent-ink); text-decoration-color: var(--rule-strong); text-underline-offset: 0.15em; }
3228
- .prose a:hover { text-decoration-color: currentColor; }
3229
-
3230
- /* Rubber-stamp motif — a bordered, uppercase, letter-spaced badge with a
3231
- slight built-in rotation (variants may override via inline transform for
3232
- a stronger tilt). Max 1 per page per the component's own caption. */
3233
- .stamp {
3234
- display: inline-block;
3235
- font-family: var(--ff-mono); font-size: var(--fs-xs); font-weight: 700;
3236
- text-transform: uppercase; letter-spacing: 0.08em;
3237
- padding: var(--space-1-5, var(--space-2)) var(--space-3);
3238
- border: 2px solid var(--fg);
3239
- border-radius: var(--r-hair);
3240
- color: var(--fg);
3241
- transform: rotate(-2deg);
3242
- white-space: nowrap;
3243
- }
3244
- .stamp.ink { background: var(--ink); border-color: var(--ink); color: var(--paper); }
3245
- .stamp.acid { background: var(--accent); border-color: var(--accent-ink); color: var(--accent-ink); }
3246
- .stamp.purple { border-color: var(--purple-2); color: var(--purple-2); }
3247
- .stamp.mascot { border-color: var(--mascot); color: var(--mascot-deep); }
3248
-
3249
- /* Same rubber-stamp face, but a real interactive <button> — resets UA button
3250
- chrome so it isn't blue link text in a browser default box. */
3251
- .btn-stamp {
3252
- display: inline-block; cursor: pointer;
3253
- font-family: var(--ff-mono); font-size: var(--fs-xs); font-weight: 700;
3254
- text-transform: uppercase; letter-spacing: 0.08em;
3255
- padding: var(--space-1-5, var(--space-2)) var(--space-3);
3256
- border: 2px solid var(--fg);
3257
- border-radius: var(--r-hair);
3258
- background: transparent; color: var(--fg);
3259
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
3260
- }
3261
- .btn-stamp:hover { background: var(--panel-hover); }
3262
- .btn-stamp.acid { border-color: var(--accent-ink); color: var(--accent-ink); }
3263
- .btn-stamp.acid:hover { background: var(--accent-tint); }
3264
- .btn-stamp.purple { border-color: var(--purple-2); color: var(--purple-2); }
3265
- .btn-stamp.mascot { border-color: var(--mascot); color: var(--mascot-deep); }
3266
-
3267
- /* Structural hairlines — "no boxes, no cards, structure comes from rules."
3268
- Three weights: .rule (default separator), .rule-double (major section
3269
- break), .rule-dotted (optional/skippable). Applied to a bare <hr>, which
3270
- otherwise falls back to the browser's default inset groove line. */
3271
- hr.rule, hr.rule-double, hr.rule-dotted {
3272
- width: 100%; height: 0; border: 0;
3273
- }
3274
- hr.rule { border-top: 1px solid var(--rule); }
3275
- hr.rule-double { border-top: 3px double var(--rule-strong); }
3276
- hr.rule-dotted { border-top: 1px dotted var(--rule-strong); }
3277
-
3278
- /* Hairline-only text input — no box, no fill. A single bottom rule that
3279
- thickens to 2px on focus, matching the row/rule zero-border aesthetic
3280
- instead of a browser-default boxed field. */
3281
- .input {
3282
- display: block; width: 100%; box-sizing: border-box;
3283
- border: 0; border-bottom: 1px solid var(--rule-strong);
3284
- border-radius: var(--r-0);
3285
- background: transparent;
3286
- padding: var(--space-2) var(--space-1);
3287
- font: inherit; color: var(--fg);
3288
- }
3289
- .input::placeholder { color: var(--fg-3); }
3290
- .input:focus-visible {
3291
- outline: none;
3292
- border-bottom-width: 2px;
3293
- border-bottom-color: var(--accent-ink);
3294
- }
3295
-
3296
- /* -- Field controls: themed base for TextField / Select / SearchInput --
3297
- Root fix: previously only `transition` was set, so themed apps got
3298
- browser-default white boxes in dark mode and labels collided with inputs
3299
- because `.ds-field` had no layout. */
3300
- .ds-field {
3301
- display: flex;
3302
- flex-direction: column;
3303
- gap: var(--space-1, 6px);
3304
- align-items: stretch;
3305
- width: 100%;
3306
- }
3307
- .ds-field-label {
3308
- font-size: var(--fs-sm, 14px);
3309
- color: var(--fg-2);
3310
- line-height: 1.3;
3311
- }
3312
- .ds-field-hint {
3313
- font-size: var(--fs-tiny, 13px);
3314
- color: var(--fg-3);
3315
- line-height: 1.35;
3316
- }
3317
- .ds-field-count {
3318
- font-size: var(--fs-tiny, 13px);
3319
- color: var(--fg-3);
3320
- align-self: flex-end;
3321
- }
3322
-
3323
- .ds-field input,
3324
- .ds-field textarea,
3325
- .ds-field .ds-select,
3326
- .ds-search-input {
3327
- width: 100%;
3328
- box-sizing: border-box;
3329
- font: inherit;
3330
- color: var(--fg);
3331
- background: var(--bg-2);
3332
- border: 0;
3333
- /* Editorial field — tonal fill with a printed baseline rule that thickens to
3334
- the electric lead on focus, instead of a uniform hairline box. */
3335
- box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3336
- inset 0 calc(-1 * var(--bw-rule)) 0 var(--rule-strong);
3337
- /* Tighter corner than the old --r-2 (14px): a dense pro control, not a soft
3338
- consumer box. Single-line controls snap to --field-height with h-only
3339
- padding so they align pixel-exact with buttons on the same --ctl-md rung. */
3340
- border-radius: var(--r-1);
3341
- height: var(--field-height);
3342
- padding: 0 var(--space-3);
3343
- transition: box-shadow var(--dur-snap) var(--ease), background var(--dur-snap) var(--ease);
3344
- }
3345
- /* textarea is multi-line: height is auto (min-height instead), keep symmetric
3346
- vertical padding so text doesn't sit flush to the top edge. */
3347
- .ds-field textarea { height: auto; min-height: calc(4 * 1.5em); padding: var(--space-2) var(--space-3); resize: vertical; }
3348
-
3349
- /* Field size ladder (size prop on TextField/Select/SearchInput). md == base.
3350
- sm/lg retune height + h-padding + font off the --ctl-* ladder, matching the
3351
- button ladder so a form of mixed controls stays on one rhythm. */
3352
- .ds-field--sm input, .ds-field--sm .ds-select, .ds-field--sm .ds-search-input {
3353
- height: var(--ctl-sm); padding: 0 var(--space-2-5); font-size: var(--fs-tiny);
3354
- }
3355
- .ds-field--sm textarea { height: auto; padding: var(--space-1-75) var(--space-2-5); font-size: var(--fs-tiny); }
3356
- .ds-field--lg input, .ds-field--lg .ds-select, .ds-field--lg .ds-search-input {
3357
- height: var(--ctl-lg); padding: 0 var(--space-4); font-size: var(--fs-body);
3358
- }
3359
- .ds-field--lg textarea { height: auto; padding: var(--space-2-5) var(--space-4); font-size: var(--fs-body); }
3360
- .ds-search-input::placeholder { color: var(--fg-3); }
3361
- /* Only present when SearchInput is passed resultCount or has a value (clear
3362
- button) - transparent to any flex/grid layout the bare input previously
3363
- sat in directly. */
3364
- .ds-search-input-wrap { display: contents; }
3365
-
3366
- /* Visible clear (X) button for SearchInput - mirrors .ds-alert-dismiss's
3367
- ghost-icon treatment but sized to the kit's 44px touch-target floor
3368
- (app-shell.css already establishes this convention for .ds-file-check /
3369
- .ds-density-btn / .ds-upload-act etc). Positioned by the consumer's own
3370
- flex/grid context (ds-search-input-wrap is display:contents), so it simply
3371
- sits after the input in DOM order. */
3372
- .ds-search-clear {
3373
- display: inline-flex;
3374
- align-items: center; justify-content: center;
3375
- min-width: 44px; min-height: 44px;
3376
- flex-shrink: 0;
3377
- cursor: pointer;
3378
- color: var(--fg-3);
3379
- background: transparent;
3380
- border: 0;
3381
- padding: 0;
3382
- border-radius: var(--r-1);
3383
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
3384
- }
3385
- .ds-search-clear:hover { background: var(--bg-2); color: var(--fg); }
3386
- .ds-search-clear:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
3387
-
3388
- .ds-field input:focus-visible,
3389
- .ds-field textarea:focus-visible,
3390
- .ds-field .ds-select:focus-visible,
3391
- .ds-search-input:focus-visible {
3392
- outline: none;
3393
- background: var(--bg);
3394
- /* Focus thickens the baseline to the lead accent — the printed-rule tell. */
3395
- box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--rule),
3396
- inset 0 calc(-1 * var(--bw-chunk)) 0 var(--accent);
3397
- }
3398
- .ds-field input:disabled,
3399
- .ds-field textarea:disabled,
3400
- .ds-field .ds-select:disabled {
3401
- opacity: .55;
3402
- cursor: not-allowed;
3403
- }
3404
- .ds-field input[aria-invalid="true"],
3405
- .ds-field textarea[aria-invalid="true"] {
3406
- box-shadow: inset 0 0 0 var(--bw-hair, 1px) var(--flame);
3407
- }
3408
-
3409
- /* Clear button for text inputs */
3410
- input[type="text"]:not(:placeholder-shown) + .input-clear,
3411
- input[type="email"]:not(:placeholder-shown) + .input-clear,
3412
- input[type="password"]:not(:placeholder-shown) + .input-clear {
3413
- display: inline-flex;
3414
- }
3415
-
3416
- .input-clear {
3417
- display: none;
3418
- align-items: center; justify-content: center;
3419
- width: 32px; height: 32px;
3420
- cursor: pointer;
3421
- color: var(--fg-3);
3422
- background: transparent;
3423
- border: 0;
3424
- padding: 0;
3425
- margin-right: 8px;
3426
- border-radius: var(--r-1);
3427
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
3428
- }
3429
- .input-clear:hover {
3430
- background: var(--bg-2);
3431
- color: var(--fg);
3432
- }
3433
-
3434
- /* ------------------------------------------------------------
3435
- Accessibility Enhancements
3436
- -------------------------------------------------------------- */
3437
-
3438
- /* Base focus-visible: 2px ring + 2px offset clears dark bg-3 surfaces (WCAG 1.4.11) */
3439
- :focus-visible {
3440
- outline: var(--focus-w) solid var(--focus-color);
3441
- outline-offset: var(--focus-offset);
3442
- }
3443
-
3444
- /* Enhanced focus-visible for all interactive elements */
3445
- [role="button"]:focus-visible,
3446
- [role="link"]:focus-visible,
3447
- [role="tab"]:focus-visible,
3448
- .row:focus-visible,
3449
- .row[tabindex]:focus-visible {
3450
- outline: var(--focus-w) solid var(--focus-color);
3451
- outline-offset: var(--focus-offset);
3452
- }
3453
-
3454
- /* Skip to main content link */
3455
- .skip-to-main {
3456
- position: absolute; top: -40px; left: 0;
3457
- background: var(--fg); color: var(--bg);
3458
- padding: 8px 16px; border-radius: var(--r-1);
3459
- text-decoration: none; z-index: var(--z-overlay, 1000);
3460
- }
3461
- .skip-to-main:focus { top: 10px; }
3462
-
3463
- /* Reduced motion preferences — scoped to the DS surface so it doesn't reach
3464
- into and neutralize the host document's own motion. */
3465
- @media (prefers-reduced-motion: reduce) {
3466
- .ds-247420 *, .app * {
3467
- animation-duration: 0.01ms !important;
3468
- animation-iteration-count: 1 !important;
3469
- transition-duration: 0.01ms !important;
3470
- }
3471
- /* Kill the residual paint frame of looping skeleton/shimmer placeholders and
3472
- give reduced-motion users a steady placeholder fill instead. */
3473
- .skeleton, .ds-skeleton, .ds-session-row-skeleton .ds-skel {
3474
- animation: none !important;
3475
- background: var(--bg-2) !important;
3476
- }
3477
- }
3478
-
3479
- /* ------------------------------------------------------------
3480
- Performance
3481
- -------------------------------------------------------------- */
3482
-
3483
- /* Limit layout/style containment to self-contained components. `paint` is
3484
- intentionally omitted — it clips focus rings, tooltips and dropdowns that
3485
- emanate from a contained .row/.btn. */
3486
- .panel, .row {
3487
- contain: layout style;
3488
- }
3489
-
3490
- /* ------------------------------------------------------------
3491
- Mobile Touch Optimizations
3492
- -------------------------------------------------------------- */
3493
-
3494
- @media (hover: none) and (pointer: coarse) {
3495
- /* Mobile devices: larger touch targets, faster responses */
3496
- .btn, .btn-primary, .btn-ghost,
3497
- button, a.row, [role="button"],
3498
- .ds-icon-btn, .composer-btn {
3499
- min-height: 48px;
3500
- min-width: 48px;
3501
- padding: 12px 20px;
3502
- }
3503
- /* Icon buttons stay square — the padding bump above would distort them. */
3504
- .ds-icon-btn, .composer-btn { padding: 0; }
3505
-
3506
- /* Remove hover effects on touch devices (use active instead) */
3507
- .btn:hover, .btn-primary:hover, .btn-ghost:hover {
3508
- background-color: inherit;
3509
- }
3510
-
3511
- /* Momentum scrolling for iOS */
3512
- .app-side, .panel, .row {
3513
- -webkit-overflow-scrolling: touch;
3514
- }
3515
-
3516
- /* Long-press feedback for drag-enabled items */
3517
- [draggable="true"], .draggable {
3518
- -webkit-user-select: none;
3519
- -webkit-touch-callout: none;
3520
- }
3521
- }
3522
-
3523
- /* Touch floor also for coarse-pointer devices that report hover (hybrid
3524
- laptops/tablets with mice) — the block above requires hover:none too. */
3525
- @media (pointer: coarse) {
3526
- .ds-icon-btn, .ds-icon-btn-xs, .ds-icon-btn-sm, .ds-icon-btn-base {
3527
- min-width: 44px; min-height: 44px;
3528
- }
3529
- .btn, .btn-primary, .btn-ghost,
3530
- .app-topbar nav a, .app-side a { min-height: 44px; }
3531
- }
3532
-
3533
- /* ------------------------------------------------------------
3534
- Theme Transition Smoothness
3535
- -------------------------------------------------------------- */
3536
-
3537
- /* Scoped to the DS wrapper so the design system never reaches out and
3538
- animates the host document's html/body. */
3539
- .ds-247420, .app {
3540
- transition: background-color var(--dur-base) var(--ease),
3541
- color var(--dur-base) var(--ease);
3542
- }
3543
-
3544
- /* ------------------------------------------------------------
3545
- Empty State & Loading States
3546
- -------------------------------------------------------------- */
3547
-
3548
- /* Skeleton loading state */
3549
- .skeleton {
3550
- background: linear-gradient(
3551
- 90deg,
3552
- var(--bg-2) 0%,
3553
- var(--bg-3) 50%,
3554
- var(--bg-2) 100%
3555
- );
3556
- background-size: 200% 100%;
3557
- animation: skeleton-load 1.5s infinite;
3558
- border-radius: var(--r-2);
3559
- }
3560
-
3561
- @keyframes skeleton-load {
3562
- 0% { background-position: 200% 0; }
3563
- 100% { background-position: -200% 0; }
3564
- }
3565
-
3566
- .skeleton-line { height: 12px; margin-bottom: 12px; }
3567
- .skeleton-title { height: 24px; margin-bottom: 16px; width: 60%; }
3568
-
3569
- /* ------------------------------------------------------------
3570
- Toast Notification Styles
3571
- -------------------------------------------------------------- */
3572
-
3573
- .toast {
3574
- position: fixed;
3575
- bottom: 24px; right: 24px;
3576
- background: var(--bg-2);
3577
- border: 1px solid var(--rule);
3578
- border-radius: var(--r-2);
3579
- padding: var(--space-3) var(--space-4);
3580
- max-width: 320px;
3581
- box-shadow: var(--shadow-overlay);
3582
- z-index: calc(var(--z-overlay, 1000) + 10);
3583
- }
3584
-
3585
- @keyframes toast-slide-in {
3586
- from {
3587
- opacity: 0;
3588
- transform: translateX(400px);
3589
- }
3590
- to {
3591
- opacity: 1;
3592
- transform: translateX(0);
3593
- }
3594
- }
3595
- @media (prefers-reduced-motion: no-preference) {
3596
- .toast { animation: toast-slide-in 0.3s ease-out; }
3597
- }
3598
-
3599
- .toast.error { border-color: var(--warn); }
3600
- .toast.success { border-color: var(--green-2); }
3601
- /* --sun measures well under 3:1 against every app background (1.1-1.5:1) -
3602
- a border effectively invisible in light theme; --amber is the tuned sibling
3603
- warning tone already used for this exact severity elsewhere. */
3604
- .toast.warning { border-color: var(--amber); }
3605
-
3606
- @media (max-width: 480px) {
3607
- .toast {
3608
- bottom: 16px; right: 16px; left: 16px;
3609
- max-width: none;
3610
- }
3611
- }
3612
-
3613
- /* ============================================================
3614
- Spinner — animated loading indicator
3615
- ============================================================ */
3616
- .ds-spinner {
3617
- display: inline-flex;
3618
- gap: 4px;
3619
- align-items: center;
3620
- height: 16px;
3621
- }
3622
- .ds-spinner span {
3623
- width: 4px;
3624
- height: 4px;
3625
- border-radius: 50%;
3626
- background: var(--accent);
3627
- }
3628
- @media (prefers-reduced-motion: no-preference) {
3629
- .ds-spinner span { animation: ds-bounce 1.4s infinite ease-in-out; }
3630
- .ds-spinner span:nth-child(1) { animation-delay: 0s; }
3631
- .ds-spinner span:nth-child(2) { animation-delay: 0.18s; }
3632
- .ds-spinner span:nth-child(3) { animation-delay: 0.36s; }
3633
- }
3634
-
3635
- .ds-spinner-lg { height: 24px; gap: 6px; }
3636
- .ds-spinner-lg span { width: 6px; height: 6px; }
3637
-
3638
- .ds-spinner-sm { height: 12px; gap: 2px; }
3639
- .ds-spinner-sm span { width: 2px; height: 2px; }
3640
-
3641
- .ds-spinner-xs { height: 8px; gap: 1px; }
3642
- .ds-spinner-xs span { width: 1.5px; height: 1.5px; }
3643
-
3644
- .ds-spinner-xl { height: 32px; gap: 8px; }
3645
- .ds-spinner-xl span { width: 8px; height: 8px; }
3646
-
3647
- @keyframes ds-bounce {
3648
- 0%, 80%, 100% { transform: translateY(0); }
3649
- 40% { transform: translateY(-8px); }
3650
- }
3651
-
3652
- /* ============================================================
3653
- Skeleton — animated loading placeholder
3654
- ============================================================ */
3655
- .ds-skeleton-group {
3656
- display: flex;
3657
- flex-direction: column;
3658
- gap: var(--space-2);
3659
- }
3660
- .ds-skeleton {
3661
- background: linear-gradient(90deg, var(--bg-2) 25%, var(--bg-3) 50%, var(--bg-2) 75%);
3662
- background-size: 200% 100%;
3663
- animation: ds-shimmer 1.5s infinite;
3664
- border-radius: var(--r-1);
3665
- }
3666
-
3667
- @keyframes ds-shimmer {
3668
- 0% { background-position: 200% 0; }
3669
- 100% { background-position: -200% 0; }
3670
- }
3671
-
3672
- /* ============================================================
3673
- Alert — contextual message banner
3674
- ============================================================ */
3675
- .ds-alert {
3676
- display: flex;
3677
- gap: var(--space-3);
3678
- align-items: flex-start;
3679
- padding: var(--space-3) var(--space-4);
3680
- border-radius: var(--r-1);
3681
- background: var(--panel-1);
3682
- border: 1px solid var(--rule-strong);
3683
- color: var(--fg);
3684
- }
3685
-
3686
- .ds-alert-info { --alert-tone: var(--sky); }
3687
- .ds-alert-success { --alert-tone: var(--green); }
3688
- .ds-alert-warn { --alert-tone: var(--amber); }
3689
- .ds-alert-error { --alert-tone: var(--warn); }
3690
-
3691
- .ds-alert-icon {
3692
- flex-shrink: 0;
3693
- color: var(--alert-tone);
3694
- font-weight: 600;
3695
- min-width: 20px;
3696
- text-align: center;
3697
- }
3698
-
3699
- .ds-alert-content {
3700
- flex: 1;
3701
- display: flex;
3702
- flex-direction: column;
3703
- gap: 4px;
3704
- }
3705
-
3706
- .ds-alert-title {
3707
- font-weight: 500;
3708
- font-size: var(--fs-sm);
3709
- }
3710
-
3711
- .ds-alert-message {
3712
- font-size: var(--fs-sm);
3713
- color: var(--fg-2);
3714
- }
3715
- .ds-alert-retry { margin-top: var(--space-2); }
3716
-
3717
- .ds-alert-dismiss {
3718
- flex-shrink: 0;
3719
- background: none;
3720
- border: none;
3721
- cursor: pointer;
3722
- color: var(--fg-3);
3723
- padding: 0;
3724
- font-size: 14px;
3725
- line-height: 1;
3726
- min-width: 20px;
3727
- text-align: center;
3728
- transition: color var(--dur-snap) var(--ease);
3729
- }
3730
- .ds-alert-dismiss:hover { color: var(--fg); }
3731
-
3732
- /* ============================================================
3733
- Responsive Perfection — Drawer rail, ultrawide cap, mobile compact
3734
- ============================================================ */
3735
-
3736
- /* Tablet + Mobile: side rail becomes off-canvas drawer (≤900px) */
3737
- /* Sidebar drawer collapse keys on the SHELL width (@container on .app, which is
3738
- container-type:inline-size) — NOT the viewport. Full-page (freddie) the shell
3739
- == viewport so this is identical to the old @media(max-width:900px); embedded
3740
- in a thebird WM window the drawer + hamburger appear when the WINDOW is narrow
3741
- even on a wide desktop. Drawer/scrim/toggle are absolute (anchored to .app), so
3742
- they overlay the window, not the page. */
3743
- @container (max-width: 900px) {
3744
- .app-body,
3745
- .app-body.no-side { grid-template-columns: 1fr !important; }
3746
- .app-side-shell {
3747
- position: absolute;
3748
- /* Full-height overlay drawer: the merged .app-chrome wraps and its real
3749
- height drifts from --app-topbar-h, so anchoring below it misplaces the
3750
- drawer. Overlay the chrome instead (z above --z-header), like ws drawers. */
3751
- left: 0; top: 0;
3752
- height: 100%;
3753
- width: 280px; max-width: 80%;
3754
- transform: translateX(-100%);
3755
- transition: transform var(--dur-base, 240ms) var(--ease, ease);
3756
- /* Above the sticky chrome (z-header): scrim > chrome > content budget. */
3757
- z-index: calc(var(--z-header, 100) + 2);
3758
- background: var(--bg);
3759
- border-right: 1px solid color-mix(in oklab, var(--fg) 8%, transparent);
3760
- overflow-y: auto;
3761
- overscroll-behavior: contain;
3762
- }
3763
- /* Lock the content scroller while the drawer is open so touch pans don't
3764
- scroll the page behind the scrim. */
3765
- .app-body.side-open .app-main { overflow: hidden; }
3766
- .app-side-shell.open,
3767
- .app-side.open,
3768
- .app-body.side-open .app-side-shell { transform: translateX(0); }
3769
- .app-side {
3770
- flex-direction: column !important;
3771
- flex-wrap: nowrap !important;
3772
- gap: var(--space-2);
3773
- padding: var(--space-3);
3774
- font-size: var(--fs-sm);
3775
- }
3776
- .app-side a {
3777
- flex: 0 0 auto; min-width: 0;
3778
- grid-template-columns: 18px 1fr auto;
3779
- padding: 10px 12px;
3780
- }
3781
- .app-side .group { width: 100%; }
3782
- /* Keep the crumb on mobile — apps mount contextual controls (model picker,
3783
- actions, live status) in crumb-right, so hiding it strands them. Collapse
3784
- the breadcrumb trail/leaf instead and let the right cluster wrap. */
3785
- .app-crumb { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 8px 12px; }
3786
- .app-crumb > .sep, .app-crumb > span:not(.crumb-right):not(.leaf) { display: none; }
3787
- .app-crumb .crumb-right { margin-left: 0; }
3788
- .chat-head { padding: var(--space-2) var(--space-3) var(--space-2); margin-bottom: var(--space-2); }
3789
- .app-main { padding-left: var(--space-3); padding-right: var(--space-3); }
3790
- }
3791
-
3792
- /* Drawer scrim — absolute so it covers THIS shell, shown when drawer open and
3793
- the shell is narrow (@container on .app). */
3794
- .app-side-scrim {
3795
- display: none;
3796
- position: absolute; inset: 0;
3797
- background: color-mix(in oklab, var(--bg) 60%, transparent);
3798
- /* Above the sticky chrome so nav behind an open drawer is not clickable. */
3799
- z-index: calc(var(--z-header, 100) + 1);
3800
- }
3801
- @container (max-width: 900px) {
3802
- .app-body.side-open .app-side-scrim { display: block; }
3803
- }
3804
-
3805
- /* Nav toggle (hamburger) — hidden when the shell is wide, shown when the shell
3806
- itself is narrow (@container on .app). Absolute so it sits in THIS shell's
3807
- topbar, not pinned to the viewport corner. */
3808
- .app-side-toggle {
3809
- display: none;
3810
- position: absolute; top: calc((var(--app-topbar-h) - 44px) / 2); left: 10px;
3811
- /* Above the sticky topbar/chrome (z-header) and the drawer/scrim so the
3812
- translucent blurred chrome never paints over or intercepts the toggle. */
3813
- z-index: calc(var(--z-header, 100) + 3);
3814
- /* 44x44 minimum hit area (WCAG 2.5.5 / Apple HIG) - 36px is below the
3815
- reliable-tap threshold on touch. */
3816
- width: 44px; height: 44px;
3817
- align-items: center; justify-content: center;
3818
- font-size: 18px; line-height: 1;
3819
- background: var(--bg-2); color: var(--fg);
3820
- border: 1px solid var(--rule, color-mix(in oklab, var(--fg) 12%, transparent));
3821
- border-radius: var(--r-1, 6px);
3822
- cursor: pointer;
3823
- }
3824
- .app-side-toggle:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
3825
- @container (max-width: 900px) {
3826
- .app-side-toggle { display: inline-flex; }
3827
- .app-topbar .brand { margin-left: 44px; }
3828
- /* Merged-chrome layout (.app-chrome > .app-crumb) puts the breadcrumb, not
3829
- the topbar brand, at the left edge where the absolute-positioned toggle
3830
- sits — without this the crumb text renders directly under the button. */
3831
- .app-chrome > .app-crumb { margin-left: 44px; }
3832
- }
3833
-
3834
- /* Desktop: the app shell is exactly viewport-height and contains its own
3835
- scroll, so only .app-main (and the side rail) scroll — never the page too.
3836
- Pinning .app to 100dvh + overflow:hidden is what prevents the double
3837
- scrollbar that a min-height:100vh shell + inner overflow:auto produces.
3838
- .app-body fills the gap between topbar/crumb and status via flex:1 rather
3839
- than a hand-computed calc() that drifts from the real chrome heights. */
3840
- @media (min-width: 901px) {
3841
- .app { height: 100dvh; min-height: 0; overflow: hidden; }
3842
- /* Embedded shell (freddie WM window) fills its container, not the viewport. */
3843
- .fd-root .app { height: 100%; overflow: visible; }
3844
- .app-body { min-height: 0; flex: 1 1 auto; }
3845
- .app-side-shell { overflow-y: auto; min-height: 0; }
3846
- .app-main { overflow-y: auto; min-height: 0; }
3847
- }
3848
-
3849
- /* Mobile (≤480) status bar compact; hide tail item */
3850
- @media (max-width: 480px) {
3851
- .app-status {
3852
- font-size: var(--fs-micro);
3853
- padding: 4px var(--space-2);
3854
- gap: var(--space-2);
3855
- }
3856
- .app-status > :last-child:not(:only-child) { display: none; }
3857
- }
3858
-
3859
- /* Tiny phone (≤360) topbar compact */
3860
- @media (max-width: 360px) {
3861
- .app-topbar {
3862
- padding: 8px var(--space-2);
3863
- font-size: var(--fs-xs);
3864
- gap: var(--space-2);
3865
- }
3866
- .app-topbar nav,
3867
- .app-topbar > :last-child { display: none; }
3868
- .brand { font-size: var(--fs-tiny); }
3869
- /* Slightly narrower drawer on tiny phones; max-width:80% remains the guard. */
3870
- .app-side-shell { width: 260px; }
3871
- }
3872
-
3873
- /* ----------------------------------------------------------------------------
3874
- WorkspaceShell — Claude-Desktop / cowork three-column app shell.
3875
- Columns: [ws-rail] [ws-sessions?] [ws-content] [ws-pane?]
3876
- Collapse via .ws-rail-collapsed / .ws-pane-collapsed classes on .ws-shell.
3877
- All sizing/colour from kit tokens; no hardcoded palette.
3878
- ---------------------------------------------------------------------------- */
3879
- .ws-shell {
3880
- /* Fluid chrome columns: shrink on small laptops, breathe on ultrawide, so the
3881
- ~848px of fixed chrome no longer eats a constant slab. An inline --ws-*-w
3882
- written by a resize drag overrides these (inline wins), pinning the width. */
3883
- --ws-rail-w: clamp(200px, 16vw, 260px);
3884
- --ws-rail-w-collapsed: 60px;
3885
- --ws-sessions-w: clamp(248px, 22vw, 360px);
3886
- --ws-pane-w: clamp(288px, 24vw, 420px);
3887
- display: grid;
3888
- grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) 1fr var(--ws-pane-w);
3889
- grid-template-areas: "rail sessions content pane";
3890
- height: 100%;
3891
- min-height: 0;
3892
- background: var(--bg);
3893
- color: var(--fg);
3894
- }
3895
- .ws-shell.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr var(--ws-pane-w); grid-template-areas: "rail content pane"; }
3896
- .ws-shell.ws-no-pane.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr; grid-template-areas: "rail content"; }
3897
- .ws-shell.ws-no-pane:not(.ws-no-sessions) { grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) 1fr; grid-template-areas: "rail sessions content"; }
3898
- /* G1 stable frame: when a tab has no pane but asks to keep the column geometry
3899
- (ws-no-pane + ws-pane-collapsed), KEEP the 4-track grid with the pane track
3900
- at width 0 instead of collapsing to a 3-track layout - so the shell does not
3901
- re-flow its column count on tab switch. The pane track stays present (0px),
3902
- so chat/history/files/live/settings share one geometry. */
3903
- .ws-shell.ws-no-pane.ws-pane-collapsed:not(.ws-no-sessions) { grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) 1fr var(--ws-pane-w); grid-template-areas: "rail sessions content pane"; }
3904
- .ws-shell.ws-no-pane.ws-pane-collapsed.ws-no-sessions { grid-template-columns: var(--ws-rail-w) 1fr var(--ws-pane-w); grid-template-areas: "rail content pane"; }
3905
- .ws-shell.ws-rail-collapsed { --ws-rail-w: var(--ws-rail-w-collapsed); }
3906
- .ws-shell.ws-pane-collapsed { --ws-pane-w: 0px; }
3907
- /* Sessions column desktop collapse (parity with the pane collapse), reclaiming
3908
- its width for a full-width thread/grid. */
3909
- .ws-shell.ws-sessions-collapsed { --ws-sessions-w: 0px; }
3910
- .ws-sessions-collapsed .ws-sessions { overflow: hidden; border-right: none; }
3911
- .ws-sessions-collapsed .ws-sessions > * { display: none; }
3912
- /* Column resize handles: a thin keyboard-accessible separator pinned to the
3913
- right edge of each chrome track; drag (or ArrowLeft/Right) writes a clamped
3914
- inline --ws-*-w. */
3915
- .ws-resizer { grid-row: 1 / -1; align-self: stretch; justify-self: end; width: 8px; margin-right: -4px; z-index: 5; cursor: col-resize; background: transparent; border: none; padding: 0; touch-action: none; }
3916
- .ws-resizer::after { content: ""; display: block; width: 2px; height: 100%; margin: 0 auto; background: transparent; transition: background var(--dur-snap) var(--ease); }
3917
- .ws-resizer:hover::after, .ws-resizer:focus-visible::after { background: var(--accent); }
3918
- .ws-resizer:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; }
3919
- .ws-resizer.ws-resizer-rail { grid-column: 1; }
3920
- .ws-resizer.ws-resizer-sessions { grid-column: 2; }
3921
- .ws-resizer.ws-resizer-pane { grid-column: 3; justify-self: end; }
3922
- /* A resizer must vanish once its TRACK is removed by the staging media blocks,
3923
- else dragging it writes a --ws-*-w var that no longer affects the now-fixed
3924
- drawer (a dead handle). Mirror the 1480 / 1100 / 900 staging exactly. */
3925
- @media (max-width: 1480px) { .ws-resizer.ws-resizer-pane { display: none; } }
3926
- @media (max-width: 1100px) { .ws-resizer.ws-resizer-sessions { display: none; } }
3927
- @media (max-width: 900px) { .ws-resizer { display: none; } }
3928
- /* A resizer must also vanish once its TRACK is desktop-collapsed: a 0px (pane/
3929
- sessions) or 60px icon-only (rail) track has no width to drag, and a drag would
3930
- write an INLINE --ws-*-w that overrides the collapse var (inline wins), fighting
3931
- the collapse state. Mirror the breakpoint guards for the collapse classes. */
3932
- .ws-rail-collapsed .ws-resizer-rail,
3933
- .ws-sessions-collapsed .ws-resizer-sessions,
3934
- .ws-pane-collapsed .ws-resizer-pane { display: none; }
3935
- /* Coarse-pointer hit target for hybrid touch laptops above the drawer breakpoint
3936
- (the 8px sliver is un-grabbable by touch). */
3937
- @media (pointer: coarse) { .ws-resizer { width: 16px; margin-right: -8px; } }
3938
- /* Ease the rail/pane collapse. Track COUNT stays stable on collapse (only a
3939
- width var flips), so grid-template-columns is animatable; reduced-motion
3940
- drops it to an instant swap. */
3941
- @media (prefers-reduced-motion: no-preference) {
3942
- .ws-shell { transition: grid-template-columns var(--dur-base) var(--ease); }
3943
- }
3944
- /* Ultrawide: cap the shell so the 1fr content track doesn't stretch to
3945
- unreadable measures while the fixed side columns pin to their clamps. */
3946
- @media (min-width: 1920px) {
3947
- .ws-shell { max-width: 1920px; margin-inline: auto; }
3948
- }
3949
-
3950
- /* Rail (left nav) */
3951
- .ws-rail {
3952
- grid-area: rail;
3953
- position: relative;
3954
- display: flex;
3955
- flex-direction: column;
3956
- min-height: 0;
3957
- background: var(--bg-2);
3958
- border-right: var(--bw-hair) solid var(--bg-3);
3959
- overflow: hidden;
3960
- transition: width var(--dur-base) var(--ease);
3961
- }
3962
- .ws-rail-toggle {
3963
- position: absolute; top: var(--space-2); right: var(--space-2);
3964
- width: 36px; height: 36px;
3965
- display: inline-flex; align-items: center; justify-content: center;
3966
- background: transparent; border: none; color: var(--fg-2);
3967
- border-radius: var(--r-1); cursor: pointer;
3968
- }
3969
- .ws-rail-toggle:hover { background: var(--bg-3); color: var(--fg); }
3970
- .ws-rail-toggle:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
3971
- .ws-rail-inner { display: flex; flex-direction: column; min-height: 0; flex: 1; padding: var(--space-3) var(--space-2); gap: var(--space-2); }
3972
- .ws-rail-head { display: flex; align-items: center; min-height: 36px; padding: 0 var(--space-2); }
3973
- .ws-rail-brand { font-family: var(--ff-body); font-weight: 600; font-size: var(--fs-body); color: var(--fg); white-space: nowrap; overflow: hidden; }
3974
- .ws-rail-action {
3975
- display: flex; align-items: center; gap: var(--space-2);
3976
- padding: var(--space-2) var(--space-3); margin: var(--space-1) 0;
3977
- background: var(--accent); color: var(--accent-fg, var(--bg));
3978
- border: none; border-radius: var(--r-1); cursor: pointer;
3979
- font-family: var(--ff-body); font-size: var(--fs-sm); font-weight: 600;
3980
- min-height: 40px; white-space: nowrap; overflow: hidden;
3981
- }
3982
- .ws-rail-action:hover { filter: brightness(0.92); }
3983
- .ws-rail-action:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
3984
- .ws-rail-nav { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
3985
- .ws-rail-item {
3986
- display: flex; align-items: center; gap: var(--space-2);
3987
- width: 100%; padding: var(--space-2); min-height: 44px;
3988
- background: transparent; border: none; color: var(--fg-2);
3989
- border-radius: var(--r-1); cursor: pointer; text-align: left;
3990
- font-family: var(--ff-body); font-size: var(--fs-sm); white-space: nowrap; overflow: hidden;
3991
- }
3992
- .ws-rail-item:hover { background: var(--bg-3); color: var(--fg); }
3993
- .ws-rail-item:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; }
3994
- .ws-rail-item.active { background: var(--accent-tint); color: var(--fg); box-shadow: inset 2px 0 0 var(--accent); }
3995
- .ws-rail-item-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }
3996
- .ws-rail-item-count { font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3); }
3997
- .ws-rail-item-flag { width: 6px; height: 6px; border-radius: 50%; flex: none; }
3998
- .ws-rail-item-flag.tone-flame { background: var(--flame); box-shadow: 0 0 0 2px color-mix(in oklab, var(--flame) 38%, transparent); }
3999
- .ws-rail-foot { margin-top: auto; padding-top: var(--space-2); border-top: var(--bw-hair) solid var(--bg-3); }
4000
-
4001
- /* Collapsed rail: icon-only — hide text labels, center icons, drop toggle's far-right pin */
4002
- .ws-rail-collapsed .ws-rail-brand,
4003
- .ws-rail-collapsed .ws-rail-action-label,
4004
- .ws-rail-collapsed .ws-rail-item-label,
4005
- .ws-rail-collapsed .ws-rail-item-count { display: none; }
4006
- .ws-rail-collapsed .ws-rail-item,
4007
- .ws-rail-collapsed .ws-rail-action { justify-content: center; gap: 0; }
4008
- .ws-rail-collapsed .ws-rail-head { justify-content: center; }
4009
-
4010
- /* Sessions column */
4011
- .ws-sessions {
4012
- grid-area: sessions;
4013
- min-height: 0; display: flex; flex-direction: column;
4014
- /* Rail and pane both sit on --bg-2 (a recessed plane framing the elevated
4015
- --bg content column); sessions used flat --bg instead, so the
4016
- conversation list and the chat thread read as the SAME surface with only
4017
- a 1px hairline between them - the flattest, most "one big beige box"
4018
- tell in the shell. --bg-2 restores the three-plane depth (rail/sessions
4019
- recessed either side, content elevated in the middle) the design
4020
- already establishes everywhere else. */
4021
- background: var(--bg-2); border-right: var(--bw-hair) solid var(--bg-3);
4022
- overflow: hidden;
4023
- }
4024
- .ws-no-sessions .ws-sessions { display: none; }
4025
-
4026
- /* Content column */
4027
- .ws-content { grid-area: content; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
4028
- /* Crumb band is the top chrome of the content column: a stable height so the
4029
- top edge aligns with the rail head, and a left gutter matching .ws-main so
4030
- the trail text is not flush against the rail border. */
4031
- .ws-crumb { flex: 0 0 auto; min-height: calc(var(--app-crumb-h) + var(--space-3)); padding: 0 var(--space-5); display: flex; align-items: center; gap: var(--space-1); border-bottom: var(--bw-hair) solid var(--bg-3); }
4032
- .ws-crumb-main { flex: 1 1 auto; min-width: 0; }
4033
- /* Content column gutter. Claude-Code-class calm: file grid / dashboard / event
4034
- list / history get a generous, consistent inner gutter instead of butting
4035
- against the 1px column borders. The chat tab opts out via .ws-main--flush
4036
- because the chat thread carries its own --measure gutter. */
4037
- .ws-main { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; padding: var(--space-4) var(--space-5); }
4038
- .ws-main.ws-main--flush { padding: 0; }
4039
- .ws-main:focus { outline: none; }
4040
-
4041
- /* Right context pane */
4042
- .ws-pane {
4043
- grid-area: pane; position: relative;
4044
- min-height: 0; display: flex; flex-direction: column;
4045
- background: var(--bg-2); border-left: var(--bw-hair) solid var(--bg-3);
4046
- overflow: hidden; transition: width var(--dur-base) var(--ease);
4047
- overscroll-behavior: contain;
4048
- }
4049
- .ws-pane-collapsed .ws-pane { width: 0; border-left: none; }
4050
- .ws-pane-collapsed .ws-pane > * { display: none; }
4051
- @media (pointer: coarse) {
4052
- .ws-rail-toggle { width: 44px; height: 44px; }
4053
- .ws-drawer-toggle { width: 44px; height: 44px; }
4054
- .ws-desktop-toggle { width: 44px; height: 44px; }
4055
- }
4056
-
4057
- /* Drawer toggles and the scrim are hidden by default and revealed by the staged
4058
- media blocks BELOW - these base rules must precede those blocks in source
4059
- order: at equal specificity the later rule wins regardless of media, so a
4060
- trailing display:none silently kills every drawer affordance (shipped bug:
4061
- the mobile drawers were dead because these lines used to sit last). */
4062
- .ws-drawer-toggle { display: none; align-items: center; justify-content: center; width: 40px; height: 40px; border: none; background: none; color: var(--fg-2); cursor: pointer; border-radius: var(--r-1); }
4063
- /* Desktop-only chrome toggle (sessions collapse): inverse of the mobile drawer-toggle. */
4064
- .ws-desktop-toggle { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; background: none; color: var(--fg-2); cursor: pointer; border-radius: var(--r-1); }
4065
- .ws-desktop-toggle:hover { background: var(--bg-2); color: var(--fg); }
4066
- .ws-desktop-toggle:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4067
- @media (max-width: 900px) { .ws-desktop-toggle { display: none; } }
4068
- /* The pane collapse toggle is meaningless once the pane TRACK is dropped (the
4069
- pane becomes a mobile overlay drawer at <=1480px, reached via its own
4070
- drawer-toggle), so hide this crumb control past that breakpoint. */
4071
- @media (max-width: 1480px) { .ws-pane-toggle { display: none; } }
4072
- @media (max-width: 480px) { .ds-247420 .ws-crumb { padding-left: var(--space-2); padding-right: var(--space-2); } }
4073
- .ws-scrim { display: none; }
4074
-
4075
- /* Responsive: the columns yield to the CONTENT in stages - the main column is
4076
- the product, so the right pane gives way first, the sessions list second,
4077
- and only below 900px does the rail itself drop to a fixed icon strip. Yielded
4078
- columns stay reachable as overlay drawers (.ws-pane-open / .ws-sessions-open,
4079
- toggled from the crumb bar) over a shared fade scrim. Before this staging the
4080
- 4-track grid survived down to 901px and crushed the chat thread to ~245px
4081
- while the 320px context pane kept its full width. */
4082
-
4083
- /* Stage 1 (<=1480px): drop the pane TRACK; the context pane becomes a right
4084
- overlay drawer. */
4085
- @media (max-width: 1480px) {
4086
- .ws-shell,
4087
- .ws-shell.ws-no-pane,
4088
- .ws-shell.ws-pane-collapsed,
4089
- .ws-shell.ws-no-pane.ws-pane-collapsed:not(.ws-no-sessions) {
4090
- grid-template-columns: var(--ws-rail-w) var(--ws-sessions-w) minmax(0, 1fr);
4091
- grid-template-areas: "rail sessions content";
4092
- }
4093
- .ws-shell.ws-no-sessions,
4094
- .ws-shell.ws-no-pane.ws-no-sessions,
4095
- .ws-shell.ws-no-pane.ws-pane-collapsed.ws-no-sessions {
4096
- grid-template-columns: var(--ws-rail-w) minmax(0, 1fr);
4097
- grid-template-areas: "rail content";
4098
- }
4099
- /* Drawer width is a literal: --ws-pane-w is 0px while pane-collapsed, and a
4100
- desktop-collapsed pane must still open as a full drawer. */
4101
- .ws-pane {
4102
- position: fixed; inset: 0 0 0 auto;
4103
- width: min(340px, 85vw);
4104
- z-index: 42; transform: translateX(110%);
4105
- transition: transform var(--dur-base) var(--ease);
4106
- }
4107
- .ws-shell.ws-pane-open .ws-pane { transform: translateX(0); width: min(340px, 85vw); }
4108
- .ws-shell.ws-pane-open.ws-pane-collapsed .ws-pane { border-left: var(--bw-hair) solid var(--bg-3); }
4109
- .ws-shell.ws-pane-open.ws-pane-collapsed .ws-pane > * { display: revert; }
4110
- .ws-pane-drawer-toggle { display: inline-flex; }
4111
- /* Scrim is always present from this stage down so it FADES with the drawer
4112
- instead of hard-appearing; pointer-events gate keeps it inert while closed. */
4113
- .ws-scrim { display: block; opacity: 0; pointer-events: none; position: fixed; inset: 0; z-index: 41; background: color-mix(in srgb, var(--fg) 38%, transparent); transition: opacity var(--dur-base) var(--ease); }
4114
- .ws-shell.ws-sessions-open .ws-scrim, .ws-shell.ws-pane-open .ws-scrim { opacity: 1; pointer-events: auto; }
4115
- }
4116
-
4117
- /* Stage 2 (<=1100px): drop the sessions TRACK; the conversation list becomes a
4118
- left overlay drawer. */
4119
- @media (max-width: 1100px) {
4120
- .ws-shell,
4121
- .ws-shell.ws-no-pane,
4122
- .ws-shell.ws-pane-collapsed,
4123
- .ws-shell.ws-no-sessions,
4124
- .ws-shell.ws-no-pane.ws-no-sessions,
4125
- .ws-shell.ws-no-pane.ws-pane-collapsed:not(.ws-no-sessions),
4126
- .ws-shell.ws-no-pane.ws-pane-collapsed.ws-no-sessions {
4127
- grid-template-columns: var(--ws-rail-w) minmax(0, 1fr);
4128
- grid-template-areas: "rail content";
4129
- }
4130
- .ws-sessions {
4131
- position: fixed; inset: 0 auto 0 0;
4132
- /* Immune to the desktop collapse var (--ws-sessions-w:0px persisted from
4133
- a desktop session would open an invisible 0-width drawer). */
4134
- width: min(max(var(--ws-sessions-w), 280px), 80vw);
4135
- z-index: 42; transform: translateX(-110%);
4136
- transition: transform var(--dur-base) var(--ease);
4137
- border-right: var(--bw-hair) solid var(--bg-3);
4138
- overscroll-behavior: contain;
4139
- }
4140
- .ws-shell.ws-sessions-open .ws-sessions { transform: translateX(0); }
4141
- /* Drawer mode ignores the desktop collapsed state: children stay rendered
4142
- and the border returns, so a persisted collapse can't blank the drawer. */
4143
- .ws-shell.ws-sessions-collapsed .ws-sessions > * { display: revert; }
4144
- .ws-shell.ws-sessions-collapsed .ws-sessions { border-right: var(--bw-hair) solid var(--bg-3); }
4145
- .ws-sessions-drawer-toggle { display: inline-flex; }
4146
- }
4147
-
4148
- /* Stage 3 (<=900px): single content column; the rail drops to a fixed icon-only
4149
- strip. Labels hide UNCONDITIONALLY here - a 60px rail showing clipped label
4150
- text ('age', 'Ne') is the worst of both worlds. */
4151
- @media (max-width: 900px) {
4152
- .ws-shell,
4153
- .ws-shell.ws-no-sessions,
4154
- .ws-shell.ws-no-pane,
4155
- .ws-shell.ws-pane-collapsed,
4156
- .ws-shell.ws-no-pane.ws-no-sessions,
4157
- .ws-shell.ws-no-pane.ws-pane-collapsed:not(.ws-no-sessions),
4158
- .ws-shell.ws-no-pane.ws-pane-collapsed.ws-no-sessions {
4159
- grid-template-columns: minmax(0, 1fr);
4160
- grid-template-areas: "content";
4161
- }
4162
- .ws-rail { position: fixed; inset: 0 auto 0 0; width: var(--ws-rail-w-collapsed); z-index: 40; }
4163
- .ws-rail .ws-rail-brand,
4164
- .ws-rail .ws-rail-action-label,
4165
- .ws-rail .ws-rail-item-label,
4166
- .ws-rail .ws-rail-item-count,
4167
- .ws-rail .ds-theme-toggle-label { display: none; }
4168
- .ws-rail .ws-rail-item, .ws-rail .ws-rail-action { justify-content: center; gap: 0; }
4169
- .ws-rail .ws-rail-head { justify-content: center; }
4170
- /* Expand/collapse is meaningless on the icon strip. */
4171
- .ws-rail-toggle { display: none; }
4172
- .ws-rail-foot { display: flex; justify-content: center; }
4173
- /* The 1100px rule's closed-state translateX(-110%) was sized for left:0;
4174
- once left shifts to the collapsed rail width, -110% no longer clears the
4175
- viewport and leaves a sliver of the "closed" drawer visible on screen. */
4176
- .ws-sessions { inset: 0 auto 0 var(--ws-rail-w-collapsed); transform: translateX(calc(-100% - var(--ws-rail-w-collapsed))); }
4177
- .ws-shell.ws-sessions-open .ws-sessions { transform: translateX(0); }
4178
- .ws-content { grid-area: content; padding-left: var(--ws-rail-w-collapsed); }
4179
- /* The Crumb's narrow collapse elsewhere keys on the .app @container, which
4180
- does not exist inside WorkspaceShell - mirror it here so the crumb bar
4181
- cannot overflow a narrow viewport (trail hides, leaf ellipsizes, the
4182
- right cluster wraps under instead of pushing past the edge). */
4183
- .ws-crumb { padding-top: 6px; padding-bottom: 6px; }
4184
- .ws-crumb .app-crumb { flex-wrap: wrap; min-width: 0; }
4185
- .ws-crumb .app-crumb > .sep, .ws-crumb .app-crumb > span:not(.crumb-right):not(.leaf) { display: none; }
4186
- .ws-crumb .app-crumb .leaf { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
4187
- .ws-crumb .app-crumb .crumb-right { margin-left: 0; flex-wrap: wrap; min-width: 0; display: inline-flex; }
4188
- }
4189
- .ws-drawer-toggle:hover { background: var(--bg-2); color: var(--fg); }
4190
- .ws-drawer-toggle:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4191
-
4192
- @media (prefers-reduced-motion: reduce) {
4193
- .ws-shell, .ws-rail, .ws-pane, .ws-sessions, .ws-scrim { transition: none; }
4194
- .ws-resizer::after { transition: none; }
4195
- }
4196
-
4197
- /* ============================================================
4198
- Row title highlight, expanded-row actions, filter pills.
4199
- ============================================================ */
4200
-
4201
- /* Highlighted match inside a Row title (Row `highlight` prop). */
4202
- .ds-hl { background: color-mix(in oklab, var(--accent) 25%, transparent); color: inherit; border-radius: var(--r-hair); }
4203
-
4204
- /* Action strip rendered inside an EXPANDED Row (Row `actions` prop). The
4205
- buttons stop propagation so they never fire the row onClick. */
4206
- .row-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); width: 100%; padding-top: var(--space-2); }
4207
- .row-act {
4208
- display: inline-flex; align-items: center; justify-content: center;
4209
- padding: 4px 10px; min-height: 28px;
4210
- background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1);
4211
- color: var(--fg-2); font-family: var(--ff-body); font-size: var(--fs-tiny); cursor: pointer;
4212
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
4213
- }
4214
- .row-act:hover { background: var(--bg-3); color: var(--fg); }
4215
- .row-act:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4216
-
4217
- /* FilterPills - a group of pill toggle buttons (aria-pressed). */
4218
- .ds-filter-pills { display: flex; flex-wrap: wrap; gap: var(--space-2); }
4219
- .ds-filter-pill {
4220
- display: inline-flex; align-items: center; justify-content: center;
4221
- padding: 4px 12px; min-height: 28px;
4222
- background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-pill);
4223
- color: var(--fg-2); font-family: var(--ff-body); font-size: var(--fs-tiny); cursor: pointer;
4224
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
4225
- }
4226
- .ds-filter-pill:hover { background: var(--bg-3); color: var(--fg); }
4227
- .ds-filter-pill.active { background: var(--accent-tint); color: var(--fg); border-color: var(--accent); }
4228
- .ds-filter-pill:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4229
-
4230
- /* Touch floor for the new small controls. */
4231
- @media (pointer: coarse) {
4232
- .row-act, .ds-filter-pill { min-height: 44px; }
4233
- }
4234
-
4235
- /* Disabled file actions (read-only row / in-flight mutation) stay visible but inert. */
4236
- .ds-file-act:disabled { opacity: .45; cursor: default; }
4237
- .ds-file-act:disabled:hover { background: transparent; color: var(--fg-3); }
4238
- .ds-file-act:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4239
-
4240
- /* ============================================================
4241
- Print — linearize WorkspaceShell so main content prints in full.
4242
- The multi-column CSS grid with overflow:auto regions clips at
4243
- the on-screen height in print/PDF; drop all chrome and let
4244
- .ws-main expand to its natural document height.
4245
- ============================================================ */
4246
- @media print {
4247
- .ws-shell { display: block; }
4248
- .ws-rail,
4249
- .ws-sessions,
4250
- .ws-pane,
4251
- .ws-crumb,
4252
- .app-status { display: none; }
4253
- .ws-main { overflow: visible; height: auto; }
4254
- /* AppShell: unpin the viewport-height frame so content flows across pages,
4255
- and drop all chrome (it prints as page furniture otherwise). */
4256
- .app { height: auto; min-height: 0; overflow: visible; display: block; }
4257
- .app-body { display: block; }
4258
- .app-main { overflow: visible; height: auto; display: block; max-width: 100%; }
4259
- .app-topbar, .app-chrome, .app-crumb, .app-side-shell, .app-side-scrim,
4260
- .app-side-toggle, .btn-fab, .skip-link, .ds-marquee { display: none; }
4261
- /* Modals/preview: print the content, not a blurred overlay clipped to one page. */
4262
- .ds-modal-backdrop { position: static; background: none; backdrop-filter: none; padding: 0; }
4263
- .ds-modal, .ds-modal-preview { max-width: 100%; max-height: none; box-shadow: none; }
4264
- .ds-modal-body, .ds-preview-body, .ds-preview-code, .ds-preview-text { overflow: visible; }
4265
- .ds-modal-actions, .ds-preview-actions { display: none; }
4266
- /* Data-density: fixed-height log clips; bg-only tiles lose their boundary. */
4267
- .ds-live-log { height: auto; max-height: none; overflow: visible; }
4268
- .ds-stat, .kpi-card { border: 1px solid var(--rule); }
4269
- .ds-session-row { break-inside: avoid; }
4270
- }
4271
-
4272
- /* ============================================================
4273
- Data density — dense observability/dashboard primitives (ported from the
4274
- gmsniff GUI). Components: PhaseWalk, TreeNode, BarRow, StatTile/StatsGrid,
4275
- SubGrid, SessionRow, DevRow, LiveLog/LiveLogEntry. src/components/data-density.js.
4276
- All colors derive from colors_and_type.css tokens — see lint-tokens.mjs.
4277
- ============================================================ */
4278
-
4279
- /* --danger-surface: semantic surface for a deviation/error callout background
4280
- (gmsniff's #3a1a1a dark-red-tinted panel). Derived from --warn so it always
4281
- reads as "danger" against the active theme instead of a baked literal. */
4282
- :root {
4283
- --danger-surface: color-mix(in oklab, var(--warn) 14%, var(--bg));
4284
- }
4285
-
4286
- /* PhaseWalk */
4287
- .ds-phasewalk { display: inline-flex; gap: 2px; vertical-align: middle; }
4288
- .ds-phasewalk-seg {
4289
- width: 18px; height: 10px; border-radius: var(--r-hair);
4290
- background: var(--bg-3); position: relative;
4291
- }
4292
- .ds-phasewalk-seg.is-reached { background: var(--success); }
4293
- .ds-phasewalk-seg.is-gap { background: var(--warn); }
4294
- /* Standalone PhaseWalk reserves headroom for the labels instead of letting
4295
- them escape upward and overlap the element above. */
4296
- .ds-phasewalk { padding-top: 15px; }
4297
- .ds-phasewalk-lbl {
4298
- position: absolute; top: -15px; left: 0;
4299
- font-size: var(--fs-micro); color: var(--fg-3); white-space: nowrap;
4300
- }
4301
- /* Inline in a single-line SessionRow the labels would overlap the row above —
4302
- segments already carry title tooltips. */
4303
- .ds-session-row .ds-phasewalk { padding-top: 0; }
4304
- .ds-session-row .ds-phasewalk-lbl { display: none; }
4305
-
4306
- /* TreeNode */
4307
- .ds-tree-node {
4308
- padding: 5px 10px; border-left: var(--bw-rule) solid var(--bg-3);
4309
- margin: 2px 0 2px 16px; font-size: var(--fs-tiny);
4310
- min-width: 0; overflow-wrap: anywhere;
4311
- }
4312
- @media (max-width: 400px) {
4313
- .ds-tree-node { margin-left: 8px; padding-left: 8px; }
4314
- }
4315
- .ds-tree-node.is-phase { border-color: var(--accent); background: var(--bg-3); font-weight: 600; }
4316
- .ds-tree-node.is-deviation { border-color: var(--warn); background: var(--danger-surface); color: var(--warn); }
4317
- .ds-tree-node.is-mutable-resolve { border-color: var(--success); }
4318
- .ds-tree-node.is-prd-add { border-color: var(--amber); }
4319
- .ds-tree-node-ts { margin-right: 8px; color: var(--fg-3); }
4320
- .ds-tree-node-pills { margin-left: 6px; }
4321
- .ds-tree-node-reason { margin-top: 2px; color: var(--fg-2); }
4322
- .ds-tree-node-deviation { margin-top: 2px; }
4323
- .ds-tree-node-residuals { margin-top: 2px; color: var(--fg-3); }
4324
-
4325
- /* BarRow */
4326
- .ds-bar-row { display: flex; align-items: center; gap: 8px; margin: 3px 0; font-size: var(--fs-tiny); }
4327
- .ds-bar-row-label { width: 100px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4328
- .ds-bar-bg { flex: 1; min-width: 60px; background: var(--bg-3); border-radius: var(--r-hair); height: 6px; overflow: hidden; }
4329
- .ds-bar-fill { background: var(--fg-3); height: 100%; border-radius: var(--r-hair); }
4330
- .ds-bar-fill.is-active { background: var(--accent); }
4331
- .ds-bar-row-value { flex-shrink: 0; color: var(--fg-2); max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4332
- @media (max-width: 400px) {
4333
- .ds-bar-row-label { width: 72px; }
4334
- }
4335
-
4336
- /* StatTile / StatsGrid */
4337
- /* min(140px, 100%) keeps the grid from collapsing to 1 column early inside
4338
- padded panels; the wrapper div stretches its tile to equal row height. */
4339
- .ds-stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr)); gap: var(--space-2); }
4340
- .ds-stats-grid > div { display: grid; }
4341
- @media (max-width: 360px) {
4342
- .ds-stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-1); }
4343
- }
4344
- .ds-stats-grid-empty { padding: var(--space-3); color: var(--fg-3); font-size: var(--fs-tiny); }
4345
- .ds-stat { min-width: 0; background: var(--bg-3); border-radius: var(--r-0); padding: 10px 12px; }
4346
- .ds-stat-val { font-size: clamp(18px, 5cqi, 22px); font-weight: 700; color: var(--accent-ink); overflow-wrap: anywhere; }
4347
- .ds-stat-val.rate-big { font-size: clamp(24px, 7cqi, 32px); color: var(--success); }
4348
- .ds-stat-val.err-rate { font-size: clamp(24px, 7cqi, 32px); color: var(--warn); }
4349
- .ds-stat-lbl { font-size: var(--fs-micro); color: var(--fg-3); margin-top: 2px; }
4350
-
4351
- /* Inline row — flex row with centered items and a small gap, for demo/kit
4352
- markup that needs to lay siblings out horizontally without an inline style. */
4353
- .ds-inline-row { display: flex; align-items: center; gap: var(--space-2, 8px); }
4354
-
4355
- /* SubGrid */
4356
- .ds-sub-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 6px; margin-top: 8px; }
4357
- .ds-sub-grid-empty { padding: var(--space-3); color: var(--fg-3); font-size: var(--fs-tiny); }
4358
- .ds-sub-btn {
4359
- padding: 6px 10px; background: var(--bg-3); border: var(--bw-hair) solid var(--bg-3);
4360
- border-radius: var(--r-0); font-size: var(--fs-tiny); cursor: pointer; color: var(--fg);
4361
- text-align: left; font-family: var(--ff-body);
4362
- }
4363
- .ds-sub-btn:hover { border-color: var(--accent); color: var(--accent-ink); }
4364
- .ds-sub-btn span { display: block; font-size: 18px; font-weight: 700; color: var(--accent-ink); }
4365
-
4366
- /* SessionRow */
4367
- .ds-session-row {
4368
- display: flex; align-items: center; gap: 8px; padding: 6px 10px;
4369
- flex-wrap: wrap; min-width: 0;
4370
- border-bottom: var(--bw-hair) solid var(--bg-3); cursor: pointer;
4371
- }
4372
- .ds-session-row:hover { background: var(--bg-3); }
4373
- .ds-session-row-id {
4374
- font-family: var(--ff-mono); font-size: var(--fs-micro); color: var(--accent-ink);
4375
- flex: 0 1 160px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
4376
- }
4377
- .ds-session-row-counts {
4378
- font-size: var(--fs-micro); color: var(--fg-3);
4379
- flex: 1 1 200px; min-width: 0;
4380
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
4381
- }
4382
- @media (max-width: 640px) {
4383
- .ds-session-row-span { flex-basis: 100%; }
4384
- }
4385
- .ds-session-row-devcnt { color: var(--warn); font-weight: 600; }
4386
- .ds-session-row-span { font-size: var(--fs-micro); color: var(--fg-3); }
4387
-
4388
- /* DevRow */
4389
- .ds-dev-row {
4390
- background: var(--danger-surface); border-left: var(--bw-chunk) solid var(--warn);
4391
- padding: 8px 12px; margin: 4px 0; border-radius: var(--r-hair); font-size: var(--fs-tiny);
4392
- }
4393
- .ds-dev-row strong { color: var(--warn); }
4394
-
4395
- /* LiveLog — natural height with a viewport-tracking cap (dvh follows mobile
4396
- URL-bar chrome; min-height keeps short-landscape from going near-zero). */
4397
- .ds-live-log {
4398
- height: auto;
4399
- max-height: calc(100vh - 200px);
4400
- max-height: calc(100dvh - 200px);
4401
- min-height: 160px;
4402
- overflow-y: auto; overflow-x: hidden;
4403
- font-family: var(--ff-mono); font-size: var(--fs-micro); line-height: 1.5;
4404
- }
4405
- /* Embedded in a WM window the viewport calc is meaningless — fill the container. */
4406
- .fd-root .ds-live-log { height: 100%; max-height: none; min-height: 0; flex: 1 1 auto; }
4407
- .ds-live-log-empty { height: auto; min-height: 0; padding: var(--space-3); color: var(--fg-3); }
4408
- .ds-live-log-entry { padding: 2px 8px; border-bottom: var(--bw-hair) solid var(--bg-3); overflow-wrap: anywhere; }
4409
- .ds-live-log-entry:hover { background: var(--bg-3); }
4410
- .ds-live-log-ts { margin-right: 6px; color: var(--fg-3); }
4411
- .ds-live-log-subtag { margin-right: 6px; padding: 0 5px; border-radius: var(--r-hair); font-size: var(--fs-micro); }
4412
- .ds-live-log-preview { margin-left: 6px; color: var(--fg-3); word-break: break-word; }
4413
-
4414
- /* Touch floors for the dense controls (not covered by the mobile width blocks). */
4415
- @media (pointer: coarse) {
4416
- .ds-sub-btn { min-height: 44px; }
4417
- .ds-session-row { min-height: 44px; }
4418
- .ds-live-log-entry { padding-block: 6px; }
4419
- }
4420
-
4421
-
4422
- /* == appended: popovers-files == */
4423
- /* FilePreviewMedia controls row — spacing + coarse-pointer tap floor */
4424
- .ds-preview-media-controls {
4425
- display: flex;
4426
- align-items: center;
4427
- gap: var(--space-2);
4428
- flex-wrap: wrap;
4429
- }
4430
- @media (hover: none), (pointer: coarse) {
4431
- .ds-preview-media-controls .chat-code-copy,
4432
- .chat-code-copy-head {
4433
- min-height: 44px;
4434
- min-width: 44px;
4435
- }
4436
- }
4437
-
4438
- /* == appended: ui-kits == */
4439
- /* ============================================================
4440
- ui_kit responsive helpers (kits-grids sweep) */
4441
- .ds-section-pad { padding: clamp(6px, 1.2vw, 16px); }
4442
- .ds-panel-gap { margin: var(--space-2) 0; }
4443
- .ds-panel-duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); margin: var(--space-2) 0; }
4444
- @media (max-width: 760px) {
4445
- .ds-panel-duo { grid-template-columns: 1fr; }
4446
- }
4447
- /* wide dense rows/tables scroll inside their panel, never the page */
4448
- .ds-scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; }
4449
-
4450
- /* --- slide deck ------------------------------------------- */
4451
- .ds-deck-stage {
4452
- aspect-ratio: 16 / 9;
4453
- width: 100%;
4454
- max-width: min(1100px, 100%);
4455
- margin: var(--space-3) auto;
4456
- background: var(--bg-2);
4457
- border-radius: var(--r-2);
4458
- padding: clamp(16px, 5vw, 64px);
4459
- box-sizing: border-box;
4460
- display: flex;
4461
- overflow: hidden;
4462
- container-type: inline-size; /* cqw units track the stage, not the viewport */
4463
- }
4464
- @media (max-height: 500px) and (orientation: landscape) {
4465
- .ds-deck-stage {
4466
- max-height: calc(100vh - 140px);
4467
- max-height: calc(100dvh - 140px);
4468
- aspect-ratio: auto;
4469
- }
4470
- }
4471
- .ds-deck-slide { flex: 1; display: flex; min-width: 0; }
4472
- .ds-deck-controls {
4473
- display: flex; align-items: center; justify-content: center; gap: 14px;
4474
- padding: 8px; font-family: var(--ff-mono); font-size: var(--fs-sm);
4475
- }
4476
- .ds-deck-count { color: var(--fg-3); }
4477
- @media (pointer: coarse) {
4478
- .ds-deck-controls .btn { min-height: 44px; min-width: 44px; }
4479
- }
4480
- .ds-slide-eyebrow {
4481
- font-family: var(--ff-mono); font-size: 11px; letter-spacing: 0.1em;
4482
- text-transform: uppercase; color: var(--fg-3); margin-bottom: clamp(12px, 2.5cqw, 24px);
4483
- }
4484
- .ds-slide-col { display: flex; flex-direction: column; justify-content: center; gap: clamp(12px, 2.5cqw, 24px); min-width: 0; }
4485
- .ds-slide-col--start { align-items: flex-start; }
4486
- .ds-slide-col--narrow { max-width: 38ch; }
4487
- .ds-slide-col--quote { max-width: 42ch; }
4488
- .ds-slide-hero {
4489
- font-size: clamp(var(--fs-h2), 6cqw, var(--fs-hero));
4490
- line-height: var(--lh-tight); letter-spacing: var(--tr-tight); font-weight: 600;
4491
- color: var(--slide-accent, var(--accent-ink));
4492
- overflow-wrap: anywhere;
4493
- }
4494
- .ds-slide-h1 {
4495
- font-size: clamp(var(--fs-h3), 4.5cqw, var(--fs-h1));
4496
- line-height: var(--lh-snug); color: var(--fg); font-weight: 500; min-width: 0;
4497
- }
4498
- .ds-slide-h1--lead { margin-bottom: 12px; }
4499
- .ds-slide-quote-body {
4500
- font-size: clamp(var(--fs-lg), 3.5cqw, var(--fs-h2));
4501
- line-height: var(--lh-snug); color: var(--fg); font-weight: 400; font-style: italic;
4502
- }
4503
- .ds-slide-cite { font-family: var(--ff-mono); font-size: var(--fs-sm); color: var(--fg-3); }
4504
- .ds-slide-bullet {
4505
- display: flex; gap: clamp(8px, 2vw, 18px); align-items: baseline;
4506
- border-bottom: 1px solid var(--rule); padding: 12px 0; flex-wrap: wrap;
4507
- }
4508
- .ds-slide-bullet-key {
4509
- flex: 0 1 clamp(56px, 18%, 100px); min-width: 0;
4510
- font-family: var(--ff-mono); font-size: var(--fs-sm);
4511
- color: var(--slide-accent, var(--accent-ink));
4512
- }
4513
- .ds-slide-bullet-val { color: var(--fg-2); font-size: var(--fs-lg); min-width: 0; }
4514
- .ds-slide-split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(12px, 3vw, 32px); margin-top: 12px; }
4515
- @media (max-width: 760px) {
4516
- .ds-slide-split { grid-template-columns: 1fr; }
4517
- }
4518
- .ds-slide-split-cell {
4519
- padding: 18px; background: var(--bg-2); border-radius: var(--r-2);
4520
- color: var(--fg-2); font-size: var(--fs-lg); line-height: var(--lh-base); min-width: 0;
4521
- }
4522
- .ds-slide-split-cell--accent { border-left: 3px solid var(--slide-accent, var(--accent)); }
4523
-
4524
- /* --- error 404 --------------------------------------------- */
4525
- .ds-err-hero {
4526
- padding: clamp(16px, 5vw, 36px) clamp(12px, 4vw, 22px);
4527
- text-align: center; display: flex; flex-direction: column; gap: 14px; align-items: center;
4528
- }
4529
- .ds-err-numeral {
4530
- font-family: var(--ff-mono); font-size: clamp(56px, 20vw, 120px);
4531
- line-height: 1; color: var(--panel-text-3); letter-spacing: -0.03em;
4532
- }
4533
- .ds-err-path {
4534
- display: inline-flex; gap: 6px; align-items: baseline; flex-wrap: wrap; max-width: 100%;
4535
- font-family: var(--ff-mono); font-size: 13px; color: var(--panel-text-2);
4536
- background: var(--panel-1); padding: 6px 12px; border-radius: var(--r-0);
4537
- overflow-wrap: anywhere; word-break: break-all;
4538
- }
4539
-
4540
- /* --- gallery ------------------------------------------------ */
4541
- .ds-tile-grid {
4542
- padding: clamp(8px, 2.5vw, 16px);
4543
- display: grid; grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr)); gap: 8px;
4544
- }
4545
- .ds-tile-grid--tight { grid-template-columns: repeat(auto-fill, minmax(min(140px, 100%), 1fr)); }
4546
- .ds-swatch-grid {
4547
- padding: clamp(8px, 2.5vw, 16px);
4548
- display: grid; grid-template-columns: repeat(auto-fill, minmax(min(160px, 100%), 1fr)); gap: 12px;
4549
- }
4550
- .ds-gallery-tile {
4551
- cursor: pointer; display: flex; flex-direction: column; gap: 6px;
4552
- padding: 12px; border-radius: var(--r-1); min-height: clamp(120px, 18vw, 160px);
4553
- border: 0; text-align: left; font: inherit; color: inherit;
4554
- background: var(--tile-tone, var(--panel-1));
4555
- transition: transform var(--dur-base) var(--ease), box-shadow var(--dur-base) var(--ease);
4556
- }
4557
- /* Flat tonal fill only (house style: no bespoke tile chrome, no borders — see
4558
- .ds-feature above), so the hover affordance is a lift, matching the same
4559
- translateY + shadow idiom .chat-msg:hover .chat-bubble already uses. */
4560
- .ds-gallery-tile:hover { transform: translateY(-2px); box-shadow: 0 2px 8px color-mix(in oklab, var(--fg) 10%, transparent); }
4561
- .ds-gallery-tile:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
4562
- .ds-gallery-tile--tight { min-height: clamp(96px, 14vw, 120px); }
4563
- .ds-lightbox {
4564
- position: fixed; inset: 0; background: var(--scrim);
4565
- display: flex; align-items: center; justify-content: center;
4566
- padding: clamp(12px, 4vw, 32px);
4567
- padding-bottom: max(clamp(12px, 4vw, 32px), env(safe-area-inset-bottom));
4568
- z-index: 50;
4569
- }
4570
- .ds-lightbox-card {
4571
- background: var(--panel-0); border-radius: var(--r-2); padding: clamp(16px, 4vw, 28px);
4572
- width: min(520px, 100%); min-width: 0;
4573
- max-height: calc(100vh - 24px);
4574
- max-height: calc(100dvh - 24px);
4575
- overflow: auto;
4576
- display: flex; flex-direction: column; gap: 14px;
4577
- }
4578
- .ds-lightbox-preview {
4579
- padding: clamp(16px, 6vw, 36px); border-radius: var(--r-1); text-align: center;
4580
- font-family: var(--ff-mono); white-space: pre-line; font-size: clamp(16px, 5vw, 24px);
4581
- background: var(--tile-tone, var(--panel-1));
4582
- }
4583
-
4584
- /* --- search ------------------------------------------------- */
4585
- .ds-topbar-search { width: min(280px, 100%); min-width: 0; flex: 1 1 clamp(140px, 30vw, 280px); }
4586
- @media (max-width: 480px) {
4587
- .ds-topbar-search { flex-basis: 100%; }
4588
- }
4589
- .ds-empty-state { padding: clamp(16px, 5vw, 24px); text-align: center; color: var(--panel-text-3); }
4590
- .ds-empty-state-glyph { font-size: clamp(24px, 6vw, 32px); }
4591
- .ds-empty-state-msg { margin: 6px 0; }
4592
- .ds-empty-state-hint { margin: 0; font-size: var(--fs-sm); }
4593
-
4594
- /* --- system primer ------------------------------------------ */
4595
- .ds-swatch-grid-sm {
4596
- display: grid; grid-template-columns: repeat(auto-fill, minmax(min(110px, 100%), 1fr));
4597
- gap: 12px; padding: clamp(10px, 2.5vw, 14px) clamp(10px, 3vw, 18px);
4598
- }
4599
- .ds-swatch-grid-lg { grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); }
4600
- .ds-swatch-col { display: flex; flex-direction: column; gap: 6px; }
4601
- .ds-swatch-chip { height: 48px; border-radius: var(--r-1); border: 1px solid var(--rule); background: var(--swatch, var(--panel-1)); }
4602
- .ds-swatch-chip--big { height: 64px; }
4603
- .ds-swatch-name { font-family: var(--ff-mono); font-size: 11px; color: var(--fg-3); }
4604
- .ds-type-panel { padding: clamp(10px, 2.5vw, 14px) clamp(10px, 3vw, 18px); display: flex; flex-direction: column; gap: 14px; }
4605
- .ds-type-row {
4606
- display: flex; align-items: baseline; gap: 14px;
4607
- border-bottom: 1px solid var(--rule); padding-bottom: 8px; flex-wrap: wrap;
4608
- }
4609
- .ds-type-row-label { flex: 0 0 64px; font-family: var(--ff-mono); font-size: 11px; color: var(--fg-3); }
4610
- .ds-type-sample {
4611
- min-width: 0; overflow-wrap: anywhere; line-height: var(--lh-tight); color: var(--fg);
4612
- font-size: var(--sample-size, var(--fs-body));
4613
- }
4614
- .ds-prim-panel { padding: clamp(10px, 2.5vw, 14px) clamp(10px, 3vw, 18px); display: flex; flex-direction: column; gap: 18px; }
4615
- .ds-prim-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
4616
- .ds-prim-label { flex: 0 0 64px; font-family: var(--ff-mono); font-size: 11px; color: var(--fg-3); }
4617
-
4618
- /* == appended: content-site == */
4619
- /* Home landing wrappers (site/theme.mjs) — replace banned inline styles. */
4620
- .ds-home-hero-wrap { padding: 0 var(--space-2, 8px); }
4621
- .ds-home-panel { margin: var(--space-2, 8px); }
4622
- @media (max-width: 480px) {
4623
- .ds-home-panel { margin: var(--space-1, 4px) 0; }
4624
- }
4625
- /* Home landing section tablist (site/theme.mjs Tabs()) — bare .tabs had no
4626
- rule anywhere, so the four links rendered as unstyled inline text jammed
4627
- together with no separation. Mirror the topbar nav pill-tab treatment. */
4628
- .tabs[role="tablist"] {
4629
- display: flex;
4630
- flex-wrap: wrap;
4631
- gap: var(--space-1, 4px);
4632
- font-size: var(--fs-sm);
4633
- }
4634
- .tabs[role="tablist"] a {
4635
- display: flex;
4636
- align-items: center;
4637
- gap: var(--space-1, 4px);
4638
- padding: var(--space-2, 8px) var(--space-3, 12px);
4639
- border-radius: var(--r-pill);
4640
- color: var(--fg-2);
4641
- transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease),
4642
- transform var(--dur-base) var(--ease-spring);
4643
- }
4644
- .tabs[role="tablist"] a:hover { background: var(--bg-2); color: var(--fg); transform: translateY(-1px); }
4645
- .tabs[role="tablist"] a.active {
4646
- color: var(--accent-ink);
4647
- background: color-mix(in oklab, var(--accent) 16%, transparent);
4648
- box-shadow: inset 0 -2px 0 0 var(--accent);
4649
- font-weight: 600;
4650
- }
4651
- .ds-quickstart {
4652
- padding: var(--space-3, 16px) var(--space-4, 24px);
4653
- display: flex;
4654
- flex-direction: column;
4655
- gap: var(--space-1-75, 6px);
4656
- }
4657
- /* Table() scroll containment — wide tables scroll inside their own box,
4658
- never the page body. */
4659
- .ds-table-wrap {
4660
- overflow-x: auto;
4661
- -webkit-overflow-scrolling: touch;
4662
- max-width: 100%;
4663
- }
4664
- .ds-table-wrap > table { min-width: 100%; }
4665
-
4666
-
4667
- /* == appended: settings kit cleanup == */
4668
- /* == appended: settings kit == */
4669
- .ds-settings-main { padding: var(--space-2, 8px); }
4670
- .ds-settings-body { padding: 14px 18px; }
4671
- .ds-settings-body-stack { display: flex; flex-direction: column; gap: var(--space-2-5, 10px); }
4672
- .ds-field-block { margin: var(--space-2-5, 10px) 0; }
4673
- .ds-field-eyebrow {
4674
- font-family: var(--ff-mono); font-size: 11px; letter-spacing: 0.06em;
4675
- text-transform: uppercase; color: var(--panel-text-3);
4676
- }
4677
- .ds-hint-sm { font-size: 12px; color: var(--panel-text-2); }
4678
- .ds-note-quiet { margin: 0; color: var(--panel-text-2); }
4679
- .ds-btn-row { display: flex; gap: var(--space-2, 8px); flex-wrap: wrap; }
4680
- .ds-btn-row-tight { gap: var(--space-1-75, 6px); }
4681
- .ds-key-input { flex: 1 1 160px; min-width: 0; font-family: var(--ff-mono); }
4682
- .ds-toggle-btn { min-width: 78px; }
4683
- .ds-toggle-label { margin-left: var(--space-2, 8px); color: inherit; opacity: 0.7; }
4684
- .ds-btn-warn { color: var(--warn); }
4685
- /* Raw --mascot text measures 3.11:1 on paper (fails 4.5:1); --mascot-deep is
4686
- the text-safe tone, same accent/accent-ink split used everywhere else. */
4687
- .ds-btn-mascot { color: var(--mascot-deep); }
4688
- .ds-savebar {
4689
- position: sticky; bottom: 8px; display: flex; align-items: center;
4690
- justify-content: flex-end; gap: 8px; flex-wrap: wrap; padding: 10px;
4691
- background: var(--panel-2); border-radius: var(--r-1); margin: 8px 0;
4692
- }
4693
- .ds-savebar-note { flex: 1 1 160px; min-width: 0; color: var(--panel-text-2); }
4694
- .ds-settings-modal { min-width: min(320px, calc(100vw - 32px)); max-width: 480px; }
4695
- .ds-modal-body-form { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
4696
- .ds-modal-note { margin: 0 0 8px; color: var(--panel-text-2); font-size: 14px; }
4697
- .ds-draft-preview {
4698
- background: var(--panel-1); padding: 12px; border-radius: var(--r-0);
4699
- font-size: 13px; color: var(--panel-text-2); max-height: 120px;
4700
- overflow-y: auto; font-family: var(--ff-mono); overflow-wrap: anywhere;
4701
- }
4702
- .ds-modal-actions { display: flex; gap: 8px; margin-top: 12px; justify-content: space-between; flex-wrap: wrap; }
4703
-
4704
- /* == appended: signin kit cleanup == */
4705
- /* -- signin kit (ui_kits/signin) -- */
4706
- .ds-auth-wrap { padding: var(--space-2, 8px); display: flex; flex-direction: column; align-items: center; }
4707
- .ds-auth-col { width: 100%; max-width: 440px; display: flex; flex-direction: column; gap: 8px; margin: clamp(12px, 4vw, 24px) 0; min-width: 0; }
4708
- .ds-auth-form { padding: clamp(12px, 3vw, 18px); display: flex; flex-direction: column; gap: 10px; }
4709
- .ds-auth-sent { align-items: center; text-align: center; }
4710
- .ds-auth-sent-glyph { font-size: 32px; color: var(--panel-accent); }
4711
- .ds-auth-sent-title { margin: 0; font-weight: 600; }
4712
- .ds-auth-sent-sub { margin: 0; color: var(--panel-text-2); overflow-wrap: anywhere; }
4713
- .ds-auth-field { display: flex; flex-direction: column; gap: 4px; }
4714
- .ds-auth-field-label { font-family: var(--ff-mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--panel-text-3); }
4715
- .ds-auth-row-between { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
4716
- .ds-auth-remember { display: flex; align-items: center; gap: 6px; cursor: pointer; }
4717
- .ds-auth-remember-text { color: var(--panel-text-2); font-size: 13px; }
4718
- .ds-auth-forgot { font-size: 13px; }
4719
- .ds-auth-error { padding: 8px 10px; background: var(--panel-1); border-radius: var(--r-0); color: var(--danger); font-size: 13px; overflow-wrap: anywhere; }
4720
- .ds-auth-providers { display: flex; gap: 8px; flex-wrap: wrap; }
4721
- .ds-auth-provider-btn { flex: 1 1 100px; min-width: 0; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px; }
4722
- .ds-auth-provider-btn--loading { opacity: 0.7; pointer-events: none; }
4723
- .ds-auth-provider-glyph { font-family: var(--ff-mono); color: var(--panel-text-3); display: inline-flex; }
4724
- .ds-auth-modes { display: flex; justify-content: center; gap: 14px; margin-top: 8px; font-size: 13px; flex-wrap: wrap; }
4725
- .ds-auth-mode-link { color: var(--panel-text-3); text-decoration: none; }
4726
- .ds-auth-mode-link--active { color: var(--panel-text); text-decoration: underline; }
4727
- .ds-auth-fineprint { text-align: center; font-size: 12px; color: var(--panel-text-3); margin: 6px 0; }
4728
-
4729
- /* == appended: gallery-misc kit cleanup == */
4730
- /* --- shared helpers (converted from inline styles) --- */
4731
- .ds-m0 { margin: 0; }
4732
- .ds-text-2 { color: var(--panel-text-2); }
4733
- .ds-text-3 { color: var(--panel-text-3); }
4734
- .ds-kit-head { display: flex; align-items: center; justify-content: space-between; gap: clamp(8px, 2vw, 16px); flex-wrap: wrap; }
4735
-
4736
- /* --- gallery kit --- */
4737
- .ds-tile-cap { flex: 1; display: flex; align-items: center; justify-content: center; font-family: var(--ff-mono); white-space: pre-line; color: var(--panel-text-2); font-size: 18px; min-width: 0; text-align: center; }
4738
- .ds-tile-meta { display: flex; align-items: center; justify-content: space-between; gap: 6px; font-size: 12px; flex-wrap: wrap; min-width: 0; }
4739
- .ds-tile-glyph { font-family: var(--ff-mono); color: var(--panel-text-3); }
4740
- .ds-tile-label { color: var(--panel-text); overflow-wrap: anywhere; }
4741
- .ds-gal-swatch { height: 64px; border-radius: var(--r-0); background: var(--swatch, var(--panel-1)); }
4742
- .ds-gal-swatch-meta { display: flex; justify-content: space-between; gap: 6px; flex-wrap: wrap; font-family: var(--ff-mono); font-size: 11px; min-width: 0; }
4743
- .ds-gal-swatch-name { color: var(--panel-text); }
4744
- .ds-gal-swatch-hint { color: var(--panel-text-3); overflow-wrap: anywhere; }
4745
- .ds-lightbox-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
4746
- .ds-lightbox-tag { font-family: var(--ff-mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--panel-text-3); }
4747
-
4748
- /* --- error_404 kit --- */
4749
- .ds-err-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; margin-top: 6px; }
4750
- .ds-err-chips { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; margin-top: 4px; }
4751
-
4752
- /* == appended: terminal-capp kit cleanup == */
4753
- /* --- terminal kit ------------------------------------------ */
4754
- .ds-section-pad-sm { padding: 8px; }
4755
- .ds-kit-head-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
4756
- .ds-cli-cmt .cmd { color: var(--fg-3); }
4757
- .ds-cli-out .cmd { color: var(--fg-2); }
4758
- .ds-cli-ok .prompt, .ds-cli-ok .cmd { color: var(--accent-ink); }
4759
- .ds-cli-warn .prompt, .ds-cli-warn .cmd { color: var(--mascot-deep); }
4760
- .ds-cli-log .prompt { color: var(--fg-3); }
4761
- .ds-cli-log .cmd { color: var(--fg-2); font-family: var(--ff-mono); }
4762
- .ds-term-body { padding: 14px 18px; display: flex; flex-direction: column; gap: 4px; background: var(--bg-2); border-radius: var(--r-1); }
4763
- .ds-term-body--tall { min-height: 280px; }
4764
- .ds-term-input-row { margin-top: 6px; }
4765
- .ds-term-input { flex: 1; min-width: 0; background: transparent; border: 0; outline: 0; font-family: var(--ff-mono); font-size: 13px; color: var(--fg); }
4766
- /* full-viewport render root (community-app) */
4767
- .ds-root-viewport { height: 100vh; height: 100dvh; }
4768
-
4769
- /* --- chat kit: widescreen detail pane -----------------------
4770
- .chat itself stays single-column (shared component, untouched) and
4771
- stretches full-width on ultrawide via the existing .app-main > .chat
4772
- max-width:none rule above (~L380) — that's fine for a bare thread, but
4773
- it leaves a lone thread column growing to 1400px+ with no other use of
4774
- the extra width. This wrapper adds a persistent "thread info" rail once
4775
- there is room to spare, instead of just widening the message column. */
4776
- .ds-chat-layout { display: flex; flex-direction: column; flex: 1; min-height: 0; gap: var(--space-3); }
4777
- .ds-chat-layout > .chat { min-width: 0; }
4778
- .ds-chat-detail { display: none; }
4779
- @media (min-width: 1100px) {
4780
- .ds-chat-layout { display: grid; grid-template-columns: minmax(0, 1fr) 280px; align-items: start; gap: var(--space-4); }
4781
- .ds-chat-detail { display: block; position: sticky; top: 0; }
4782
- }
1
+ /* app-shell.css -- re-exports the split source sheets in src/css/app-shell/
2
+ in their original order. This file is a published package export consumed
3
+ directly via <link rel="stylesheet" href="app-shell.css"> by preview/*.html
4
+ and ui_kits/*/index.html (never through the build step, which reads the
5
+ split files directly for byte-identical dist output -- see scripts/build.mjs).
6
+ Splitting only: no rule was added, removed, or reordered versus the
7
+ original monolithic app-shell.css. */
8
+ @import url('./src/css/app-shell/base.css');
9
+ @import url('./src/css/app-shell/topbar.css');
10
+ @import url('./src/css/app-shell/primitives.css');
11
+ @import url('./src/css/app-shell/panel-row.css');
12
+ @import url('./src/css/app-shell/hero-content.css');
13
+ @import url('./src/css/app-shell/responsive.css');
14
+ @import url('./src/css/app-shell/chat-basic.css');
15
+ @import url('./src/css/app-shell/files.css');
16
+ @import url('./src/css/app-shell/catalog-theme.css');
17
+ @import url('./src/css/app-shell/chat-polish.css');
18
+ @import url('./src/css/app-shell/sidebar-misc.css');
19
+ @import url('./src/css/app-shell/states-interactions.css');
20
+ @import url('./src/css/app-shell/loading-alerts.css');
21
+ @import url('./src/css/app-shell/responsive2-workspace.css');
22
+ @import url('./src/css/app-shell/row-print.css');
23
+ @import url('./src/css/app-shell/data-density.css');
24
+ @import url('./src/css/app-shell/kits-appended.css');