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.
- package/README.md +22 -2
- package/app-shell.css +24 -4782
- package/colors_and_type.css +43 -0
- package/dist/247420.css +74 -3
- package/dist/247420.js +16 -16
- package/package.json +14 -8
- package/src/bootstrap.js +10 -1
- package/src/components/chat-message-parts.js +355 -0
- package/src/components/chat.js +23 -320
- package/src/components/data-density.js +15 -10
- package/src/components/files-modals.js +14 -2
- package/src/components/files.js +5 -1
- package/src/components/freddie/helpers.js +46 -8
- package/src/css/app-shell/base.css +157 -0
- package/src/css/app-shell/catalog-theme.css +144 -0
- package/src/css/app-shell/chat-basic.css +12 -0
- package/src/css/app-shell/chat-polish.css +459 -0
- package/src/css/app-shell/data-density.css +150 -0
- package/src/css/app-shell/files.css +709 -0
- package/src/css/app-shell/hero-content.css +407 -0
- package/src/css/app-shell/kits-appended.css +361 -0
- package/src/css/app-shell/loading-alerts.css +120 -0
- package/src/css/app-shell/panel-row.css +177 -0
- package/src/css/app-shell/primitives.css +197 -0
- package/src/css/app-shell/responsive.css +266 -0
- package/src/css/app-shell/responsive2-workspace.css +465 -0
- package/src/css/app-shell/row-print.css +75 -0
- package/src/css/app-shell/sidebar-misc.css +79 -0
- package/src/css/app-shell/states-interactions.css +696 -0
- package/src/css/app-shell/topbar.css +319 -0
- package/src/deck-stage.js +6 -4
- package/src/highlight.js +20 -4
- package/src/index.js +15 -5
- package/src/kits/os/freddie/pages-chat.js +1 -1
- package/src/kits/os/freddie/pages-core.js +1 -1
- package/src/kits/os/freddie/pages-os.js +1 -1
- package/src/kits/os/freddie/pages-tools.js +1 -1
- package/src/kits/os/wm.js +38 -0
- package/src/kits/slides/deck-stage-overlay.js +68 -0
- package/src/kits/slides/deck-stage-state.js +81 -0
- package/src/kits/slides/deck-stage-style.js +117 -0
- package/src/kits/slides/deck-stage.js +167 -0
- package/src/kits/slides/index.js +5 -0
- package/src/markdown.js +89 -11
- package/src/kits/os/freddie/helpers.js +0 -33
package/app-shell.css
CHANGED
|
@@ -1,4782 +1,24 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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');
|