@cplieger/web-terminal-ui 8.1.1 → 8.1.2

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 CHANGED
@@ -164,7 +164,7 @@ are importable from `…/features/<name>` (`clipboard`, `context-menu`,
164
164
  background activity that outlives a turn (a workflow run, in the
165
165
  `web-terminal-kiro` case). It is a rounded-square ring rather than a disc, so
166
166
  it does not read as another status dot, and band width carries its three
167
- states: a 2px band with a breathing glow while a background task is running, a
167
+ states: a 2px band whose centre closes and reopens while a background task is running, a
168
168
  1px band with a halo while one is paused and resumable, and a 2px band with
169
169
  that same halo while one is blocked on the user. The state and the count are
170
170
  announced in the tab's accessible name and repeated as the mark's hover
package/css/00-tokens.css CHANGED
@@ -178,7 +178,7 @@
178
178
  with nothing enforcing it.
179
179
  Shared with marotte's `--dot-beat-dur` (01-tokens.css) for the reason the
180
180
  --status-* inks are: one signal across the two apps, beat included. */
181
- --dot-beat-dur: 1.2s;
181
+ --dot-beat-dur: 1.6s;
182
182
  --dur-standard: 0.2s;
183
183
  --dur-enter: 0.25s;
184
184
  --dur-exit: 0.15s;
package/css/30-tabs.css CHANGED
@@ -993,7 +993,8 @@
993
993
  three states the channel is BAND WIDTH, in a deliberate 2:1 ratio, because
994
994
  waiting and input share one ink.
995
995
 
996
- A literal 3px radius, not --radius-sm: the smallest token rung is 4px, which on
996
+ A 3px radius of its own (--wt-mark-radius, which the working state's clip reads
997
+ too), not --radius-sm: the smallest token rung is 4px, which on
997
998
  a 9px box reads as a circle and defeats the silhouette.
998
999
 
999
1000
  It costs ZERO layout while no state is set. The footprint cancel is a negative
@@ -1010,12 +1011,13 @@
1010
1011
  /* waiting and input both speak through this ink and separate by band alone;
1011
1012
  working overrides it. */
1012
1013
  --wt-mark-ink: var(--status-input);
1014
+ --wt-mark-radius: 3px;
1013
1015
 
1014
1016
  width: var(--wt-mark-size);
1015
1017
  height: var(--wt-mark-size);
1016
1018
  flex: none;
1017
1019
  position: relative;
1018
- border-radius: 3px;
1020
+ border-radius: var(--wt-mark-radius);
1019
1021
  /* Zero-width at rest, so the ring is drawn entirely by a state's band. The
1020
1022
  colour rides --wt-mark-ink here rather than in each state, so a state sets
1021
1023
  one variable and one width. */
@@ -1051,86 +1053,64 @@
1051
1053
  opacity var(--dur-enter) var(--ease-enter) var(--dur-standard);
1052
1054
  }
1053
1055
 
1054
- /* Working: the heavier bare band, plus a breathing glow. Its own compositing
1055
- layer for the reason the status dot's rule records — a nested overlay layer
1056
- snaps to device pixels independently of a fractional parent position, which at
1057
- this size reads as an off-centre pulse. */
1056
+ /* Working: the heavier bare band, plus a hole that closes on the beat. Its own
1057
+ compositing layer so the overlay layers snap against the ring rather than the
1058
+ page: their offset from it is whole pixels (the rounded inset below), so the
1059
+ closing circle stays centred however the chip centres a 9px mark (17.5px down). */
1058
1060
  :where(.wt-root) .wt-activity-mark[data-activity="working"] {
1059
1061
  --wt-mark-ink: var(--status-working);
1060
- /* The band, named because the MASK below has to know it: the overlay's hole is
1061
- the mark's inradius less this. Paired with the declaration it describes so the
1062
- two cannot drift apart on width. */
1062
+ /* The band, named because the closing overlay below derives the hole from it. */
1063
1063
  --wt-mark-band: 2px;
1064
1064
 
1065
1065
  border-width: var(--wt-mark-band);
1066
1066
  transform: translateZ(0);
1067
+ /* The overlays' clip. `overflow` clips at the padding box, which under border-box
1068
+ sizing IS the hole; where supported the clip is the mark's own outline instead,
1069
+ because the hole's rounded inner corners as a clip edge leave an anti-aliased
1070
+ seam against the band's own (measured at dpr 2: four pixels 15% dark at the
1071
+ peak). Over the band the ink paints the band's own colour. */
1072
+ overflow: hidden;
1067
1073
  }
1068
1074
 
1069
- /* The breath: a pre-painted radial animating OPACITY only, never colours, so the
1070
- beat costs compositing rather than a repaint per frame for as long as a
1071
- background task runs (the battery reason the dot's glow overlay records).
1072
-
1073
- PLUS A MASK, which is the one thing a ring needs and a disc does not, and whose
1074
- absence was a real defect rather than a missing nicety. Over a solid disc the
1075
- overlay's bright core has almost no headroom above its own backdrop; over a
1076
- ring's transparent HOLE it has all of it, so an unmasked core FILLED the hole at
1077
- the peak and the mark rendered as a solid rounded square. That is the one thing
1078
- this element may never be: the mark and the activity dot 8px away share their ink
1079
- at zero hue distance, so the ring-against-disc silhouette is the entire WCAG
1080
- 1.4.1 channel between them, and a fill collapses it for the brightest third of
1081
- every cycle. Masked, the beat lights the BAND and the hole's four corners, so
1082
- what a reader sees is the hole morphing between a square and the circle inscribed
1083
- in it — the ring survives the peak.
1084
-
1085
- EVERY NUMBER HERE IS A RELATION, and the port that copied marotte's LITERALS is
1086
- what left this rule running with nothing visible to show. Two of them were
1087
- arithmetic for an 8px mark with a 2px band: `inset: -1px` put the overlay on that
1088
- mark's own border box, and the core stop `70%` is where the core reaches the
1089
- hole's CORNER there — sqrt(2) * (size / 2 - band) / (size / 2) = 70.71%. At 9px
1090
- and 2px the same relation is 78.57%, so the copied 70% ended the solid core at
1091
- 2.45px while the mask opens only at 2.5px: core and visible mask did not
1092
- intersect at all, the beat lit nothing the mask kept, and 4 pixels of a 9x9 mark
1093
- moved between trough and peak against marotte's 20 (measured at dpr 1, both
1094
- marks, sRGB). Written as relations they survive any size: the inset IS the band,
1095
- the core stop IS the hole's corner radius, the mask's hole IS its inradius. So
1096
- port marotte's `.tab-run-dot` (`12-tabs.css`, "THE GLOW BEAT") — the same mark
1097
- solving the same problem, with the mask from the start — by its relations, never
1098
- by its numbers. sqrt(2) is spelled to four decimals because the synced stylelint
1099
- sets `number-max-precision: 4` outside colour functions.
1100
-
1101
- NOTHING HERE EXTENDS PAST THE MARK. `box-sizing: border-box` (01-scope.css) puts
1102
- the padding box — an absolutely positioned child's containing block — INSIDE the
1103
- band, so `inset: calc(-1 * var(--wt-mark-band))` grows the overlay back out to
1104
- exactly the mark's own border box and no further. Read it as "fill the mark", not
1105
- as "overhang it": a larger inset would reach outside, where a feathered edge's
1106
- visible radius moves with the beat and renders differently at every pixel
1107
- density. `border-radius: 50%` is the same discipline on the last literal, and it
1108
- is inert BY CONSTRUCTION rather than by measurement, which is why no assertion
1109
- pins it: for side S and radius r the clip's 45° boundary is (S/2 - r) * sqrt(2) + r,
1110
- monotonically decreasing in r, so the inscribed circle is the WORST case for
1111
- reaching the hole's corners — and it still clears them for any band at or above
1112
- S/2 * (1 - 1/sqrt(2)), 1.32px here against 2px. Do not assert it, and do not drop
1113
- it either: it is what keeps a literal radius out of a rule whose whole defect was
1114
- literals. */
1075
+ @supports (clip-path: inset(0 round 1px)) {
1076
+ :where(.wt-root) .wt-activity-mark[data-activity="working"] {
1077
+ overflow: visible;
1078
+ clip-path: inset(0 round var(--wt-mark-radius));
1079
+ }
1080
+ }
1081
+
1082
+ /* The close: the square hole fills from its corners, passes through its inscribed
1083
+ circle and shrinks to nothing, then reopens. Compositor-only: ::before is ink
1084
+ around a transparent circle of the hole's corner radius sqrt(2) * h, scaled down
1085
+ to --wt-mark-close and clipped by the parent; half-size (h + 1px) /
1086
+ --wt-mark-close keeps its edge under the band at the peak (a 71px layer, ~20 KB
1087
+ at dpr 1). Scaling leaves a 0.35px pinhole that reads as a dark pixel, so ::after
1088
+ fades a fill over the hole as the scale bottoms out. Every length is a relation to
1089
+ --wt-mark-size and --wt-mark-band (sqrt(2) to the stylelint's 4 decimals). */
1115
1090
  :where(.wt-root) .wt-activity-mark[data-activity="working"]::before {
1091
+ --wt-mark-hole: calc(var(--wt-mark-size) / 2 - var(--wt-mark-band));
1092
+ --wt-mark-close: 0.1;
1093
+
1116
1094
  content: "";
1117
1095
  position: absolute;
1118
- inset: calc(-1 * var(--wt-mark-band));
1119
- border-radius: 50%;
1096
+ inset: round(down, var(--wt-mark-hole) - (var(--wt-mark-hole) + 1px) / var(--wt-mark-close), 1px);
1120
1097
  background: radial-gradient(
1121
- closest-side,
1122
- color-mix(in oklch, var(--wt-mark-ink) 78%, #fff) 0
1123
- calc(1.4142 * (var(--wt-mark-size) / 2 - var(--wt-mark-band))),
1124
- transparent 92%
1098
+ circle,
1099
+ transparent 0 calc(1.4142 * var(--wt-mark-hole)),
1100
+ var(--wt-mark-ink) calc(1.4142 * var(--wt-mark-hole) + 0.5px)
1125
1101
  );
1126
- opacity: 0;
1127
1102
  animation: wt-activity-breath var(--dot-beat-dur) ease-in-out infinite;
1103
+ will-change: transform;
1104
+ }
1105
+
1106
+ :where(.wt-root) .wt-activity-mark[data-activity="working"]::after {
1107
+ content: "";
1108
+ position: absolute;
1109
+ inset: -1px;
1110
+ background: var(--wt-mark-ink);
1111
+ opacity: 0;
1112
+ animation: wt-activity-seal var(--dot-beat-dur) ease-in-out infinite;
1128
1113
  will-change: opacity;
1129
- mask: radial-gradient(
1130
- closest-side,
1131
- transparent 0 calc(var(--wt-mark-size) / 2 - var(--wt-mark-band)),
1132
- #000 calc(var(--wt-mark-size) / 2 - var(--wt-mark-band))
1133
- );
1134
1114
  }
1135
1115
 
1136
1116
  /* Waiting: the thin band plus a static halo. Stopped and resumable, nobody being
@@ -1205,25 +1185,30 @@
1205
1185
  }
1206
1186
  }
1207
1187
 
1208
- /* The activity mark's breath: rest, one swell, rest. Opacity only — a colour
1209
- keyframe here would reintroduce the per-frame repaint this shape exists to avoid.
1210
-
1211
- The dot's glow ENVELOPE, at a lower CEILING, and the ceiling is where the two
1212
- deliberately part. 0.55 rather than 1 is marotte's number for the same mark
1213
- (`12-tabs.css`), and it is what keeps a strip of busy tabs reading as one pulse
1214
- instead of a row of flashes — the dot can afford 1 because its overlay lifts a
1215
- solid disc of its own hue and has almost no headroom above its own backdrop,
1216
- while this one is masked onto a 2px band over the page and has all of it. Used by
1217
- the mark alone, so the value moves nothing else. */
1188
+ /* The activity mark's close: open, shut, open, on the dot glow's envelope so the
1189
+ hole shuts as the disc beside it peaks. Transform only, so it never repaints. */
1218
1190
  @keyframes wt-activity-breath {
1219
1191
  0%,
1220
1192
  70%,
1193
+ 100% {
1194
+ transform: scale(1);
1195
+ }
1196
+
1197
+ 35% {
1198
+ transform: scale(var(--wt-mark-close));
1199
+ }
1200
+ }
1201
+
1202
+ @keyframes wt-activity-seal {
1203
+ 0%,
1204
+ 20%,
1205
+ 50%,
1221
1206
  100% {
1222
1207
  opacity: 0;
1223
1208
  }
1224
1209
 
1225
1210
  35% {
1226
- opacity: 0.55;
1211
+ opacity: 1;
1227
1212
  }
1228
1213
  }
1229
1214
 
@@ -1270,13 +1255,14 @@
1270
1255
  by band alone (3px bare / 1px halo / 2px halo).
1271
1256
 
1272
1257
  Re-pointing --wt-mark-band rather than declaring `border-width` keeps the number
1273
- in the one place the state's own rule reads it from, the mask's divisor included
1258
+ in the one place the state's own rule reads it from, the overlay's hole included
1274
1259
  — which is moot here only because the overlay is gone one rule below. */
1275
1260
  :where(.wt-root) .wt-activity-mark[data-activity="working"] {
1276
1261
  --wt-mark-band: 3px;
1277
1262
  }
1278
1263
 
1279
- :where(.wt-root) .wt-activity-mark[data-activity="working"]::before {
1264
+ :where(.wt-root) .wt-activity-mark[data-activity="working"]::before,
1265
+ :where(.wt-root) .wt-activity-mark[data-activity="working"]::after {
1280
1266
  content: none;
1281
1267
  }
1282
1268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "8.1.1",
3
+ "version": "8.1.2",
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": "MPL-2.0",