efficient-web-builder 0.10.0 → 2.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 (1211) hide show
  1. package/CHANGELOG.md +50 -367
  2. package/README.md +108 -261
  3. package/dist/components/AddBlockRow.d.ts +1 -0
  4. package/dist/components/AddBlockRow.js +1 -0
  5. package/dist/components/AddChildRow.d.ts +1 -0
  6. package/dist/components/AddChildRow.js +1 -0
  7. package/dist/components/AddSidePanel.d.ts +2 -0
  8. package/dist/components/AddSidePanel.js +1 -0
  9. package/dist/components/AnimatedStat.d.ts +6 -0
  10. package/dist/components/AnimatedStat.js +1 -0
  11. package/dist/components/ApplyCornersToSiblings.d.ts +9 -0
  12. package/dist/components/ApplyCornersToSiblings.js +1 -0
  13. package/dist/components/Banner.d.ts +12 -0
  14. package/dist/components/Banner.js +1 -0
  15. package/dist/components/BlockFrame.d.ts +15 -0
  16. package/dist/components/BlockFrame.js +1 -0
  17. package/dist/components/BlockGapField.d.ts +5 -0
  18. package/dist/components/BlockGapField.js +1 -0
  19. package/dist/components/BlogPostSource.d.ts +14 -0
  20. package/dist/components/BlogPostSource.js +1 -0
  21. package/dist/components/BoxSurfaceContext.d.ts +7 -0
  22. package/dist/components/BoxSurfaceContext.js +1 -0
  23. package/dist/components/BoxTabs.d.ts +6 -0
  24. package/dist/components/BoxTabs.js +1 -0
  25. package/dist/components/Breadcrumb.d.ts +10 -0
  26. package/dist/components/Breadcrumb.js +1 -0
  27. package/dist/components/Btn.d.ts +22 -0
  28. package/dist/components/Btn.js +1 -0
  29. package/dist/components/ButtonBlock.d.ts +13 -0
  30. package/dist/components/ButtonBlock.js +1 -0
  31. package/dist/components/CardShadowField.d.ts +6 -0
  32. package/dist/components/CardShadowField.js +1 -0
  33. package/dist/components/Cards.d.ts +15 -0
  34. package/dist/components/Cards.js +1 -0
  35. package/dist/components/CategoryBar.d.ts +16 -0
  36. package/dist/components/CategoryBar.js +1 -0
  37. package/dist/components/ChartSection.d.ts +16 -0
  38. package/dist/components/ChartSection.js +1 -0
  39. package/dist/components/ChatSettingsPanel.d.ts +1 -0
  40. package/dist/components/ChatSettingsPanel.js +1 -0
  41. package/dist/components/ClearColoursDialog.d.ts +5 -0
  42. package/dist/components/ClearColoursDialog.js +1 -0
  43. package/dist/components/ContactLocations.d.ts +17 -0
  44. package/dist/components/ContactLocations.js +73 -0
  45. package/dist/components/CornersField.d.ts +6 -0
  46. package/dist/components/CornersField.js +1 -0
  47. package/dist/components/CountUpText.d.ts +3 -0
  48. package/dist/components/CountUpText.js +1 -0
  49. package/dist/components/Divider.d.ts +9 -0
  50. package/dist/components/Divider.js +1 -0
  51. package/dist/components/DocBody.d.ts +13 -0
  52. package/dist/components/DocBody.js +7 -0
  53. package/dist/components/DocMasthead.d.ts +13 -0
  54. package/dist/components/DocMasthead.js +1 -0
  55. package/dist/components/DocMetaCards.d.ts +10 -0
  56. package/dist/components/DocMetaCards.js +1 -0
  57. package/dist/components/DocTabs.d.ts +12 -0
  58. package/dist/components/DocTabs.js +1 -0
  59. package/dist/components/DuplicateToPageMenu.d.ts +12 -0
  60. package/dist/components/DuplicateToPageMenu.js +1 -0
  61. package/dist/components/EditorScreenMount.d.ts +1 -0
  62. package/dist/components/EditorScreenMount.js +1 -0
  63. package/dist/components/EditorStoreProvider.d.ts +1 -0
  64. package/dist/components/EditorStoreProvider.js +1 -0
  65. package/dist/components/EditorialSectionHead.d.ts +11 -0
  66. package/dist/components/EditorialSectionHead.js +1 -0
  67. package/dist/components/Eyebrow.d.ts +9 -0
  68. package/dist/components/Eyebrow.js +1 -0
  69. package/dist/components/FAQ.d.ts +20 -0
  70. package/dist/components/FAQ.js +1 -0
  71. package/dist/components/FeaturedCover.d.ts +13 -0
  72. package/dist/components/FeaturedCover.js +1 -0
  73. package/dist/components/FieldGlyph.d.ts +12 -0
  74. package/dist/components/FieldGlyph.js +1 -0
  75. package/dist/components/Flex.d.ts +32 -0
  76. package/dist/components/Flex.js +1 -0
  77. package/dist/components/Footprint.d.ts +16 -0
  78. package/dist/components/Footprint.js +27 -0
  79. package/dist/components/Grid.d.ts +11 -0
  80. package/dist/components/Grid.js +1 -0
  81. package/dist/components/Heading.d.ts +11 -0
  82. package/dist/components/Heading.js +1 -0
  83. package/dist/components/HeadingAudit.d.ts +1 -0
  84. package/dist/components/HeadingAudit.js +1 -0
  85. package/dist/components/HeroArrows.d.ts +8 -0
  86. package/dist/components/HeroArrows.js +1 -0
  87. package/dist/components/HeroGroup.d.ts +20 -0
  88. package/dist/components/HeroGroup.js +1 -0
  89. package/dist/components/HeroHeldFrame.d.ts +8 -0
  90. package/dist/components/HeroHeldFrame.js +1 -0
  91. package/dist/components/HeroImageField.d.ts +6 -0
  92. package/dist/components/HeroImageField.js +1 -0
  93. package/dist/components/HeroPoster.d.ts +10 -0
  94. package/dist/components/HeroPoster.js +1 -0
  95. package/dist/components/HeroSection.d.ts +35 -0
  96. package/dist/components/HeroSection.js +11 -0
  97. package/dist/components/HeroSingle.d.ts +17 -0
  98. package/dist/components/HeroSingle.js +1 -0
  99. package/dist/components/HeroSlide.d.ts +30 -0
  100. package/dist/components/HeroSlide.js +1 -0
  101. package/dist/components/HoverEffectField.d.ts +6 -0
  102. package/dist/components/HoverEffectField.js +1 -0
  103. package/dist/components/ImageBlock.d.ts +18 -0
  104. package/dist/components/ImageBlock.js +1 -0
  105. package/dist/components/ImageFocusRow.d.ts +7 -0
  106. package/dist/components/ImageFocusRow.js +1 -0
  107. package/dist/components/IntegrationsSection.d.ts +14 -0
  108. package/dist/components/IntegrationsSection.js +1 -0
  109. package/dist/components/IntroBleed.d.ts +32 -0
  110. package/dist/components/IntroBleed.js +1 -0
  111. package/dist/components/KeepHint.d.ts +5 -0
  112. package/dist/components/KeepHint.js +1 -0
  113. package/dist/components/KeyFeatures.d.ts +14 -0
  114. package/dist/components/KeyFeatures.js +1 -0
  115. package/dist/components/LocationGroupHeader.d.ts +5 -0
  116. package/dist/components/LocationGroupHeader.js +1 -0
  117. package/dist/components/LocationsGroupedField.d.ts +5 -0
  118. package/dist/components/LocationsGroupedField.js +1 -0
  119. package/dist/components/LocationsTreeContext.d.ts +2 -0
  120. package/dist/components/LocationsTreeContext.js +1 -0
  121. package/dist/components/Logo.d.ts +27 -0
  122. package/dist/components/Logo.js +1 -0
  123. package/dist/components/LogoBlock.d.ts +8 -0
  124. package/dist/components/LogoBlock.js +1 -0
  125. package/dist/components/Logos.d.ts +15 -0
  126. package/dist/components/Logos.js +1 -0
  127. package/dist/components/LongRead.d.ts +20 -0
  128. package/dist/components/LongRead.js +1 -0
  129. package/dist/components/Manifesto.d.ts +14 -0
  130. package/dist/components/Manifesto.js +1 -0
  131. package/dist/components/Masthead.d.ts +16 -0
  132. package/dist/components/Masthead.js +1 -0
  133. package/dist/components/MediaSidePanel.d.ts +1 -0
  134. package/dist/components/MediaSidePanel.js +1 -0
  135. package/dist/components/MediaUploadField.d.ts +9 -0
  136. package/dist/components/MediaUploadField.js +1 -0
  137. package/dist/components/MockupSection.d.ts +59 -0
  138. package/dist/components/MockupSection.js +1 -0
  139. package/dist/components/MultiSelectField.d.ts +19 -0
  140. package/dist/components/MultiSelectField.js +1 -0
  141. package/dist/components/NodeContextMenu.d.ts +2 -0
  142. package/dist/components/NodeContextMenu.js +1 -0
  143. package/dist/components/OrderSummary.d.ts +17 -0
  144. package/dist/components/OrderSummary.js +1 -0
  145. package/dist/components/PageEditGuardNotices.d.ts +1 -0
  146. package/dist/components/PageEditGuardNotices.js +1 -0
  147. package/dist/components/PagesPanel.d.ts +8 -0
  148. package/dist/components/PagesPanel.js +1 -0
  149. package/dist/components/PanelToggleIcon.d.ts +1 -0
  150. package/dist/components/PanelToggleIcon.js +1 -0
  151. package/dist/components/ParagraphsA.d.ts +15 -0
  152. package/dist/components/ParagraphsA.js +1 -0
  153. package/dist/components/PartGlyphs.d.ts +7 -0
  154. package/dist/components/PartGlyphs.js +1 -0
  155. package/dist/components/PartInkControl.d.ts +7 -0
  156. package/dist/components/PartInkControl.js +1 -0
  157. package/dist/components/PartResetDot.d.ts +5 -0
  158. package/dist/components/PartResetDot.js +1 -0
  159. package/dist/components/PartSettingsPanel.d.ts +68 -0
  160. package/dist/components/PartSettingsPanel.js +1 -0
  161. package/dist/components/PartStyledText.d.ts +5 -0
  162. package/dist/components/PartStyledText.js +1 -0
  163. package/dist/components/PartTextBox.d.ts +20 -0
  164. package/dist/components/PartTextBox.js +1 -0
  165. package/dist/components/PaymentBadges.d.ts +14 -0
  166. package/dist/components/PaymentBadges.js +1 -0
  167. package/dist/components/PhoneMenuTools.d.ts +12 -0
  168. package/dist/components/PhoneMenuTools.js +1 -0
  169. package/dist/components/PhotographyField.d.ts +6 -0
  170. package/dist/components/PhotographyField.js +1 -0
  171. package/dist/components/Placeholder.d.ts +18 -0
  172. package/dist/components/Placeholder.js +1 -0
  173. package/dist/components/PostCard.d.ts +37 -0
  174. package/dist/components/PostCard.js +1 -0
  175. package/dist/components/PreviewChatContext.d.ts +23 -0
  176. package/dist/components/PreviewChatContext.js +1 -0
  177. package/dist/components/PreviewDeviceContext.d.ts +2 -0
  178. package/dist/components/PreviewDeviceContext.js +1 -0
  179. package/dist/components/PreviewFocusContext.d.ts +15 -0
  180. package/dist/components/PreviewFocusContext.js +1 -0
  181. package/dist/components/PreviewFooterContext.d.ts +17 -0
  182. package/dist/components/PreviewFooterContext.js +1 -0
  183. package/dist/components/PreviewHeaderContext.d.ts +97 -0
  184. package/dist/components/PreviewHeaderContext.js +1 -0
  185. package/dist/components/PreviewLocationPartContext.d.ts +10 -0
  186. package/dist/components/PreviewLocationPartContext.js +1 -0
  187. package/dist/components/PreviewPartAnchors.d.ts +16 -0
  188. package/dist/components/PreviewPartAnchors.js +1 -0
  189. package/dist/components/PreviewPartStyleContext.d.ts +21 -0
  190. package/dist/components/PreviewPartStyleContext.js +1 -0
  191. package/dist/components/PreviewPartsContext.d.ts +1 -0
  192. package/dist/components/PreviewPartsContext.js +1 -0
  193. package/dist/components/PreviewSocialContext.d.ts +30 -0
  194. package/dist/components/PreviewSocialContext.js +1 -0
  195. package/dist/components/PreviewSurfaceContext.d.ts +10 -0
  196. package/dist/components/PreviewSurfaceContext.js +1 -0
  197. package/dist/components/PreviewThemeFrame.d.ts +6 -0
  198. package/dist/components/PreviewThemeFrame.js +11 -0
  199. package/dist/components/PreviewTickerContext.d.ts +4 -0
  200. package/dist/components/PreviewTickerContext.js +1 -0
  201. package/dist/components/ProcessSteps.d.ts +32 -0
  202. package/dist/components/ProcessSteps.js +1 -0
  203. package/dist/components/Prose.d.ts +9 -0
  204. package/dist/components/Prose.js +1 -0
  205. package/dist/components/PublishedFooterContext.d.ts +11 -0
  206. package/dist/components/PublishedFooterContext.js +1 -0
  207. package/dist/components/PublishedPartStylesContext.d.ts +15 -0
  208. package/dist/components/PublishedPartStylesContext.js +1 -0
  209. package/dist/components/RMark.d.ts +6 -0
  210. package/dist/components/RMark.js +1 -0
  211. package/dist/components/Reel.d.ts +15 -0
  212. package/dist/components/Reel.js +1 -0
  213. package/dist/components/RuleLine.d.ts +1 -0
  214. package/dist/components/RuleLine.js +1 -0
  215. package/dist/components/SearchSelectField.d.ts +22 -0
  216. package/dist/components/SearchSelectField.js +1 -0
  217. package/dist/components/SectionAnimation.d.ts +7 -0
  218. package/dist/components/SectionAnimation.js +1 -0
  219. package/dist/components/SectionEditor.d.ts +63 -0
  220. package/dist/components/SectionEditor.js +1 -0
  221. package/dist/components/SectionEditorShell.d.ts +1 -0
  222. package/dist/components/SectionEditorShell.js +1 -0
  223. package/dist/components/SectionEyebrow.d.ts +10 -0
  224. package/dist/components/SectionEyebrow.js +1 -0
  225. package/dist/components/SectionHeader.d.ts +29 -0
  226. package/dist/components/SectionHeader.js +1 -0
  227. package/dist/components/SectionIcon.d.ts +5 -0
  228. package/dist/components/SectionIcon.js +1 -0
  229. package/dist/components/SectionTextEntries.d.ts +5 -0
  230. package/dist/components/SectionTextEntries.js +1 -0
  231. package/dist/components/SectionTextPartBridge.d.ts +6 -0
  232. package/dist/components/SectionTextPartBridge.js +1 -0
  233. package/dist/components/SectionThemePicker.d.ts +25 -0
  234. package/dist/components/SectionThemePicker.js +1 -0
  235. package/dist/components/SectionThumbnail.d.ts +3 -0
  236. package/dist/components/SectionThumbnail.js +1 -0
  237. package/dist/components/Series.d.ts +19 -0
  238. package/dist/components/Series.js +1 -0
  239. package/dist/components/ShadowColourField.d.ts +5 -0
  240. package/dist/components/ShadowColourField.js +1 -0
  241. package/dist/components/SidePanelHead.d.ts +1 -0
  242. package/dist/components/SidePanelHead.js +1 -0
  243. package/dist/components/SideSpaceField.d.ts +7 -0
  244. package/dist/components/SideSpaceField.js +1 -0
  245. package/dist/components/SiteEditorProvider.d.ts +3 -0
  246. package/dist/components/SiteEditorProvider.js +1 -0
  247. package/dist/components/SiteFields.d.ts +6 -0
  248. package/dist/components/SiteFields.js +1 -0
  249. package/dist/components/SiteGutterFrame.d.ts +9 -0
  250. package/dist/components/SiteGutterFrame.js +1 -0
  251. package/dist/components/SiteLogoContext.d.ts +25 -0
  252. package/dist/components/SiteLogoContext.js +1 -0
  253. package/dist/components/SitePhotographyContext.d.ts +8 -0
  254. package/dist/components/SitePhotographyContext.js +1 -0
  255. package/dist/components/SiteStyleUnset.d.ts +1 -0
  256. package/dist/components/SiteStyleUnset.js +1 -0
  257. package/dist/components/SiteWidthField.d.ts +7 -0
  258. package/dist/components/SiteWidthField.js +1 -0
  259. package/dist/components/SlotChildActionsContext.d.ts +2 -0
  260. package/dist/components/SlotChildActionsContext.js +1 -0
  261. package/dist/components/SlotChildRows.d.ts +16 -0
  262. package/dist/components/SlotChildRows.js +1 -0
  263. package/dist/components/SocialLinksPanel.d.ts +3 -0
  264. package/dist/components/SocialLinksPanel.js +1 -0
  265. package/dist/components/SourceStatus.d.ts +14 -0
  266. package/dist/components/SourceStatus.js +1 -0
  267. package/dist/components/Spacer.d.ts +6 -0
  268. package/dist/components/Spacer.js +1 -0
  269. package/dist/components/SpecStrip.d.ts +10 -0
  270. package/dist/components/SpecStrip.js +1 -0
  271. package/dist/components/Split.d.ts +33 -0
  272. package/dist/components/Split.js +1 -0
  273. package/dist/components/StandaloneSectionEditor.d.ts +19 -0
  274. package/dist/components/StandaloneSectionEditor.js +1 -0
  275. package/dist/components/Stats.d.ts +14 -0
  276. package/dist/components/Stats.js +1 -0
  277. package/dist/components/StatsParagraph.d.ts +21 -0
  278. package/dist/components/StatsParagraph.js +1 -0
  279. package/dist/components/StorefrontDocumentContext.d.ts +2 -0
  280. package/dist/components/StorefrontDocumentContext.js +1 -0
  281. package/dist/components/StorefrontEditor.d.ts +19 -0
  282. package/dist/components/StorefrontEditor.js +1 -0
  283. package/dist/components/StorefrontEditorChrome.d.ts +1 -0
  284. package/dist/components/StorefrontEditorChrome.js +1 -0
  285. package/dist/components/StorefrontLinkField.d.ts +19 -0
  286. package/dist/components/StorefrontLinkField.js +1 -0
  287. package/dist/components/StylesPanel.d.ts +11 -0
  288. package/dist/components/StylesPanel.js +1 -0
  289. package/dist/components/SwitchToBoxFooter.d.ts +1 -0
  290. package/dist/components/SwitchToBoxFooter.js +1 -0
  291. package/dist/components/TemplateGlyph.d.ts +6 -0
  292. package/dist/components/TemplateGlyph.js +1 -0
  293. package/dist/components/Testimonials.d.ts +16 -0
  294. package/dist/components/Testimonials.js +1 -0
  295. package/dist/components/Text.d.ts +11 -0
  296. package/dist/components/Text.js +1 -0
  297. package/dist/components/TextStyleRow.d.ts +13 -0
  298. package/dist/components/TextStyleRow.js +1 -0
  299. package/dist/components/TextStylesField.d.ts +17 -0
  300. package/dist/components/TextStylesField.js +1 -0
  301. package/dist/components/ThemeCard.d.ts +40 -0
  302. package/dist/components/ThemeCard.js +1 -0
  303. package/dist/components/ThemeColourField.d.ts +5 -0
  304. package/dist/components/ThemeColourField.js +1 -0
  305. package/dist/components/ThemeField.d.ts +8 -0
  306. package/dist/components/ThemeField.js +1 -0
  307. package/dist/components/Ticker.d.ts +18 -0
  308. package/dist/components/Ticker.js +1 -0
  309. package/dist/components/TickerSection.d.ts +18 -0
  310. package/dist/components/TickerSection.js +1 -0
  311. package/dist/components/TickerSettingsPanel.d.ts +3 -0
  312. package/dist/components/TickerSettingsPanel.js +1 -0
  313. package/dist/components/Timeline.d.ts +24 -0
  314. package/dist/components/Timeline.js +1 -0
  315. package/dist/components/TimelineTrack.d.ts +22 -0
  316. package/dist/components/TimelineTrack.js +1 -0
  317. package/dist/components/TitleWithButtons.d.ts +14 -0
  318. package/dist/components/TitleWithButtons.js +1 -0
  319. package/dist/components/UiIcon.d.ts +2 -0
  320. package/dist/components/UiIcon.js +1 -0
  321. package/dist/components/WebsiteScreenParts.d.ts +2 -0
  322. package/dist/components/WebsiteScreenParts.js +1 -0
  323. package/dist/components/WebsiteSettingsPanel.d.ts +46 -0
  324. package/dist/components/WebsiteSettingsPanel.js +1 -0
  325. package/dist/components/WidthFollowField.d.ts +10 -0
  326. package/dist/components/WidthFollowField.js +1 -0
  327. package/dist/components/cartGlyph.d.ts +1 -0
  328. package/dist/components/cartGlyph.js +1 -0
  329. package/dist/components/floating-editor/tokens.d.ts +27 -0
  330. package/dist/components/floating-editor/tokens.js +1 -0
  331. package/dist/components/goldPhrases.d.ts +7 -0
  332. package/dist/components/goldPhrases.js +2 -0
  333. package/dist/components/integrationIcons.d.ts +2 -0
  334. package/dist/components/integrationIcons.js +1 -0
  335. package/dist/components/panel-widths.d.ts +2 -0
  336. package/dist/components/panel-widths.js +1 -0
  337. package/dist/components/preview-devices.d.ts +3 -0
  338. package/dist/components/preview-devices.js +1 -0
  339. package/dist/components/socialIcons.d.ts +8 -0
  340. package/dist/components/socialIcons.js +1 -0
  341. package/dist/components/storefront-editor.css +4870 -0
  342. package/dist/components/useCountUp.d.ts +10 -0
  343. package/dist/components/useCountUp.js +1 -0
  344. package/dist/components/useFooterFocus.d.ts +2 -0
  345. package/dist/components/useFooterFocus.js +1 -0
  346. package/dist/components/useLocationsTree.d.ts +6 -0
  347. package/dist/components/useLocationsTree.js +1 -0
  348. package/dist/components/usePartBridge.d.ts +1 -0
  349. package/dist/components/usePartBridge.js +1 -0
  350. package/dist/components/usePinnedChromeSeed.d.ts +2 -0
  351. package/dist/components/usePinnedChromeSeed.js +1 -0
  352. package/dist/components/usePreviewHref.d.ts +5 -0
  353. package/dist/components/usePreviewHref.js +1 -0
  354. package/dist/components/useReducedMotion.d.ts +4 -0
  355. package/dist/components/useReducedMotion.js +1 -0
  356. package/dist/components/useSnapshotSource.d.ts +17 -0
  357. package/dist/components/useSnapshotSource.js +1 -0
  358. package/dist/components/useSocialSettings.d.ts +14 -0
  359. package/dist/components/useSocialSettings.js +1 -0
  360. package/dist/components/useStorefrontBlogPosts.d.ts +2 -0
  361. package/dist/components/useStorefrontBlogPosts.js +1 -0
  362. package/dist/lib/client-only.d.ts +1 -0
  363. package/dist/lib/client-only.js +1 -0
  364. package/dist/lib/content/authored-props.d.ts +4 -0
  365. package/dist/lib/content/authored-props.js +1 -0
  366. package/dist/lib/content/box-background.d.ts +31 -0
  367. package/dist/lib/content/box-background.js +1 -0
  368. package/dist/lib/content/box-layout.d.ts +78 -0
  369. package/dist/lib/content/box-layout.js +1 -0
  370. package/dist/lib/content/button-arrow.d.ts +2 -0
  371. package/dist/lib/content/button-arrow.js +1 -0
  372. package/dist/lib/content/card-corners.d.ts +12 -0
  373. package/dist/lib/content/card-corners.js +1 -0
  374. package/dist/lib/content/card-image-background.d.ts +11 -0
  375. package/dist/lib/content/card-image-background.js +1 -0
  376. package/dist/lib/content/card-image-shape.d.ts +5 -0
  377. package/dist/lib/content/card-image-shape.js +1 -0
  378. package/dist/lib/content/cart-drawer.d.ts +20 -0
  379. package/dist/lib/content/cart-drawer.js +1 -0
  380. package/dist/lib/content/contact.d.ts +79 -0
  381. package/dist/lib/content/contact.js +3 -0
  382. package/dist/lib/content/container-direction.d.ts +14 -0
  383. package/dist/lib/content/container-direction.js +1 -0
  384. package/dist/lib/content/container-gap.d.ts +31 -0
  385. package/dist/lib/content/container-gap.js +1 -0
  386. package/dist/lib/content/footer.d.ts +30 -0
  387. package/dist/lib/content/footer.js +1 -0
  388. package/dist/lib/content/footprint.d.ts +22 -0
  389. package/dist/lib/content/footprint.js +1 -0
  390. package/dist/lib/content/gallery-card-layout.d.ts +7 -0
  391. package/dist/lib/content/gallery-card-layout.js +1 -0
  392. package/dist/lib/content/gallery-layout.d.ts +154 -0
  393. package/dist/lib/content/gallery-layout.js +1 -0
  394. package/dist/lib/content/grid-span.d.ts +15 -0
  395. package/dist/lib/content/grid-span.js +1 -0
  396. package/dist/lib/content/image-corners.d.ts +7 -0
  397. package/dist/lib/content/image-corners.js +1 -0
  398. package/dist/lib/content/image-fit.d.ts +11 -0
  399. package/dist/lib/content/image-fit.js +1 -0
  400. package/dist/lib/content/image-treatment.d.ts +20 -0
  401. package/dist/lib/content/image-treatment.js +1 -0
  402. package/dist/lib/content/image-width.d.ts +9 -0
  403. package/dist/lib/content/image-width.js +1 -0
  404. package/dist/lib/content/page-background.d.ts +4 -0
  405. package/dist/lib/content/page-background.js +1 -0
  406. package/dist/lib/content/page-width.d.ts +63 -0
  407. package/dist/lib/content/page-width.js +1 -0
  408. package/dist/lib/content/product-page.d.ts +11 -0
  409. package/dist/lib/content/product-page.js +1 -0
  410. package/dist/lib/content/section-animation.d.ts +13 -0
  411. package/dist/lib/content/section-animation.js +1 -0
  412. package/dist/lib/content/section-background.d.ts +7 -0
  413. package/dist/lib/content/section-background.js +1 -0
  414. package/dist/lib/content/section-corners.d.ts +16 -0
  415. package/dist/lib/content/section-corners.js +1 -0
  416. package/dist/lib/content/section-height.d.ts +9 -0
  417. package/dist/lib/content/section-height.js +1 -0
  418. package/dist/lib/content/section-min-height.d.ts +60 -0
  419. package/dist/lib/content/section-min-height.js +1 -0
  420. package/dist/lib/content/section-padding.d.ts +33 -0
  421. package/dist/lib/content/section-padding.js +1 -0
  422. package/dist/lib/content/section-surface.d.ts +13 -0
  423. package/dist/lib/content/section-surface.js +1 -0
  424. package/dist/lib/content/section-type-size.d.ts +7 -0
  425. package/dist/lib/content/section-type-size.js +1 -0
  426. package/dist/lib/content/section-width.d.ts +19 -0
  427. package/dist/lib/content/section-width.js +1 -0
  428. package/dist/lib/content/site-gutter.d.ts +2 -0
  429. package/dist/lib/content/site-gutter.js +1 -0
  430. package/dist/lib/content/site-name.d.ts +2 -0
  431. package/dist/lib/content/site-name.js +1 -0
  432. package/dist/lib/content/social-links.d.ts +12 -0
  433. package/dist/lib/content/social-links.js +1 -0
  434. package/dist/lib/content/subscribe.d.ts +76 -0
  435. package/dist/lib/content/subscribe.js +2 -0
  436. package/dist/lib/content/view-tiers.d.ts +2 -0
  437. package/dist/lib/content/view-tiers.js +1 -0
  438. package/dist/lib/editor/preview-href.d.ts +6 -0
  439. package/dist/lib/editor/preview-href.js +1 -0
  440. package/dist/lib/editor-admin-bridge.d.ts +2 -0
  441. package/dist/lib/editor-admin-bridge.js +1 -0
  442. package/dist/lib/editor-auth.d.ts +3 -0
  443. package/dist/lib/editor-auth.js +1 -0
  444. package/dist/lib/editor-backend-error.d.ts +2 -0
  445. package/dist/lib/editor-backend-error.js +1 -0
  446. package/dist/lib/editor-block-metadata.d.ts +6 -0
  447. package/dist/lib/editor-block-metadata.js +1 -0
  448. package/dist/lib/editor-blocks.d.ts +20 -0
  449. package/dist/lib/editor-blocks.js +1 -0
  450. package/dist/lib/editor-canvas-styles.d.ts +12 -0
  451. package/dist/lib/editor-canvas-styles.js +1 -0
  452. package/dist/lib/editor-chrome.d.ts +2 -0
  453. package/dist/lib/editor-chrome.js +1 -0
  454. package/dist/lib/editor-document-adapter.d.ts +7 -0
  455. package/dist/lib/editor-document-adapter.js +1 -0
  456. package/dist/lib/editor-document-addressing.d.ts +2 -0
  457. package/dist/lib/editor-document-addressing.js +1 -0
  458. package/dist/lib/editor-document-contract.d.ts +1 -0
  459. package/dist/lib/editor-document-contract.js +0 -0
  460. package/dist/lib/editor-document-hooks.d.ts +1 -0
  461. package/dist/lib/editor-document-hooks.js +0 -0
  462. package/dist/lib/editor-document-switch.d.ts +2 -0
  463. package/dist/lib/editor-document-switch.js +1 -0
  464. package/dist/lib/editor-events.d.ts +8 -0
  465. package/dist/lib/editor-events.js +1 -0
  466. package/dist/lib/editor-failure.d.ts +1 -0
  467. package/dist/lib/editor-failure.js +1 -0
  468. package/dist/lib/editor-form-window.d.ts +21 -0
  469. package/dist/lib/editor-form-window.js +1 -0
  470. package/dist/lib/editor-host-inbound.d.ts +1 -0
  471. package/dist/lib/editor-host-inbound.js +1 -0
  472. package/dist/lib/editor-host.d.ts +2 -0
  473. package/dist/lib/editor-host.js +1 -0
  474. package/dist/lib/editor-key-scope.d.ts +1 -0
  475. package/dist/lib/editor-key-scope.js +1 -0
  476. package/dist/lib/editor-load-error.d.ts +1 -0
  477. package/dist/lib/editor-load-error.js +1 -0
  478. package/dist/lib/editor-media-panel.d.ts +2 -0
  479. package/dist/lib/editor-media-panel.js +1 -0
  480. package/dist/lib/editor-media-picker.d.ts +2 -0
  481. package/dist/lib/editor-media-picker.js +1 -0
  482. package/dist/lib/editor-page-lock.d.ts +1 -0
  483. package/dist/lib/editor-page-lock.js +1 -0
  484. package/dist/lib/editor-page-rows.d.ts +16 -0
  485. package/dist/lib/editor-page-rows.js +1 -0
  486. package/dist/lib/editor-screen.d.ts +2 -0
  487. package/dist/lib/editor-screen.js +1 -0
  488. package/dist/lib/editor-section-templates.d.ts +1 -0
  489. package/dist/lib/editor-section-templates.js +1 -0
  490. package/dist/lib/editor-session-context.d.ts +1 -0
  491. package/dist/lib/editor-session-context.js +1 -0
  492. package/dist/lib/editor-session-loader.d.ts +4 -0
  493. package/dist/lib/editor-session-loader.js +1 -0
  494. package/dist/lib/editor-session.d.ts +2 -0
  495. package/dist/lib/editor-session.js +1 -0
  496. package/dist/lib/editor-site-slug.d.ts +2 -0
  497. package/dist/lib/editor-site-slug.js +1 -0
  498. package/dist/lib/editor-site.d.ts +3 -0
  499. package/dist/lib/editor-site.js +1 -0
  500. package/dist/lib/editor-theme.d.ts +1 -0
  501. package/dist/lib/editor-theme.js +1 -0
  502. package/dist/lib/fit-words.d.ts +3 -0
  503. package/dist/lib/fit-words.js +220 -0
  504. package/dist/lib/fonts.d.ts +32 -0
  505. package/dist/lib/fonts.js +1 -0
  506. package/dist/lib/geocode.d.ts +6 -0
  507. package/dist/lib/geocode.js +1 -0
  508. package/dist/lib/hero-arrows.d.ts +23 -0
  509. package/dist/lib/hero-arrows.js +1 -0
  510. package/dist/lib/hero-group-placement.d.ts +46 -0
  511. package/dist/lib/hero-group-placement.js +1 -0
  512. package/dist/lib/hero-overlay-anchor.d.ts +6 -0
  513. package/dist/lib/hero-overlay-anchor.js +1 -0
  514. package/dist/lib/hero-phone-stack.d.ts +27 -0
  515. package/dist/lib/hero-phone-stack.js +1 -0
  516. package/dist/lib/hero-swipe.d.ts +16 -0
  517. package/dist/lib/hero-swipe.js +1 -0
  518. package/dist/lib/hero-texture.d.ts +4 -0
  519. package/dist/lib/hero-texture.js +1 -0
  520. package/dist/lib/hero-video-control.d.ts +13 -0
  521. package/dist/lib/hero-video-control.js +1 -0
  522. package/dist/lib/image-focus.d.ts +16 -0
  523. package/dist/lib/image-focus.js +1 -0
  524. package/dist/lib/links/link-filters.d.ts +1 -0
  525. package/dist/lib/links/link-filters.js +0 -0
  526. package/dist/lib/links/link-picker-setup.d.ts +29 -0
  527. package/dist/lib/links/link-picker-setup.js +1 -0
  528. package/dist/lib/links/page-link-rules.d.ts +22 -0
  529. package/dist/lib/links/page-link-rules.js +1 -0
  530. package/dist/lib/locations-grid.d.ts +10 -0
  531. package/dist/lib/locations-grid.js +1 -0
  532. package/dist/lib/logo/analyze-logo-client.d.ts +3 -0
  533. package/dist/lib/logo/analyze-logo-client.js +1 -0
  534. package/dist/lib/logo/reverse-svg.d.ts +9 -0
  535. package/dist/lib/logo/reverse-svg.js +1 -0
  536. package/dist/lib/map-styles.d.ts +30 -0
  537. package/dist/lib/map-styles.js +1 -0
  538. package/dist/lib/maps-config.d.ts +16 -0
  539. package/dist/lib/maps-config.js +1 -0
  540. package/dist/lib/maps-loader.d.ts +3 -0
  541. package/dist/lib/maps-loader.js +1 -0
  542. package/dist/lib/markup.d.ts +38 -0
  543. package/dist/lib/markup.js +2 -0
  544. package/dist/lib/media.d.ts +10 -0
  545. package/dist/lib/media.js +1 -0
  546. package/dist/lib/parse-option.d.ts +3 -0
  547. package/dist/lib/parse-option.js +1 -0
  548. package/dist/lib/popup/text-kinds.d.ts +11 -0
  549. package/dist/lib/popup/text-kinds.js +1 -0
  550. package/dist/lib/preview-header-settings.d.ts +39 -0
  551. package/dist/lib/preview-header-settings.js +0 -0
  552. package/dist/lib/puck/ColourControl.d.ts +11 -0
  553. package/dist/lib/puck/ColourControl.js +1 -0
  554. package/dist/lib/puck/MarkupToolbar.d.ts +13 -0
  555. package/dist/lib/puck/MarkupToolbar.js +1 -0
  556. package/dist/lib/puck/add-palette.d.ts +14 -0
  557. package/dist/lib/puck/add-palette.js +1 -0
  558. package/dist/lib/puck/add-target-types.d.ts +1 -0
  559. package/dist/lib/puck/add-target-types.js +0 -0
  560. package/dist/lib/puck/add-target.d.ts +3 -0
  561. package/dist/lib/puck/add-target.js +1 -0
  562. package/dist/lib/puck/apply-card-corners.d.ts +21 -0
  563. package/dist/lib/puck/apply-card-corners.js +1 -0
  564. package/dist/lib/puck/block-choice.d.ts +2 -0
  565. package/dist/lib/puck/block-choice.js +1 -0
  566. package/dist/lib/puck/block-offer.d.ts +10 -0
  567. package/dist/lib/puck/block-offer.js +1 -0
  568. package/dist/lib/puck/block-types-in.d.ts +1 -0
  569. package/dist/lib/puck/block-types-in.js +1 -0
  570. package/dist/lib/puck/blocks/block-types.d.ts +12 -0
  571. package/dist/lib/puck/blocks/block-types.js +1 -0
  572. package/dist/lib/puck/blocks/general/Banner/fields.d.ts +2 -0
  573. package/dist/lib/puck/blocks/general/Banner/fields.js +1 -0
  574. package/dist/lib/puck/blocks/general/Banner/render.d.ts +2 -0
  575. package/dist/lib/puck/blocks/general/Banner/render.js +1 -0
  576. package/dist/lib/puck/blocks/general/Breadcrumb/fields.d.ts +2 -0
  577. package/dist/lib/puck/blocks/general/Breadcrumb/fields.js +1 -0
  578. package/dist/lib/puck/blocks/general/Breadcrumb/render.d.ts +5 -0
  579. package/dist/lib/puck/blocks/general/Breadcrumb/render.js +1 -0
  580. package/dist/lib/puck/blocks/general/ButtonBlock/fields.d.ts +2 -0
  581. package/dist/lib/puck/blocks/general/ButtonBlock/fields.js +1 -0
  582. package/dist/lib/puck/blocks/general/ButtonBlock/render.d.ts +2 -0
  583. package/dist/lib/puck/blocks/general/ButtonBlock/render.js +1 -0
  584. package/dist/lib/puck/blocks/general/Cards/fields.d.ts +2 -0
  585. package/dist/lib/puck/blocks/general/Cards/fields.js +1 -0
  586. package/dist/lib/puck/blocks/general/Cards/render.d.ts +6 -0
  587. package/dist/lib/puck/blocks/general/Cards/render.js +1 -0
  588. package/dist/lib/puck/blocks/general/CategoryBar/fields.d.ts +2 -0
  589. package/dist/lib/puck/blocks/general/CategoryBar/fields.js +1 -0
  590. package/dist/lib/puck/blocks/general/CategoryBar/render.d.ts +2 -0
  591. package/dist/lib/puck/blocks/general/CategoryBar/render.js +1 -0
  592. package/dist/lib/puck/blocks/general/Chart/fields.d.ts +2 -0
  593. package/dist/lib/puck/blocks/general/Chart/fields.js +1 -0
  594. package/dist/lib/puck/blocks/general/Chart/render.d.ts +6 -0
  595. package/dist/lib/puck/blocks/general/Chart/render.js +2 -0
  596. package/dist/lib/puck/blocks/general/ContactLocations/fields.d.ts +2 -0
  597. package/dist/lib/puck/blocks/general/ContactLocations/fields.js +1 -0
  598. package/dist/lib/puck/blocks/general/ContactLocations/render.d.ts +2 -0
  599. package/dist/lib/puck/blocks/general/ContactLocations/render.js +1 -0
  600. package/dist/lib/puck/blocks/general/CustomHTML/fields.d.ts +2 -0
  601. package/dist/lib/puck/blocks/general/CustomHTML/fields.js +1 -0
  602. package/dist/lib/puck/blocks/general/CustomHTML/render.d.ts +2 -0
  603. package/dist/lib/puck/blocks/general/CustomHTML/render.js +3 -0
  604. package/dist/lib/puck/blocks/general/Divider/fields.d.ts +2 -0
  605. package/dist/lib/puck/blocks/general/Divider/fields.js +1 -0
  606. package/dist/lib/puck/blocks/general/Divider/render.d.ts +2 -0
  607. package/dist/lib/puck/blocks/general/Divider/render.js +1 -0
  608. package/dist/lib/puck/blocks/general/DocBody/fields.d.ts +2 -0
  609. package/dist/lib/puck/blocks/general/DocBody/fields.js +1 -0
  610. package/dist/lib/puck/blocks/general/DocBody/render.d.ts +5 -0
  611. package/dist/lib/puck/blocks/general/DocBody/render.js +1 -0
  612. package/dist/lib/puck/blocks/general/DocMasthead/fields.d.ts +2 -0
  613. package/dist/lib/puck/blocks/general/DocMasthead/fields.js +1 -0
  614. package/dist/lib/puck/blocks/general/DocMasthead/render.d.ts +7 -0
  615. package/dist/lib/puck/blocks/general/DocMasthead/render.js +1 -0
  616. package/dist/lib/puck/blocks/general/DocMetaCards/fields.d.ts +2 -0
  617. package/dist/lib/puck/blocks/general/DocMetaCards/fields.js +1 -0
  618. package/dist/lib/puck/blocks/general/DocMetaCards/render.d.ts +4 -0
  619. package/dist/lib/puck/blocks/general/DocMetaCards/render.js +1 -0
  620. package/dist/lib/puck/blocks/general/DocTabs/fields.d.ts +2 -0
  621. package/dist/lib/puck/blocks/general/DocTabs/fields.js +1 -0
  622. package/dist/lib/puck/blocks/general/DocTabs/render.d.ts +6 -0
  623. package/dist/lib/puck/blocks/general/DocTabs/render.js +1 -0
  624. package/dist/lib/puck/blocks/general/Eyebrow/fields.d.ts +2 -0
  625. package/dist/lib/puck/blocks/general/Eyebrow/fields.js +1 -0
  626. package/dist/lib/puck/blocks/general/Eyebrow/render.d.ts +2 -0
  627. package/dist/lib/puck/blocks/general/Eyebrow/render.js +1 -0
  628. package/dist/lib/puck/blocks/general/FAQ/fields.d.ts +2 -0
  629. package/dist/lib/puck/blocks/general/FAQ/fields.js +1 -0
  630. package/dist/lib/puck/blocks/general/FAQ/render.d.ts +7 -0
  631. package/dist/lib/puck/blocks/general/FAQ/render.js +1 -0
  632. package/dist/lib/puck/blocks/general/FeaturedCover/fields.d.ts +2 -0
  633. package/dist/lib/puck/blocks/general/FeaturedCover/fields.js +1 -0
  634. package/dist/lib/puck/blocks/general/FeaturedCover/render.d.ts +13 -0
  635. package/dist/lib/puck/blocks/general/FeaturedCover/render.js +1 -0
  636. package/dist/lib/puck/blocks/general/Flex/fields.d.ts +2 -0
  637. package/dist/lib/puck/blocks/general/Flex/fields.js +1 -0
  638. package/dist/lib/puck/blocks/general/Flex/render.d.ts +7 -0
  639. package/dist/lib/puck/blocks/general/Flex/render.js +1 -0
  640. package/dist/lib/puck/blocks/general/Footprint/fields.d.ts +2 -0
  641. package/dist/lib/puck/blocks/general/Footprint/fields.js +1 -0
  642. package/dist/lib/puck/blocks/general/Footprint/render.d.ts +7 -0
  643. package/dist/lib/puck/blocks/general/Footprint/render.js +2 -0
  644. package/dist/lib/puck/blocks/general/Grid/fields.d.ts +2 -0
  645. package/dist/lib/puck/blocks/general/Grid/fields.js +1 -0
  646. package/dist/lib/puck/blocks/general/Grid/render.d.ts +7 -0
  647. package/dist/lib/puck/blocks/general/Grid/render.js +1 -0
  648. package/dist/lib/puck/blocks/general/Heading/fields.d.ts +2 -0
  649. package/dist/lib/puck/blocks/general/Heading/fields.js +1 -0
  650. package/dist/lib/puck/blocks/general/Heading/render.d.ts +2 -0
  651. package/dist/lib/puck/blocks/general/Heading/render.js +1 -0
  652. package/dist/lib/puck/blocks/general/Hero/fields.d.ts +2 -0
  653. package/dist/lib/puck/blocks/general/Hero/fields.js +1 -0
  654. package/dist/lib/puck/blocks/general/Hero/render.d.ts +7 -0
  655. package/dist/lib/puck/blocks/general/Hero/render.js +1 -0
  656. package/dist/lib/puck/blocks/general/HeroGroup/fields.d.ts +2 -0
  657. package/dist/lib/puck/blocks/general/HeroGroup/fields.js +1 -0
  658. package/dist/lib/puck/blocks/general/HeroGroup/render.d.ts +8 -0
  659. package/dist/lib/puck/blocks/general/HeroGroup/render.js +1 -0
  660. package/dist/lib/puck/blocks/general/HeroSingle/fields.d.ts +2 -0
  661. package/dist/lib/puck/blocks/general/HeroSingle/fields.js +1 -0
  662. package/dist/lib/puck/blocks/general/HeroSingle/render.d.ts +8 -0
  663. package/dist/lib/puck/blocks/general/HeroSingle/render.js +1 -0
  664. package/dist/lib/puck/blocks/general/HeroSlide/fields.d.ts +2 -0
  665. package/dist/lib/puck/blocks/general/HeroSlide/fields.js +1 -0
  666. package/dist/lib/puck/blocks/general/HeroSlide/render.d.ts +8 -0
  667. package/dist/lib/puck/blocks/general/HeroSlide/render.js +1 -0
  668. package/dist/lib/puck/blocks/general/ImageBlock/fields.d.ts +2 -0
  669. package/dist/lib/puck/blocks/general/ImageBlock/fields.js +1 -0
  670. package/dist/lib/puck/blocks/general/ImageBlock/render.d.ts +2 -0
  671. package/dist/lib/puck/blocks/general/ImageBlock/render.js +1 -0
  672. package/dist/lib/puck/blocks/general/Integrations/fields.d.ts +2 -0
  673. package/dist/lib/puck/blocks/general/Integrations/fields.js +2 -0
  674. package/dist/lib/puck/blocks/general/Integrations/render.d.ts +2 -0
  675. package/dist/lib/puck/blocks/general/Integrations/render.js +2 -0
  676. package/dist/lib/puck/blocks/general/IntroBleed/fields.d.ts +2 -0
  677. package/dist/lib/puck/blocks/general/IntroBleed/fields.js +1 -0
  678. package/dist/lib/puck/blocks/general/IntroBleed/render.d.ts +7 -0
  679. package/dist/lib/puck/blocks/general/IntroBleed/render.js +2 -0
  680. package/dist/lib/puck/blocks/general/KeyFeatures/fields.d.ts +2 -0
  681. package/dist/lib/puck/blocks/general/KeyFeatures/fields.js +1 -0
  682. package/dist/lib/puck/blocks/general/KeyFeatures/render.d.ts +6 -0
  683. package/dist/lib/puck/blocks/general/KeyFeatures/render.js +1 -0
  684. package/dist/lib/puck/blocks/general/LogoBlock/fields.d.ts +2 -0
  685. package/dist/lib/puck/blocks/general/LogoBlock/fields.js +1 -0
  686. package/dist/lib/puck/blocks/general/LogoBlock/render.d.ts +2 -0
  687. package/dist/lib/puck/blocks/general/LogoBlock/render.js +1 -0
  688. package/dist/lib/puck/blocks/general/Logos/fields.d.ts +2 -0
  689. package/dist/lib/puck/blocks/general/Logos/fields.js +1 -0
  690. package/dist/lib/puck/blocks/general/Logos/render.d.ts +2 -0
  691. package/dist/lib/puck/blocks/general/Logos/render.js +1 -0
  692. package/dist/lib/puck/blocks/general/LongRead/fields.d.ts +2 -0
  693. package/dist/lib/puck/blocks/general/LongRead/fields.js +1 -0
  694. package/dist/lib/puck/blocks/general/LongRead/render.d.ts +19 -0
  695. package/dist/lib/puck/blocks/general/LongRead/render.js +1 -0
  696. package/dist/lib/puck/blocks/general/Manifesto/fields.d.ts +2 -0
  697. package/dist/lib/puck/blocks/general/Manifesto/fields.js +2 -0
  698. package/dist/lib/puck/blocks/general/Manifesto/render.d.ts +2 -0
  699. package/dist/lib/puck/blocks/general/Manifesto/render.js +1 -0
  700. package/dist/lib/puck/blocks/general/Masthead/fields.d.ts +2 -0
  701. package/dist/lib/puck/blocks/general/Masthead/fields.js +1 -0
  702. package/dist/lib/puck/blocks/general/Masthead/render.d.ts +8 -0
  703. package/dist/lib/puck/blocks/general/Masthead/render.js +1 -0
  704. package/dist/lib/puck/blocks/general/Mockup/fields.d.ts +2 -0
  705. package/dist/lib/puck/blocks/general/Mockup/fields.js +2 -0
  706. package/dist/lib/puck/blocks/general/Mockup/render.d.ts +2 -0
  707. package/dist/lib/puck/blocks/general/Mockup/render.js +2 -0
  708. package/dist/lib/puck/blocks/general/OrderSummary/fields.d.ts +2 -0
  709. package/dist/lib/puck/blocks/general/OrderSummary/fields.js +1 -0
  710. package/dist/lib/puck/blocks/general/OrderSummary/render.d.ts +6 -0
  711. package/dist/lib/puck/blocks/general/OrderSummary/render.js +1 -0
  712. package/dist/lib/puck/blocks/general/ParagraphsA/fields.d.ts +2 -0
  713. package/dist/lib/puck/blocks/general/ParagraphsA/fields.js +1 -0
  714. package/dist/lib/puck/blocks/general/ParagraphsA/render.d.ts +9 -0
  715. package/dist/lib/puck/blocks/general/ParagraphsA/render.js +1 -0
  716. package/dist/lib/puck/blocks/general/PaymentBadges/fields.d.ts +2 -0
  717. package/dist/lib/puck/blocks/general/PaymentBadges/fields.js +1 -0
  718. package/dist/lib/puck/blocks/general/PaymentBadges/render.d.ts +5 -0
  719. package/dist/lib/puck/blocks/general/PaymentBadges/render.js +1 -0
  720. package/dist/lib/puck/blocks/general/PostCard/fields.d.ts +2 -0
  721. package/dist/lib/puck/blocks/general/PostCard/fields.js +1 -0
  722. package/dist/lib/puck/blocks/general/PostCard/render.d.ts +9 -0
  723. package/dist/lib/puck/blocks/general/PostCard/render.js +1 -0
  724. package/dist/lib/puck/blocks/general/ProcessSteps/fields.d.ts +2 -0
  725. package/dist/lib/puck/blocks/general/ProcessSteps/fields.js +1 -0
  726. package/dist/lib/puck/blocks/general/ProcessSteps/render.d.ts +13 -0
  727. package/dist/lib/puck/blocks/general/ProcessSteps/render.js +1 -0
  728. package/dist/lib/puck/blocks/general/Prose/fields.d.ts +2 -0
  729. package/dist/lib/puck/blocks/general/Prose/fields.js +1 -0
  730. package/dist/lib/puck/blocks/general/Prose/render.d.ts +7 -0
  731. package/dist/lib/puck/blocks/general/Prose/render.js +1 -0
  732. package/dist/lib/puck/blocks/general/SectionHeader/fields.d.ts +2 -0
  733. package/dist/lib/puck/blocks/general/SectionHeader/fields.js +1 -0
  734. package/dist/lib/puck/blocks/general/SectionHeader/render.d.ts +2 -0
  735. package/dist/lib/puck/blocks/general/SectionHeader/render.js +1 -0
  736. package/dist/lib/puck/blocks/general/Series/fields.d.ts +2 -0
  737. package/dist/lib/puck/blocks/general/Series/fields.js +1 -0
  738. package/dist/lib/puck/blocks/general/Series/render.d.ts +8 -0
  739. package/dist/lib/puck/blocks/general/Series/render.js +1 -0
  740. package/dist/lib/puck/blocks/general/Spacer/fields.d.ts +2 -0
  741. package/dist/lib/puck/blocks/general/Spacer/fields.js +1 -0
  742. package/dist/lib/puck/blocks/general/Spacer/render.d.ts +2 -0
  743. package/dist/lib/puck/blocks/general/Spacer/render.js +1 -0
  744. package/dist/lib/puck/blocks/general/SpecStrip/fields.d.ts +2 -0
  745. package/dist/lib/puck/blocks/general/SpecStrip/fields.js +1 -0
  746. package/dist/lib/puck/blocks/general/SpecStrip/render.d.ts +2 -0
  747. package/dist/lib/puck/blocks/general/SpecStrip/render.js +1 -0
  748. package/dist/lib/puck/blocks/general/Split/fields.d.ts +2 -0
  749. package/dist/lib/puck/blocks/general/Split/fields.js +1 -0
  750. package/dist/lib/puck/blocks/general/Split/render.d.ts +10 -0
  751. package/dist/lib/puck/blocks/general/Split/render.js +2 -0
  752. package/dist/lib/puck/blocks/general/Stats/fields.d.ts +2 -0
  753. package/dist/lib/puck/blocks/general/Stats/fields.js +1 -0
  754. package/dist/lib/puck/blocks/general/Stats/render.d.ts +2 -0
  755. package/dist/lib/puck/blocks/general/Stats/render.js +1 -0
  756. package/dist/lib/puck/blocks/general/StatsParagraph/fields.d.ts +2 -0
  757. package/dist/lib/puck/blocks/general/StatsParagraph/fields.js +1 -0
  758. package/dist/lib/puck/blocks/general/StatsParagraph/render.d.ts +7 -0
  759. package/dist/lib/puck/blocks/general/StatsParagraph/render.js +1 -0
  760. package/dist/lib/puck/blocks/general/Testimonials/fields.d.ts +2 -0
  761. package/dist/lib/puck/blocks/general/Testimonials/fields.js +1 -0
  762. package/dist/lib/puck/blocks/general/Testimonials/render.d.ts +6 -0
  763. package/dist/lib/puck/blocks/general/Testimonials/render.js +1 -0
  764. package/dist/lib/puck/blocks/general/Text/fields.d.ts +2 -0
  765. package/dist/lib/puck/blocks/general/Text/fields.js +1 -0
  766. package/dist/lib/puck/blocks/general/Text/render.d.ts +2 -0
  767. package/dist/lib/puck/blocks/general/Text/render.js +1 -0
  768. package/dist/lib/puck/blocks/general/Ticker/fields.d.ts +2 -0
  769. package/dist/lib/puck/blocks/general/Ticker/fields.js +1 -0
  770. package/dist/lib/puck/blocks/general/Ticker/render.d.ts +5 -0
  771. package/dist/lib/puck/blocks/general/Ticker/render.js +1 -0
  772. package/dist/lib/puck/blocks/general/Timeline/fields.d.ts +2 -0
  773. package/dist/lib/puck/blocks/general/Timeline/fields.js +1 -0
  774. package/dist/lib/puck/blocks/general/Timeline/render.d.ts +10 -0
  775. package/dist/lib/puck/blocks/general/Timeline/render.js +2 -0
  776. package/dist/lib/puck/blocks/general/TimelineTrack/fields.d.ts +2 -0
  777. package/dist/lib/puck/blocks/general/TimelineTrack/fields.js +1 -0
  778. package/dist/lib/puck/blocks/general/TimelineTrack/render.d.ts +8 -0
  779. package/dist/lib/puck/blocks/general/TimelineTrack/render.js +1 -0
  780. package/dist/lib/puck/blocks/general/TitleWithButtons/fields.d.ts +2 -0
  781. package/dist/lib/puck/blocks/general/TitleWithButtons/fields.js +1 -0
  782. package/dist/lib/puck/blocks/general/TitleWithButtons/render.d.ts +5 -0
  783. package/dist/lib/puck/blocks/general/TitleWithButtons/render.js +2 -0
  784. package/dist/lib/puck/blocks/general/index.d.ts +2 -0
  785. package/dist/lib/puck/blocks/general/index.js +1 -0
  786. package/dist/lib/puck/blocks/general/renders.d.ts +57 -0
  787. package/dist/lib/puck/blocks/general/renders.js +1 -0
  788. package/dist/lib/puck/blocks/general/section-bg-built-in.d.ts +6 -0
  789. package/dist/lib/puck/blocks/general/section-bg-built-in.js +1 -0
  790. package/dist/lib/puck/blocks/placeholders.d.ts +25 -0
  791. package/dist/lib/puck/blocks/placeholders.js +1 -0
  792. package/dist/lib/puck/blocks/scaffold.d.ts +22 -0
  793. package/dist/lib/puck/blocks/scaffold.js +1 -0
  794. package/dist/lib/puck/blocks/shared-fields.d.ts +14 -0
  795. package/dist/lib/puck/blocks/shared-fields.js +1 -0
  796. package/dist/lib/puck/booking-preview.d.ts +3 -0
  797. package/dist/lib/puck/booking-preview.js +1 -0
  798. package/dist/lib/puck/box-footer.d.ts +9 -0
  799. package/dist/lib/puck/box-footer.js +1 -0
  800. package/dist/lib/puck/build-config.d.ts +3 -0
  801. package/dist/lib/puck/build-config.js +1 -0
  802. package/dist/lib/puck/cap-hero-groups.d.ts +1 -0
  803. package/dist/lib/puck/cap-hero-groups.js +1 -0
  804. package/dist/lib/puck/container-blocks.d.ts +10 -0
  805. package/dist/lib/puck/container-blocks.js +1 -0
  806. package/dist/lib/puck/content-tree.d.ts +2 -0
  807. package/dist/lib/puck/content-tree.js +1 -0
  808. package/dist/lib/puck/controls.d.ts +335 -0
  809. package/dist/lib/puck/controls.js +1 -0
  810. package/dist/lib/puck/device-fields.d.ts +2 -0
  811. package/dist/lib/puck/device-fields.js +1 -0
  812. package/dist/lib/puck/editor-page-context.d.ts +6 -0
  813. package/dist/lib/puck/editor-page-context.js +1 -0
  814. package/dist/lib/puck/enclosing-section.d.ts +1 -0
  815. package/dist/lib/puck/enclosing-section.js +1 -0
  816. package/dist/lib/puck/field-buckets.d.ts +2 -0
  817. package/dist/lib/puck/field-buckets.js +1 -0
  818. package/dist/lib/puck/field-groups.d.ts +23 -0
  819. package/dist/lib/puck/field-groups.js +1 -0
  820. package/dist/lib/puck/field-markers.d.ts +2 -0
  821. package/dist/lib/puck/field-markers.js +1 -0
  822. package/dist/lib/puck/field-noun.d.ts +3 -0
  823. package/dist/lib/puck/field-noun.js +1 -0
  824. package/dist/lib/puck/footer-block-row-name.d.ts +4 -0
  825. package/dist/lib/puck/footer-block-row-name.js +1 -0
  826. package/dist/lib/puck/footer-blocks.d.ts +11 -0
  827. package/dist/lib/puck/footer-blocks.js +1 -0
  828. package/dist/lib/puck/footer-return.d.ts +3 -0
  829. package/dist/lib/puck/footer-return.js +1 -0
  830. package/dist/lib/puck/general-registry.d.ts +4 -0
  831. package/dist/lib/puck/general-registry.js +1 -0
  832. package/dist/lib/puck/geocode-resolve.d.ts +14 -0
  833. package/dist/lib/puck/geocode-resolve.js +1 -0
  834. package/dist/lib/puck/header-clearance.d.ts +49 -0
  835. package/dist/lib/puck/header-clearance.js +1 -0
  836. package/dist/lib/puck/header-ink.d.ts +16 -0
  837. package/dist/lib/puck/header-ink.js +1 -0
  838. package/dist/lib/puck/heading-outline.d.ts +7 -0
  839. package/dist/lib/puck/heading-outline.js +1 -0
  840. package/dist/lib/puck/hidable.d.ts +2 -0
  841. package/dist/lib/puck/hidable.js +1 -0
  842. package/dist/lib/puck/home-page.d.ts +1 -0
  843. package/dist/lib/puck/home-page.js +1 -0
  844. package/dist/lib/puck/left-view.d.ts +5 -0
  845. package/dist/lib/puck/left-view.js +1 -0
  846. package/dist/lib/puck/locations-focus.d.ts +22 -0
  847. package/dist/lib/puck/locations-focus.js +1 -0
  848. package/dist/lib/puck/locations-tree.d.ts +23 -0
  849. package/dist/lib/puck/locations-tree.js +2 -0
  850. package/dist/lib/puck/locked-blocks.d.ts +2 -0
  851. package/dist/lib/puck/locked-blocks.js +1 -0
  852. package/dist/lib/puck/media-manifest.json +138 -0
  853. package/dist/lib/puck/migrate-legacy.d.ts +2 -0
  854. package/dist/lib/puck/migrate-legacy.js +1 -0
  855. package/dist/lib/puck/mockup-variant-fields.d.ts +10 -0
  856. package/dist/lib/puck/mockup-variant-fields.js +1 -0
  857. package/dist/lib/puck/neutralization-contracts.d.ts +43 -0
  858. package/dist/lib/puck/neutralization-contracts.js +1 -0
  859. package/dist/lib/puck/one-h1.d.ts +3 -0
  860. package/dist/lib/puck/one-h1.js +1 -0
  861. package/dist/lib/puck/overlay-portal.d.ts +2 -0
  862. package/dist/lib/puck/overlay-portal.js +1 -0
  863. package/dist/lib/puck/overrides.d.ts +2 -0
  864. package/dist/lib/puck/overrides.js +1 -0
  865. package/dist/lib/puck/page-render.d.ts +5 -0
  866. package/dist/lib/puck/page-render.js +1 -0
  867. package/dist/lib/puck/page-root-fields.d.ts +29 -0
  868. package/dist/lib/puck/page-root-fields.js +1 -0
  869. package/dist/lib/puck/page-root.d.ts +55 -0
  870. package/dist/lib/puck/page-root.js +1 -0
  871. package/dist/lib/puck/palette-sections.d.ts +2 -0
  872. package/dist/lib/puck/palette-sections.js +1 -0
  873. package/dist/lib/puck/panel-drop-empty-headings.d.ts +3 -0
  874. package/dist/lib/puck/panel-drop-empty-headings.js +1 -0
  875. package/dist/lib/puck/panel-fields.d.ts +2 -0
  876. package/dist/lib/puck/panel-fields.js +1 -0
  877. package/dist/lib/puck/panel-heading-scope.d.ts +8 -0
  878. package/dist/lib/puck/panel-heading-scope.js +1 -0
  879. package/dist/lib/puck/panel-no-control-mark.d.ts +2 -0
  880. package/dist/lib/puck/panel-no-control-mark.js +1 -0
  881. package/dist/lib/puck/part-anchors.d.ts +23 -0
  882. package/dist/lib/puck/part-anchors.js +1 -0
  883. package/dist/lib/puck/part-id.d.ts +1 -0
  884. package/dist/lib/puck/part-id.js +1 -0
  885. package/dist/lib/puck/part-inks.d.ts +9 -0
  886. package/dist/lib/puck/part-inks.js +1 -0
  887. package/dist/lib/puck/part-key.d.ts +9 -0
  888. package/dist/lib/puck/part-key.js +1 -0
  889. package/dist/lib/puck/part-row-field.d.ts +2 -0
  890. package/dist/lib/puck/part-row-field.js +1 -0
  891. package/dist/lib/puck/part-rows.d.ts +15 -0
  892. package/dist/lib/puck/part-rows.js +1 -0
  893. package/dist/lib/puck/part-styles.d.ts +133 -0
  894. package/dist/lib/puck/part-styles.js +2 -0
  895. package/dist/lib/puck/part-text.d.ts +12 -0
  896. package/dist/lib/puck/part-text.js +1 -0
  897. package/dist/lib/puck/part-typing.d.ts +11 -0
  898. package/dist/lib/puck/part-typing.js +1 -0
  899. package/dist/lib/puck/passes/catalogue-labels.d.ts +2 -0
  900. package/dist/lib/puck/passes/catalogue-labels.js +1 -0
  901. package/dist/lib/puck/passes/cell-width.d.ts +2 -0
  902. package/dist/lib/puck/passes/cell-width.js +1 -0
  903. package/dist/lib/puck/passes/clickable-parts.d.ts +2 -0
  904. package/dist/lib/puck/passes/clickable-parts.js +1 -0
  905. package/dist/lib/puck/passes/corners.d.ts +3 -0
  906. package/dist/lib/puck/passes/corners.js +1 -0
  907. package/dist/lib/puck/passes/document-position.d.ts +16 -0
  908. package/dist/lib/puck/passes/document-position.js +1 -0
  909. package/dist/lib/puck/passes/empty-headings.d.ts +2 -0
  910. package/dist/lib/puck/passes/empty-headings.js +1 -0
  911. package/dist/lib/puck/passes/hidden-nodes.d.ts +2 -0
  912. package/dist/lib/puck/passes/hidden-nodes.js +1 -0
  913. package/dist/lib/puck/passes/index.d.ts +5 -0
  914. package/dist/lib/puck/passes/index.js +1 -0
  915. package/dist/lib/puck/passes/inspector.d.ts +2 -0
  916. package/dist/lib/puck/passes/inspector.js +1 -0
  917. package/dist/lib/puck/passes/registry-context.d.ts +2 -0
  918. package/dist/lib/puck/passes/registry-context.js +1 -0
  919. package/dist/lib/puck/passes/section-anchor.d.ts +2 -0
  920. package/dist/lib/puck/passes/section-anchor.js +1 -0
  921. package/dist/lib/puck/passes/section-animation.d.ts +2 -0
  922. package/dist/lib/puck/passes/section-animation.js +1 -0
  923. package/dist/lib/puck/passes/section-height.d.ts +2 -0
  924. package/dist/lib/puck/passes/section-height.js +1 -0
  925. package/dist/lib/puck/passes/section-padding.d.ts +8 -0
  926. package/dist/lib/puck/passes/section-padding.js +1 -0
  927. package/dist/lib/puck/passes/section-theme.d.ts +3 -0
  928. package/dist/lib/puck/passes/section-theme.js +1 -0
  929. package/dist/lib/puck/passes/section-width.d.ts +10 -0
  930. package/dist/lib/puck/passes/section-width.js +1 -0
  931. package/dist/lib/puck/passes/settings-groups.d.ts +3 -0
  932. package/dist/lib/puck/passes/settings-groups.js +1 -0
  933. package/dist/lib/puck/passes/slot-rules.d.ts +4 -0
  934. package/dist/lib/puck/passes/slot-rules.js +1 -0
  935. package/dist/lib/puck/passes/types.d.ts +51 -0
  936. package/dist/lib/puck/passes/types.js +0 -0
  937. package/dist/lib/puck/phone-override.d.ts +4 -0
  938. package/dist/lib/puck/phone-override.js +1 -0
  939. package/dist/lib/puck/pinned-seed-gate.d.ts +7 -0
  940. package/dist/lib/puck/pinned-seed-gate.js +1 -0
  941. package/dist/lib/puck/preview-click.d.ts +1 -0
  942. package/dist/lib/puck/preview-click.js +1 -0
  943. package/dist/lib/puck/preview-dom.d.ts +1 -0
  944. package/dist/lib/puck/preview-dom.js +1 -0
  945. package/dist/lib/puck/preview-events.d.ts +2 -0
  946. package/dist/lib/puck/preview-events.js +1 -0
  947. package/dist/lib/puck/preview-selection-context.d.ts +2 -0
  948. package/dist/lib/puck/preview-selection-context.js +1 -0
  949. package/dist/lib/puck/registry.d.ts +3 -0
  950. package/dist/lib/puck/registry.js +1 -0
  951. package/dist/lib/puck/renamed-props.d.ts +2 -0
  952. package/dist/lib/puck/renamed-props.js +1 -0
  953. package/dist/lib/puck/sanitize.d.ts +1 -0
  954. package/dist/lib/puck/sanitize.js +1 -0
  955. package/dist/lib/puck/section-catalog.d.ts +36 -0
  956. package/dist/lib/puck/section-catalog.js +1 -0
  957. package/dist/lib/puck/section-descriptions.d.ts +1 -0
  958. package/dist/lib/puck/section-descriptions.js +1 -0
  959. package/dist/lib/puck/section-doc.d.ts +1 -0
  960. package/dist/lib/puck/section-doc.js +1 -0
  961. package/dist/lib/puck/section-groups.d.ts +21 -0
  962. package/dist/lib/puck/section-groups.js +1 -0
  963. package/dist/lib/puck/section-text-suppress.d.ts +9 -0
  964. package/dist/lib/puck/section-text-suppress.js +1 -0
  965. package/dist/lib/puck/site-gap-fields.d.ts +2 -0
  966. package/dist/lib/puck/site-gap-fields.js +1 -0
  967. package/dist/lib/puck/slider-typing.d.ts +9 -0
  968. package/dist/lib/puck/slider-typing.js +1 -0
  969. package/dist/lib/puck/slot-child-name.d.ts +2 -0
  970. package/dist/lib/puck/slot-child-name.js +1 -0
  971. package/dist/lib/puck/slot-heal.d.ts +1 -0
  972. package/dist/lib/puck/slot-heal.js +1 -0
  973. package/dist/lib/puck/slot-section-spec.d.ts +1 -0
  974. package/dist/lib/puck/slot-section-spec.js +0 -0
  975. package/dist/lib/puck/slot-specs.d.ts +9 -0
  976. package/dist/lib/puck/slot-specs.js +1 -0
  977. package/dist/lib/puck/snapshot-contracts.d.ts +4 -0
  978. package/dist/lib/puck/snapshot-contracts.js +1 -0
  979. package/dist/lib/puck/source-bound-parts.d.ts +12 -0
  980. package/dist/lib/puck/source-bound-parts.js +1 -0
  981. package/dist/lib/puck/style-clipboard.d.ts +1 -0
  982. package/dist/lib/puck/style-clipboard.js +1 -0
  983. package/dist/lib/puck/templateFields.d.ts +2 -0
  984. package/dist/lib/puck/templateFields.js +1 -0
  985. package/dist/lib/puck/text-control-kinds.d.ts +2 -0
  986. package/dist/lib/puck/text-control-kinds.js +1 -0
  987. package/dist/lib/puck/text-parts.d.ts +30 -0
  988. package/dist/lib/puck/text-parts.js +1 -0
  989. package/dist/lib/puck/theme-swatches.d.ts +3 -0
  990. package/dist/lib/puck/theme-swatches.js +1 -0
  991. package/dist/lib/puck/tree-reveal.d.ts +1 -0
  992. package/dist/lib/puck/tree-reveal.js +1 -0
  993. package/dist/lib/puck/use-section-ops.d.ts +2 -0
  994. package/dist/lib/puck/use-section-ops.js +1 -0
  995. package/dist/lib/reel.d.ts +33 -0
  996. package/dist/lib/reel.js +1 -0
  997. package/dist/lib/safe-storage.d.ts +10 -0
  998. package/dist/lib/safe-storage.js +1 -0
  999. package/dist/lib/save-as-template-action.d.ts +29 -0
  1000. package/dist/lib/save-as-template-action.js +1 -0
  1001. package/dist/lib/section-template-types.d.ts +1 -0
  1002. package/dist/lib/section-template-types.js +0 -0
  1003. package/dist/lib/sections/card-cover-index.d.ts +7 -0
  1004. package/dist/lib/sections/card-cover-index.js +1 -0
  1005. package/dist/lib/sections/collection-card-text-regions.d.ts +4 -0
  1006. package/dist/lib/sections/collection-card-text-regions.js +1 -0
  1007. package/dist/lib/sections/divider-style.d.ts +12 -0
  1008. package/dist/lib/sections/divider-style.js +1 -0
  1009. package/dist/lib/sections/faq-text-regions.d.ts +4 -0
  1010. package/dist/lib/sections/faq-text-regions.js +1 -0
  1011. package/dist/lib/sections/hero-overlay.d.ts +10 -0
  1012. package/dist/lib/sections/hero-overlay.js +1 -0
  1013. package/dist/lib/sections/intro-bleed-layout.d.ts +15 -0
  1014. package/dist/lib/sections/intro-bleed-layout.js +1 -0
  1015. package/dist/lib/sections/new-section-node.d.ts +2 -0
  1016. package/dist/lib/sections/new-section-node.js +1 -0
  1017. package/dist/lib/sections/product-card-style.d.ts +18 -0
  1018. package/dist/lib/sections/product-card-style.js +1 -0
  1019. package/dist/lib/sections/section-eyebrow-look.d.ts +11 -0
  1020. package/dist/lib/sections/section-eyebrow-look.js +1 -0
  1021. package/dist/lib/sections/section-head-placement.d.ts +31 -0
  1022. package/dist/lib/sections/section-head-placement.js +1 -0
  1023. package/dist/lib/sections/section-head-style.d.ts +16 -0
  1024. package/dist/lib/sections/section-head-style.js +1 -0
  1025. package/dist/lib/sections/section-number-style.d.ts +10 -0
  1026. package/dist/lib/sections/section-number-style.js +1 -0
  1027. package/dist/lib/sections/section-presets.d.ts +9 -0
  1028. package/dist/lib/sections/section-presets.js +1 -0
  1029. package/dist/lib/sections/section-text-kinds.d.ts +8 -0
  1030. package/dist/lib/sections/section-text-kinds.js +1 -0
  1031. package/dist/lib/sections/section-text-regions.d.ts +3 -0
  1032. package/dist/lib/sections/section-text-regions.js +1 -0
  1033. package/dist/lib/sections/spec-table-regions.d.ts +11 -0
  1034. package/dist/lib/sections/spec-table-regions.js +1 -0
  1035. package/dist/lib/sections/text-regions.d.ts +21 -0
  1036. package/dist/lib/sections/text-regions.js +1 -0
  1037. package/dist/lib/sections/ticker-join.d.ts +9 -0
  1038. package/dist/lib/sections/ticker-join.js +1 -0
  1039. package/dist/lib/sections/ticker-scroll.d.ts +11 -0
  1040. package/dist/lib/sections/ticker-scroll.js +1 -0
  1041. package/dist/lib/sections/ticker-style.d.ts +17 -0
  1042. package/dist/lib/sections/ticker-style.js +1 -0
  1043. package/dist/lib/site-runtime.d.ts +28 -0
  1044. package/dist/lib/site-runtime.js +1 -0
  1045. package/dist/lib/site-theme-row.d.ts +52 -0
  1046. package/dist/lib/site-theme-row.js +0 -0
  1047. package/dist/lib/slugify.d.ts +1 -0
  1048. package/dist/lib/slugify.js +1 -0
  1049. package/dist/lib/source-rules/accent-token-name.d.ts +20 -0
  1050. package/dist/lib/source-rules/accent-token-name.js +4 -0
  1051. package/dist/lib/source-rules/button-corners.d.ts +13 -0
  1052. package/dist/lib/source-rules/button-corners.js +2 -0
  1053. package/dist/lib/source-rules/editor-chrome-note.d.ts +16 -0
  1054. package/dist/lib/source-rules/editor-chrome-note.js +13 -0
  1055. package/dist/lib/source-rules/editor-context-menu.d.ts +5 -0
  1056. package/dist/lib/source-rules/editor-context-menu.js +4 -0
  1057. package/dist/lib/source-rules/editor-puck-drag-off.d.ts +8 -0
  1058. package/dist/lib/source-rules/editor-puck-drag-off.js +4 -0
  1059. package/dist/lib/source-rules/efficient-easel-version.d.ts +9 -0
  1060. package/dist/lib/source-rules/efficient-easel-version.js +2 -0
  1061. package/dist/lib/source-rules/every-button-follows-the-button-colour.d.ts +12 -0
  1062. package/dist/lib/source-rules/every-button-follows-the-button-colour.js +7 -0
  1063. package/dist/lib/source-rules/flat-theme-palette.d.ts +10 -0
  1064. package/dist/lib/source-rules/flat-theme-palette.js +8 -0
  1065. package/dist/lib/source-rules/full-width-means-full.d.ts +8 -0
  1066. package/dist/lib/source-rules/full-width-means-full.js +2 -0
  1067. package/dist/lib/source-rules/heading-ceiling-sweep.d.ts +11 -0
  1068. package/dist/lib/source-rules/heading-ceiling-sweep.js +8 -0
  1069. package/dist/lib/source-rules/label-hints.d.ts +7 -0
  1070. package/dist/lib/source-rules/label-hints.js +5 -0
  1071. package/dist/lib/source-rules/label-vocabulary.d.ts +14 -0
  1072. package/dist/lib/source-rules/label-vocabulary.js +5 -0
  1073. package/dist/lib/source-rules/mirrored-rows-agree.d.ts +19 -0
  1074. package/dist/lib/source-rules/mirrored-rows-agree.js +10 -0
  1075. package/dist/lib/source-rules/no-comment-in-jsx.d.ts +9 -0
  1076. package/dist/lib/source-rules/no-comment-in-jsx.js +7 -0
  1077. package/dist/lib/source-rules/no-glyph-rows.d.ts +15 -0
  1078. package/dist/lib/source-rules/no-glyph-rows.js +4 -0
  1079. package/dist/lib/source-rules/one-colour-control.d.ts +7 -0
  1080. package/dist/lib/source-rules/one-colour-control.js +2 -0
  1081. package/dist/lib/source-rules/one-dropdown.d.ts +10 -0
  1082. package/dist/lib/source-rules/one-dropdown.js +4 -0
  1083. package/dist/lib/source-rules/one-editable-list.d.ts +16 -0
  1084. package/dist/lib/source-rules/one-editable-list.js +6 -0
  1085. package/dist/lib/source-rules/one-editor-list.d.ts +7 -0
  1086. package/dist/lib/source-rules/one-editor-list.js +3 -0
  1087. package/dist/lib/source-rules/one-right-click.d.ts +10 -0
  1088. package/dist/lib/source-rules/one-right-click.js +2 -0
  1089. package/dist/lib/source-rules/one-switch.d.ts +9 -0
  1090. package/dist/lib/source-rules/one-switch.js +7 -0
  1091. package/dist/lib/source-rules/one-up-level-link.d.ts +16 -0
  1092. package/dist/lib/source-rules/one-up-level-link.js +18 -0
  1093. package/dist/lib/source-rules/panel-colour-tokens.d.ts +48 -0
  1094. package/dist/lib/source-rules/panel-colour-tokens.js +20 -0
  1095. package/dist/lib/source-rules/panel-no-divider-lines.d.ts +14 -0
  1096. package/dist/lib/source-rules/panel-no-divider-lines.js +10 -0
  1097. package/dist/lib/source-rules/panel-one-arrow.d.ts +21 -0
  1098. package/dist/lib/source-rules/panel-one-arrow.js +1 -0
  1099. package/dist/lib/source-rules/panel-one-hover.d.ts +8 -0
  1100. package/dist/lib/source-rules/panel-one-hover.js +2 -0
  1101. package/dist/lib/source-rules/panel-rows-use-the-shared-row.d.ts +23 -0
  1102. package/dist/lib/source-rules/panel-rows-use-the-shared-row.js +22 -0
  1103. package/dist/lib/source-rules/part-slider-fills-row.d.ts +11 -0
  1104. package/dist/lib/source-rules/part-slider-fills-row.js +29 -0
  1105. package/dist/lib/source-rules/part-style-naming.d.ts +10 -0
  1106. package/dist/lib/source-rules/part-style-naming.js +2 -0
  1107. package/dist/lib/source-rules/puck-document-surfaces.d.ts +16 -0
  1108. package/dist/lib/source-rules/puck-document-surfaces.js +8 -0
  1109. package/dist/lib/source-rules/reduced-motion.d.ts +26 -0
  1110. package/dist/lib/source-rules/reduced-motion.js +9 -0
  1111. package/dist/lib/source-rules/route-content-width.d.ts +22 -0
  1112. package/dist/lib/source-rules/route-content-width.js +5 -0
  1113. package/dist/lib/source-rules/rule.d.ts +21 -0
  1114. package/dist/lib/source-rules/rule.js +1 -0
  1115. package/dist/lib/source-rules/section-theme-preview.d.ts +9 -0
  1116. package/dist/lib/source-rules/section-theme-preview.js +8 -0
  1117. package/dist/lib/source-rules/site-gutter.d.ts +6 -0
  1118. package/dist/lib/source-rules/site-gutter.js +4 -0
  1119. package/dist/lib/source-rules/site-square-by-default.d.ts +17 -0
  1120. package/dist/lib/source-rules/site-square-by-default.js +3 -0
  1121. package/dist/lib/source-rules/site-width.d.ts +11 -0
  1122. package/dist/lib/source-rules/site-width.js +7 -0
  1123. package/dist/lib/source-rules/size-token-paints.d.ts +8 -0
  1124. package/dist/lib/source-rules/size-token-paints.js +11 -0
  1125. package/dist/lib/source-rules/social-icons-import.d.ts +6 -0
  1126. package/dist/lib/source-rules/social-icons-import.js +6 -0
  1127. package/dist/lib/source-rules/source-status-is-shared.d.ts +7 -0
  1128. package/dist/lib/source-rules/source-status-is-shared.js +1 -0
  1129. package/dist/lib/source-rules/style-face-matches-size.d.ts +10 -0
  1130. package/dist/lib/source-rules/style-face-matches-size.js +4 -0
  1131. package/dist/lib/source-rules/support/accent-rename-bytes.d.ts +5 -0
  1132. package/dist/lib/source-rules/support/accent-rename-bytes.js +1 -0
  1133. package/dist/lib/source-rules/support/css-box-model.d.ts +5 -0
  1134. package/dist/lib/source-rules/support/css-box-model.js +1 -0
  1135. package/dist/lib/source-rules/support/css-length.d.ts +2 -0
  1136. package/dist/lib/source-rules/support/css-length.js +1 -0
  1137. package/dist/lib/source-rules/support/css-rules.d.ts +12 -0
  1138. package/dist/lib/source-rules/support/css-rules.js +1 -0
  1139. package/dist/lib/source-rules/support/css-source.d.ts +2 -0
  1140. package/dist/lib/source-rules/support/css-source.js +3 -0
  1141. package/dist/lib/source-rules/support/label-extraction.d.ts +14 -0
  1142. package/dist/lib/source-rules/support/label-extraction.js +5 -0
  1143. package/dist/lib/source-rules/support/panel-select-cascade.d.ts +72 -0
  1144. package/dist/lib/source-rules/support/panel-select-cascade.js +2 -0
  1145. package/dist/lib/source-rules/support/part-slider-call-sites.d.ts +9 -0
  1146. package/dist/lib/source-rules/support/part-slider-call-sites.js +17 -0
  1147. package/dist/lib/source-rules/support/registry-source.d.ts +14 -0
  1148. package/dist/lib/source-rules/support/registry-source.js +3 -0
  1149. package/dist/lib/source-rules/support/ts-source.d.ts +2 -0
  1150. package/dist/lib/source-rules/support/ts-source.js +3 -0
  1151. package/dist/lib/source-rules/theme-mode-controls-gone.d.ts +10 -0
  1152. package/dist/lib/source-rules/theme-mode-controls-gone.js +5 -0
  1153. package/dist/lib/source-rules/upload-intake-guard.d.ts +11 -0
  1154. package/dist/lib/source-rules/upload-intake-guard.js +6 -0
  1155. package/dist/lib/source-rules/word-styles.d.ts +8 -0
  1156. package/dist/lib/source-rules/word-styles.js +4 -0
  1157. package/dist/lib/theme-dom.d.ts +14 -0
  1158. package/dist/lib/theme-dom.js +1 -0
  1159. package/dist/lib/theme-row-persist.d.ts +34 -0
  1160. package/dist/lib/theme-row-persist.js +1 -0
  1161. package/dist/lib/theme-save-message.d.ts +6 -0
  1162. package/dist/lib/theme-save-message.js +1 -0
  1163. package/dist/lib/theme.d.ts +177 -0
  1164. package/dist/lib/theme.js +18 -0
  1165. package/dist/lib/use-host-products.d.ts +2 -0
  1166. package/dist/lib/use-host-products.js +1 -0
  1167. package/dist/lib/web-backend.d.ts +138 -0
  1168. package/dist/lib/web-backend.js +1 -0
  1169. package/dist/lib/web-runtime.d.ts +7 -0
  1170. package/dist/lib/web-runtime.js +1 -0
  1171. package/dist/lib/website-document-addressing.d.ts +2 -0
  1172. package/dist/lib/website-document-addressing.js +1 -0
  1173. package/dist/lib/website-host-inbound.d.ts +1 -0
  1174. package/dist/lib/website-host-inbound.js +1 -0
  1175. package/dist/lib/website-session.d.ts +5 -0
  1176. package/dist/lib/website-session.js +1 -0
  1177. package/dist/lib/word-styles.d.ts +54 -0
  1178. package/dist/lib/word-styles.js +1 -0
  1179. package/package.json +50 -36
  1180. package/dist/chunks/EditorPreviewCheckout-5KOAA2SK.js +0 -4
  1181. package/dist/chunks/PreviewFooter-H6XJUR5S.js +0 -1
  1182. package/dist/chunks/SectionEditorShell-EEZR7KFE.js +0 -1
  1183. package/dist/chunks/SiteEditorProvider-ECGZVKLG.js +0 -1
  1184. package/dist/chunks/StorefrontEditorChrome-4TFS62GY.js +0 -1
  1185. package/dist/chunks/chunk-6CP4D6VJ.js +0 -1
  1186. package/dist/chunks/chunk-7QXLVNFD.js +0 -1
  1187. package/dist/chunks/chunk-BJOS4Y35.js +0 -15
  1188. package/dist/chunks/chunk-BJOS4Y35.js.LEGAL.txt +0 -27
  1189. package/dist/chunks/chunk-EIXUXJQ5.js +0 -153
  1190. package/dist/chunks/chunk-EIXUXJQ5.js.LEGAL.txt +0 -4
  1191. package/dist/chunks/chunk-H7O6ZYX6.js +0 -1
  1192. package/dist/chunks/chunk-HVOO2GNE.js +0 -1
  1193. package/dist/chunks/chunk-IFRGL3GL.js +0 -1
  1194. package/dist/chunks/chunk-ML2AR222.js +0 -1
  1195. package/dist/chunks/chunk-RRHBLOH4.js +0 -1
  1196. package/dist/chunks/chunk-S6SXIMIG.js +0 -2
  1197. package/dist/chunks/chunk-SNH7IFTZ.js +0 -1
  1198. package/dist/chunks/chunk-Y4CZD2YY.js +0 -230
  1199. package/dist/chunks/chunk-Y6SLVHK3.js +0 -1
  1200. package/dist/chunks/chunk-YVQUISDX.js +0 -1
  1201. package/dist/chunks/fitment-data-6NXSKODX.js +0 -1
  1202. package/dist/chunks/gallery-builds-data-SWA6XKFF.js +0 -1
  1203. package/dist/chunks/headers-3CAQXQEY.js +0 -1
  1204. package/dist/chunks/render-client-YGYDU3K2.js +0 -1
  1205. package/dist/contract.d.ts +0 -48
  1206. package/dist/contract.js +0 -1
  1207. package/dist/editor.css +0 -1
  1208. package/dist/index.d.ts +0 -721
  1209. package/dist/index.js +0 -11058
  1210. package/dist/links.d.ts +0 -86
  1211. package/dist/links.js +0 -1
@@ -0,0 +1,4870 @@
1
+ /*
2
+ * Storefront editor chrome — themed to the efficient-shell design system.
3
+ *
4
+ * The editor is efficient-easel's engine (`efficient-easel/easel`), hosted by
5
+ * StorefrontEditorChrome. Until blueprint 6.3 (2026-10-02) it was Puck
6
+ * (@measured/puck) driven through its composition API, and much of the
7
+ * reasoning below was measured against Puck's sheets; where a note names a
8
+ * Puck class or `puck.css`, read it as history unless it says otherwise.
9
+ * This file:
10
+ * 1. Tunes the canvas's own marks — the engine's lists, outlines and drop
11
+ * cells (`efficient-easel/easel.css`, `drag.css`) — to the storefront's
12
+ * rules about what a merchant should see.
13
+ * 2. Styles the bespoke chrome around the engine's pieces (top bar,
14
+ * left/right panels, device frame, inspector).
15
+ *
16
+ * Everything is scoped under `.sf-editor` so it can never leak into the public
17
+ * storefront. Colour is driven entirely by the `--sf-*` tokens below so the
18
+ * chrome themes in ONE place. The editor is deliberately light-only — it does
19
+ * NOT follow the admin portal's active theme. The admin still bridges its theme
20
+ * into the iframe, but lib/editor-theme.ts pins the editor to the light base
21
+ * regardless (see that file), so there is no dark block.
22
+ *
23
+ * WHERE THE VALUES LIVE (card 66034). They no longer live here. Every `--sf-*`
24
+ * colour, radius, shadow and font below is now an ALIAS onto the `--es-*` shell
25
+ * token layer that card 66024 promoted into the `efficient-easel` package
26
+ * (`efficient-easel/shell.css`, imported by each editor entry and scoped to the
27
+ * `es-editor` class this same root carries). The campaign designer aliases its
28
+ * own `--cd-*` names onto the identical set, so the two editors are guaranteed to
29
+ * agree by construction rather than by two lists being kept in step by hand.
30
+ *
31
+ * The alias layer is kept — rather than renaming every call site to `--es-*` —
32
+ * because `--sf-*` is the storefront chrome's own vocabulary, used ~700 times
33
+ * below and in the components' inline styles; the indirection is what lets the
34
+ * shared layer change underneath without touching any of them. Add a NEW value
35
+ * to editor-shell and alias it here; never re-introduce a literal.
36
+ */
37
+
38
+ /* Mounted inside another app's window (the efficient-web-builder package — the admin
39
+ portal's Storefront editor): the editor fills the WINDOW it is drawn in, not
40
+ the browser viewport, or its bottom edge (the canvas's last rows, "+ New
41
+ page") would sit under the window's own frame. The package wraps the editor
42
+ in `.sf-embedded`; the shop's own /edit routes never do. */
43
+ .sf-embedded {
44
+ height: 100%;
45
+ min-height: 0;
46
+ }
47
+ .sf-embedded .sf-editor {
48
+ height: 100%;
49
+ }
50
+
51
+ /* Visually-hidden, screen-reader-only (e.g. the live search-result count). */
52
+ .sf-sr-only {
53
+ position: absolute;
54
+ width: 1px;
55
+ height: 1px;
56
+ margin: -1px;
57
+ padding: 0;
58
+ border: 0;
59
+ overflow: hidden;
60
+ clip: rect(0, 0, 0, 0);
61
+ white-space: nowrap;
62
+ }
63
+
64
+ /* ── Top bar ─────────────────────────────────────────────────────────────
65
+ The bar, its clusters, the icon buttons, the divider, the device switch and
66
+ the ghost / primary buttons are editor-shell's (efficient-easel/chrome.css,
67
+ `.es-topbar` and friends), lifted from this sheet and shared with the
68
+ campaign editor (redesign PR 2a). Only the website's own additions follow. */
69
+
70
+ /* Reuse-component buttons in the top bar inherit the ghost look */
71
+ .sf-actions-slot > button {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 6px;
75
+ padding: 7px 12px;
76
+ border: 1px solid var(--sf-btn-border);
77
+ background: var(--sf-panel);
78
+ border-radius: 8px;
79
+ cursor: pointer;
80
+ font-size: 13px;
81
+ font-weight: 500;
82
+ color: var(--sf-btn-text);
83
+ }
84
+ .sf-actions-slot > button:hover {
85
+ background: var(--sf-hover);
86
+ }
87
+
88
+ /* ── Body / floating panels ───────────────────────────────────────────── */
89
+ /* The panel's title and, beside it, WHICH SITE it is styling (card 66167).
90
+ `min-width: 0` on both so the name — which is a merchant's own words and can
91
+ be any length — is what gives way when the panel is dragged narrow, rather
92
+ than pushing the ✕ off the head. `baseline`, not `center`: the two are
93
+ different sizes and a shared baseline is what makes them read as one line. */
94
+ .sf-panel-heading {
95
+ display: flex;
96
+ align-items: baseline;
97
+ gap: 6px;
98
+ min-width: 0;
99
+ }
100
+ /* The site's name, read-only. Quiet and small — it is context, not a setting,
101
+ and the title beside it is the thing being named. It ellipsises rather than
102
+ wrapping, because a two-line head would move every row in the panel below it;
103
+ the whole name is on the `title`, along with where to change it. */
104
+ .sf-panel-site {
105
+ margin: 0;
106
+ min-width: 0;
107
+ overflow: hidden;
108
+ font-size: 11px;
109
+ font-weight: 500;
110
+ color: var(--sf-muted);
111
+ white-space: nowrap;
112
+ text-overflow: ellipsis;
113
+ }
114
+ .sf-panel-site::before {
115
+ content: "·";
116
+ margin-right: 6px;
117
+ }
118
+ .sf-x {
119
+ width: 24px;
120
+ height: 24px;
121
+ border: none;
122
+ background: transparent;
123
+ border-radius: 6px;
124
+ cursor: pointer;
125
+ color: var(--sf-muted);
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ font-size: 14px;
130
+ }
131
+ .sf-x:hover {
132
+ background: var(--sf-hover);
133
+ }
134
+
135
+ /* ── Left panel: section tree (wraps Puck.Outline) ────────────────────── */
136
+
137
+ /* EVERY SEAM THE TREE STACKS, and no bare number among them.
138
+ The pairs are spelled out rather than written `> * + *` because the tree
139
+ interleaves group labels and subtrees between its rows: a rule that only
140
+ knows about `.sf-row` leaves those seams to whatever padding happens to be
141
+ nearby, which looks governed and is not.
142
+ `.sf-group-label` keeps its own `padding` and takes no margin here — it is a
143
+ heading, and a heading's air is part of the heading. */
144
+ .sf-tree > .sf-row + .sf-row,
145
+ .sf-tree > .sf-subtree + .sf-row {
146
+ margin-top: var(--es-rhythm-between);
147
+ }
148
+ .sf-row {
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 8px;
152
+ /* ONE ROW, EVERY DEPTH (the owner's "One Ladder", 2026-09-04). The tree used
153
+ to draw three: a top-level bar (11px/600 in a 1px outline), a nested bar
154
+ (10.5px/600 on a mixed fill) and a leaf (11.5px/400). A merchant reading
155
+ down the panel had to learn three shapes that all meant "a level". Depth is
156
+ now said ONCE, by `.sf-subtree` — an indent and a hairline — and by nothing
157
+ else, so a row's look never has to be decided twice. */
158
+ padding: 7px 11px;
159
+ border-radius: var(--sf-row-radius);
160
+ cursor: pointer;
161
+ transition: background 0.12s;
162
+ /* Anchors the drop-indicator rules drawn during a drag-to-reorder, and the
163
+ drag grip, which sits in this row's own left padding. */
164
+ position: relative;
165
+ /* THE GROUND THIS ROW IS PAINTED ON, as a value the row's own children can
166
+ read. `.sf-row-actions` fades the name out beneath the hover eye/trash, and
167
+ a fade has to end in the colour that is actually behind the row — which
168
+ differs per state (plain, hovered, selected, bar, nested bar).
169
+ Every rule that changes the row's background changes this in the SAME
170
+ declaration block and then paints `background: var(--sf-row-scrim)`, so the
171
+ two can never name different colours. A resting row paints nothing, so its
172
+ ground is the panel it sits on. */
173
+ --sf-row-scrim: var(--sf-panel);
174
+ }
175
+ .sf-row:hover {
176
+ --sf-row-scrim: var(--sf-hover);
177
+ background: var(--sf-row-scrim);
178
+ }
179
+ /* The Layers rows' drag / drop marks (the dimmed grabbed row, the before /
180
+ after line, the drop-INTO box) and the group label are editor-shell's since
181
+ redesign 2b (`.es-tree-row.is-*`, `.es-tree-group`). The `.sf-row` rules here
182
+ serve the Pages list and the standalone section editor, which keep them. */
183
+ .sf-row.is-selected {
184
+ --sf-row-scrim: var(--sf-accent-tint);
185
+ background: var(--sf-row-scrim);
186
+ box-shadow: inset 2px 0 0 var(--sf-accent);
187
+ }
188
+ .sf-row.is-selected .sf-chip {
189
+ background: var(--sf-raised);
190
+ color: var(--sf-accent);
191
+ }
192
+ .sf-row.is-selected .sf-row-name {
193
+ color: var(--sf-accent-text);
194
+ font-weight: 600;
195
+ }
196
+ /* `.sf-add-section` WAS HERE, and it is gone on purpose (card 66228). Lewis
197
+ ruled on 2026-08-31 that ONE `+` in the top bar replaces every other add door
198
+ — the drawing is `editor-add-and-context-v1.html` in the SHARED `mockups/`
199
+ folder that sits beside the repo checkouts, NOT in this repo (card 66400.c:
200
+ the path here read `mockups/…`, which resolves to nothing from inside
201
+ efficient-storefront and has never existed in any commit of it, so a reader
202
+ checking the citation that justifies this removal found an empty hole):
203
+ 「加东西只有一个入口。树底下原来那个 + Add block 也去掉了。」 Two doors to one
204
+ action is the fault this line keeps removing.
205
+ Removing THIS door is safe on its own — it was one of three ways into the
206
+ same picker, and the other two stay: the `.sf-gap` seam between two rows
207
+ (`openPickerAt(index)`) and the `.sf-preview-gap` overlay in the canvas
208
+ (`openPickerAt(g.index)`). The Footer panel's own Add block row also still
209
+ works. Do not remove those in the same breath as the top-bar `+`: the earlier
210
+ round said it plainly — 「右边那一行不能先删」 — get the order backwards and a
211
+ merchant loses a route.
212
+ `.sf-plus` is untouched; both of its rules are scoped to other buttons
213
+ (`.sf-add-card .sf-plus`) and `PagesPanel` / `AddChildRow` still render it. */
214
+
215
+ /* ── "Add section" seam between two tree rows ──────────────────────────────
216
+ A slim hover target sitting in the gap between rows; on hover (or keyboard
217
+ focus) it draws an accent insertion line with a centred "+" and clicking it
218
+ opens the Add-section picker targeted at that seam. Negative margin keeps it
219
+ from adding visible bulk to the resting tree. */
220
+ .sf-gap {
221
+ position: relative;
222
+ display: block;
223
+ width: 100%;
224
+ height: 9px;
225
+ /* Small negative margin so the seam mostly lives in the inter-row gap; kept
226
+ shallow so it barely overlaps the neighbouring rows' padding. */
227
+ margin: -2px 0;
228
+ padding: 0;
229
+ border: 0;
230
+ background: transparent;
231
+ cursor: pointer;
232
+ z-index: 1;
233
+ }
234
+ .sf-gap-line {
235
+ position: absolute;
236
+ left: 12px;
237
+ right: 12px;
238
+ top: 50%;
239
+ height: 2px;
240
+ transform: translateY(-50%);
241
+ border-radius: 2px;
242
+ background: var(--sf-accent);
243
+ opacity: 0;
244
+ transition: opacity 0.12s;
245
+ }
246
+ .sf-gap-plus {
247
+ position: absolute;
248
+ left: 50%;
249
+ top: 50%;
250
+ transform: translate(-50%, -50%) scale(0.7);
251
+ width: 18px;
252
+ height: 18px;
253
+ border-radius: 50%;
254
+ background: var(--sf-accent);
255
+ color: #fff;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ font-size: 14px;
260
+ line-height: 1;
261
+ box-shadow: 0 0 0 2px var(--sf-panel);
262
+ opacity: 0;
263
+ transition: opacity 0.12s, transform 0.12s;
264
+ }
265
+ .sf-gap:hover .sf-gap-line,
266
+ .sf-gap:focus-visible .sf-gap-line {
267
+ opacity: 1;
268
+ }
269
+ .sf-gap:hover .sf-gap-plus,
270
+ .sf-gap:focus-visible .sf-gap-plus {
271
+ opacity: 1;
272
+ transform: translate(-50%, -50%) scale(1);
273
+ }
274
+
275
+ /* ── Nested slot children (e.g. each Collections column) ────────────────────
276
+ ONE level of nesting costs 10px, and that is the whole indent — no 17px
277
+ margin, no rule down the left any more. Both were doing the job the row
278
+ ANATOMY was undoing: the parent above paid for a grip and a caret its
279
+ children did not have, so 26px of wrapper still left a child's name 18px to
280
+ the LEFT of its parent's (card 66199). With the grip out of the flow and the
281
+ caret at the row's right end, the wrapper is the only thing left that moves a
282
+ row sideways, so it can be small and still be the whole answer. The guide
283
+ rule goes with it: the bars now say what contains what, and a hairline down
284
+ the side of a grey bar reads as a border that failed to close. */
285
+ .sf-subtree {
286
+ /* THE ONLY THING IN THIS TREE THAT SAYS DEPTH (One Ladder, 2026-09-04).
287
+ Every row is now the same shape, so this wrapper carries the whole job:
288
+ 16px of indent and a 1px rule down the left of the group it opens.
289
+
290
+ THE RULE IS NOT DECORATION. The fill it replaces was ~2% off white and
291
+ disappeared on a cheap panel; a hairline at `--sf-line` #e5e7eb is 10% and
292
+ does not. It also does something the fill never could — it runs the HEIGHT
293
+ of the group, so a child eight rows down still shows which parent it is
294
+ under without that parent being on screen.
295
+
296
+ The gap is the tree's own `--es-rhythm-between`, not a number of its own —
297
+ rows inside a group sit exactly as far apart as rows outside one, which is
298
+ what makes the ladder read as one list at three depths rather than three
299
+ lists. */
300
+ margin: var(--es-rhythm-between) 0 var(--es-rhythm-between) 16px;
301
+ padding-left: 9px;
302
+ border-left: 1px solid var(--sf-line);
303
+ display: flex;
304
+ flex-direction: column;
305
+ gap: var(--es-rhythm-between);
306
+ }
307
+
308
+ /* ── Footer "Add block" menu — heterogeneous block-type picker ──────────── */
309
+
310
+ /* ── Add — a mode over the left panel (components/AddSidePanel) ────────────
311
+ Blueprint step 3.7: the floating `.sf-picker` dialog is gone, and Add is the
312
+ left panel's content while it is open. The search, the headed groups and the
313
+ tiles are efficient-easel/drag's `AddPanel` (drag.css); a section is a CARD
314
+ with its cover there (`.es-add-grid.is-cards`). What is the website's alone
315
+ is the panel's inset and the Live chat row under the groups. */
316
+ /* The Live chat bubble: site-wide and not a section, so a switch on a row
317
+ rather than a tile. The name opens its settings; the switch turns it on. */
318
+ .sf-addside-sitewide {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 10px;
322
+ margin-top: 16px;
323
+ padding: 10px;
324
+ border: 1px solid var(--sf-divider);
325
+ border-radius: 10px;
326
+ }
327
+ .sf-addside-sitewide-glyph {
328
+ flex: none;
329
+ display: grid;
330
+ place-items: center;
331
+ width: 28px;
332
+ height: 28px;
333
+ border-radius: 7px;
334
+ background: var(--sf-hover);
335
+ color: var(--sf-muted);
336
+ }
337
+ .sf-addside-sitewide-name {
338
+ flex: 1;
339
+ min-width: 0;
340
+ display: flex;
341
+ flex-direction: column;
342
+ align-items: flex-start;
343
+ gap: 2px;
344
+ padding: 0;
345
+ border: 0;
346
+ background: none;
347
+ font: 600 12.5px/1.3 var(--sf-font);
348
+ color: var(--sf-text);
349
+ text-align: left;
350
+ cursor: pointer;
351
+ }
352
+ .sf-addside-sitewide-name:hover {
353
+ color: var(--sf-accent);
354
+ }
355
+ .sf-addside-sitewide-desc {
356
+ font-weight: 400;
357
+ font-size: 11.5px;
358
+ color: var(--sf-muted);
359
+ }
360
+
361
+ /* "Switch to Box footer" (blueprint step H.4) — a short note and its button
362
+ under the old footer's settings. A confirmation replaces the button in place
363
+ rather than opening a dialog: it is one decision, made where it is offered. */
364
+ .sf-box-footer-switch {
365
+ display: flex;
366
+ flex-direction: column;
367
+ align-items: flex-start;
368
+ gap: 10px;
369
+ margin: 18px 0 6px;
370
+ padding: 12px;
371
+ border: 1px solid var(--sf-divider);
372
+ border-radius: 10px;
373
+ }
374
+ .sf-box-footer-switch-text {
375
+ margin: 0;
376
+ font-size: 12px;
377
+ line-height: 1.45;
378
+ color: var(--sf-muted);
379
+ }
380
+ .sf-box-footer-switch-row {
381
+ display: flex;
382
+ gap: 8px;
383
+ }
384
+
385
+ /* Section-cover wireframe tokens — the bespoke palette the SectionThumbnail
386
+ * covers draw with (see components/SectionThumbnail.tsx). Defined once here so no
387
+ * colour literal lives in the .tsx (doc/no-hardcoding.md R3); the admin mirror
388
+ * defines the same block in src/index.css. Light-only: the picker + the console
389
+ * catalog both sit on a light surface. */
390
+ .sf-thumb {
391
+ --sf-thumb-card: #ffffff;
392
+ --sf-thumb-thumb-bg: #fcfdff;
393
+ --sf-thumb-thumb-border: #edeff4;
394
+ --sf-thumb-block: #e9ebf0;
395
+ --sf-thumb-bar-light: #dde1e8;
396
+ --sf-thumb-bar-dark: #c3c9d3;
397
+ --sf-thumb-hatch1: #e2e5ec;
398
+ --sf-thumb-hatch2: #edeff4;
399
+ --sf-thumb-accent: #2f6bed;
400
+ --sf-thumb-accent-tint: #e6edfd;
401
+ --sf-thumb-border: #e7eaf1;
402
+
403
+ /* ── THE SCENE PALETTE ─────────────────────────────────────────────────
404
+ Added when the covers stopped being grey wireframes. The eleven tokens
405
+ above are all pale grey-blue, because the old covers only ever drew bars:
406
+ they could say WHERE things sat and nothing else, which is why the owner
407
+ said he could not get any useful information out of them (2026-09-04).
408
+
409
+ A cover that reads at 196px needs the two things a wireframe throws away —
410
+ colour and content — so these are the grounds a scene is painted on, plus
411
+ the one warm highlight that stands in for a brand accent. Still tokens and
412
+ still no literals in the component (doc/no-hardcoding.md R3), and still
413
+ ONE definition per app.
414
+
415
+ ⚠️ THE GROUNDS ARE NOT SITE COLOURS, and are not wired to a site's theme.
416
+ A cover pictures a KIND of section, and has to read the same in every
417
+ tenant and in the admin portal's catalog — a merchant comparing "Hero"
418
+ against "Split" is choosing a layout, not previewing their own palette.
419
+ Wiring the grounds to `--sf-accent` would repaint all 68 the moment a site
420
+ changed its accent, and make two shops disagree about what one section is.
421
+
422
+ ⚠️ `--sf-thumb-accent` IS THE EXCEPTION, and it was here first. The ADMIN
423
+ portal overrides it per site (`--sf-cover-accent`, src/index.css) so a
424
+ Sites card's covers wear that site's real accent. That is a deliberate,
425
+ scoped override of ONE token — not a licence to theme the grounds, which
426
+ would take the same cover to a different picture rather than a different
427
+ highlight. */
428
+ --sf-thumb-ink: #1b2027; /* dark photographic ground, top */
429
+ --sf-thumb-ink-2: #39434f; /* …and its gradient end */
430
+ --sf-thumb-ink-soft: #2c3540; /* the lighter dark ground, for split scenes */
431
+ --sf-thumb-ink-soft-2: #4a5763;
432
+ --sf-thumb-paper: #f4f1ed; /* warm light ground */
433
+ --sf-thumb-paper-2: #cfc8bf; /* …and its gradient end */
434
+ --sf-thumb-warm: #e7c26a; /* the highlight — overline + CTA on a dark ground */
435
+ --sf-thumb-rust: #b4652a; /* the overline on a light ground */
436
+ --sf-thumb-on-ink: #ffffff;
437
+ --sf-thumb-scene-text: #171412; /* headline / price on a light ground */
438
+ --sf-thumb-scene-muted: #6b7280; /* section titles and secondary lines */
439
+ --sf-thumb-scene-rule: #eceef1; /* the hairline between table rows */
440
+ /* The product MARK — option 甲, picked by the owner on 2026-09-04: two soft
441
+ blocks, deliberately NOT a picture of a wheel. The catalogue also sells
442
+ tyres, lift kits, lights and pedals, so a round mark would be wrong on most
443
+ of it; this says "a product sits here" and claims nothing more. */
444
+ --sf-thumb-mark-1: #c8ccd1;
445
+ --sf-thumb-mark-2: #8e9aa6;
446
+ /* The scrim that keeps a headline legible over a dark photographic ground —
447
+ the one place a cover needs transparency, so it is a token like every other
448
+ colour here rather than an `rgba()` sitting in the component. */
449
+ --sf-thumb-scrim: rgba(0, 0, 0, 0.55);
450
+ }
451
+ /* Drives the Ticker cover's scrolling strip. */
452
+ @keyframes sf-thumb-marquee {
453
+ from {
454
+ transform: translateX(0);
455
+ }
456
+ to {
457
+ transform: translateX(-33.34%);
458
+ }
459
+ }
460
+
461
+ /* ── The one destructive confirm in the panel (Reset color) ─────────────────
462
+ [ADD] Reset On Every Site Style. Every other reset on Site styles clears a
463
+ number or a rung; this one deletes a merchant's authored palettes, so it asks
464
+ first (owner's ruling 2026-09-05, option B).
465
+
466
+ THE COLOURS LIVE HERE RATHER THAN ON THE COMPONENT, and that is the rule
467
+ rather than a preference. `test-panel-colour-tokens` sweeps `components/` for
468
+ literals — "a literal there is a private copy of one step of that scale: it
469
+ does not move when the scale moves". The older `LeaveConfirmDialog` spells its
470
+ palette in Tailwind literals and predates that sweep; a second dialog copying
471
+ it would have passed review and failed the gate.
472
+
473
+ The destructive button takes the `--sf-danger*` family that already exists,
474
+ not a red of its own. The radius is the 9px the theme cards and the add-card
475
+ button already wear, so the dialog reads as the same object as the rest of the
476
+ panel rather than a new shape. */
477
+ .sf-cleardlg-veil {
478
+ position: fixed;
479
+ inset: 0;
480
+ z-index: 200;
481
+ display: flex;
482
+ align-items: center;
483
+ justify-content: center;
484
+ padding: 16px;
485
+ background: var(--sf-addcard-veil);
486
+ }
487
+ .sf-cleardlg {
488
+ width: 100%;
489
+ max-width: 360px;
490
+ box-sizing: border-box;
491
+ padding: 20px;
492
+ border: 1px solid var(--sf-line);
493
+ border-radius: 9px;
494
+ background: var(--sf-panel);
495
+ color: var(--sf-text);
496
+ box-shadow: 0 20px 50px var(--sf-accent-shadow);
497
+ }
498
+ .sf-cleardlg-title {
499
+ margin: 0;
500
+ font-size: 15px;
501
+ font-weight: 600;
502
+ color: var(--sf-text);
503
+ }
504
+ .sf-cleardlg-body {
505
+ margin: 8px 0 0;
506
+ font-size: 13px;
507
+ line-height: 1.6;
508
+ color: var(--sf-muted);
509
+ }
510
+ /* The one line a merchant must not skim. NOT red — the button already carries
511
+ the danger colour, and a red sentence over a red button is the panel shouting
512
+ twice. Full-strength ink and a heavier weight instead. */
513
+ .sf-cleardlg-final {
514
+ margin: 8px 0 0;
515
+ font-size: 13px;
516
+ line-height: 1.6;
517
+ font-weight: 500;
518
+ color: var(--sf-text);
519
+ }
520
+ .sf-cleardlg-btns {
521
+ display: flex;
522
+ justify-content: flex-end;
523
+ gap: 8px;
524
+ margin-top: 18px;
525
+ }
526
+ .sf-cleardlg-btn {
527
+ font-family: var(--sf-font);
528
+ font-size: 13px;
529
+ padding: 7px 14px;
530
+ border-radius: var(--sf-field-radius);
531
+ border: 1px solid var(--sf-btn-border);
532
+ background: var(--sf-panel);
533
+ color: var(--sf-btn-text);
534
+ cursor: pointer;
535
+ }
536
+ .sf-cleardlg-btn:hover { background: var(--sf-hover); }
537
+ .sf-cleardlg-btn--danger {
538
+ font-weight: 600;
539
+ border-color: var(--sf-danger-strong);
540
+ background: var(--sf-danger-strong);
541
+ color: var(--sf-on-accent);
542
+ }
543
+ .sf-cleardlg-btn--danger:hover { filter: brightness(0.94); }
544
+
545
+ .sf-addcard-overlay {
546
+ position: absolute;
547
+ inset: 0;
548
+ display: flex;
549
+ align-items: center;
550
+ justify-content: center;
551
+ background: var(--sf-addcard-veil);
552
+ backdrop-filter: blur(2px);
553
+ -webkit-backdrop-filter: blur(2px);
554
+ opacity: 0;
555
+ transition: opacity 0.15s;
556
+ pointer-events: none;
557
+ }
558
+ .sf-addcard:hover .sf-addcard-overlay,
559
+ .sf-addcard:focus-visible .sf-addcard-overlay {
560
+ opacity: 1;
561
+ }
562
+ .sf-addcard-addbtn {
563
+ display: inline-flex;
564
+ align-items: center;
565
+ gap: 6px;
566
+ background: var(--sf-accent);
567
+ color: #fff;
568
+ border-radius: 9px;
569
+ padding: 8px 14px;
570
+ font-size: 12.5px;
571
+ font-weight: 600;
572
+ box-shadow: 0 4px 12px var(--sf-accent-shadow);
573
+ }
574
+ .sf-addcard-plus {
575
+ font-size: 15px;
576
+ line-height: 1;
577
+ margin-top: -1px;
578
+ }
579
+ .sf-addcard-meta {
580
+ display: flex;
581
+ flex-direction: column;
582
+ gap: 2px;
583
+ padding: 0 2px 2px;
584
+ }
585
+ .sf-addcard-name {
586
+ font-size: 12.5px;
587
+ font-weight: 600;
588
+ color: var(--sf-text);
589
+ }
590
+ .sf-addcard-desc {
591
+ font-size: 10.5px;
592
+ line-height: 1.35;
593
+ color: var(--sf-muted);
594
+ }
595
+
596
+ /* Empty-search state */
597
+ .sf-addempty {
598
+ display: flex;
599
+ flex-direction: column;
600
+ align-items: center;
601
+ justify-content: center;
602
+ gap: 9px;
603
+ padding: 64px 20px;
604
+ text-align: center;
605
+ color: var(--sf-muted);
606
+ }
607
+ .sf-addempty-icon {
608
+ display: flex;
609
+ color: var(--sf-field-placeholder);
610
+ }
611
+ .sf-addempty-icon svg {
612
+ width: 30px;
613
+ height: 30px;
614
+ }
615
+ .sf-addempty-title {
616
+ font-size: 14px;
617
+ font-weight: 600;
618
+ color: var(--sf-text);
619
+ }
620
+ .sf-addempty-sub {
621
+ font-size: 12.5px;
622
+ }
623
+
624
+ /* ── Center: device-framed preview (wraps Puck.Preview) ──────────────── */
625
+ .sf-frame--tablet,
626
+ .sf-frame--mobile {
627
+ /* device frames don't stretch to full width but still fill height */
628
+ align-self: stretch;
629
+ }
630
+ /* The device's edge is an OUTLINE drawn inside the frame, not a border: the
631
+ frame is border-box, so a 1px border sat inside the 390px width and the page
632
+ was laid out at 388 (800 → 798 on tablet) — card `[FIX] Editor Canvas Runs
633
+ The Live Page Head`. An outline takes no width, so the page gets the whole
634
+ device (DEVICE_W in StorefrontEditorChrome); painted last, it sits over the
635
+ page's outermost pixel. No radius of their own: `.sf-frame` above is square
636
+ and these follow it, so the website's corners are never clipped. */
637
+ .sf-frame--tablet {
638
+ width: 800px;
639
+ outline: 1px solid var(--sf-device-border);
640
+ outline-offset: -1px;
641
+ }
642
+ .sf-frame--mobile {
643
+ width: 390px;
644
+ outline: 1px solid var(--sf-device-border);
645
+ outline-offset: -1px;
646
+ }
647
+
648
+ /* The Cart / Checkout surfaces are previewed in-canvas by rendering the real
649
+ components inside the Puck preview iframe (same-document, no nested `<iframe
650
+ src>`) — see components/EditorPreviewSurface. No editor-side frame CSS is
651
+ needed; the ribbon is styled inline in that component. */
652
+
653
+ /* ── One dashed outline on hover, not a field of them (card 66395) ─────────
654
+ The owner, 2026-09-01: hovering a section shows too many dashed lines. He
655
+ wants to find the hover target directly, and ONE is enough.
656
+
657
+ He was right, and the hover outline was never the cause. Puck draws FIVE
658
+ dashed outlines and four of them are STATES -- they paint only while a
659
+ merchant is hovering, selecting or dragging. The fifth has no state in its
660
+ selector at all:
661
+
662
+ ._DropZone_<hash>:empty { background: <azure tint>; outline: 2px dashed }
663
+
664
+ `:empty` is true whenever a slot holds nothing, so that box is painted the
665
+ whole time. Measured in the real editor on the `sharpen` homepage: FOUR of
666
+ those boxes standing with nothing hovered, three of them stacked inside the
667
+ hero. Hovering added the fifth, and his eye had to find it among the four
668
+ that were already there.
669
+
670
+ So the standing one goes and the four states stay. Puck set
671
+ `[data-puck-dragging]` on `[data-puck-entry]` for the length of a drag, so
672
+ scoping to `:not([data-puck-dragging])` left every drag affordance exactly
673
+ as Puck shipped it -- a merchant still sees where a block can land, which is
674
+ the one thing that must not be traded away for this. (The engine sets no
675
+ such attribute and draws its drop targets over the frame, not in it, so the
676
+ scope is gone; the note on the rule below says why that is safe.)
677
+
678
+ ONLY the outline. Puck's rule paints a background tint as well, and that is
679
+ left alone: an empty slot stays a visible block, so nothing becomes
680
+ unfindable -- it just stops drawing a line round itself.
681
+
682
+ SPECIFICITY, NOT `!important`. Ours was (0,4,0) against Puck's (0,2,0), and
683
+ `storefront-editor.css` was imported after `puck.css` at every call site so
684
+ source order settled any tie (it is imported after the engine's
685
+ `efficient-easel/easel.css` now; guard §9). An `!important` here would also beat
686
+ `._DropZone--isDestination_`, which IS `!important`, and blind the merchant
687
+ mid-drag.
688
+
689
+ `[class*=]`, NOT the literal class. Puck's CSS-module names carry a build
690
+ hash (`_DropZone_1i2sv_1`); a literal one would stop matching on the next
691
+ Puck bump with no error at all, and the field of outlines would come back
692
+ silently. `scripts/test-one-hover-outline.tsx` asserts all of this, and
693
+ asserts Puck still draws the rule we are overriding -- if it ever stops,
694
+ this becomes dead code and the guard turns that into a red rather than
695
+ leaving something that looks load-bearing.
696
+
697
+ ── An empty slot still ANSWERS when you point at it (card 66395.b) ────────
698
+
699
+ The paragraph above is right about the standing box and wrong about what
700
+ else it took. `:empty` does not stop being true while a slot is HOVERED or
701
+ SELECTED -- it only says the slot holds nothing -- and this selector is
702
+ LONGER than the Puck rules that paint those two states:
703
+
704
+ ours [data-puck-entry]:not([data-puck-dragging]) …:empty (0,4,0)
705
+ Puck --hoveringOverArea_:not(--isRootZone_) (0,2,0)
706
+ Puck --isAreaSelected_ (0,1,0)
707
+
708
+ So on an empty slot this rule won everywhere instead of only at rest.
709
+ Measured in the real editor on the `sharpen` homepage, merged main
710
+ 8603ffd82: all four empty zones read `outline: none 0px` while hovered with
711
+ `--hoveringOverArea_` set, and two of them read the same while
712
+ `--isAreaSelected_` was set.
713
+
714
+ That matters more than a missing line. Hovering an empty slot IS the
715
+ deliberate act of pointing at it to put the first block in, so the merchant
716
+ got nothing at the one moment the affordance was being asked for. The
717
+ background tint the paragraph above relies on is true while nothing is
718
+ happening -- and something IS happening here.
719
+
720
+ THE TWO EXCLUSIONS ARE PUCK'S OWN CONDITIONS, NEGATED, NOT NEW ONES. Puck
721
+ paints a non-drag dashed outline on a zone in exactly two cases:
722
+
723
+ ._DropZone--isAreaSelected_
724
+ ._DropZone--hoveringOverArea_:not(._DropZone--isRootZone_)
725
+
726
+ and this rule now stands aside for each, character for character. §6 of
727
+ the guard BUILDS those two exclusions out of Puck's stylesheet and fails if
728
+ ours do not match, so a sixth state upstream becomes a red on the day Puck
729
+ ships it rather than a silent hole like this one.
730
+
731
+ ⚠️ WHY THE HOVER EXCLUSION KEEPS ITS `:not(--isRootZone_)`, AND MUST. Puck
732
+ computes `hoveringOverArea = inNextDeepestArea || isRootZone`, so THE ROOT
733
+ ZONE CARRIES IT AT ALL TIMES -- that is why Puck's own rule qualifies it.
734
+ A bare `:not(--hoveringOverArea_)` here would therefore stop neutralising
735
+ the root zone, and an empty page would get its standing dashed box back:
736
+ half of the defect this rule exists to remove, restored quietly by a fix
737
+ for the other half. Nesting the qualifier keeps that box gone; measured on
738
+ the board, `empty · ROOT ZONE at rest` still reads `none 0px`.
739
+
740
+ ONE RULE, NOT TWO. `:not(A:not(B))` says "not (hovering AND not root)" in
741
+ one compound, so the root zone needs no second rule fighting this one over
742
+ the same outline. Specificity rises to (0,7,0), which still loses to
743
+ `--isDestination_` because that is `!important` and this is not -- the drag
744
+ affordance does not move in either direction, and the three DRAGGING rows
745
+ on the board read `dashed 2px` before and after.
746
+
747
+ Whether any of this is really PAINTED is not a question CSS text can
748
+ answer. `scripts/probe-empty-slot-outline.mjs` rendered these states in a
749
+ browser over Puck's classes; it went with Puck (blueprint 6.3), so measure
750
+ the engine's canvas in the live editor. */
751
+ /* Since blueprint 5.5 the lists are efficient-easel's `.es-zone`, and the
752
+ states this section answers are its classes: `.is-area-selected` (Puck's
753
+ `--isAreaSelected_`), `.is-hovering-area` (`--hoveringOverArea_`) and
754
+ `.is-root` (`--isRootZone_`). The reasoning above is unchanged; only the
755
+ names moved, from Puck's hashed modules to classes this repo can spell.
756
+
757
+ ⚠️ NO DRAG SCOPE ANY MORE, AND A DRAG STILL SHOWS WHERE A BLOCK CAN LAND.
758
+ This rule and A/B below were scoped to `:not([data-puck-dragging])` so a
759
+ drag kept Puck's in-frame drop targets (`--isDestination_`). Nothing has
760
+ set that attribute since 5.5 — the engine has no such state — so the
761
+ `:not()` matched every time and only added specificity: dead code that
762
+ looked load-bearing. The engine draws a drag's targets in the HOST page,
763
+ over the frame: the drop line, and on an empty slot the dashed
764
+ `.es-canvas-empty` "+ Add block" cell (efficient-easel/drag.css), sized
765
+ to the zone, at rest and mid-drag alike. Measured 2026-10-03 in
766
+ /edit/preview-newpage with a drag from the Add panel: the cell stays over
767
+ the empty slot for the whole drag, so the zone's own outline under it is
768
+ not what the merchant sees during one. Every zone state (rest, selected,
769
+ hovered, root; empty and filled) computed the same outline before and
770
+ after the scope went. Without it this rule is (0,6,0) against the engine's
771
+ `.es-zone:empty` (0,2,0). */
772
+ [data-puck-entry]
773
+ .es-zone:empty:not(.is-area-selected):not(.is-hovering-area:not(.is-root)) {
774
+ outline: none;
775
+ }
776
+
777
+ /* ── No dashed lines while pointing, and ONE line, not three (card 66517) ───
778
+ ⚠️ THIS CARD'S MERGED COMMITS ON `main` SAY 66511, AND THAT IS NOT A TYPO.
779
+ Two cards were allocated 66511 on 2026-09-02: this one, and the `ground` glyph
780
+ card whose number is baked into a published `efficient-easel` icon entry and
781
+ named in this repo's released CHANGELOG. That one keeps the number, because
782
+ its copies cannot be recalled; this one moved, because comments can. The
783
+ squash subjects on `main` still read `66511 · …` and are deliberately NOT
784
+ rewritten — a merged subject is a record of what was done on the day, not a
785
+ pointer, and rewriting `main`'s history for a number costs more than the
786
+ confusion it saves. Follow 66517; `components/ThemeCard.tsx` is the other.
787
+ The owner, 2026-09-02, with a screenshot of the sharpen homepage: pointing
788
+ at the hero heading draws three nested dashed rectangles, and he does not
789
+ want dashed lines.
790
+
791
+ THIS IS THE SECOND TIME HE HAS RAISED IT, and card 66395 above did not miss.
792
+ It removed a box that was painted the whole time, on every empty slot,
793
+ whether or not anything was happening — a real defect, and it is gone. What
794
+ he is pointing at now is the set that is SUPPOSED to appear. Three rules
795
+ fire at once and each is correct on its own terms:
796
+
797
+ the zone whose owner you CLICKED --isAreaSelected_
798
+ the deepest zone you are INSIDE --hoveringOverArea_:not(--isRootZone_)
799
+ the block you are POINTING AT [data-puck-overlay-portal]:hover
800
+
801
+ MEASURED, not inferred, by scripts/probe-hover-outline-count.mjs (retired
802
+ with Puck in blueprint 6.3) against both stylesheets in the app's own import
803
+ order, with a real pointer:
804
+
805
+ pointing at a block 2 dashed boxes
806
+ pointing at a block you have clicked 3 dashed boxes <- his picture
807
+
808
+ ⚠️ THE ROOT ZONE IS THE THIRD ONE, AND IT SURPRISES PEOPLE. Puck's
809
+ `isAreaSelected` rule carries no `:not(--isRootZone_)`, unlike its hover
810
+ rule, so selecting a top-level section outlines the whole page as well as
811
+ the section. That is why the count goes 2 -> 3 on a click rather than
812
+ staying at 2.
813
+
814
+ WHAT THIS DOES. One line, on the thing you are pointing at, and it is
815
+ SOLID. The zones keep their background tint and lose their line: the tint
816
+ already says which slot you are in, and it says it without drawing a
817
+ rectangle round a rectangle.
818
+
819
+ ⚠️ AN EMPTY SLOT STILL HAS TO ANSWER. Card 66395.b exists because the first
820
+ version of the rule above took that away — hovering an empty slot IS the
821
+ deliberate act of pointing at it to put the first block in, and it got
822
+ nothing at the one moment the affordance was being asked for. An empty zone
823
+ has no block in it and therefore no overlay to carry the line, so rule B
824
+ below gives it one of its own. Solid, like everything else here.
825
+
826
+ ⚠️ A DRAG IS NOT TOUCHED, AND MUST NOT BE. Under Puck every rule here sat
827
+ inside `:not([data-puck-dragging])`, so `--isDestination_` and
828
+ `--isEnabled_` shipped exactly as Puck wrote them. Dashed means something
829
+ during a drag — it is where a block can land — and that is the one
830
+ affordance card 66395 said must never be traded away for this. It still
831
+ holds, by other means: the engine draws a drag's targets over the frame
832
+ (the drop line, the `.es-canvas-empty` cell), where nothing here reaches,
833
+ so the scope went with Puck — see the note on the empty-slot rule above.
834
+
835
+ SPECIFICITY, NOT `!important`, the same as the rule above. A is (0,4,0)
836
+ against the engine's (0,3,0) state rules; its first selector carries
837
+ `:not(.is-root)` for that, in place of the old drag scope — without either
838
+ it would only TIE `.es-zone.is-area-selected:not(.is-root)` and win on
839
+ source order. Nothing is lost on the root: the engine never outlines a
840
+ selected root list, and an EMPTY selected root still gets B. B is (0,5,0)
841
+ so it wins over A on an empty zone. C has no upstream rule left to answer
842
+ (Puck's overlay-portal sheet went in 5.5). Ours is imported after the
843
+ engine's sheet at every call site all the same (guard §9).
844
+
845
+ `[class*=]`, NOT the literal class names — Puck's CSS-module names carry a
846
+ build hash, and a literal one would stop matching on the next Puck bump with
847
+ no error at all, silently putting all three boxes back. */
848
+
849
+ /* A · a zone draws no line while you point at it or after you click it. */
850
+ [data-puck-entry] .es-zone.is-area-selected:not(.is-root),
851
+ [data-puck-entry] .es-zone.is-hovering-area:not(.is-root) {
852
+ outline: none;
853
+ }
854
+
855
+ /* ⚠️ EVERY COLOUR BELOW CARRIES ITS OWN FALLBACK, AND THAT IS NOT DECORATION.
856
+ These rules paint inside the canvas FRAME, whose document has no `.es-editor`
857
+ root, so no `--es-*` token is defined there (efficient-easel/easel.css
858
+ falls back the same way). Until blueprint 5.5 they leaned on Puck's
859
+ `puck.css`, which declared its `--puck-color-azure-*` ramp on the frame's
860
+ `:root` and drew the pointed-at block's colour and offset itself. 5.5 took
861
+ that sheet off the canvas: a bare `var(--puck-color-azure-08)` then resolved
862
+ to nothing, and an `outline` with an unresolvable colour is no outline at all
863
+ — the empty slot stopped answering when pointed at, silently. The colours are
864
+ Puck's ramp as the shell re-tints it (`--es-accent-line` = azure-08,
865
+ `--es-accent-tint-strong` = azure-09). */
866
+
867
+ /* B · …except an EMPTY one, which has no block to carry the line for it. */
868
+ [data-puck-entry]
869
+ .es-zone:empty:is(.is-area-selected, .is-hovering-area:not(.is-root)) {
870
+ outline: 1px solid var(--es-accent-line, #bfdbfe);
871
+ }
872
+
873
+ /* C · the block you are pointing at: one line, solid, 1px, 2px off the words.
874
+ Puck's own overlay-portal sheet drew it 2px dashed and this file narrowed it;
875
+ since that sheet left with 5.5 the whole line is declared here. */
876
+ [data-puck-overlay-portal]:hover {
877
+ outline: 1px solid var(--es-accent-tint-strong, #dbeafe);
878
+ outline-offset: 2px;
879
+ }
880
+
881
+ /* …and the rest of what Puck's overlay-portal sheet (lib/overlay-portal/
882
+ styles.css) gave a text-part anchor, carried here for the same reason: the
883
+ anchor takes the pointer, and draws a ring while the caret is in it. The
884
+ OPEN part's own hairline (PART_PICK_CSS, (0,3,0)) still outranks the ring
885
+ (0,2,0) — one line, card 11103. */
886
+ [data-puck-overlay-portal],
887
+ [data-puck-overlay-portal] * {
888
+ pointer-events: auto !important;
889
+ }
890
+ [data-puck-overlay-portal]:focus-within {
891
+ outline: 2px solid var(--es-accent-line, #bfdbfe);
892
+ outline-offset: 2px;
893
+ }
894
+
895
+ /* D · …and ONLY the innermost one.
896
+ ⚠️ FOUND IN THE REAL EDITOR, NOT ON THE BOARD, AND IT IS THE HALF THE BOARD
897
+ COULD NOT SEE. Puck nests an overlay per component, and `:hover` matches
898
+ every ancestor in that chain — so pointing at the heading of a section put
899
+ FOUR overlays in `:hover` at once. Measured at /edit/preview-footer with a
900
+ real pointer: 4 hovered overlays, 4 painted lines. Rules A-C removed every
901
+ dashed line and still left him four thin ones.
902
+
903
+ The board modelled ONE overlay because that is what a nesting sketch needs;
904
+ the editor stacks them because that is what a component tree does. Same
905
+ family as measuring a component outside the container it ships in — the
906
+ experiment was honest and the population was wrong.
907
+
908
+ `:has()` reads "an overlay that contains another hovered overlay stands
909
+ aside", which leaves exactly the deepest one — the thing under the cursor,
910
+ which is the one you are actually reaching for. Re-measured with this rule
911
+ live in the same editor: 4 hovered, 1 painted, 225x46, the heading itself.
912
+
913
+ ⚠️ SETTLED BY THE OWNER, 2026-09-02: keep the innermost. He was shown the
914
+ alternative and the reason the choice is not obvious — on a heading the
915
+ innermost box hugs the text, which is right, but on the Subscribe button the
916
+ innermost is the button's LABEL (63x16) rather than the button (97x38). He
917
+ picked innermost anyway. DO NOT re-open this as "the button's box looks
918
+ small": that is the known cost of the rule he chose.
919
+
920
+ ⚠️ AND THIS ONLY HIDES THE LINE. The duplicate overlays are still in the DOM.
921
+ Measured on /edit/preview-footer: TWELVE overlays carrying only FOUR distinct
922
+ `data-sf-part` ids, and all EIGHT nested pairs share their ancestor's id — so
923
+ the extra boxes were never separate things to select and never had settings
924
+ of their own. Marking one element per part is a separate card touching
925
+ several components; it is not this one. */
926
+ [data-puck-overlay-portal]:hover:has([data-puck-overlay-portal]:hover) {
927
+ outline: none;
928
+ }
929
+
930
+ /* ── Canvas "Add section" seams (overlay between preview sections) ──────────
931
+ Absolutely positioned over .sf-preview-host (the iframe's box), one band per
932
+ seam between top-level sections, placed at the boundary Y measured from the
933
+ live iframe. The container ignores pointer events so the canvas stays
934
+ interactive; only the slim bands are clickable. On hover a band shows an accent
935
+ insertion line + a dark "Add section" pill; clicking opens the picker targeted
936
+ at that seam. It sits inside the scaled device frame, so it tracks the zoom. */
937
+
938
+ /* ── Right panel: inspector (wraps Puck.Fields) ──────────────────────── */
939
+ /* The head — chip, the panel's name as its h2 (card 66127), the kind line and
940
+ the Duplicate / Copy-to-page / Remove buttons — is editor-shell's
941
+ `.es-insp-head` (efficient-easel/chrome.css), lifted from this sheet (redesign 2a). */
942
+ /* The settings panel's scrolling body — and the settings TAB STRIP is inside it,
943
+ not above it (`PanelTabs` in lib/puck/controls.tsx renders the shell's
944
+ `SettingsTabs` among the fields). So the strip's width is this box's content
945
+ width, and `.sf-scroll`'s scrollbar is 11px of SPACE, not an overlay: a bucket
946
+ whose fields overflow used to take 11px off the strip.
947
+
948
+ `scrollbar-gutter: stable` is what stops that, and it is a fix rather than a
949
+ polish because the strip has three tiers (card 8810) that turn on its width —
950
+ glyphs replace the words under 270px, and the active tab's word goes too under
951
+ 240px. A tier line is a cliff, so a saved panel width whose two strip widths
952
+ straddle one changes the TABS as a scrollbar comes and goes: the merchant
953
+ clicks Layout, its fields overflow, and the four bucket words they were just
954
+ reading become four glyphs. Two bands did this — saved 298-308 and 268-278 —
955
+ and `scripts/test-panel-strip-width-stable.ts` sweeps the whole resizable band
956
+ rather than trusting those two numbers.
957
+
958
+ AND THE 320 DEFAULT DID NOT ALREADY FIX IT, which is the part that reads like
959
+ it should. Card 8810 took `DEFAULT_PANEL_W.right` to 320, whose strip widths
960
+ (292 and 281) clear both bands — but `StorefrontEditorChrome.tsx` seeds the
961
+ width from `localStorage` under `PANEL_WIDTHS_KEY` and falls back to the
962
+ default ONLY when nothing is stored. A merchant who has ever dragged this
963
+ panel keeps their width permanently, and the old default was 308, sitting at
964
+ the top of the first band. So the merchants left inside it are the ones who
965
+ have used the editor most, and no change to a default can reach them.
966
+
967
+ The cost is 11px: a panel that is not scrolling is now as narrow as one that
968
+ is, never the reverse — at 320 the body is 281 in both states instead of 292
969
+ and 281. Nothing gets wider. Accepted (Lewis, from the mockup) as the price of
970
+ a strip that never jumps.
971
+
972
+ `stable` and not `stable both-edges`: reserve the gutter on the scrollbar's
973
+ own side, so the fields keep the padding they have on the other. */
974
+ .sf-insp-body {
975
+ flex: 1;
976
+ overflow-y: auto;
977
+ scrollbar-gutter: stable;
978
+ /* THE SIDE PADDING, ONCE. It was the literal `14px` below and a second
979
+ literal `8px` in the container query further down, and the module rule this
980
+ card adds needed a THIRD copy to cancel it — which is exactly how that rule
981
+ came to hang 6px OUTSIDE the panel at every width under 280 (measured: gap
982
+ -6.0px at 260 and at 220, against 0.0 at the default 320). One name, read
983
+ by the padding, by the narrow override, and by anything that undoes it. */
984
+ --sf-insp-pad-x: 14px;
985
+ padding: 4px var(--sf-insp-pad-x) 18px;
986
+
987
+ /* THE DROPDOWN ARROW, WRITTEN ONCE — card 66399.c.
988
+ Read by `.sf-insp-body select` at rest AND by the hover rule beside it. The
989
+ hover rule HAS to restate the image (overriding without `!important`
990
+ replaces only the properties you name, and Puck's hover repaints the arrow
991
+ darker), so without this token the glyph would be spelled out twice —
992
+ putting back the second copy card 66399 had just removed, and leaving the
993
+ two to drift apart on the next Puck bump.
994
+
995
+ ⚠️ NOT AN `--es-*` ALIAS, and deliberately not one. Every other `--sf-*`
996
+ above is a design value we choose, so it belongs in the shell. This is
997
+ PUCK'S OWN GLYPH, matched to the package polygon for polygon so the six
998
+ rows Puck drew and the 55 the panel drew could not look different. Since
999
+ Puck left (blueprint 6.3) it is the panel's one arrow, declared here once;
1000
+ `scripts/test-panel-one-arrow.tsx` §4 fails if a second copy appears or a
1001
+ select resolves to anything else. */
1002
+ --sf-select-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%23c3c3c3'><polygon points='0,0 100,0 50,50'/></svg>");
1003
+ }
1004
+
1005
+ /* …AND THE 14 IS A DIFFERENT COST AT EACH END OF A PANEL THE MERCHANT DRAGS
1006
+ (card 66212). One number, held at every width from 220 to 560:
1007
+
1008
+ 320px default 308 client (1px seam + 11px scrollbar) − 28 = 280 content
1009
+ 220px minimum 208 client − 28 = 180 content
1010
+
1011
+ — 8.8% of the body at the default and 13.4% at the minimum, spent on the same
1012
+ gap. Lewis reported it on 2026-08-29 with a screenshot of the right panel at
1013
+ its minimum width.
1014
+
1015
+ WHY 280, WHICH IS NOT A ROUND NUMBER AND NOT AN OPINION. All 88 registered
1016
+ sections were rendered as real inspector panels — the live Puck config's own
1017
+ fields, each field's own `render()`, over this sheet and the shell's two — and
1018
+ swept across the resizable band in Chromium, counting elements whose
1019
+ `scrollWidth` exceeds their `clientWidth`:
1020
+
1021
+ panel 281px content 241 0 fields overflow
1022
+ panel 280px content 240 1 field overflows ← the first one to go
1023
+ panel 240px content 200 29 fields overflow
1024
+ panel 220px content 180 337 fields overflow
1025
+
1026
+ 280 is the widest panel at which anything in the inspector stops fitting
1027
+ (ProductGrid's empty multi-value chip wants 204px and has 203px), so it is
1028
+ where the 12px is owed and not one pixel earlier. With 8px each side the first
1029
+ overflow moves down to a 268px panel, and at the 220px minimum the count falls
1030
+ 337 → 97.
1031
+
1032
+ `< 280px` AND NOT `<= 280px`, because the query is answered by the CONTAINER's
1033
+ content box and `.sf-panel` draws a 1px seam border (card 66161): a panel
1034
+ saved at 281 asks with 280 and keeps its 14, a panel saved at 280 asks with
1035
+ 279 and gets the 8. `scripts/test-panel-padding-narrows.ts` holds both ends.
1036
+
1037
+ ONLY THE SIDES MOVE. The 4px above and the 18px below are vertical room and
1038
+ this card is horizontal; `padding-inline` says so in the declaration rather
1039
+ than repeating the other two numbers where they could drift.
1040
+
1041
+ NOT A FONT SIZE, deliberately. The panel's sizes are already 12 / 11 / 10px
1042
+ and 10 has nothing to spare, and type is paid vertically as well — card 66206
1043
+ has just set the row rhythm to 16px, which shrinking type would hand straight
1044
+ back. The padding was the horizontal cost, so the padding is what moved. */
1045
+ @container (width < 280px) {
1046
+ .sf-insp-body {
1047
+ /* The TOKEN, not `padding-inline` — so everything that reads the padding
1048
+ narrows with it, instead of only the padding itself. */
1049
+ --sf-insp-pad-x: 8px;
1050
+ }
1051
+ }
1052
+
1053
+ /* "Duplicate to another page" dropdown — portalled to <body> and pinned under
1054
+ the inspector button (see DuplicateToPageMenu), so it's position:fixed and
1055
+ right-aligned. Shares the floating-menu language of .sf-blockmenu. */
1056
+ .sf-dupmenu-backdrop {
1057
+ position: fixed;
1058
+ inset: 0;
1059
+ z-index: 1000;
1060
+ }
1061
+ .sf-dupmenu {
1062
+ position: fixed;
1063
+ z-index: 1001;
1064
+ display: flex;
1065
+ flex-direction: column;
1066
+ gap: 1px;
1067
+ min-width: 200px;
1068
+ max-width: 280px;
1069
+ max-height: 60vh;
1070
+ overflow: auto;
1071
+ padding: 4px;
1072
+ border: 1px solid var(--sf-line);
1073
+ border-radius: 8px;
1074
+ background: var(--sf-panel);
1075
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
1076
+ }
1077
+ .sf-dupmenu-head {
1078
+ padding: 6px 9px 4px;
1079
+ font-size: 11px;
1080
+ font-weight: 600;
1081
+ letter-spacing: 0.02em;
1082
+ color: var(--sf-muted);
1083
+ }
1084
+ .sf-dupmenu-empty {
1085
+ padding: 8px 9px;
1086
+ font-size: 13px;
1087
+ color: var(--sf-muted);
1088
+ }
1089
+ .sf-dupmenu-item {
1090
+ display: flex;
1091
+ align-items: center;
1092
+ gap: 8px;
1093
+ width: 100%;
1094
+ padding: 7px 9px;
1095
+ border: none;
1096
+ background: transparent;
1097
+ border-radius: 6px;
1098
+ cursor: pointer;
1099
+ font-size: 13px;
1100
+ text-align: left;
1101
+ color: var(--sf-text);
1102
+ }
1103
+ .sf-dupmenu-item:hover:not(:disabled) {
1104
+ background: var(--sf-accent-tint);
1105
+ color: var(--sf-accent);
1106
+ }
1107
+ .sf-dupmenu-item:disabled {
1108
+ cursor: default;
1109
+ opacity: 0.6;
1110
+ }
1111
+ .sf-dupmenu-name {
1112
+ flex: 1;
1113
+ min-width: 0;
1114
+ overflow: hidden;
1115
+ text-overflow: ellipsis;
1116
+ white-space: nowrap;
1117
+ }
1118
+ /* Here the note is a word trailing a menu row ("draft", "Copying…"), not a
1119
+ block: no inset of its own, and it takes the ROW's leading rather than the
1120
+ note's, or a draft row would stand 4px taller than a published one and the
1121
+ menu would lose its even rhythm. It stays quiet against the page name beside
1122
+ it by COLOUR, which is the note's own and the whole reason one class can
1123
+ serve both places. Placement only; the note style itself is .sf-note. */
1124
+ .sf-dupmenu-item > .sf-note {
1125
+ padding: 0;
1126
+ flex: none;
1127
+ line-height: inherit;
1128
+ }
1129
+
1130
+ /* Floating-styled form controls for Puck's native fields */
1131
+ .sf-insp-body input[type="text"],
1132
+ .sf-insp-body input[type="number"],
1133
+ .sf-insp-body input:not([type]),
1134
+ .sf-insp-body textarea {
1135
+ width: 100%;
1136
+ border: 1px solid var(--sf-input-border);
1137
+ background: var(--sf-field-bg);
1138
+ border-radius: var(--sf-field-radius);
1139
+ padding: 8px 11px;
1140
+ font-size: 13px;
1141
+ color: var(--sf-field-text);
1142
+ font-family: var(--sf-font);
1143
+ }
1144
+ .sf-insp-body input::placeholder,
1145
+ .sf-insp-body textarea::placeholder {
1146
+ color: var(--sf-field-placeholder);
1147
+ opacity: 1; /* Firefox dims placeholders by default; keep our grey exact */
1148
+ }
1149
+ .sf-insp-body textarea {
1150
+ min-height: 74px;
1151
+ line-height: 1.45;
1152
+ resize: vertical;
1153
+ }
1154
+ .sf-insp-body input[type="text"]:focus,
1155
+ .sf-insp-body input:not([type]):focus,
1156
+ .sf-insp-body textarea:focus {
1157
+ outline: none;
1158
+ border-color: var(--sf-accent);
1159
+ box-shadow: 0 0 0 3px var(--sf-accent-ring);
1160
+ }
1161
+ /* A value the backend refused — the row says why in an `.sf-note--error` under
1162
+ it (Site settings ▸ Social links is the first user). Keyed on `aria-invalid`,
1163
+ not a class, so the red a sighted merchant sees and the "invalid" a screen
1164
+ reader announces are one attribute and cannot disagree. */
1165
+ .sf-insp-body input[aria-invalid="true"] {
1166
+ border-color: var(--sf-danger);
1167
+ }
1168
+ .sf-insp-body select {
1169
+ width: 100%;
1170
+ border: 1px solid var(--sf-input-border);
1171
+ /* ⚠️ LONGHAND, NEVER `background:` — card 66397. Puck draws this control's
1172
+ dropdown arrow as a 100x100 SVG polygon and then shrinks it with longhands
1173
+ (`background-size: 12px`, `background-repeat: no-repeat`). The shorthand
1174
+ that used to sit here reset every longhand it did not name, so on hover —
1175
+ where Puck re-declares only the IMAGE — the full-size tile repeated 2.48
1176
+ times across the 248px row and drew a dark bar with three triangles. That
1177
+ is what Lewis filed. It shipped in 486bf97fd and was never a regression.
1178
+
1179
+ Setting only the colour leaves Puck's own geometry alone, which is why this
1180
+ needs no override of the package's rules and no hashed Puck class name to
1181
+ rot on the next bump. `scripts/test-panel-one-arrow.tsx` §3 holds the
1182
+ invariant (moved there when Puck left): the arrow is drawn ONCE or not at
1183
+ all, never tiled, at rest or on hover.
1184
+
1185
+ 🎨 THE ARROW COMING BACK IS THE OWNER'S OWN RULING, not a side effect. It
1186
+ puts a visible arrow into the panel's six Puck selects, which have not had
1187
+ one since the editor shipped. He was shown the row as it is today, this,
1188
+ and an arrow-less alternative — three shots of the real panel at its own
1189
+ 320px — and on 2026-09-01 he chose this one: "the little arrow is fine."
1190
+ Do not quietly take it away again; it is a decision, with a date.
1191
+
1192
+ 🎯 AND IT IS THE SAME ARROW ON EVERY ROW — card 66399. That ruling put an
1193
+ arrow on the SIX selects Puck draws. The panel draws 55 more from its own
1194
+ markup (the `select()` helper's custom field, and the `<select>`s panel
1195
+ components write themselves), and Puck never styles those: nothing set
1196
+ `appearance`, so each one drew whatever arrow the merchant's own browser
1197
+ draws. Measured on origin/main at 320px and again at the 220px minimum, the
1198
+ Divider block's "Style" row and its "Spacing" row — three rows apart in one
1199
+ panel — resolved `appearance: auto` / no image against `appearance: none` /
1200
+ the 12px triangle. A merchant could ask why two rows look different and the
1201
+ only true answer was "because of which library declared them".
1202
+
1203
+ So the five declarations below repeat Puck's OWN geometry, exactly, onto
1204
+ every select the panel draws. Two things follow from that, and both are the
1205
+ point rather than a side effect:
1206
+
1207
+ • THE SIX DO NOT MOVE. Puck's `select._Input-input_…` is (0,1,1) and this
1208
+ rule is (0,1,1) too, so the two collide at equal weight and source order
1209
+ decides — but they now declare the SAME values, so it does not matter
1210
+ which wins. Nothing here has to out-specify the package, and no hashed
1211
+ Puck class is named where it would rot on the next bump.
1212
+ • NEITHER TRAP IS SPRUNG. `appearance: none` alone leaves a row with NO
1213
+ arrow, which looks exactly like a stylesheet that failed to load; the
1214
+ image alone leaves the browser's arrow beside ours, TWO on one row.
1215
+ They only work as a pair.
1216
+
1217
+ ⚠️ LONGHANDS, for card 66397's reason above — and this rule is now the one
1218
+ that would break if that were forgotten, because it is the rule that holds
1219
+ the image. `scripts/test-panel-one-arrow.tsx` resolves the whole cascade per
1220
+ markup shape and fails if any shape's arrow differs from any other's, and if
1221
+ this image ever stops matching the one Puck ships.
1222
+
1223
+ ✅ NOW UNIFIED, one state along — card 66399.c. Puck darkens its own arrow on
1224
+ HOVER at (0,4,1), and that rule can reach only its six. This card's rule
1225
+ below holds it off at (0,5,1) so all 61 rows answer the pointer the same
1226
+ way; `scripts/test-panel-one-hover.tsx` is the guard for it.
1227
+
1228
+ ⚠️ THAT NUMBER WAS WRONG HERE UNTIL 66399.c. It read (0,4,2), and so did the
1229
+ arrow guard's header and sh#1024's body. `:has(> select)` counts in the
1230
+ ELEMENT column, not the class one — and one column out is exactly the
1231
+ difference between three of our classes tying with Puck and losing to it. */
1232
+ background-color: var(--sf-field-bg);
1233
+ appearance: none;
1234
+ -webkit-appearance: none;
1235
+ background-image: var(--sf-select-arrow);
1236
+ background-size: 12px;
1237
+ background-repeat: no-repeat;
1238
+ background-position: calc(100% - 12px) calc(50% + 3px);
1239
+ border-radius: var(--sf-field-radius);
1240
+ padding: 8px 11px;
1241
+ font-size: 13px;
1242
+ color: var(--sf-field-text);
1243
+ cursor: pointer;
1244
+ }
1245
+
1246
+ /* ── EVERY DROPDOWN ANSWERS THE POINTER THE SAME WAY — card 66399.c ─────────
1247
+ Card 66399 gave all 61 panel dropdowns the same arrow AT REST. Under the
1248
+ pointer they still split: Puck repaints its own six — ground, arrow AND
1249
+ border — from a rule the other 55 have no equivalent of, so one row lights up
1250
+ and the row three above it does nothing.
1251
+
1252
+ 🎨 THIS IS THE OWNER'S PICK, WITH A DATE. Lewis was shown four treatments at
1253
+ the panel's own 320px and again at the 220px minimum on 2026-09-02 and chose
1254
+ this one: the ground goes to the panel's own `--sf-hover` and NOTHING ELSE
1255
+ MOVES. He was choosing against a darkened glyph and against a border that
1256
+ jumps 93 points — both of which exist nowhere else in this editor. Changing
1257
+ what this looks like is his call, not a later card's.
1258
+
1259
+ RULE ZERO, ONE GRAMMAR: `--sf-hover` is already what `.sf-row`, `.sf-chooser`,
1260
+ `.sf-rung`, `.sf-seg2 button` and the shell's own `.es-drill` do on hover. The
1261
+ dropdowns join that vocabulary instead of inventing a fifth one — which is
1262
+ also why nothing needs to change in `efficient-easel`: the shell already owns the
1263
+ VALUE (`--es-hover`), and this is the shop agreeing with it.
1264
+
1265
+ ⚠️ WHY FOUR CLASSES, AND WHY THAT IS NOT COSMETIC. Puck's hover rule is
1266
+ (0,4,1) — `._Input_`(class) + `:has(> select)`(ELEMENT: `:has()` takes its
1267
+ argument's specificity) + `:hover`(class) + `._Input-input_`(class) +
1268
+ `:not([disabled])`(attribute). Measured, three of our classes TIES it and then
1269
+ wins only because `storefront-editor.css` is imported after `puck.css` at
1270
+ every editor entry; move an import anywhere and the six rows silently revert
1271
+ to Puck's azure with nothing going red. The fourth class buys independence
1272
+ from load order. No `!important`, and no hashed Puck class name that would rot
1273
+ on the next bump.
1274
+
1275
+ ⚠️ ALL THREE DECLARATIONS ARE LOAD-BEARING. Overriding without `!important`
1276
+ replaces only the properties you NAME, so the arrow and the border are
1277
+ restated at their RESTING values purely to hold Puck's darker pair off. They
1278
+ are not a second opinion about how the row should look.
1279
+
1280
+ AND IT STANDS ASIDE EVERYWHERE ELSE. It needs `:hover`, so it cannot touch the
1281
+ row at rest; it is inside `.sf-insp-body`, so it cannot reach the storefront's
1282
+ own selects; and carrying no `!important` is what keeps Puck's drag paint
1283
+ (`[data-dnd-placeholder]`, which IS `!important`) out of its reach. Card 66395
1284
+ §6 is the precedent for getting that wrong — a shop rule that out-specified
1285
+ Puck the same way went on to win states it should have stood aside for, and
1286
+ every guard stayed green. `scripts/test-panel-one-hover.tsx` resolves the
1287
+ whole cascade per markup shape and asserts what must NOT move as well as what
1288
+ must. */
1289
+ .sf-editor .sf-body .sf-panel .sf-insp-body select:hover {
1290
+ background-color: var(--sf-hover);
1291
+ background-image: var(--sf-select-arrow);
1292
+ border-color: var(--sf-input-border);
1293
+ }
1294
+ .sf-insp-body input[type="range"] {
1295
+ accent-color: var(--sf-accent);
1296
+ }
1297
+
1298
+ /* The panel's one-line dropdown TRIGGER and its in-flow MENU box, worn by the
1299
+ section Theme row (SectionThemePicker) and the part style chooser. They were
1300
+ MultiSelectField's; that control and SearchSelectField are efficient-easel's
1301
+ FieldSelect now (card [MERGE] One Pop-up List For Editor And One For
1302
+ Shoppers), so their option rows and readout went with them. */
1303
+ .sf-multi-btn {
1304
+ width: 100%;
1305
+ display: flex;
1306
+ align-items: center;
1307
+ justify-content: space-between;
1308
+ gap: 8px;
1309
+ border: 1px solid var(--sf-input-border);
1310
+ background: var(--sf-field-bg);
1311
+ border-radius: var(--sf-field-radius);
1312
+ padding: 8px 11px;
1313
+ font-size: 13px;
1314
+ font-family: var(--sf-font);
1315
+ color: var(--sf-field-text);
1316
+ text-align: left;
1317
+ cursor: pointer;
1318
+ }
1319
+ .sf-multi-btn:focus-visible {
1320
+ outline: none;
1321
+ border-color: var(--sf-accent);
1322
+ box-shadow: 0 0 0 3px var(--sf-accent-ring);
1323
+ }
1324
+ .sf-multi-caret {
1325
+ font-size: 10px;
1326
+ color: var(--sf-text-secondary);
1327
+ flex: none;
1328
+ }
1329
+ .sf-multi-menu {
1330
+ margin: 6px 0 0;
1331
+ padding: 4px;
1332
+ border: 1px solid var(--sf-input-border);
1333
+ border-radius: var(--sf-field-radius);
1334
+ background: var(--sf-field-bg);
1335
+ display: flex;
1336
+ flex-direction: column;
1337
+ }
1338
+
1339
+ /* ── The section's Theme row (card 66313) ──────────────────────────────────
1340
+ A dropdown, not a swatch strip. The TRIGGER is `.sf-multi-btn` — the panel's
1341
+ existing dropdown trigger, worn rather than copied, because two triggers with
1342
+ different metrics in one panel is a difference nobody decided. What is new is
1343
+ the colour chip, the per-row Edit and the edit sheet, and only those take
1344
+ names here.
1345
+
1346
+ The menu is IN FLOW (`.sf-multi-menu`, inherited), never absolutely
1347
+ positioned: the inspector is its own scroll container, so a floating menu
1348
+ clips against it once the control sits near the bottom of a long section. */
1349
+ /* ── The closed trigger: the card, shut ────────────────────────────────────
1350
+ It no longer wears `.sf-multi-btn`. That class is the panel's ONE-LINE
1351
+ dropdown trigger — a 13px row with 8px of padding — and the reason it was
1352
+ worn here (two triggers with different metrics is a difference nobody
1353
+ decided) now points the other way: this control draws a theme card, so the
1354
+ thing it must match is the card, and it matches it by sharing the rule above.
1355
+ The caret is still `.sf-multi-caret`, the panel's own arrow, so the part that
1356
+ says "this opens" is unchanged.
1357
+
1358
+ THE HEIGHT IS THE DESIGN, not an overflow. ~110px against the old ~36px row,
1359
+ in a 320px panel, and Lewis was told the cost and chose it on 2026-09-03:
1360
+ seeing the theme happens every time the tab is opened, editing it happens
1361
+ occasionally. A shorter third drawing to win the space back would be a third
1362
+ way of drawing one thing, which is worse than the space. */
1363
+ /* ⚠️ A FRAME, NOT A BUTTON, SINCE 2026-09-03 — it holds one. `Edit` on the
1364
+ closed control has to be clickable, and a <button> inside a <button> is
1365
+ invalid markup that no two browsers un-nest the same way. So the press moved
1366
+ inside: `.sf-theme-pick` (the shared card's own press) wraps the preview and
1367
+ owns the disclosure, and the foot below it holds the name, the Edit and the
1368
+ caret. See `ThemeCardTrigger`. */
1369
+ .sf-secttheme-btn {
1370
+ display: block;
1371
+ width: 100%;
1372
+ }
1373
+ /* THE PANEL'S BUTTON RING, unchanged — `editor-focus-ring-vocabulary`: a button
1374
+ answers focus with the accent border and the ring, exactly as `.sf-multi-btn`
1375
+ did when it was worn here, so keyboard focus looks the same as it did before
1376
+ the drawing changed. */
1377
+ /* THE PANEL'S BUTTON RING, on whichever child actually took focus. It is drawn
1378
+ on the FRAME (`:focus-within`) rather than on the press, because the press has
1379
+ no border of its own — the frame is what a merchant sees, so the frame is what
1380
+ must light up. Same three tokens `.sf-multi-btn` used when this control was
1381
+ one button, so keyboard focus looks exactly as it did. */
1382
+ .sf-secttheme-btn:focus-within {
1383
+ outline: none;
1384
+ border-color: var(--sf-accent);
1385
+ box-shadow: 0 0 0 3px var(--sf-accent-ring);
1386
+ }
1387
+ .sf-secttheme-btn .sf-theme-pick:focus-visible,
1388
+ .sf-secttheme-btn .sf-theme-edit:focus-visible {
1389
+ outline: none;
1390
+ }
1391
+ /* THE CARET'S OWN BOX. A pointer target only (`aria-hidden`, out of the tab
1392
+ order — see `ThemeCardTrigger`), so it takes no ring and no hover treatment
1393
+ of its own: the frame's hover already answers the pointer. Sized to the row
1394
+ rather than to the glyph, so the click area is the whole right end. */
1395
+ .sf-secttheme-caret {
1396
+ flex: none;
1397
+ border: 0;
1398
+ background: none;
1399
+ padding: 3px 2px 3px 6px;
1400
+ font: inherit;
1401
+ line-height: 1;
1402
+ color: var(--sf-text-secondary);
1403
+ cursor: pointer;
1404
+ }
1405
+ /* WHERE THE SELECTED CARD WENT. Panel-quiet — the muted grey every other
1406
+ readout uses, never a colour that would read as a warning. It exists because
1407
+ a theme's name is positional, so hiding one leaves 1 · 2 · 4 · 5 · 6 and a
1408
+ missing number reads as missing data. Not a row: there is nothing here to
1409
+ choose. */
1410
+ .sf-secttheme-oncard {
1411
+ margin: 0;
1412
+ padding: 5px 4px 7px;
1413
+ font-size: 11px;
1414
+ line-height: 1.4;
1415
+ color: var(--sf-text-secondary);
1416
+ }
1417
+ /* A site that stores no palette at all has nothing to draw. Sized to the
1418
+ preview it stands in for, and dashed rather than empty for the reason
1419
+ `.sf-secttheme-chip--none` is: a bare outline reads as an image that failed.
1420
+ `Follow the page` does NOT come here — it borrows the page's own palette. */
1421
+ .sf-secttheme-nopreview {
1422
+ display: block;
1423
+ height: 74px;
1424
+ background: var(--sf-field-bg);
1425
+ border-bottom: 1px dashed var(--sf-input-border);
1426
+ }
1427
+ .sf-secttheme-chip {
1428
+ width: 30px;
1429
+ height: 20px;
1430
+ flex: none;
1431
+ display: flex;
1432
+ overflow: hidden;
1433
+ border-radius: 4px;
1434
+ /* A hairline of the merchant's own ink would vanish on a theme whose paper is
1435
+ that ink. A translucent black keeps the chip's edge on every palette. */
1436
+ border: 1px solid rgba(0, 0, 0, 0.14);
1437
+ }
1438
+ .sf-secttheme-chip i {
1439
+ display: block;
1440
+ height: 100%;
1441
+ flex: 1 1 auto;
1442
+ }
1443
+ /* The accent is the SMALLER half. The paper is what a merchant recognises a
1444
+ theme by — it is most of what the page is — and the accent is the thing
1445
+ picked out on it, so the chip is drawn in the proportion the page is. */
1446
+ /* `i.` MATTERS. `.sf-secttheme-chip i` above is (0,1,1) and a bare class is
1447
+ (0,1,0), so without the element it loses to the rule it is meant to override
1448
+ and both halves take `flex: 1 1 auto` — an even 14/14 split. Measured in
1449
+ Chromium: 14/14 before, 16.8/11.2 after, which is the 60/40 the design asks
1450
+ for. No guard we own looks at a proportion, so this could only be found by
1451
+ drawing it and reading the boxes back. */
1452
+ .sf-secttheme-chip i.sf-secttheme-chip-accent {
1453
+ flex: 0 0 40%;
1454
+ }
1455
+ /* "Follow the page" has no palette of its own to show. A neutral block, not an
1456
+ empty box: an outline with nothing in it reads as an image that failed. */
1457
+ .sf-secttheme-chip--none {
1458
+ background: var(--sf-field-bg);
1459
+ border-style: dashed;
1460
+ }
1461
+ .sf-secttheme-name {
1462
+ overflow: hidden;
1463
+ text-overflow: ellipsis;
1464
+ white-space: nowrap;
1465
+ text-align: left;
1466
+ flex: 1 1 auto;
1467
+ min-width: 0;
1468
+ }
1469
+ /* ⚠️ THE MENU HOLDS CARDS NOW, NOT ONE-LINE ROWS (card 66503), so it is spaced
1470
+ like the Site styles theme list it shares its drawing with — `.sf-theme-list`
1471
+ is `gap: 8px` and this matches it, because two lists of the same cards sitting
1472
+ at two different rhythms is the same drift one level up from the one this card
1473
+ closed. The 1px gap below was right for rows 30px tall and wrong for cards. */
1474
+ .sf-secttheme-menu {
1475
+ display: flex;
1476
+ flex-direction: column;
1477
+ padding: 4px;
1478
+ gap: 8px;
1479
+ /* ── THE TINT SAYS "THESE ARE OPTIONS" ────────────────────────────────────
1480
+ Lewis, 2026-09-04: 「代表这些是选项.」 It is a job, not decoration, and the
1481
+ job exists because of a deliberate earlier decision: the closed control and
1482
+ these rows draw the SAME card (66503), so the only thing separating *what
1483
+ this section is on* from *what you could pick* was position.
1484
+
1485
+ ⚠️ THE FILL, NOT THE BORDER, and that is the whole of the choice. The cards
1486
+ are `--sf-panel` white and this container was `--sf-field-bg` white, so the
1487
+ container was invisible — which was the complaint. Filling it makes the 4px
1488
+ padding and the 8px gaps read as ONE REGION: the tint DRAWS the set instead
1489
+ of outlining it. A 1px line has to enclose a 707px stack to say the same
1490
+ thing, and at the 220px minimum it says it more quietly still. He was shown
1491
+ border-only, fill-only and both, at 320 and 220, and chose the fill.
1492
+
1493
+ ⚠️ AND IT KEEPS BLUE OFF THE PALETTES. Every card is a page in the
1494
+ merchant's OWN colours on its own white fill, so a tinted CONTAINER never
1495
+ puts blue against a palette. Same care `.sf-secttheme-chip` takes when it
1496
+ draws its hairline in translucent black rather than in a colour.
1497
+
1498
+ ⚠️ RULE ZERO — THIS IS AN EXCEPTION AND HERE IS ITS REASON. `.sf-multi-menu`
1499
+ is the shared dropdown container and exactly two controls wear it: this
1500
+ list, and `MultiSelectField` (components/MultiSelectField.tsx:144), a list
1501
+ of plain text options. That one is NOT tinted, and must not be "fixed" to
1502
+ match: a list of WORDS in a bordered box already looks like a menu, because
1503
+ that is what a menu is. A list of preview CARDS does not — each card is a
1504
+ miniature web page, so the region reads as CONTENT. The tint buys back the
1505
+ "these are choices" that the card drawing spent. He was offered the other
1506
+ answer (tint every dropdown) on the same board and did not take it.
1507
+
1508
+ No new hex: `--sf-accent-tint` is the shell's own (#eff6ff). A blue invented
1509
+ in a panel stylesheet is a second source for a decision the shell made. */
1510
+ background: var(--sf-accent-tint);
1511
+ }
1512
+ /* The follow row is still a ROW — it has no palette, so there is nothing for a
1513
+ card to draw. It keeps its own height rather than being stretched to a card's. */
1514
+ .sf-secttheme-menu > .sf-secttheme-item {
1515
+ flex: none;
1516
+ }
1517
+ .sf-secttheme-item {
1518
+ display: flex;
1519
+ align-items: center;
1520
+ gap: 9px;
1521
+ width: 100%;
1522
+ padding: 6px 7px;
1523
+ border: 0;
1524
+ background: transparent;
1525
+ border-radius: max(calc(var(--sf-field-radius) - 2px), 2px);
1526
+ font-size: 13px;
1527
+ font-family: var(--sf-font);
1528
+ color: var(--sf-field-text);
1529
+ text-align: left;
1530
+ cursor: pointer;
1531
+ }
1532
+ .sf-secttheme-item:hover {
1533
+ background: var(--sf-accent-ring);
1534
+ }
1535
+ /* ⚠️ ONLY THE FOLLOW ROW WEARS `.sf-secttheme-item` SINCE CARD 66503. Its
1536
+ theme siblings are `.sf-theme-card`, the Site styles card, so `is-active`
1537
+ here has one owner and one meaning again — the `:has(.sf-secttheme-pick…)`
1538
+ half went with the two-button row it described, along with `.sf-secttheme-pick`
1539
+ and `.sf-secttheme-edit`, which now have nothing to draw. */
1540
+ .sf-secttheme-item.is-active {
1541
+ background: var(--sf-accent-ring);
1542
+ font-weight: 600;
1543
+ }
1544
+ .sf-secttheme-item:focus-visible,
1545
+ .sf-secttheme-done:focus-visible {
1546
+ outline: 2px solid var(--sf-accent);
1547
+ outline-offset: -2px;
1548
+ border-radius: 4px;
1549
+ }
1550
+ .sf-secttheme-sep {
1551
+ height: 1px;
1552
+ margin: 3px 0;
1553
+ background: var(--sf-input-border);
1554
+ }
1555
+ /* ── The edit sheet ─────────────────────────────────────────────────────── */
1556
+ .sf-secttheme-sheet {
1557
+ margin-top: 6px;
1558
+ border: 1px solid var(--sf-input-border);
1559
+ border-radius: var(--sf-field-radius);
1560
+ overflow: hidden;
1561
+ }
1562
+ .sf-secttheme-sheet-head {
1563
+ display: flex;
1564
+ align-items: center;
1565
+ padding: 6px 9px;
1566
+ background: var(--sf-field-bg);
1567
+ border-bottom: 1px solid var(--sf-input-border);
1568
+ font-size: 12px;
1569
+ font-weight: 600;
1570
+ color: var(--sf-field-text);
1571
+ }
1572
+ .sf-secttheme-done {
1573
+ margin-left: auto;
1574
+ padding: 2px 4px;
1575
+ border: 0;
1576
+ background: transparent;
1577
+ color: var(--sf-text-secondary);
1578
+ font-size: 11px;
1579
+ font-family: var(--sf-font);
1580
+ cursor: pointer;
1581
+ }
1582
+ /* ⚠️ THE SHEET'S FIVE COLOUR ROWS ARE `ThemeColourRow` SINCE CARD 66503 — the
1583
+ shell's `SettingsField` wearing the shop's picker, the same row the Site
1584
+ styles card opens. So the row draws itself and the sheet only has to inset
1585
+ it, exactly as `.sf-theme-colours` does for the card. The local
1586
+ `.sf-secttheme-row` / `-hex` that used to draw a second, smaller colour row
1587
+ went with it, and with them the 11px hex that E6 would have had something to
1588
+ say about. */
1589
+ .sf-secttheme-rows {
1590
+ padding: 0 12px 12px;
1591
+ }
1592
+ .sf-secttheme-rows > .es-field:first-child {
1593
+ margin-top: var(--es-rhythm-group-first);
1594
+ }
1595
+ /* THE WARNING IS THE LAST THING IN THE SHEET AND IT IS NOT QUIET. A theme is a
1596
+ shared preset: editing it from one section repaints every section and page on
1597
+ it. Drawn in the panel's own warning ink rather than a new colour. */
1598
+ .sf-secttheme-warn {
1599
+ margin: 0;
1600
+ padding: 7px 9px;
1601
+ border-top: 1px solid var(--sf-input-border);
1602
+ background: var(--sf-accent-ring);
1603
+ font-size: 11px;
1604
+ line-height: 1.4;
1605
+ color: var(--sf-text-secondary);
1606
+ }
1607
+
1608
+ /* Collapsed-panel state hook (when Puck's UI flags hide a side) */
1609
+ .sf-panel--hidden {
1610
+ display: none;
1611
+ }
1612
+
1613
+ /* ── The icon rail: the shared editor-shell EaselRail (`.es-rail`) ───────────
1614
+ The rail markup + base styles live in the `efficient-easel` package (imported via
1615
+ `efficient-easel/rail.css`); the storefront no longer hand-rolls `.sf-rail`.
1616
+ editor-shell ships its own defaults, so we remap the themeable `--es-rail-*`
1617
+ custom properties onto the storefront's own `--sf-*` tokens, which is what
1618
+ keeps the shared rail reading as this editor's chrome rather than as the
1619
+ package's. (One mode only — the editor is deliberately light-only; see the
1620
+ note at the top of this file.)
1621
+ Scoped `.sf-editor .es-rail` (specificity 0,2,0) so it always beats rail.css's
1622
+ base `.es-rail` (0,1,0) whatever order the two stylesheets load in.
1623
+
1624
+ THREE TOKENS ARE DELIBERATELY NOT REMAPPED HERE, and each absence is load-
1625
+ bearing since card 66198 laid the rail into the top bar as a row:
1626
+
1627
+ --es-rail-w only the COLUMN reads it (`.es-rail { width:
1628
+ var(--es-rail-w) }`); the row is `width: auto` and sizes
1629
+ to its buttons, so a remap would be config nothing reads.
1630
+ --es-rail-bg the row variant re-points these to transparent / none / 0
1631
+ --es-rail-shadow ON PURPOSE: the bar already has its own surface, and a
1632
+ --es-rail-radius second white card floating inside it is the bug. This
1633
+ rule ties with `.es-rail.es-rail-row` on specificity
1634
+ (0,2,0 both), so setting them here would silently WIN
1635
+ and put that card back. The rail's surface is the bar's.
1636
+
1637
+ Everything left below is colour the row variant does not touch. */
1638
+
1639
+ /* ── Left panel: Pages view (Wix-style page list) ───────────────────────── */
1640
+ .sf-pages {
1641
+ display: flex;
1642
+ flex-direction: column;
1643
+ min-height: 0;
1644
+ height: 100%;
1645
+ }
1646
+ /* Site styles left panel — same column shell as .sf-pages; its body reuses the
1647
+ inspector's .sf-insp-body so the (right-inspector) field styling applies. */
1648
+ .sf-styles {
1649
+ display: flex;
1650
+ flex-direction: column;
1651
+ min-height: 0;
1652
+ height: 100%;
1653
+ }
1654
+
1655
+ /* The left panel's search field. It had a second wearer — the Add panel's own
1656
+ search (card 66400.c) — until that panel was deleted and the `+` was pointed
1657
+ at the one picker; the picker has its own `.sf-addsearch`, sized for a 772px
1658
+ dialog. So this is the Pages list's control alone again. */
1659
+ .sf-pages-search {
1660
+ flex: none;
1661
+ display: flex;
1662
+ align-items: center;
1663
+ gap: 7px;
1664
+ margin: 0 12px 8px;
1665
+ padding: 7px 10px;
1666
+ background: var(--sf-track-bg);
1667
+ border-radius: 8px;
1668
+ color: var(--sf-muted);
1669
+ }
1670
+ .sf-pages-search-input {
1671
+ flex: 1;
1672
+ min-width: 0;
1673
+ border: none;
1674
+ background: transparent;
1675
+ font-family: var(--sf-font);
1676
+ font-size: 13px;
1677
+ color: var(--sf-text);
1678
+ outline: none;
1679
+ }
1680
+ .sf-pages-search-input::placeholder {
1681
+ color: var(--sf-muted);
1682
+ }
1683
+ .sf-pages-list {
1684
+ flex: 1;
1685
+ min-height: 0;
1686
+ overflow-y: auto;
1687
+ padding: 0 8px 8px;
1688
+ }
1689
+ /* A note in this list is not the panel's body — it stands where a page row
1690
+ would, so it takes the ROW's inset and lines up with the group heads above
1691
+ it rather than with the panel edge. Placement only; the note style itself is
1692
+ .sf-note. */
1693
+ .sf-pages-list > .sf-note {
1694
+ padding: 10px 12px;
1695
+ }
1696
+ .sf-pages-list > .sf-note--error {
1697
+ margin: 4px 4px 0;
1698
+ }
1699
+ .sf-pgroup {
1700
+ margin-top: 2px;
1701
+ }
1702
+ .sf-pgroup-head {
1703
+ width: 100%;
1704
+ display: flex;
1705
+ align-items: center;
1706
+ gap: 7px;
1707
+ padding: 8px 8px 6px;
1708
+ border: none;
1709
+ background: transparent;
1710
+ cursor: pointer;
1711
+ color: var(--sf-muted);
1712
+ }
1713
+ .sf-pgroup-head:hover {
1714
+ color: var(--sf-text);
1715
+ }
1716
+ .sf-pgroup-label {
1717
+ flex: 1;
1718
+ text-align: left;
1719
+ font-size: 11px;
1720
+ font-weight: 700;
1721
+ letter-spacing: 0.4px;
1722
+ text-transform: uppercase;
1723
+ }
1724
+ .sf-pgroup-count {
1725
+ flex: none;
1726
+ font-size: 11px;
1727
+ font-weight: 600;
1728
+ color: var(--sf-muted);
1729
+ background: var(--sf-track-bg);
1730
+ border-radius: 999px;
1731
+ padding: 1px 7px;
1732
+ }
1733
+ .sf-pgroup-body {
1734
+ display: flex;
1735
+ flex-direction: column;
1736
+ }
1737
+ /* Page rows reuse .sf-row; anchors (Cart/Checkout) reset link chrome. */
1738
+ .sf-prow {
1739
+ text-decoration: none;
1740
+ color: inherit;
1741
+ }
1742
+ .sf-page-draft {
1743
+ flex: none;
1744
+ font-size: 10px;
1745
+ font-weight: 600;
1746
+ color: var(--sf-muted);
1747
+ text-transform: uppercase;
1748
+ letter-spacing: 0.3px;
1749
+ }
1750
+ /* Page-TYPE pills — mirror the admin Console's Pages list so a per-product PDP
1751
+ template (dynamic, blue) or an editable popup (lightbox, green) is as obvious
1752
+ in the editor's page list as it is in the Console. */
1753
+ .sf-badge-dynamic,
1754
+ .sf-badge-lightbox {
1755
+ flex: none;
1756
+ font-size: 9px;
1757
+ font-weight: 700;
1758
+ letter-spacing: 0.3px;
1759
+ text-transform: uppercase;
1760
+ padding: 2px 7px;
1761
+ border-radius: 999px;
1762
+ }
1763
+ .sf-badge-dynamic {
1764
+ color: var(--sf-accent-text);
1765
+ background: var(--sf-accent-tint);
1766
+ }
1767
+ .sf-badge-lightbox {
1768
+ color: #047857; /* emerald-700 — matches the Console's Lightbox pill */
1769
+ background: #ecfdf5; /* emerald-50 */
1770
+ }
1771
+ .sf-page-edit {
1772
+ flex: none;
1773
+ width: 24px;
1774
+ height: 24px;
1775
+ border: none;
1776
+ background: transparent;
1777
+ border-radius: 6px;
1778
+ color: var(--sf-muted);
1779
+ display: none;
1780
+ align-items: center;
1781
+ justify-content: center;
1782
+ cursor: pointer;
1783
+ }
1784
+ .sf-row:hover .sf-page-edit,
1785
+ .sf-row:focus-within .sf-page-edit {
1786
+ display: flex;
1787
+ }
1788
+ .sf-page-edit:hover {
1789
+ background: var(--sf-hover);
1790
+ color: var(--sf-text);
1791
+ }
1792
+ /* Row hover-delete (template pages — inline page management). */
1793
+ .sf-page-edit--danger:hover {
1794
+ background: var(--sf-danger-tint);
1795
+ color: var(--sf-danger);
1796
+ }
1797
+ .sf-page-edit--danger:disabled {
1798
+ opacity: 0.5;
1799
+ cursor: default;
1800
+ }
1801
+ /* "Preview" pill on Cart / Checkout — signals "opens the live page, view-only". */
1802
+ .sf-badge-preview {
1803
+ flex: none;
1804
+ font-size: 9px;
1805
+ font-weight: 700;
1806
+ letter-spacing: 0.3px;
1807
+ text-transform: uppercase;
1808
+ padding: 2px 7px;
1809
+ border-radius: 999px;
1810
+ color: var(--sf-warning);
1811
+ background: var(--sf-preview-tint);
1812
+ }
1813
+ .sf-pages-new {
1814
+ flex: none;
1815
+ margin: 6px 10px 10px;
1816
+ display: flex;
1817
+ align-items: center;
1818
+ justify-content: center;
1819
+ gap: 6px;
1820
+ padding: 9px;
1821
+ border: 1px dashed var(--sf-line);
1822
+ border-radius: var(--sf-row-radius);
1823
+ background: transparent;
1824
+ color: var(--sf-accent-text);
1825
+ font-family: var(--sf-font);
1826
+ font-size: 13px;
1827
+ font-weight: 600;
1828
+ cursor: pointer;
1829
+ }
1830
+ .sf-pages-new:hover {
1831
+ background: var(--sf-hover);
1832
+ }
1833
+
1834
+ /* ── Layers rows (redesign 2b) ──────────────────────────────────────────
1835
+ The Layers tree's rows are editor-shell's `TreeRow` (efficient-easel/tree.css):
1836
+ the name box with the eye and trash laid over its END (card 66199), the
1837
+ hover / focus-within reveal, a hidden row's permanent eye, the grip in the
1838
+ row's own left padding, the caret-or-spacer column (One Ladder), the drag and
1839
+ drop marks and the rename box — all lifted from this sheet. What stays below
1840
+ is `.sf-row-del`, which the standalone section editor's own rows still wear. */
1841
+ /* Per-row delete — a hover-revealed trash on section, slot-child and
1842
+ footer-block rows. Laid out inside `.sf-row-actions` above, invisible +
1843
+ non-interactive until the row is hovered or a control inside it is focused,
1844
+ so it stays out of the way of the visibility eye.
1845
+ Deleting is a normal dispatch, so Undo restores the row. */
1846
+ .sf-row-del {
1847
+ width: 22px;
1848
+ height: 22px;
1849
+ border: none;
1850
+ background: transparent;
1851
+ border-radius: 5px;
1852
+ cursor: pointer;
1853
+ color: var(--sf-danger);
1854
+ display: flex;
1855
+ align-items: center;
1856
+ justify-content: center;
1857
+ opacity: 0;
1858
+ pointer-events: none;
1859
+ flex: none;
1860
+ transition: opacity 0.12s, background 0.12s, color 0.12s;
1861
+ }
1862
+ .sf-row:hover .sf-row-del,
1863
+ .sf-row:focus-within .sf-row-del {
1864
+ opacity: 0.7;
1865
+ pointer-events: auto;
1866
+ }
1867
+ .sf-row-del:hover {
1868
+ opacity: 1;
1869
+ background: var(--sf-danger-tint);
1870
+ color: var(--sf-danger-strong);
1871
+ }
1872
+ .sf-panel-head-actions {
1873
+ display: flex;
1874
+ align-items: center;
1875
+ gap: 2px;
1876
+ }
1877
+
1878
+ /* The relocated heading-outline tool sits muted at the bottom of the tree. */
1879
+ .sf-tree-tool {
1880
+ margin-top: 14px;
1881
+ /* The left inset is the tree's 11px column, not `.sf-tree`'s padding — that
1882
+ is 0 on this side now so the bars can bleed. */
1883
+ padding: 10px 0 0 11px;
1884
+ }
1885
+
1886
+ /* `.sf-field-section` AND `.sf-field-group` WERE HERE, and they are gone on
1887
+ purpose (card 66300). They were the panel's two heading tiers — a 12px label
1888
+ over a 1.5px rule, and a 10.5px label over a 1px one — and the rules are what
1889
+ the owner objected to on 2026-08-31: "these lines, I still find them annoying
1890
+ to look at".
1891
+ Both tiers are drawn by `efficient-easel/panel` now: `SettingsGroupHead` (the
1892
+ filled pill) and `SettingsGroupSub` (the quiet tier, added in 0.25.0 for this).
1893
+ Nothing in this repo renders either class any more — the four panels that
1894
+ inlined `.sf-field-group` as a bare div (HeroText, Ticker, HeroMedia, Chat)
1895
+ call `GroupLabel` in the same card, because leaving them behind would have put
1896
+ the hairline back in four panels and given the editor two answers to "what does
1897
+ a group heading look like" — which is the fault the conversion is fixing.
1898
+ Deleted rather than left dead: a rule nobody renders is a rule the next person
1899
+ edits and watches do nothing.
1900
+ CARD 66300.a FINISHED THE JOB. The last selector that named `.sf-field-section`
1901
+ went with them: a `[class*="_PuckFields-field_"]:first-child` descendant that
1902
+ pulled the first header flush to the panel top. It had matched nothing since the
1903
+ rule above was deleted in this same pull request. The two comments that described
1904
+ the pair went with it, and so did the section header that had nothing left under
1905
+ it — the real "floating custom controls" rules live further up, at
1906
+ `Floating-styled form controls for Puck's native fields`.
1907
+ MEASURED BEFORE CUTTING, with controls in the same sweep, so the zero is a
1908
+ measurement and not a query that finds nothing: `.sf-field-section` and
1909
+ `.sf-field-group` return ZERO renders across app/, components/, lib/ AND the
1910
+ installed `efficient-easel` / `efficient-shell` bundles, while the same query returns
1911
+ `.sf-field-label` 45, `.sf-panel-title` 5, `.sf-insp-name` 1, `.es-group-head` 2
1912
+ and `.es-group-sub` 1. The shell controls are the ones that matter: they prove the
1913
+ sweep reaches into the package that draws the replacement markup, so a class
1914
+ rendered THERE could not have hidden from it. */
1915
+
1916
+ /* ── The settings TABS (card 8802; the Inspector's since blueprint 4.4) ───────
1917
+ The strip is `.es-tabs` from `efficient-easel/panel.css`, drawn by
1918
+ efficient-easel's `Inspector` (components/SiteFields) above its rows. Puck's
1919
+ flat list of hashed wrappers, which made `lib/puck/controls.tsx` mark each
1920
+ bucket and hide the others' rows with a DOM pass, is gone: a row on another
1921
+ tab is `hidden` by the Inspector itself, and a bucket header is a marker
1922
+ field that draws no row at all. What is left here is only the storefront's
1923
+ own look of the strip.
1924
+
1925
+ Editor-only, like every rule around it — never on the published page. */
1926
+
1927
+ /* AND IT IS PINNED TO THE TOP OF THE PANEL (card [FIX] Pin The Settings Tabs).
1928
+ Lewis, 2026-09-03, scrolling a Product Grid's settings: the four bucket tabs
1929
+ scrolled away with the rows, so a merchant three screens down had no way to
1930
+ change bucket without scrolling back up. The strip is a BOUNDARY, not a row —
1931
+ it should behave like the panel head it sits under.
1932
+
1933
+ `position: sticky` on the STRIP'S WRAPPER, and the two conditions it needs
1934
+ are both already true here, which is why this is three declarations and not a
1935
+ restructure:
1936
+
1937
+ the nearest scrolling ancestor is `.sf-insp-body` (`overflow-y: auto`
1938
+ above); the Inspector's own `.es-insp` / `.es-insp-rows` carry NO
1939
+ overflow, and `SectionTextSuppressProvider` / `FoldHeadingScope` are
1940
+ context only and draw no element — so nothing between the wrapper and the
1941
+ scroll box clips it. Checked, not assumed: a sticky that does not stick
1942
+ reports nothing at all.
1943
+
1944
+ THE BACKGROUND IS NOT DECORATION. A pinned element with no background lets
1945
+ the rows scroll through it, and `.es-tabs` paints none of its own — it draws
1946
+ only the rule along its bottom edge. `var(--sf-panel)` is the same token the
1947
+ panel behind it uses, so the strip stays the surface it is already sitting
1948
+ on rather than becoming a second one.
1949
+
1950
+ NOTHING HERE TOUCHES `.es-tabs` — the shell owns the strip's own looks, and
1951
+ `scripts/test-panel-tabs-single-home.ts` fails if this repo reaches into it.
1952
+ What is styled is the strip's place in this panel (`.es-insp-tabs`, the
1953
+ Inspector's own wrapper around it); that place is this repo's to set. */
1954
+ .sf-insp-body .es-insp-tabs {
1955
+ padding: 0;
1956
+ position: sticky;
1957
+ /* Flush with the top of the scroll box, so the rows pass UNDER the strip
1958
+ rather than beside it. The body's own 4px of top padding is above this
1959
+ line and scrolls away with the first row, which is what makes the strip
1960
+ land against the panel head. */
1961
+ top: 0;
1962
+ /* Over the rows and over the fold headings, under the panel head — which is
1963
+ outside this scroll box entirely and cannot be overlapped. */
1964
+ z-index: 2;
1965
+ background: var(--sf-panel);
1966
+ /* …AND THE PAINT REACHES ABOVE IT. Lewis, 2026-09-03, with a screenshot: the
1967
+ strip pins, but a row's words show through a thin band above it.
1968
+
1969
+ MEASURED, not guessed. In a real `.sf-panel` scrolled 260px, the band
1970
+ between the scrollport's top edge and the pinned strip is exactly 4px —
1971
+ `.sf-insp-body`'s own `padding-top` — and `elementFromPoint` in the middle
1972
+ of it returns a row's `.sf-field-label`. The strip is doing its job; the
1973
+ 4px above it simply belongs to nobody, so the rows keep it.
1974
+
1975
+ A SHADOW AND NOT LAYOUT, because the gap is not the complaint: the spacing
1976
+ is right and only the transparency is wrong. `box-shadow` paints and
1977
+ occupies nothing, so the strip stays exactly where it is and every row
1978
+ below keeps its position — a `padding-top` here, or a negative `top`,
1979
+ would move the strip and change the spacing he asked to keep.
1980
+
1981
+ 16px AND NOT 4, deliberately. The scroll box clips its own overflow, so
1982
+ paint above the scrollport's top edge cannot escape into the panel head no
1983
+ matter how far it reaches — the value only has to be AT LEAST the body's
1984
+ padding. Pinning it to 4 would mean a future change to
1985
+ `.sf-insp-body`'s padding silently reopens this leak, and a leak that
1986
+ comes back looks like the fix was never made. */
1987
+ box-shadow: 0 -16px 0 0 var(--sf-panel);
1988
+ }
1989
+ /* The NARROW panel is the shell's problem, and the shell solves it. Nothing
1990
+ here styles `.es-tabs` — see scripts/test-panel-tabs-single-home.ts, which
1991
+ fails if anything does.
1992
+
1993
+ Why the upstream rule exists, since this is where the defect was found: the
1994
+ settings panel is draggable down to MIN_PANEL_W = 220px
1995
+ (components/panel-widths.ts) and four tabs need 267px, so at anything under
1996
+ ~270 the last tab — Layout — was clipped off the right edge and could not be
1997
+ reached at all. Measured: at 220px and at 260px the fourth tab sat past the
1998
+ strip's right edge; wrapping runs the strip to two rows (37px → 75px) and
1999
+ every tab stays reachable, while the default 308px panel is untouched at one
2000
+ row.
2001
+
2002
+ `efficient-easel/src/panel/panel.css` now carries that as `flex-wrap: wrap` on
2003
+ `.es-tabs` itself, with `flex: none; white-space: nowrap` on `.es-tab` so a
2004
+ wrapped tab keeps the width its label needs. Both editors get it, which is
2005
+ the point — the storefront had been fixing this for itself while the campaign
2006
+ designer went without. Card 8802 held a local copy here under a comment
2007
+ claiming card 8802.d had already deleted it; 8802.d had done the upstream
2008
+ half only, so the copy outlived its own obituary until card 66130. */
2009
+ /* ── THE PANEL'S VERTICAL RHYTHM COMES FROM THE SHELL ──────────────────────
2010
+ Card [PLAN] Panel Spacing Rules. Lewis, 2026-09-03: the panel is too loose.
2011
+ Measured before anything was changed, in Chromium against these stylesheets
2012
+ and editor-shell 0.29.0, on a Product Grid's Media tab at the real 320px:
2013
+
2014
+ a group heading to its first setting 74px
2015
+ one setting to the next 50px
2016
+ the last setting to the next heading 74px
2017
+
2018
+ 36px OF EVERY ONE OF THOSE WAS NEVER A DECISION. Puck wraps each field in
2019
+ `._PuckFields-field_` and gives it `padding: 16px 16px 12px` plus an 8px
2020
+ `margin-top` between wrappers. That padding was the air around a RULED cell
2021
+ — Puck draws a `border-top` between fields. Card 66334 deleted every one of
2022
+ those lines, on the owner's word, and left the padding behind. It has been
2023
+ dead air stacked on top of the shell's rhythm ever since, and it is why a
2024
+ panel that reads as "too loose" measures as one nobody has loosened.
2025
+
2026
+ ⚠️ ZEROED, NOT TUNED — and since blueprint 4.4 there is nothing left to zero.
2027
+ The rows are efficient-easel's `.es-insp-row`, which carry no padding, no
2028
+ gap and no rule of their own, so every gap below comes from ONE place.
2029
+
2030
+ `--es-rhythm-*` ARE THE SHELL'S AND ARE NOT COPIED. They are declared on
2031
+ `.es-editor` in `efficient-easel/src/panel/panel.css`, floored on
2032
+ `--es-row-gap`, and the six values are the owner's own from 2026-08-31
2033
+ (card 66301, "too tight, open it up"). Reading them here is what stops this
2034
+ sheet from being a second source for numbers that sheet already owns — the
2035
+ fault card 66228 removed from the Layers tree and this leaves in the
2036
+ settings panel. Never re-state one as a literal beside the token.
2037
+
2038
+ ⛔ NO RAW VERTICAL PX BETWEEN TWO THINGS IN THIS PANEL. A control's own
2039
+ INNER padding is not a gap and is not covered. Enforced by
2040
+ `scripts/test-panel-spacing-tokens.ts`, which reads this file and fails on a
2041
+ raw length in a between-element margin — the allow-list there carries a
2042
+ written reason for each of the few that remain. */
2043
+ /* BETWEEN two settings. */
2044
+ .sf-field {
2045
+ margin-top: var(--es-rhythm-between);
2046
+ }
2047
+
2048
+ /* ── ONE SETTINGS MODULE ENDS AND THE NEXT BEGINS ──────────────────────────
2049
+ Owner ruling, Lewis, 2026-09-03. He asked for "a line after each settings
2050
+ module" because two modules in one panel read as one long list, then
2051
+ rejected the first drawing of it in his own words: it read like a header's
2052
+ rule with its options under it, not like a boundary between two things.
2053
+
2054
+ THAT IS NOT A DRAWING MISTAKE, and it is why this rule looks the way it
2055
+ does. A hairline INSET to the rows belongs to the content column, and so to
2056
+ the heading directly beneath it; an interface's commonest use of that shape
2057
+ is a header rule, so that is what the eye picks. Five versions were drawn at
2058
+ the panel's real 320px and he chose this one:
2059
+
2060
+ FULL BLEED — the rule runs to the panel's own edges, past the rows, so it
2061
+ reads as the panel's structure rather than as something belonging to the
2062
+ text under it. This is the ONLY difference from the version he rejected,
2063
+ and `scripts/test-panel-no-divider-lines.tsx` asserts it: without that
2064
+ check, insetting the line is a one-word edit that turns the allowed rule
2065
+ back into the rejected one with every test still green.
2066
+
2067
+ 30px OF AIR ON EACH SIDE — his number, picked on the real editor after
2068
+ looking at 28. Symmetric on purpose: the full bleed is what says which
2069
+ side the rule belongs to, so the air does not have to.
2070
+
2071
+ ⚠️ WRITTEN AS `field + field`, FOUR CLASSES, AND THAT IS NOT COSMETIC. Two
2072
+ rules further down are (0,3,0) — card 66334's `border-top: 0` and this
2073
+ card's own `margin-top: 0` — and a two-class rule loses to both SILENTLY:
2074
+ the line never paints and the panel looks untouched. Measured in a fixture
2075
+ against this stylesheet before it reached the preview door. Same specificity
2076
+ trap as the 8px below, on the same card, found the same way.
2077
+
2078
+ THE FIRST MODULE TAKES NO RULE. A module heading is not `:first-child`
2079
+ here — the hidden bucket header and the tab strip come before it — so
2080
+ excluding it means naming the wrappers that can precede it. The strip
2081
+ already draws its own bottom rule; a second one under it is two lines.
2082
+
2083
+ ⚠️ THIS IS A LINE, so it is on card 66334's allow-list as a KEEP with his
2084
+ words. It is NOT one of the twenty-eight that card removed: those were drawn
2085
+ between every ROW, which is what he called annoying. This is one per
2086
+ module — three or four in a panel. */
2087
+ /* ⚠️ THE THEME SHEET REACHES THE SAME RULE BY JOINING THIS SELECTOR LIST, never
2088
+ by declaring the values again. Lewis, 2026-09-04, looking at the open theme
2089
+ card: `SURFACES` sat against the row above it and there was no line between
2090
+ the two modules. Both symptoms had one cause — the sheet hand-rolled its own
2091
+ `.sf-subgroup` heading while the shell shipped `SettingsGroupSub`, so this
2092
+ rule, which keys on `.es-group-sub`, could never see it.
2093
+
2094
+ The heading is the shell's now, and these two selectors are what carry the
2095
+ rule into a sheet that does not draw Puck field wrappers: it renders its rows
2096
+ as bare `SettingsField`s inside its own container. Same 30px, same full
2097
+ bleed, same token-read cancel — one block, because a second copy of his
2098
+ number is the thing that goes stale.
2099
+
2100
+ ⚠️ AND THE COMMENT LIVES ABOVE THE RULE, NOT INSIDE THE SELECTOR LIST. Put
2101
+ between two selectors it split the list in the divider guard's parser, which
2102
+ then reported half of one rule as an unlisted line — a comment changing what
2103
+ a guard sees. */
2104
+ .sf-insp-body .es-insp-row:has(.es-group-sub),
2105
+ .sf-theme-colours .es-group-sub,
2106
+ .sf-secttheme-rows .es-group-sub {
2107
+ border-top: 1px solid var(--sf-line);
2108
+ margin-top: 30px;
2109
+ padding-top: 30px;
2110
+ /* Cancel the panel body's side padding, then put it back inside, so the rule
2111
+ reaches the panel edge while the words stay on the rows' left edge.
2112
+ ⚠️ READ FROM THE TOKEN, never typed. A literal `14px` here hung the line
2113
+ 6px OUTSIDE the panel at every width under 280, where card 66212 narrows
2114
+ the padding to 8 — measured at 260 and at 220 before this was a `var()`. */
2115
+ margin-left: calc(var(--sf-insp-pad-x) * -1);
2116
+ margin-right: calc(var(--sf-insp-pad-x) * -1);
2117
+ padding-left: var(--sf-insp-pad-x);
2118
+ padding-right: var(--sf-insp-pad-x);
2119
+ }
2120
+ /* The heading's own top margin is the wrapper's job now. */
2121
+ .sf-insp-body .es-insp-row:has(.es-group-sub) .es-group-sub {
2122
+ margin-top: 0;
2123
+ }
2124
+ /* No rule directly under the tab strip, or under a hidden bucket header. */
2125
+ .sf-insp-body .es-insp-row:nth-child(1 of :not([hidden], :empty, :has(> [data-sf-device-hidden], > [data-sf-no-control]))):has(.es-group-sub) {
2126
+ border-top: 0;
2127
+ margin-top: 0;
2128
+ padding-top: var(--es-rhythm-group-first);
2129
+ }
2130
+ /* A HEADING OWNS ITS FIRST SETTING, so that setting sits closer to it than to
2131
+ the settings below it. The shell says this as
2132
+ `.es-group-body > .es-field:first-child`, which cannot fire here: Puck's
2133
+ inspector is a FLAT sibling list, so a heading never wraps the rows it heads
2134
+ and there is no `:first-child` to select. Same rule, said the only way this
2135
+ DOM can say it — the row after the heading's wrapper. Without it the
2136
+ heading's own 24px bottom margin simply loses the collapse to the 38px above
2137
+ the row, and every heading sits as far from its own settings as from
2138
+ somebody else's. */
2139
+ .sf-insp-body .es-insp-row:has(.es-group-sub) + .es-insp-row .sf-field,
2140
+ .sf-insp-body .es-insp-row:has(.es-group-head) + .es-insp-row .sf-field {
2141
+ margin-top: var(--es-rhythm-group-first);
2142
+ }
2143
+ .sf-field-label {
2144
+ display: block;
2145
+ font-size: 12px;
2146
+ font-weight: 500;
2147
+ color: var(--sf-text-secondary);
2148
+ /* WITHIN one setting: its label to its own control. This gap is the only
2149
+ thing saying which label owns which control, so it stays plainly smaller
2150
+ than the gap BETWEEN settings — the pair the shell asks a consumer to keep
2151
+ when it re-scales (panel.css, `--es-rhythm-within`). */
2152
+ margin-bottom: var(--es-rhythm-within);
2153
+ }
2154
+ .sf-field-label--inline {
2155
+ margin-bottom: 0;
2156
+ }
2157
+ /* A hint that answers a question you only have WHILE TYPING waits until the box
2158
+ is in use (card 8802.g). Which sentence is which — and why one waits and the
2159
+ other never does — is decided once, in `hintIsWhileTyping` in
2160
+ lib/puck/controls.tsx; this is only the showing and the hiding.
2161
+
2162
+ CSS rather than React state, the same call card 8802.h made in editor-shell:
2163
+ a repaint on focus drops the browser's text selection, and the formatting bar
2164
+ above the box exists to act on that selection.
2165
+
2166
+ SCOPED TO THE MODIFIER, NEVER TO .es-field-hint ITSELF. The shared caption
2167
+ class also carries the anchor sentence, which is on every section and is the
2168
+ only thing that says what the term of art "Anchor name" means. A rule written
2169
+ one class wider would take that explanation off all of them at once. */
2170
+ /* ── The section panel's own hints switch ───────────────────────────────────
2171
+ [ADD] Section Panel Folds Its Hints, 2026-09-06. Owner asked for the same
2172
+ control the Basics tab has had since card 66177 — a section's Style tab was
2173
+ carrying two- and three-line explanations on rows a merchant has read once.
2174
+
2175
+ ⚠️ IT FOLDS A ROW'S EXPLANATION, NEVER A STATE. Every quiet line is
2176
+ `.es-field-hint` now, and two kinds must stay on screen: the "Anchor name"
2177
+ sentence, the only place that term of art is explained, and the source note,
2178
+ which tells a merchant why clicking a hydrated word gave them no caret. Both
2179
+ are marked `.sf-hint-keep`, and the rule below leaves them alone. */
2180
+ /* ⚠️ AND NEVER A HINT THAT HOLDS `.sf-hint-keep` ([MERGE] One Settings Row And
2181
+ Hint In The Website Editor, 2026-10-06). The shop's own hint classes are
2182
+ gone; every quiet line under a control is `SettingsField`'s `hint`. The few
2183
+ that are a STATE rather than an explanation — loading, empty, why a control
2184
+ is disabled — wrap their words in `components/KeepHint.tsx`, and a line under
2185
+ no row wears `sf-hint-keep` itself; this rule leaves both on screen. `scripts/test-section-hints-fold.ts` holds both. */
2186
+ .sf-panel--right.sf-hints-folded .es-field-hint:not(.sf-hint-keep):not(:has(.sf-hint-keep)) {
2187
+ display: none;
2188
+ }
2189
+
2190
+ .es-field-hint:has(.sf-hint-while-typing) {
2191
+ display: none;
2192
+ }
2193
+ .es-field:focus-within > .es-field-hint:has(.sf-hint-while-typing) {
2194
+ display: block;
2195
+ }
2196
+
2197
+ /* ── Device-aware fields (lib/puck/device-fields.tsx) ─────────────────────────
2198
+ The rule that collapses a row holding the device gate's marker
2199
+ (`[data-sf-device-hidden]`) is efficient-easel/screen.css's since 0.103.0:
2200
+ the screen's own gate paints the marker, so every host that mounts the screen
2201
+ needs the rule, and this sheet goes to one of them. It loads before this
2202
+ sheet, where the rule stood; no rule here meets it on `display !important`. */
2203
+ /* ── A field that draws NO CONTROL here (card 66178) ──────────────────────────
2204
+ Puck owned the wrapper around every top-level field and ruled the gap between
2205
+ two of them, so a field whose render returned nothing still took its
2206
+ `16px 16px 12px` and still drew the line: a hairline with only blank space on
2207
+ one side. (Since blueprint 4.4 the row is efficient-easel's `.es-insp-row`.) It was three of
2208
+ them stacked on the Collections Content tab, and 338 such rows across 74
2209
+ panels.
2210
+
2211
+ The field says so instead, and the wrapper goes — `display: none`, so the row
2212
+ leaves the accessibility tree with it rather than lingering as an unlabelled
2213
+ blank a screen reader still walks through. Declarative, like the device
2214
+ gate's rule (efficient-easel/screen.css) and the one below, so it cannot race
2215
+ the tab pass; and the attribute name is stated once in
2216
+ `lib/puck/panel-no-control-mark`, which `scripts/test-panel-no-empty-rows.tsx`
2217
+ §4 reads back to prove this rule is really here.
2218
+ The marker is the screen's (efficient-easel paints it), so the rule belongs
2219
+ in efficient-easel/screen.css too, and moves there once that storefront guard
2220
+ reads both sheets (`editorSheet()`); easel's tests/screen-css-closure lists it
2221
+ as open until then. */
2222
+ .es-insp-row:has(> [data-sf-no-control]) {
2223
+ display: none !important;
2224
+ }
2225
+
2226
+ /* ── NO DIVIDER HAIRLINES IN THIS PANEL (card 66334) ──────────────────────────
2227
+ The owner asked for these lines to go twice. Deleting our own was only part of
2228
+ it: the rule below is drawn by a PACKAGE, it is the majority of what is
2229
+ actually on screen, and it cannot be deleted from here.
2230
+
2231
+ Puck rules the gap between every pair of top-level fields, so it draws once
2232
+ per field for the whole panel — 28 lines in a single section panel, measured.
2233
+ That is most of the panel's lines on its own, which is why removing the shop's
2234
+ own two heading rules (card 66300) did not change what he saw.
2235
+
2236
+ ⚠️ THERE WERE TWO OF THESE, AND THE OTHER ONE IS GONE. The shell drew
2237
+ `.es-drill + .es-drill`, 16 more lines between the rows that list a section's
2238
+ text parts. It was held here under a written expiry, because a rule that lives
2239
+ in another repo could not be reached from this one. editor-shell 0.29.0 (card
2240
+ 66350) deleted it AT SOURCE and card 66351 deleted this repo's copy with it —
2241
+ the ending the Puck rule below cannot have, since `@measured/puck` is not ours
2242
+ to change. That seam is now held from both ends by
2243
+ `scripts/test-drill-rows-separated-by-air.ts`: it fails if the shell starts
2244
+ drawing on it again, and it fails if a local override for it reappears here.
2245
+
2246
+ ⚠️ SPECIFICITY, NOT `!important`. Puck's rule is (0,3,0) —
2247
+ `wrapFields field + field` — so this restates all three parts to match it, and
2248
+ wins on order: `storefront-editor.css` is imported after `puck.css` and after
2249
+ `panel.css` at every call site. The `[class*=]` form is this file's existing
2250
+ idiom for Puck's hashed module names (see the three rules above); a bare class
2251
+ would break on the next Puck bump, silently, by putting the lines back.
2252
+
2253
+ This is an override of a live upstream rule, not dead code:
2254
+ `scripts/test-panel-no-divider-lines.tsx` §2 asserts the upstream rule still
2255
+ exists before checking it, so if Puck ever drops its own line, the guard says
2256
+ to delete the override rather than leave it lying here — which is exactly what
2257
+ happened to the drill one above. */
2258
+
2259
+ /* The header panel's px readouts (Size, Space from the logo, Height) follow
2260
+ option E (Lewis, 2026-10-07): grey while the setting is unchanged — the grey
2261
+ is what says "default" — and the text colour once it is set, beside the ↺. */
2262
+ .sf-header-value[data-changed] { color: var(--sf-text); }
2263
+
2264
+ /* ── Inherit / overridden badge: GONE (owner 2026-10-06) ─────────────────────
2265
+ The `.sf-inherit` chip ("Inherits · Site" / "Overridden ↺") was replaced by
2266
+ easel's `SettingsDefaultReset` — the word "Default", and a "Reset" button —
2267
+ which draws itself from `efficient-easel/panel.css`. One part, both editors;
2268
+ no shop rule styles it. `scripts/test-one-default-reset.ts` fails on a new
2269
+ `.sf-inherit` rule. */
2270
+
2271
+ /* Read-only value display — same metrics as an inspector input, but styled as a
2272
+ system-set value the panel SHOWS rather than edits (e.g. the Website name,
2273
+ which is renamed in the Console). Muted fill + secondary text + a default
2274
+ cursor so it never reads as an editable field; a long name ellipsises. */
2275
+ .sf-field-readonly {
2276
+ border: 1px solid var(--sf-input-border);
2277
+ background: var(--sf-chip-bg);
2278
+ border-radius: var(--sf-field-radius);
2279
+ padding: 8px 11px;
2280
+ font-size: 13px;
2281
+ color: var(--sf-text-secondary);
2282
+ font-family: var(--sf-font);
2283
+ cursor: default;
2284
+ overflow: hidden;
2285
+ text-overflow: ellipsis;
2286
+ white-space: nowrap;
2287
+ }
2288
+
2289
+ /* Text field with a visibility eye: leading type glyph · label · trailing eye.
2290
+ The eye flips a stored `hidden` flag that drops the element from the slide
2291
+ render — see controls.tsx. The input stays editable (dimmed) while hidden. */
2292
+ .sf-field-head {
2293
+ display: flex;
2294
+ align-items: center;
2295
+ gap: 8px;
2296
+ /* The same label-to-control gap as `.sf-field-label` above — this is that
2297
+ label, with a glyph and an eye beside it. */
2298
+ margin-bottom: var(--es-rhythm-within);
2299
+ }
2300
+ .sf-field-head .sf-field-label {
2301
+ flex: 1;
2302
+ min-width: 0;
2303
+ margin-bottom: 0;
2304
+ }
2305
+ .sf-field-eye {
2306
+ flex: none;
2307
+ width: 22px;
2308
+ height: 22px;
2309
+ border: none;
2310
+ background: transparent;
2311
+ border-radius: 5px;
2312
+ cursor: pointer;
2313
+ color: var(--sf-muted);
2314
+ display: flex;
2315
+ align-items: center;
2316
+ justify-content: center;
2317
+ opacity: 0.6;
2318
+ transition: background 0.12s, color 0.12s, opacity 0.12s;
2319
+ }
2320
+ .sf-field-eye:hover {
2321
+ background: var(--sf-hover);
2322
+ opacity: 1;
2323
+ color: var(--sf-text-secondary);
2324
+ }
2325
+ /* Hidden: the element is dropped from the slide, but the input stays so the copy
2326
+ is preserved and editable. Strike the label + dim the input + light the eye —
2327
+ mirroring the section tree's hidden-row cue. */
2328
+ .es-field.is-hidden .es-field-label {
2329
+ color: var(--sf-muted);
2330
+ text-decoration: line-through;
2331
+ }
2332
+ .es-field.is-hidden .sf-field-eye {
2333
+ opacity: 1;
2334
+ color: var(--sf-text-secondary);
2335
+ }
2336
+ .es-field.is-hidden input,
2337
+ .es-field.is-hidden textarea {
2338
+ opacity: 0.5;
2339
+ }
2340
+
2341
+ /* Formatting bar above a copy field — bold · italic · strike · highlight.
2342
+ Select words in the field, press a button; the button writes the markup the
2343
+ renderer understands, so a merchant never types a delimiter. Sits between the
2344
+ label row and the input, at the icon-button metrics the eye already uses, so a
2345
+ copy field gains a row of chrome rather than a new visual language.
2346
+ The buttons take their ink from the same tokens as every other inspector icon,
2347
+ so light and dark both follow the panel — no colour is named here. */
2348
+ /* ⚠️ THE ROW ITSELF IS `efficient-easel/copy`'s `MarkBar` NOW (`.es-copy-bar`), and
2349
+ these rules are what keep it looking like the shop's. The shell dresses its
2350
+ bar from `--es-*` with its own metrics; the inspector's bar has to belong to
2351
+ the inspector. Same rule, two skins — the split the two editors already make.
2352
+
2353
+ `.sf-markup` — the container class this repo's own bar carried — is GONE from
2354
+ these selectors, because nothing renders it any more (swept: no `"sf-markup"`
2355
+ class in app / components / lib). `.sf-markup-btn` stays: the Clear button is
2356
+ still the host's own and still takes its metrics from it. */
2357
+ .sf-copybox > .es-copy-bar,
2358
+ .es-list-above > .es-copy-bar {
2359
+ display: flex;
2360
+ align-items: center;
2361
+ gap: 2px;
2362
+ /* ZERO, and it is the shell's 4px being neutralised rather than a gap being
2363
+ chosen. `copy.css` gives its bar a gap to the box below it, because in the
2364
+ campaign designer the bar floats above a separately framed box. Here the
2365
+ bar sits INSIDE the frame (card 8802.g) and the list column already spaces
2366
+ its children — both places set 0, which is what the two rules further down
2367
+ used to do one at a time. */
2368
+ margin-bottom: 0;
2369
+ }
2370
+ /* ⚠️ THE BAR WRAPS, and it has to now that three of its buttons are WORDS.
2371
+
2372
+ It was `nowrap` to reproduce the old `.sf-markup` exactly, which was right
2373
+ while every button was a 24px glyph. Highlight / Bold / Quiet are names
2374
+ (card: the three blank buttons), and three names do not share a line with
2375
+ four glyphs and a Clear button in a narrow panel. Measured, `text` row:
2376
+
2377
+ panel nowrap wrap
2378
+ 320px Clear 6px inside 6px inside, one line
2379
+ 220px Clear 12px OUTSIDE 6px inside, two lines
2380
+ 181px Clear 51px OUTSIDE 6px inside, two lines
2381
+
2382
+ So the wrap is not a preference: without it the fix for the blank buttons
2383
+ would trade three unreadable buttons for an unreachable one. `copy.css`
2384
+ already wraps; this rule now only exists to say why that is wanted here. */
2385
+ .sf-copybox > .es-copy-bar,
2386
+ .es-list-above > .es-copy-bar {
2387
+ flex-wrap: wrap;
2388
+ row-gap: 2px;
2389
+ }
2390
+ /* A button that draws a GLYPH is a square; a button that draws a WORD is as
2391
+ wide as its word. `has-glyph` is the shell's own marker for the difference
2392
+ (editor-shell 0.62.0), so the two cases are told apart by what they hold
2393
+ rather than by a list of style names kept in step by hand. */
2394
+ .sf-markup-btn,
2395
+ .sf-copybox .es-copy-mark.has-glyph,
2396
+ .es-list-above .es-copy-mark.has-glyph {
2397
+ width: 24px;
2398
+ }
2399
+ .sf-copybox .es-copy-mark:not(.has-glyph),
2400
+ .es-list-above .es-copy-mark:not(.has-glyph) {
2401
+ /* The style's NAME — Highlight / Bold / Quiet. Padded so two of them do not
2402
+ read as one word, and never wrapped mid-name. */
2403
+ padding: 0 6px;
2404
+ font-size: 11px;
2405
+ line-height: 1;
2406
+ white-space: nowrap;
2407
+ }
2408
+ .sf-markup-btn,
2409
+ .sf-copybox .es-copy-mark,
2410
+ .es-list-above .es-copy-mark {
2411
+ height: 22px;
2412
+ border: none;
2413
+ background: transparent;
2414
+ border-radius: 5px;
2415
+ cursor: pointer;
2416
+ color: var(--sf-icon);
2417
+ display: flex;
2418
+ align-items: center;
2419
+ justify-content: center;
2420
+ opacity: 0.75;
2421
+ transition: background 0.12s, color 0.12s, opacity 0.12s;
2422
+ }
2423
+ .sf-markup-btn:hover:not(:disabled),
2424
+ .sf-copybox .es-copy-mark:hover:not(:disabled),
2425
+ .es-list-above .es-copy-mark:hover:not(:disabled) {
2426
+ background: var(--sf-hover);
2427
+ color: var(--sf-text-secondary);
2428
+ opacity: 1;
2429
+ }
2430
+ .sf-markup-btn:active:not(:disabled),
2431
+ .sf-copybox .es-copy-mark:active:not(:disabled),
2432
+ .es-list-above .es-copy-mark:active:not(:disabled) {
2433
+ background: var(--sf-accent-tint);
2434
+ color: var(--sf-accent-text);
2435
+ }
2436
+ .sf-markup-btn:disabled,
2437
+ .sf-copybox .es-copy-mark:disabled,
2438
+ .es-list-above .es-copy-mark:disabled {
2439
+ color: var(--sf-disabled);
2440
+ cursor: default;
2441
+ }
2442
+ /* The fifth button — clear formatting (card 8809) — pushed away from the four.
2443
+ The four ADD a mark to a selection; this one TAKES every mark off the whole
2444
+ field, so it does not stand shoulder to shoulder with them. This one rule is
2445
+ the whole separation: `margin-left: auto` makes the bar's own `.es-divider-v`
2446
+ eat the free space, which IS the flexible spacer, and carries the button after
2447
+ it to the far end. No new class, no new line and no new colour — the divider
2448
+ already draws itself in --sf-divider, and the button takes `.sf-markup-btn`
2449
+ for its metrics, its hover and its :disabled look.
2450
+
2451
+ The height is the one adjustment: `.es-divider-v` is 22px for the 30px icon
2452
+ buttons of the top toolbar, which in a 22px row would run edge to edge and
2453
+ read as a wall rather than a seam. 14px matches the glyphs it separates. */
2454
+ .sf-copybox > .es-copy-bar > .es-copy-bar-split,
2455
+ .es-list-above > .es-copy-bar > .es-copy-bar-split {
2456
+ margin-left: auto;
2457
+ height: 14px;
2458
+ }
2459
+ /* ⚠️ AND THE AUTO MARGIN IS ON THE BUTTON TOO, not only on the divider.
2460
+ `margin-left: auto` eats the free space of the line the item is ON. While
2461
+ the bar was one line that was the same thing. Now that it wraps, the divider
2462
+ can end line 1 while Clear starts line 2 — and Clear, with no auto margin of
2463
+ its own, sat at the LEFT of that line. Measured at 181px: three rows, glyphs
2464
+ / names / Clear, with Clear 135px from the right edge it is supposed to be
2465
+ at. One declaration, and it is at the far end of whichever line it lands on. */
2466
+ .sf-copybox > .es-copy-bar > .sf-markup-clear,
2467
+ .es-list-above > .es-copy-bar > .sf-markup-clear {
2468
+ margin-left: auto;
2469
+ }
2470
+ /* The shell's divider dressed as the shop's `.es-divider-v`: the same seam, so
2471
+ the bar has ONE divider rather than the shell's plus a hand-rolled one.
2472
+ NO COLOUR IS NAMED — copy.css already draws it in `--es-copy-rule`, which the
2473
+ editor aliases onto its own border token, exactly as `.es-divider-v` drew
2474
+ itself in `--es-divider`. A colour here would be a second answer. */
2475
+ .sf-copybox > .es-copy-bar > .es-copy-bar-split,
2476
+ .es-list-above > .es-copy-bar > .es-copy-bar-split {
2477
+ align-self: center;
2478
+ margin-right: 2px;
2479
+ }
2480
+ /* A hidden element's copy is still editable, so the bar dims with the input
2481
+ rather than disappearing — same cue, same reason as the input's 0.5. */
2482
+ .es-field.is-hidden .es-copy-bar {
2483
+ opacity: 0.5;
2484
+ }
2485
+
2486
+ /* ── The copy box: ONE frame around the bar and the words ─────────────────────
2487
+ Card 8802.g. The bar and the input used to be siblings — the bar with no
2488
+ frame of its own, the input with one — so a copy field read as a row of loose
2489
+ glyphs floating above a box rather than as a single control, and the eye had
2490
+ to decide which box the bar belonged to. The border, the radius and the field
2491
+ background move onto this wrapper; the input inside gives up all three; and
2492
+ the WHOLE box takes the focus ring, so a click anywhere in it lights up the
2493
+ thing you are about to type into.
2494
+
2495
+ The shared-bar list keeps its own shape on purpose: `.es-list-above` holds ONE
2496
+ bar above N rows, which cannot be framed together with the words the way one
2497
+ bar above one box can. Every rule here is scoped to a DIRECT child of
2498
+ .sf-copybox, so none of them reaches it. */
2499
+ .sf-copybox {
2500
+ border: 1px solid var(--sf-input-border);
2501
+ border-radius: var(--sf-field-radius);
2502
+ background: var(--sf-field-bg);
2503
+ /* The bar's strip is square; this is what bends it to the frame's radius. */
2504
+ overflow: hidden;
2505
+ }
2506
+ .sf-copybox:focus-within {
2507
+ border-color: var(--sf-accent);
2508
+ box-shadow: 0 0 0 3px var(--sf-accent-ring);
2509
+ }
2510
+ /* The bar, inside the frame: a strip at the top, divided from the words by a
2511
+ hairline. --sf-hover is the panel's own faintest fill, the tint every other
2512
+ inspector control uses to say "chrome, not content", so no new colour and no
2513
+ literal is introduced here. */
2514
+ .sf-copybox > .es-copy-bar {
2515
+ margin-bottom: 0;
2516
+ padding: 3px 6px;
2517
+ /* ⚠️ NOT a panel divider, and card 66334 deliberately left it. This hairline
2518
+ is INSIDE one control: it divides the formatting bar from the words, and it
2519
+ exists instead of giving the bar a second full frame -- which is the thing
2520
+ card 8802.g removed. `scripts/test-copy-field-box.tsx` asserts both halves
2521
+ ("divided by a hairline" AND "takes no second frame"), so deleting it here
2522
+ puts that card's defect back. */
2523
+ border-bottom: 1px solid var(--sf-input-border);
2524
+ background: var(--sf-hover);
2525
+ }
2526
+ /* THE BAR WHILE TYPING (blueprint 4.9's text box design, 4.10 here): at rest a
2527
+ copy box is its words, and the strip arrives with the caret — a column of
2528
+ text settings is a column of words, not a column of toolbars. Hidden, not
2529
+ unmounted: the bar keeps its place in the markup, and a keyboard user who
2530
+ Shift+Tabs out of the words lands on its last button, which keeps the box
2531
+ focused and the bar shown. */
2532
+ .sf-copybox:not(:focus-within) > .es-copy-bar {
2533
+ display: none;
2534
+ }
2535
+ /* PRESSED: the selection already wears this mark, so a press takes it off. The
2536
+ same tint as the :active press above, held — under the pointer too, which is
2537
+ where it is right after the press (the hover rule would otherwise win). */
2538
+ .sf-copybox .es-copy-mark.is-active,
2539
+ .sf-copybox .es-copy-mark.is-active:hover:not(:disabled),
2540
+ .es-list-above .es-copy-mark.is-active,
2541
+ .es-list-above .es-copy-mark.is-active:hover:not(:disabled) {
2542
+ background: var(--sf-accent-tint);
2543
+ color: var(--sf-accent-text);
2544
+ }
2545
+ /* The input gives up its own frame to the wrapper. The `.sf-insp-body` +
2546
+ attribute selectors are not decoration: the base control rule near the top of
2547
+ this file is `.sf-insp-body input[type="text"]`, which ties on specificity
2548
+ with a plain `.sf-copybox > input`, and a reader would have to know both line
2549
+ numbers to see why this one wins. `display: block` kills the inline
2550
+ descender gap that would otherwise show a sliver of background under a
2551
+ textarea, inside the frame. */
2552
+ .sf-copybox > input,
2553
+ .sf-copybox > textarea,
2554
+ .sf-insp-body .sf-copybox > input[type="text"],
2555
+ .sf-insp-body .sf-copybox > input:not([type]),
2556
+ .sf-insp-body .sf-copybox > textarea {
2557
+ border: none;
2558
+ border-radius: 0;
2559
+ background: transparent;
2560
+ display: block;
2561
+ width: 100%;
2562
+ }
2563
+ .sf-copybox > input:focus,
2564
+ .sf-copybox > textarea:focus,
2565
+ .sf-insp-body .sf-copybox > input[type="text"]:focus,
2566
+ .sf-insp-body .sf-copybox > input:not([type]):focus,
2567
+ .sf-insp-body .sf-copybox > textarea:focus {
2568
+ box-shadow: none;
2569
+ }
2570
+
2571
+ /* Color swatches */
2572
+ .sf-swatches {
2573
+ display: flex;
2574
+ align-items: center;
2575
+ gap: 10px;
2576
+ }
2577
+ .sf-swatch {
2578
+ width: 26px;
2579
+ height: 26px;
2580
+ border-radius: 7px;
2581
+ border: none;
2582
+ cursor: pointer;
2583
+ padding: 0;
2584
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.18);
2585
+ }
2586
+ .sf-swatch.is-active {
2587
+ box-shadow: 0 0 0 2px var(--sf-panel), 0 0 0 4px var(--sf-accent);
2588
+ }
2589
+ .sf-swatch-hex {
2590
+ font-size: 11px;
2591
+ color: var(--sf-muted);
2592
+ font-variant-numeric: tabular-nums;
2593
+ margin-left: 2px;
2594
+ }
2595
+
2596
+ /* ── THE colour control — efficient-easel's `ColourPicker` (blueprint 4.8) ──
2597
+ One swatch, one typeable hex, and — pressing the swatch — the package's own
2598
+ window under the row: the site's theme colours, a saturation square, a hue
2599
+ strip and the hex. Its look is `efficient-easel/pick.css`; what is here is
2600
+ only how the shop's rows hold it. There used to be two colour controls in
2601
+ this file's rows, then one on `<input type="color">`, whose window was the
2602
+ operating system's. See lib/puck/ColourControl.tsx.
2603
+
2604
+ THE ROW HOLDS THE PICKER IN ITS LABEL LINE — `SettingsField`'s `action`
2605
+ slot (`colorInput()` too, since [MERGE] One Settings Row And Hint) — so the picker's own box steps aside
2606
+ (`display: contents`) and its two halves become items of that line: the
2607
+ swatch and hex stay at the end of the label line (`margin-left: auto`, which
2608
+ the shell gives `.es-field-value` and nothing else), and the window wraps
2609
+ onto a line of its own at the row's FULL width. Left inside the picker's box
2610
+ it would open in the few pixels the action slot has. */
2611
+ .es-field-head:has(> .es-pick-colour) {
2612
+ flex-wrap: wrap;
2613
+ }
2614
+ .es-field-head > .es-pick-colour {
2615
+ display: contents;
2616
+ }
2617
+ .es-field-head > .es-pick-colour > .es-pick-colour-row {
2618
+ margin-left: auto;
2619
+ }
2620
+ .es-field-head > .es-pick-colour > .es-pick-window {
2621
+ flex: 1 0 100%;
2622
+ min-width: 0;
2623
+ /* LAST on the line, whatever follows it in the markup: the row's Default /
2624
+ Reset part (easel's `SettingsDefaultReset`, owner 2026-10-06) comes after
2625
+ the picker, and without this an open window pushed Reset onto a line of
2626
+ its own UNDER the window, away from the swatch it belongs beside. */
2627
+ order: 1;
2628
+ }
2629
+
2630
+ /* ⚠️ THE HEX BOX MUST OUT-SPECIFY THE SHARED INSPECTOR INPUT. `.sf-insp-body
2631
+ input[type="text"]` (0,2,1) is `width: 100%`, `padding: 8px 11px`,
2632
+ `font-size: 13px` — a full-width form field — and it beats the package's
2633
+ one-class rule. Inherited here it would eat the row and shove the swatch out
2634
+ of it, and at 13px `#DDDDDD` rendered `#DDDDD`, one digit short, on a control
2635
+ whose whole job is to show a hex exactly (the 2026-09-04 drawing). So EVERY
2636
+ property the shared rule sets is restated, at the package's own values, on
2637
+ a selector that wins on specificity (0,3,1) and not on source order. */
2638
+ .sf-insp-body input[type="text"].es-pick-hex {
2639
+ width: var(--es-pick-readout-w);
2640
+ border: 1px solid transparent;
2641
+ background: transparent;
2642
+ border-radius: 6px;
2643
+ padding: 4px 6px;
2644
+ font-size: 12px;
2645
+ color: var(--es-text);
2646
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
2647
+ flex: none;
2648
+ }
2649
+ .sf-insp-body input[type="text"].es-pick-hex:hover {
2650
+ border-color: var(--es-input-border);
2651
+ }
2652
+ .sf-insp-body input[type="text"].es-pick-hex:focus {
2653
+ outline: none;
2654
+ border-color: var(--es-accent);
2655
+ box-shadow: none;
2656
+ background: var(--es-panel);
2657
+ }
2658
+
2659
+
2660
+ /* ── Site styles' own controls (card 66148) ───────────────────────────────────
2661
+ These three shapes -- a dropdown, a strip of choice rungs, and the small text
2662
+ around them -- were spelled out in raw Tailwind `zinc-*` inside ThemeField,
2663
+ TypeGroupsField and CornersField, and never read a token.
2664
+
2665
+ ⚠️ `zinc` and `gray` ARE NOT THE SAME RAMP, which is what made this invisible
2666
+ for so long: every pair is close enough to look deliberate and none of them
2667
+ match. Measured in this project (Tailwind v4, no config override), resolved
2668
+ through a canvas because v4 emits lab():
2669
+
2670
+ border zinc-300 #d4d4d8 --sf-input-border #d1d5db
2671
+ menu zinc-200 #e4e4e7 --sf-divider #e5e7eb
2672
+ quiet zinc-400 #9f9fa9 --sf-disabled #9ca3af
2673
+ hint zinc-500 #71717b --sf-muted #6b7280
2674
+ text zinc-800 #27272a --sf-field-text #1f2937
2675
+ loud zinc-900 #18181b --sf-text #111827
2676
+ ground zinc-100 #f4f4f5 --sf-chip-bg #f3f4f6
2677
+
2678
+ So the panel ran two neutral ramps as well as two spacings, and along the same
2679
+ boundary. These classes put every one of them on the panel's own tokens, which
2680
+ alias the shell's -- the alias block at the top of this file is untouched.
2681
+
2682
+ They are CLASSES, not a copy per component, because the dropdown was written
2683
+ twice (ThemeField's preset picker, TypeGroupsField's font picker) and the rung
2684
+ strip three times (Corners, Capitals, Spacing). Same markup, same colours,
2685
+ three chances to drift. */
2686
+
2687
+ /* The dropdown: a full-width button that opens a menu under itself. */
2688
+ .sf-chooser {
2689
+ display: flex;
2690
+ width: 100%;
2691
+ align-items: center;
2692
+ gap: 8px;
2693
+ padding: 6px 10px;
2694
+ border: 1px solid var(--sf-input-border);
2695
+ border-radius: var(--sf-field-radius);
2696
+ background: var(--sf-field-bg);
2697
+ color: var(--sf-field-text);
2698
+ font-size: 13px;
2699
+ text-align: left;
2700
+ cursor: pointer;
2701
+ }
2702
+ .sf-chooser:hover {
2703
+ background: var(--sf-hover);
2704
+ }
2705
+ /* The chosen thing's name. Clips rather than wraps: a long font name must not
2706
+ push the category and the caret off the end of a 288px row. */
2707
+ .sf-chooser-name {
2708
+ flex: 1;
2709
+ min-width: 0;
2710
+ overflow: hidden;
2711
+ text-overflow: ellipsis;
2712
+ white-space: nowrap;
2713
+ }
2714
+ /* SANS / SERIF / MONO beside a font's name -- a classification, not the value,
2715
+ so it reads quieter than everything else on the row. */
2716
+ .sf-chooser-meta {
2717
+ flex: none;
2718
+ font-size: 10px;
2719
+ text-transform: uppercase;
2720
+ letter-spacing: 0.05em;
2721
+ color: var(--sf-disabled);
2722
+ }
2723
+ .sf-chooser-caret {
2724
+ flex: none;
2725
+ color: var(--sf-disabled);
2726
+ }
2727
+ .sf-chooser-menu {
2728
+ position: absolute;
2729
+ left: 0;
2730
+ z-index: 50;
2731
+ margin-top: 4px;
2732
+ width: 100%;
2733
+ overflow: hidden;
2734
+ padding: 4px 0;
2735
+ border: 1px solid var(--sf-divider);
2736
+ border-radius: var(--sf-field-radius);
2737
+ background: var(--sf-panel);
2738
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
2739
+ }
2740
+ /* The font menu is long enough to need its own ceiling; the theme menu is not. */
2741
+ .sf-chooser-menu--tall {
2742
+ max-height: 256px;
2743
+ overflow: hidden auto;
2744
+ }
2745
+ .sf-chooser-item {
2746
+ display: flex;
2747
+ width: 100%;
2748
+ align-items: center;
2749
+ gap: 8px;
2750
+ padding: 6px 10px;
2751
+ border: 0;
2752
+ background: none;
2753
+ text-align: left;
2754
+ font-size: 13px;
2755
+ color: var(--sf-text-secondary);
2756
+ cursor: pointer;
2757
+ }
2758
+ .sf-chooser-item:hover {
2759
+ background: var(--sf-hover);
2760
+ }
2761
+ .sf-chooser-item.is-active {
2762
+ font-weight: 600;
2763
+ color: var(--sf-text);
2764
+ }
2765
+ /* "Add new theme" -- an action rather than a choice, so it takes the accent.
2766
+ That colour is the whole signal now (card 66334 removed the rule that used to
2767
+ sit above it). `--sf-accent-text` and not `--sf-accent`: the shell keeps a
2768
+ darker variant for accent-coloured TEXT, which is what this is. */
2769
+ .sf-chooser-add {
2770
+ color: var(--sf-accent-text);
2771
+ font-weight: 500;
2772
+ }
2773
+ .sf-chooser-add:hover {
2774
+ background: var(--sf-hover);
2775
+ }
2776
+
2777
+ /* The choice rungs: two or three buttons in a row, one of them held down.
2778
+
2779
+ ⏳ LEAVING. Every pick-one row draws efficient-easel's SettingsSegmented since
2780
+ [MERGE] One Pick-One Control (2026-10-06); only TextStylesField still draws
2781
+ this strip, because the S2 card owns that file, and `.sf-rung` alone is the
2782
+ hints switch's base. `test-segmented-controls` §8 stops a new strip. Delete
2783
+ these rules when the last of those two moves.
2784
+
2785
+ `--sf-disabled` on the pressed border is not a disabled state -- it is the one
2786
+ token on the gray ramp that lands where `zinc-400` did, a mid-grey a step
2787
+ darker than a field border. */
2788
+ .sf-rungs {
2789
+ display: flex;
2790
+ width: 100%;
2791
+ gap: 4px;
2792
+ }
2793
+ .sf-rung {
2794
+ flex: 1;
2795
+ padding: 4px 8px;
2796
+ border: 1px solid var(--sf-input-border);
2797
+ border-radius: var(--sf-field-radius);
2798
+ background: var(--sf-field-bg);
2799
+ color: var(--sf-text-secondary);
2800
+ font-size: 12px;
2801
+ cursor: pointer;
2802
+ }
2803
+ .sf-rung:hover:not(.is-active) {
2804
+ background: var(--sf-hover);
2805
+ }
2806
+ .sf-rung.is-active {
2807
+ border-color: var(--sf-disabled);
2808
+ background: var(--sf-chip-bg);
2809
+ color: var(--sf-text);
2810
+ font-weight: 600;
2811
+ }
2812
+ /* The Card shadow miniature: a tiny card showing the shadow each option casts,
2813
+ drawn in the package's segmented-control glyph slot beside the word (it was
2814
+ `.sf-rung-chip` in the hand-built `.sf-rung--inline` strip until [MERGE] One
2815
+ Pick-One Control, 2026-10-06). Its shadow is set inline, per option, because
2816
+ that IS what the option is showing.
2817
+
2818
+ 16px wide to sit inside the slot (`--es-chip-icon`). Small, but it only has
2819
+ to answer "none, a little, or a lot", and the 1px border keeps all three
2820
+ legible at this size. */
2821
+ .sf-shadow-chip {
2822
+ display: block;
2823
+ flex: none;
2824
+ box-sizing: border-box;
2825
+ width: 16px;
2826
+ height: 11px;
2827
+ border: 1px solid var(--sf-disabled);
2828
+ background: var(--sf-field-bg);
2829
+ }
2830
+
2831
+ /* The one control that folds the Basics tab's five explanations (card 66177).
2832
+ It sits at the FOOT of the tab, under the last row's own separation, and it
2833
+ is deliberately quiet: it is chrome about the panel, not one of the
2834
+ merchant's settings, so it must not read as loudly as the rows above it. */
2835
+ .sf-hints {
2836
+ display: flex;
2837
+ margin-top: 28px;
2838
+ }
2839
+ /* A rung that is not part of a strip: it sizes to its own word rather than
2840
+ sharing a row, so the `flex: 1` every rung takes has to come back off. */
2841
+ .sf-hints-toggle {
2842
+ display: inline-flex;
2843
+ flex: none;
2844
+ align-items: center;
2845
+ gap: 5px;
2846
+ color: var(--sf-muted);
2847
+ }
2848
+ .sf-hints-toggle svg {
2849
+ flex: none;
2850
+ }
2851
+
2852
+ /* A heading INSIDE one control, below the row's own label: "Dark surfaces",
2853
+ "Light surfaces". Deliberately not `.sf-field-group`, which heads a group of
2854
+ settings -- these head a group of colours inside ONE setting. */
2855
+ /* ⛔ `.sf-subgroup` LIVED HERE AND IS GONE (2026-09-04). It was this panel's
2856
+ hand-rolled group heading, and the shell has shipped `SettingsGroupSub` all
2857
+ along — the theme sheet draws that now, so nothing renders this class. It is
2858
+ deleted rather than left: a rule nobody paints is the shape `ss.focal-dead-css`
2859
+ was a whole card to clean up, and the next person reading it would take it for
2860
+ the panel's heading and match the wrong thing.
2861
+
2862
+ ⚠️ ITS ABSENCE IS THE FIX, not tidiness. It declared a font and NO MARGIN, so
2863
+ the heading took whatever air happened to be there — which is why `SURFACES`
2864
+ sat against the row above it — and card 1103's module rule keys on
2865
+ `.es-group-sub`, so a heading wearing this name could never draw the line
2866
+ between two modules. */
2867
+ /* The small and large A flanking a size slider. */
2868
+ .sf-scale-a {
2869
+ flex: none;
2870
+ font-size: 11px;
2871
+ line-height: 1;
2872
+ color: var(--sf-disabled);
2873
+ }
2874
+ .sf-scale-a--lg {
2875
+ font-size: 15px;
2876
+ color: var(--sf-muted);
2877
+ }
2878
+ /* The "Auto" button that clears an optional number back to the built-in. */
2879
+ .sf-auto-reset {
2880
+ padding: 2px 6px;
2881
+ border: 1px solid var(--sf-input-border);
2882
+ border-radius: var(--sf-field-radius);
2883
+ background: var(--sf-field-bg);
2884
+ font-size: 11px;
2885
+ color: var(--sf-text-secondary);
2886
+ cursor: pointer;
2887
+ }
2888
+ .sf-auto-reset:hover {
2889
+ background: var(--sf-hover);
2890
+ }
2891
+ /* ─── The Text list (card 66162) ──────────────────────────────────────────
2892
+ ONE list of text styles, one row per font role. Rows are separated by a rule
2893
+ rather than boxed: the old three groups were cards, and four cards in a 288px
2894
+ panel spend more of it on borders than on settings. */
2895
+ /* ── SITE STYLES: THE SAME AIR UNDER THE TAB STRIP, IN EVERY TAB ───────────
2896
+ Lewis, 2026-09-04, on SHARPEN, unprompted: Basics and Theme have a band of
2897
+ space above their first row and Text has none — why is it not uniform?
2898
+
2899
+ MEASURED at the left panel's real 288px, before anything changed:
2900
+
2901
+ Basics 38px first element is `CornersField` → the shell's `.es-field`
2902
+ Theme 38px first element is a `.es-field` too
2903
+ Text 0px first element is `.sf-style-list`, which states no margin
2904
+
2905
+ NOBODY DECIDED EITHER NUMBER, and that is the whole finding. `.es-field`'s
2906
+ 38 is `--es-rhythm-between` — the gap BETWEEN two settings. The first row has
2907
+ nothing above it, so that rule is spending a between-rows gap on a boundary
2908
+ it was never measured against; and the Text tab simply has no rule at all.
2909
+ Two tabs are wrong in one direction, the third in the other, and no line
2910
+ anywhere said what the air under the strip should be.
2911
+
2912
+ ⚠️ NOT CAUSED BY THE PANEL-SPACING CARD. Checked rather than assumed: the
2913
+ same three tabs measure 38 / 38 / 0 on `ad353cf60`, the commit before that
2914
+ card's three PRs. Those changed `.sf-field` and Puck's field wrappers; this
2915
+ panel draws the shell's `.es-field`, whose 38 has stood since card 66301 —
2916
+ the owner's own re-scale on 2026-08-31.
2917
+
2918
+ `--es-rhythm-group-first` IS THE NAME THIS ALREADY HAD. It means "a boundary
2919
+ to the first thing under it", which is exactly what the tab strip is. Not a
2920
+ new number, and not the nearest number picked for its arithmetic: 24 is
2921
+ tighter than 38, which is the direction the owner asked for, and it is not 0,
2922
+ which reads as the first row having fallen through the strip.
2923
+
2924
+ ON THE TAB PANEL'S FIRST CHILD, whatever that child is. A rule per tab would
2925
+ be three statements of one fact, and would go stale the first time a tab's
2926
+ opening control changes — which is precisely how these three drifted apart. */
2927
+ #sf-site-styles-tabpanel > :first-child {
2928
+ margin-top: var(--es-rhythm-group-first);
2929
+ }
2930
+
2931
+ .sf-style-list {
2932
+ display: flex;
2933
+ flex-direction: column;
2934
+ }
2935
+ /* The whole row is the control -- there is no Edit button, because the row's
2936
+ own name is what a merchant points at.
2937
+
2938
+ ⚠️ `center`, NOT `baseline` (card 66177). The row used to hold one word; it
2939
+ now holds a chevron, a name and a readout drawn at that style's own size, and
2940
+ sitting a 15px readout on the same baseline as an 11px name tips the whole
2941
+ line. */
2942
+ .sf-style-head {
2943
+ display: flex;
2944
+ width: 100%;
2945
+ align-items: center;
2946
+ gap: 8px;
2947
+ padding: 10px 0;
2948
+ background: none;
2949
+ border: 0;
2950
+ text-align: left;
2951
+ cursor: pointer;
2952
+ }
2953
+ .sf-style-head:hover .sf-style-sample {
2954
+ color: var(--sf-text);
2955
+ }
2956
+ /* The visible sign that a row opens (card 66177): `aria-expanded` had said so
2957
+ to a screen reader since 66162, and nothing had said it to an eye. One glyph,
2958
+ turned a quarter when the row is open. */
2959
+ .sf-style-caret {
2960
+ display: inline-flex;
2961
+ flex: none;
2962
+ align-items: center;
2963
+ color: var(--sf-disabled);
2964
+ transition: transform 120ms ease;
2965
+ }
2966
+ .sf-style-row.is-open .sf-style-caret {
2967
+ transform: rotate(90deg);
2968
+ }
2969
+ /* The style's NAME, in the panel's own label type -- the same 11px/600 quiet
2970
+ word every `SettingsField` on the other two tabs wears, so a merchant moving
2971
+ between tabs does not have to learn how to read a second kind of row. */
2972
+ .sf-style-name {
2973
+ flex: none;
2974
+ font-size: 11px;
2975
+ font-weight: 600;
2976
+ color: var(--sf-muted);
2977
+ }
2978
+ /* The row's READOUT -- the face it is set in, named, and drawn in itself.
2979
+ Everything about how it looks is inline, per row, because that IS what the
2980
+ row is showing; the only things the stylesheet may decide here are where it
2981
+ sits, its colour and its overflow.
2982
+
2983
+ `margin-left: auto` puts it at the end of the line, which is where every
2984
+ other row in this panel carries its value (`.es-field-value` does the same).
2985
+ A long face name ellipses rather than pushing the tick off the row. */
2986
+ .sf-style-sample {
2987
+ min-width: 0;
2988
+ margin-left: auto;
2989
+ overflow: hidden;
2990
+ color: var(--sf-text);
2991
+ text-overflow: ellipsis;
2992
+ white-space: nowrap;
2993
+ }
2994
+ /* Where Done used to sit. The editor autosaves, so the thing to say here is
2995
+ "stored", and it says it once and goes quiet again. */
2996
+ .sf-style-slot {
2997
+ display: inline-flex;
2998
+ flex: none;
2999
+ width: 14px;
3000
+ height: 14px;
3001
+ align-items: center;
3002
+ justify-content: center;
3003
+ /* Quiet ink, not green — and the REASON is the panel, not the palette.
3004
+
3005
+ ⚠️ THIS NOTE USED TO SAY THE SHARED LAYER HAS NO GREEN. That was WRONG:
3006
+ `--es-success` has shipped in editor-shell since card 8802.g, and the top
3007
+ bar's saved dot (editor-shell's `.es-savestate.is-saved`) wears it. The rule that
3008
+ is real and still binding is the other half — a shop-LOCAL green would be a
3009
+ second source for a design-system colour (doc/editor-ui.md E2, enforced by
3010
+ scripts/test-inspector-inherit.tsx). Take the token; never invent the value.
3011
+
3012
+ So this tick stays muted because of WHERE it is. It sits at the end of a
3013
+ settings row, inches from the Reset button, which is already an accent-blue
3014
+ mark on the same line; a second coloured mark beside it would read as a
3015
+ second thing to act on. The top bar has no such neighbour — its dot is the
3016
+ only mark in the corner — which is why the same idea is allowed a colour
3017
+ there and not here. Card 66103 is not the reason: it settled the WORD. */
3018
+ color: var(--sf-muted);
3019
+ opacity: 0;
3020
+ transform: scale(0.6);
3021
+ transition: opacity 160ms ease, transform 160ms ease;
3022
+ }
3023
+ .sf-style-slot.is-saved {
3024
+ opacity: 1;
3025
+ transform: scale(1);
3026
+ }
3027
+ @media (prefers-reduced-motion: reduce) {
3028
+ .sf-style-slot {
3029
+ transition: none;
3030
+ }
3031
+ }
3032
+ /* The settings under an open row. */
3033
+ .sf-style-body {
3034
+ padding: 2px 0 12px;
3035
+ }
3036
+ /* The face picker -- a native select, so the platform draws the list, the
3037
+ keyboard works and a phone gets its own wheel. */
3038
+ .sf-select {
3039
+ padding: 5px 8px;
3040
+ border: 1px solid var(--sf-input-border);
3041
+ border-radius: var(--sf-field-radius);
3042
+ background: var(--sf-field-bg);
3043
+ color: var(--sf-text);
3044
+ font-size: 12px;
3045
+ cursor: pointer;
3046
+ }
3047
+ /* A rung showing a LETTERFORM rather than a word -- the B and the I. It is the
3048
+ letter's own shape that carries the meaning, so it is sized to be read as a
3049
+ glyph and it keeps its accessible name in `aria-label`. */
3050
+ .sf-rung--glyph {
3051
+ flex: 0 0 34px;
3052
+ font-size: 13px;
3053
+ line-height: 18px;
3054
+ text-align: center;
3055
+ }
3056
+ .sf-rung--glyph:disabled {
3057
+ color: var(--sf-disabled);
3058
+ cursor: not-allowed;
3059
+ }
3060
+ .sf-rung--glyph:disabled:hover {
3061
+ background: var(--sf-field-bg);
3062
+ }
3063
+ /* One theme's swatch in the preset dropdown, and one colour row's chip. */
3064
+ /* ── The theme list: one card per theme, each a small page in its own palette ──
3065
+ Card 66304. Sized against the RIGHT panel's real widths — 320px default and
3066
+ 220px the narrowest a merchant can drag to (components/panel-widths.ts). The
3067
+ approved drawing says 288px six times; 288 is the LEFT panel's default and
3068
+ this control never opens at it, so every measurement below was re-derived
3069
+ rather than inherited.
3070
+
3071
+ The OUTER spacing is the shell's — `--es-rhythm-*`, shipped in editor-shell
3072
+ 0.22.0 — so this list sits in the panel's rhythm rather than a hand-tuned one.
3073
+ The spacing INSIDE a card is the preview's own: it is a picture of a web page,
3074
+ not a run of settings, and the panel's row rhythm would be the wrong ruler. */
3075
+ .sf-theme-list {
3076
+ display: flex;
3077
+ flex-direction: column;
3078
+ gap: 8px;
3079
+ margin-top: max(var(--es-row-gap), var(--es-rhythm-group-first));
3080
+ }
3081
+
3082
+ /* ⚠️ THE CLOSED TRIGGER WEARS THIS FRAME TOO (`.sf-secttheme-btn`), rather than
3083
+ a second frame with the same numbers in it. It is the same card — one open in
3084
+ a list, one shut in a row — so the border, the radius and the clip are
3085
+ declared once and both wear them. Only what is genuinely different about a
3086
+ BUTTON (its resets, its focus ring) is written under its own name below. */
3087
+ .sf-theme-card,
3088
+ .sf-secttheme-btn {
3089
+ border: 1px solid var(--sf-input-border);
3090
+ border-radius: 9px;
3091
+ overflow: hidden;
3092
+ background: var(--sf-panel);
3093
+ }
3094
+ .sf-theme-card:hover,
3095
+ .sf-secttheme-btn:hover { border-color: var(--sf-muted); }
3096
+ /* The ring says WHICH theme is live. Colour alone would not: `aria-checked` on
3097
+ the radio carries the same fact for a screen reader. */
3098
+ .sf-theme-card.is-selected {
3099
+ border-color: var(--sf-accent);
3100
+ box-shadow: 0 0 0 2px var(--sf-accent-ring);
3101
+ }
3102
+
3103
+ .sf-theme-pick {
3104
+ display: block;
3105
+ width: 100%;
3106
+ padding: 0;
3107
+ border: 0;
3108
+ background: none;
3109
+ font: inherit;
3110
+ text-align: left;
3111
+ cursor: pointer;
3112
+ }
3113
+
3114
+ /* ── The preview itself: a page, drawn in the theme's own colours ───────────
3115
+ Every length here is fixed rather than relative, and deliberately: this is a
3116
+ picture of a page at a small size, so the type scale is the drawing's own and
3117
+ must not follow the panel's. It stays legible at 220px because nothing in it
3118
+ wraps — the one line that could is clipped. */
3119
+ .sf-theme-page {
3120
+ display: flex;
3121
+ flex-direction: column;
3122
+ gap: 4px;
3123
+ padding: 10px 12px;
3124
+ }
3125
+ .sf-theme-eyebrow {
3126
+ font-size: 7.5px;
3127
+ font-weight: 700;
3128
+ letter-spacing: 0.14em;
3129
+ text-transform: uppercase;
3130
+ line-height: 1;
3131
+ }
3132
+ .sf-theme-headline {
3133
+ font-size: 14px;
3134
+ font-weight: 750;
3135
+ letter-spacing: -0.01em;
3136
+ line-height: 1.1;
3137
+ }
3138
+ .sf-theme-body {
3139
+ font-size: 9.5px;
3140
+ line-height: 1.4;
3141
+ opacity: 0.9;
3142
+ /* One line, clipped. A preview that wraps changes height with its own words
3143
+ and the list stops being a set of comparable cards. */
3144
+ white-space: nowrap;
3145
+ overflow: hidden;
3146
+ text-overflow: ellipsis;
3147
+ }
3148
+ .sf-theme-row {
3149
+ display: flex;
3150
+ align-items: center;
3151
+ gap: 7px;
3152
+ margin-top: 1px;
3153
+ }
3154
+ .sf-theme-btn {
3155
+ font-size: 8.5px;
3156
+ font-weight: 700;
3157
+ letter-spacing: 0.02em;
3158
+ padding: 5px 11px;
3159
+ border-radius: 4px;
3160
+ white-space: nowrap;
3161
+ flex: none;
3162
+ }
3163
+ .sf-theme-mini {
3164
+ flex: 1;
3165
+ min-width: 0;
3166
+ height: 22px;
3167
+ border-radius: 3px;
3168
+ border: 1px solid;
3169
+ display: flex;
3170
+ align-items: center;
3171
+ justify-content: center;
3172
+ font-size: 7.5px;
3173
+ font-weight: 600;
3174
+ opacity: 0.75;
3175
+ }
3176
+
3177
+ /* ── The name strip: which theme this is, and the way into its colours ────── */
3178
+ .sf-theme-foot {
3179
+ display: flex;
3180
+ align-items: center;
3181
+ justify-content: space-between;
3182
+ gap: 8px;
3183
+ padding: 5px 8px 6px 12px;
3184
+ background: var(--sf-hover);
3185
+ }
3186
+ .sf-theme-card.is-selected .sf-theme-foot { color: var(--sf-accent-text); }
3187
+ .sf-theme-name {
3188
+ font-size: 11px;
3189
+ font-weight: 700;
3190
+ min-width: 0;
3191
+ overflow: hidden;
3192
+ text-overflow: ellipsis;
3193
+ white-space: nowrap;
3194
+ }
3195
+ /* The one sentence a card says when its content colour cannot be read on its own
3196
+ page. Amber rather than red: the theme still works, it is a warning about the
3197
+ colour a merchant chose, not a failure of the editor. It never carries a
3198
+ number — the ratio it was measured from stays out of the panel. */
3199
+ .sf-theme-warn {
3200
+ flex: none;
3201
+ margin-left: auto;
3202
+ font-size: 10px;
3203
+ font-weight: 700;
3204
+ color: #b45309;
3205
+ white-space: nowrap;
3206
+ }
3207
+ .sf-theme-warn + .sf-theme-edit { margin-left: 8px; }
3208
+ /* With no warning beside it the Edit still sits hard right. */
3209
+ .sf-theme-name { margin-right: auto; }
3210
+
3211
+ .sf-theme-edit {
3212
+ flex: none;
3213
+ border: 1px solid var(--sf-input-border);
3214
+ background: var(--sf-panel);
3215
+ border-radius: 4px;
3216
+ font: inherit;
3217
+ font-size: 10px;
3218
+ font-weight: 700;
3219
+ letter-spacing: 0.03em;
3220
+ color: var(--sf-text-secondary);
3221
+ padding: 3px 8px;
3222
+ line-height: 1.3;
3223
+ cursor: pointer;
3224
+ }
3225
+ .sf-theme-edit:hover { border-color: var(--sf-muted); color: var(--sf-text); }
3226
+ .sf-theme-edit.is-open {
3227
+ background: var(--sf-accent);
3228
+ border-color: var(--sf-accent);
3229
+ color: #fff;
3230
+ }
3231
+
3232
+ /* This theme's own colours, inside this theme's own card. The first row carries
3233
+ the shell's group-first rhythm so it clears the strip above it by the same
3234
+ distance a first setting clears any heading. */
3235
+ .sf-theme-colours { padding: 0 12px 12px; }
3236
+ .sf-theme-colours[hidden] { display: none; }
3237
+ .sf-theme-colours > .es-field:first-child { margin-top: var(--es-rhythm-group-first); }
3238
+
3239
+ .sf-theme-reset {
3240
+ width: 100%;
3241
+ margin-top: var(--es-rhythm-between-tight);
3242
+ border: 0;
3243
+ background: none;
3244
+ font: inherit;
3245
+ font-size: 10.5px;
3246
+ font-weight: 600;
3247
+ color: var(--sf-accent-text);
3248
+ padding: 0;
3249
+ cursor: pointer;
3250
+ text-align: left;
3251
+ }
3252
+ .sf-theme-reset:disabled { color: var(--sf-muted); cursor: default; }
3253
+
3254
+ .sf-theme-add {
3255
+ display: flex;
3256
+ align-items: center;
3257
+ gap: 6px;
3258
+ margin-top: 8px;
3259
+ border: 0;
3260
+ background: none;
3261
+ font: inherit;
3262
+ font-size: 12px;
3263
+ font-weight: 600;
3264
+ color: var(--sf-accent-text);
3265
+ padding: 4px 0;
3266
+ cursor: pointer;
3267
+ }
3268
+ .sf-theme-add:hover { text-decoration: underline; }
3269
+
3270
+ /* `.sf-ink-row` / `.sf-ink-hex` RETIRED — the second colour control.
3271
+ It drew Page settings' Background, the five rows a theme carries and Theme
3272
+ button colour, at 32x24 with a border and a monospaced hex, while
3273
+ `.sf-colorinput` drew the other seven rows at 26x26. Owner 2026-09-04 chose
3274
+ the 26x26 one and the two became one; every row those rules used to paint now
3275
+ draws the one colour control (efficient-easel's `ColourPicker` since
3276
+ blueprint 4.8, held by the rules above). */
3277
+
3278
+ /* ── Theme colour, one per mode (components/ThemeColourField.tsx) ─────────────
3279
+ Two colour rows joined by the link switch, then the readability line. The card
3280
+ around them is what says the three belong together: they are one decision. */
3281
+ .sf-colour-lede {
3282
+ margin-top: -3px;
3283
+ margin-bottom: 10px;
3284
+ }
3285
+ .sf-colour-pair {
3286
+ border: 1px solid var(--sf-divider);
3287
+ border-radius: var(--sf-row-radius);
3288
+ padding: 2px 11px 11px;
3289
+ background: var(--sf-app-bg);
3290
+ }
3291
+ /* The rows sit inside a card that already spaces them, so they set their own
3292
+ top margin rather than taking a second one from the panel's row class.
3293
+ Card 66148 moved the FLEX half here too: these three declarations came from
3294
+ `.sf-field--row`, the panel row class Site styles no longer draws, and a
3295
+ sub-row inside one setting's card has no business borrowing the class that
3296
+ sets the distance between two settings. Copied exactly — nothing moves. */
3297
+ .sf-colour-row {
3298
+ margin-top: 9px;
3299
+ display: flex;
3300
+ align-items: center;
3301
+ justify-content: space-between;
3302
+ gap: 12px;
3303
+ }
3304
+ /* The two mode names inside the colour card. They used to borrow their type
3305
+ from `.sf-field-label`, which is the class the whole panel stopped drawing in
3306
+ card 66148 — so the three declarations they actually used are written here,
3307
+ against the same token, and nothing moves. They stay 12px/500 rather than
3308
+ joining the shell's 11px/600 row label on purpose: these name a colour input
3309
+ INSIDE one setting, and reading as a quieter copy of the row above them is
3310
+ what said they were the same thing. */
3311
+ .sf-colour-mode {
3312
+ font-size: 12px;
3313
+ font-weight: 500;
3314
+ color: var(--sf-text-secondary);
3315
+ }
3316
+ .sf-colour-mode small {
3317
+ display: block;
3318
+ margin-top: 1px;
3319
+ font-size: 11px;
3320
+ font-weight: 400;
3321
+ color: var(--sf-muted);
3322
+ }
3323
+
3324
+ .sf-readability {
3325
+ margin-top: 10px;
3326
+ padding: 11px 12px 12px;
3327
+ border: 1px solid var(--sf-divider);
3328
+ border-radius: var(--sf-row-radius);
3329
+ }
3330
+ .sf-readability-head {
3331
+ margin-bottom: 7px;
3332
+ font-size: 10px;
3333
+ font-weight: 700;
3334
+ letter-spacing: 0.06em;
3335
+ text-transform: uppercase;
3336
+ color: var(--sf-muted);
3337
+ }
3338
+ .sf-readability-row {
3339
+ display: flex;
3340
+ align-items: center;
3341
+ gap: 9px;
3342
+ padding: 4px 0;
3343
+ font-size: 12px;
3344
+ }
3345
+ .sf-readability-who {
3346
+ flex: 1;
3347
+ color: var(--sf-text-secondary);
3348
+ }
3349
+ .sf-readability-dot {
3350
+ width: 8px;
3351
+ height: 8px;
3352
+ flex: none;
3353
+ border-radius: 50%;
3354
+ background: var(--sf-disabled);
3355
+ }
3356
+ /* No `tabular-nums` here, unlike the candidate buttons below: card 66204 took
3357
+ the ratio off this row and it prints a grade in words, so there are no digits
3358
+ left to line up. */
3359
+ .sf-readability-val {
3360
+ color: var(--sf-muted);
3361
+ }
3362
+ /* Grade, in colour AND in the words beside it — the dot summarises the label,
3363
+ it never carries the meaning on its own.
3364
+ TWO colours for three grades, deliberately. The shared --es-* layer carries
3365
+ danger and warning and no counterpart for "this reads fine", and a shop-local
3366
+ green would be a second source for a design-system colour (doc/editor-ui.md
3367
+ E2, enforced by scripts/test-inspector-inherit.tsx). It is also the better
3368
+ panel: a settings row that lights up green on every healthy value is noise,
3369
+ and the words already say which grade it is. So GOOD is drawn as an ordinary
3370
+ measurement in the panel's own ink, and only the two states that want the
3371
+ merchant's attention take a colour. */
3372
+ .sf-readability-dot.is-good { background: var(--sf-muted); }
3373
+ .sf-readability-dot.is-large-only { background: var(--sf-warning); }
3374
+ .sf-readability-dot.is-hard { background: var(--sf-danger); }
3375
+ .sf-readability-val.is-good { color: var(--sf-text-secondary); }
3376
+ .sf-readability-val.is-large-only { color: var(--sf-warning); }
3377
+ .sf-readability-val.is-hard { color: var(--sf-danger); }
3378
+
3379
+ .sf-readability-advice {
3380
+ margin-top: 7px;
3381
+ font-size: 11px;
3382
+ line-height: 1.6;
3383
+ color: var(--sf-muted);
3384
+ }
3385
+ .sf-readability-suggestions {
3386
+ display: flex;
3387
+ flex-wrap: wrap;
3388
+ gap: 6px;
3389
+ margin-top: 8px;
3390
+ }
3391
+ .sf-readability-suggestion {
3392
+ display: inline-flex;
3393
+ align-items: center;
3394
+ gap: 5px;
3395
+ height: 26px;
3396
+ padding: 0 9px;
3397
+ border: 1px solid var(--sf-input-border);
3398
+ border-radius: var(--sf-field-radius);
3399
+ background: var(--sf-field-bg);
3400
+ color: var(--sf-btn-text);
3401
+ font-size: 11px;
3402
+ font-variant-numeric: tabular-nums;
3403
+ cursor: pointer;
3404
+ }
3405
+ .sf-readability-suggestion:hover {
3406
+ background: var(--sf-hover);
3407
+ }
3408
+ .sf-readability-chip {
3409
+ width: 9px;
3410
+ height: 9px;
3411
+ flex: none;
3412
+ border-radius: 2px;
3413
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.18);
3414
+ }
3415
+
3416
+ /* The nine-square grid moved into `efficient-easel/panel` as `SettingsPlacement`
3417
+ (0.35.0), so the `.sf-focal*` rules that lived here are gone rather than left
3418
+ as a second copy nobody would remember to update. The chosen cell also stopped
3419
+ being card 8811's tint on the way: the shell tells the chosen thing apart by
3420
+ being the darkest in the row on a grey track (66138 / 66302), and its suite
3421
+ bans the tint token package-wide, so the retired look could not travel. */
3422
+
3423
+ /* Slider */
3424
+ .sf-slider {
3425
+ display: flex;
3426
+ align-items: center;
3427
+ gap: 12px;
3428
+ }
3429
+ /* …AND IT TAKES THE WHOLE ROW WHEREVER THE SHELL STACKS IT (card 66247, owner
3430
+ 2026-08-31: the slider fills the row).
3431
+
3432
+ `efficient-easel`'s `SettingsField` puts a control on its own line for one stated
3433
+ reason — its own comment, `src/panel/SettingsField.tsx`: "The row is stacked
3434
+ rather than side-by-side so the control gets the panel's full width — a 308px
3435
+ panel cannot afford a label column AND a usable slider." The slot it stacks
3436
+ into is `.es-field-ctl`, and that slot is `display: flex`.
3437
+
3438
+ A block-level `.sf-slider` fills its parent, which is why the sliders in this
3439
+ panel's OWN `.sf-field` rows always looked right. As a flex ITEM it does not:
3440
+ with no `flex` and no `width` it is `flex: 0 1 auto` and sizes to its content,
3441
+ a bare `input[type=range]` — 129px in Chromium, and 129px whatever the panel
3442
+ is dragged to. Measured before this rule existed (scripts/harness/part-slider-width.tsx):
3443
+
3444
+ panel 220 (MIN_PANEL_W) field content 192 track 129 67% of the row
3445
+ panel 320 (the default) field content 280 track 129 46% of the row
3446
+ panel 560 (MAX_PANEL_W) field content 520 track 129 25% of the row
3447
+
3448
+ So the slot fills the row and the track inside it did not, and the wider a
3449
+ merchant dragged the panel the smaller a fraction of it the control took.
3450
+
3451
+ `flex: 1`, not `width: 100%`, and that is the same call the rule ten lines
3452
+ below already made for the range input inside this box: a percentage width is
3453
+ right only while the slider is alone in the slot, and would overflow the row
3454
+ the day a second thing joined it. Written against `.es-field-ctl` rather than
3455
+ against this panel or that one, because the promise being kept is the SHELL's
3456
+ and every row it stacks is owed it — `PartSettingsPanel`'s Size / Line
3457
+ spacing / Letter spacing and `SiteWidthField`'s Site width are the four this
3458
+ reaches today. Guarded by `scripts/test-part-slider-fills-row.ts`. */
3459
+ .es-field-ctl > .sf-slider {
3460
+ flex: 1;
3461
+ min-width: 0;
3462
+ }
3463
+ /* The range was the one control still drawn entirely by the browser — a native
3464
+ track and thumb tinted with `accent-color`, sitting beside a panel of bespoke
3465
+ toggles, segmenteds and swatches. It read as an unstyled leftover at every OS,
3466
+ and each OS drew it differently. Drawn explicitly here instead, off the same
3467
+ tokens as its neighbours: a 4px rail on the field track colour, and a white
3468
+ thumb ringed in the accent (the shell's control shape — cf. `.es-switch`).
3469
+
3470
+ `accent-color` is deliberately dropped: with an explicit thumb + track it only
3471
+ fights the rules below. The two vendor thumb selectors CANNOT be combined into
3472
+ one rule — a selector list is discarded wholesale by any browser that doesn't
3473
+ recognise one member, which would leave the thumb unstyled. */
3474
+ .sf-slider input[type="range"] {
3475
+ flex: 1;
3476
+ min-width: 0;
3477
+ height: 18px;
3478
+ margin: 0;
3479
+ padding: 0;
3480
+ background: transparent;
3481
+ cursor: pointer;
3482
+ -webkit-appearance: none;
3483
+ appearance: none;
3484
+ }
3485
+ .sf-slider input[type="range"]::-webkit-slider-runnable-track {
3486
+ height: 4px;
3487
+ border-radius: 999px;
3488
+ background: var(--sf-track-bg);
3489
+ box-shadow: inset 0 0 0 1px var(--sf-input-border);
3490
+ }
3491
+ .sf-slider input[type="range"]::-moz-range-track {
3492
+ height: 4px;
3493
+ border-radius: 999px;
3494
+ background: var(--sf-track-bg);
3495
+ box-shadow: inset 0 0 0 1px var(--sf-input-border);
3496
+ }
3497
+ .sf-slider input[type="range"]::-webkit-slider-thumb {
3498
+ -webkit-appearance: none;
3499
+ appearance: none;
3500
+ width: 14px;
3501
+ height: 14px;
3502
+ /* Centre the thumb on a 4px track: (4 - 14) / 2. Webkit lays the thumb out
3503
+ from the track's top edge, so without this it hangs below the rail. */
3504
+ margin-top: -5px;
3505
+ border-radius: 50%;
3506
+ background: var(--sf-knob);
3507
+ border: 2px solid var(--sf-accent);
3508
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
3509
+ transition: transform 0.12s;
3510
+ }
3511
+ .sf-slider input[type="range"]::-moz-range-thumb {
3512
+ width: 14px;
3513
+ height: 14px;
3514
+ border-radius: 50%;
3515
+ background: var(--sf-knob);
3516
+ border: 2px solid var(--sf-accent);
3517
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
3518
+ transition: transform 0.12s;
3519
+ }
3520
+ .sf-slider input[type="range"]:hover::-webkit-slider-thumb {
3521
+ transform: scale(1.12);
3522
+ }
3523
+ .sf-slider input[type="range"]:hover::-moz-range-thumb {
3524
+ transform: scale(1.12);
3525
+ }
3526
+ /* THE NOTCH — where the style is, on a track that overrides it (card 66116).
3527
+ Drag away from it to override; land on it and the override is cleared, so the
3528
+ way back is on the control the merchant is already holding.
3529
+
3530
+ Placed against the thumb's REAL travel, not the track's width: a range input
3531
+ moves the thumb's centre from half a thumb in to half a thumb short of the
3532
+ end, so a plain percentage would sit beside the thumb rather than under it at
3533
+ both ends. `--sf-notch` is the 0-1 position the control computes. */
3534
+ .sf-slider.has-notch {
3535
+ position: relative;
3536
+ }
3537
+ .sf-slider.has-notch::before {
3538
+ content: "";
3539
+ position: absolute;
3540
+ top: 50%;
3541
+ left: calc(7px + (100% - 14px) * var(--sf-notch));
3542
+ width: 2px;
3543
+ height: 10px;
3544
+ margin-top: -5px;
3545
+ border-radius: 1px;
3546
+ background: var(--sf-input-border);
3547
+ pointer-events: none;
3548
+ }
3549
+
3550
+ /* THE NUMBER, AND IT IS A BUTTON (card 66275). A track that runs 200–900 in
3551
+ tens has seventy stops on it, and 380 is one of them — reachable by dragging
3552
+ only if the merchant lands the thumb on exactly the right pixel. So the
3553
+ readout is pressed to type into, which means it is a real <button> and needs
3554
+ the browser's own button drawing taken off it: everything below the first
3555
+ five declarations is undoing an appearance, not adding one. It must measure
3556
+ exactly what the <span> measured, because `.es-field-value` and this box sit
3557
+ in the same 42px column and a merchant reads down it. */
3558
+ .sf-slider-val {
3559
+ font-size: 12px;
3560
+ color: var(--sf-icon);
3561
+ font-variant-numeric: tabular-nums;
3562
+ min-width: 42px;
3563
+ text-align: right;
3564
+ padding: 0;
3565
+ border: none;
3566
+ background: none;
3567
+ font-family: inherit;
3568
+ line-height: inherit;
3569
+ }
3570
+ button.sf-slider-val {
3571
+ cursor: text;
3572
+ border-radius: 4px;
3573
+ }
3574
+ button.sf-slider-val:hover {
3575
+ color: var(--sf-text);
3576
+ box-shadow: inset 0 0 0 1px var(--sf-input-border);
3577
+ }
3578
+ /* The same box, being typed into. It keeps the readout's own width and
3579
+ alignment so the row does not shift under the merchant's caret the moment
3580
+ they click it — a number that jumps sideways as it becomes editable reads as
3581
+ two different controls. */
3582
+ input.sf-slider-typed {
3583
+ width: 42px;
3584
+ color: var(--sf-text);
3585
+ box-shadow: inset 0 0 0 1px var(--sf-accent);
3586
+ border-radius: 4px;
3587
+ padding: 1px 3px;
3588
+ /* iOS zooms a focused input under 16px unless the page says otherwise; the
3589
+ editor is desktop-first but the panel is reachable on a tablet. */
3590
+ touch-action: manipulation;
3591
+ }
3592
+ input.sf-slider-typed:focus {
3593
+ outline: none;
3594
+ }
3595
+
3596
+ /* Pinned editor-only toggle bar — sits BETWEEN the inspector head and the
3597
+ scrolling body (SectionEditor), so an aid like "Preview hover state" stays
3598
+ put at the top while the fields below it scroll. `flex: none` keeps it from
3599
+ shrinking; the body (`flex: 1`) takes the remaining height and scrolls. */
3600
+ .sf-preview-pin {
3601
+ display: flex;
3602
+ align-items: center;
3603
+ justify-content: space-between;
3604
+ gap: 12px;
3605
+ flex: none;
3606
+ padding: 9px 14px;
3607
+ background: var(--sf-panel);
3608
+ }
3609
+ .sf-preview-pin-text {
3610
+ display: flex;
3611
+ flex-direction: column;
3612
+ gap: 1px;
3613
+ min-width: 0;
3614
+ }
3615
+ .sf-preview-pin-label {
3616
+ font-size: 13px;
3617
+ font-weight: 500;
3618
+ color: var(--sf-text);
3619
+ }
3620
+ .sf-preview-pin-hint {
3621
+ font-size: 11px;
3622
+ color: var(--sf-muted);
3623
+ }
3624
+
3625
+ /* Columns-per-line stepper — a compact − N + shown for the view the footer's
3626
+ "Columns per line" control is currently editing (the top-bar device). */
3627
+ .sf-colstep {
3628
+ display: flex;
3629
+ align-items: center;
3630
+ gap: 6px;
3631
+ }
3632
+ .sf-colstep-btn {
3633
+ width: 22px;
3634
+ height: 22px;
3635
+ padding: 0;
3636
+ border: 1px solid var(--sf-line);
3637
+ border-radius: 6px;
3638
+ background: transparent;
3639
+ color: var(--sf-text);
3640
+ font-size: 15px;
3641
+ line-height: 1;
3642
+ cursor: pointer;
3643
+ display: flex;
3644
+ align-items: center;
3645
+ justify-content: center;
3646
+ }
3647
+ .sf-colstep-btn:hover:not(:disabled) {
3648
+ border-color: var(--sf-accent);
3649
+ }
3650
+ .sf-colstep-btn:disabled {
3651
+ opacity: 0.4;
3652
+ cursor: default;
3653
+ }
3654
+ .sf-colstep-value {
3655
+ min-width: 14px;
3656
+ text-align: center;
3657
+ font-size: 13px;
3658
+ font-weight: 500;
3659
+ color: var(--sf-text);
3660
+ }
3661
+
3662
+ /* Checkbox — a literal tick box tinted to the editor accent. The row is the
3663
+ shell's SettingsField with the box in its `action` slot (label left, box
3664
+ right); the label points at the box, so the text is clickable. */
3665
+ .sf-checkbox {
3666
+ cursor: pointer;
3667
+ }
3668
+ .sf-checkbox input[type="checkbox"] {
3669
+ width: 17px;
3670
+ height: 17px;
3671
+ margin: 0;
3672
+ accent-color: var(--sf-accent);
3673
+ cursor: pointer;
3674
+ }
3675
+
3676
+ /* A field group greyed out because a higher-level setting overrides it — e.g. the
3677
+ manual ProductPicker (Category + Product) when the Product Grid's Products
3678
+ source is set to All wheels / a collection. */
3679
+ .sf-locked {
3680
+ opacity: 0.5;
3681
+ pointer-events: none;
3682
+ }
3683
+
3684
+ /* ── Keyboard focus rings on the inspector controls (card 66034) ──────────────
3685
+ The inspector's bespoke controls are all <button>/<input>, so they were always
3686
+ reachable by Tab — but every one of them had its browser outline suppressed by
3687
+ the reset at the top of this file and nothing put back, leaving a keyboard user
3688
+ with no visible caret anywhere in the panel (flagged by the 2026-08-06 audit).
3689
+
3690
+ `:focus-visible`, not `:focus`, so a mouse click never paints a ring — the
3691
+ reason the outline was dropped in the first place. Same 2px accent ring as
3692
+ the top bar's `.es-btn-ghost` (editor-shell) / `.sf-addcard`, so focus reads identically across the whole
3693
+ editor rather than per-control. */
3694
+ .sf-seg2 button:focus-visible,
3695
+ .sf-readability-suggestion:focus-visible,
3696
+ .sf-swatch:focus-visible,
3697
+ .sf-field-eye:focus-visible,
3698
+ .sf-slider input[type="range"]:focus-visible,
3699
+ .sf-insp-body select:focus-visible,
3700
+ .sf-checkbox input[type="checkbox"]:focus-visible {
3701
+ outline: 2px solid var(--sf-accent);
3702
+ outline-offset: 2px;
3703
+ }
3704
+ /* The segmented sits in a 3px-padded track, so a 2px offset ring would be clipped
3705
+ by the track's own rounding — tuck it inside instead. */
3706
+ .sf-seg2 button:focus-visible {
3707
+ outline-offset: -1px;
3708
+ }
3709
+ /* The text list's × and "+ Add item" (card 22118) are efficient-easel's since
3710
+ 2026-10-06 and ring themselves (fields.css). Its row input keeps the shared
3711
+ `.sf-insp-body input[type="text"]:focus` cue every text input draws. */
3712
+
3713
+ /* Segmented */
3714
+ .sf-seg2 {
3715
+ display: flex;
3716
+ gap: 3px;
3717
+ background: var(--sf-track-bg);
3718
+ border: 1px solid var(--sf-input-border);
3719
+ border-radius: 9px;
3720
+ padding: 3px;
3721
+ }
3722
+ .sf-seg2 button {
3723
+ flex: 1;
3724
+ border: none;
3725
+ background: transparent;
3726
+ border-radius: 6px;
3727
+ padding: 6px 8px;
3728
+ font-size: 12px;
3729
+ font-weight: 500;
3730
+ color: var(--sf-icon);
3731
+ cursor: pointer;
3732
+ transition: background 0.12s, color 0.12s, box-shadow 0.12s;
3733
+ }
3734
+ /* …AND AT THE NARROW END, THE LAST 3.48px (card 66221). Wrapping the header
3735
+ panel's rows put every strip onto a line of its own, which took the widest of
3736
+ them — Background, four options — from 88px over the body to 3.48px over it.
3737
+ The strip's own width is where those last pixels are, and it is spent like
3738
+ this at a 220px panel:
3739
+
3740
+ border 1 × 2 2.00
3741
+ padding 3 × 2 6.00
3742
+ gaps 3 × 3 9.00
3743
+ buttons Page 44.75 + Dark 43.09 + Light 45.30
3744
+ + Accent 56.34 189.48
3745
+ total 206.48 body: 203
3746
+
3747
+ Every button is ALREADY at its min-content width — `flex: 1` cannot shrink one
3748
+ below its own word, because `min-width` is `auto`. So the strip does not get
3749
+ narrower by being asked; something has to cost less. The word is not available
3750
+ (the four options are derived from `SECTION_SURFACES` so they match a section's
3751
+ own Background row exactly) and neither is the type (12 / 11 / 10px already,
3752
+ and type is paid vertically too). That leaves the padding either side of each
3753
+ word, and taking it 8 → 6 gives back 4 × 4 = 16px:
3754
+
3755
+ 220px panel strip 190.48 of 203 12.52px spare, on the widest strip we draw
3756
+
3757
+ HEADROOM AND NOT A HAIRLINE, deliberately: this file's own probe records that
3758
+ these widths depend on the platform UI font, so a fix that cleared the defect
3759
+ by half a pixel would be a fix that held on the machine that measured it. The
3760
+ other rows gain the same 16px, which is what moves Alignment off its 3.05px
3761
+ margin and "Text colour at the top" off its 0.00.
3762
+
3763
+ SCOPED TO THE NARROW PANEL, on the container card 66212 already put on
3764
+ `.sf-panel` — the same rule, one level down: that card paid the horizontal
3765
+ cost out of the body's padding, and this pays the rest out of the strip's.
3766
+ Above 280px nothing changes, so all 23 `segmented()` call sites and the four
3767
+ panels that draw their own label rows are byte-identical at every width where
3768
+ they already fit. A `.sf-seg2` that is not inside a `.sf-panel` matches no
3769
+ container and is untouched by construction. */
3770
+ @container (width < 280px) {
3771
+ .sf-seg2 button {
3772
+ padding-inline: 6px;
3773
+ }
3774
+ }
3775
+
3776
+ /* ─── The strip needs a parent with a WIDTH (card 66248) ──────────────────────
3777
+ The shared strip is `efficient-easel`'s `SettingsSegmented` now, and its outer
3778
+ box `.es-segs-fit` is `container-type: inline-size` — so it takes its inline
3779
+ size FROM its parent and contributes NOTHING to it. `HeaderSettingsPanel`'s
3780
+ `Field` puts the control in a `flex-shrink-0` div, which sizes TO its content.
3781
+
3782
+ A parent sizing to a child that sizes to its parent resolves to ZERO. Measured
3783
+ in a real browser at both panel widths: control 0px, strip 0px, every word
3784
+ clamped to `max-width: 0`, and each button reduced to its own 16px of padding.
3785
+ Seven rows drew a label and no control, and every automated gate we own was
3786
+ green over it — a zero-width box is present in every tree walk and absent to
3787
+ every merchant. Forcing `container-type: normal` restores the strip to
3788
+ 104.37px, which is what says the containment is the cause and not a symptom.
3789
+
3790
+ THE RULE HAS TO SIT ABOVE THE CONTAINMENT, so this is not something the strip
3791
+ or a wrapper inside it can fix — a child cannot bootstrap width from a parent
3792
+ that has none. Measured: a full-width wrapper INSIDE the control div leaves
3793
+ all three at 0.
3794
+
3795
+ `:has()` KEEPS IT OFF THE ROWS THIS CARD DID NOT MOVE. `Field`'s control div
3796
+ also carries switches and selects, which ride the LABEL line by card 8801's
3797
+ deliberate design — that is what lets a 308px panel show four settings instead
3798
+ of three. Matching only a control div that actually contains a strip leaves
3799
+ them exactly as they were. Not a new dependency: `efficient-easel`'s own
3800
+ `panel.css` uses `:has()` three times, so the package this panel renders
3801
+ already requires it.
3802
+
3803
+ FULL WIDTH IS THE HOUSE SHAPE, not a compromise — `efficient-easel`'s
3804
+ `SettingsField` stacks its own rows for the same reason ("a 308px panel cannot
3805
+ afford a label column AND a usable slider"). The label moves above the strip
3806
+ and the strip takes the body's width. Scoped to `.sf-panel` because
3807
+ `.flex-shrink-0` is a Tailwind utility used in seven other files, and none of
3808
+ them is this panel. */
3809
+ /* An inactive segment gave no feedback at all before the click, so the strip read
3810
+ as static text until you hit it. */
3811
+ .sf-seg2 button:hover:not(.is-active) {
3812
+ color: var(--sf-text-secondary);
3813
+ background: var(--sf-hover);
3814
+ }
3815
+ .sf-seg2 button.is-active {
3816
+ background: var(--sf-raised);
3817
+ color: var(--sf-text-strong);
3818
+ /* Softer and shorter than the old flat 0.14 drop: the active segment should
3819
+ look lifted off the track, not outlined against it. */
3820
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.04);
3821
+ }
3822
+ /* ─── A control the panel has taken out of force (card 66216) ─────────────
3823
+ The setting still exists and still shows the merchant's saved answer; another
3824
+ setting in the same panel has simply taken the decision away — the Header's
3825
+ "Transparent at the top" and "Text colour at the top" under a pinned
3826
+ Background. DIMMED, NEVER HIDDEN: a control that disappears reads as a feature
3827
+ that was removed, and leaves the merchant no way to see what to change.
3828
+
3829
+ `--sf-disabled` is the panel's existing token for exactly this — the same dim
3830
+ the top bar's icon and ghost buttons wear (editor-shell's `.es-iconbtn` /
3831
+ `.es-btn-ghost`, lifted from this sheet); this is the same state on
3832
+ the control that had no rule for it (the switch is efficient-easel's
3833
+ `.es-switch`, which dims itself). The track keeps its border and the
3834
+ pressed segment keeps its lift, so the strip still READS as a strip with an
3835
+ answer in it — it is quieter, not broken. */
3836
+ .sf-seg2[data-disabled] {
3837
+ opacity: 0.5;
3838
+ cursor: not-allowed;
3839
+ }
3840
+ .sf-seg2[data-disabled] button {
3841
+ cursor: not-allowed;
3842
+ color: var(--sf-disabled);
3843
+ }
3844
+ .sf-seg2[data-disabled] button.is-active {
3845
+ color: var(--sf-text-secondary);
3846
+ }
3847
+ /* Neither may answer the pointer any more — `:hover:not(.is-active)` above would
3848
+ otherwise still light a segment the merchant cannot choose. */
3849
+ .sf-seg2[data-disabled] button:hover:not(.is-active) {
3850
+ background: transparent;
3851
+ color: var(--sf-disabled);
3852
+ }
3853
+
3854
+ /* A segment whose label is a DRAWING rather than a word — the part panel's three
3855
+ alignment rules, which have no honest three-letter word between them. Laid out
3856
+ as a block so it centres on the button instead of sitting on a text baseline
3857
+ with a descender's worth of space beneath it. */
3858
+ .sf-seg2 button > svg {
3859
+ display: block;
3860
+ margin: 0 auto;
3861
+ }
3862
+
3863
+ /* Lines of copy (textList, the ticker): efficient-easel's ONE list since the
3864
+ owner's ruling of 2026-10-06 — its rows, its ×, its "+ Add item" and its
3865
+ drag are fields.css's (`.es-list-*`). What is the storefront's own is the ONE
3866
+ formatting bar it carries (`.es-list-above`, rules further up, with the copy
3867
+ box's) and the row that bar is aimed at.
3868
+
3869
+ The row the bar is aimed at has to stay visible AFTER the caret leaves — the
3870
+ merchant clicks the bar, which takes the focus ring off the row — so the cue
3871
+ is painted rather than borrowed from :focus. Accent tokens only. */
3872
+ .es-list-above > .es-copy-bar {
3873
+ margin-bottom: 0;
3874
+ }
3875
+ input.sf-list-marking {
3876
+ border-color: var(--sf-accent);
3877
+ box-shadow: 0 0 0 1px var(--sf-accent-tint);
3878
+ }
3879
+
3880
+ /* Blog source — the Events block's "link to Storefront → Blog" control:
3881
+ a status line + a refresh button + a hint. Snapshots posts onto the block. */
3882
+ .sf-blogsource {
3883
+ display: flex;
3884
+ flex-direction: column;
3885
+ gap: 9px;
3886
+ }
3887
+ .sf-blogsource-status {
3888
+ margin: 0;
3889
+ font-size: 12px;
3890
+ color: var(--sf-text-secondary);
3891
+ }
3892
+ .sf-blogsource-status[data-state="error"] {
3893
+ color: var(--sf-danger);
3894
+ }
3895
+ /* The "Open" control inside a source note — the route the sentence names.
3896
+ [ADD] Source Note Opens The Record, 2026-09-06.
3897
+
3898
+ AN INLINE BUTTON, NOT A BLOCK ONE (`.sf-blogsource-btn` is the block form on
3899
+ the product inspector). This one sits INSIDE the sentence, at the end, so it
3900
+ inherits the caption's 11px and only its colour and underline say it is
3901
+ pressable — a 12px pill on the end of an 11px line would be a second control
3902
+ grammar for the same act.
3903
+
3904
+ It is a real <button> and not an <a>: the editor is embedded and reaches the
3905
+ admin over postMessage, so there is no href to give. Styled as a link because
3906
+ that is what it does, named as a button because that is what it is. */
3907
+ .sf-source-open {
3908
+ display: inline;
3909
+ padding: 0;
3910
+ border: 0;
3911
+ background: none;
3912
+ font: inherit;
3913
+ color: var(--sf-accent-text);
3914
+ text-decoration: underline;
3915
+ text-underline-offset: 2px;
3916
+ cursor: pointer;
3917
+ }
3918
+ .sf-source-open:hover { color: var(--sf-accent); }
3919
+ .sf-source-open:focus-visible {
3920
+ outline: 2px solid var(--sf-accent);
3921
+ outline-offset: 2px;
3922
+ border-radius: 2px;
3923
+ }
3924
+
3925
+ .sf-blogsource-btn {
3926
+ align-self: flex-start;
3927
+ padding: 7px 14px;
3928
+ border: 1px solid var(--sf-input-border);
3929
+ border-radius: 8px;
3930
+ background: transparent;
3931
+ color: var(--sf-text-secondary);
3932
+ font-size: 12px;
3933
+ font-weight: 500;
3934
+ cursor: pointer;
3935
+ transition: border-color 0.12s, color 0.12s, background 0.12s;
3936
+ }
3937
+ .sf-blogsource-btn:hover:not(:disabled) {
3938
+ border-color: var(--sf-accent);
3939
+ color: var(--sf-accent);
3940
+ }
3941
+ .sf-blogsource-btn:disabled {
3942
+ opacity: 0.55;
3943
+ cursor: default;
3944
+ }
3945
+
3946
+ /* Form picker — the "which form does this section show" control. Reuses the
3947
+ blog-source card for its frame, buttons and hint, and adds the read-only
3948
+ summary of the picked form (its fields, and where the answers go). */
3949
+ .sf-formpicker {
3950
+ display: flex;
3951
+ flex-direction: column;
3952
+ gap: 9px;
3953
+ }
3954
+ .sf-formsummary-caption {
3955
+ margin-bottom: 4px;
3956
+ font-size: 11px;
3957
+ font-weight: 600;
3958
+ letter-spacing: 0.02em;
3959
+ color: var(--sf-text-secondary);
3960
+ }
3961
+ .sf-formsummary-fields {
3962
+ margin: 0;
3963
+ padding-left: 18px;
3964
+ font-size: 12px;
3965
+ line-height: 1.55;
3966
+ color: var(--sf-text-secondary);
3967
+ }
3968
+ .sf-formsummary-warn {
3969
+ margin: 0;
3970
+ font-size: 11.5px;
3971
+ line-height: 1.45;
3972
+ color: var(--sf-danger);
3973
+ }
3974
+ .sf-formsummary-actions {
3975
+ display: flex;
3976
+ flex-wrap: wrap;
3977
+ gap: 8px;
3978
+ }
3979
+
3980
+ /* THE MEDIA ROW IS efficient-easel's `ImageBox` since blueprint step 4.8
3981
+ (`es-pick-image-*`, efficient-easel/pick.css): the picture, its name under
3982
+ it, Replace and Remove. What stays here is the shop's own around it — the
3983
+ button the focus row borrows, the drop's live regions, and the template's
3984
+ inert slot. */
3985
+ .sf-image-btn {
3986
+ border: 1px solid var(--sf-btn-border);
3987
+ background: var(--sf-panel);
3988
+ color: var(--sf-btn-text);
3989
+ border-radius: 6px;
3990
+ padding: 4px 10px;
3991
+ font-size: 12px;
3992
+ font-weight: 500;
3993
+ cursor: pointer;
3994
+ transition: background 0.12s;
3995
+ }
3996
+ .sf-image-btn:hover:not(:disabled) {
3997
+ background: var(--sf-hover);
3998
+ }
3999
+ .sf-image-btn:disabled {
4000
+ cursor: default;
4001
+ opacity: 0.55;
4002
+ }
4003
+ /* Loading / error line under the slot, so a slow or failed picker round-trip is
4004
+ visible instead of nothing happening. */
4005
+ .sf-image-status {
4006
+ margin-top: 6px;
4007
+ font-size: 11px;
4008
+ color: var(--sf-muted);
4009
+ }
4010
+ .sf-image-status--error {
4011
+ color: var(--sf-danger);
4012
+ }
4013
+ /* The shell's FileIntakeAlert renders a dropped file's rejection as a list whose
4014
+ items carry the kit's own light-theme utilities (text-xs text-red-600). Inside
4015
+ the editor they read the slot's status line instead, so a rejection flips with
4016
+ the editor theme like the error line above it. */
4017
+ ul.sf-image-status {
4018
+ list-style: none;
4019
+ padding: 0;
4020
+ }
4021
+ .sf-image-status--error li {
4022
+ color: inherit;
4023
+ font-size: inherit;
4024
+ }
4025
+ /* Shown in place of the media slot above when the open document can't hold
4026
+ media (a template): the same dashed frame, plainly not a button. It is the
4027
+ SLOT in its inert state, not a note — it was only ever counted as one
4028
+ because of its old name, `.sf-media-note` (card 66121). */
4029
+ .sf-image-slot-inert {
4030
+ border: 1.5px dashed var(--sf-input-border);
4031
+ border-radius: 9px;
4032
+ padding: 12px 14px;
4033
+ font-size: 12px;
4034
+ line-height: 1.5;
4035
+ color: var(--sf-muted);
4036
+ background-image: repeating-linear-gradient(45deg, var(--sf-hatch) 0 1px, transparent 1px 12px);
4037
+ }
4038
+
4039
+ /* ── The image FOCUS point (card 66226) ──────────────────────────────────────
4040
+ WHICH PART OF A BACKGROUND IMAGE SURVIVES ITS CROP, as any point on the
4041
+ picture rather than as one of nine. The `Image focus` row opens (the shared
4042
+ .sf-style-head grammar, see components/ImageFocusRow.tsx), shows the whole
4043
+ photograph, and a press or an arrow key puts the point where the merchant
4044
+ wants it. The stored value is the same CSS `object-position` string it has
4045
+ always been.
4046
+
4047
+ ⚠️ THE MEDIA ROW'S OWN FRAME IS NOT RESTYLED, EVER. `.es-pick-image-frame`
4048
+ stays `cover` because that is what the PAGE will look like, and that
4049
+ is its job. This block is a second frame, on screen only while a merchant is
4050
+ actually setting the point, because a `cover` view cannot express a point it
4051
+ has already cropped away. */
4052
+ .sf-focus {
4053
+ display: flex;
4054
+ flex-direction: column;
4055
+ gap: 6px;
4056
+ }
4057
+ /* THE PHOTOGRAPH IS A REAL <button>, not a div wearing a tabIndex. What this
4058
+ card replaced is a `role="radiogroup"` a keyboard could already drive, so the
4059
+ successor has to earn its place in the tab order the same way. `touch-action`
4060
+ is off so a drag on a phone moves the point instead of scrolling the panel. */
4061
+ .sf-focus-canvas {
4062
+ display: flex;
4063
+ align-items: center;
4064
+ justify-content: center;
4065
+ min-height: 150px;
4066
+ padding: 0;
4067
+ border: 1px solid var(--sf-line);
4068
+ border-radius: 6px;
4069
+ background: var(--sf-preview-bg);
4070
+ cursor: crosshair;
4071
+ touch-action: none;
4072
+ }
4073
+ .sf-focus-canvas:focus-visible {
4074
+ outline: 2px solid var(--sf-accent);
4075
+ outline-offset: 1px;
4076
+ }
4077
+ /* Shrink-wraps the picture. The marker and crosshairs are positioned against
4078
+ THIS and not against the canvas, because a whole photograph in a fixed-height
4079
+ box is letterboxed — measuring against the box would land the marker on a
4080
+ different pixel for every aspect ratio, and the merchant would watch the dot
4081
+ drift away from where they pressed. */
4082
+ .sf-focus-pane {
4083
+ position: relative;
4084
+ display: inline-block;
4085
+ max-width: 100%;
4086
+ line-height: 0;
4087
+ }
4088
+ .sf-focus-img {
4089
+ display: block;
4090
+ width: auto;
4091
+ height: auto;
4092
+ max-width: 100%;
4093
+ max-height: 150px;
4094
+ object-fit: contain;
4095
+ }
4096
+ .sf-focus-cross {
4097
+ position: absolute;
4098
+ background: rgb(255 255 255 / 45%);
4099
+ pointer-events: none;
4100
+ }
4101
+ .sf-focus-cross--h {
4102
+ right: 0;
4103
+ left: 0;
4104
+ height: 1px;
4105
+ }
4106
+ .sf-focus-cross--v {
4107
+ top: 0;
4108
+ bottom: 0;
4109
+ width: 1px;
4110
+ }
4111
+ /* Drawn in white with a dark ring rather than in a panel token: it sits on a
4112
+ merchant's own photograph, which can be any colour, and a token that read
4113
+ correctly against the panel would vanish against half of them. */
4114
+ .sf-focus-marker {
4115
+ position: absolute;
4116
+ width: 18px;
4117
+ height: 18px;
4118
+ border: 2px solid #fff;
4119
+ border-radius: 50%;
4120
+ box-shadow: 0 0 0 1.5px rgb(0 0 0 / 55%), 0 1px 5px rgb(0 0 0 / 50%);
4121
+ transform: translate(-50%, -50%);
4122
+ pointer-events: none;
4123
+ }
4124
+ .sf-focus-bar {
4125
+ display: flex;
4126
+ align-items: center;
4127
+ gap: 8px;
4128
+ }
4129
+ .sf-focus-hint {
4130
+ min-width: 0;
4131
+ flex: 1;
4132
+ font-size: 11px;
4133
+ color: var(--sf-muted);
4134
+ }
4135
+ /* THE PHOTOGRAPH IS NEVER ON SCREEN TWICE. The 96px crop belongs to the
4136
+ Background image field and this row belongs to `imageFocal` — two sibling
4137
+ Puck fields, which cannot reach each other from React — so the crop stands
4138
+ down for as long as a focus session is open, and comes back the moment it
4139
+ closes. The `:has()` on Puck's own field wrapper is this stylesheet's
4140
+ established idiom for exactly that problem (see the `[data-sf-bucket]` and
4141
+ `[data-sf-no-control]` rules above). */
4142
+ .es-insp-rows:has([data-sf-focusing]) .es-pick-image-frame {
4143
+ display: none;
4144
+ }
4145
+
4146
+ /* ── The note (card 66121) ───────────────────────────────────────────────────
4147
+ THE chrome's quiet sentence: what a panel says when it has something to tell
4148
+ the merchant and nothing to let them edit. A whole-panel stand-in for
4149
+ settings that belong to a website the open document does not have; "No pages
4150
+ yet"; "Loading…"; the word "draft" trailing a row in the duplicate-to-page
4151
+ menu. One voice, so it is one class.
4152
+
4153
+ It was six classes at five sizes — .sf-pages-note, .sf-sm-note,
4154
+ .sf-panel-note, .sf-dupmenu-note, and two more that were only counted as
4155
+ notes because of their names (see .sf-image-slot-inert and .sf-device-scope).
4156
+ A third of those arrived with the part panel and was renamed the same way,
4157
+ for the same reason — see .sf-part-style-state, which is a card's subtitle.
4158
+ None of them was wrong on its own; each was written beside the block that
4159
+ needed it, which is exactly how you get six. 13px is where four of the six
4160
+ already were and is the size the panels are set in — a note is a SENTENCE, so
4161
+ it is read at the panel's reading size, and the muted colour is what makes it
4162
+ quiet.
4163
+
4164
+ THE CLASS CARRIES THE TYPE. The padding here is the note's home placement: a
4165
+ panel's own body inset. Where a note sits somewhere else, the block it sits
4166
+ in says so (`.sf-pages-list > .sf-note`, below its own list) — never a
4167
+ `--wide` / `--tight` pair here, because a family of padding variants is the
4168
+ same six classes wearing one name. */
4169
+ .sf-note {
4170
+ padding: 14px 16px;
4171
+ font-size: 13px;
4172
+ line-height: 1.5;
4173
+ color: var(--sf-muted);
4174
+ }
4175
+ .sf-note p + p {
4176
+ margin-top: 8px;
4177
+ }
4178
+ /* The one word the note lifts out of its own quiet: the thing being explained. */
4179
+ .sf-note strong {
4180
+ color: var(--sf-text);
4181
+ font-weight: 600;
4182
+ }
4183
+ /* The one line in a note that is NOT addressed to the merchant: the part id and
4184
+ the check that failed, on the screen a part could not be resolved on (card
4185
+ [FIX] Part Says It Is Gone). It is in the panel rather than the console
4186
+ because the person who meets this defect first is the one taking the
4187
+ screenshot, and a screenshot carries no console. Quieter and smaller than the
4188
+ sentence above it, so it reads as a footnote and not as an instruction. */
4189
+ .sf-note-detail {
4190
+ margin-top: 10px;
4191
+ font-size: 11px;
4192
+ color: var(--sf-muted);
4193
+ opacity: 0.75;
4194
+ word-break: break-all;
4195
+ }
4196
+ /* Inline create/delete failure (a native alert is a no-op in the iframe). The
4197
+ note's only loud state, and the only one that is not merely informative. */
4198
+ .sf-note--error {
4199
+ color: var(--sf-danger);
4200
+ background: var(--sf-danger-tint);
4201
+ border-radius: 6px;
4202
+ }
4203
+ /* Where this section's words come from, when they are not the merchant's to
4204
+ type (card 66089, owner ruling 7). A MODIFIER rather than a second note
4205
+ style: it sits BESIDE live controls instead of replacing one — under them
4206
+ since 2026-09-03, when the note moved to the foot of the panel so it stops
4207
+ standing between the panel head and the tab strip (`components/SectionEditor.tsx`
4208
+ carries the owner's word and the reason it was kept rather than deleted).
4209
+ It used to draw
4210
+ a rule under itself to say where it stopped; card 66334 removed that with
4211
+ every other divider, and the note's own colour and air carry it instead. It
4212
+ is the panel's quietest thing on purpose; it explains, it does not warn. */
4213
+
4214
+ /* ── Part panel: the inspector scoped to ONE clicked control ──────────── */
4215
+ /* The style CARD: which site style this part follows, what that style actually
4216
+ looks like, and how far this part has been pulled away from it. Sits above
4217
+ every control because it is the thing the controls are relative TO — a
4218
+ merchant who does not know a part follows a style reads every override below
4219
+ as an ordinary setting.
4220
+
4221
+ It SHOWS the style rather than only naming it (card 66116, v3): deciding
4222
+ whether to override "Headings" is deciding about a face, a size and a weight,
4223
+ and the page has already measured all three. */
4224
+ .sf-part-style {
4225
+ position: relative;
4226
+ /* ⚠️ NO `overflow: hidden` HERE, AND IT CANNOT COME BACK. The style chooser's
4227
+ menu is absolutely positioned inside this card, so clipping the card clipped
4228
+ the MENU: a merchant opening it saw one truncated row and no way to reach
4229
+ the other six. The clip existed only to round the 3px accent edge below,
4230
+ which now rounds itself. */
4231
+ margin: 0 0 4px;
4232
+ padding: 11px 13px;
4233
+ border: 1px solid var(--sf-divider);
4234
+ border-radius: 12px;
4235
+ background: var(--sf-track-bg);
4236
+ }
4237
+ /* Marked only once something has actually been overridden, so the card is quiet
4238
+ on the ~all sections nobody has styled. The edge is the third carrier of that
4239
+ one fact (with the chip and the accent labels below) and the only one visible
4240
+ while the card is scrolled to the top of a long panel. */
4241
+ .sf-part-style.has-overrides {
4242
+ border-color: var(--sf-accent-ring);
4243
+ }
4244
+ .sf-part-style.has-overrides::before {
4245
+ content: "";
4246
+ position: absolute;
4247
+ inset: 0 auto 0 0;
4248
+ width: 3px;
4249
+ background: var(--sf-accent);
4250
+ /* Rounds ITSELF against the card's left corners, so the card no longer needs
4251
+ `overflow: hidden` to hide the square ends. Left radii only — the right side
4252
+ is a straight edge into the card. */
4253
+ border-radius: 12px 0 0 12px;
4254
+ }
4255
+ /* The card's own caption — the panel's small-label voice, so it reads as a
4256
+ heading for the name below rather than as another name. */
4257
+ .sf-part-style-caption {
4258
+ margin-bottom: 2px;
4259
+ font-size: 11px;
4260
+ font-weight: 600;
4261
+ letter-spacing: 0.06em;
4262
+ text-transform: uppercase;
4263
+ color: var(--sf-muted);
4264
+ }
4265
+ .sf-part-style-count {
4266
+ margin-bottom: 3px;
4267
+ font-size: 11px;
4268
+ font-weight: 600;
4269
+ color: var(--sf-muted);
4270
+ }
4271
+ .sf-part-style-name {
4272
+ min-width: 0;
4273
+ font-size: 20px;
4274
+ line-height: 1.15;
4275
+ letter-spacing: -0.01em;
4276
+ color: var(--sf-text-strong);
4277
+ overflow: hidden;
4278
+ white-space: nowrap;
4279
+ text-overflow: ellipsis;
4280
+ }
4281
+ /* ── The style box's name, as a chooser ([ADD] Text Follows Style) ──────────
4282
+ The name was a readout; it is a button now. Everything it looked like it
4283
+ kept: `.sf-part-style-name` still carries the face and weight, still drawn in
4284
+ the style's own type, so the one place in the panel that shows a value BY
4285
+ being it did not become a place that merely names it.
4286
+
4287
+ The popup is `.sf-multi-menu`'s, reused rather than redrawn — the trigger's
4288
+ own chrome is all that is added here. */
4289
+ .sf-part-style-pick {
4290
+ position: relative;
4291
+ }
4292
+ .sf-part-style-trigger {
4293
+ display: flex;
4294
+ align-items: center;
4295
+ gap: 8px;
4296
+ width: 100%;
4297
+ padding: 0;
4298
+ border: 0;
4299
+ background: none;
4300
+ text-align: left;
4301
+ cursor: pointer;
4302
+ /* The name can be long and the panel narrows to 220; the caret must not be
4303
+ pushed off the end, so the NAME is what gives way. */
4304
+ min-width: 0;
4305
+ }
4306
+ .sf-part-style-trigger .sf-part-style-name {
4307
+ min-width: 0;
4308
+ overflow: hidden;
4309
+ text-overflow: ellipsis;
4310
+ white-space: nowrap;
4311
+ }
4312
+ /* Matches `.es-group-sign` in editor-shell exactly — same 16px box, same right
4313
+ edge, same `−` / `+`. Deliberately NOT a caret: this panel already teaches one
4314
+ "this opens" mark on Typeface and Size & spacing, and a second vocabulary for
4315
+ the same act is what made the old dot unreadable. */
4316
+ .sf-part-style-sign {
4317
+ margin-left: auto;
4318
+ width: 16px;
4319
+ flex: none;
4320
+ text-align: center;
4321
+ color: var(--sf-muted);
4322
+ font-size: 14px;
4323
+ line-height: 1;
4324
+ }
4325
+ .sf-part-style-trigger[aria-expanded="false"] .sf-part-style-sign::before {
4326
+ content: "+";
4327
+ }
4328
+ .sf-part-style-trigger[aria-expanded="true"] .sf-part-style-sign::before {
4329
+ content: "−";
4330
+ }
4331
+ .sf-part-style-trigger:focus-visible {
4332
+ outline: 2px solid var(--sf-accent);
4333
+ outline-offset: 2px;
4334
+ border-radius: 3px;
4335
+ }
4336
+ /* Sits over the card's own content rather than pushing it down: the card is a
4337
+ summary and a menu that grew it would move the controls under it while a
4338
+ merchant was reading them. */
4339
+ .sf-part-style-menu {
4340
+ position: absolute;
4341
+ z-index: 30;
4342
+ left: 0;
4343
+ right: 0;
4344
+ box-shadow: 0 8px 24px rgb(0 0 0 / 0.18);
4345
+ }
4346
+ .sf-part-style-option {
4347
+ display: flex;
4348
+ align-items: baseline;
4349
+ gap: 8px;
4350
+ width: 100%;
4351
+ padding: 7px 9px;
4352
+ border: 0;
4353
+ background: none;
4354
+ text-align: left;
4355
+ /* E6 — a field label is never smaller than 12px, and an option is a label. */
4356
+ font-size: 13px;
4357
+ color: var(--sf-field-text);
4358
+ border-radius: max(calc(var(--sf-field-radius) - 2px), 2px);
4359
+ cursor: pointer;
4360
+ }
4361
+ .sf-part-style-option:hover {
4362
+ background: var(--sf-accent-ring);
4363
+ }
4364
+ .sf-part-style-option:focus-visible {
4365
+ outline: 2px solid var(--sf-accent);
4366
+ outline-offset: -2px;
4367
+ }
4368
+ .sf-part-style-option.is-on {
4369
+ background: var(--sf-accent-ring);
4370
+ font-weight: 600;
4371
+ }
4372
+ .sf-part-style-option-name {
4373
+ /* ⚠️ THE NAME NEVER GIVES WAY — it is the whole content of the row. It was
4374
+ `flex: 1 1 auto` with an ellipsis, so a note beside it squeezed "No style"
4375
+ down to "No…": the one row a merchant needs to find became unreadable while
4376
+ its explanation stayed whole. The NOTE gives way now, and only it. */
4377
+ flex: 0 0 auto;
4378
+ white-space: nowrap;
4379
+ }
4380
+ .sf-part-style-option .sf-note {
4381
+ min-width: 0;
4382
+ margin-left: auto;
4383
+ overflow: hidden;
4384
+ text-overflow: ellipsis;
4385
+ white-space: nowrap;
4386
+ }
4387
+ /* The text region's Style row is easel's FieldSelect (card [MERGE] One Pop-up
4388
+ List For Editor And One For Shoppers). Its "No style" row draws its own note
4389
+ so the NAME keeps its width and the NOTE gives way (owner 2026-09-06) — the
4390
+ reverse of FieldSelect's meta, which is right for a price. */
4391
+ .es-fsel-label.sf-part-style-keep {
4392
+ flex: none;
4393
+ }
4394
+ .sf-part-style-aside {
4395
+ min-width: 0;
4396
+ margin-left: auto;
4397
+ overflow: hidden;
4398
+ text-overflow: ellipsis;
4399
+ white-space: nowrap;
4400
+ font-size: 11px;
4401
+ font-weight: 400;
4402
+ color: var(--es-muted);
4403
+ }
4404
+ .sf-part-style-meta {
4405
+ margin-top: 1px;
4406
+ font-size: 11px;
4407
+ color: var(--sf-muted);
4408
+ font-variant-numeric: tabular-nums;
4409
+ }
4410
+ .sf-part-style-top {
4411
+ display: flex;
4412
+ align-items: center;
4413
+ gap: 9px;
4414
+ margin-top: 8px;
4415
+ }
4416
+ /* Pushes "Edit style" to the end whatever sits before it — a note, or the chip
4417
+ and Clear all together. */
4418
+ .sf-part-style-gap {
4419
+ flex: 1;
4420
+ }
4421
+ /* THE STYLE CARD'S OWN SUBTITLE — not a note, and never was one (card 66123).
4422
+ It was called `.sf-part-style-note`, which is the only reason the note gate
4423
+ counted it: the third time a name alone put something in that family, after
4424
+ `.sf-image-slot-inert` and `.sf-device-scope` (card 66121). Two things say it
4425
+ is not a note. It is painted `--sf-text-secondary` where every real note is
4426
+ `--sf-muted` — a note is quiet BY COLOUR, and this line is not. And it does
4427
+ not sit under a block explaining it; it sits INSIDE the bordered style card,
4428
+ in a row with the override chip and "Edit style", finishing the card's own
4429
+ head where `.sf-part-style-name` and `-meta` leave off. Folded onto `.sf-note`
4430
+ it read at 13px and wrapped, taking "Edit style" over two lines with it. So
4431
+ it keeps its look exactly and takes an honest name. */
4432
+ .sf-part-style-state {
4433
+ font-size: 11.5px;
4434
+ color: var(--sf-text-secondary);
4435
+ }
4436
+ /* The override count. A chip rather than plain text because it is a NUMBER a
4437
+ merchant looks for, and because it has to read at a glance beside a link. */
4438
+ .sf-part-chip {
4439
+ display: inline-flex;
4440
+ align-items: center;
4441
+ border-radius: 999px;
4442
+ padding: 2px 9px;
4443
+ background: var(--sf-accent-tint);
4444
+ color: var(--sf-accent-text);
4445
+ font-size: 10.5px;
4446
+ font-weight: 700;
4447
+ font-variant-numeric: tabular-nums;
4448
+ }
4449
+
4450
+ /* The panel's one text-button link — "Edit style", "Clear all". Underlined
4451
+ rather than coloured-only, so it is still a link without colour vision. */
4452
+ .sf-part-link {
4453
+ border: 0;
4454
+ background: none;
4455
+ padding: 0;
4456
+ font: inherit;
4457
+ font-size: 12px;
4458
+ font-weight: 500;
4459
+ color: var(--sf-accent-text);
4460
+ cursor: pointer;
4461
+ text-decoration: underline;
4462
+ text-underline-offset: 3px;
4463
+ text-decoration-thickness: 1px;
4464
+ text-decoration-color: var(--sf-accent-ring);
4465
+ }
4466
+ .sf-part-link:hover {
4467
+ text-decoration-color: currentColor;
4468
+ }
4469
+
4470
+ /* THE GROUPS AND THE ROWS ARE NOT DRAWN HERE ANY MORE. `.es-group` /
4471
+ `.es-field` come from `efficient-easel/panel.css`, imported by each editor entry
4472
+ — the folding pill with its count chip and the icon + quiet label + loud value
4473
+ row are the shared grammar both editors' panels are built from. What is left
4474
+ below is only what this panel adds to them.
4475
+
4476
+ The override mark, half one, is theirs too: `.es-field.is-changed
4477
+ .es-field-label` takes the accent. Blue AND the dot is deliberately two
4478
+ signals — colour alone would not survive a colour-blind merchant or a
4479
+ greyscale screenshot, which is exactly when "why does this page not match the
4480
+ site" gets asked. */
4481
+
4482
+ /* One note for a whole group, where the rule it states belongs to every row in
4483
+ it rather than to any one of them (E6: a label is a name, never a sentence).
4484
+ It stands above the first row, so it takes the ROW's left inset instead of
4485
+ the panel's and pulls the group's own top gap back out from under itself.
4486
+ `.es-group-body` is editor-shell's, which is the block it genuinely sits in.
4487
+ Placement only; the note style itself is .sf-note. */
4488
+ .es-group-body > .sf-note {
4489
+ margin: 12px 0 -4px;
4490
+ padding: 0 0 0 2px;
4491
+ }
4492
+
4493
+ /* …and inside a SLOT LIST the note sits between the group heading and the rows
4494
+ it describes, so it takes the ROW's inset rather than the note's own default
4495
+ 16px: `.es-drill` is `10px 11px` and `.sf-slot-rows .sf-add-card` is
4496
+ `8px 11px`, and a sentence stepped five pixels past the rows it is about
4497
+ reads as belonging to something above them. The BOX only — the colour, the
4498
+ size and the line-height stay the one note style's (card 66211). */
4499
+ .sf-slot-rows > .sf-note {
4500
+ padding: 10px 11px 12px;
4501
+ }
4502
+
4503
+ /* A value that reads "style" is not a measurement, so it is drawn as quietly as
4504
+ the label rather than as loudly as a number. */
4505
+ /* The `style` word is quieter than a number, because it is the absence of one.
4506
+ Since card 66275 that word lives inside the typeable readout rather than
4507
+ being the value slot's own text, so the rule has to reach the control the
4508
+ slot now holds — without it the row keeps drawing `style` at full strength
4509
+ and the one visual difference between "set" and "following the style"
4510
+ disappears. */
4511
+ .sf-part-field.is-style-value > .es-field-head > .es-field-value,
4512
+ .sf-part-field.is-style-value > .es-field-head > .es-field-value > .sf-slider-val {
4513
+ color: var(--sf-muted);
4514
+ }
4515
+
4516
+ /* The font dropdown fills the row like every other control in the panel; the
4517
+ inspector's own `select` rules give it its box. */
4518
+ .sf-part-select {
4519
+ width: 100%;
4520
+ min-width: 0;
4521
+ }
4522
+
4523
+ /* The `Color` row's control row: the dropdown, and — only once `Custom…` is
4524
+ chosen — the swatch and hex under it.
4525
+
4526
+ ⚠️ A COLUMN, NOT A ROW, AND 220px IS WHY. `MIN_PANEL_W` is 220, which leaves
4527
+ a 181px content box; the dropdown alone wants that, and the swatch (26) plus
4528
+ the hex box (57) plus two 8px gaps is another 99. Side by side they do not
4529
+ fit, and the first thing to collapse would be the dropdown showing which
4530
+ colour is selected. Stacked, each keeps its full width at every panel size —
4531
+ and the shell's `SettingsField` already gives a row a control area under its
4532
+ label, so this costs no new shape.
4533
+
4534
+ The swatch pair is right-aligned to sit under the dropdown's own right edge,
4535
+ where `SettingsField`'s `action` slot puts every other trailing control. */
4536
+ .sf-part-ink {
4537
+ display: flex;
4538
+ flex-direction: column;
4539
+ gap: 8px;
4540
+ min-width: 0;
4541
+ }
4542
+ /* In this column the picker keeps its own box: the window opens under the
4543
+ dropdown at the column's width, and the swatch and hex sit at its end. */
4544
+ .sf-part-ink > .es-pick-colour > .es-pick-colour-row {
4545
+ align-self: flex-end;
4546
+ }
4547
+
4548
+ /* The override mark, half two — and it IS the reset button, which is why it is
4549
+ a real <button> with a name and not a painted dot. The dot shrinks away under
4550
+ the pointer and the reset arrow takes its place, so the affordance is
4551
+ discovered by hovering the only new thing on the row. */
4552
+ .sf-part-mark {
4553
+ width: 18px;
4554
+ height: 18px;
4555
+ flex: none;
4556
+ display: grid;
4557
+ place-items: center;
4558
+ padding: 0;
4559
+ border: 0;
4560
+ border-radius: 50%;
4561
+ background: none;
4562
+ color: var(--sf-accent);
4563
+ cursor: pointer;
4564
+ }
4565
+ .sf-part-mark-dot {
4566
+ transform-origin: center;
4567
+ transition: transform 0.15s ease, opacity 0.15s ease;
4568
+ }
4569
+ .sf-part-mark-arrow {
4570
+ opacity: 0;
4571
+ transition: opacity 0.15s ease;
4572
+ }
4573
+ .sf-part-mark:hover .sf-part-mark-dot,
4574
+ .sf-part-mark:focus-visible .sf-part-mark-dot {
4575
+ transform: scale(0);
4576
+ opacity: 0;
4577
+ }
4578
+ .sf-part-mark:hover .sf-part-mark-arrow,
4579
+ .sf-part-mark:focus-visible .sf-part-mark-arrow {
4580
+ opacity: 1;
4581
+ }
4582
+ @media (prefers-reduced-motion: reduce) {
4583
+ .sf-part-mark-dot,
4584
+ .sf-part-mark-arrow {
4585
+ transition: none;
4586
+ }
4587
+ }
4588
+
4589
+ /* The copy box at the TOP of the drill (card 8819). Clicking a word drills
4590
+ straight to its editor, so the box is the first thing on the part screen —
4591
+ right under the way back, above the style it follows. It is the plain copy box
4592
+ (.sf-copybox) the section screen used to draw; only its placement is here. */
4593
+ .sf-part-text {
4594
+ margin-top: 4px;
4595
+ margin-bottom: 4px;
4596
+ }
4597
+ /* Roomy in the drill: this is the part's PRIMARY editor now, not a folded-away
4598
+ afterthought, so a multi-line field opens with room to write rather than the
4599
+ browser's two-row default. Scoped to the drill so the identifier rows (a slide
4600
+ name) and every other panel's copy box keep their own height. */
4601
+ .sf-part-text .sf-copybox textarea {
4602
+ min-height: 84px;
4603
+ line-height: 1.5;
4604
+ resize: vertical;
4605
+ }
4606
+
4607
+ /* ── The text ENTRY group at the bottom of the section screen (card 8819) ──── */
4608
+
4609
+ /* The section screen lists its scalar text parts here, under the real settings,
4610
+ as the way into each part's drill. The rows themselves are the shell's
4611
+ `.es-drill` (single source); this is only the group frame around them. */
4612
+ .sf-entries {
4613
+ margin-top: 32px;
4614
+ }
4615
+
4616
+
4617
+ /* The group's caption — the section panel's small-caps heading, so this group
4618
+ reads like the buckets above it. A real <h3>, so it joins the panel outline. */
4619
+ .sf-entries-head {
4620
+ margin: 14px 0 4px;
4621
+ font-size: 11px;
4622
+ font-weight: 600;
4623
+ letter-spacing: 0.06em;
4624
+ text-transform: uppercase;
4625
+ color: var(--sf-muted);
4626
+ }
4627
+
4628
+ /* The "hidden on the page" eye a row carries when a part is toggled off — the
4629
+ same cue the copy box's own eye draws, so the two read as one idea. */
4630
+ .sf-entry-eye {
4631
+ display: inline-flex;
4632
+ align-items: center;
4633
+ color: var(--sf-muted);
4634
+ }
4635
+
4636
+ /* ── A slot section's children, listed in its OWN panel (card 66186) ──────────
4637
+ The Hero Slideshow's Content tab was empty: `slides` is a slot, and Puck draws
4638
+ a slot as nothing in the inspector, so the slides were reachable only from the
4639
+ Sections tree. The rows are the shell's `.es-drill` (single source, as in
4640
+ `.sf-entries` above) and the heading is the panel's own `.sf-field-group`, so
4641
+ the only thing this frame owns is where the add row meets them.
4642
+
4643
+ It owns that because the two come from different places and did not line up:
4644
+ `.sf-add-card` is the TREE's affordance and pads `6px 9px`, a drill row pads
4645
+ `10px 11px`, so the "+" sat 2px inside the names above it (measured).
4646
+
4647
+ ⚠️ It used to draw a `border-top` as well, to match the `.es-drill + .es-drill`
4648
+ separator that could not reach a row which is not a drill. That separator is
4649
+ gone entirely now — editor-shell 0.29.0 deleted it and card 66351 deleted this
4650
+ repo's override of it — so matching it means drawing NOTHING, and the padding
4651
+ above is the whole of what this rule still owns. */
4652
+ .sf-slot-rows .sf-add-card {
4653
+ padding: 8px 11px;
4654
+ border-radius: 0;
4655
+ }
4656
+
4657
+ /* The drop zone round the media row's box. `SettingsField` puts its control in
4658
+ `.es-field-ctl`, a flex ROW, where a plain block shrinks to its content: the
4659
+ empty slot drew 190px wide in a 280px row. It takes the row instead. */
4660
+ .sf-image-drop {
4661
+ flex: 1;
4662
+ min-width: 0;
4663
+ }
4664
+ /* Drag-over highlight for the media row (MediaUploadField's drop shortcut):
4665
+ the empty slot takes the accent, a filled picture an accent ring. */
4666
+ .sf-image-drop.is-dragover .es-pick-image-slot {
4667
+ border-color: var(--sf-accent);
4668
+ background-color: var(--sf-accent-tint);
4669
+ }
4670
+ .sf-image-drop.is-dragover .es-pick-image-frame {
4671
+ outline: 2px solid var(--sf-accent);
4672
+ outline-offset: -2px;
4673
+ }
4674
+
4675
+
4676
+ /* ── THE PHOTO WALL'S LAYOUT PICKER ─────────────────────────────────────────
4677
+ The Customer Photo Grid's one layout control: a strip of pictures, filtered by
4678
+ how many photos sit in a row. The owner, 2026-09-07, on the numbers it
4679
+ replaced: he could not read them, and he was right — 6·3·3, 2·1·1 and "two
4680
+ thirds" were three notations for one idea. There are no numbers on this
4681
+ control at all. The picture is the explanation and the name is only so the
4682
+ layout can be named.
4683
+
4684
+ ⚠️ THE FILTER CHIPS CARRY NO ICON, and that is his ruling of 2026-08-29 about
4685
+ filter rows, applying here for the reason he gave: the buckets are grown from
4686
+ the layouts, so a glyph per bucket is a queue that never empties.
4687
+
4688
+ THE STRIP FILLS BY TILE WIDTH, NOT BY A COLUMN COUNT. `auto-fill` with a 58px
4689
+ minimum gives four across the 320px panel and three at the 220px minimum, so
4690
+ nothing is cut off and nothing scrolls sideways at either width. */
4691
+ .sf-gallery-filter {
4692
+ display: flex;
4693
+ gap: 5px;
4694
+ flex-wrap: wrap;
4695
+ }
4696
+ .sf-gallery-filter button {
4697
+ border: 1px solid var(--sf-input-border);
4698
+ background: var(--sf-field-bg);
4699
+ border-radius: 999px;
4700
+ padding: 4px 11px;
4701
+ font-size: 11.5px;
4702
+ font-weight: 600;
4703
+ color: var(--sf-icon);
4704
+ cursor: pointer;
4705
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
4706
+ }
4707
+ .sf-gallery-filter button:hover {
4708
+ background: var(--sf-hover);
4709
+ }
4710
+ .sf-gallery-filter button[aria-pressed="true"] {
4711
+ background: var(--sf-accent);
4712
+ border-color: var(--sf-accent);
4713
+ color: var(--sf-on-accent);
4714
+ }
4715
+
4716
+ .sf-gallery-layouts {
4717
+ display: grid;
4718
+ grid-template-columns: repeat(auto-fill, minmax(58px, 1fr));
4719
+ gap: 6px;
4720
+ margin-top: 7px;
4721
+ }
4722
+ /* ⚠️ THE STRIP MUST BE TOLD TO FILL THE SHELL'S CONTROL SLOT, OR IT COLLAPSES.
4723
+ editor-shell card 66516, and the shop is now the second consumer to meet it.
4724
+
4725
+ `SettingsField` stacks its control into `.es-field-ctl`, and that slot is
4726
+ `display: flex`. A grid written for a BLOCK row — which this was, for its whole
4727
+ life — therefore stops stretching and sizes to its own content. Measured on
4728
+ this card's board: 291px wide before the move, 58px after. One tile per line,
4729
+ nineteen lines deep, and every layout still clickable, so nothing looked
4730
+ broken enough to fail.
4731
+
4732
+ The shell already ships this exact two-line cure for its own `.es-tabs-fit`
4733
+ and `.es-segs-fit`, and its note on that rule is the reason this comment is
4734
+ long: card 66508 shipped 1198 green checks over a control 0px wide, because
4735
+ every check asked whether markup EXISTED and none asked whether it OCCUPIED
4736
+ SPACE. `scripts/probe-settings-rows-shared.mjs` now asks that question, which
4737
+ is what caught this one.
4738
+
4739
+ `flex: 1 1 auto` and not a width, because the slot may hold a value readout
4740
+ or a second control beside the strip. `min-width: 0` because a flex item's
4741
+ automatic minimum is its content size, which is what stops it shrinking.
4742
+
4743
+ ⚠️ BOTH GALLERY CONTROLS ARE NAMED, and the second one is why this is a rule
4744
+ and not a patch. `.sf-gallery-filter` — the `Photos in a row` chips — is the
4745
+ same shape, and it did NOT look broken: measured 291px before and 185px after
4746
+ at a 320px panel, with the row a pixel and a half SHORTER, because the chips
4747
+ simply stopped stretching. Nothing wrapped at the two widths that were
4748
+ measured, so every height check passed. What it had was a smaller box to wrap
4749
+ inside at any width nobody happened to measure.
4750
+
4751
+ ⚠️ SO THIS IS A DEFECT A HEIGHT RULE CANNOT CATCH, AND THAT IS THE POINT.
4752
+ The card that made this change stops at a row moving more than about 6px
4753
+ (GG, 2026-09-22). This row moved 1.5px, in the RIGHT direction, and was
4754
+ wrong. Measuring how tall a row stands cannot see a control that quietly
4755
+ stopped filling it — only asking the width can, which is why
4756
+ `scripts/probe-settings-rows-shared.mjs` now asks it of every row on both
4757
+ sides, and why that question found both of these on its first run.
4758
+
4759
+ A rule that fixed the strip and left the chips would be a trap wearing a fix
4760
+ — which is exactly what the shell says about naming both of its own `-fit`
4761
+ wrappers. This is the FOURTH instance of this collapse in this repo:
4762
+ `.sf-slider` carries the same two lines from card 66247, and the shell
4763
+ carries them for `.es-tabs-fit` and `.es-segs-fit`.
4764
+
4765
+ `.sf-linkstack` was on this list until the link field became the shared
4766
+ picker ([MERGE] One Link Picker For Both Editors): its stack is now the
4767
+ shell's own `.es-flink`, and the shell carries the same two lines for it.
4768
+ Every consumer control that is a flex or grid box written for a block row
4769
+ belongs on this selector; the day one is fixed somewhere else instead, the
4770
+ list stops being the place to look. */
4771
+ .es-field-ctl > .sf-gallery-layouts,
4772
+ .es-field-ctl > .sf-gallery-filter {
4773
+ flex: 1 1 auto;
4774
+ min-width: 0;
4775
+ }
4776
+ .sf-gallery-layouts button {
4777
+ border: 1px solid var(--sf-input-border);
4778
+ background: var(--sf-field-bg);
4779
+ border-radius: 7px;
4780
+ padding: 5px;
4781
+ cursor: pointer;
4782
+ transition: border-color 0.12s, box-shadow 0.12s, background 0.12s;
4783
+ }
4784
+ .sf-gallery-layouts button:hover {
4785
+ background: var(--sf-hover);
4786
+ }
4787
+ .sf-gallery-layouts button[aria-pressed="true"] {
4788
+ border-color: var(--sf-accent);
4789
+ box-shadow: 0 0 0 2px var(--sf-accent-ring);
4790
+ background: var(--sf-panel);
4791
+ }
4792
+
4793
+ /* The little drawing of a wall. Same twelve positions the real wall uses, so the
4794
+ picture cannot disagree with what pressing it draws. */
4795
+ .sf-gallery-pic {
4796
+ display: grid;
4797
+ grid-template-columns: repeat(12, minmax(0, 1fr));
4798
+ grid-auto-rows: 6px;
4799
+ grid-auto-flow: row dense;
4800
+ gap: 2px;
4801
+ }
4802
+ .sf-gallery-pic i {
4803
+ display: block;
4804
+ background: var(--sf-track-bg);
4805
+ border-radius: 1.5px;
4806
+ }
4807
+ .sf-gallery-layouts button[aria-pressed="true"] .sf-gallery-pic i {
4808
+ background: var(--sf-accent);
4809
+ }
4810
+
4811
+ /* How many the filter left. Without it an empty-looking strip is a mystery. */
4812
+ .sf-gallery-count {
4813
+ font-size: 10.5px;
4814
+ color: var(--sf-disabled);
4815
+ margin-top: 7px;
4816
+ }
4817
+
4818
+ /* ── Side panel heads (components/SidePanelHead) ─────────────────────────
4819
+ Every side panel's head is efficient-easel's `InspectorHead`; these two
4820
+ are the pieces the website editor adds to it. The hairline parts a panel's
4821
+ own tools from the hide button that ends every head, and a mode's way out
4822
+ is a word ("Done", "Cancel") on the engine's ghost button, drawn to the
4823
+ 28px of the head's icon buttons so the row keeps one height. */
4824
+
4825
+ /* ── The Contact / Locations inspector's level views ────────────────────────
4826
+ LocationsGroupedField shows ONE level at a time (section, group, city, its
4827
+ image or map). Each level used to be wrapped in a bare `.sf-field`, which
4828
+ made the whole view one "row" with rows inside it. The rows are
4829
+ efficient-easel's `SettingsField` now ([MERGE] One Settings Row And Hint In
4830
+ The Website Editor, 2026-10-06); this keeps the view's own top air, from the
4831
+ shell's rhythm token like every row beside it. */
4832
+ .sf-locations-view {
4833
+ margin-top: var(--es-rhythm-between);
4834
+ }
4835
+
4836
+ /* ── A quiet line that belongs to no row ────────────────────────────────────
4837
+ [MERGE] One Settings Row And Hint In The Website Editor, 2026-10-06. A line
4838
+ UNDER A CONTROL is `SettingsField`'s own `hint`. A few lines sit under no
4839
+ row — the caption inside a linked-source card, a panel's opening sentence,
4840
+ the empty state of an open focus picker — and they wear the SAME class,
4841
+ `<p class="es-field-hint sf-hint-keep">`: one hint line in the whole editor,
4842
+ not a shop copy of its look. That replaces the shop's `.sf-field-hint` and
4843
+ `.sf-blogsource-hint`, which drew this idea twice, slightly differently.
4844
+
4845
+ Not a NEW class, and not `.sf-note`: `test-editor-chrome-note` holds the
4846
+ chrome to ONE note style, and these are hints, at the hint's size.
4847
+ `sf-hint-keep` on the element keeps the hints switch off them — none is a
4848
+ row's explanation.
4849
+
4850
+ Inside a source card the card's own gap spaces it. */
4851
+ .sf-blogsource > .es-field-hint,
4852
+ .sf-formsummary .es-field-hint {
4853
+ margin: 0;
4854
+ }
4855
+
4856
+ /* The copy box sits in `SettingsField`'s control slot, which is
4857
+ `display: flex` — the trap the Gallery layouts note above describes. Told to
4858
+ fill the slot, or it shrinks to its own words. (The select fields need no
4859
+ rule: easel's `.es-fsel` is `width: 100%` itself.) */
4860
+ .es-field-ctl > .sf-copybox {
4861
+ flex: 1 1 auto;
4862
+ min-width: 0;
4863
+ }
4864
+
4865
+ /* The focal-point picker's row is TextStyleRow's disclosure row, not a
4866
+ SettingsField; this keeps the gap above it that its old bare `.sf-field`
4867
+ wrapper gave, from the same rhythm token. */
4868
+ .sf-focal-row {
4869
+ margin-top: var(--es-rhythm-between);
4870
+ }