efficient-easel 0.101.0 → 0.102.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 (232) hide show
  1. package/README.md +37 -0
  2. package/dist/LinkPicker-W2Rtxetz.d.ts +111 -0
  3. package/dist/autosave/index.d.ts +6 -3
  4. package/dist/autosave/index.js +1 -1
  5. package/dist/chrome/index.d.ts +3 -2
  6. package/dist/chrome/index.js +1 -25
  7. package/dist/chunk-26OOWIJJ.js +1 -0
  8. package/dist/chunk-2LK54I4E.js +1 -0
  9. package/dist/chunk-3QVB4AG2.js +3 -0
  10. package/dist/chunk-3U4GP5VA.js +10 -0
  11. package/dist/chunk-3WSPDAK2.js +1 -0
  12. package/dist/chunk-4ES5JGGX.js +1 -0
  13. package/dist/chunk-5464H65G.js +4 -0
  14. package/dist/chunk-57DJOKXT.js +1 -0
  15. package/dist/chunk-5MZ3RNQQ.js +13 -0
  16. package/dist/chunk-5ZBY24K7.js +1 -0
  17. package/dist/chunk-6DJHRLCU.js +25 -0
  18. package/dist/chunk-6FY3PZS3.js +1 -0
  19. package/dist/chunk-6TZTKAB5.js +1 -0
  20. package/dist/chunk-6W7WLPZH.js +1 -0
  21. package/dist/chunk-7F52SZOI.js +2 -0
  22. package/dist/chunk-7MU3RJ6S.js +1 -0
  23. package/dist/chunk-7RNTMJER.js +5 -0
  24. package/dist/chunk-A7ZJII7O.js +1 -0
  25. package/dist/chunk-AJBJ74ME.js +1 -0
  26. package/dist/chunk-AKFJK65Z.js +1 -0
  27. package/dist/chunk-AKZWUV5Z.js +1 -0
  28. package/dist/chunk-BDTITTMW.js +1 -0
  29. package/dist/chunk-BSPCSC45.js +1 -0
  30. package/dist/chunk-BXZ6WLHC.js +22 -0
  31. package/dist/chunk-D7M6IRPJ.js +8 -0
  32. package/dist/chunk-DUVRZ456.js +1 -0
  33. package/dist/chunk-E7GGVILG.js +1 -0
  34. package/dist/chunk-F2FPZIZV.js +62 -0
  35. package/dist/chunk-F6CCTEIX.js +1 -0
  36. package/dist/chunk-FGNR22NY.js +1 -0
  37. package/dist/chunk-FT35FX7D.js +2 -0
  38. package/dist/chunk-FUDHI5I4.js +1 -0
  39. package/dist/chunk-GENAF3P6.js +1 -0
  40. package/dist/chunk-GOTN2KY4.js +2 -0
  41. package/dist/chunk-GXQCZJOP.js +1 -0
  42. package/dist/chunk-GZO72N3Q.js +0 -0
  43. package/dist/chunk-HMX55DYT.js +1 -0
  44. package/dist/chunk-HQ4VVRIC.js +1 -0
  45. package/dist/chunk-HWW4XJOH.js +1 -0
  46. package/dist/chunk-I54UCYOU.js +1 -0
  47. package/dist/chunk-IQ5TA7AM.js +1 -0
  48. package/dist/chunk-IWAPPEL4.js +6 -0
  49. package/dist/chunk-IYT6NN2J.js +1 -0
  50. package/dist/chunk-J4RAY75C.js +1 -0
  51. package/dist/chunk-KCBEE2MC.js +2 -0
  52. package/dist/chunk-KDYIMJTZ.js +1 -0
  53. package/dist/chunk-KQMML6JM.js +4 -0
  54. package/dist/chunk-OM67AJQC.js +1 -0
  55. package/dist/chunk-OYX47SL7.js +1 -0
  56. package/dist/chunk-P67FP7PG.js +9 -0
  57. package/dist/chunk-PP44LCJM.js +1 -0
  58. package/dist/chunk-PWZOS45R.js +2 -0
  59. package/dist/chunk-Q3SQBNJB.js +1 -0
  60. package/dist/chunk-R2HLPNQF.js +1 -0
  61. package/dist/chunk-R5L2E7JX.js +1 -0
  62. package/dist/chunk-R5ZVNJLJ.js +2 -0
  63. package/dist/chunk-RLBXE4EU.js +1 -0
  64. package/dist/chunk-RW4XBUUW.js +1 -0
  65. package/dist/chunk-SERF2FAU.js +1 -0
  66. package/dist/chunk-TEZ6V6PA.js +4 -0
  67. package/dist/chunk-TTJ54QGG.js +18 -0
  68. package/dist/chunk-UH7TGWHR.js +1 -0
  69. package/dist/chunk-UVWVYMMZ.js +1 -0
  70. package/dist/chunk-VLSTGOVD.js +2 -0
  71. package/dist/chunk-VV5ADNBR.js +1 -0
  72. package/dist/chunk-ZE7I3PLX.js +1 -0
  73. package/dist/chunk-ZZ7VLIHQ.js +1 -0
  74. package/dist/drag/index.d.ts +3 -41
  75. package/dist/drag/index.js +1 -18
  76. package/dist/easel/index.js +1 -9
  77. package/dist/{words-BMzV04b1.d.ts → engine-DBxp_v6B.d.ts} +1 -58
  78. package/dist/fields/index.d.ts +2 -109
  79. package/dist/fields/index.js +1 -22
  80. package/dist/gold/index.d.ts +4 -2
  81. package/dist/host-B-byaQxg.d.ts +480 -0
  82. package/dist/menu/index.js +1 -1
  83. package/dist/node-ops/index.js +1 -1
  84. package/dist/panel/index.js +16 -16
  85. package/dist/panelWidths-D9hTLR25.d.ts +66 -0
  86. package/dist/screen/components/AddBlockRow.d.ts +14 -0
  87. package/dist/screen/components/AddBlockRow.js +1 -0
  88. package/dist/screen/components/AddChildRow.d.ts +39 -0
  89. package/dist/screen/components/AddChildRow.js +1 -0
  90. package/dist/screen/components/AddSidePanel.d.ts +54 -0
  91. package/dist/screen/components/AddSidePanel.js +1 -0
  92. package/dist/screen/components/MediaSidePanel.d.ts +47 -0
  93. package/dist/screen/components/MediaSidePanel.js +1 -0
  94. package/dist/screen/components/NodeContextMenu.d.ts +364 -0
  95. package/dist/screen/components/NodeContextMenu.js +1 -0
  96. package/dist/screen/components/PageEditGuardNotices.d.ts +5 -0
  97. package/dist/screen/components/PageEditGuardNotices.js +1 -0
  98. package/dist/screen/components/PanelToggleIcon.d.ts +14 -0
  99. package/dist/screen/components/PanelToggleIcon.js +1 -0
  100. package/dist/screen/components/PreviewDeviceContext.d.ts +28 -0
  101. package/dist/screen/components/PreviewDeviceContext.js +1 -0
  102. package/dist/screen/components/PreviewPartsContext.d.ts +33 -0
  103. package/dist/screen/components/PreviewPartsContext.js +1 -0
  104. package/dist/screen/components/ScreenChrome.d.ts +5 -0
  105. package/dist/screen/components/ScreenChrome.js +1 -0
  106. package/dist/screen/components/ScreenDocumentContext.d.ts +20 -0
  107. package/dist/screen/components/ScreenDocumentContext.js +1 -0
  108. package/dist/screen/components/ScreenMount.d.ts +75 -0
  109. package/dist/screen/components/ScreenMount.js +1 -0
  110. package/dist/screen/components/ScreenStoreProvider.d.ts +63 -0
  111. package/dist/screen/components/ScreenStoreProvider.js +1 -0
  112. package/dist/screen/components/SectionScreenShell.d.ts +5 -0
  113. package/dist/screen/components/SectionScreenShell.js +16 -0
  114. package/dist/screen/components/SidePanelHead.d.ts +81 -0
  115. package/dist/screen/components/SidePanelHead.js +1 -0
  116. package/dist/screen/components/SlotChildActionsContext.d.ts +41 -0
  117. package/dist/screen/components/SlotChildActionsContext.js +1 -0
  118. package/dist/screen/components/UiIcon.d.ts +52 -0
  119. package/dist/screen/components/UiIcon.js +1 -0
  120. package/dist/screen/components/panel-widths.d.ts +37 -0
  121. package/dist/screen/components/panel-widths.js +1 -0
  122. package/dist/screen/components/usePartBridge.d.ts +66 -0
  123. package/dist/screen/components/usePartBridge.js +1 -0
  124. package/dist/screen/index.d.ts +31 -0
  125. package/dist/screen/index.js +1 -0
  126. package/dist/screen/lib/admin-bridge.d.ts +105 -0
  127. package/dist/screen/lib/admin-bridge.js +1 -0
  128. package/dist/screen/lib/backend-error.d.ts +106 -0
  129. package/dist/screen/lib/backend-error.js +1 -0
  130. package/dist/screen/lib/chrome-theme.d.ts +21 -0
  131. package/dist/screen/lib/chrome-theme.js +1 -0
  132. package/dist/screen/lib/client-only.d.ts +24 -0
  133. package/dist/screen/lib/client-only.js +1 -0
  134. package/dist/screen/lib/content/view-tiers.d.ts +25 -0
  135. package/dist/screen/lib/content/view-tiers.js +1 -0
  136. package/dist/screen/lib/document-addressing.d.ts +20 -0
  137. package/dist/screen/lib/document-addressing.js +1 -0
  138. package/dist/screen/lib/document-contract.d.ts +230 -0
  139. package/dist/screen/lib/document-contract.js +1 -0
  140. package/dist/screen/lib/document-hooks.d.ts +71 -0
  141. package/dist/screen/lib/document-hooks.js +0 -0
  142. package/dist/screen/lib/document-switch.d.ts +74 -0
  143. package/dist/screen/lib/document-switch.js +1 -0
  144. package/dist/screen/lib/failure.d.ts +54 -0
  145. package/dist/screen/lib/failure.js +1 -0
  146. package/dist/screen/lib/host-chrome.d.ts +181 -0
  147. package/dist/screen/lib/host-chrome.js +1 -0
  148. package/dist/screen/lib/host-inbound.d.ts +20 -0
  149. package/dist/screen/lib/host-inbound.js +1 -0
  150. package/dist/screen/lib/host.d.ts +20 -0
  151. package/dist/screen/lib/host.js +1 -0
  152. package/dist/screen/lib/key-scope.d.ts +23 -0
  153. package/dist/screen/lib/key-scope.js +1 -0
  154. package/dist/screen/lib/links/link-filters.d.ts +41 -0
  155. package/dist/screen/lib/links/link-filters.js +0 -0
  156. package/dist/screen/lib/load-error.d.ts +29 -0
  157. package/dist/screen/lib/load-error.js +1 -0
  158. package/dist/screen/lib/media-panel.d.ts +94 -0
  159. package/dist/screen/lib/media-panel.js +1 -0
  160. package/dist/screen/lib/media-picker.d.ts +68 -0
  161. package/dist/screen/lib/media-picker.js +1 -0
  162. package/dist/screen/lib/page-lock.d.ts +5 -0
  163. package/dist/screen/lib/page-lock.js +1 -0
  164. package/dist/screen/lib/puck/add-target-types.d.ts +55 -0
  165. package/dist/screen/lib/puck/add-target-types.js +0 -0
  166. package/dist/screen/lib/puck/block-choice.d.ts +52 -0
  167. package/dist/screen/lib/puck/block-choice.js +1 -0
  168. package/dist/screen/lib/puck/block-types-in.d.ts +39 -0
  169. package/dist/screen/lib/puck/block-types-in.js +1 -0
  170. package/dist/screen/lib/puck/content-tree.d.ts +196 -0
  171. package/dist/screen/lib/puck/content-tree.js +1 -0
  172. package/dist/screen/lib/puck/device-fields.d.ts +126 -0
  173. package/dist/screen/lib/puck/device-fields.js +1 -0
  174. package/dist/screen/lib/puck/field-buckets.d.ts +22 -0
  175. package/dist/screen/lib/puck/field-buckets.js +1 -0
  176. package/dist/screen/lib/puck/field-markers.d.ts +205 -0
  177. package/dist/screen/lib/puck/field-markers.js +1 -0
  178. package/dist/screen/lib/puck/hidable.d.ts +44 -0
  179. package/dist/screen/lib/puck/hidable.js +1 -0
  180. package/dist/screen/lib/puck/palette-sections.d.ts +96 -0
  181. package/dist/screen/lib/puck/palette-sections.js +1 -0
  182. package/dist/screen/lib/puck/panel-fields.d.ts +47 -0
  183. package/dist/screen/lib/puck/panel-fields.js +1 -0
  184. package/dist/screen/lib/puck/panel-no-control-mark.d.ts +74 -0
  185. package/dist/screen/lib/puck/panel-no-control-mark.js +1 -0
  186. package/dist/screen/lib/puck/part-id.d.ts +23 -0
  187. package/dist/screen/lib/puck/part-id.js +1 -0
  188. package/dist/screen/lib/puck/preview-click.d.ts +49 -0
  189. package/dist/screen/lib/puck/preview-click.js +1 -0
  190. package/dist/screen/lib/puck/preview-dom.d.ts +8 -0
  191. package/dist/screen/lib/puck/preview-dom.js +1 -0
  192. package/dist/screen/lib/puck/preview-events.d.ts +25 -0
  193. package/dist/screen/lib/puck/preview-events.js +1 -0
  194. package/dist/screen/lib/puck/preview-selection-context.d.ts +72 -0
  195. package/dist/screen/lib/puck/preview-selection-context.js +1 -0
  196. package/dist/screen/lib/puck/section-doc.d.ts +20 -0
  197. package/dist/screen/lib/puck/section-doc.js +1 -0
  198. package/dist/screen/lib/puck/slot-child-name.d.ts +186 -0
  199. package/dist/screen/lib/puck/slot-child-name.js +1 -0
  200. package/dist/screen/lib/puck/slot-heal.d.ts +5 -0
  201. package/dist/screen/lib/puck/slot-heal.js +1 -0
  202. package/dist/screen/lib/puck/slot-section-spec.d.ts +96 -0
  203. package/dist/screen/lib/puck/slot-section-spec.js +0 -0
  204. package/dist/screen/lib/puck/style-clipboard.d.ts +78 -0
  205. package/dist/screen/lib/puck/style-clipboard.js +1 -0
  206. package/dist/screen/lib/puck/text-control-kinds.d.ts +71 -0
  207. package/dist/screen/lib/puck/text-control-kinds.js +1 -0
  208. package/dist/screen/lib/puck/tree-reveal.d.ts +62 -0
  209. package/dist/screen/lib/puck/tree-reveal.js +1 -0
  210. package/dist/screen/lib/puck/use-section-ops.d.ts +86 -0
  211. package/dist/screen/lib/puck/use-section-ops.js +1 -0
  212. package/dist/screen/lib/screen-parts.d.ts +727 -0
  213. package/dist/screen/lib/screen-parts.js +1 -0
  214. package/dist/screen/lib/section-template-types.d.ts +49 -0
  215. package/dist/screen/lib/section-template-types.js +0 -0
  216. package/dist/screen/lib/section-templates.d.ts +23 -0
  217. package/dist/screen/lib/section-templates.js +1 -0
  218. package/dist/screen/lib/session-context.d.ts +30 -0
  219. package/dist/screen/lib/session-context.js +1 -0
  220. package/dist/screen/lib/session.d.ts +255 -0
  221. package/dist/screen/lib/session.js +1 -0
  222. package/dist/screen/screen.css +1088 -0
  223. package/dist/session-DSQFmIvx.d.ts +43 -0
  224. package/dist/strings/catalogue.d.ts +4 -2
  225. package/dist/strings/catalogue.js +1 -1
  226. package/dist/strings/index.d.ts +4 -2
  227. package/dist/strings/index.js +1 -1
  228. package/dist/style-clipboard/index.js +1 -1
  229. package/dist/{types-DGcCsxs8.d.ts → types-CqY85foL.d.ts} +3 -65
  230. package/dist/words-D_Lnw1Gr.d.ts +59 -0
  231. package/package.json +343 -2
  232. package/dist/chunk-IG3U5NWJ.js +0 -1
@@ -0,0 +1,1088 @@
1
+ /*
2
+ * efficient-easel/screen.css — the assembled editor screen's own rules: its
3
+ * frame (`.sf-editor`, and the `--sf-*` names it aliases onto easel's `--es-*`
4
+ * tokens), the panels' heads, the add panel and block menu, the media panel,
5
+ * the preview's frame and the gaps between sections, toasts and messages.
6
+ *
7
+ * They came from efficient-web-builder's components/storefront-editor.css with
8
+ * the screen (that repo's doc/adr/0004): a rule is here when every `sf-` class
9
+ * it names is drawn by the screen, so every host that mounts the screen gets
10
+ * it; a rule that names a class only a host draws stays in that host's sheet.
11
+ * The `sf-` names are kept: the hosts' sheets and their guards address them.
12
+ *
13
+ * Load it after the other easel sheets and before a host's own sheet, which is
14
+ * where these rules sat in the builder's; that order is the one the cascade
15
+ * was measured in.
16
+ */
17
+
18
+ .sf-editor {
19
+ /* ── Accent (efficient-shell blue) ───────────────────────────────────── */
20
+ --sf-accent: var(--es-accent);
21
+ --sf-accent-tint: var(--es-accent-tint);
22
+ --sf-accent-tint-strong: var(--es-accent-tint-strong);
23
+ --sf-accent-text: var(--es-accent-text);
24
+ --sf-accent-ring: var(--es-accent-ring);
25
+ --sf-accent-shadow: var(--es-accent-shadow);
26
+ --sf-on-accent: var(--es-on-accent);
27
+
28
+ /* ── Surfaces (cool gray ramp) ──────────────────────────────────────── */
29
+ --sf-app-bg: var(--es-app-bg);
30
+ --sf-preview-bg: var(--es-preview-bg);
31
+ --sf-panel: var(--es-panel);
32
+ --sf-raised: var(--es-raised);
33
+ --sf-frame-bg: var(--es-frame-bg);
34
+ --sf-hover: var(--es-hover);
35
+ --sf-hatch: var(--es-hatch);
36
+ --sf-addcard-veil: var(--es-addcard-veil);
37
+
38
+ /* ── Fields ─────────────────────────────────────────────────────────── */
39
+ --sf-field-bg: var(--es-field-bg);
40
+ --sf-track-bg: var(--es-track-bg);
41
+ --sf-input-border: var(--es-input-border);
42
+ --sf-field-text: var(--es-field-text);
43
+ --sf-field-placeholder: var(--es-field-placeholder);
44
+ --sf-knob: var(--es-knob);
45
+
46
+ /* ── Text ───────────────────────────────────────────────────────────── */
47
+ --sf-text: var(--es-text);
48
+ --sf-text-strong: var(--es-text-strong);
49
+ --sf-text-secondary: var(--es-text-secondary);
50
+ --sf-muted: var(--es-muted);
51
+ --sf-icon: var(--es-icon);
52
+ --sf-disabled: var(--es-disabled);
53
+
54
+ /* ── Lines / chips / buttons ────────────────────────────────────────── */
55
+ --sf-divider: var(--es-divider);
56
+ --sf-line: var(--es-line);
57
+ --sf-btn-border: var(--es-btn-border);
58
+ --sf-btn-text: var(--es-btn-text);
59
+ --sf-chip-bg: var(--es-chip-bg);
60
+ --sf-chip-fg: var(--es-chip-fg);
61
+
62
+ /* ── Named chrome literals ──────────────────────────────────────────── */
63
+ --sf-device-border: var(--es-device-border);
64
+ --sf-toast-bg: var(--es-toast-bg);
65
+ --sf-scrollbar: var(--es-scrollbar);
66
+ --sf-scrollbar-hover: var(--es-scrollbar-hover);
67
+
68
+ /* ── Danger / warning / success (shell red / amber / green) ─────────── */
69
+ --sf-danger: var(--es-danger);
70
+ --sf-danger-strong: var(--es-danger-strong);
71
+ --sf-danger-tint: var(--es-danger-tint);
72
+ --sf-warning: var(--es-warning);
73
+ --sf-preview-tint: var(--es-preview-tint);
74
+
75
+ /* ── Radii / shape (tighter, shell-like) ────────────────────────────── */
76
+ /*
77
+ * ┌─ FLAT CHROME (card 66161) — these three do NOT alias the shell. ─────────
78
+ * │
79
+ * │ The panels used to be floating cards: rounded, drop-shadowed, sitting on
80
+ * │ the grey app background with a 10px gap between each pair and a 10px frame
81
+ * │ padding around the lot. The owner asked for lines instead. On a 1280px
82
+ * │ laptop that gave the website preview 50px back — 576px to 626px — because
83
+ * │ gaps, corners and shadows hold nothing. No panel got narrower and no
84
+ * │ vertical measurement changed.
85
+ * │
86
+ * │ WHY THESE STOPPED ALIASING, AND EXACTLY WHAT ENDS IT
87
+ * │
88
+ * │ The `--sf-*` block exists exactly so the shop can pick its own values off
89
+ * │ the shared `--es-*` layer. Flat is the SHOP's decision, not the shell's.
90
+ * │
91
+ * │ Note the ORIGINAL reason is already gone, so do not go looking for it:
92
+ * │ when this was written, card 66160 was mid-release on editor-shell and two
93
+ * │ windows bumping one package version would have collided. That released as
94
+ * │ `editor-shell@0.13.0` the same day, and the shop still declares `^0.12.0`.
95
+ * │ The values stayed local anyway, for a reason that has nothing to do with
96
+ * │ release timing:
97
+ * │
98
+ * │ THE OWNER DECIDED FLAT FOR THE STOREFRONT EDITOR ONLY.
99
+ * │
100
+ * │ Putting these in the shell's defaults would make the campaign editor flat
101
+ * │ too, which nobody has asked for. The campaign editor is also still pinned
102
+ * │ to `editor-shell@0.2.0` and cannot receive shell changes at all until it
103
+ * │ comes off that pin.
104
+ * │
105
+ * │ So this is NOT a half-finished alias block, and it is NOT drift. Do not
106
+ * │ "fix" these back into `var(--es-…)`.
107
+ * │
108
+ * │ IT ENDS WHEN BOTH ARE TRUE:
109
+ * │ 1. the campaign editor comes off its `editor-shell@0.2.0` pin, AND
110
+ * │ 2. the owner decides the campaign editor should go flat as well.
111
+ * │ Then promote the three values into the shell defaults, delete these three
112
+ * │ lines, and drop the matching waiver in `scripts/test-inspector-inherit.tsx`
113
+ * │ in the same change. Either one alone is not enough.
114
+ * │
115
+ * │ `scripts/test-editor-flat-chrome.ts` fails if they are re-aliased.
116
+ * └──────────────────────────────────────────────────────────────────────────
117
+ */
118
+ --sf-panel-radius: 0;
119
+ --sf-row-radius: var(--es-row-radius);
120
+ --sf-field-radius: var(--es-field-radius);
121
+ /* The editor "frame" gap: the space between the panels and the preview, and
122
+ the padding around the whole framed area (top/bottom + outer left/right).
123
+ One token so the frame tightens in ONE place — .sf-body drives it, and the
124
+ Add-section picker + section-editor top strip track it.
125
+ Flat since 66161 (see the note above). It MUST stay a length rather than a
126
+ bare `0`: it is spent inside calc() below and in the picker's width, and
127
+ `calc(0 + 46px)` is invalid CSS — the whole declaration would be dropped and
128
+ the left grip would land at the frame's edge instead of on its seam. */
129
+ --sf-panel-gap: 0px;
130
+ /* Side-panel widths ride in as editor-shell's --es-left-w / --es-right-w, set
131
+ on the editor ROOT by BOTH editors through components/panel-widths.ts (the
132
+ page chrome per drag; the standalone section editor at the fixed defaults).
133
+ The literal widths deliberately do NOT live here — see that module. */
134
+ /* Top-bar height. ONE source for both the chrome that draws it AND the
135
+ working-area overlay (the Add-section picker) that must clear
136
+ it to sit inside the frame, not cover it.
137
+ Storefront-LOCAL geometry: the top bar is this editor's own chrome
138
+ dimension, not part of the shared token layer. */
139
+ --sf-topbar-h: 52px;
140
+ /* There is no `--sf-rail-w`. Card 66198 moved the icon rail into the top bar,
141
+ where editor-shell's row variant is `width: auto` — the rail is as wide as
142
+ its buttons, so no element has a width for that token to name. It was NOT
143
+ set to 0: a 0 would leave every calc() below arithmetically correct while
144
+ keeping a token still aimed at the content column, so restoring a
145
+ non-zero value would move it. Deleting it means the
146
+ next person has to write the term back on purpose.
147
+
148
+ Where the content column BEGINS. Now the frame padding and nothing else:
149
+ the left panel is `.sf-body`'s first in-flow child, so its box starts at the
150
+ padding edge. Everything drawn against that box — the left resize grip — and
151
+ every overlay framed in the working area reads this one name.
152
+
153
+ Card 66149 is why the name exists: the left grip spent
154
+ `--sf-panel-gap + --es-left-w` while a rail really was in front of the
155
+ panel, and landed 56px inside it where no merchant could reach it. This
156
+ card is the mirror image — the danger now is a pin that keeps PAYING for a
157
+ rail nobody renders — and the guard is the same, because
158
+ `scripts/test-editor-rail-in-topbar.ts` evaluates this at probe values with
159
+ no `--sf-rail-w` in the table at all: a restored rail term throws by name
160
+ rather than quietly reserving 46px of website.
161
+
162
+ Still written as `var(--sf-panel-gap)` and not a bare `0`, for the reason
163
+ given at that token above: it is spent inside calc() and `calc(0 + 288px)`
164
+ is invalid CSS — the whole declaration would be dropped and the left grip
165
+ would land at the frame's edge instead of on its seam. */
166
+ --sf-content-x: var(--sf-panel-gap);
167
+ /* Flat since 66161 — see the note at --sf-panel-radius above. */
168
+ --sf-panel-shadow: none;
169
+ --sf-font: var(--es-font);
170
+
171
+ /* No `--puck-*` names here. Puck read its `--puck-color-azure-*` ramp and
172
+ `--puck-font-family` from editor-shell's shell.css on this same root, and
173
+ they were deliberately never re-declared in this file — one blue is the
174
+ point. Puck and those names left with blueprint 6.1/6.3; the blue is the
175
+ `--es-*` set in efficient-easel/shell.css. */
176
+
177
+ /*
178
+ * Fill the iframe viewport. `100dvh` resolves against the viewport directly,
179
+ * so the editor always fills the embedding iframe with no flex/min-height
180
+ * dependency (percentage heights don't resolve against the app shell's
181
+ * min-height, which otherwise left a gap below the editor). Mounted inside
182
+ * another app's window it fills that window instead — `.sf-embedded`, below.
183
+ */
184
+ width: 100%;
185
+ height: 100vh;
186
+ height: 100dvh;
187
+ display: flex;
188
+ flex-direction: column;
189
+ overflow: hidden;
190
+ position: relative; /* positioning context for the floating add-section modal + toast */
191
+ background: var(--sf-app-bg);
192
+ color: var(--sf-text);
193
+ font-family: var(--sf-font);
194
+ font-size: 14px;
195
+ -webkit-font-smoothing: antialiased;
196
+ }
197
+
198
+ .sf-editor *,
199
+ .sf-editor *::before,
200
+ .sf-editor *::after {
201
+ box-sizing: border-box;
202
+ }
203
+
204
+ /* Scrollbars inside the editor panels */
205
+ .sf-scroll::-webkit-scrollbar {
206
+ width: 11px;
207
+ height: 11px;
208
+ }
209
+
210
+ .sf-scroll::-webkit-scrollbar-thumb {
211
+ background: var(--sf-scrollbar);
212
+ border-radius: 8px;
213
+ border: 3px solid transparent;
214
+ background-clip: content-box;
215
+ }
216
+
217
+ .sf-scroll::-webkit-scrollbar-thumb:hover {
218
+ background: var(--sf-scrollbar-hover);
219
+ background-clip: content-box;
220
+ }
221
+
222
+ .sf-scroll::-webkit-scrollbar-track {
223
+ background: transparent;
224
+ }
225
+
226
+ /* Standalone section editor — a slim top strip carrying the "Preview mode"
227
+ switch (left) and the autosave pill (right), above the reused sf-body
228
+ (canvas + inspector). */
229
+ .sf-section-bar {
230
+ flex: none;
231
+ display: flex;
232
+ align-items: center;
233
+ justify-content: space-between;
234
+ gap: 16px;
235
+ /* PINNED at 10px by card 66161, which took `--sf-panel-gap` to 0 to flatten
236
+ the PAGE chrome. This strip belongs to the standalone SECTION editor — a
237
+ different surface — and the 10px here is real padding holding the
238
+ "Preview mode" switch and the autosave pill off the window edge, not a gap
239
+ that existed so a card could float. Following the token to 0 would have
240
+ changed a surface this card was never about, so it is spelled out instead.
241
+ Deliberately its own literal rather than a shared "inset" token: one token
242
+ quietly deciding the padding of a dialog AND a different editor's strip is
243
+ the coupling that made this a question at all. Whether this strip should go
244
+ flat too is its own decision, on its own card. */
245
+ padding: 8px 10px;
246
+ }
247
+
248
+ /* The save pill (dot, ring, words, the autosave switch around it) is
249
+ editor-shell's `.es-savestate` (efficient-easel/chrome.css), lifted from this
250
+ sheet with its card-66103 quiet state and the countdown ring (redesign 2a). */
251
+ /* ── Body / floating panels ───────────────────────────────────────────── */
252
+ .sf-body {
253
+ flex: 1;
254
+ display: flex;
255
+ gap: var(--sf-panel-gap);
256
+ padding: var(--sf-panel-gap);
257
+ min-height: 0;
258
+ /* Positioning context for the absolutely-placed resize grips (editor-shell's .es-resizer),
259
+ which the chrome pins over each panel↔preview seam. */
260
+ position: relative;
261
+ }
262
+
263
+ /* ── Body / floating panels ───────────────────────────────────────────── */
264
+ /* THE SIDE PANELS AND THEIR GRIPS ARE editor-shell's (efficient-easel/chrome.css):
265
+ `.es-sidepanel` — flush, a hairline on the SEAM side only (card 66161: draw the
266
+ seams, leave the outer edges alone), the size-container context every
267
+ `@container` rule in this sheet asks (card 66212) — and `.es-resizer`, pinned
268
+ over each seam from `--es-left-w` / `--es-right-w`, with the drag's
269
+ `body.es-col-resizing`. Lifted from this sheet (redesign 2a). The panels keep
270
+ `.sf-panel` / `.sf-panel--left|right` as well, which is what this sheet's own
271
+ descendant rules (`.sf-panel--right .sf-insp-body`, …) hang on. */
272
+ .sf-panel-head {
273
+ display: flex;
274
+ align-items: center;
275
+ justify-content: space-between;
276
+ padding: 13px 14px 11px;
277
+ flex: none;
278
+ }
279
+
280
+ /* ── Body / floating panels ───────────────────────────────────────────── */
281
+ /* A panel's own name. It is an `h2` in the views that own their title
282
+ (StylesPanel), so the rule states the three properties a heading element
283
+ would otherwise inherit from the UA sheet — `margin` above all, which is
284
+ `0.83em 0` on an h2 and would push the whole head open. The `--btn` variant
285
+ below re-states its own margin and wins by source order. */
286
+ .sf-panel-title {
287
+ font-size: 13px;
288
+ font-weight: 600;
289
+ color: var(--sf-text);
290
+ margin: 0;
291
+ }
292
+
293
+ /* In the standalone section editor the title doubles as the SELECT-ROOT control
294
+ (clicking the section name selects the section node → its section-level
295
+ fields), so it renders as a <button>. Reset the UA button chrome back to the
296
+ heading it replaces, then add row-style hover / selected affordance so it
297
+ reads as interactive and shows when the root is the active selection. */
298
+ .sf-panel-title--btn {
299
+ appearance: none;
300
+ border: 0;
301
+ background: transparent;
302
+ font-family: inherit;
303
+ cursor: pointer;
304
+ text-align: left;
305
+ padding: 5px 8px;
306
+ margin: -5px -8px;
307
+ border-radius: var(--sf-row-radius);
308
+ transition: background 0.12s;
309
+ }
310
+
311
+ .sf-panel-title--btn:hover {
312
+ background: var(--sf-hover);
313
+ }
314
+
315
+ .sf-panel-title--btn.is-selected {
316
+ color: var(--sf-accent-text);
317
+ background: var(--sf-accent-tint);
318
+ box-shadow: inset 2px 0 0 var(--sf-accent);
319
+ }
320
+
321
+ /* ── Left panel: section tree (wraps Puck.Outline) ────────────────────── */
322
+ .sf-tree {
323
+ flex: 1;
324
+ overflow-y: auto;
325
+ /* THE TWO SIDES PAY FOR DIFFERENT THINGS, and they always did.
326
+ LEFT 8 — the inset a rounded row needs. The reason WAS "a rounded bar
327
+ cannot bleed to a panel edge"; the bars are gone (One Ladder, 2026-09-04)
328
+ and the reason survives them unchanged, because every row in the tree is
329
+ still a rounded control and an inset control is simply a control.
330
+ RIGHT 11 — this element also carries `.sf-scroll`, and
331
+ `.sf-scroll::-webkit-scrollbar` is `width: 11px` of REAL, space-taking
332
+ gutter, not an overlay. This tree scrolls on almost every page. What used
333
+ to live at the row's right end was the caret; it is the count pill now,
334
+ and a count sitting under a scrollbar thumb is the same defect wearing a
335
+ different glyph.
336
+ Everything inside still starts on the same 11px text column, which is what
337
+ keeps the words aligned down the panel. */
338
+ padding: 0 11px 10px 8px;
339
+
340
+ /* ── THE TREE'S RHYTHM — the shell's names, the tree's own density ─────────
341
+ `efficient-easel` 0.19.0 stopped hiding its spacing inside literals and gave
342
+ each rhythm a name (card 66228, `efficient-easel/panel.css`). The tree reads
343
+ those names. It does NOT read the panel's VALUES and does not compute them:
344
+
345
+ · Copying 28/7 in would be a second source of truth for numbers the shell
346
+ is already the source of — the one rule that sits above a green suite.
347
+ · A scale FACTOR off them would be a lie. Lewis picked the tree's two
348
+ numbers by eye on 2026-08-31 (`mockups/layers-panel-v9-left-only-and-drag.html`
349
+ and `editor-add-and-context-v1.html`): 10 between bars, 6 between rows.
350
+ Against the panel that is 10/36 = 0.28 and 6/28 = 0.21 — TWO factors,
351
+ not one, so there is no single number to scale by. `panel.css` already
352
+ refuses this shape for the same reason, in its own words: 28 was
353
+ measured, it is not "10 plus 18".
354
+
355
+ So what is shared is the VOCABULARY and the RULE, not the values: `-group`
356
+ means "a heading, from what is above it" in both panels and `-between`
357
+ means "one row, from the next" in both, each stated once at its own
358
+ density. A tree at the panel's 28px a row would scroll forever.
359
+
360
+ Declared HERE rather than on `.sf-editor` deliberately — the settings panel
361
+ is a sibling reading the same names from the shell, and an override at the
362
+ editor root would re-rhythm IT too. `scripts/test-tree-rhythm-tokens.ts` is
363
+ the gate. */
364
+ --es-rhythm-group: 10px;
365
+ --es-rhythm-between: 6px;
366
+ }
367
+
368
+ .sf-chip {
369
+ width: 18px;
370
+ height: 18px;
371
+ border-radius: 5px;
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: center;
375
+ font-size: 12px;
376
+ flex: none;
377
+ /* NO GROUND AND NO RULE. Every row carries a glyph now, so a filled tile on
378
+ each one is 40 tiles down a page — the chip was a way of picking the icon
379
+ row out of a list that mostly had none, and there is no such list any more.
380
+ The glyph itself is the mark; `--sf-icon` is the same grey the top bar's
381
+ icons wear. */
382
+ background: transparent;
383
+ color: var(--sf-icon);
384
+ }
385
+
386
+ .sf-row-name {
387
+ flex: 1;
388
+ /* One size at every depth — the tree's three (11 / 10.5 / 11.5) were a second
389
+ way of saying the indent already says. 12px/500 is `.es-group-head`'s size
390
+ at a row's weight, so the tree and the inspector's headings agree on how big
391
+ a name in this editor is. */
392
+ font-size: 12px;
393
+ font-weight: 500;
394
+ color: var(--sf-text);
395
+ white-space: nowrap;
396
+ overflow: hidden;
397
+ text-overflow: ellipsis;
398
+ }
399
+
400
+ /* THE TWO BAR TIERS ARE GONE (One Ladder, 2026-09-04).
401
+
402
+ `.sf-row--bar` (a 1px outline, 11px/600) and `.sf-row--bar-nested` (a
403
+ `color-mix` ground one shade off the panel, 10.5px/600) were how this tree
404
+ said "a level". Both are deleted, and with them the whole question of which
405
+ tier a row belongs to.
406
+
407
+ THE NESTED BAR IS THE ONE WORTH RECORDING. Its own note said it: at ~2% off
408
+ white the fill alone disappears on a cheap panel, so it needed a hairline
409
+ beside the fill to survive. A difference that has to be said twice to be seen
410
+ once is not carrying its weight — and the indent and the subtree's guide line
411
+ were already saying the same thing, legibly, for free.
412
+
413
+ What replaced them is `.sf-subtree` below: an indent and a 1px rule. Nothing
414
+ in this tree is told apart by a fill any more except the SELECTED row, which
415
+ is the one place a fill is not competing with anything. */
416
+ .sf-add-card {
417
+ display: flex;
418
+ align-items: center;
419
+ gap: 8px;
420
+ width: 100%;
421
+ padding: 6px 9px;
422
+ border: none;
423
+ background: transparent;
424
+ border-radius: 8px;
425
+ cursor: pointer;
426
+ color: var(--sf-accent);
427
+ font-size: 12px;
428
+ font-weight: 500;
429
+ }
430
+
431
+ .sf-add-card:hover {
432
+ background: var(--sf-accent-tint);
433
+ }
434
+
435
+ .sf-add-card .sf-plus {
436
+ width: 18px;
437
+ height: 18px;
438
+ border-radius: 5px;
439
+ display: flex;
440
+ align-items: center;
441
+ justify-content: center;
442
+ font-size: 13px;
443
+ flex: none;
444
+ background: var(--sf-accent-tint);
445
+ }
446
+
447
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
448
+ .sf-blockmenu {
449
+ display: flex;
450
+ flex-direction: column;
451
+ gap: 1px;
452
+ margin: 2px 0 4px;
453
+ padding: 4px;
454
+ border: 1px solid var(--sf-line);
455
+ border-radius: 8px;
456
+ background: var(--sf-panel);
457
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
458
+ }
459
+
460
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
461
+ .sf-blockmenu-item {
462
+ display: flex;
463
+ align-items: center;
464
+ gap: 8px;
465
+ width: 100%;
466
+ padding: 7px 9px;
467
+ border: none;
468
+ background: transparent;
469
+ border-radius: 6px;
470
+ cursor: pointer;
471
+ color: var(--sf-text, var(--sf-text));
472
+ font-size: 12.5px;
473
+ text-align: left;
474
+ }
475
+
476
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
477
+ .sf-blockmenu-item:hover {
478
+ background: var(--sf-accent-tint);
479
+ color: var(--sf-accent);
480
+ }
481
+
482
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
483
+ /* A group heading inside that menu — drawn only where a choice list carries
484
+ groups (a Box's does; the Footer's and the Slide box's do not, and render
485
+ exactly as before).
486
+
487
+ ⚠️ THE SAME TYPE AS `.sf-addgroup-title` BELOW, and deliberately not a second
488
+ look: that is the heading the Add-section panel puts over every one of its
489
+ categories, and this menu is the OTHER door a merchant adds something
490
+ through. One grammar for "these belong together" (rule zero). It is not the
491
+ same CLASS because the two live in different boxes — a 4px-padded floating
492
+ menu and a 20px panel column — so only the spacing differs, and it differs
493
+ because the container does.
494
+
495
+ `role="presentation"` on the row: it is a label, not a menuitem, and a
496
+ keyboard user arrowing through the menu must not land on it. */
497
+ .sf-menugroup-head {
498
+ display: flex;
499
+ align-items: baseline;
500
+ gap: 8px;
501
+ padding: 9px 9px 5px;
502
+ }
503
+
504
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
505
+ /* No top padding on the first heading — the menu's own 4px inset already sets
506
+ it in, and a second inset would leave the first group sitting lower than the
507
+ rows it names. */
508
+ .sf-menugroup-head:first-child {
509
+ padding-top: 4px;
510
+ }
511
+
512
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
513
+ .sf-menugroup-title {
514
+ font-size: 11px;
515
+ font-weight: 700;
516
+ letter-spacing: 0.07em;
517
+ text-transform: uppercase;
518
+ color: var(--sf-muted);
519
+ }
520
+
521
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
522
+ .sf-menugroup-count {
523
+ font-size: 11px;
524
+ color: var(--sf-field-placeholder);
525
+ }
526
+
527
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
528
+ /* ── Add — a mode over the left panel (components/AddSidePanel) ────────────
529
+ Blueprint step 3.7: the floating `.sf-picker` dialog is gone, and Add is the
530
+ left panel's content while it is open. The search, the headed groups and the
531
+ tiles are efficient-easel/drag's `AddPanel` (drag.css); a section is a CARD
532
+ with its cover there (`.es-add-grid.is-cards`). What is the website's alone
533
+ is the panel's inset and the Live chat row under the groups. */
534
+ .sf-addside {
535
+ flex: 1;
536
+ min-height: 0;
537
+ overflow-y: auto;
538
+ padding: 0 14px 16px;
539
+ }
540
+
541
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
542
+ /* ── Add — a mode over the left panel (components/AddSidePanel) ────────────
543
+ Blueprint step 3.7: the floating `.sf-picker` dialog is gone, and Add is the
544
+ left panel's content while it is open. The search, the headed groups and the
545
+ tiles are efficient-easel/drag's `AddPanel` (drag.css); a section is a CARD
546
+ with its cover there (`.es-add-grid.is-cards`). What is the website's alone
547
+ is the panel's inset and the Live chat row under the groups. */
548
+ /* The hairline round a section's cover — the one the dialog's cards wore
549
+ (`--sf-thumb-thumb-border`'s value). The cover box is a containing block in
550
+ the engine itself since efficient-easel 0.80.1: a scene drawn to fill it
551
+ painted over the whole panel before. */
552
+ .sf-addside .es-add-tile-cover {
553
+ border: 1px solid #edeff4;
554
+ }
555
+
556
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
557
+ /* ── Add — a mode over the left panel (components/AddSidePanel) ────────────
558
+ Blueprint step 3.7: the floating `.sf-picker` dialog is gone, and Add is the
559
+ left panel's content while it is open. The search, the headed groups and the
560
+ tiles are efficient-easel/drag's `AddPanel` (drag.css); a section is a CARD
561
+ with its cover there (`.es-add-grid.is-cards`). What is the website's alone
562
+ is the panel's inset and the Live chat row under the groups. */
563
+ /* The Media view's body — the HOST's library (lib/media-panel), drawn
564
+ in whatever the panel leaves under the head. It scrolls itself (its grid
565
+ keeps its own toolbar pinned), so this box only hands it the height. */
566
+ .sf-media-panel {
567
+ flex: 1;
568
+ min-height: 0;
569
+ display: flex;
570
+ flex-direction: column;
571
+ overflow: hidden;
572
+ }
573
+
574
+ /* Toast — transient confirmation (e.g. after adding a section) */
575
+ .sf-toast {
576
+ position: absolute;
577
+ left: 50%;
578
+ bottom: 34px;
579
+ transform: translateX(-50%);
580
+ z-index: 60;
581
+ display: flex;
582
+ align-items: center;
583
+ gap: 10px;
584
+ padding: 11px 17px;
585
+ border-radius: 11px;
586
+ background: var(--sf-toast-bg);
587
+ color: #fff;
588
+ font-size: 13px;
589
+ box-shadow: 0 12px 30px rgba(15, 23, 42, 0.3);
590
+ }
591
+
592
+ .sf-toast-check {
593
+ display: inline-flex;
594
+ align-items: center;
595
+ justify-content: center;
596
+ width: 18px;
597
+ height: 18px;
598
+ border-radius: 50%;
599
+ background: var(--sf-accent);
600
+ color: #fff;
601
+ font-size: 11px;
602
+ flex: none;
603
+ }
604
+
605
+ /* Error variant — same dark pill, red mark instead of the green check. */
606
+ .sf-toast--error .sf-toast-check {
607
+ background: var(--sf-danger);
608
+ }
609
+
610
+ /* ── Center: device-framed preview (wraps Puck.Preview) ──────────────── */
611
+ .sf-center {
612
+ flex: 1;
613
+ min-width: 0;
614
+ min-height: 0;
615
+ background: var(--sf-preview-bg);
616
+ border-radius: var(--sf-panel-radius);
617
+ box-shadow: var(--sf-panel-shadow);
618
+ overflow: hidden;
619
+ display: flex;
620
+ /* `safe center`: centre the device sizer when it fits, but fall back to
621
+ flex-start when a zoom makes it larger than the well, so the near edge
622
+ stays reachable while the well scrolls (overflow-x set inline per-zoom). */
623
+ justify-content: safe center;
624
+ align-items: safe center;
625
+ /* No inset: the preview frame fills the well so it flows flush with the panels
626
+ at exactly the --sf-panel-gap frame gap (the flex gap between panel and this
627
+ well). Any padding here would ADD to that gap. In tablet/mobile the fixed-width
628
+ device frame still centres, showing the well's tint down its sides. */
629
+ padding: 0;
630
+ }
631
+
632
+ /* ── Center: device-framed preview (wraps Puck.Preview) ──────────────── */
633
+ .sf-frame {
634
+ width: 100%;
635
+ height: 100%;
636
+ max-height: 100%;
637
+ background: var(--sf-frame-bg);
638
+ /* SQUARE (owner, 2026-10-07: "the website should not have round corners").
639
+ The desktop frame fills the well edge to edge and meets the toolbar and the
640
+ panels directly, and those have been flat since 66161 (--sf-panel-radius:
641
+ 0) — so a 10px radius here drew one rounded notch, top-left, where the
642
+ page meets the chrome, and clipped that corner of the website itself. The
643
+ tablet and phone frames below are square for the same reason (owner, the
644
+ same day): their 18px radius read as a device's edge, and it clipped all
645
+ four corners of the page inside. They are told apart from the well by their
646
+ width and their outline, not by a corner. */
647
+ border-radius: 0;
648
+ overflow: hidden;
649
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 12px 36px rgba(0, 0, 0, 0.1);
650
+ transition: width 0.25s ease;
651
+ display: flex;
652
+ flex-direction: column;
653
+ min-height: 0;
654
+ }
655
+
656
+ /* ── Center: device-framed preview (wraps Puck.Preview) ──────────────── */
657
+ /* Sizer box for the device frame: its layout size is the frame's *painted*
658
+ (scaled) size, so the .sf-center well centres it when it fits and scrolls it
659
+ when a zoom makes it larger. The frame inside is scaled from its top-left. */
660
+ .sf-frame-scaler {
661
+ flex: none;
662
+ position: relative;
663
+ }
664
+
665
+ /* The template editor's faux browser bar (dots · page switcher · URL ·
666
+ "Preview mode" toggle) is gone: pages switch from the shared left Pages
667
+ panel, the URL is drawn by the editor WINDOW's title bar (the admin console
668
+ owns that chrome; card 66159), and Preview mode was removed — the canvas
669
+ meets the toolbar directly in both modes. */
670
+ .sf-preview-host {
671
+ flex: 1;
672
+ min-height: 0;
673
+ position: relative;
674
+ }
675
+
676
+ /* Puck.Preview fills the frame */
677
+ .sf-preview-host > div,
678
+ .sf-preview-host iframe {
679
+ width: 100% !important;
680
+ height: 100% !important;
681
+ border: 0;
682
+ }
683
+
684
+ /* …but `height: 100%` only means anything when the host's OWN height is
685
+ definite, and that depends on which shell mounted it.
686
+
687
+ The page editor wraps the host in `.sf-frame` — a column flex the chrome
688
+ sizes in pixels (`frameStyle`/`scalerStyle` in ScreenChrome) — so
689
+ the host's `flex: 1` above resolves against a real number and the rule works.
690
+
691
+ The SECTION editor has no device switch, so it drops the host straight into
692
+ the `.sf-center` well (SectionScreenShell). That well is a ROW flex with
693
+ `align-items: safe center`: the host is centred on the cross axis instead of
694
+ stretched, so its height stays `auto`. `height: 100%` then has no definite
695
+ containing block, the iframe falls back to its intrinsic 150px, and a
696
+ `100vh`/`h-screen` section inside the frame measures the viewport as 150px —
697
+ which is how a full-bleed Hero came out as a thin band floating mid-canvas.
698
+
699
+ Stretching the host restores a definite height, so the frame's viewport IS
700
+ the canvas. Scoped to the direct child so the page editor, whose host is a
701
+ child of `.sf-frame`, is untouched. */
702
+ .sf-center > .sf-preview-host {
703
+ align-self: stretch;
704
+ }
705
+
706
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
707
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
708
+ seam between top-level sections, placed at the boundary Y measured from the
709
+ live iframe. The container ignores pointer events so the canvas stays
710
+ interactive; only the slim bands are clickable. On hover a band shows an accent
711
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
712
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
713
+ .sf-preview-gaps {
714
+ position: absolute;
715
+ inset: 0;
716
+ overflow: hidden;
717
+ pointer-events: none;
718
+ z-index: 6;
719
+ }
720
+
721
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
722
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
723
+ seam between top-level sections, placed at the boundary Y measured from the
724
+ live iframe. The container ignores pointer events so the canvas stays
725
+ interactive; only the slim bands are clickable. On hover a band shows an accent
726
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
727
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
728
+ /* The full-width band only POSITIONS the affordance over a seam — it is itself
729
+ click-through (pointer-events:none) so it never steals canvas clicks / Puck
730
+ selection near a section boundary. Only the centred pill is interactive. */
731
+ .sf-preview-gap {
732
+ position: absolute;
733
+ left: 0;
734
+ right: 0;
735
+ height: 24px;
736
+ transform: translateY(-50%);
737
+ display: flex;
738
+ align-items: center;
739
+ justify-content: center;
740
+ padding: 0;
741
+ border: 0;
742
+ background: transparent;
743
+ pointer-events: none;
744
+ }
745
+
746
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
747
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
748
+ seam between top-level sections, placed at the boundary Y measured from the
749
+ live iframe. The container ignores pointer events so the canvas stays
750
+ interactive; only the slim bands are clickable. On hover a band shows an accent
751
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
752
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
753
+ .sf-preview-gap-line {
754
+ position: absolute;
755
+ left: 0;
756
+ right: 0;
757
+ top: 50%;
758
+ height: 2px;
759
+ transform: translateY(-50%);
760
+ background: var(--sf-accent);
761
+ opacity: 0;
762
+ pointer-events: none;
763
+ transition: opacity 0.12s;
764
+ }
765
+
766
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
767
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
768
+ seam between top-level sections, placed at the boundary Y measured from the
769
+ live iframe. The container ignores pointer events so the canvas stays
770
+ interactive; only the slim bands are clickable. On hover a band shows an accent
771
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
772
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
773
+ /* The only clickable element: a compact "+" chip that is hidden at rest and
774
+ fades in on hover, expanding to the full "Add section" pill. Keeping it
775
+ invisible until hovered means the canvas carries no persistent affordance,
776
+ while the dead zone stays no larger than this small chip (opacity:0 still
777
+ hit-tests, so the seam remains hoverable). */
778
+ .sf-preview-gap-pill {
779
+ position: relative;
780
+ display: inline-flex;
781
+ align-items: center;
782
+ padding: 4px;
783
+ border-radius: 999px;
784
+ background: var(--sf-toast-bg);
785
+ color: #fff;
786
+ font-family: var(--sf-font);
787
+ font-size: 12px;
788
+ font-weight: 500;
789
+ white-space: nowrap;
790
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.28);
791
+ opacity: 0;
792
+ cursor: pointer;
793
+ pointer-events: auto;
794
+ transition: opacity 0.12s, padding 0.12s;
795
+ }
796
+
797
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
798
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
799
+ seam between top-level sections, placed at the boundary Y measured from the
800
+ live iframe. The container ignores pointer events so the canvas stays
801
+ interactive; only the slim bands are clickable. On hover a band shows an accent
802
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
803
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
804
+ .sf-preview-gap-pill:hover {
805
+ opacity: 1;
806
+ padding: 4px 12px 4px 4px;
807
+ }
808
+
809
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
810
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
811
+ seam between top-level sections, placed at the boundary Y measured from the
812
+ live iframe. The container ignores pointer events so the canvas stays
813
+ interactive; only the slim bands are clickable. On hover a band shows an accent
814
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
815
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
816
+ .sf-preview-gap-plus {
817
+ display: inline-flex;
818
+ align-items: center;
819
+ justify-content: center;
820
+ width: 18px;
821
+ height: 18px;
822
+ border-radius: 50%;
823
+ background: var(--sf-accent);
824
+ color: #fff;
825
+ font-size: 13px;
826
+ line-height: 1;
827
+ flex: none;
828
+ }
829
+
830
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
831
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
832
+ seam between top-level sections, placed at the boundary Y measured from the
833
+ live iframe. The container ignores pointer events so the canvas stays
834
+ interactive; only the slim bands are clickable. On hover a band shows an accent
835
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
836
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
837
+ /* Label collapsed until hover so the resting chip is just the "+". */
838
+ .sf-preview-gap-label {
839
+ max-width: 0;
840
+ margin-left: 0;
841
+ overflow: hidden;
842
+ opacity: 0;
843
+ transition: max-width 0.14s, margin-left 0.14s, opacity 0.12s;
844
+ }
845
+
846
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
847
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
848
+ seam between top-level sections, placed at the boundary Y measured from the
849
+ live iframe. The container ignores pointer events so the canvas stays
850
+ interactive; only the slim bands are clickable. On hover a band shows an accent
851
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
852
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
853
+ .sf-preview-gap-pill:hover .sf-preview-gap-label {
854
+ max-width: 140px;
855
+ margin-left: 6px;
856
+ opacity: 1;
857
+ }
858
+
859
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
860
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
861
+ seam between top-level sections, placed at the boundary Y measured from the
862
+ live iframe. The container ignores pointer events so the canvas stays
863
+ interactive; only the slim bands are clickable. On hover a band shows an accent
864
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
865
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
866
+ .sf-preview-gap:has(.sf-preview-gap-pill:hover) .sf-preview-gap-line {
867
+ opacity: 1;
868
+ }
869
+
870
+ .sf-msg {
871
+ padding: 24px;
872
+ font-size: 13px;
873
+ color: var(--sf-icon);
874
+ }
875
+
876
+ .sf-msg--error {
877
+ color: var(--sf-danger);
878
+ }
879
+
880
+ /* ── The icon rail: the shared editor-shell EaselRail (`.es-rail`) ───────────
881
+ The rail markup + base styles live in the `efficient-easel` package (imported via
882
+ `efficient-easel/rail.css`); the storefront no longer hand-rolls `.sf-rail`.
883
+ editor-shell ships its own defaults, so we remap the themeable `--es-rail-*`
884
+ custom properties onto the storefront's own `--sf-*` tokens, which is what
885
+ keeps the shared rail reading as this editor's chrome rather than as the
886
+ package's. (One mode only — the editor is deliberately light-only; see the
887
+ note at the top of this file.)
888
+ Scoped `.sf-editor .es-rail` (specificity 0,2,0) so it always beats rail.css's
889
+ base `.es-rail` (0,1,0) whatever order the two stylesheets load in.
890
+
891
+ THREE TOKENS ARE DELIBERATELY NOT REMAPPED HERE, and each absence is load-
892
+ bearing since card 66198 laid the rail into the top bar as a row:
893
+
894
+ --es-rail-w only the COLUMN reads it (`.es-rail { width:
895
+ var(--es-rail-w) }`); the row is `width: auto` and sizes
896
+ to its buttons, so a remap would be config nothing reads.
897
+ --es-rail-bg the row variant re-points these to transparent / none / 0
898
+ --es-rail-shadow ON PURPOSE: the bar already has its own surface, and a
899
+ --es-rail-radius second white card floating inside it is the bug. This
900
+ rule ties with `.es-rail.es-rail-row` on specificity
901
+ (0,2,0 both), so setting them here would silently WIN
902
+ and put that card back. The rail's surface is the bar's.
903
+
904
+ Everything left below is colour the row variant does not touch. */
905
+ .sf-editor .es-rail {
906
+ --es-rail-fg: var(--sf-muted);
907
+ --es-rail-hover-bg: var(--sf-hover);
908
+ --es-rail-hover-fg: var(--sf-text);
909
+ --es-rail-accent: var(--sf-accent);
910
+ --es-rail-accent-tint: var(--sf-accent-tint);
911
+ }
912
+
913
+ /* ── The icon rail: the shared editor-shell EaselRail (`.es-rail`) ───────────
914
+ The rail markup + base styles live in the `efficient-easel` package (imported via
915
+ `efficient-easel/rail.css`); the storefront no longer hand-rolls `.sf-rail`.
916
+ editor-shell ships its own defaults, so we remap the themeable `--es-rail-*`
917
+ custom properties onto the storefront's own `--sf-*` tokens, which is what
918
+ keeps the shared rail reading as this editor's chrome rather than as the
919
+ package's. (One mode only — the editor is deliberately light-only; see the
920
+ note at the top of this file.)
921
+ Scoped `.sf-editor .es-rail` (specificity 0,2,0) so it always beats rail.css's
922
+ base `.es-rail` (0,1,0) whatever order the two stylesheets load in.
923
+
924
+ THREE TOKENS ARE DELIBERATELY NOT REMAPPED HERE, and each absence is load-
925
+ bearing since card 66198 laid the rail into the top bar as a row:
926
+
927
+ --es-rail-w only the COLUMN reads it (`.es-rail { width:
928
+ var(--es-rail-w) }`); the row is `width: auto` and sizes
929
+ to its buttons, so a remap would be config nothing reads.
930
+ --es-rail-bg the row variant re-points these to transparent / none / 0
931
+ --es-rail-shadow ON PURPOSE: the bar already has its own surface, and a
932
+ --es-rail-radius second white card floating inside it is the bug. This
933
+ rule ties with `.es-rail.es-rail-row` on specificity
934
+ (0,2,0 both), so setting them here would silently WIN
935
+ and put that card back. The rail's surface is the bar's.
936
+
937
+ Everything left below is colour the row variant does not touch. */
938
+ /* THE RAIL COLUMN (owner, 2026-09-04: "the top control panel and the panel
939
+ below it" must read as one) — the cluster exactly the left panel's width,
940
+ carrying its hairline up through the bar, the buttons spread across it (his
941
+ option A, spread evenly), and the arrow under the open panel's icon — is editor-shell's
942
+ `.es-cluster--rail` (efficient-easel/chrome.css), lifted from this sheet with its
943
+ `flex: none` and its clip-path triangle (redesign 2a). ⚠️ `MIN_PANEL_W` 220 is
944
+ still load-bearing on it: six 34px buttons fill 204 exactly, and a SEVENTH
945
+ rail button does not fit at 220. `scripts/test-editor-rail-in-topbar.ts`
946
+ holds that arithmetic. */
947
+ /* The "+" is a plain character in a rail button, the way it was a plain
948
+ character in the toolbar button it replaces — every other "+" affordance in
949
+ this editor is one (`.sf-gap-plus`, `.sf-addcard-plus`, `.sf-plus`). The rail
950
+ sizes SVGs and has nothing to say about text, so its one line is here. */
951
+ .sf-rail-plus {
952
+ font-size: 20px;
953
+ line-height: 1;
954
+ }
955
+
956
+ /* ── NO DIVIDER HAIRLINES IN THIS PANEL (card 66334) ──────────────────────────
957
+ The owner asked for these lines to go twice. Deleting our own was only part of
958
+ it: the rule below is drawn by a PACKAGE, it is the majority of what is
959
+ actually on screen, and it cannot be deleted from here.
960
+
961
+ Puck rules the gap between every pair of top-level fields, so it draws once
962
+ per field for the whole panel — 28 lines in a single section panel, measured.
963
+ That is most of the panel's lines on its own, which is why removing the shop's
964
+ own two heading rules (card 66300) did not change what he saw.
965
+
966
+ ⚠️ THERE WERE TWO OF THESE, AND THE OTHER ONE IS GONE. The shell drew
967
+ `.es-drill + .es-drill`, 16 more lines between the rows that list a section's
968
+ text parts. It was held here under a written expiry, because a rule that lives
969
+ in another repo could not be reached from this one. editor-shell 0.29.0 (card
970
+ 66350) deleted it AT SOURCE and card 66351 deleted this repo's copy with it —
971
+ the ending the Puck rule below cannot have, since `@measured/puck` is not ours
972
+ to change. That seam is now held from both ends by
973
+ `scripts/test-drill-rows-separated-by-air.ts`: it fails if the shell starts
974
+ drawing on it again, and it fails if a local override for it reappears here.
975
+
976
+ ⚠️ SPECIFICITY, NOT `!important`. Puck's rule is (0,3,0) —
977
+ `wrapFields field + field` — so this restates all three parts to match it, and
978
+ wins on order: `storefront-editor.css` is imported after `puck.css` and after
979
+ `panel.css` at every call site. The `[class*=]` form is this file's existing
980
+ idiom for Puck's hashed module names (see the three rules above); a bare class
981
+ would break on the next Puck bump, silently, by putting the lines back.
982
+
983
+ This is an override of a live upstream rule, not dead code:
984
+ `scripts/test-panel-no-divider-lines.tsx` §2 asserts the upstream rule still
985
+ exists before checking it, so if Puck ever drops its own line, the guard says
986
+ to delete the override rather than leave it lying here — which is exactly what
987
+ happened to the drill one above. */
988
+ /* The one line that says WHICH device's settings are on screen. Quiet on
989
+ purpose — it sits above every group, so it earns its space by being read once
990
+ and then ignored. Hidden entirely on desktop, the resting state.
991
+
992
+ It is a SCOPE banner, not a note (card 66121): it does not explain, it says
993
+ what the panel below it is showing, and it is tinted so it reads as a state.
994
+ Nor is it one of this file's chips — those are all small square glyph holders
995
+ (.sf-chip, .sf-insp-chip) or a colour swatch (.sf-readability-chip); this is
996
+ a full-width strip, so it keeps its own rule. */
997
+ .sf-device-scope {
998
+ margin: 8px 0 2px;
999
+ padding: 7px 10px;
1000
+ border-radius: 7px;
1001
+ background: var(--sf-chip-bg);
1002
+ font-size: 11.5px;
1003
+ line-height: 1.35;
1004
+ color: var(--sf-muted);
1005
+ }
1006
+
1007
+ /* ─── The strip needs a parent with a WIDTH (card 66248) ──────────────────────
1008
+ The shared strip is `efficient-easel`'s `SettingsSegmented` now, and its outer
1009
+ box `.es-segs-fit` is `container-type: inline-size` — so it takes its inline
1010
+ size FROM its parent and contributes NOTHING to it. `HeaderSettingsPanel`'s
1011
+ `Field` puts the control in a `flex-shrink-0` div, which sizes TO its content.
1012
+
1013
+ A parent sizing to a child that sizes to its parent resolves to ZERO. Measured
1014
+ in a real browser at both panel widths: control 0px, strip 0px, every word
1015
+ clamped to `max-width: 0`, and each button reduced to its own 16px of padding.
1016
+ Seven rows drew a label and no control, and every automated gate we own was
1017
+ green over it — a zero-width box is present in every tree walk and absent to
1018
+ every merchant. Forcing `container-type: normal` restores the strip to
1019
+ 104.37px, which is what says the containment is the cause and not a symptom.
1020
+
1021
+ THE RULE HAS TO SIT ABOVE THE CONTAINMENT, so this is not something the strip
1022
+ or a wrapper inside it can fix — a child cannot bootstrap width from a parent
1023
+ that has none. Measured: a full-width wrapper INSIDE the control div leaves
1024
+ all three at 0.
1025
+
1026
+ `:has()` KEEPS IT OFF THE ROWS THIS CARD DID NOT MOVE. `Field`'s control div
1027
+ also carries switches and selects, which ride the LABEL line by card 8801's
1028
+ deliberate design — that is what lets a 308px panel show four settings instead
1029
+ of three. Matching only a control div that actually contains a strip leaves
1030
+ them exactly as they were. Not a new dependency: `efficient-easel`'s own
1031
+ `panel.css` uses `:has()` three times, so the package this panel renders
1032
+ already requires it.
1033
+
1034
+ FULL WIDTH IS THE HOUSE SHAPE, not a compromise — `efficient-easel`'s
1035
+ `SettingsField` stacks its own rows for the same reason ("a 308px panel cannot
1036
+ afford a label column AND a usable slider"). The label moves above the strip
1037
+ and the strip takes the body's width. Scoped to `.sf-panel` because
1038
+ `.flex-shrink-0` is a Tailwind utility used in seven other files, and none of
1039
+ them is this panel. */
1040
+ .sf-panel .flex-shrink-0:has(.es-segs-fit) {
1041
+ flex-basis: 100%;
1042
+ min-width: 0;
1043
+ }
1044
+
1045
+ /* ── Side panel heads (components/SidePanelHead) ─────────────────────────
1046
+ Every side panel's head is efficient-easel's `InspectorHead`; these two
1047
+ are the pieces the website editor adds to it. The hairline parts a panel's
1048
+ own tools from the hide button that ends every head, and a mode's way out
1049
+ is a word ("Done", "Cancel") on the engine's ghost button, drawn to the
1050
+ 28px of the head's icon buttons so the row keeps one height. */
1051
+ .sf-head-divider {
1052
+ width: 1px;
1053
+ height: 16px;
1054
+ margin: 0 4px;
1055
+ background: var(--sf-line);
1056
+ flex: none;
1057
+ }
1058
+
1059
+ /* ── Side panel heads (components/SidePanelHead) ─────────────────────────
1060
+ Every side panel's head is efficient-easel's `InspectorHead`; these two
1061
+ are the pieces the website editor adds to it. The hairline parts a panel's
1062
+ own tools from the hide button that ends every head, and a mode's way out
1063
+ is a word ("Done", "Cancel") on the engine's ghost button, drawn to the
1064
+ 28px of the head's icon buttons so the row keeps one height. */
1065
+ .es-btn-ghost.sf-head-textbtn {
1066
+ height: 28px;
1067
+ padding: 0 10px;
1068
+ font-size: 12.5px;
1069
+ border-radius: 7px;
1070
+ white-space: nowrap;
1071
+ }
1072
+
1073
+ /* ── Side panel heads (components/SidePanelHead) ─────────────────────────
1074
+ Every side panel's head is efficient-easel's `InspectorHead`; these two
1075
+ are the pieces the website editor adds to it. The hairline parts a panel's
1076
+ own tools from the hide button that ends every head, and a mode's way out
1077
+ is a word ("Done", "Cancel") on the engine's ghost button, drawn to the
1078
+ 28px of the head's icon buttons so the row keeps one height. */
1079
+ /* A hidden side panel's strip (components/SidePanelHead `PanelShowStrip`):
1080
+ one Show panel button where the panel was. It IS a side panel — the
1081
+ engine's `.es-sidepanel--left|right` gives it the panels' ground and the
1082
+ hairline on the seam side — only 40px wide, its button level with the
1083
+ heads' buttons (the head's 12px top padding, a 28px button). */
1084
+ .es-sidepanel.sf-panel-strip {
1085
+ width: 40px;
1086
+ align-items: center;
1087
+ padding-top: 12px;
1088
+ }