@cplieger/web-terminal-ui 3.3.1 → 3.4.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 +13 -9
- package/css/00-tokens.css +9 -2
- package/css/02-app.css +0 -139
- package/css/03-primitives.css +1 -0
- package/css/04-feedback.css +20 -6
- package/css/05-tabs.css +106 -23
- package/css/06-mobile.css +104 -38
- package/css/07-animations.css +22 -13
- package/package.json +1 -1
- package/src/composition.ts +29 -4
- package/src/features/activity-monitor.ts +8 -1
- package/src/features/animations.ts +10 -5
- package/src/features/clipboard.ts +8 -2
- package/src/features/connection-banner.ts +0 -1
- package/src/features/context-menu.ts +32 -0
- package/src/features/predictive-echo.ts +10 -0
- package/src/features/tabs.ts +602 -112
- package/src/input-placeholder.ts +2 -2
- package/src/kernel/kernel.ts +82 -10
- package/src/presets.ts +18 -15
- package/src/viewport.ts +80 -3
package/README.md
CHANGED
|
@@ -92,16 +92,20 @@ individual features instead:
|
|
|
92
92
|
- `presetSingle()` — single-pane desktop UI (context menu, clipboard,
|
|
93
93
|
scroll-to-bottom, predictive echo, connection banner).
|
|
94
94
|
- `presetTouch()` — `presetSingle()` plus the mobile key toolbar.
|
|
95
|
-
- `presetTabbed()` — the generic tabbed UI: `presetTouch()` plus tabs
|
|
96
|
-
|
|
95
|
+
- `presetTabbed()` — the generic tabbed UI: `presetTouch()` plus tabs, the
|
|
96
|
+
activity monitor, and animations. Each tab's title is OSC-first — it follows
|
|
97
97
|
the process window title (OSC 0/2) when the program sets one and keeps it
|
|
98
|
-
updated, otherwise the last command submitted.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
updated, otherwise the last command submitted. The per-tab activity dot
|
|
99
|
+
reveals itself only when a session reports OSC 9;4 progress, so a plain shell
|
|
100
|
+
keeps clean, label-only tabs. Requires a server that speaks the session API
|
|
101
|
+
(`/api/sessions` and `/ws?session=`), such as `web-terminal-server`.
|
|
102
|
+
- `presetAgentTabbed()` — the same feature set as `presetTabbed()` (activity
|
|
103
|
+
monitor included), but with `preferInputTitle`: each tab's label follows the
|
|
104
|
+
latest submitted line (persisted server-side and recovered on reload) and the
|
|
105
|
+
program's OSC 0/2 title is ignored — for an agent shell such as `vibecli`
|
|
106
|
+
whose program emits a non-empty but useless OSC title. Its status dots
|
|
107
|
+
(idle / working / done / needs-input) come from the same activity monitor,
|
|
108
|
+
driven by the server's status SSE and its OSC-9 classifier.
|
|
105
109
|
|
|
106
110
|
### Options
|
|
107
111
|
|
package/css/00-tokens.css
CHANGED
|
@@ -12,12 +12,19 @@
|
|
|
12
12
|
overriding them via createTerminal's `theme` option (which sets them on the
|
|
13
13
|
terminal root) or its own CSS. vibecli sets a purple palette. Everything
|
|
14
14
|
above is the fixed design palette; everything here is the public theme API. */
|
|
15
|
-
--accent:
|
|
16
|
-
--tab-
|
|
15
|
+
--accent: oklch(72% 0.15 250deg); /* light blue; themed per consumer */
|
|
16
|
+
--tab-bg: oklch(30% 0 0deg / 90%); /* inactive-tab chip (translucent) */
|
|
17
|
+
--tab-hover-bg: var(--text-active); /* pressed controls / mobile switcher hovers */
|
|
17
18
|
--tab-active-bg: color-mix(in oklch, var(--text) 22%, var(--surface)); /* active tab fill */
|
|
18
19
|
--tab-active-fg: var(--text); /* active tab text */
|
|
20
|
+
--tab-active-border: color-mix(in oklch, var(--accent) 90%, transparent); /* accent edge */
|
|
19
21
|
--radius: 0.75rem;
|
|
20
22
|
--radius-inner: 0.5rem;
|
|
23
|
+
/* Small square icon buttons (e.g. the 24px tab-close x): at that size the
|
|
24
|
+
0.5rem inner radius is a third of the side and reads as far rounder than
|
|
25
|
+
the >=42px controls that also use --radius-inner. This third rung of the
|
|
26
|
+
0.75/0.5/0.25rem ladder keeps a small hover box's corners in proportion. */
|
|
27
|
+
--radius-sm: 0.25rem;
|
|
21
28
|
--touch-target: 2.75rem; /* 44px — Apple HIG minimum */
|
|
22
29
|
--pill-outer: calc(
|
|
23
30
|
var(--touch-target) + 2 * var(--sp-1) + 2px
|
package/css/02-app.css
CHANGED
|
@@ -167,86 +167,6 @@
|
|
|
167
167
|
display: block;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
/* Connection-state banner: bottom-center on touch (lifted above the
|
|
171
|
-
virtual keyboard via --kb-inset), top-center on desktop. Subtle
|
|
172
|
-
backdrop blur. Hidden until status.ts sets `.visible`. Distinct from
|
|
173
|
-
the `.pill` family because it wants to span the full width centrally
|
|
174
|
-
rather than dock to a corner. */
|
|
175
|
-
.conn-banner {
|
|
176
|
-
position: fixed;
|
|
177
|
-
bottom: var(--sp-3);
|
|
178
|
-
left: 50%;
|
|
179
|
-
transform: translateX(-50%);
|
|
180
|
-
max-width: calc(100% - 2 * var(--sp-3));
|
|
181
|
-
padding: var(--sp-1) var(--sp-3);
|
|
182
|
-
background: var(--surface);
|
|
183
|
-
backdrop-filter: var(--blur);
|
|
184
|
-
border: 1px solid var(--border);
|
|
185
|
-
border-radius: var(--radius);
|
|
186
|
-
color: var(--text);
|
|
187
|
-
font-family: var(--font-ui);
|
|
188
|
-
font-size: 0.8125rem;
|
|
189
|
-
font-weight: 500;
|
|
190
|
-
text-align: center;
|
|
191
|
-
z-index: 20;
|
|
192
|
-
display: none;
|
|
193
|
-
pointer-events: auto;
|
|
194
|
-
opacity: 0;
|
|
195
|
-
transition:
|
|
196
|
-
opacity var(--dur-standard) var(--ease-standard),
|
|
197
|
-
display var(--dur-standard) allow-discrete;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.conn-banner.visible {
|
|
201
|
-
display: block;
|
|
202
|
-
opacity: 1;
|
|
203
|
-
|
|
204
|
-
@starting-style {
|
|
205
|
-
opacity: 0;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/* Color coding by notification type */
|
|
210
|
-
.conn-banner[data-state="reconnecting"] {
|
|
211
|
-
border-color: oklch(70% 0.15 60deg / 40%);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.conn-banner[data-state="offline"] {
|
|
215
|
-
color: #f55;
|
|
216
|
-
border-color: oklch(60% 0.2 25deg / 50%);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.conn-banner[data-state="restarted"] {
|
|
220
|
-
color: #fa5;
|
|
221
|
-
border-color: oklch(70% 0.15 60deg / 40%);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
.conn-banner[data-state="toast"] {
|
|
225
|
-
border-color: oklch(70% 0.1 160deg / 30%);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/* Virtual keyboard awareness: when the keyboard is open the
|
|
229
|
-
visualViewport height shrinks. viewport.ts sets --kb-inset on
|
|
230
|
-
termWrap; we use it here to lift the banner above the keyboard.
|
|
231
|
-
Falls back to 0px when the variable isn't set (desktop). */
|
|
232
|
-
@media (pointer: coarse) {
|
|
233
|
-
.conn-banner {
|
|
234
|
-
bottom: calc(var(--sp-3) + var(--kb-inset, 0px));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/* Desktop (pointer: fine): move the banner to top-center so it no longer
|
|
239
|
-
shares the bottom-center anchor with the scroll-to-bottom FAB
|
|
240
|
-
(.kb-scroll-bottom). Sharing it let the banner (pointer-events:auto,
|
|
241
|
-
z-index:20) render over the FAB and intercept its click. Touch keeps
|
|
242
|
-
the bottom placement and the --kb-inset keyboard lift above. */
|
|
243
|
-
@media (pointer: fine) {
|
|
244
|
-
.conn-banner {
|
|
245
|
-
top: var(--sp-3);
|
|
246
|
-
bottom: auto;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
170
|
/* Clickable URLs. OSC 8 hyperlinks (base .term-link) underline on HOVER only:
|
|
251
171
|
an application can attach one OSC 8 link to a whole padded/bordered region —
|
|
252
172
|
e.g. a markdown link whose URL wraps inside a table cell, where the terminal
|
|
@@ -614,62 +534,3 @@
|
|
|
614
534
|
transition: none;
|
|
615
535
|
}
|
|
616
536
|
}
|
|
617
|
-
|
|
618
|
-
/* ===== Context menu ===== */
|
|
619
|
-
.ctx-menu {
|
|
620
|
-
position: fixed;
|
|
621
|
-
display: none;
|
|
622
|
-
flex-direction: column;
|
|
623
|
-
background: var(--surface);
|
|
624
|
-
backdrop-filter: var(--blur);
|
|
625
|
-
border: 1px solid var(--border);
|
|
626
|
-
border-radius: var(--radius);
|
|
627
|
-
padding: var(--sp-1);
|
|
628
|
-
z-index: 30;
|
|
629
|
-
opacity: 0;
|
|
630
|
-
scale: 0.95;
|
|
631
|
-
transition:
|
|
632
|
-
opacity var(--dur-exit) var(--ease-standard),
|
|
633
|
-
scale var(--dur-exit) var(--ease-standard),
|
|
634
|
-
display var(--dur-exit) allow-discrete;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
.ctx-menu.visible {
|
|
638
|
-
display: flex;
|
|
639
|
-
opacity: 1;
|
|
640
|
-
scale: 1;
|
|
641
|
-
|
|
642
|
-
@starting-style {
|
|
643
|
-
opacity: 0;
|
|
644
|
-
scale: 0.95;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
.ctx-menu button {
|
|
649
|
-
background: transparent;
|
|
650
|
-
color: var(--text);
|
|
651
|
-
border: none;
|
|
652
|
-
border-radius: var(--radius-inner);
|
|
653
|
-
font-family: var(--font-ui);
|
|
654
|
-
font-size: 0.8125rem;
|
|
655
|
-
font-weight: 500;
|
|
656
|
-
padding: var(--sp-1) var(--sp-3);
|
|
657
|
-
cursor: pointer;
|
|
658
|
-
text-align: left;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
.ctx-menu button:active {
|
|
662
|
-
background: var(--text-active);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/* Touch (long-press) menu: bump the rows to the 44px --touch-target
|
|
666
|
-
standard the rest of the touch UI uses (the key-toolbar grid is
|
|
667
|
-
repeat(4, var(--touch-target))). Desktop right-click keeps the
|
|
668
|
-
compact 24px menu. padding-block re-centres the label in the taller box. */
|
|
669
|
-
@media (pointer: coarse) {
|
|
670
|
-
.ctx-menu button {
|
|
671
|
-
min-height: var(--touch-target);
|
|
672
|
-
padding-block: var(--sp-2);
|
|
673
|
-
touch-action: manipulation;
|
|
674
|
-
}
|
|
675
|
-
}
|
package/css/03-primitives.css
CHANGED
package/css/04-feedback.css
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
font-weight: 500;
|
|
25
25
|
text-align: center;
|
|
26
26
|
z-index: 20;
|
|
27
|
+
pointer-events: none; /* passive status surface: never steal taps from the terminal beneath */
|
|
27
28
|
display: none;
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -59,8 +60,15 @@
|
|
|
59
60
|
background: var(--surface);
|
|
60
61
|
backdrop-filter: var(--blur);
|
|
61
62
|
border: 1px solid var(--border);
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
/* Match the tab "+" buttons (and every other .wt-btn), which round to
|
|
64
|
+
--radius-inner. Both are 44px boxes, so matching the corner radius makes
|
|
65
|
+
the FAB read as the same-sized button as the new-tab "+" beside/above it
|
|
66
|
+
rather than a rounder, different-looking control. */
|
|
67
|
+
border-radius: var(--radius-inner);
|
|
68
|
+
/* Full --text icon, matching the switcher's keyboard/switch/"+" icons (which
|
|
69
|
+
use .wt-btn's default color). Was a muted 70% mix that read as grey next to
|
|
70
|
+
those white bar icons. */
|
|
71
|
+
color: var(--text);
|
|
64
72
|
z-index: 10;
|
|
65
73
|
display: none;
|
|
66
74
|
}
|
|
@@ -70,20 +78,25 @@
|
|
|
70
78
|
display: inline-flex;
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
/* Hover brightens the
|
|
74
|
-
|
|
81
|
+
/* Hover brightens the border; press fills the background (the .wt-btn :active
|
|
82
|
+
fill would otherwise be masked by this button's own surface fill). The icon
|
|
83
|
+
is already full --text, so hover no longer needs to change it. */
|
|
75
84
|
.wt-scroll-bottom:hover {
|
|
76
85
|
border-color: color-mix(in oklch, var(--text) 35%, transparent);
|
|
77
|
-
color: var(--text);
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
.wt-scroll-bottom:active {
|
|
81
89
|
background: var(--text-active);
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
/* Mobile + tabbed: lift above the switcher bar and the keyboard (WS3)
|
|
92
|
+
/* Mobile + tabbed: lift above the switcher bar and the keyboard (WS3), and pull
|
|
93
|
+
the right edge in to --sp-2 so the button sits directly above the switcher's
|
|
94
|
+
"+" (whose right edge is inset by the bar's --sp-2 padding). Both are 44px
|
|
95
|
+
border-box boxes, so matching the right inset lines them up in one vertical
|
|
96
|
+
column (they were 4px out: the FAB's base --sp-3 vs the "+"'s --sp-2). */
|
|
85
97
|
@media (pointer: coarse) {
|
|
86
98
|
.wt-tabbed .wt-scroll-bottom {
|
|
99
|
+
right: var(--sp-2);
|
|
87
100
|
bottom: calc(
|
|
88
101
|
var(--wt-switcher-h) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
|
|
89
102
|
);
|
|
@@ -158,6 +171,7 @@
|
|
|
158
171
|
font-family: var(--font-ui);
|
|
159
172
|
font-size: 0.75rem;
|
|
160
173
|
z-index: 24;
|
|
174
|
+
pointer-events: none; /* passive status surface: never steal taps from the terminal beneath */
|
|
161
175
|
display: none;
|
|
162
176
|
}
|
|
163
177
|
|
package/css/05-tabs.css
CHANGED
|
@@ -32,13 +32,16 @@
|
|
|
32
32
|
display: inline-flex;
|
|
33
33
|
align-items: center;
|
|
34
34
|
gap: var(--sp-1);
|
|
35
|
-
/* Browser-style tabs:
|
|
36
|
-
as the strip fills; past the floor the bar scrolls horizontally
|
|
37
|
-
(.wt-tab-bar has overflow-x: auto).
|
|
35
|
+
/* Browser-style tabs: up to 300px per tab when there is room, shrinking toward
|
|
36
|
+
a 100px floor as the strip fills; past the floor the bar scrolls horizontally
|
|
37
|
+
(.wt-tab-bar has overflow-x: auto). flex-grow is 0, so the 300px basis is a
|
|
38
|
+
hard cap — every tab is the same size and never stretches past it, whatever
|
|
39
|
+
its title; the label truncates instead. box-sizing keeps the basis a true
|
|
40
|
+
total. */
|
|
38
41
|
box-sizing: border-box;
|
|
39
|
-
flex: 0 1
|
|
40
|
-
min-width:
|
|
41
|
-
padding: var(--sp-2)
|
|
42
|
+
flex: 0 1 300px;
|
|
43
|
+
min-width: 100px;
|
|
44
|
+
padding: var(--sp-2);
|
|
42
45
|
/* A transparent border is reserved on every tab so toggling the inactive
|
|
43
46
|
outline (below) never shifts the strip's layout. */
|
|
44
47
|
border: 1px solid transparent;
|
|
@@ -51,21 +54,28 @@
|
|
|
51
54
|
user-select: none;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
/* Inactive tabs read as outlined chips (so adjacent tabs
|
|
55
|
-
separated
|
|
57
|
+
/* Inactive tabs read as outlined chips with a subtle dark fill (so adjacent tabs
|
|
58
|
+
stay separated from the bar and from each other) and lift on hover. */
|
|
56
59
|
.wt-tab:not(.wt-tab-active) {
|
|
57
60
|
border-color: var(--border);
|
|
61
|
+
background: var(--tab-bg);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
.wt-tab:not(.wt-tab-active):hover {
|
|
61
|
-
|
|
65
|
+
/* A solid lift over the chip. --tab-hover-bg is a translucent wash meant for a
|
|
66
|
+
transparent-rest control; replacing the --tab-bg chip with it would read as
|
|
67
|
+
"more transparent", not "lighter", so bake the same 15% --text lift into the
|
|
68
|
+
chip colour instead. */
|
|
69
|
+
background: color-mix(in oklch, var(--tab-bg), var(--text) 15%);
|
|
62
70
|
color: var(--tab-active-fg);
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
/* The active tab reads clearly above the inactive chips: a stronger fill
|
|
66
|
-
|
|
73
|
+
/* The active tab reads clearly above the inactive chips: a stronger fill plus an
|
|
74
|
+
accent-coloured edge (inactive chips carry a faint white --border; the active
|
|
75
|
+
one gets the accent equivalent). Themeable via --accent (vibecli -> purple). */
|
|
67
76
|
.wt-tab-active {
|
|
68
77
|
background: var(--tab-active-bg);
|
|
78
|
+
border-color: var(--tab-active-border);
|
|
69
79
|
color: var(--tab-active-fg);
|
|
70
80
|
}
|
|
71
81
|
|
|
@@ -75,6 +85,7 @@
|
|
|
75
85
|
flex: 1 1 auto;
|
|
76
86
|
min-width: 0;
|
|
77
87
|
overflow: hidden;
|
|
88
|
+
white-space: nowrap;
|
|
78
89
|
text-overflow: ellipsis;
|
|
79
90
|
}
|
|
80
91
|
|
|
@@ -91,12 +102,16 @@
|
|
|
91
102
|
align-items: center;
|
|
92
103
|
justify-content: center;
|
|
93
104
|
flex: none;
|
|
94
|
-
width: 1.
|
|
95
|
-
height: 1.
|
|
105
|
+
width: 1.5rem;
|
|
106
|
+
height: 1.5rem;
|
|
96
107
|
padding: 0;
|
|
97
108
|
background: transparent;
|
|
98
109
|
border: none;
|
|
99
|
-
|
|
110
|
+
/* --radius-sm (not --radius-inner): this is a small 24px box, where the
|
|
111
|
+
inner radius reads as a third of the side and looks far rounder than the
|
|
112
|
+
larger tab / "+" / menu controls. --radius-sm keeps the hover box in
|
|
113
|
+
proportion with them. */
|
|
114
|
+
border-radius: var(--radius-sm);
|
|
100
115
|
color: color-mix(in oklch, var(--text) 60%, transparent);
|
|
101
116
|
cursor: pointer;
|
|
102
117
|
}
|
|
@@ -116,6 +131,16 @@
|
|
|
116
131
|
visibility: visible;
|
|
117
132
|
}
|
|
118
133
|
|
|
134
|
+
/* Coarse pointer (an iPad using the strip): no hover fires, so an inactive tab's close
|
|
135
|
+
would be unreachable without first activating the tab. Reveal every tab's close there,
|
|
136
|
+
matching the always-visible mobile switcher closes. Kept in lockstep with the strip-shown
|
|
137
|
+
gate (pointer: coarse, width >= 601px). */
|
|
138
|
+
@media (pointer: coarse) and (width >= 601px) {
|
|
139
|
+
.wt-tab:not(.wt-tab-active) .wt-tab-close {
|
|
140
|
+
visibility: visible;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
119
144
|
/* The add-tab (+) button: its hover box matches an active tab's fill and
|
|
120
145
|
stretches to the strip height; the 2px strip gap (not a custom margin) spaces
|
|
121
146
|
it from the last tab. Its glyph is an inline SVG. */
|
|
@@ -126,21 +151,25 @@
|
|
|
126
151
|
flex: none;
|
|
127
152
|
/* Square box that matches the tab height (a stretched flex item ignores
|
|
128
153
|
aspect-ratio against its SVG's intrinsic width, so size it explicitly). The
|
|
129
|
-
tab height is the close-button (1.
|
|
154
|
+
tab height is the close-button (1.5rem) + vertical padding (2×sp-2) +
|
|
130
155
|
border. */
|
|
131
|
-
width: calc(1.
|
|
132
|
-
height: calc(1.
|
|
156
|
+
width: calc(1.5rem + 2 * var(--sp-2) + 2px);
|
|
157
|
+
height: calc(1.5rem + 2 * var(--sp-2) + 2px);
|
|
133
158
|
padding: 0;
|
|
134
159
|
background: transparent;
|
|
135
160
|
border: none;
|
|
136
161
|
border-radius: var(--radius-inner);
|
|
137
|
-
|
|
162
|
+
/* The "+" is the primary action, so carry it in the accent colour (a light
|
|
163
|
+
blue by default; consumer-themed) — matching the mobile switcher's "+"
|
|
164
|
+
(.wt-switcher-new) rather than the muted grey the close / tab controls use. */
|
|
165
|
+
color: var(--accent);
|
|
138
166
|
cursor: pointer;
|
|
139
167
|
}
|
|
140
168
|
|
|
141
169
|
.wt-tab-new:hover {
|
|
170
|
+
/* Keep the accent glyph on hover (no colour override) so the "+" stays the
|
|
171
|
+
accent colour like the mobile "+"; hover only adds the fill. */
|
|
142
172
|
background: var(--tab-active-bg);
|
|
143
|
-
color: var(--tab-active-fg);
|
|
144
173
|
}
|
|
145
174
|
|
|
146
175
|
/* The +/x icons are inline SVG so they center exactly in their flex-centered
|
|
@@ -165,6 +194,54 @@
|
|
|
165
194
|
height: 16px;
|
|
166
195
|
}
|
|
167
196
|
|
|
197
|
+
/* Desktop-strip keyboard button (tabs feature): opens the on-screen key grid on
|
|
198
|
+
a WIDE touchscreen (an iPad using the multi-tab strip), where there is no
|
|
199
|
+
mobile switcher to carry one. Built + wired by the SAME factory as the mobile
|
|
200
|
+
switcher's keyboard button (see makeKbButton in tabs.ts). Uses the shared
|
|
201
|
+
.wt-btn primitive for its 44px box + centered icon. Hidden on a fine-pointer
|
|
202
|
+
desktop (a real keyboard is present) and until a keyboardToggle feature is
|
|
203
|
+
wired ([hidden]); shown only on a coarse pointer at strip widths (the media
|
|
204
|
+
block below, kept in lockstep with SWITCHER_MEDIA in src/features/tabs.ts).
|
|
205
|
+
Fixed at the strip's bottom-right — above the fixed bottom strip, with a
|
|
206
|
+
matching-surface fill — so it caps the strip's right edge and never scrolls
|
|
207
|
+
with the tabs. */
|
|
208
|
+
.wt-tab-kb {
|
|
209
|
+
display: none;
|
|
210
|
+
position: fixed;
|
|
211
|
+
right: var(--sp-2);
|
|
212
|
+
bottom: calc(var(--safe-bottom) + var(--sp-2));
|
|
213
|
+
z-index: 16;
|
|
214
|
+
background: var(--surface);
|
|
215
|
+
border: 1px solid var(--border);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Grid open: filled so the toggled state reads at a glance (mirrors the
|
|
219
|
+
switcher keyboard button). */
|
|
220
|
+
.wt-tab-kb.wt-active {
|
|
221
|
+
background: var(--tab-hover-bg);
|
|
222
|
+
color: var(--tab-active-fg);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Sticky-Ctrl armed: invert (matches the switcher keyboard button + the armed
|
|
226
|
+
Ctrl key) so a pending Ctrl is visible with the grid closed. */
|
|
227
|
+
.wt-tab-kb.wt-armed {
|
|
228
|
+
background: var(--text);
|
|
229
|
+
color: var(--bg);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Show + reserve room only on a WIDE touchscreen (where the desktop strip is
|
|
233
|
+
used AND a keyboardToggle is wired, hence :not([hidden])): the strip gets
|
|
234
|
+
matching right padding so the tabs + "+" never scroll under the fixed button. */
|
|
235
|
+
@media (pointer: coarse) and (width >= 601px) {
|
|
236
|
+
.wt-tab-kb:not([hidden]) {
|
|
237
|
+
display: inline-flex;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.wt-tab-bar {
|
|
241
|
+
padding-right: calc(var(--touch-target) + 2 * var(--sp-2));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
168
245
|
/* The tab being dragged (reorder) dims while it moves. */
|
|
169
246
|
.wt-tab-dragging {
|
|
170
247
|
opacity: 0.5;
|
|
@@ -217,10 +294,12 @@
|
|
|
217
294
|
overlap the last rows (a mirror of the old top offset). The tabs feature
|
|
218
295
|
marks the surface (.wt-with-tabbar) and keeps the measured strip height in
|
|
219
296
|
--wt-tabbar-h; the surface is position:fixed inset:0, so a bottom offset
|
|
220
|
-
clears it.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
297
|
+
clears it. Applies whenever the desktop strip is shown — a fine pointer OR a
|
|
298
|
+
wide viewport (an iPad) — the complement of the narrow-coarse switcher gate in
|
|
299
|
+
css/06-mobile.css (kept in lockstep with SWITCHER_MEDIA in tabs.ts). On the
|
|
300
|
+
narrow-coarse phone the strip is hidden and the mobile switcher's inset is
|
|
301
|
+
applied through viewport.ts (--wt-reserve-bottom) instead. */
|
|
302
|
+
@media (pointer: fine), (width >= 601px) {
|
|
224
303
|
.term.wt-with-tabbar {
|
|
225
304
|
bottom: var(--wt-tabbar-h, 44px);
|
|
226
305
|
}
|
|
@@ -308,5 +387,9 @@
|
|
|
308
387
|
@media (prefers-reduced-motion: reduce) {
|
|
309
388
|
.wt-status-dot[data-status="working"] {
|
|
310
389
|
animation: none;
|
|
390
|
+
/* The pulse is working's ONLY non-hue cue; with motion removed,
|
|
391
|
+
add a static shape cue (an inner hole -> a ring) so working differs
|
|
392
|
+
from done (a solid green disc) by shape, not hue alone (WCAG 1.4.1). */
|
|
393
|
+
box-shadow: inset 0 0 0 2px var(--bg);
|
|
311
394
|
}
|
|
312
395
|
}
|