@cplieger/web-terminal-ui 3.5.0 → 4.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 +60 -27
- package/css/00-tokens.css +18 -2
- package/css/01-scope.css +76 -0
- package/css/{02-app.css → 02-terminal.css} +113 -239
- package/css/10-primitives.css +98 -0
- package/css/20-connection-banner.css +42 -0
- package/css/21-scroll-to-bottom.css +65 -0
- package/css/22-context-menu.css +45 -0
- package/css/23-toolbar.css +181 -0
- package/css/{05-tabs.css → 30-tabs.css} +127 -66
- package/css/{06-mobile.css → 31-switcher.css} +85 -80
- package/css/{07-animations.css → 40-animations.css} +15 -15
- package/css/MANIFEST +19 -11
- package/css/MANIFEST.single +12 -0
- package/css/MANIFEST.tabbed +16 -0
- package/css/MANIFEST.touch +12 -0
- package/css/{01-reset.css → page.css} +18 -19
- package/package.json +19 -6
- package/scaffold/index.html +5 -0
- package/src/features/activity-monitor.ts +32 -2
- package/src/features/connection-banner.ts +1 -0
- package/src/features/context-menu.ts +6 -33
- package/src/features/menu-position.ts +50 -0
- package/src/features/mobile-toolbar.ts +5 -4
- package/src/features/{tabs.ts → tabs/index.ts} +310 -721
- package/src/features/tabs/model.ts +342 -0
- package/src/features/tabs/strip.ts +101 -0
- package/src/features/tabs/switcher.ts +129 -0
- package/src/index.ts +1 -0
- package/src/kernel/a11y.ts +30 -5
- package/src/kernel/conn-state.ts +16 -6
- package/src/kernel/kernel.ts +170 -59
- package/src/kernel/types.ts +68 -15
- package/src/presets/agent-tabbed.ts +15 -0
- package/src/presets/single.ts +27 -0
- package/src/presets/tabbed.ts +42 -0
- package/src/presets/touch.ts +13 -0
- package/src/presets.ts +12 -76
- package/src/viewport.ts +28 -15
- package/css/03-primitives.css +0 -83
- package/css/04-feedback.css +0 -180
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
/* ===== Terminal ===== */
|
|
2
|
-
.term {
|
|
3
|
-
|
|
2
|
+
:where(.wt-root) .term {
|
|
3
|
+
/* Absolute against .wt-root (the positioning boundary in BOTH layout modes):
|
|
4
|
+
in wt-viewport the root is a fixed full-viewport box, so this is
|
|
5
|
+
geometrically identical to the pre-v4 `position: fixed`; in wt-container it
|
|
6
|
+
fills the embedder's panel. viewport.ts still pins top/bottom inline to
|
|
7
|
+
track the visual viewport around the soft keyboard. */
|
|
8
|
+
position: absolute;
|
|
4
9
|
inset: 0;
|
|
5
10
|
overflow: hidden auto;
|
|
11
|
+
/* Scroll stays in the terminal: reaching the top of scrollback (or the
|
|
12
|
+
bottom) must not chain the gesture into an embedding page (wt-container
|
|
13
|
+
mode), and a touch pull inside the terminal must not trigger the host's
|
|
14
|
+
pull-to-refresh. Full-page hosts already get this via page.css's
|
|
15
|
+
html/body overscroll-behavior: none; this covers the embedded case. */
|
|
16
|
+
overscroll-behavior: contain;
|
|
17
|
+
/* Reserve the scrollbar gutter from the start on classic-scrollbar
|
|
18
|
+
platforms. Without it the scrollbar pops in when scrollback first
|
|
19
|
+
exceeds a screen, narrowing the content box -> ResizeObserver -> column
|
|
20
|
+
recount -> PTY resize -> the running program reflows: a one-time layout
|
|
21
|
+
jump every fresh session. Real terminals reserve this space permanently;
|
|
22
|
+
with the boundary's thin dark scrollbars (01-scope.css) the idle gutter
|
|
23
|
+
is a subtle strip. No-op on overlay-scrollbar platforms. */
|
|
24
|
+
scrollbar-gutter: stable;
|
|
6
25
|
/* The scroll controller (vterm scroll.ts) is the single owner of
|
|
7
26
|
scrollTop: it pins to the bottom only while the user is following,
|
|
8
27
|
and does nothing while they are scrolled up reading. New output
|
|
@@ -27,7 +46,7 @@
|
|
|
27
46
|
color: var(--text);
|
|
28
47
|
}
|
|
29
48
|
|
|
30
|
-
.term-row {
|
|
49
|
+
:where(.wt-root) .term-row {
|
|
31
50
|
white-space: pre;
|
|
32
51
|
min-height: 17px;
|
|
33
52
|
font-variant-ligatures: none;
|
|
@@ -48,7 +67,7 @@
|
|
|
48
67
|
cell tracks the column on every flush. */
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
.term-row:empty::after {
|
|
70
|
+
:where(.wt-root) .term-row:empty::after {
|
|
52
71
|
content: "\00a0";
|
|
53
72
|
}
|
|
54
73
|
|
|
@@ -56,7 +75,7 @@
|
|
|
56
75
|
as the first row when history older than the buffer holds was evicted —
|
|
57
76
|
client-side cap or a server-reported resume gap). A quiet, centered,
|
|
58
77
|
non-selectable divider so the user knows scrollback doesn't go further up. */
|
|
59
|
-
.term-trim-marker {
|
|
78
|
+
:where(.wt-root) .term-trim-marker {
|
|
60
79
|
color: color-mix(in oklch, var(--text) 60%, transparent);
|
|
61
80
|
font-size: 0.6875rem;
|
|
62
81
|
text-align: center;
|
|
@@ -65,7 +84,7 @@
|
|
|
65
84
|
font-family: var(--font-ui);
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
.term-input {
|
|
87
|
+
:where(.wt-root) .term-input {
|
|
69
88
|
/* The hidden keyboard target. position:absolute (NOT fixed) so it lives in
|
|
70
89
|
the terminal's scroll/content coordinate space alongside .composition-view
|
|
71
90
|
and .pred-cursor: composition.positionCompositionView() moves it to the
|
|
@@ -94,7 +113,7 @@
|
|
|
94
113
|
font-size: 16px;
|
|
95
114
|
}
|
|
96
115
|
|
|
97
|
-
.term-output {
|
|
116
|
+
:where(.wt-root) .term-output {
|
|
98
117
|
position: relative;
|
|
99
118
|
outline: none;
|
|
100
119
|
|
|
@@ -128,7 +147,7 @@
|
|
|
128
147
|
so the candidate popup tracks what the user is typing.
|
|
129
148
|
Visibility is gated by the `.active` class so empty state doesn't
|
|
130
149
|
render a phantom box on top of the cursor. */
|
|
131
|
-
.composition-view {
|
|
150
|
+
:where(.wt-root) .composition-view {
|
|
132
151
|
position: absolute;
|
|
133
152
|
pointer-events: none;
|
|
134
153
|
background: var(--surface);
|
|
@@ -143,7 +162,7 @@
|
|
|
143
162
|
display: none;
|
|
144
163
|
}
|
|
145
164
|
|
|
146
|
-
.composition-view.active {
|
|
165
|
+
:where(.wt-root) .composition-view.active {
|
|
147
166
|
display: block;
|
|
148
167
|
}
|
|
149
168
|
|
|
@@ -153,7 +172,7 @@
|
|
|
153
172
|
cursor (predict.ts deactivates on bail and the renderer hides on
|
|
154
173
|
coincidence). Dashed outline + reduced opacity reads as "tentative"
|
|
155
174
|
— distinct from the solid block of the real cursor. */
|
|
156
|
-
.pred-cursor {
|
|
175
|
+
:where(.wt-root) .pred-cursor {
|
|
157
176
|
position: absolute;
|
|
158
177
|
pointer-events: none;
|
|
159
178
|
border: 1px dashed var(--text);
|
|
@@ -163,7 +182,7 @@
|
|
|
163
182
|
box-sizing: border-box;
|
|
164
183
|
}
|
|
165
184
|
|
|
166
|
-
.pred-cursor.visible {
|
|
185
|
+
:where(.wt-root) .pred-cursor.visible {
|
|
167
186
|
display: block;
|
|
168
187
|
}
|
|
169
188
|
|
|
@@ -177,17 +196,17 @@
|
|
|
177
196
|
Auto-detected bare URLs (.term-autolink) are tightly scoped to the URL text and
|
|
178
197
|
never bleed, so they keep a subtle persistent underline. (The engine also only
|
|
179
198
|
anchors runs with real link text, so the underline hugs the text either way.) */
|
|
180
|
-
.term-link {
|
|
199
|
+
:where(.wt-root) .term-link {
|
|
181
200
|
color: inherit;
|
|
182
201
|
text-decoration: none;
|
|
183
202
|
cursor: pointer;
|
|
184
203
|
}
|
|
185
204
|
|
|
186
|
-
.term-autolink {
|
|
205
|
+
:where(.wt-root) .term-autolink {
|
|
187
206
|
text-decoration: underline dotted;
|
|
188
207
|
}
|
|
189
208
|
|
|
190
|
-
.term-link:hover {
|
|
209
|
+
:where(.wt-root) .term-link:hover {
|
|
191
210
|
text-decoration: underline solid;
|
|
192
211
|
}
|
|
193
212
|
|
|
@@ -197,9 +216,9 @@
|
|
|
197
216
|
the viewport. JS-driven instead of CSS animation because the cursor
|
|
198
217
|
span is recreated every row update — a CSS animation would restart on
|
|
199
218
|
each recreation and never reach the "off" frame during streaming. */
|
|
200
|
-
.term-cursor,
|
|
201
|
-
.term-cursor-underline,
|
|
202
|
-
.term-cursor-bar {
|
|
219
|
+
:where(.wt-root) .term-cursor,
|
|
220
|
+
:where(.wt-root) .term-cursor-underline,
|
|
221
|
+
:where(.wt-root) .term-cursor-bar {
|
|
203
222
|
box-sizing: border-box;
|
|
204
223
|
display: inline-block;
|
|
205
224
|
height: 17px;
|
|
@@ -207,35 +226,70 @@
|
|
|
207
226
|
vertical-align: top;
|
|
208
227
|
}
|
|
209
228
|
|
|
210
|
-
.term-cursor {
|
|
229
|
+
:where(.wt-root) .term-cursor {
|
|
211
230
|
background: var(--text);
|
|
212
231
|
color: var(--bg);
|
|
213
232
|
}
|
|
214
233
|
|
|
215
234
|
/* Underline cursor (DECSCUSR 3/4) */
|
|
216
|
-
.term-cursor-underline {
|
|
235
|
+
:where(.wt-root) .term-cursor-underline {
|
|
217
236
|
background: transparent;
|
|
218
237
|
color: var(--text);
|
|
219
238
|
border-bottom: 2px solid var(--text);
|
|
220
239
|
}
|
|
221
240
|
|
|
222
241
|
/* Bar cursor (DECSCUSR 5/6) */
|
|
223
|
-
.term-cursor-bar {
|
|
242
|
+
:where(.wt-root) .term-cursor-bar {
|
|
224
243
|
background: transparent;
|
|
225
244
|
color: var(--text);
|
|
226
245
|
border-left: 2px solid var(--text);
|
|
227
246
|
}
|
|
228
247
|
|
|
229
|
-
.cursor-blink-off .term-cursor {
|
|
248
|
+
:where(.wt-root) .cursor-blink-off .term-cursor {
|
|
230
249
|
background: transparent;
|
|
231
250
|
color: var(--text);
|
|
232
251
|
}
|
|
233
252
|
|
|
234
|
-
|
|
235
|
-
|
|
253
|
+
/* Caret overlay (engine >= v3): the renderer paints the REAL cursor as one
|
|
254
|
+
absolutely-positioned element in the scroll container (a sibling of
|
|
255
|
+
.pred-cursor, below it in the stack), not as a restyled span inside the
|
|
256
|
+
cursor row — rows stay pure content, so a native text selection survives
|
|
257
|
+
cursor motion and typing. The engine sets left/top/width/height/line-height
|
|
258
|
+
inline each flush; the .term-cursor / -underline / -bar variant classes
|
|
259
|
+
above keep supplying the colors and borders, and the JS-driven
|
|
260
|
+
.cursor-blink-off toggle (now on the scroll container) keeps driving the
|
|
261
|
+
block blink. */
|
|
262
|
+
:where(.wt-root) .term-cursor-overlay {
|
|
263
|
+
position: absolute;
|
|
264
|
+
pointer-events: none;
|
|
265
|
+
white-space: pre;
|
|
266
|
+
overflow: hidden;
|
|
267
|
+
z-index: 3;
|
|
268
|
+
box-sizing: border-box;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
:where(.wt-root) .term-cursor-overlay:not(.visible) {
|
|
272
|
+
display: none;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* DECSCNM reverse video (private mode 5): the engine toggles
|
|
276
|
+
.term-reverse-video on the scroll container (render.updateReverseVideo);
|
|
277
|
+
this rule swaps the DEFAULT color pair for the terminal subtree via the
|
|
278
|
+
captured copies from 00-tokens.css. Everything painted with var(--text)/
|
|
279
|
+
var(--bg) — default-colored cells, the screen background, the block cursor's
|
|
280
|
+
inverted cell — follows automatically; explicitly colored cells keep their
|
|
281
|
+
colors, matching xterm's DECSCNM semantics (the default pair swaps, not a
|
|
282
|
+
per-pixel inversion). */
|
|
283
|
+
:where(.wt-root) .term.term-reverse-video {
|
|
284
|
+
--text: var(--bg-default);
|
|
285
|
+
--bg: var(--text-default);
|
|
236
286
|
}
|
|
237
287
|
|
|
238
|
-
|
|
288
|
+
:where(.wt-root) .term-blink {
|
|
289
|
+
animation: wt-blink-anim 1s step-end infinite;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@keyframes wt-blink-anim {
|
|
239
293
|
50% {
|
|
240
294
|
opacity: 0;
|
|
241
295
|
}
|
|
@@ -243,9 +297,9 @@
|
|
|
243
297
|
|
|
244
298
|
/* When the terminal isn't focused, render the cursor as an outline
|
|
245
299
|
rather than a filled block (xterm.js convention). */
|
|
246
|
-
.term:not(.focus) .term-cursor,
|
|
247
|
-
.term:not(.focus) .term-cursor-underline,
|
|
248
|
-
.term:not(.focus) .term-cursor-bar {
|
|
300
|
+
:where(.wt-root) .term:not(.focus) .term-cursor,
|
|
301
|
+
:where(.wt-root) .term:not(.focus) .term-cursor-underline,
|
|
302
|
+
:where(.wt-root) .term:not(.focus) .term-cursor-bar {
|
|
249
303
|
background: transparent;
|
|
250
304
|
color: var(--text);
|
|
251
305
|
border: none;
|
|
@@ -257,280 +311,100 @@
|
|
|
257
311
|
}
|
|
258
312
|
|
|
259
313
|
/* ===== Terminal color classes (basic 16 — themeable) ===== */
|
|
260
|
-
.term-fg-0 {
|
|
314
|
+
:where(.wt-root) .term-fg-0 {
|
|
261
315
|
color: #000;
|
|
262
316
|
}
|
|
263
|
-
.term-fg-1 {
|
|
317
|
+
:where(.wt-root) .term-fg-1 {
|
|
264
318
|
color: #a00;
|
|
265
319
|
}
|
|
266
|
-
.term-fg-2 {
|
|
320
|
+
:where(.wt-root) .term-fg-2 {
|
|
267
321
|
color: #0a0;
|
|
268
322
|
}
|
|
269
|
-
.term-fg-3 {
|
|
323
|
+
:where(.wt-root) .term-fg-3 {
|
|
270
324
|
color: #a50;
|
|
271
325
|
}
|
|
272
|
-
.term-fg-4 {
|
|
326
|
+
:where(.wt-root) .term-fg-4 {
|
|
273
327
|
color: #00a;
|
|
274
328
|
}
|
|
275
|
-
.term-fg-5 {
|
|
329
|
+
:where(.wt-root) .term-fg-5 {
|
|
276
330
|
color: #a0a;
|
|
277
331
|
}
|
|
278
|
-
.term-fg-6 {
|
|
332
|
+
:where(.wt-root) .term-fg-6 {
|
|
279
333
|
color: #0aa;
|
|
280
334
|
}
|
|
281
|
-
.term-fg-7 {
|
|
335
|
+
:where(.wt-root) .term-fg-7 {
|
|
282
336
|
color: #aaa;
|
|
283
337
|
}
|
|
284
|
-
.term-fg-8 {
|
|
338
|
+
:where(.wt-root) .term-fg-8 {
|
|
285
339
|
color: #555;
|
|
286
340
|
}
|
|
287
|
-
.term-fg-9 {
|
|
341
|
+
:where(.wt-root) .term-fg-9 {
|
|
288
342
|
color: #f55;
|
|
289
343
|
}
|
|
290
|
-
.term-fg-10 {
|
|
344
|
+
:where(.wt-root) .term-fg-10 {
|
|
291
345
|
color: #5f5;
|
|
292
346
|
}
|
|
293
|
-
.term-fg-11 {
|
|
347
|
+
:where(.wt-root) .term-fg-11 {
|
|
294
348
|
color: #ff5;
|
|
295
349
|
}
|
|
296
|
-
.term-fg-12 {
|
|
350
|
+
:where(.wt-root) .term-fg-12 {
|
|
297
351
|
color: #55f;
|
|
298
352
|
}
|
|
299
|
-
.term-fg-13 {
|
|
353
|
+
:where(.wt-root) .term-fg-13 {
|
|
300
354
|
color: #f5f;
|
|
301
355
|
}
|
|
302
|
-
.term-fg-14 {
|
|
356
|
+
:where(.wt-root) .term-fg-14 {
|
|
303
357
|
color: #5ff;
|
|
304
358
|
}
|
|
305
|
-
.term-fg-15 {
|
|
359
|
+
:where(.wt-root) .term-fg-15 {
|
|
306
360
|
color: #fff;
|
|
307
361
|
}
|
|
308
362
|
|
|
309
|
-
.term-bg-0 {
|
|
363
|
+
:where(.wt-root) .term-bg-0 {
|
|
310
364
|
background: #000;
|
|
311
365
|
}
|
|
312
|
-
.term-bg-1 {
|
|
366
|
+
:where(.wt-root) .term-bg-1 {
|
|
313
367
|
background: #a00;
|
|
314
368
|
}
|
|
315
|
-
.term-bg-2 {
|
|
369
|
+
:where(.wt-root) .term-bg-2 {
|
|
316
370
|
background: #0a0;
|
|
317
371
|
}
|
|
318
|
-
.term-bg-3 {
|
|
372
|
+
:where(.wt-root) .term-bg-3 {
|
|
319
373
|
background: #a50;
|
|
320
374
|
}
|
|
321
|
-
.term-bg-4 {
|
|
375
|
+
:where(.wt-root) .term-bg-4 {
|
|
322
376
|
background: #00a;
|
|
323
377
|
}
|
|
324
|
-
.term-bg-5 {
|
|
378
|
+
:where(.wt-root) .term-bg-5 {
|
|
325
379
|
background: #a0a;
|
|
326
380
|
}
|
|
327
|
-
.term-bg-6 {
|
|
381
|
+
:where(.wt-root) .term-bg-6 {
|
|
328
382
|
background: #0aa;
|
|
329
383
|
}
|
|
330
|
-
.term-bg-7 {
|
|
384
|
+
:where(.wt-root) .term-bg-7 {
|
|
331
385
|
background: #aaa;
|
|
332
386
|
}
|
|
333
|
-
.term-bg-8 {
|
|
387
|
+
:where(.wt-root) .term-bg-8 {
|
|
334
388
|
background: #555;
|
|
335
389
|
}
|
|
336
|
-
.term-bg-9 {
|
|
390
|
+
:where(.wt-root) .term-bg-9 {
|
|
337
391
|
background: #f55;
|
|
338
392
|
}
|
|
339
|
-
.term-bg-10 {
|
|
393
|
+
:where(.wt-root) .term-bg-10 {
|
|
340
394
|
background: #5f5;
|
|
341
395
|
}
|
|
342
|
-
.term-bg-11 {
|
|
396
|
+
:where(.wt-root) .term-bg-11 {
|
|
343
397
|
background: #ff5;
|
|
344
398
|
}
|
|
345
|
-
.term-bg-12 {
|
|
399
|
+
:where(.wt-root) .term-bg-12 {
|
|
346
400
|
background: #55f;
|
|
347
401
|
}
|
|
348
|
-
.term-bg-13 {
|
|
402
|
+
:where(.wt-root) .term-bg-13 {
|
|
349
403
|
background: #f5f;
|
|
350
404
|
}
|
|
351
|
-
.term-bg-14 {
|
|
405
|
+
:where(.wt-root) .term-bg-14 {
|
|
352
406
|
background: #5ff;
|
|
353
407
|
}
|
|
354
|
-
.term-bg-15 {
|
|
408
|
+
:where(.wt-root) .term-bg-15 {
|
|
355
409
|
background: #fff;
|
|
356
410
|
}
|
|
357
|
-
|
|
358
|
-
/* ===== Key toolbar (touch devices only) =====
|
|
359
|
-
Uses interpolate-size: allow-keywords (on :root) to animate
|
|
360
|
-
width/height between a fixed collapsed size and auto (intrinsic).
|
|
361
|
-
Toggle is first in DOM so it's at top-left of the grid — visible
|
|
362
|
-
when the container clips to 1-cell size. Container is right-anchored
|
|
363
|
-
so it grows leftward on open. */
|
|
364
|
-
.key-toolbar {
|
|
365
|
-
display: contents;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/* Touch-only toolbar children: the on-screen Esc/Tab/arrows/hamburger
|
|
369
|
-
are useless on desktop where the user has a real keyboard. Hidden by
|
|
370
|
-
default; the @media (pointer: coarse) block below restores them. */
|
|
371
|
-
.kb-key,
|
|
372
|
-
.kb-toggle {
|
|
373
|
-
display: none;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
@media (pointer: coarse) {
|
|
377
|
-
.key-toolbar {
|
|
378
|
-
position: fixed;
|
|
379
|
-
top: calc(var(--safe-top) + var(--sp-2) + var(--vv-top, 0px));
|
|
380
|
-
right: var(--sp-2);
|
|
381
|
-
z-index: 10;
|
|
382
|
-
display: grid;
|
|
383
|
-
grid-template-columns: repeat(4, var(--touch-target)) var(--touch-target);
|
|
384
|
-
grid-template-rows: var(--touch-target) var(--touch-target);
|
|
385
|
-
place-content: start end;
|
|
386
|
-
gap: var(--sp-1);
|
|
387
|
-
padding: var(--sp-1);
|
|
388
|
-
background: var(--surface);
|
|
389
|
-
backdrop-filter: var(--blur);
|
|
390
|
-
border: 1px solid var(--border);
|
|
391
|
-
border-radius: var(--radius);
|
|
392
|
-
overflow: hidden;
|
|
393
|
-
width: auto;
|
|
394
|
-
height: auto;
|
|
395
|
-
transition:
|
|
396
|
-
width var(--dur-enter) var(--ease-standard),
|
|
397
|
-
height var(--dur-enter) var(--ease-standard);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/* Collapsed: fixed size = 1 button + padding + border. Content is
|
|
401
|
-
right-aligned (justify-content:end) so the toggle (col 5) stays at
|
|
402
|
-
the right edge, visible within the clipped area. --pill-outer
|
|
403
|
-
includes the 2px border (box-sizing:border-box); omitting it left
|
|
404
|
-
the content box 2px short, pinning the button top-right with
|
|
405
|
-
unequal bottom/left gaps. */
|
|
406
|
-
.key-toolbar.collapsed {
|
|
407
|
-
width: var(--pill-outer);
|
|
408
|
-
height: var(--pill-outer);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/* Grid placement: toggle at col 5 row 1 (top-right). */
|
|
412
|
-
.kb-toggle {
|
|
413
|
-
grid-area: 1 / 5;
|
|
414
|
-
position: relative;
|
|
415
|
-
}
|
|
416
|
-
.kb-r1c1 {
|
|
417
|
-
grid-area: 1 / 1;
|
|
418
|
-
}
|
|
419
|
-
.kb-r1c2 {
|
|
420
|
-
grid-area: 1 / 2;
|
|
421
|
-
}
|
|
422
|
-
.kb-r1c3 {
|
|
423
|
-
grid-area: 1 / 3;
|
|
424
|
-
}
|
|
425
|
-
.kb-r1c4 {
|
|
426
|
-
grid-area: 1 / 4;
|
|
427
|
-
}
|
|
428
|
-
.kb-r2c1 {
|
|
429
|
-
grid-area: 2 / 1;
|
|
430
|
-
}
|
|
431
|
-
.kb-r2c2 {
|
|
432
|
-
grid-area: 2 / 2;
|
|
433
|
-
}
|
|
434
|
-
.kb-r2c3 {
|
|
435
|
-
grid-area: 2 / 3;
|
|
436
|
-
}
|
|
437
|
-
.kb-r2c4 {
|
|
438
|
-
grid-area: 2 / 4;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/* Keys fade in/out during open/close. The container's width/height
|
|
442
|
-
transition handles the background slide; opacity handles the
|
|
443
|
-
individual button appearance. Stagger the fade slightly after
|
|
444
|
-
the container starts growing (delay on open) and before it
|
|
445
|
-
finishes shrinking (no delay on close). */
|
|
446
|
-
.kb-key {
|
|
447
|
-
opacity: 1;
|
|
448
|
-
transition: opacity var(--dur-standard) var(--ease-enter) 0.05s;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
.key-toolbar.collapsed .kb-key {
|
|
452
|
-
opacity: 0;
|
|
453
|
-
transition: opacity var(--dur-exit) var(--ease-exit);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/* Shared button */
|
|
457
|
-
.key-toolbar button {
|
|
458
|
-
width: var(--touch-target);
|
|
459
|
-
height: var(--touch-target);
|
|
460
|
-
background: transparent;
|
|
461
|
-
color: var(--text);
|
|
462
|
-
border: none;
|
|
463
|
-
border-radius: var(--radius-inner);
|
|
464
|
-
font-size: 0.9375rem;
|
|
465
|
-
font-family: var(--font-ui);
|
|
466
|
-
font-weight: 500;
|
|
467
|
-
cursor: pointer;
|
|
468
|
-
touch-action: manipulation;
|
|
469
|
-
user-select: none;
|
|
470
|
-
display: flex;
|
|
471
|
-
align-items: center;
|
|
472
|
-
justify-content: center;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
.key-toolbar button:active {
|
|
476
|
-
background: var(--text-active);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/* Sticky Ctrl, armed: inverted fill so the toggle state reads clearly
|
|
480
|
-
in the monochrome palette (stays filled while pressed). */
|
|
481
|
-
.kb-key.armed,
|
|
482
|
-
.kb-key.armed:active {
|
|
483
|
-
background: var(--text);
|
|
484
|
-
color: var(--bg);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
.key-toolbar button svg {
|
|
488
|
-
width: var(--icon);
|
|
489
|
-
height: var(--icon);
|
|
490
|
-
stroke: currentcolor;
|
|
491
|
-
fill: none;
|
|
492
|
-
stroke-width: 2;
|
|
493
|
-
stroke-linecap: round;
|
|
494
|
-
stroke-linejoin: round;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/* Toggle icon crossfade. Both icons are stacked absolute so they
|
|
498
|
-
can independently fade + rotate during the open<->close
|
|
499
|
-
transition. inset:0 + margin:auto centers them within the
|
|
500
|
-
button (works because the SVGs carry explicit width/height); a
|
|
501
|
-
bare `position: absolute` would pin them at the top-left, giving
|
|
502
|
-
the toggle visibly unequal left/right padding when collapsed. */
|
|
503
|
-
.kb-toggle .icon-hamburger,
|
|
504
|
-
.kb-toggle .icon-close {
|
|
505
|
-
position: absolute;
|
|
506
|
-
inset: 0;
|
|
507
|
-
margin: auto;
|
|
508
|
-
transition:
|
|
509
|
-
opacity var(--dur-standard) linear,
|
|
510
|
-
transform var(--dur-standard) linear;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
.kb-toggle .icon-close {
|
|
514
|
-
opacity: 1;
|
|
515
|
-
transform: rotate(0deg);
|
|
516
|
-
}
|
|
517
|
-
.kb-toggle .icon-hamburger {
|
|
518
|
-
opacity: 0;
|
|
519
|
-
transform: rotate(-90deg);
|
|
520
|
-
}
|
|
521
|
-
.key-toolbar.collapsed .kb-toggle .icon-close {
|
|
522
|
-
opacity: 0;
|
|
523
|
-
transform: rotate(90deg);
|
|
524
|
-
}
|
|
525
|
-
.key-toolbar.collapsed .kb-toggle .icon-hamburger {
|
|
526
|
-
opacity: 1;
|
|
527
|
-
transform: rotate(0deg);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/* No-transition on first render */
|
|
531
|
-
.key-toolbar.no-transition,
|
|
532
|
-
.key-toolbar.no-transition .kb-toggle .icon-hamburger,
|
|
533
|
-
.key-toolbar.no-transition .kb-toggle .icon-close {
|
|
534
|
-
transition: none;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* ===== web-terminal-ui design system: primitives =====
|
|
2
|
+
The kernel builds named layout regions (wt-region-*) as siblings of the
|
|
3
|
+
terminal surface (.term, styled in 02-terminal.css); features mount chrome
|
|
4
|
+
into region slots. The regions are transparent groupings (display: contents)
|
|
5
|
+
so each feature's element floats over the root-filling terminal, and an
|
|
6
|
+
absent feature contributes nothing.
|
|
7
|
+
|
|
8
|
+
The stylesheet is layered by concern and concatenated in manifest order:
|
|
9
|
+
tokens (00) -> scope + layout modes (01) -> terminal core (02) -> these
|
|
10
|
+
shared primitives (10) -> per-feature chrome (20-23, 30-31, one file per
|
|
11
|
+
feature so a per-preset manifest ships only what its composition mounts) ->
|
|
12
|
+
animations (40, where every .wt-animate-gated transition/keyframe lives, so
|
|
13
|
+
motion is opt-in from one place). Cascade order is the manifest file order.
|
|
14
|
+
|
|
15
|
+
Primitives are the building blocks every feature reuses: the transparent
|
|
16
|
+
layout regions, the 44px touch button, and the toast surface. */
|
|
17
|
+
|
|
18
|
+
:where(.wt-root) .wt-region,
|
|
19
|
+
:where(.wt-root) .wt-slot {
|
|
20
|
+
display: contents;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ===== Shared button primitive (44px touch target) ===== */
|
|
24
|
+
:where(.wt-root) .wt-btn {
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
min-width: var(--touch-target);
|
|
29
|
+
min-height: var(--touch-target);
|
|
30
|
+
background: transparent;
|
|
31
|
+
color: var(--text);
|
|
32
|
+
border: none;
|
|
33
|
+
border-radius: var(--radius-inner);
|
|
34
|
+
font: inherit;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
touch-action: manipulation;
|
|
37
|
+
user-select: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:where(.wt-root) .wt-btn:active {
|
|
41
|
+
background: var(--text-active);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Keyboard-focus ring for every chrome control (all interactive chrome is a
|
|
45
|
+
real <button>: tabs, +, keyboard, switcher rows, menu items, the FAB).
|
|
46
|
+
:focus-visible only — a tap/click never shows the ring (the UA heuristic),
|
|
47
|
+
so the touch-first surfaces stay clean while Tab-key navigation gets the
|
|
48
|
+
family's accent ring (the vibekit/subflux convention: 2px accent, 2px out).
|
|
49
|
+
Follows each control's border-radius; themeable via --accent like the "+"
|
|
50
|
+
glyphs. The terminal's own hidden textarea is not a button and keeps its
|
|
51
|
+
outline: none (02-terminal.css). */
|
|
52
|
+
:where(.wt-root) button:focus-visible {
|
|
53
|
+
outline: 2px solid var(--accent);
|
|
54
|
+
outline-offset: 2px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:where(.wt-root) .wt-btn svg {
|
|
58
|
+
width: var(--icon);
|
|
59
|
+
height: var(--icon);
|
|
60
|
+
stroke: currentcolor;
|
|
61
|
+
fill: none;
|
|
62
|
+
stroke-width: 2;
|
|
63
|
+
stroke-linecap: round;
|
|
64
|
+
stroke-linejoin: round;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ===== Toast (kernel shared primitive) =====
|
|
68
|
+
Top-centre on every form factor (both desktop and the tabbed mobile UI dock a
|
|
69
|
+
bar at the bottom, so a bottom toast would collide with it; top-centre is
|
|
70
|
+
clear and the same everywhere). The vv-top/safe-top offset keeps it below the
|
|
71
|
+
notch and follows iOS's visual-viewport shift. Absolute against .wt-root,
|
|
72
|
+
like all chrome (identical geometry in wt-viewport mode; see 01-scope.css). */
|
|
73
|
+
:where(.wt-root) .wt-toast {
|
|
74
|
+
position: absolute;
|
|
75
|
+
top: calc(var(--safe-top, 0px) + var(--vv-top, 0px) + var(--sp-3));
|
|
76
|
+
left: 50%;
|
|
77
|
+
transform: translateX(-50%);
|
|
78
|
+
max-width: calc(100% - 2 * var(--sp-3));
|
|
79
|
+
padding: var(--sp-1) var(--sp-3);
|
|
80
|
+
background: var(--surface);
|
|
81
|
+
backdrop-filter: var(--blur);
|
|
82
|
+
border: 1px solid oklch(70% 0.1 160deg / 30%);
|
|
83
|
+
border-radius: var(--radius);
|
|
84
|
+
color: var(--text);
|
|
85
|
+
font-family: var(--font-ui);
|
|
86
|
+
font-size: 0.8125rem;
|
|
87
|
+
font-weight: 500;
|
|
88
|
+
text-align: center;
|
|
89
|
+
z-index: 40;
|
|
90
|
+
pointer-events: none; /* passive status surface: never steal taps from the terminal beneath */
|
|
91
|
+
display: none;
|
|
92
|
+
opacity: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:where(.wt-root) .wt-toast.visible {
|
|
96
|
+
display: block;
|
|
97
|
+
opacity: 1;
|
|
98
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* ===== Connection banner (connectionBanner feature) =====
|
|
2
|
+
Top-centre on every form factor (same rationale as the toast: a bottom bar is
|
|
3
|
+
always docked now). Absolute against .wt-root, like all chrome (identical
|
|
4
|
+
geometry in wt-viewport mode; see 01-scope.css). */
|
|
5
|
+
:where(.wt-root) .wt-conn-banner {
|
|
6
|
+
position: absolute;
|
|
7
|
+
top: calc(var(--safe-top, 0px) + var(--vv-top, 0px) + var(--sp-3));
|
|
8
|
+
left: 50%;
|
|
9
|
+
transform: translateX(-50%);
|
|
10
|
+
max-width: calc(100% - 2 * var(--sp-3));
|
|
11
|
+
padding: var(--sp-1) var(--sp-3);
|
|
12
|
+
background: var(--surface);
|
|
13
|
+
backdrop-filter: var(--blur);
|
|
14
|
+
border: 1px solid var(--border);
|
|
15
|
+
border-radius: var(--radius);
|
|
16
|
+
color: var(--text);
|
|
17
|
+
font-family: var(--font-ui);
|
|
18
|
+
font-size: 0.8125rem;
|
|
19
|
+
font-weight: 500;
|
|
20
|
+
text-align: center;
|
|
21
|
+
z-index: 20;
|
|
22
|
+
pointer-events: none; /* passive status surface: never steal taps from the terminal beneath */
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:where(.wt-root) .wt-conn-banner.visible {
|
|
27
|
+
display: block;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:where(.wt-root) .wt-conn-banner[data-state="reconnecting"] {
|
|
31
|
+
border-color: oklch(70% 0.15 60deg / 40%);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:where(.wt-root) .wt-conn-banner[data-state="offline"] {
|
|
35
|
+
color: #f55;
|
|
36
|
+
border-color: oklch(60% 0.2 25deg / 50%);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:where(.wt-root) .wt-conn-banner[data-state="restarted"] {
|
|
40
|
+
color: #fa5;
|
|
41
|
+
border-color: oklch(70% 0.15 60deg / 40%);
|
|
42
|
+
}
|