@cplieger/web-terminal-ui 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/css/00-tokens.css +13 -1
- package/css/02-app.css +24 -120
- package/css/03-primitives.css +82 -0
- package/css/04-feedback.css +166 -0
- package/css/05-tabs.css +310 -0
- package/css/06-mobile.css +335 -0
- package/css/07-animations.css +102 -0
- package/css/MANIFEST +7 -1
- package/package.json +2 -2
- package/src/features/clipboard.ts +10 -0
- package/src/features/connection-banner.ts +1 -1
- package/src/features/context-menu.ts +174 -42
- package/src/features/mobile-toolbar.ts +59 -1
- package/src/features/scroll-to-bottom.ts +22 -6
- package/src/features/tabs.ts +1305 -206
- package/src/kernel/kernel.ts +65 -4
- package/src/kernel/types.ts +8 -0
- package/src/presets.ts +36 -6
- package/src/viewport.ts +16 -1
- package/css/03-v3.css +0 -629
package/css/03-v3.css
DELETED
|
@@ -1,629 +0,0 @@
|
|
|
1
|
-
/* ===== v3 kernel + feature styles =====
|
|
2
|
-
The v3 kernel builds named layout regions (wt-region-*) as siblings of the
|
|
3
|
-
terminal surface (.term, styled in 02-app.css), and features mount chrome into
|
|
4
|
-
region slots. The regions are transparent groupings (display: contents) so
|
|
5
|
-
each feature's element floats over the fixed-inset terminal exactly as the
|
|
6
|
-
pre-v3 chrome did; the terminal-core, key-toolbar (.key-toolbar/.kb-*), and
|
|
7
|
-
cursor/color styles in 02-app.css are reused unchanged.
|
|
8
|
-
|
|
9
|
-
Note: this is the pragmatic first-cut v3 stylesheet. The design's section 22.8
|
|
10
|
-
per-feature CSS split (core.css + mobile-toolbar.css/context-menu.css/tabs.css/
|
|
11
|
-
activity.css/animations.css + a css-manifest.mjs assembler) is a tracked
|
|
12
|
-
follow-up; the classes below are the seams that split will carve along. */
|
|
13
|
-
|
|
14
|
-
/* Regions + slots are transparent: they group a feature's DOM without imposing
|
|
15
|
-
layout, so a feature element inside them positions itself (fixed) over the
|
|
16
|
-
terminal, and an absent feature contributes nothing. */
|
|
17
|
-
.wt-region,
|
|
18
|
-
.wt-slot {
|
|
19
|
-
display: contents;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* ===== Shared button primitive (44px touch target) ===== */
|
|
23
|
-
.wt-btn {
|
|
24
|
-
display: inline-flex;
|
|
25
|
-
align-items: center;
|
|
26
|
-
justify-content: center;
|
|
27
|
-
min-width: var(--touch-target);
|
|
28
|
-
min-height: var(--touch-target);
|
|
29
|
-
background: transparent;
|
|
30
|
-
color: var(--text);
|
|
31
|
-
border: none;
|
|
32
|
-
border-radius: var(--radius-inner);
|
|
33
|
-
font: inherit;
|
|
34
|
-
cursor: pointer;
|
|
35
|
-
touch-action: manipulation;
|
|
36
|
-
user-select: none;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.wt-btn:active {
|
|
40
|
-
background: var(--text-active);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.wt-btn svg {
|
|
44
|
-
width: var(--icon);
|
|
45
|
-
height: var(--icon);
|
|
46
|
-
stroke: currentcolor;
|
|
47
|
-
fill: none;
|
|
48
|
-
stroke-width: 2;
|
|
49
|
-
stroke-linecap: round;
|
|
50
|
-
stroke-linejoin: round;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/* ===== Toast (kernel shared primitive) ===== */
|
|
54
|
-
.wt-toast {
|
|
55
|
-
position: fixed;
|
|
56
|
-
bottom: calc(var(--sp-3) + var(--kb-inset, 0px));
|
|
57
|
-
left: 50%;
|
|
58
|
-
transform: translateX(-50%);
|
|
59
|
-
max-width: calc(100% - 2 * var(--sp-3));
|
|
60
|
-
padding: var(--sp-1) var(--sp-3);
|
|
61
|
-
background: var(--surface);
|
|
62
|
-
backdrop-filter: var(--blur);
|
|
63
|
-
border: 1px solid oklch(70% 0.1 160deg / 30%);
|
|
64
|
-
border-radius: var(--radius);
|
|
65
|
-
color: var(--text);
|
|
66
|
-
font-family: var(--font-ui);
|
|
67
|
-
font-size: 0.8125rem;
|
|
68
|
-
font-weight: 500;
|
|
69
|
-
text-align: center;
|
|
70
|
-
z-index: 40;
|
|
71
|
-
display: none;
|
|
72
|
-
opacity: 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.wt-toast.visible {
|
|
76
|
-
display: block;
|
|
77
|
-
opacity: 1;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/* ===== Connection banner (connectionBanner feature) ===== */
|
|
81
|
-
.wt-conn-banner {
|
|
82
|
-
position: fixed;
|
|
83
|
-
bottom: calc(var(--sp-3) + var(--kb-inset, 0px));
|
|
84
|
-
left: 50%;
|
|
85
|
-
transform: translateX(-50%);
|
|
86
|
-
max-width: calc(100% - 2 * var(--sp-3));
|
|
87
|
-
padding: var(--sp-1) var(--sp-3);
|
|
88
|
-
background: var(--surface);
|
|
89
|
-
backdrop-filter: var(--blur);
|
|
90
|
-
border: 1px solid var(--border);
|
|
91
|
-
border-radius: var(--radius);
|
|
92
|
-
color: var(--text);
|
|
93
|
-
font-family: var(--font-ui);
|
|
94
|
-
font-size: 0.8125rem;
|
|
95
|
-
font-weight: 500;
|
|
96
|
-
text-align: center;
|
|
97
|
-
z-index: 20;
|
|
98
|
-
display: none;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.wt-conn-banner.visible {
|
|
102
|
-
display: block;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.wt-conn-banner[data-state="reconnecting"] {
|
|
106
|
-
border-color: oklch(70% 0.15 60deg / 40%);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.wt-conn-banner[data-state="offline"] {
|
|
110
|
-
color: #f55;
|
|
111
|
-
border-color: oklch(60% 0.2 25deg / 50%);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.wt-conn-banner[data-state="restarted"] {
|
|
115
|
-
color: #fa5;
|
|
116
|
-
border-color: oklch(70% 0.15 60deg / 40%);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/* Desktop: move both the banner and toast to top-center so they don't collide
|
|
120
|
-
with the bottom-anchored scroll-to-bottom control. */
|
|
121
|
-
@media (pointer: fine) {
|
|
122
|
-
.wt-conn-banner,
|
|
123
|
-
.wt-toast {
|
|
124
|
-
top: var(--sp-3);
|
|
125
|
-
bottom: auto;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/* ===== Scroll-to-bottom (scrollToBottom feature) ===== */
|
|
130
|
-
.wt-scroll-bottom {
|
|
131
|
-
position: fixed;
|
|
132
|
-
left: 50%;
|
|
133
|
-
bottom: calc(var(--safe-bottom, 0px) + var(--sp-3) + var(--kb-inset, 0px));
|
|
134
|
-
transform: translateX(-50%);
|
|
135
|
-
gap: var(--sp-1);
|
|
136
|
-
padding: var(--sp-2) var(--sp-3);
|
|
137
|
-
background: var(--surface);
|
|
138
|
-
backdrop-filter: var(--blur);
|
|
139
|
-
border: 1px solid var(--border);
|
|
140
|
-
border-radius: var(--radius);
|
|
141
|
-
font-size: 0.875rem;
|
|
142
|
-
font-weight: 500;
|
|
143
|
-
z-index: 10;
|
|
144
|
-
display: none;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/* The scroll slot gets .scrolled-up toggled by the feature; show the button. */
|
|
148
|
-
.wt-slot-scroll.scrolled-up .wt-scroll-bottom {
|
|
149
|
-
display: inline-flex;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/* ===== Context menu (contextMenu feature) ===== */
|
|
153
|
-
.wt-ctx-menu {
|
|
154
|
-
position: fixed;
|
|
155
|
-
display: none;
|
|
156
|
-
flex-direction: column;
|
|
157
|
-
background: var(--surface);
|
|
158
|
-
backdrop-filter: var(--blur);
|
|
159
|
-
border: 1px solid var(--border);
|
|
160
|
-
border-radius: var(--radius);
|
|
161
|
-
padding: var(--sp-1);
|
|
162
|
-
z-index: 30;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.wt-ctx-menu.visible {
|
|
166
|
-
display: flex;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.wt-ctx-menu button {
|
|
170
|
-
background: transparent;
|
|
171
|
-
color: var(--text);
|
|
172
|
-
border: none;
|
|
173
|
-
border-radius: var(--radius-inner);
|
|
174
|
-
font-family: var(--font-ui);
|
|
175
|
-
font-size: 0.8125rem;
|
|
176
|
-
font-weight: 500;
|
|
177
|
-
padding: var(--sp-1) var(--sp-3);
|
|
178
|
-
cursor: pointer;
|
|
179
|
-
text-align: left;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.wt-ctx-menu button:active {
|
|
183
|
-
background: var(--text-active);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
@media (pointer: coarse) {
|
|
187
|
-
.wt-ctx-menu button {
|
|
188
|
-
min-height: var(--touch-target);
|
|
189
|
-
padding-block: var(--sp-2);
|
|
190
|
-
touch-action: manipulation;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/* ===== Tab bar (tabs feature) ===== */
|
|
195
|
-
.wt-region-top-bar {
|
|
196
|
-
/* On desktop the tab strip docks to the top; the region is transparent, so
|
|
197
|
-
the bar element below owns its own fixed placement. */
|
|
198
|
-
display: contents;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.wt-tab-bar {
|
|
202
|
-
position: fixed;
|
|
203
|
-
top: calc(var(--safe-top, 0px) + var(--vv-top, 0px));
|
|
204
|
-
left: 0;
|
|
205
|
-
right: 0;
|
|
206
|
-
display: flex;
|
|
207
|
-
align-items: center;
|
|
208
|
-
gap: 2px;
|
|
209
|
-
padding: var(--sp-1);
|
|
210
|
-
background: var(--surface);
|
|
211
|
-
backdrop-filter: var(--blur);
|
|
212
|
-
border-bottom: 1px solid var(--border);
|
|
213
|
-
overflow-x: auto;
|
|
214
|
-
z-index: 15;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.wt-tab {
|
|
218
|
-
display: inline-flex;
|
|
219
|
-
align-items: center;
|
|
220
|
-
gap: var(--sp-1);
|
|
221
|
-
padding: var(--sp-1) var(--sp-2);
|
|
222
|
-
border-radius: var(--radius-inner);
|
|
223
|
-
color: color-mix(in oklch, var(--text) 70%, transparent);
|
|
224
|
-
font-family: var(--font-ui);
|
|
225
|
-
font-size: 0.8125rem;
|
|
226
|
-
cursor: pointer;
|
|
227
|
-
white-space: nowrap;
|
|
228
|
-
user-select: none;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.wt-tab-active {
|
|
232
|
-
background: color-mix(in oklch, var(--text) 10%, var(--surface));
|
|
233
|
-
color: var(--text);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.wt-tab-label {
|
|
237
|
-
max-width: 20ch;
|
|
238
|
-
overflow: hidden;
|
|
239
|
-
text-overflow: ellipsis;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/* The tab close (×) and the add-tab (+) button share one look: a centered,
|
|
243
|
-
muted square glyph button that shows a box on hover like an active tab. Same
|
|
244
|
-
size and color so they read as a matched pair (and the + centers vertically
|
|
245
|
-
in the strip via the shared flex box + the bar's align-items: center). */
|
|
246
|
-
.wt-tab-close,
|
|
247
|
-
.wt-tab-new {
|
|
248
|
-
display: inline-flex;
|
|
249
|
-
align-items: center;
|
|
250
|
-
justify-content: center;
|
|
251
|
-
width: 1.375rem;
|
|
252
|
-
height: 1.375rem;
|
|
253
|
-
padding: 0;
|
|
254
|
-
background: transparent;
|
|
255
|
-
border: none;
|
|
256
|
-
border-radius: var(--radius-inner);
|
|
257
|
-
color: color-mix(in oklch, var(--text) 60%, transparent);
|
|
258
|
-
font-size: 1rem;
|
|
259
|
-
line-height: 1;
|
|
260
|
-
cursor: pointer;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.wt-tab-close:hover,
|
|
264
|
-
.wt-tab-new:hover {
|
|
265
|
-
background: var(--text-active);
|
|
266
|
-
color: var(--text);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/* Push the terminal surface below the fixed tab strip so it does not overlap
|
|
270
|
-
the first rows. The tabs feature marks the surface (.wt-with-topbar) and keeps
|
|
271
|
-
the measured strip height in --wt-tabbar-h. The surface is position:fixed
|
|
272
|
-
inset:0, so a top offset is what clears it. Desktop only: the strip is hidden
|
|
273
|
-
on a coarse pointer, where the switcher docks to the bottom instead, so the
|
|
274
|
-
surface stays full-height there. (The prior `.wt-tab-bar ~ .term` /
|
|
275
|
-
`:has(> .wt-tab-bar) .term` rule never matched: the bar lives inside a
|
|
276
|
-
display:contents region, so it is neither a DOM sibling nor an ancestor of
|
|
277
|
-
.term.) */
|
|
278
|
-
@media (pointer: fine) {
|
|
279
|
-
.term.wt-with-topbar {
|
|
280
|
-
top: var(--wt-tabbar-h, 40px);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/* ===== Activity dots (activityMonitor via tabs) =====
|
|
285
|
-
Four-state, distinguishable without color (shape + motion, not hue alone),
|
|
286
|
-
per section 12 / WCAG 1.4.1. The one .wt-status-dot vocabulary is shared by
|
|
287
|
-
the desktop strip, the mobile switcher, and the overview sheet rows. */
|
|
288
|
-
.wt-status-dot {
|
|
289
|
-
width: 9px;
|
|
290
|
-
height: 9px;
|
|
291
|
-
border-radius: 50%;
|
|
292
|
-
flex: none;
|
|
293
|
-
/* Default / idle (a new session with no turn yet): a hollow grey dot, so
|
|
294
|
-
"nothing has happened" reads as an outline rather than a filled state.
|
|
295
|
-
The fill (solid vs hollow) is the non-hue cue that separates the idle state
|
|
296
|
-
from the filled done/working/input states (WCAG 1.4.1). */
|
|
297
|
-
background: transparent;
|
|
298
|
-
border: 1.5px solid color-mix(in oklch, var(--text) 45%, transparent);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
.wt-status-dot[data-status="idle"] {
|
|
302
|
-
background: transparent;
|
|
303
|
-
border: 1.5px solid color-mix(in oklch, var(--text) 45%, transparent);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/* Agent working: solid pulsing purple, matching Kiro IDE's "thinking" color
|
|
307
|
-
(#a78bfa). Motion is the primary non-hue cue. Server-side this is driven by
|
|
308
|
-
the OSC 9;4 progress signal, so it tracks agent activity, not the user typing
|
|
309
|
-
at the prompt. */
|
|
310
|
-
.wt-status-dot[data-status="working"] {
|
|
311
|
-
background: #a78bfa;
|
|
312
|
-
border-color: #a78bfa;
|
|
313
|
-
animation: wt-pulse 1.2s ease-in-out infinite;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/* Agent done / ready: solid green, matching Kiro IDE's "Ready" (#22c55e), no
|
|
317
|
-
motion. The filled dot distinguishes it from the hollow idle without hue. */
|
|
318
|
-
.wt-status-dot[data-status="done"] {
|
|
319
|
-
background: #22c55e;
|
|
320
|
-
border-color: #22c55e;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/* User action required (permission / approval): solid amber matching Kiro IDE's
|
|
324
|
-
"waiting-approval" (#fbbf24), plus a ring so it reads as "needs you" via shape
|
|
325
|
-
as well as hue. It persists until you act on or switch into the tab. */
|
|
326
|
-
.wt-status-dot[data-status="input"] {
|
|
327
|
-
background: #fbbf24;
|
|
328
|
-
border-color: #fbbf24;
|
|
329
|
-
box-shadow: 0 0 0 2px oklch(85% 0.16 85deg / 40%);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/* Exited: a dim hollow dot, static; distinct from idle by its lower opacity. */
|
|
333
|
-
.wt-status-dot[data-status="exited"] {
|
|
334
|
-
background: transparent;
|
|
335
|
-
border: 1.5px solid color-mix(in oklch, var(--text) 30%, transparent);
|
|
336
|
-
opacity: 0.5;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
@keyframes wt-pulse {
|
|
340
|
-
50% {
|
|
341
|
-
opacity: 0.4;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/* Reduced-motion (or the animations feature off): drop the pulse, keep color. */
|
|
346
|
-
@media (prefers-reduced-motion: reduce) {
|
|
347
|
-
.wt-status-dot[data-status="working"] {
|
|
348
|
-
animation: none;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/* ===== Animations feature =====
|
|
353
|
-
Motion is off unless the animations feature marks the root .wt-animate (and
|
|
354
|
-
the OS is not in reduced-motion). Transitions are declared here, gated on it. */
|
|
355
|
-
.wt-animate .wt-toast,
|
|
356
|
-
.wt-animate .wt-conn-banner,
|
|
357
|
-
.wt-animate .wt-scroll-bottom,
|
|
358
|
-
.wt-animate .wt-ctx-menu {
|
|
359
|
-
transition:
|
|
360
|
-
opacity var(--dur-standard) var(--ease-standard),
|
|
361
|
-
display var(--dur-standard) allow-discrete;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
.wt-animate .wt-tab,
|
|
365
|
-
.wt-animate .wt-tab-close,
|
|
366
|
-
.wt-animate .wt-tab-new {
|
|
367
|
-
transition:
|
|
368
|
-
background-color var(--dur-micro) var(--ease-standard),
|
|
369
|
-
color var(--dur-micro) var(--ease-standard);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/* ===== Mobile bottom-switcher (tabs feature, section 12) =====
|
|
373
|
-
The switcher is the mobile tab surface: it shows the active tab and a
|
|
374
|
-
position indicator as a tap/swipe target, plus a >=44px overview control that
|
|
375
|
-
carries the aggregate needs-input badge. Hidden on a fine pointer, where the
|
|
376
|
-
top strip is used instead; the top strip is correspondingly hidden on a
|
|
377
|
-
coarse pointer so tabs live in the thumb zone. */
|
|
378
|
-
.wt-switcher {
|
|
379
|
-
display: none;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
@media (pointer: coarse) {
|
|
383
|
-
.wt-tab-bar,
|
|
384
|
-
.wt-tab-new {
|
|
385
|
-
display: none;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.wt-switcher {
|
|
389
|
-
position: fixed;
|
|
390
|
-
left: 0;
|
|
391
|
-
right: 0;
|
|
392
|
-
/* Lift above the soft keyboard rather than hiding, so the switch surface
|
|
393
|
-
stays reachable in the terminal's primary working state (section 12). */
|
|
394
|
-
bottom: calc(var(--safe-bottom, 0px) + var(--kb-inset, 0px));
|
|
395
|
-
display: flex;
|
|
396
|
-
align-items: stretch;
|
|
397
|
-
gap: var(--sp-1);
|
|
398
|
-
padding: var(--sp-1);
|
|
399
|
-
background: var(--surface);
|
|
400
|
-
backdrop-filter: var(--blur);
|
|
401
|
-
border-top: 1px solid var(--border);
|
|
402
|
-
z-index: 25;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
.wt-switcher-current {
|
|
407
|
-
flex: 1 1 auto;
|
|
408
|
-
display: flex;
|
|
409
|
-
align-items: center;
|
|
410
|
-
gap: var(--sp-2);
|
|
411
|
-
min-height: var(--touch-target);
|
|
412
|
-
padding: 0 var(--sp-2);
|
|
413
|
-
background: transparent;
|
|
414
|
-
color: var(--text);
|
|
415
|
-
border: none;
|
|
416
|
-
border-radius: var(--radius-inner);
|
|
417
|
-
font-family: var(--font-ui);
|
|
418
|
-
font-size: 0.875rem;
|
|
419
|
-
cursor: pointer;
|
|
420
|
-
overflow: hidden;
|
|
421
|
-
/* Read horizontal swipe here; let vertical scrolling pass through. */
|
|
422
|
-
touch-action: pan-y;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
.wt-switcher-current:active {
|
|
426
|
-
background: var(--text-active);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.wt-switcher-label {
|
|
430
|
-
flex: 1 1 auto;
|
|
431
|
-
overflow: hidden;
|
|
432
|
-
text-overflow: ellipsis;
|
|
433
|
-
white-space: nowrap;
|
|
434
|
-
text-align: left;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
.wt-switcher-pos {
|
|
438
|
-
flex: none;
|
|
439
|
-
color: color-mix(in oklch, var(--text) 60%, transparent);
|
|
440
|
-
font-variant-numeric: tabular-nums;
|
|
441
|
-
font-size: 0.8125rem;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
.wt-switcher-overview {
|
|
445
|
-
position: relative;
|
|
446
|
-
flex: none;
|
|
447
|
-
padding: 0 var(--sp-3);
|
|
448
|
-
font-family: var(--font-ui);
|
|
449
|
-
font-size: 0.8125rem;
|
|
450
|
-
font-weight: 600;
|
|
451
|
-
border: 1px solid var(--border);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
.wt-switcher-count {
|
|
455
|
-
font-variant-numeric: tabular-nums;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/* Aggregate needs-input attention: a background tab is blocked on you. Shape +
|
|
459
|
-
color (a ring plus a corner glyph), never hue alone (WCAG 1.4.1). */
|
|
460
|
-
.wt-switcher-overview[data-attention="input"] {
|
|
461
|
-
border-color: oklch(70% 0.18 25deg);
|
|
462
|
-
box-shadow: 0 0 0 2px oklch(70% 0.18 25deg / 35%);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.wt-switcher-overview[data-attention="input"]::after {
|
|
466
|
-
content: "";
|
|
467
|
-
position: absolute;
|
|
468
|
-
top: 3px;
|
|
469
|
-
right: 3px;
|
|
470
|
-
width: 8px;
|
|
471
|
-
height: 8px;
|
|
472
|
-
border-radius: 50%;
|
|
473
|
-
background: oklch(70% 0.18 25deg);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/* ===== Overview sheet (tabs feature, section 12) =====
|
|
477
|
-
A modal, focus-trapped bottom sheet listing every tab; distinct from the
|
|
478
|
-
non-modal overlay (context menu). Opened from the switcher; mobile-only in
|
|
479
|
-
practice (the switcher that opens it is hidden on a fine pointer). */
|
|
480
|
-
.wt-sheet-scrim {
|
|
481
|
-
position: fixed;
|
|
482
|
-
inset: 0;
|
|
483
|
-
background: oklch(0% 0 0deg / 50%);
|
|
484
|
-
z-index: 45;
|
|
485
|
-
display: none;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
.wt-sheet-scrim.visible {
|
|
489
|
-
display: block;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
.wt-sheet {
|
|
493
|
-
position: fixed;
|
|
494
|
-
left: 0;
|
|
495
|
-
right: 0;
|
|
496
|
-
bottom: 0;
|
|
497
|
-
max-height: 70vh;
|
|
498
|
-
display: flex;
|
|
499
|
-
flex-direction: column;
|
|
500
|
-
gap: var(--sp-2);
|
|
501
|
-
padding: var(--sp-3);
|
|
502
|
-
padding-bottom: calc(var(--sp-3) + var(--safe-bottom, 0px));
|
|
503
|
-
background: var(--surface);
|
|
504
|
-
backdrop-filter: var(--blur);
|
|
505
|
-
border-top: 1px solid var(--border);
|
|
506
|
-
border-radius: var(--radius) var(--radius) 0 0;
|
|
507
|
-
z-index: 46;
|
|
508
|
-
overflow-y: auto;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
.wt-sheet[hidden] {
|
|
512
|
-
display: none;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
.wt-sheet-header {
|
|
516
|
-
display: flex;
|
|
517
|
-
align-items: center;
|
|
518
|
-
justify-content: space-between;
|
|
519
|
-
gap: var(--sp-2);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
.wt-sheet-title {
|
|
523
|
-
font-family: var(--font-ui);
|
|
524
|
-
font-size: 0.9375rem;
|
|
525
|
-
font-weight: 600;
|
|
526
|
-
color: var(--text);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
.wt-sheet-new {
|
|
530
|
-
flex: none;
|
|
531
|
-
padding: 0 var(--sp-3);
|
|
532
|
-
font-family: var(--font-ui);
|
|
533
|
-
font-size: 0.8125rem;
|
|
534
|
-
font-weight: 500;
|
|
535
|
-
border: 1px solid var(--border);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
.wt-sheet-list {
|
|
539
|
-
list-style: none;
|
|
540
|
-
margin: 0;
|
|
541
|
-
padding: 0;
|
|
542
|
-
display: flex;
|
|
543
|
-
flex-direction: column;
|
|
544
|
-
gap: 2px;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
.wt-sheet-row {
|
|
548
|
-
display: flex;
|
|
549
|
-
align-items: stretch;
|
|
550
|
-
gap: var(--sp-1);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
.wt-sheet-select {
|
|
554
|
-
flex: 1 1 auto;
|
|
555
|
-
display: flex;
|
|
556
|
-
align-items: center;
|
|
557
|
-
gap: var(--sp-2);
|
|
558
|
-
min-height: var(--touch-target);
|
|
559
|
-
padding: 0 var(--sp-2);
|
|
560
|
-
background: transparent;
|
|
561
|
-
color: var(--text);
|
|
562
|
-
border: none;
|
|
563
|
-
border-radius: var(--radius-inner);
|
|
564
|
-
font-family: var(--font-ui);
|
|
565
|
-
font-size: 0.875rem;
|
|
566
|
-
text-align: left;
|
|
567
|
-
cursor: pointer;
|
|
568
|
-
overflow: hidden;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
.wt-sheet-row-active .wt-sheet-select {
|
|
572
|
-
background: color-mix(in oklch, var(--text) 10%, var(--surface));
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
.wt-sheet-select:active {
|
|
576
|
-
background: var(--text-active);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
.wt-sheet-row-label {
|
|
580
|
-
flex: 1 1 auto;
|
|
581
|
-
overflow: hidden;
|
|
582
|
-
text-overflow: ellipsis;
|
|
583
|
-
white-space: nowrap;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
.wt-sheet-close {
|
|
587
|
-
flex: none;
|
|
588
|
-
border: 1px solid var(--border);
|
|
589
|
-
font-size: 1rem;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/* ===== Catching-up cue (tabs feature, sections 12/13) =====
|
|
593
|
-
A switched-into tab's cached screen is stale until its resume delta lands, so
|
|
594
|
-
it must not read as live. This unobtrusive cue shows only if the delta has not
|
|
595
|
-
arrived shortly after the switch, and clears on the first screen frame. */
|
|
596
|
-
.wt-catchup {
|
|
597
|
-
position: fixed;
|
|
598
|
-
top: calc(var(--safe-top, 0px) + var(--vv-top, 0px) + 44px);
|
|
599
|
-
left: 50%;
|
|
600
|
-
transform: translateX(-50%);
|
|
601
|
-
padding: 2px var(--sp-2);
|
|
602
|
-
background: var(--surface);
|
|
603
|
-
backdrop-filter: var(--blur);
|
|
604
|
-
border: 1px solid var(--border);
|
|
605
|
-
border-radius: var(--radius);
|
|
606
|
-
color: color-mix(in oklch, var(--text) 75%, transparent);
|
|
607
|
-
font-family: var(--font-ui);
|
|
608
|
-
font-size: 0.75rem;
|
|
609
|
-
z-index: 24;
|
|
610
|
-
display: none;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
.wt-catchup.visible {
|
|
614
|
-
display: block;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/* ===== Switch motion (animations feature) =====
|
|
618
|
-
A subtle content fade on switch, so the cache-first repaint reads as a change
|
|
619
|
-
rather than a flicker. Gated on .wt-animate (and prefers-reduced-motion is
|
|
620
|
-
respected because the animations feature does not set .wt-animate then). */
|
|
621
|
-
.wt-animate .term.wt-switching .term-output {
|
|
622
|
-
animation: wt-switch-in var(--dur-standard) var(--ease-standard);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
@keyframes wt-switch-in {
|
|
626
|
-
from {
|
|
627
|
-
opacity: 0.55;
|
|
628
|
-
}
|
|
629
|
-
}
|