efficient-easel 0.105.1 → 1.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.
Files changed (184) hide show
  1. package/README.md +103 -29
  2. package/dist/{LinkPicker-W2Rtxetz.d.ts → LinkPicker-hPEr1MJx.d.ts} +6 -4
  3. package/dist/autosave/index.d.ts +5 -5
  4. package/dist/chrome/chrome.css +50 -273
  5. package/dist/chrome/index.d.ts +61 -56
  6. package/dist/chrome/index.js +1 -1
  7. package/dist/chunk-2GVCAIPX.js +1 -0
  8. package/dist/chunk-4GNPKHV4.js +64 -0
  9. package/dist/chunk-5545A3PC.js +4 -0
  10. package/dist/chunk-7SQF6LZL.js +1 -0
  11. package/dist/chunk-BXMXTO36.js +1 -0
  12. package/dist/{chunk-NYKIQJT2.js → chunk-CKEPQNSQ.js} +1 -1
  13. package/dist/chunk-DJMMTWFP.js +1 -0
  14. package/dist/{chunk-PQXIFBDY.js → chunk-E2O2MRZR.js} +1 -1
  15. package/dist/chunk-EA2HTX37.js +5 -0
  16. package/dist/{chunk-SARZN4M7.js → chunk-FEK3S6HN.js} +1 -1
  17. package/dist/chunk-FPAVNX3Y.js +6 -0
  18. package/dist/chunk-HMJS344F.js +1 -0
  19. package/dist/chunk-JLTZQ4US.js +1 -0
  20. package/dist/chunk-JMGPPXNQ.js +24 -0
  21. package/dist/chunk-K56YDHYQ.js +1 -0
  22. package/dist/chunk-LOMEH6DG.js +1 -0
  23. package/dist/{chunk-V4LF7D4F.js → chunk-MAEXP3KE.js} +1 -1
  24. package/dist/chunk-MJM6SFD3.js +3 -0
  25. package/dist/chunk-NLRW4BGG.js +8 -0
  26. package/dist/chunk-OJAJVH7S.js +5 -0
  27. package/dist/chunk-ONRYLR2D.js +2 -0
  28. package/dist/chunk-ORR3Q4WV.js +1 -0
  29. package/dist/chunk-OTEXGJDU.js +5 -0
  30. package/dist/chunk-P2VEMZH5.js +1 -0
  31. package/dist/chunk-PT6V53OO.js +1 -0
  32. package/dist/chunk-PYCLHOSA.js +9 -0
  33. package/dist/chunk-Q27BB5ZC.js +14 -0
  34. package/dist/chunk-QLD57GBB.js +6 -0
  35. package/dist/{chunk-NWIW7AWH.js → chunk-QMNML7XH.js} +1 -1
  36. package/dist/chunk-QR4DLZ3P.js +5 -0
  37. package/dist/chunk-TJ7YIRWQ.js +21 -0
  38. package/dist/{chunk-3P6AMTOS.js → chunk-VPFNZ7TB.js} +1 -1
  39. package/dist/chunk-W3XX2CNW.js +16 -0
  40. package/dist/chunk-X6DQWK2C.js +1 -0
  41. package/dist/chunk-XWR5YVA7.js +9 -0
  42. package/dist/chunk-YABL4EWX.js +7 -0
  43. package/dist/chunk-ZNNATBDC.js +1 -0
  44. package/dist/chunk-ZP4G3X44.js +4 -0
  45. package/dist/copy/copy.css +66 -177
  46. package/dist/copy/index.d.ts +0 -2
  47. package/dist/copy/index.js +31 -32
  48. package/dist/drag/drag.css +31 -11
  49. package/dist/drag/index.d.ts +10 -0
  50. package/dist/drag/index.js +1 -1
  51. package/dist/easel/index.d.ts +10 -1
  52. package/dist/easel/index.js +1 -1
  53. package/dist/fields/fields.css +35 -304
  54. package/dist/fields/index.d.ts +87 -95
  55. package/dist/fields/index.js +1 -1
  56. package/dist/gold/gold.css +3 -3
  57. package/dist/gold/index.d.ts +6 -5
  58. package/dist/gold/index.js +1 -1
  59. package/dist/{host-BQ_b9Qzk.d.ts → host-CiuzZJXj.d.ts} +11 -1
  60. package/dist/index.d.ts +3 -3
  61. package/dist/index.js +1 -1
  62. package/dist/menu/index.d.ts +49 -93
  63. package/dist/menu/index.js +1 -1
  64. package/dist/menu/menu.css +37 -246
  65. package/dist/node-ops/index.d.ts +1 -1
  66. package/dist/panel/index.d.ts +46 -72
  67. package/dist/panel/index.js +17 -18
  68. package/dist/panel/panel.css +143 -372
  69. package/dist/pick/index.d.ts +52 -33
  70. package/dist/pick/index.js +7 -7
  71. package/dist/pick/pick.css +19 -150
  72. package/dist/rail/index.d.ts +48 -90
  73. package/dist/rail/index.js +1 -1
  74. package/dist/rail/rail.css +21 -108
  75. package/dist/screen/components/AddBlockRow.d.ts +8 -2
  76. package/dist/screen/components/AddBlockRow.js +1 -1
  77. package/dist/screen/components/AddChildRow.d.ts +4 -3
  78. package/dist/screen/components/AddChildRow.js +1 -1
  79. package/dist/screen/components/AddSidePanel.js +1 -1
  80. package/dist/screen/components/MediaSidePanel.d.ts +2 -2
  81. package/dist/screen/components/MediaSidePanel.js +1 -1
  82. package/dist/screen/components/NodeContextMenu.d.ts +31 -9
  83. package/dist/screen/components/NodeContextMenu.js +1 -1
  84. package/dist/screen/components/PageEditGuardNotices.js +1 -1
  85. package/dist/screen/components/ScreenChrome.js +1 -1
  86. package/dist/screen/components/ScreenDocumentContext.d.ts +2 -2
  87. package/dist/screen/components/ScreenDocumentContext.js +1 -1
  88. package/dist/screen/components/ScreenMount.d.ts +8 -8
  89. package/dist/screen/components/ScreenMount.js +1 -1
  90. package/dist/screen/components/ScreenStoreProvider.d.ts +8 -8
  91. package/dist/screen/components/ScreenStoreProvider.js +1 -1
  92. package/dist/screen/components/SectionScreenShell.js +16 -16
  93. package/dist/screen/components/SidePanelHead.d.ts +4 -2
  94. package/dist/screen/components/SidePanelHead.js +1 -1
  95. package/dist/screen/components/UiIcon.d.ts +10 -26
  96. package/dist/screen/components/UiIcon.js +2 -1
  97. package/dist/screen/components/panel-widths.d.ts +15 -1
  98. package/dist/screen/components/panel-widths.js +1 -1
  99. package/dist/screen/components/usePartBridge.d.ts +8 -8
  100. package/dist/screen/components/usePartBridge.js +1 -1
  101. package/dist/screen/index.d.ts +9 -9
  102. package/dist/screen/index.js +1 -1
  103. package/dist/screen/lib/admin-bridge.d.ts +1 -1
  104. package/dist/screen/lib/admin-bridge.js +1 -1
  105. package/dist/screen/lib/chrome-theme.d.ts +20 -12
  106. package/dist/screen/lib/chrome-theme.js +1 -1
  107. package/dist/screen/lib/document-addressing.d.ts +2 -2
  108. package/dist/screen/lib/host-chrome.d.ts +1 -1
  109. package/dist/screen/lib/host.d.ts +2 -2
  110. package/dist/screen/lib/key-scope.d.ts +31 -2
  111. package/dist/screen/lib/key-scope.js +1 -1
  112. package/dist/screen/lib/links/link-filters.d.ts +1 -1
  113. package/dist/screen/lib/media-panel.d.ts +2 -2
  114. package/dist/screen/lib/media-picker.d.ts +2 -2
  115. package/dist/screen/lib/page-lock.d.ts +1 -1
  116. package/dist/screen/lib/puck/section-doc.d.ts +5 -1
  117. package/dist/screen/lib/puck/section-doc.js +1 -1
  118. package/dist/screen/lib/puck/style-clipboard.d.ts +8 -8
  119. package/dist/screen/lib/puck/use-section-ops.js +1 -1
  120. package/dist/screen/lib/screen-parts.d.ts +47 -10
  121. package/dist/screen/lib/screen-parts.js +1 -1
  122. package/dist/screen/lib/section-templates.d.ts +1 -1
  123. package/dist/screen/lib/session-context.d.ts +1 -1
  124. package/dist/screen/lib/session.d.ts +1 -1
  125. package/dist/screen/screen.css +54 -206
  126. package/dist/screen/words.d.ts +17 -1
  127. package/dist/screen/words.js +1 -1
  128. package/dist/shell/index.d.ts +134 -118
  129. package/dist/shell/index.js +1 -1
  130. package/dist/shell/shell.css +130 -88
  131. package/dist/strings/catalogue.d.ts +5 -5
  132. package/dist/strings/catalogue.js +1 -1
  133. package/dist/strings/index.d.ts +5 -5
  134. package/dist/strings/index.js +1 -1
  135. package/dist/style-clipboard/index.d.ts +1 -1
  136. package/dist/text-styles/index.d.ts +12 -3
  137. package/dist/text-styles/index.js +33 -38
  138. package/dist/text-styles/text-styles.css +39 -71
  139. package/dist/tree/index.d.ts +49 -44
  140. package/dist/tree/index.js +1 -1
  141. package/dist/tree/tree.css +31 -653
  142. package/dist/{types-DJDBtdB2.d.ts → types-BqGnUVX5.d.ts} +32 -32
  143. package/dist/types-BsyPh6EW.d.ts +99 -0
  144. package/dist/{types-DWS-s9z-.d.ts → types-CsgMwHHa.d.ts} +14 -11
  145. package/dist/{types-CqY85foL.d.ts → types-Dt1CFR1S.d.ts} +4 -2
  146. package/dist/{wordFit-D4S36PvB.d.ts → wordFit-7XpmhcaW.d.ts} +3 -5
  147. package/dist/words-Cl9xMUYY.d.ts +41 -0
  148. package/dist/{words-D_Lnw1Gr.d.ts → words-CvFnwGSr.d.ts} +1 -1
  149. package/package.json +5 -1
  150. package/dist/chunk-2UX75JDP.js +0 -1
  151. package/dist/chunk-3EDSDPIP.js +0 -1
  152. package/dist/chunk-3QVB4AG2.js +0 -3
  153. package/dist/chunk-3WSPDAK2.js +0 -1
  154. package/dist/chunk-53G47SWO.js +0 -1
  155. package/dist/chunk-5MZ3RNQQ.js +0 -13
  156. package/dist/chunk-5ZBBXZPV.js +0 -6
  157. package/dist/chunk-7FBDZTUD.js +0 -2
  158. package/dist/chunk-7V5WZ46F.js +0 -2
  159. package/dist/chunk-BXZ6WLHC.js +0 -22
  160. package/dist/chunk-BZ5ASXOL.js +0 -1
  161. package/dist/chunk-EOJNJCMJ.js +0 -16
  162. package/dist/chunk-EWVJSRRY.js +0 -6
  163. package/dist/chunk-FMGFZNPW.js +0 -1
  164. package/dist/chunk-H24N5TMZ.js +0 -11
  165. package/dist/chunk-HLEET6NV.js +0 -65
  166. package/dist/chunk-IE4GFYKO.js +0 -1
  167. package/dist/chunk-KTVH2ZRL.js +0 -15
  168. package/dist/chunk-N7VRF7PR.js +0 -5
  169. package/dist/chunk-OWBO4WKA.js +0 -0
  170. package/dist/chunk-P67FP7PG.js +0 -9
  171. package/dist/chunk-P6O363EE.js +0 -6
  172. package/dist/chunk-Q4N7C6CT.js +0 -10
  173. package/dist/chunk-QH6LCNOX.js +0 -8
  174. package/dist/chunk-RW4XBUUW.js +0 -1
  175. package/dist/chunk-TTJ54QGG.js +0 -18
  176. package/dist/chunk-VSHV6KJQ.js +0 -5
  177. package/dist/chunk-VSOP4GUM.js +0 -1
  178. package/dist/chunk-WZ7TGAAT.js +0 -3
  179. package/dist/chunk-YC4TB3CJ.js +0 -25
  180. package/dist/chunk-ZFDCDJNZ.js +0 -16
  181. package/dist/chunk-ZNS7727Y.js +0 -12
  182. package/dist/chunk-ZTTWFAAC.js +0 -1
  183. package/dist/types-Djdq6Xkd.d.ts +0 -138
  184. package/dist/words-DgHcY5qB.d.ts +0 -33
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * Values come from the shell token layer (`efficient-easel/shell.css`), never from
5
5
  * literals: this file is the storefront editor's approved panel design expressed
6
- * once, so both editors read the same rules. Light only — neither editor has a
7
- * dark mode.
6
+ * once, so both editors read the same rules. The token layer resolves to
7
+ * efficient-shell's tokens, so the panel follows the shell's theme, dark
8
+ * included (UI-24).
8
9
  *
9
10
  * Two details are deliberate and were decided by measurement, not taste:
10
11
  * · rows sit 32px apart while a label sits 12px from its own control, so a
@@ -188,13 +189,10 @@
188
189
 
189
190
  The context lives HERE rather than in a host's own panel rule so the shell is
190
191
  self-contained: a host that never declares `container-type` would silently
191
- pin the strip to one state with nothing to see and nothing to fail.
192
-
193
- `.es-rail-fit` is the top-bar rail's column (`TopBarRail`), which reads the
194
- word-fit rule below: its container is the left panel's own width. */
192
+ pin the strip to one state with nothing to see and nothing to fail. (The
193
+ top-bar rail's words are measured by the shell's IconRail since 4.165.0.) */
195
194
  .es-tabs-fit,
196
- .es-segs-fit,
197
- .es-rail-fit {
195
+ .es-segs-fit {
198
196
  container-type: inline-size;
199
197
  flex: none;
200
198
  }
@@ -249,133 +247,28 @@
249
247
  min-width: 0;
250
248
  }
251
249
 
252
- /* THE TRACK (card 66138). Lewis, looking at a real settings panel: the tab he
253
- is NOT on does not invite a click — it does not even look like a button. He
254
- was shown six treatments in `mockups/settings-tab-strip-options.html` and
255
- picked B, the segmented control.
256
-
257
- This REPLACES the underline strip, and with it card 8811's accent-tint ground
258
- (see `.es-tab.is-active` below). The grey track is the part doing the work
259
- Lewis asked for: it is what makes every segment look pressable, where a mark
260
- under the chosen tab only ever made the chosen tab louder.
261
-
262
- FULL WIDTH, no side inset. The old strip carried `padding: 0 14px` because
263
- underline tabs must start inset from the panel edge. A track is a FILLED row,
264
- and every other filled row here — `.es-group-head` at `width: 100%` — runs
265
- the full width of the body's content box; a track inset 14px further than the
266
- rows beneath it is visibly out of line. It is also worth 28px of room, which
267
- is the difference between three segments fitting the shop's default panel and
268
- not.
269
-
270
- NOWRAP, unlike the strip it replaces. A segmented control is one row by
271
- definition — a second row of track under the first is not a wrapped control,
272
- it is two controls. The guarantee that no bucket is ever out of reach is
273
- re-made out of different parts: segments that share the leftover space, and
274
- the drop rules below. */
250
+ /* THE TRACK AND ITS CHIPS ARE THE SHELL'S (efficient-easel issue 199 step 4, harness
251
+ UI-24). `SettingsTabs` is the shell's `Tabs` (`pill`), `SettingsSegmented`
252
+ its `Segmented`: the grey track, the chosen chip filled with the accent (card
253
+ 66138's segmented look, Lewis 2026-10-06's blue), the quiet chips' ink, the
254
+ focus ring, a count (`CountBadge`) and a chip out of force are the kit's.
255
+ What stays here is LAYOUT the shell has no prop for:
256
+
257
+ FULL WIDTH, ONE ROW. A track is a filled row, and every other filled row
258
+ here runs the full width of the body's content box; a segmented control is
259
+ one row by definition, so it never wraps. A SEGMENT TAKES ITS OWN WIDTH, THEN
260
+ SHARES WHAT IS LEFT (`1 1 auto`, not equal segments: three equal segments
261
+ need a 267px track against the 281px the shop's default panel gives). */
275
262
  .es-tabs,
276
263
  .es-segs {
277
264
  display: flex;
278
265
  flex-wrap: nowrap;
279
- gap: var(--es-track-gap);
280
- padding: var(--es-track-pad);
281
- background: var(--es-track-bg);
282
- border-radius: var(--es-row-radius);
283
- flex: none;
266
+ width: 100%;
284
267
  }
285
-
286
- /* A SEGMENT TAKES ITS OWN WIDTH, THEN SHARES WHAT IS LEFT.
287
-
288
- `1 1 auto` and not `1 1 0`: equal segments were measured and are too
289
- expensive. Every equal segment takes the width of the WIDEST label, and
290
- "Content" is 85.36px at 12px/600, so three equal segments need a 267px track
291
- and four need 355px — against the 281px the shop's default panel gives. With
292
- natural widths the track still reaches both edges, and "Style" is not forced
293
- to be as wide as "Content" to get there.
294
-
295
- `box-sizing` for the same reason the switch sets it: this file assumes no
296
- reset of the host's. */
297
- .es-tab,
298
- .es-seg {
299
- box-sizing: border-box;
268
+ .es-tabs > [role='tab'],
269
+ .es-segs > button {
300
270
  flex: 1 1 auto;
301
- border: 0;
302
- background: transparent;
303
- border-radius: 6px;
304
- padding: 4px var(--es-chip-pad-x);
305
- font: inherit;
306
- font-size: 12px;
307
- font-weight: 600;
308
- color: var(--es-text-secondary);
309
271
  white-space: nowrap;
310
- cursor: pointer;
311
- display: inline-flex;
312
- align-items: center;
313
- justify-content: center;
314
- gap: var(--es-chip-icon-gap);
315
- transition: background 0.12s, color 0.12s, box-shadow 0.12s;
316
- }
317
-
318
- /* A quiet segment is READABLE, not faint. It was `--es-muted`, which on the new
319
- grey track sits at about 4.3:1 and misses AA; this is about 8.9:1. The track
320
- is what says "pressable" now, so the label no longer has to whisper to stay
321
- out of the chosen tab's way. */
322
- .es-tab:hover:not(.is-active),
323
- .es-seg:hover:not(.is-active) { color: var(--es-text); }
324
-
325
- /* Keyboard focus, themed rather than left to the UA. Same ring as the switch,
326
- so the panel answers a keyboard the one way. */
327
- .es-tab:focus-visible,
328
- .es-seg:focus-visible {
329
- outline: none;
330
- box-shadow: 0 0 0 3px var(--es-accent-ring);
331
- }
332
-
333
- /* THE CHOSEN CHIP IS BLUE (Lewis, 2026-10-06): 「用蓝色的背景, 不要黑色的了」.
334
-
335
- Said while the shop's hand-built "pick one of 2-3" rungs were merged into
336
- this control. It was `--es-text`, near-black, since card 66302; before that a
337
- raised white chip (card 66138). Both editors draw every segmented control and
338
- the settings tab strip from this one rule, so this is the whole change.
339
-
340
- THE NUMBERS STILL HOLD, which is why the colour could move at all. Card 66302
341
- made the chip the ONLY state cue once the words drop at the narrow end, and
342
- WCAG 1.4.11 asks 3:1 of it: `--es-accent` on `--es-track-bg` is
343
- 4.70:1, and the white word on it is 5.17:1 (1.4.3 asks 4.5:1 at 12px).
344
-
345
- ⚠️ THE LIFT STAYS, AND IT IS NOT THE MESSAGE. `--es-raised-shadow` seats the
346
- chip on the track; the colour is what tells the states apart, because a
347
- shadow cannot be scored and that is how this once sat at 1.10:1 unnoticed.
348
- `tests/panel-chip-contrast.test.ts` pins the ratios AND, since the ruling,
349
- the colour itself. */
350
- .es-tab.is-active,
351
- .es-seg.is-active {
352
- background: var(--es-accent);
353
- color: var(--es-on-accent);
354
- box-shadow: var(--es-raised-shadow);
355
- border-radius: 6px;
356
- }
357
-
358
- /* Focused AND chosen. The quiet chip's ring is a translucent accent wash, and
359
- laid against a chip that is itself the accent it reads as the chip growing,
360
- not as focus. So this ring stands OFF the chip: a 2px gap in the panel
361
- colour, then a solid accent ring — the blue chip, a white gap, a blue line. */
362
- .es-tab.is-active:focus-visible,
363
- .es-seg.is-active:focus-visible {
364
- box-shadow: 0 0 0 2px var(--es-panel), 0 0 0 4px var(--es-accent);
365
- }
366
-
367
- .es-tab-badge {
368
- min-width: 16px;
369
- height: 16px;
370
- padding: 0 4px;
371
- border-radius: 999px;
372
- background: var(--es-accent);
373
- color: var(--es-on-accent);
374
- font-size: 10px;
375
- font-weight: 700;
376
- display: inline-flex;
377
- align-items: center;
378
- justify-content: center;
379
272
  }
380
273
 
381
274
  /* The glyph in the chip: 16px of drawing, the same size a field's icon takes,
@@ -408,74 +301,86 @@
408
301
  .es-tab-icon svg,
409
302
  .es-seg-icon svg { width: var(--es-chip-icon); height: var(--es-chip-icon); display: block; }
410
303
 
304
+ /* A tab with a glyph holds its glyph and its word in ONE shell Tooltip (which
305
+ says the word on hover, where the panel has dropped it). The Tooltip's
306
+ wrapper stands where the shell's tab put them side by side: the same
307
+ `gap-1.5`, so the measured thresholds below still hold. A clipped word is
308
+ out of the flow, so a glyph-only tab keeps no gap. */
309
+ .es-tab-tip {
310
+ position: relative;
311
+ display: inline-flex;
312
+ align-items: center;
313
+ gap: calc(1.5 * var(--spacing));
314
+ }
315
+
411
316
  /* ── where the word stops fitting ──────────────────────────────────────────── */
412
317
 
413
- /* MEASURED in Chromium, `tests/fixtures/tabs-segmented-fit.html`, against these
414
- * stylesheets and the shop's four buckets Content / Media / Style / Layout, by
415
- * EDGE GEOMETRY — the right edge of the last segment against the right content
416
- * edge of the track. Not `scrollWidth`: a flex button reports a `scrollWidth`
417
- * equal to its `clientWidth` while its own content is drawn outside it, so it
418
- * says "fits" about a strip that visibly does not.
318
+ /* RE-MEASURED ON THE SHELL'S PILL (efficient-easel issue 199, part C, 2026-10-10).
319
+ * The chip is efficient-shell's `Tabs` pill since step 4, wider than the 0.106
320
+ * chip the old numbers were taken on, so the thresholds were measured again: in
321
+ * headless Chromium, against the COMPILED shell CSS and these sheets with their
322
+ * @container tiers taken out, the shop's four buckets Content / Media / Style /
323
+ * Layout with 16px glyphs, by EDGE GEOMETRY — the right edge of the last tab
324
+ * against the right content edge of the track (not `scrollWidth`, which says
325
+ * "fits" about a flex button drawing past itself).
419
326
  *
420
327
  * Smallest container width with nothing drawn past the track edge:
421
328
  *
422
329
  * tabs words glyph + active word glyphs only
423
- * 2 168px 126px 73px
424
- * 3 239px 161px 108px
425
- * 4 320px 196px 143px
330
+ * 2 197px 151px 92px
331
+ * 3 280px 195px 136px
332
+ * 4 375px 239px 180px
426
333
  *
427
- * THE GLYPHS-ONLY COLUMN SAID 120px FOR BOTH 2 AND 3 TABS UNTIL CARD 66248,
428
- * and it was wrong twice over. `tabs-segmented-fit.html` searched upward from
429
- * 110px, so it could never have reported anything smaller than 110 whatever the
430
- * truth was — and 120 is not even that, so a second hand had been on it. The
431
- * truth is arithmetic and there is nothing to measure: a chip with no word is a
432
- * glyph in its padding, so the strip is `n × 32px` plus `(n − 1)` gaps plus the
433
- * track's own padding — 73, 108, 143. The 4-tab cell was right by luck of being
434
- * above the floor. Re-measured with the search starting at 40px, all three
435
- * agree with the arithmetic to the pixel; `(d)` now pins them to it, so the
436
- * cell cannot drift from the sheet again.
334
+ * The glyphs-only column is arithmetic (`n` chips of a 16px glyph in `px-3`,
335
+ * `gap-1` apart in a `p-1` track) and the measurement agrees with it to the
336
+ * pixel, which is what says the method is sound; `tests/panel-layout.test.ts`
337
+ * (d) pins the arithmetic.
437
338
  *
438
339
  * The drag band gives containers from 181px (a 220px panel, the minimum) to
439
- * 281px (a 320px panel, the default — the panel minus 28px of body padding
440
- * minus the 11px reserved scrollbar gutter). Read against it:
340
+ * 281px (a 320px panel, the default). Read against it:
441
341
  *
442
- * · TWO tabs never drop a word. 168px is under the 181px floor, so both words
443
- * survive the whole band — and this is the case Lewis is looking at.
444
- * · THREE tabs carry their words down to 239px.
445
- * · FOUR tabs need 320px, which the band never reaches, so a four-bucket
446
- * section is glyph-plus-one-word at the default panel, and below 196px even
447
- * that one word stops fitting.
342
+ * · TWO tabs now need 197px for both words, so at the narrow end of the band
343
+ * the tab not being read loses its word (it never did on the old chip).
344
+ * · THREE tabs carry their words down to 280px — the default panel, just —
345
+ * then one word down to 195px, then glyphs.
346
+ * · FOUR tabs need 375px, which the band never reaches: glyph-plus-one-word
347
+ * down to 239px, then glyphs, which fit the 181px floor.
448
348
  *
449
349
  * WHY THE RULES ARE KEYED ON THE TAB COUNT. With natural widths, whether the
450
350
  * words fit is a question about the SUM of the labels, so the answer moves with
451
351
  * how many buckets a section has. A width-only rule would have to answer for
452
352
  * the worst case — four tabs — and would then strip the word off a TWO-tab
453
- * strip at the default panel width. That is the exact bug this card exists to
454
- * remove, so it cannot be the fix for it.
353
+ * strip at the default panel width.
455
354
  *
456
355
  * Only `.has-icon` is ever touched. A tab with no glyph has nothing to fall
457
356
  * back to, so it keeps its word at every width — a button holding neither word
458
- * nor glyph is not a narrower segment, it is an empty one. */
357
+ * nor glyph is not a narrower segment, it is an empty one. A word is clipped,
358
+ * never `display: none`: the shell's tab takes its name from it. */
359
+
360
+ /* TWO OR MORE, under 197px: the word on the tab he is not reading goes. */
361
+ @container (width < 197px) {
362
+ .es-tabs:has([role='tab']:nth-child(2)) [role='tab']:not([aria-selected='true']) .es-tab-word.has-icon { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
363
+ }
459
364
 
460
- /* THREE OR MORE, under 240px: the words on the tabs he is not reading go. The
461
- NAME does not go with them — it is still on the button as `title` and
462
- `aria-label`, in every state. */
463
- @container (width < 240px) {
464
- .es-tabs:has(.es-tab:nth-child(3)) .es-tab.has-icon:not(.is-active) .es-tab-word { display: none; }
365
+ /* THREE OR MORE, under 280px. */
366
+ @container (width < 280px) {
367
+ .es-tabs:has([role='tab']:nth-child(3)) [role='tab']:not([aria-selected='true']) .es-tab-word.has-icon { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
465
368
  }
466
369
 
467
- /* FOUR OR MORE: a fourth label is another 78px of track, so the same words stop
468
- fitting a long way sooner. */
469
- @container (width < 320px) {
470
- .es-tabs:has(.es-tab:nth-child(4)) .es-tab.has-icon:not(.is-active) .es-tab-word { display: none; }
370
+ /* FOUR OR MORE, under 375px: a fourth label is another ~95px of track. */
371
+ @container (width < 375px) {
372
+ .es-tabs:has([role='tab']:nth-child(4)) [role='tab']:not([aria-selected='true']) .es-tab-word.has-icon { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
471
373
  }
472
374
 
473
- /* FOUR OR MORE at the narrow end: the last word goes as well, and the strip is
474
- four glyphs on a track. It reads — the track and the chips are what say
475
- "these are buttons", which is more than the bare glyphs it replaces ever
476
- said. */
477
- @container (width < 200px) {
478
- .es-tabs:has(.es-tab:nth-child(4)) .es-tab.has-icon.is-active .es-tab-word { display: none; }
375
+ /* THREE OR MORE at the narrow end, under 195px: the last word goes as well. */
376
+ @container (width < 195px) {
377
+ .es-tabs:has([role='tab']:nth-child(3)) [role='tab'][aria-selected='true'] .es-tab-word.has-icon { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
378
+ }
379
+
380
+ /* FOUR OR MORE, under 239px: the strip is four glyphs on a track. It reads —
381
+ the track and the chips are what say "these are buttons". */
382
+ @container (width < 239px) {
383
+ .es-tabs:has([role='tab']:nth-child(4)) [role='tab'][aria-selected='true'] .es-tab-word.has-icon { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
479
384
  }
480
385
 
481
386
  /* ── the segmented control: the same chip, a different promise ─────────────── */
@@ -571,16 +476,18 @@
571
476
  whether it was given a `fit`, so the arithmetic below is reached by a strip
572
477
  that has one and never by a strip that does not. What an unmeasured strip
573
478
  does is the strip's own call: a segmented control keeps every word (there is
574
- nowhere honest to drop one), a top-bar rail stays icon-only (always fits). */
479
+ nowhere honest to drop one). */
575
480
  .es-segs {
576
- --es-fit-chip: calc(2 * var(--es-chip-pad-x) + var(--es-chip-icon));
577
- --es-fit-word-gap: var(--es-chip-icon-gap);
578
- --es-fit-gap: var(--es-track-gap);
579
- --es-fit-ends: var(--es-track-pad);
481
+ /* The shell's `sm` Segmented, read off Tailwind's spacing step (the host's
482
+ `--spacing`): a chip pads px-2.5, the track p-1, its chips gap-1, a glyph
483
+ and its word gap-1.5. The word-fit arithmetic must add what is drawn. */
484
+ --es-fit-chip: calc(2 * 2.5 * var(--spacing) + var(--es-chip-icon));
485
+ --es-fit-word-gap: calc(1.5 * var(--spacing));
486
+ --es-fit-gap: var(--spacing);
487
+ --es-fit-ends: var(--spacing);
580
488
  }
581
489
 
582
- .es-segs.is-measured,
583
- .es-rail-row.is-measured {
490
+ .es-segs.is-measured {
584
491
  --es-fit-strip: calc(2 * var(--es-fit-ends) + (var(--es-fit-count) - 1) * var(--es-fit-gap));
585
492
 
586
493
  --es-fit-words-from: calc(
@@ -604,8 +511,7 @@
604
511
 
605
512
  `100cqi` stays a unit until a word USES it, so it is the width of the
606
513
  container around that word — the strip's own `-fit` wrapper. */
607
- .es-segs,
608
- .es-rail-row.is-measured {
514
+ .es-segs {
609
515
  --es-fit-room-all: calc((100cqi - var(--es-fit-words-from, 0px)) * 10000);
610
516
  --es-fit-room-chosen: calc((100cqi - var(--es-fit-chosen-from, 0px)) * 10000);
611
517
  }
@@ -614,8 +520,7 @@
614
520
  switch has to be a length for `max-width` to clamp it. Nothing is lost by
615
521
  it: the label is on the button as `title` and `aria-label` in every state, so
616
522
  the name a screen reader reads never depended on the span being drawn. */
617
- .es-seg-word,
618
- .es-rail-row.is-measured .es-rail-word {
523
+ .es-seg-word {
619
524
  overflow: hidden;
620
525
  max-width: var(--es-fit-room-all);
621
526
  }
@@ -625,8 +530,7 @@
625
530
  and the chips are what say "these are buttons"; a track that also still says
626
531
  which value is set reads better, and it costs one word. On the rail "chosen"
627
532
  is the button whose panel is open. */
628
- .es-seg.is-active .es-seg-word,
629
- .es-rail-row.is-measured .es-rail-btn.is-active .es-rail-word { max-width: var(--es-fit-room-chosen); }
533
+ .es-segs > button[aria-pressed='true'] .es-seg-word { max-width: var(--es-fit-room-chosen); }
630
534
 
631
535
  /* THE GAP GOES WITH THE WORD IT SEPARATES. The chip's `gap` is a flex gap, and
632
536
  a flex gap does not care that the word is now zero wide — it would leave 6px
@@ -636,66 +540,21 @@
636
540
  untouched and keeps the flex gap, which is right there: its word is
637
541
  `display: none`, so the gap goes with the element, and a tab also has a badge
638
542
  on the other side of that word for the gap to separate. */
639
- .es-seg { gap: 0; }
640
- .es-seg.has-icon .es-seg-word,
641
- .es-rail-row.is-measured .es-rail-word { margin-left: clamp(0px, var(--es-fit-room-all), var(--es-fit-word-gap)); }
642
- .es-seg.has-icon.is-active .es-seg-word,
643
- .es-rail-row.is-measured .es-rail-btn.is-active .es-rail-word { margin-left: clamp(0px, var(--es-fit-room-chosen), var(--es-fit-word-gap)); }
644
-
645
- /* ── a segment out of force (card 66303; the RULE is card 66216's) ──────────
646
-
647
- The setting still EXISTS and still shows the merchant's saved answer. Another
648
- setting has simply taken the decision away — the Header's "Text colour at the
649
- top" under a pinned Background, which cannot be transparent. DIMMED, NEVER
650
- HIDDEN: a control that disappears reads as a feature that was removed, and
651
- leaves the merchant no way to see what to change to get it back.
652
-
653
- THE STATE IS ON THE BUTTONS, and `SettingsSegmented` renders it as their own
654
- `disabled` rather than as a fade a host paints over them. A faded parent
655
- leaves every segment focusable and announcing as ENABLED, which is the defect
656
- the 2026-08-06 audit and card 66034 closed; it is also why the prop had to
657
- come here at all, since nothing outside this component owns those buttons.
658
-
659
- `--es-disabled` is the shell's existing token for exactly this state, already
660
- worn by the drill row's quiet glyph. Nothing here is a literal, and nothing
661
- here removes anything: the track keeps its border and the pressed chip keeps
662
- its lift, so the strip still READS as a strip with an answer in it. */
663
- .es-seg:disabled { cursor: not-allowed; color: var(--es-disabled); }
664
-
665
- /* The chosen one stays the loudest thing on the track. It is still the
666
- merchant's answer — a pressed chip faded to the same grey as the ones beside
667
- it stops saying which answer that was.
668
-
669
- GREY, NOT BLUE, AND NOT FADED (2026-10-06). The live chip is the accent now,
670
- and blue is the colour that says "this is in force" — so an answer out of
671
- force drops to `--es-muted`. The arithmetic decided the rest: the blue chip
672
- faded to half over the panel is 2.04:1 against its faded track, under the
673
- 3:1 a state cue needs. `--es-muted` with no fade is 4.39:1 on the track and
674
- its white word 4.83:1. That is why the whole strip no longer fades (below):
675
- any fade took this chip under 3:1 again.
676
-
677
- ⚠️ THE WORD COLOUR BELONGS TO THE CHIP'S GROUND, NOT TO THE TRACK (card
678
- 66302): a label written for one ground went unreadable when the ground moved
679
- under it. So the ground and the word are declared together, here. */
680
- .es-seg:disabled.is-active {
681
- background: var(--es-muted);
682
- color: var(--es-on-accent);
683
- }
684
-
685
- /* And none of them answers the pointer any more: the hover rule above would
686
- otherwise keep lighting a segment that cannot be chosen. */
687
- .es-seg:disabled:hover:not(.is-active) { color: var(--es-disabled); }
688
-
689
- /* THE WHOLE CONTROL, when every option went at once. Every button is then
690
- `:disabled`, so the quiet words are already `--es-disabled` and the chosen
691
- chip is already grey — the strip says "out of force" through them.
692
-
693
- IT USED TO FADE TO 0.5, and that fade is gone on purpose (2026-10-06). An
694
- opacity on the track composites the chosen chip with it, and no chip colour
695
- the ruling allows survives that above 3:1: blue faded is 2.04:1, the grey
696
- above faded is about 2:1. The chip is the one thing a disabled strip must
697
- still say clearly, so the fade went rather than the chip. */
698
- .es-segs[data-disabled] { cursor: not-allowed; }
543
+ .es-segs.is-measured > button { gap: 0; }
544
+ .es-segs.is-measured > button:has(.es-seg-icon) .es-seg-word { margin-left: clamp(0px, var(--es-fit-room-all), var(--es-fit-word-gap)); }
545
+ .es-segs.is-measured > button[aria-pressed='true']:has(.es-seg-icon) .es-seg-word { margin-left: clamp(0px, var(--es-fit-room-chosen), var(--es-fit-word-gap)); }
546
+
547
+ /* ── the chosen segment ──────────────────────────────────────────────────────
548
+ The shell's Segmented draws it (efficient-shell 4.164.0): an inset 1px
549
+ `--ink-faint` ring on the white chip, 3:1 and more on the track in both
550
+ themes (WCAG 1.4.11; `tests/panel-chip-contrast.test.ts`). It supersedes the
551
+ accent fill this sheet painted as a SHELL GAP (Lewis, 2026-10-06): under
552
+ harness UI-24 the shell's design wins, and the gap is closed. */
553
+
554
+ /* ── a segment out of force (card 66303) ─────────────────────────────────────
555
+ The chip out of force is the shell's (its own `disabled`, dimmed); the whole
556
+ control out of force says so on its fit box, for the pointer. */
557
+ .es-segs-fit[data-disabled] { cursor: not-allowed; }
699
558
 
700
559
  /* ── a group: the soft pill row that folds ─────────────────────────────────── */
701
560
 
@@ -734,7 +593,10 @@
734
593
  color: var(--es-text);
735
594
  background: var(--es-hover);
736
595
  border-radius: var(--es-row-radius);
737
- padding: 8px 12px;
596
+ /* Its words start where a tree row's do: the shell's `TreeRow` pads `px-2`,
597
+ two of the shell's `--spacing` steps, and so does this (the Layers panel
598
+ drew a heading at 12px over rows at 8px: two text columns). */
599
+ padding: 8px calc(2 * var(--spacing));
738
600
  /* A heading needs more air above it than the settings it heads have between
739
601
  them (38px), or it reads as one more row. Floored on `--es-row-gap` so it
740
602
  rises with the rest of the panel (card 66203). */
@@ -889,7 +751,8 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
889
751
  the part inside a component of its own (it reads a live value through a
890
752
  hook) is invisible to SettingsField, and without this its label stayed grey
891
753
  beside a Reset — measured on the website's section Width row. */
892
- .es-field:has(> .es-field-head > .es-default-reset.is-changed) > .es-field-head .es-field-label {
754
+ .es-field:has(> .es-field-head > .es-default-reset.is-changed) > .es-field-head .es-field-label,
755
+ .es-field:has(> .es-field-head > span > .es-default-reset.is-changed) > .es-field-head .es-field-label {
893
756
  color: var(--es-accent-text);
894
757
  }
895
758
 
@@ -950,7 +813,7 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
950
813
  margin: 0;
951
814
  padding: 0;
952
815
  border: 0;
953
- border-radius: 6px;
816
+ border-radius: var(--radius-md);
954
817
  background: transparent;
955
818
  color: var(--es-muted);
956
819
  }
@@ -983,14 +846,18 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
983
846
  outline-offset: 1px;
984
847
  }
985
848
 
986
- .es-field-name + .es-default-reset { margin-left: auto; }
849
+ /* A pressable part explains itself in the shell's Tooltip, whose wrapper is
850
+ then the head's item: the rules below place either. */
851
+ .es-field-name + .es-default-reset,
852
+ .es-field-name + span:has(> .es-default-reset) { margin-left: auto; }
987
853
 
988
854
  /* THE GREY VALUE IS THE "DEFAULT" WORD NOW (option E): a row whose slot is
989
855
  empty draws its value grey, and the value takes the text colour once the
990
856
  setting is changed. Scoped to rows that carry the part, so a value on a row
991
857
  with nothing to reset keeps its colour. */
992
858
  .es-field:has(> .es-field-head > span.es-default-reset) .es-field-value { color: var(--es-muted); }
993
- .es-field-value + .es-default-reset { margin-left: -4px; }
859
+ .es-field-value + .es-default-reset,
860
+ .es-field-value + span:has(> .es-default-reset) { margin-left: -4px; }
994
861
 
995
862
  /* A boolean row is ONE line (card 8801, Option A). The switch rides the label
996
863
  line in the `action` slot, so there is no control row under it — and with no
@@ -1007,73 +874,10 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1007
874
  .es-field.is-inline { margin-top: max(var(--es-row-gap), var(--es-rhythm-between-tight)); }
1008
875
  .es-field.is-inline .es-field-head { margin-bottom: 0; }
1009
876
 
1010
- /* ── the switch: one yes-or-no shape, both editors ─────────────────────────── */
1011
-
1012
- .es-switch {
1013
- appearance: none;
1014
- -webkit-appearance: none;
1015
- box-sizing: border-box;
1016
- margin: 0;
1017
- padding: 0;
1018
- border: 0;
1019
- flex: none;
1020
- position: relative;
1021
- width: 34px;
1022
- height: 20px;
1023
- border-radius: 999px;
1024
- background: var(--es-toggle-off);
1025
- cursor: pointer;
1026
- transition: background 0.15s ease;
1027
- }
1028
-
1029
- /* The knob. `box-sizing` is load-bearing, not tidiness: the locked knob below
1030
- takes a 1px border, and under content-box that border would grow it to 18px
1031
- and eat the 2px gap the whole shape is built on. */
1032
- .es-switch::after {
1033
- content: '';
1034
- box-sizing: border-box;
1035
- position: absolute;
1036
- top: 2px;
1037
- left: 2px;
1038
- width: 16px;
1039
- height: 16px;
1040
- border-radius: 50%;
1041
- background: var(--es-knob);
1042
- /* The knob has to lift off the track, and the shadow is a plain token on
1043
- purpose. A softer mix reads slightly better, but if color-mix() ever fails
1044
- to parse the WHOLE declaration drops and the knob goes flat with no
1045
- warning — not a risk worth carrying in a leaf both editors render
1046
- thousands of times for the sake of a 1px shadow. */
1047
- box-shadow: 0 1px 2px var(--es-input-border);
1048
- transition: transform 0.15s cubic-bezier(0.2, 0.8, 0.3, 1);
1049
- }
1050
-
1051
- /* On is the accent — the same blue as an active tab, so the panel keeps ONE
1052
- accent. The travel is 14px: 34 track − 16 knob − 2 gap − 2 gap. */
1053
- .es-switch[aria-checked='true'] { background: var(--es-accent); }
1054
- .es-switch[aria-checked='true']::after { transform: translateX(14px); }
1055
-
1056
- .es-switch:hover:not(:disabled) { filter: brightness(0.96); }
1057
-
1058
- .es-switch:focus-visible {
1059
- outline: none;
1060
- box-shadow: 0 0 0 3px var(--es-accent-ring);
1061
- }
1062
-
1063
- /* Held down: the knob stretches the way a physical one would.
1064
-
1065
- ON, it must stretch LEFT. The knob is 16px at translateX(14px), so growing it
1066
- to 19px in place would put its right edge at 2 + 14 + 19 = 35px inside a 34px
1067
- track and poke it past the round end. Pulling the travel back to 11px lands
1068
- the right edge on 32 and keeps the 2px gap on both sides. */
1069
- .es-switch:active:not(:disabled)::after { width: 19px; }
1070
- .es-switch[aria-checked='true']:active:not(:disabled)::after { transform: translateX(11px); }
1071
-
1072
- /* Locked. Still readable as on or off, but flat and grey, and the knob trades
1073
- its shadow for a hairline so it does not look pressable. */
1074
- .es-switch:disabled { background: var(--es-divider); cursor: not-allowed; }
1075
- .es-switch:disabled[aria-checked='true'] { background: var(--es-input-border); }
1076
- .es-switch:disabled::after { box-shadow: none; border: 1px solid var(--es-divider); }
877
+ /* ── the switch: one yes-or-no shape, both editors ─────────────────────────
878
+ The shell's `Switch` since efficient-easel issue 199 step 4 (harness UI-24): its
879
+ track, knob, on colour, focus ring and locked look are the kit's. `.es-switch`
880
+ stays on its wrapper as a name a host's layout may hold. */
1077
881
 
1078
882
  /* ── a drill-in row: name, preview, chevron (card 8819) ────────────────────── */
1079
883
 
@@ -1215,14 +1019,10 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1215
1019
  .es-drill-chev::before { content: '\203A'; }
1216
1020
 
1217
1021
  @media (prefers-reduced-motion: reduce) {
1218
- .es-tab,
1219
- .es-seg,
1220
1022
  /* `button.` and not `.es-group-head`: the transition it cancels is declared
1221
1023
  on `button.es-group-head`, and a plainer selector would lose to it. */
1222
1024
  button.es-group-head,
1223
- .es-drill,
1224
- .es-switch,
1225
- .es-switch::after { transition: none; }
1025
+ .es-drill { transition: none; }
1226
1026
  }
1227
1027
 
1228
1028
  /* ── SettingsPlacement — the 3×3 position grid ─────────────────────────────
@@ -1246,23 +1046,20 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1246
1046
  padding: 4px;
1247
1047
  background: var(--es-track-bg);
1248
1048
  border: 1px solid var(--es-input-border);
1249
- border-radius: 9px;
1049
+ border-radius: var(--radius-lg);
1250
1050
  }
1251
- .es-placement-cell {
1252
- border: none;
1253
- background: transparent;
1254
- border-radius: 6px;
1255
- cursor: pointer;
1256
- padding: 0;
1051
+ /* Each cell is the shell's IconButton in its Tooltip's wrapper: the wrapper is
1052
+ the grid's item, and the button fills it. The button's own look (hover,
1053
+ focus ring, disabled) is the shell's. */
1054
+ .es-placement > span,
1055
+ .es-corners > span {
1257
1056
  display: flex;
1258
- align-items: center;
1259
- justify-content: center;
1260
- transition: background 0.12s;
1261
1057
  }
1262
- .es-placement-cell:hover { background: var(--es-hover); }
1263
- /* The dot is the whole of the cell's state: a lit cell grows it and paints it
1264
- the accent. Size and colour move together so the chosen square reads at a
1265
- glance and still reads for someone who cannot separate the two colours. */
1058
+ .es-placement-cell,
1059
+ .es-corners-cell {
1060
+ width: 100%;
1061
+ height: 100%;
1062
+ }
1266
1063
  .es-placement-dot {
1267
1064
  width: 7px;
1268
1065
  height: 7px;
@@ -1294,9 +1091,11 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1294
1091
  /* A position the caller has closed. It keeps its square — nine that became
1295
1092
  eight would read as a broken control — and stops answering the pointer; the
1296
1093
  reason is on the button's `title`. */
1297
- .es-placement-cell.is-off { cursor: default; }
1298
- .es-placement-cell.is-off:hover { background: transparent; }
1299
1094
  .es-placement-cell.is-off .es-placement-dot { opacity: 0.22; }
1095
+ /* Off with a reason is `aria-disabled` (the keyboard reaches it and hears why),
1096
+ so the pointer is told here what `:disabled` used to tell it. */
1097
+ .es-placement-cell.is-off,
1098
+ .es-corners-cell.is-off { cursor: not-allowed; }
1300
1099
  /* A cell that draws a GLYPH instead of its dot — the Scrolling Bar's
1301
1100
  `Scroll direction`, where four live cells mean a direction of travel and a
1302
1101
  dot cannot say which way. It follows the cell through every state the dot
@@ -1326,12 +1125,7 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1326
1125
  }
1327
1126
  .es-placement-cell.is-off .es-placement-icon { opacity: 0.22; }
1328
1127
  /* Keyboard focus is visible, like every other control in this panel. */
1329
- .es-placement-cell:focus-visible {
1330
- outline: 2px solid var(--es-accent-ring);
1331
- outline-offset: 1px;
1332
- }
1333
1128
  @media (prefers-reduced-motion: reduce) {
1334
- .es-placement-cell,
1335
1129
  .es-placement-icon,
1336
1130
  .es-placement-dot { transition: none; }
1337
1131
  }
@@ -1353,7 +1147,7 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1353
1147
  height: 96px;
1354
1148
  padding: 4px;
1355
1149
  background: var(--es-track-bg);
1356
- border-radius: 8px;
1150
+ border-radius: var(--radius-lg);
1357
1151
  }
1358
1152
  /* The BOX the four corners belong to. Inset so the cells sit ON its corners
1359
1153
  rather than beside it, dashed so it reads as the outline of the thing being
@@ -1366,23 +1160,6 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1366
1160
  opacity: 0.45;
1367
1161
  pointer-events: none;
1368
1162
  }
1369
- .es-corners-cell {
1370
- position: relative;
1371
- border: none;
1372
- background: transparent;
1373
- border-radius: 6px;
1374
- cursor: pointer;
1375
- padding: 0;
1376
- display: flex;
1377
- align-items: center;
1378
- justify-content: center;
1379
- }
1380
- .es-corners-cell:hover { background: var(--es-hover); }
1381
- /* THE MARK IS THE ANSWER, drawn rather than labelled. Each cell holds a square
1382
- whose MATCHING corner is rounded when the corner is on and square when it is
1383
- off, so the state is carried by SHAPE as well as by fill — which is what
1384
- keeps it readable for someone who cannot separate the accent from the
1385
- ground. The per-corner radius is set by the four rules below. */
1386
1163
  .es-corners-mark {
1387
1164
  width: 18px;
1388
1165
  height: 18px;
@@ -1400,21 +1177,15 @@ button.es-group-head:hover { background: var(--es-chip-bg); }
1400
1177
  background: var(--es-accent);
1401
1178
  opacity: 1;
1402
1179
  }
1403
- .es-corners-tl.is-on .es-corners-mark { border-radius: 8px 0 0 0; }
1404
- .es-corners-tr.is-on .es-corners-mark { border-radius: 0 8px 0 0; }
1405
- .es-corners-br.is-on .es-corners-mark { border-radius: 0 0 8px 0; }
1406
- .es-corners-bl.is-on .es-corners-mark { border-radius: 0 0 0 8px; }
1180
+ .es-corners-tl.is-on .es-corners-mark { border-radius: var(--radius-lg) 0 0 0; }
1181
+ .es-corners-tr.is-on .es-corners-mark { border-radius: 0 var(--radius-lg) 0 0; }
1182
+ .es-corners-br.is-on .es-corners-mark { border-radius: 0 0 var(--radius-lg) 0; }
1183
+ .es-corners-bl.is-on .es-corners-mark { border-radius: 0 0 0 var(--radius-lg); }
1407
1184
  /* A corner the caller has closed. It keeps its cell — four that became three
1408
1185
  would read as a broken control — and stops answering the pointer; the reason
1409
1186
  is on the button's `title`. */
1410
- .es-corners-cell.is-off { cursor: default; }
1411
- .es-corners-cell.is-off:hover { background: transparent; }
1412
1187
  .es-corners-cell.is-off .es-corners-mark { opacity: 0.22; }
1413
1188
  /* Keyboard focus is visible, like every other control in this panel. */
1414
- .es-corners-cell:focus-visible {
1415
- outline: 2px solid var(--es-accent-ring);
1416
- outline-offset: 1px;
1417
- }
1418
1189
  @media (prefers-reduced-motion: reduce) {
1419
1190
  .es-corners-mark { transition: none; }
1420
1191
  }