@cplieger/web-terminal-ui 3.5.0 → 4.0.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.
@@ -2,26 +2,29 @@
2
2
  The bottom bar (switcher) is the mobile tab surface, the externally-driven key
3
3
  grid docks above it, and the overview sheet lists every tab. The desktop strip
4
4
  is hidden only on a narrow coarse-pointer device (a phone) and these appear
5
- instead; all motion (bar number fade, sheet slide) lives in 07-animations.css. */
5
+ instead; all motion (bar number fade, sheet slide) lives in 40-animations.css. */
6
6
 
7
7
  /* The switcher is used only on a NARROW coarse-pointer device (a phone), where
8
8
  the top strip is hidden so tabs live in the thumb zone. A wide touchscreen (an
9
9
  iPad) and any fine-pointer device keep the multi-tab top strip — a single
10
- giant tab wastes a big screen. This media query MUST stay in lockstep with
11
- SWITCHER_MEDIA in src/features/tabs.ts and the complementary strip clearance
12
- in css/05-tabs.css. */
13
- .wt-switcher {
10
+ giant tab wastes a big screen. "Narrow" is the kernel's .wt-narrow root class
11
+ (ROOT width at the single kernel breakpoint — so an embedded terminal in a
12
+ narrow panel counts as narrow); this file pairs it with (pointer: coarse),
13
+ and css/30-tabs.css carries the complementary strip clearance. */
14
+ :where(.wt-root) .wt-switcher {
14
15
  display: none;
15
16
  }
16
17
 
17
- @media (pointer: coarse) and (width <= 600px) {
18
- .wt-tab-bar,
19
- .wt-tab-new {
18
+ @media (pointer: coarse) {
19
+ :where(.wt-root.wt-narrow) .wt-tab-bar,
20
+ :where(.wt-root.wt-narrow) .wt-tab-new {
20
21
  display: none;
21
22
  }
22
23
 
23
- .wt-switcher {
24
- position: fixed;
24
+ :where(.wt-root.wt-narrow) .wt-switcher {
25
+ /* Absolute against .wt-root, like all chrome (identical geometry in
26
+ wt-viewport mode; see 01-scope.css). */
27
+ position: absolute;
25
28
  left: 0;
26
29
  right: 0;
27
30
  /* Lift above the soft keyboard rather than hiding, so the switch surface
@@ -39,7 +42,7 @@
39
42
  /* The always-visible bar row: active tab + keyboard + "+". The padding gives the
40
43
  active-tab chip breathing room above/below within the bar, matching the
41
44
  desktop strip (both use --sp-2). */
42
- .wt-switcher-bar {
45
+ :where(.wt-root) .wt-switcher-bar {
43
46
  display: flex;
44
47
  align-items: stretch;
45
48
  gap: var(--sp-1);
@@ -48,8 +51,8 @@
48
51
 
49
52
  /* The other tabs, revealed below the bar row on expand (the bar slides up).
50
53
  Collapsed to zero height; expanded caps the height and scrolls. The max-height
51
- transition lives in 07-animations.css. */
52
- .wt-switcher-list {
54
+ transition lives in 40-animations.css. */
55
+ :where(.wt-root) .wt-switcher-list {
53
56
  list-style: none;
54
57
  margin: 0;
55
58
  /* Match the bar's horizontal padding (--sp-2) so a listed row lines up with
@@ -73,7 +76,7 @@
73
76
  box-sizing: border-box;
74
77
  }
75
78
 
76
- .wt-switcher-expanded .wt-switcher-list {
79
+ :where(.wt-root) .wt-switcher-expanded .wt-switcher-list {
77
80
  /* Cap against the DYNAMIC viewport so a shown mobile browser toolbar can't push the list
78
81
  (and the bar above it) off the top; the switcher's own bottom: kb-inset already lifts it
79
82
  clear of the soft keyboard. */
@@ -90,7 +93,7 @@
90
93
  sibling over the surface's reserved right padding). The wrap is the flex item
91
94
  that grows in the bar (and fills the space when the keyboard button collapses
92
95
  on expand). */
93
- .wt-switcher-current-wrap {
96
+ :where(.wt-root) .wt-switcher-current-wrap {
94
97
  position: relative;
95
98
  flex: 1 1 auto;
96
99
  display: flex;
@@ -101,7 +104,7 @@
101
104
  overflow: hidden;
102
105
  }
103
106
 
104
- .wt-switcher-current {
107
+ :where(.wt-root) .wt-switcher-current {
105
108
  position: relative;
106
109
  flex: 1 1 auto;
107
110
  display: flex;
@@ -130,7 +133,7 @@
130
133
  padding, mirroring the listed rows' close. Hover fill only on real hover
131
134
  pointers so it never sticks white after a touch (a removed tab shifts the
132
135
  list under the finger otherwise). */
133
- .wt-switcher-current-close {
136
+ :where(.wt-root) .wt-switcher-current-close {
134
137
  position: absolute;
135
138
  /* Centre vertically via top/bottom (not a translateY transform), so transform
136
139
  is free for the swipe translate — the close moves with the dot + label. And
@@ -142,14 +145,14 @@
142
145
  }
143
146
 
144
147
  @media (hover: hover) {
145
- .wt-switcher-current-close:hover {
148
+ :where(.wt-root) .wt-switcher-current-close:hover {
146
149
  color: var(--text);
147
150
  }
148
151
  }
149
152
 
150
153
  /* Press feedback: brighten the active fill slightly (a lighter press over an
151
154
  already-filled row). */
152
- .wt-switcher-current:active {
155
+ :where(.wt-root) .wt-switcher-current:active {
153
156
  background: color-mix(in oklch, var(--tab-active-bg), var(--tab-active-fg) 12%);
154
157
  }
155
158
 
@@ -157,7 +160,7 @@
157
160
  parts out; a horizontal swipe translates it (clipped by the row's
158
161
  overflow: hidden) so the active tab area physically slides with the finger,
159
162
  and the incoming tab's label slides in from the swipe direction on commit. */
160
- .wt-switcher-current-inner {
163
+ :where(.wt-root) .wt-switcher-current-inner {
161
164
  display: flex;
162
165
  align-items: center;
163
166
  gap: var(--sp-2);
@@ -165,7 +168,7 @@
165
168
  min-width: 0;
166
169
  }
167
170
 
168
- .wt-switcher-label {
171
+ :where(.wt-root) .wt-switcher-label {
169
172
  flex: 1 1 auto;
170
173
  overflow: hidden;
171
174
  text-overflow: ellipsis;
@@ -178,7 +181,7 @@
178
181
  just the wt-btn min-width) makes it animatable: when the tab list expands the
179
182
  button collapses to zero width and fades out (the key grid is closed too, in
180
183
  JS), and the active row flex-grows to fill the freed space. */
181
- .wt-switcher-kb {
184
+ :where(.wt-root) .wt-switcher-kb {
182
185
  /* Rigid 44px box like the "+" and switch buttons (flex: none), so a long
183
186
  active-tab title that overflows the bar is absorbed entirely by the
184
187
  ellipsizing active-tab field, not shared with this button. It is otherwise
@@ -199,11 +202,11 @@
199
202
  overflow: hidden;
200
203
  }
201
204
 
202
- .wt-switcher-kb[hidden] {
205
+ :where(.wt-root) .wt-switcher-kb[hidden] {
203
206
  display: none;
204
207
  }
205
208
 
206
- .wt-switcher-kb.wt-active {
209
+ :where(.wt-root) .wt-switcher-kb.wt-active {
207
210
  background: var(--tab-hover-bg);
208
211
  color: var(--tab-active-fg);
209
212
  }
@@ -212,12 +215,12 @@
212
215
  Ctrl key uses) while a Ctrl press is pending, so the modifier stays visible
213
216
  even with the key grid — and its own Ctrl button — closed. After .wt-active so
214
217
  the armed fill wins when the grid is also open. */
215
- .wt-switcher-kb.wt-armed {
218
+ :where(.wt-root) .wt-switcher-kb.wt-armed {
216
219
  background: var(--text);
217
220
  color: var(--bg);
218
221
  }
219
222
 
220
- .wt-switcher-expanded .wt-switcher-kb {
223
+ :where(.wt-root) .wt-switcher-expanded .wt-switcher-kb {
221
224
  width: 0;
222
225
  /* Absorb the flex gap the collapsed button would otherwise leave behind. */
223
226
  margin-inline-start: calc(-1 * var(--sp-1));
@@ -230,9 +233,9 @@
230
233
  absolutely placed in the top-right corner. It only earns its place with ≥2
231
234
  tabs (a single tab has nothing to switch to), so it is COLLAPSED to zero width
232
235
  by default and revealed by .wt-switcher-multi (the tabs feature sets it on tab
233
- count); the appear/collapse is animated in 07-animations.css and the active
236
+ count); the appear/collapse is animated in 40-animations.css and the active
234
237
  chip shrinks/grows to fill the freed space in lockstep (flex layout). */
235
- .wt-switcher-switch {
238
+ :where(.wt-root) .wt-switcher-switch {
236
239
  position: relative;
237
240
  flex: none;
238
241
  width: 0;
@@ -247,7 +250,7 @@
247
250
  }
248
251
 
249
252
  /* Revealed once a second tab exists: full 44px box, interactive, no gap eat. */
250
- .wt-switcher-multi .wt-switcher-switch {
253
+ :where(.wt-root) .wt-switcher-multi .wt-switcher-switch {
251
254
  width: var(--touch-target);
252
255
  opacity: 1;
253
256
  pointer-events: auto;
@@ -256,10 +259,10 @@
256
259
 
257
260
  /* When the tab list expands the switch button collapses too (its job is done:
258
261
  the list is open), mirroring the keyboard button's collapse in the same
259
- animation (07-animations.css animates width + margin + opacity). Placed after
262
+ animation (40-animations.css animates width + margin + opacity). Placed after
260
263
  the .wt-switcher-multi reveal so it wins when both apply (expanded, two or
261
264
  more tabs). */
262
- .wt-switcher-expanded .wt-switcher-switch {
265
+ :where(.wt-root) .wt-switcher-expanded .wt-switcher-switch {
263
266
  width: 0;
264
267
  opacity: 0;
265
268
  margin-inline-start: calc(-1 * var(--sp-1));
@@ -267,17 +270,17 @@
267
270
  }
268
271
 
269
272
  @media (hover: hover) {
270
- .wt-switcher-switch:hover {
273
+ :where(.wt-root) .wt-switcher-switch:hover {
271
274
  background: var(--tab-hover-bg);
272
275
  }
273
276
  }
274
277
 
275
278
  /* Latest-wins background-tab notification dot on the switch button — the moved
276
279
  attention cue. Amber = a background terminal needs input, green = a background
277
- turn finished. Reuses the exact .wt-status-dot colours (05-tabs.css) so it
280
+ turn finished. Reuses the exact .wt-status-dot colours (30-tabs.css) so it
278
281
  matches the per-tab dots. Hidden until the tabs feature sets data-status;
279
282
  cleared when the list opens (expandSwitcher). */
280
- .wt-switcher-switch-dot {
283
+ :where(.wt-root) .wt-switcher-switch-dot {
281
284
  position: absolute;
282
285
  top: 6px;
283
286
  right: 6px;
@@ -285,10 +288,10 @@
285
288
 
286
289
  /* The dot carries the .wt-status-dot class, so its size, shape, and colour come
287
290
  straight from the shared .wt-status-dot[data-status="input"|"done"] rules
288
- (05-tabs.css) — the SAME dot colours every tab already uses, defined once.
291
+ (30-tabs.css) — the SAME dot colours every tab already uses, defined once.
289
292
  .wt-status-dot is display:none by default; force it visible only while a
290
293
  notification is set (data-status present). */
291
- .wt-switcher-switch-dot[data-status] {
294
+ :where(.wt-root) .wt-switcher-switch-dot[data-status] {
292
295
  display: block;
293
296
  }
294
297
 
@@ -296,72 +299,54 @@
296
299
  permanent and prominent. Hover fill is gated to real hover pointers: on touch,
297
300
  :hover sticks after a tap (no pointer-leave), so the purple would linger — the
298
301
  press feedback there comes from :active, which clears on release. */
299
- .wt-switcher-new {
302
+ :where(.wt-root) .wt-switcher-new {
300
303
  flex: none;
301
304
  color: var(--accent);
302
305
  }
303
306
 
304
307
  @media (hover: hover) {
305
- .wt-switcher-new:hover {
308
+ :where(.wt-root) .wt-switcher-new:hover {
306
309
  background: var(--tab-hover-bg);
307
310
  }
308
311
  }
309
312
 
310
- .wt-switcher-new:active {
313
+ :where(.wt-root) .wt-switcher-new:active {
311
314
  background: var(--tab-hover-bg);
312
315
  }
313
316
 
314
317
  /* ===== Externally-driven key grid (tabbed presets, WS3) =====
315
- In the tabbed presets the mobile toolbar hides its own toggle (the tab bar's
316
- keyboard button drives it) and the grid docks above the bar on the right, to
317
- the left of where scroll-to-bottom sits. It collapses to fully hidden, since
318
- there is no in-grid toggle to keep visible. */
319
- @media (pointer: coarse) and (width <= 600px) {
320
- .wt-toolbar-external .kb-toggle {
318
+ In the tabbed presets the mobile toolbar hides its own toggle (a tab-chrome
319
+ keyboard button drives it — the switcher's on a narrow phone, the strip's
320
+ .wt-tab-kb on a wide touchscreen) and the grid docks bottom-right above the
321
+ tab chrome, to the left of where scroll-to-bottom sits. It collapses to
322
+ fully hidden, since there is no in-grid toggle to keep visible. The BASE
323
+ rules here are the wide-touchscreen (strip) placement — above --wt-tabbar-h;
324
+ the .wt-narrow override below re-anchors above the switcher bar instead
325
+ (--wt-switcher-h). Everything else (toggle hidden, right offset, grid
326
+ columns, collapsed fully hidden) is identical in both modes. */
327
+ @media (pointer: coarse) {
328
+ :where(.wt-root) .wt-toolbar-external .kb-toggle {
321
329
  display: none;
322
330
  }
323
331
 
324
- .key-toolbar.wt-toolbar-external {
332
+ :where(.wt-root) .key-toolbar.wt-toolbar-external {
325
333
  top: auto;
326
334
  right: calc(var(--sp-3) + var(--touch-target) + var(--sp-2));
327
335
  bottom: calc(
328
- var(--wt-switcher-h) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
336
+ var(--wt-tabbar-h, 44px) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
329
337
  );
330
338
  grid-template-columns: repeat(4, var(--touch-target));
331
339
  }
332
340
 
333
- .key-toolbar.wt-toolbar-external.collapsed {
341
+ :where(.wt-root) .key-toolbar.wt-toolbar-external.collapsed {
334
342
  display: none;
335
343
  }
336
- }
337
344
 
338
- /* Desktop tab strip on a WIDE touchscreen (an iPad): the mobile switcher is
339
- gone, so the desktop strip carries its OWN keyboard button (.wt-tab-kb, built
340
- by the same factory as the switcher's — see css/05-tabs.css + tabs.ts) to open
341
- the key grid. So, exactly like switcher mode, the toolbar's own toggle is
342
- hidden and the collapsed grid fully hidden — the tab-strip keyboard button is
343
- the sole control (this replaces the old CSS-only workaround that un-hid the
344
- toolbar's own toggle here). The grid still docks bottom-right ABOVE the strip:
345
- above the tabs (never overlapping them, even when many are open and the strip
346
- scrolls) and to the left of the scroll-to-bottom FAB (same right offset as
347
- switcher mode). Complements the strip-shown breakpoint in css/05-tabs.css;
348
- keep in lockstep with SWITCHER_MEDIA in src/features/tabs.ts. */
349
- @media (pointer: coarse) and (width >= 601px) {
350
- .wt-toolbar-external .kb-toggle {
351
- display: none;
352
- }
353
-
354
- .key-toolbar.wt-toolbar-external {
355
- top: auto;
356
- right: calc(var(--sp-3) + var(--touch-target) + var(--sp-2));
345
+ /* Narrow phone (mobile switcher mode): dock above the switcher bar. */
346
+ :where(.wt-root.wt-narrow) .key-toolbar.wt-toolbar-external {
357
347
  bottom: calc(
358
- var(--wt-tabbar-h, 44px) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
348
+ var(--wt-switcher-h) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
359
349
  );
360
- grid-template-columns: repeat(4, var(--touch-target));
361
- }
362
-
363
- .key-toolbar.wt-toolbar-external.collapsed {
364
- display: none;
365
350
  }
366
351
  }
367
352
 
@@ -369,12 +354,12 @@
369
354
  One row per OTHER tab in the expanded list: a stretched select target (dot +
370
355
  label) with the close (x) laid inside at the right, mirroring the desktop
371
356
  tab's merged chrome. */
372
- .wt-switcher-row {
357
+ :where(.wt-root) .wt-switcher-row {
373
358
  position: relative;
374
359
  display: flex;
375
360
  }
376
361
 
377
- .wt-switcher-row-select {
362
+ :where(.wt-root) .wt-switcher-row-select {
378
363
  flex: 1 1 auto;
379
364
  display: flex;
380
365
  align-items: center;
@@ -394,7 +379,7 @@
394
379
  }
395
380
 
396
381
  @media (hover: hover) {
397
- .wt-switcher-row-select:hover {
382
+ :where(.wt-root) .wt-switcher-row-select:hover {
398
383
  background: color-mix(in oklch, var(--tab-bg), var(--text) 15%);
399
384
  }
400
385
  }
@@ -403,11 +388,11 @@
403
388
  chip (the same 15% --text lift the desktop tabs use), not the translucent
404
389
  --tab-hover-bg wash that would erase the chip fill. The active tab is set apart
405
390
  by its accent border, so a lit inactive row is never mistaken for it. */
406
- .wt-switcher-row-select:active {
391
+ :where(.wt-root) .wt-switcher-row-select:active {
407
392
  background: color-mix(in oklch, var(--tab-bg), var(--text) 15%);
408
393
  }
409
394
 
410
- .wt-switcher-row-label {
395
+ :where(.wt-root) .wt-switcher-row-label {
411
396
  flex: 1 1 auto;
412
397
  overflow: hidden;
413
398
  text-overflow: ellipsis;
@@ -415,7 +400,7 @@
415
400
  }
416
401
 
417
402
  /* The close sits flush at the row's right edge (no gap), vertically centered. */
418
- .wt-switcher-row-close {
403
+ :where(.wt-root) .wt-switcher-row-close {
419
404
  position: absolute;
420
405
  top: 50%;
421
406
  right: 0;
@@ -427,7 +412,7 @@
427
412
  when closing a tab shifts the list under the finger the newly-under close
428
413
  would latch white. Touch feedback comes from the surrounding row's :active. */
429
414
  @media (hover: hover) {
430
- .wt-switcher-row-close:hover {
415
+ :where(.wt-root) .wt-switcher-row-close:hover {
431
416
  color: var(--text);
432
417
  }
433
418
  }
@@ -4,10 +4,10 @@
4
4
  base rules in 03-06 carry the resting state, and these rules add the
5
5
  transitions and keyframes on top. A few pieces of motion are intentionally NOT
6
6
  gated on .wt-animate and animate unconditionally: terminal-core motion (cursor
7
- and SGR blink) and the mobile key toolbar open/close in 02-app.css, plus the
8
- working-status-dot pulse in 05-tabs.css (motion is its only non-hue cue, per
9
- WCAG 1.4.1). prefers-reduced-motion still defeats all of it via the reset in
10
- 01-reset.css. Keeping the chrome-surface motion in one file is the
7
+ and SGR blink) and the mobile key toolbar open/close in 23-toolbar.css, plus the
8
+ working-status-dot pulse in 30-tabs.css (motion is its only non-hue cue, per
9
+ WCAG 1.4.1). prefers-reduced-motion still defeats all of it via the scoped
10
+ kill-switch in 01-scope.css. Keeping the chrome-surface motion in one file is the
11
11
  design-system seam for "does this chrome animate, and how". */
12
12
 
13
13
  /* Fade the toast OUT on hide (display allow-discrete holds display:block for
@@ -21,16 +21,16 @@
21
21
  shared opacity transition would be inert while the `display allow-discrete`
22
22
  half held them fully visible for --dur-standard before popping out on hide.
23
23
  They show and hide instantly. */
24
- .wt-animate .wt-toast {
24
+ :where(.wt-root.wt-animate) .wt-toast {
25
25
  transition:
26
26
  opacity var(--dur-standard) var(--ease-standard),
27
27
  display var(--dur-standard) allow-discrete;
28
28
  }
29
29
 
30
30
  /* Tab chrome micro-transitions (hover/active color shifts). */
31
- .wt-animate .wt-tab,
32
- .wt-animate .wt-tab-close,
33
- .wt-animate .wt-tab-new {
31
+ :where(.wt-root.wt-animate) .wt-tab,
32
+ :where(.wt-root.wt-animate) .wt-tab-close,
33
+ :where(.wt-root.wt-animate) .wt-tab-new {
34
34
  transition:
35
35
  background-color var(--dur-micro) var(--ease-standard),
36
36
  color var(--dur-micro) var(--ease-standard);
@@ -41,7 +41,7 @@
41
41
  immediately with no exit animation, so the strip reflows in one frame (a
42
42
  delayed exit made the "+" teleport and put a last-tab replacement in the wrong
43
43
  slot). scale() is a transform, so the enter never shifts the "+". */
44
- .wt-animate .wt-tab-enter {
44
+ :where(.wt-root.wt-animate) .wt-tab-enter {
45
45
  animation: wt-tab-in var(--dur-enter) var(--ease-enter);
46
46
  }
47
47
 
@@ -58,7 +58,7 @@
58
58
  — animating only max-height let the padding snap away instantly at the start
59
59
  of a collapse (a choppy, two-step close). The tabs feature clears the rows
60
60
  after the collapse. */
61
- .wt-animate .wt-switcher-list {
61
+ :where(.wt-root.wt-animate) .wt-switcher-list {
62
62
  transition:
63
63
  max-height var(--dur-enter) var(--ease-standard),
64
64
  padding var(--dur-enter) var(--ease-standard);
@@ -67,7 +67,7 @@
67
67
  /* When the tab list expands, the keyboard button collapses to zero width and
68
68
  fades (the key grid is closed in JS); the active row flex-grows into the freed
69
69
  space in lockstep as the button's width animates. */
70
- .wt-animate .wt-switcher-kb {
70
+ :where(.wt-root.wt-animate) .wt-switcher-kb {
71
71
  transition:
72
72
  width var(--dur-enter) var(--ease-standard),
73
73
  margin var(--dur-enter) var(--ease-standard),
@@ -78,7 +78,7 @@
78
78
  at one tab (.wt-switcher-multi, tabs feature). Animate its width + margin +
79
79
  opacity so it slides in from zero width — the active-tab chip shrinks to make
80
80
  room in lockstep (flex) — rather than popping. Mirrors the keyboard button. */
81
- .wt-animate .wt-switcher-switch {
81
+ :where(.wt-root.wt-animate) .wt-switcher-switch {
82
82
  transition:
83
83
  width var(--dur-enter) var(--ease-standard),
84
84
  margin var(--dur-enter) var(--ease-standard),
@@ -89,15 +89,15 @@
89
89
  reads as a change rather than a flicker. A swipe additionally slides the
90
90
  content in from the swipe's direction (next from the right, prev from the
91
91
  left, WS5). */
92
- .wt-animate .term.wt-switching .term-output {
92
+ :where(.wt-root.wt-animate) .term.wt-switching .term-output {
93
93
  animation: wt-switch-in var(--dur-standard) var(--ease-standard);
94
94
  }
95
95
 
96
- .wt-animate .term.wt-switching-next .term-output {
96
+ :where(.wt-root.wt-animate) .term.wt-switching-next .term-output {
97
97
  animation: wt-switch-next var(--dur-standard) var(--ease-standard);
98
98
  }
99
99
 
100
- .wt-animate .term.wt-switching-prev .term-output {
100
+ :where(.wt-root.wt-animate) .term.wt-switching-prev .term-output {
101
101
  animation: wt-switch-prev var(--dur-standard) var(--ease-standard);
102
102
  }
103
103
 
package/css/MANIFEST CHANGED
@@ -1,12 +1,20 @@
1
- # web-terminal-ui CSS bundle manifest
2
- # Files concatenated in declared order to produce the served style.css.
3
- # The order IS the cascade: tokens + reset + terminal core, then the v3 design
4
- # system (shared primitives, then per-feature chrome), then all motion last.
1
+ # web-terminal-ui CSS — the REFERENCE FULL-PAGE bundle.
2
+ # What the full-page product serves (web-terminal-server, web-terminal-kiro,
3
+ # scaffold/index.html): the page kit (page.css — html/body reset + @font-face;
4
+ # full-page hosts ONLY) plus the complete tabbed component set. An EMBEDDING
5
+ # consumer must NOT serve this file's bundle — it concatenates the per-preset
6
+ # manifest matching its composition instead (MANIFEST.single / MANIFEST.touch /
7
+ # MANIFEST.tabbed), which contains no page-level rules.
8
+ # Files concatenate in declared order; the order IS the cascade.
9
+ page.css
5
10
  00-tokens.css
6
- 01-reset.css
7
- 02-app.css
8
- 03-primitives.css
9
- 04-feedback.css
10
- 05-tabs.css
11
- 06-mobile.css
12
- 07-animations.css
11
+ 01-scope.css
12
+ 02-terminal.css
13
+ 10-primitives.css
14
+ 20-connection-banner.css
15
+ 21-scroll-to-bottom.css
16
+ 22-context-menu.css
17
+ 23-toolbar.css
18
+ 30-tabs.css
19
+ 31-switcher.css
20
+ 40-animations.css
@@ -0,0 +1,12 @@
1
+ # Component bundle for presetSingle() compositions (no toolbar, no tabs, no
2
+ # animations feature). Library styles only — root-scoped, no page-level rules;
3
+ # the host page owns its own reset/fonts (full-page hosts use the reference
4
+ # MANIFEST instead, whose page.css carries those).
5
+ # Files concatenate in declared order; the order IS the cascade.
6
+ 00-tokens.css
7
+ 01-scope.css
8
+ 02-terminal.css
9
+ 10-primitives.css
10
+ 20-connection-banner.css
11
+ 21-scroll-to-bottom.css
12
+ 22-context-menu.css
@@ -0,0 +1,16 @@
1
+ # Component bundle for presetTabbed() / presetAgentTabbed() compositions: the
2
+ # touch set plus tabs (strip + mobile switcher) and the animations feature's
3
+ # motion. Library styles only — root-scoped, no page-level rules. A full-page
4
+ # host serves the reference MANIFEST instead (this set + page.css).
5
+ # Files concatenate in declared order; the order IS the cascade.
6
+ 00-tokens.css
7
+ 01-scope.css
8
+ 02-terminal.css
9
+ 10-primitives.css
10
+ 20-connection-banner.css
11
+ 21-scroll-to-bottom.css
12
+ 22-context-menu.css
13
+ 23-toolbar.css
14
+ 30-tabs.css
15
+ 31-switcher.css
16
+ 40-animations.css
@@ -0,0 +1,12 @@
1
+ # Component bundle for presetTouch() compositions (single + the mobile key
2
+ # toolbar). Library styles only — root-scoped, no page-level rules; the host
3
+ # page owns its own reset/fonts (vibekit's embedded panel serves exactly this).
4
+ # Files concatenate in declared order; the order IS the cascade.
5
+ 00-tokens.css
6
+ 01-scope.css
7
+ 02-terminal.css
8
+ 10-primitives.css
9
+ 20-connection-banner.css
10
+ 21-scroll-to-bottom.css
11
+ 22-context-menu.css
12
+ 23-toolbar.css
@@ -1,4 +1,11 @@
1
- /* ===== Reset ===== */
1
+ /* ===== Page kit (full-page hosts ONLY — never ship this to an embedder) =====
2
+ The page-level half of the full-page terminal product: what the HOST PAGE
3
+ owns when the terminal IS the page (web-terminal-server, web-terminal-kiro,
4
+ scaffold/index.html). The component bundle (00-10) never touches the host
5
+ document; everything here is deliberately page-scoped. An embedding consumer
6
+ (vibekit) serves a per-preset manifest instead and owns its own page.
7
+ Concatenated FIRST by the reference MANIFEST. */
8
+
2
9
  *,
3
10
  *::before,
4
11
  *::after {
@@ -11,7 +18,10 @@ body {
11
18
  padding: 0;
12
19
  height: 100%;
13
20
  overflow: hidden;
14
- background: var(--bg);
21
+ /* The pre-JS backdrop (the loading state paints over it). Matches the
22
+ component's --bg default (css/00-tokens.css, oklch(0% 0 0deg)); a themed
23
+ full-page host overrides both together. */
24
+ background: #000;
15
25
  color-scheme: dark;
16
26
  -webkit-font-smoothing: antialiased;
17
27
  /* Disable the browser's landscape text autosizing ("text inflation"): without
@@ -29,23 +39,12 @@ body {
29
39
  touch-action: pan-x pan-y;
30
40
  }
31
41
 
32
- /* stylelint-disable declaration-no-important, time-min-milliseconds --
33
- Canonical prefers-reduced-motion override. !important and near-zero
34
- durations are required to forcibly defeat any animation/transition rule
35
- regardless of specificity, which is the whole point of this a11y reset. */
36
- @media (prefers-reduced-motion: reduce) {
37
- *,
38
- *::before,
39
- *::after {
40
- animation-duration: 0.01ms !important;
41
- animation-iteration-count: 1 !important;
42
- transition-duration: 0.01ms !important;
43
- scroll-behavior: auto !important;
44
- }
45
- }
46
- /* stylelint-enable declaration-no-important, time-min-milliseconds */
47
-
48
- /* ===== Fonts ===== */
42
+ /* ===== Fonts =====
43
+ The terminal web font, served by the full-page host at /vendor/fonts (both
44
+ web-terminal-server and web-terminal-kiro ship these files there; see their
45
+ Dockerfiles). An embedder does not serve this file, so it never inherits
46
+ these URLs — it either ships its own @font-face or lets --font-mono fall back
47
+ to the platform monospace stack. */
49
48
  @font-face {
50
49
  font-family: "MonaspiceNe NFM";
51
50
  src: url("/vendor/fonts/MonaspiceNeNerdFontMono-Regular.otf") format("opentype");
package/package.json CHANGED
@@ -1,12 +1,25 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "3.5.0",
3
+ "version": "4.0.0",
4
4
  "type": "module",
5
5
  "description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
6
6
  "license": "GPL-3.0-or-later",
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
- "./presets": "./src/presets.ts"
9
+ "./presets": "./src/presets.ts",
10
+ "./presets/single": "./src/presets/single.ts",
11
+ "./presets/touch": "./src/presets/touch.ts",
12
+ "./presets/tabbed": "./src/presets/tabbed.ts",
13
+ "./presets/agent-tabbed": "./src/presets/agent-tabbed.ts",
14
+ "./features/clipboard": "./src/features/clipboard.ts",
15
+ "./features/context-menu": "./src/features/context-menu.ts",
16
+ "./features/scroll-to-bottom": "./src/features/scroll-to-bottom.ts",
17
+ "./features/predictive-echo": "./src/features/predictive-echo.ts",
18
+ "./features/connection-banner": "./src/features/connection-banner.ts",
19
+ "./features/mobile-toolbar": "./src/features/mobile-toolbar.ts",
20
+ "./features/tabs": "./src/features/tabs/index.ts",
21
+ "./features/activity-monitor": "./src/features/activity-monitor.ts",
22
+ "./features/animations": "./src/features/animations.ts"
10
23
  },
11
24
  "sideEffects": [
12
25
  "**/*.css"
@@ -25,7 +38,7 @@
25
38
  "verify": "bash scripts/verify.sh"
26
39
  },
27
40
  "peerDependencies": {
28
- "@cplieger/web-terminal-engine": ">=2.4.0"
41
+ "@cplieger/web-terminal-engine": "^2.8.0"
29
42
  },
30
43
  "devDependencies": {
31
44
  "@eslint/js": "10.0.1",
@@ -38,13 +51,13 @@
38
51
  "eslint": "10.7.0",
39
52
  "fast-check": "4.9.0",
40
53
  "happy-dom": "20.10.6",
41
- "html-validate": "11.5.5",
54
+ "html-validate": "11.5.6",
42
55
  "knip": "6.26.0",
43
56
  "prettier": "3.9.5",
44
57
  "stylelint": "17.14.0",
45
58
  "stylelint-config-standard": "40.0.0",
46
59
  "typescript": "npm:@typescript/typescript6@6.0.2",
47
- "typescript-eslint": "8.63.0",
60
+ "typescript-eslint": "8.64.0",
48
61
  "vitest": "4.1.10"
49
62
  },
50
63
  "publishConfig": {
@@ -6,6 +6,11 @@
6
6
  <meta name="color-scheme" content="dark">
7
7
  <title>Web Terminal</title>
8
8
  <!-- App icons and web-app manifest are deployment-specific; add your own. -->
9
+ <!-- /style.css = the package's css/MANIFEST concatenated: the reference
10
+ FULL-PAGE bundle (css/page.css — html/body reset + @font-face expecting
11
+ the font files at /vendor/fonts/ — plus the complete tabbed component
12
+ set). An embedder serves a per-preset manifest (css/MANIFEST.single/
13
+ .touch/.tabbed) instead and passes layout: "container". -->
9
14
  <link rel="stylesheet" href="/style.css">
10
15
  <!-- [html-validate-disable-next no-style-tag -- critical CSS for instant loading indicator] -->
11
16
  <style>