@riverbankcms/sdk 0.104.3 → 0.107.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 (236) hide show
  1. package/README.md +84 -51
  2. package/dist/{PreviewEditorSidebar-CBMhU9p_.mjs → PreviewEditorSidebar-DpAbAl08.mjs} +1811 -1179
  3. package/dist/{PreviewEditorUI-C5KEw4M8.mjs → PreviewEditorUI-O7cn6Xxv.mjs} +3 -3
  4. package/dist/{PreviewQueryProvider-CStABBUh.mjs → PreviewQueryProvider-D0tnKl0t.mjs} +1 -1
  5. package/dist/{SdkPreviewModeRuntime-Iq12HjL7.mjs → SdkPreviewModeRuntime-CVB7urtl.mjs} +2144 -393
  6. package/dist/{SiteChromeCustomizeContext-Cx0NMdyv.mjs → SiteChromeCustomizeContext-C_sizso-.mjs} +3825 -2653
  7. package/dist/_dts/ai/src/contracts/proposals.d.ts +32945 -1339
  8. package/dist/_dts/api/src/bookingConfig.d.ts +2 -0
  9. package/dist/_dts/api/src/bookingManagementEndpoints.d.ts +5 -1
  10. package/dist/_dts/api/src/contentRuntime.d.ts +6 -15
  11. package/dist/_dts/api/src/courses.d.ts +68 -10
  12. package/dist/_dts/api/src/endpoints.d.ts +29 -0
  13. package/dist/_dts/api/src/index.d.ts +124 -124
  14. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +9 -0
  15. package/dist/_dts/api/src/sdkContracts.d.ts +116 -6
  16. package/dist/_dts/api/src/siteOperations.d.ts +1 -0
  17. package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +26 -4
  18. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +31 -46
  19. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +36 -4
  20. package/dist/_dts/blocks/src/definitions.d.ts +1 -1
  21. package/dist/_dts/blocks/src/portal/types.d.ts +25 -7
  22. package/dist/_dts/blocks/src/system/blocks/course-registration.d.ts +2 -2
  23. package/dist/_dts/blocks/src/system/blocks/courses/shared/schemas.d.ts +2 -0
  24. package/dist/_dts/blocks/src/system/blocks/courses/shared/types.d.ts +2 -0
  25. package/dist/_dts/blocks/src/system/blocks/events/event-calendar.d.ts +4 -0
  26. package/dist/_dts/blocks/src/system/blocks/events/recurring-schedule-projection.d.ts +52 -0
  27. package/dist/_dts/blocks/src/system/blocks/events/recurring-schedule.d.ts +22 -0
  28. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +45 -0
  29. package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +10 -0
  30. package/dist/_dts/blocks/src/system/blocks/feature-cards.d.ts +4 -1
  31. package/dist/_dts/blocks/src/system/blocks/index.d.ts +1 -0
  32. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +9 -0
  33. package/dist/_dts/blocks/src/system/blocks/newsletter-signup.d.ts +8 -2
  34. package/dist/_dts/blocks/src/system/blocks/offerings-preview/index.d.ts +8 -8
  35. package/dist/_dts/blocks/src/system/blocks/offerings-preview/model.d.ts +24 -24
  36. package/dist/_dts/blocks/src/system/blocks/offerings-preview/scheduleSummary.d.ts +29 -0
  37. package/dist/_dts/blocks/src/system/blocks/shop.d.ts +2 -1
  38. package/dist/_dts/blocks/src/system/constants/blockKinds.d.ts +1 -1
  39. package/dist/_dts/blocks/src/system/customize/backgrounds.d.ts +2 -5
  40. package/dist/_dts/blocks/src/system/data/loaderEndpoints.d.ts +1 -1
  41. package/dist/_dts/blocks/src/system/designCapabilities.d.ts +1 -0
  42. package/dist/_dts/blocks/src/system/fragments/library/newsletterForm.d.ts +2 -2
  43. package/dist/_dts/blocks/src/system/node/fragments/styledCard.d.ts +3 -0
  44. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +1 -1
  45. package/dist/_dts/blocks/src/system/runtime/blockComposition.d.ts +2 -0
  46. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +90 -6
  47. package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +45 -3
  48. package/dist/_dts/blocks/src/system/runtime/nodes/basic.d.ts +1 -0
  49. package/dist/_dts/blocks/src/system/runtime/nodes/commerce/shared/display.d.ts +2 -2
  50. package/dist/_dts/blocks/src/system/runtime/nodes/course-details/logic.d.ts +2 -1
  51. package/dist/_dts/blocks/src/system/runtime/nodes/course-details/view.d.ts +1 -0
  52. package/dist/_dts/blocks/src/system/runtime/nodes/course-details.server.d.ts +2 -1
  53. package/dist/_dts/blocks/src/system/runtime/nodes/course-page-surface-scope.d.ts +17 -0
  54. package/dist/_dts/blocks/src/system/runtime/nodes/course-registration/types.d.ts +3 -0
  55. package/dist/_dts/blocks/src/system/runtime/nodes/course-registration.interactive.d.ts +5 -0
  56. package/dist/_dts/blocks/src/system/runtime/nodes/course-registration.server.d.ts +1 -1
  57. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/eventScope.d.ts +9 -18
  58. package/dist/_dts/blocks/src/system/runtime/nodes/events/EventCalendar.interactive.d.ts +106 -0
  59. package/dist/_dts/blocks/src/system/runtime/nodes/events/RecurringSchedule.d.ts +11 -0
  60. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +7 -3
  61. package/dist/_dts/blocks/src/system/runtime/nodes/media.d.ts +3 -0
  62. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.interactive.d.ts +11 -1
  63. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.server.d.ts +1 -1
  64. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.types.d.ts +3 -0
  65. package/dist/_dts/blocks/src/system/runtime/nodes/offerings-preview.d.ts +6 -6
  66. package/dist/_dts/blocks/src/system/runtime/nodes/shop.interactive.d.ts +11 -5
  67. package/dist/_dts/blocks/src/system/runtime/nodes/shop.types.d.ts +2 -1
  68. package/dist/_dts/blocks/src/system/runtime/nodes/svg.d.ts +7 -0
  69. package/dist/_dts/core/src/branded.d.ts +44 -0
  70. package/dist/_dts/core/src/dated-offering-settlement.d.ts +47 -0
  71. package/dist/_dts/core/src/event-page-surface-subject.d.ts +44 -0
  72. package/dist/_dts/core/src/index.d.ts +49 -47
  73. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +1452 -15
  74. package/dist/_dts/editor-blocks/src/block-editor/FeatureCardIconsPanel.d.ts +1 -1
  75. package/dist/_dts/editor-blocks/src/block-editor/GeneratedIconSetDialog.d.ts +4 -1
  76. package/dist/_dts/media-core/src/index.d.ts +2 -1
  77. package/dist/_dts/media-core/src/storagePathDomain.d.ts +47 -0
  78. package/dist/_dts/media-core/src/url.d.ts +1 -21
  79. package/dist/_dts/preview-next/src/client/blocks/pageDesignRuntimePreview.d.ts +4 -2
  80. package/dist/_dts/preview-next/src/client/blocks/previewProjection/runtimeContentDecoration.d.ts +2 -1
  81. package/dist/_dts/preview-next/src/client/preview/PageBlockEditorPanel.d.ts +2 -1
  82. package/dist/_dts/preview-next/src/client/preview/PreviewEditorSidebar.d.ts +3 -9
  83. package/dist/_dts/preview-next/src/client/preview/PreviewShell.d.ts +7 -11
  84. package/dist/_dts/preview-next/src/client/preview/PreviewShellLayout.d.ts +3 -9
  85. package/dist/_dts/preview-next/src/client/preview/SiteChromeCustomizeContext.d.ts +2 -6
  86. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.d.ts +5 -9
  87. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.state.d.ts +81 -43
  88. package/dist/_dts/preview-next/src/client/preview/StyleConfigurator.typography.d.ts +61 -0
  89. package/dist/_dts/preview-next/src/client/preview/siteStylePreviewPlanner.d.ts +7 -17
  90. package/dist/_dts/preview-next/src/client/preview/styleConfiguratorSnapshot.d.ts +15 -8
  91. package/dist/_dts/preview-next/src/client/preview/typographyFontCatalog.d.ts +57 -0
  92. package/dist/_dts/sdk/src/cli/commands/pull.d.ts +1 -1
  93. package/dist/_dts/sdk/src/cli/commands/push/execute/theme.d.ts +4 -7
  94. package/dist/_dts/sdk/src/cli/commands/style.d.ts +24 -10
  95. package/dist/_dts/sdk/src/cli/site-commands/oneOffCommands.d.ts +3 -3
  96. package/dist/_dts/sdk/src/cli/sync/media.d.ts +16 -2
  97. package/dist/_dts/sdk/src/client/management/blocks.d.ts +1 -1
  98. package/dist/_dts/sdk/src/client/management/index.d.ts +1 -1
  99. package/dist/_dts/sdk/src/client/management/types.d.ts +132 -23
  100. package/dist/_dts/sdk/src/client/types.d.ts +2 -0
  101. package/dist/_dts/sdk/src/config/types.d.ts +1 -1
  102. package/dist/_dts/sdk/src/config/validation.d.ts +3 -0
  103. package/dist/_dts/sdk/src/contracts/index.d.ts +1 -1
  104. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +23 -3
  105. package/dist/_dts/sdk/src/data/dataLoaderContext.d.ts +17 -0
  106. package/dist/_dts/sdk/src/data/dataLoaderValue.d.ts +36 -0
  107. package/dist/_dts/sdk/src/data/defineOwnDataProperty.d.ts +2 -0
  108. package/dist/_dts/sdk/src/data/executeCodeLoaders.d.ts +36 -40
  109. package/dist/_dts/sdk/src/data/index.d.ts +3 -3
  110. package/dist/_dts/sdk/src/data/resolveBlockData.d.ts +54 -0
  111. package/dist/_dts/sdk/src/data/types.d.ts +43 -78
  112. package/dist/_dts/sdk/src/next/index.d.ts +1 -0
  113. package/dist/_dts/sdk/src/next/redirect-destination.d.ts +1 -1
  114. package/dist/_dts/sdk/src/next/resolveTheme.d.ts +2 -1
  115. package/dist/_dts/sdk/src/next/types.d.ts +10 -1
  116. package/dist/_dts/sdk/src/public-api/contracts.d.ts +3 -8
  117. package/dist/_dts/sdk/src/rendering/helpers/loadContent.d.ts +9 -4
  118. package/dist/_dts/sdk/src/rendering/helpers/loadPage.d.ts +6 -10
  119. package/dist/_dts/sdk/src/rendering/index.d.ts +1 -0
  120. package/dist/_dts/sdk/src/server.d.ts +4 -3
  121. package/dist/_dts/sdk/src/types/block-kinds.d.ts +1 -1
  122. package/dist/_dts/sdk/src/version.d.ts +1 -1
  123. package/dist/_dts/site-commands/src/capabilityGaps.d.ts +2 -2
  124. package/dist/_dts/site-commands/src/commands.d.ts +921 -30
  125. package/dist/_dts/theme-core/src/buttons/generateDefaultButtonSystem.d.ts +5 -1
  126. package/dist/_dts/theme-core/src/buttons/personalities/index.d.ts +11 -0
  127. package/dist/_dts/theme-core/src/buttons/personalities/quiet-arrow.d.ts +17 -0
  128. package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +2 -2
  129. package/dist/_dts/theme-core/src/buttons/types.d.ts +9 -5
  130. package/dist/_dts/theme-core/src/data.d.ts +12 -0
  131. package/dist/_dts/theme-core/src/fonts/index.d.ts +1 -1
  132. package/dist/_dts/theme-core/src/fonts/types.d.ts +6 -0
  133. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  134. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +36 -24
  135. package/dist/_dts/theme-core/src/palette/colors.d.ts +12 -0
  136. package/dist/_dts/theme-core/src/palette/curated/brand-led.d.ts +10 -5
  137. package/dist/_dts/theme-core/src/palette/curated/high-contrast.d.ts +6 -3
  138. package/dist/_dts/theme-core/src/palette/curated/index.d.ts +31 -236
  139. package/dist/_dts/theme-core/src/palette/curated/soft-natural.d.ts +8 -4
  140. package/dist/_dts/theme-core/src/palette/curated/types.d.ts +14 -12
  141. package/dist/_dts/theme-core/src/palette/curated/warm-dark.d.ts +4 -2
  142. package/dist/_dts/theme-core/src/palette/curated/warm-neutral.d.ts +8 -4
  143. package/dist/_dts/theme-core/src/palette/editorState.d.ts +33 -3
  144. package/dist/_dts/theme-core/src/palette/index.d.ts +0 -4
  145. package/dist/_dts/theme-core/src/palette/libraryModel.d.ts +12 -6
  146. package/dist/_dts/theme-core/src/palette/persistenceReadSource.d.ts +20 -72
  147. package/dist/_dts/theme-core/src/palette/resolveSelection.d.ts +29 -4
  148. package/dist/_dts/theme-core/src/palette/selection.d.ts +6 -4
  149. package/dist/_dts/theme-core/src/schema.d.ts +121 -4
  150. package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +218 -158
  151. package/dist/_dts/theme-core/src/site-styles/appearancePresetTypes.d.ts +5 -6
  152. package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +218 -158
  153. package/dist/_dts/theme-core/src/site-styles/compositionBudget.d.ts +1 -0
  154. package/dist/_dts/theme-core/src/site-styles/contentTemplateRuntimeCompiler.d.ts +2 -1
  155. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +161 -51
  156. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +6 -0
  157. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -2
  158. package/dist/_dts/theme-core/src/site-styles/index.d.ts +1 -1
  159. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorModel.d.ts +24 -1
  160. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +14 -48
  161. package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +10 -23
  162. package/dist/_dts/theme-core/src/site-styles/siteStyleThemeCompiler.d.ts +1 -2
  163. package/dist/_dts/theme-core/src/site-styles/siteStyleTypography.d.ts +456 -0
  164. package/dist/_dts/theme-core/src/site-styles/types.d.ts +52 -3
  165. package/dist/cli/index.mjs +3155 -1701
  166. package/dist/client/{DiscountCodeDisclosure-BXM73_E5.mjs → DiscountCodeDisclosure-BwGkvmVH.mjs} +1 -1
  167. package/dist/client/{EventCombined.client-DT5elP7j.mjs → EventCombined.client-JnWE6Hzc.mjs} +3 -3
  168. package/dist/client/{EventListing.client-pTYGfV9C.mjs → EventListing.client-DQewc_kg.mjs} +2 -2
  169. package/dist/client/{LeafletStaticMap-dQrhlUe3.mjs → LeafletStaticMap-DPAU3SFt.mjs} +1 -1
  170. package/dist/client/{MagicLinkForm-u6Dul4j5.mjs → MagicLinkForm-D4kpWCbW.mjs} +2 -2
  171. package/dist/client/{StepIndicator-nWqineJ2.mjs → StepIndicator-CBlkb4rA.mjs} +2 -2
  172. package/dist/client/{accordion-CSJcTUyH.mjs → accordion-DAIBVpTS.mjs} +1 -1
  173. package/dist/client/{bookingForm-C73wOuVa.mjs → bookingForm-DZ-4l5_1.mjs} +7 -7
  174. package/dist/client/bookings.mjs +239 -110
  175. package/dist/client/{buttons-DfYyqt8C.mjs → buttons-DJgynGHQ.mjs} +122 -58
  176. package/dist/client/{carousel-fMBb0JSc.mjs → carousel-D-SLfX8r.mjs} +1 -1
  177. package/dist/client/{cart-BtaCwT3S.mjs → cart-CEgbgJhX.mjs} +3 -3
  178. package/dist/client/{checkout-DkTwZSnh.mjs → checkout-CXWrCRAA.mjs} +3 -3
  179. package/dist/client/client.mjs +4300 -1560
  180. package/dist/client/{colorStyles-Wyl6JujD.mjs → colorStyles-jZS6KfkP.mjs} +1 -1
  181. package/dist/client/{colorTokenParts-dT8MISgJ.mjs → colorTokenParts-CMugwvX_.mjs} +3 -1
  182. package/dist/client/{courseRegistration-DnLmapJu.mjs → courseRegistration-DVmLrRVz.mjs} +56 -21
  183. package/dist/client/{eventCalendar-BvLOc9KY.mjs → eventCalendar-DCuyPY_c.mjs} +29 -21
  184. package/dist/client/{eventCombined-kShgJPQr.mjs → eventCombined-kP2CA2D4.mjs} +2 -2
  185. package/dist/client/{eventDetails-C5NTM0Vo.mjs → eventDetails-DU0LeF5e.mjs} +4 -4
  186. package/dist/client/{eventListing-B82ZdKP6.mjs → eventListing-CcHF87BI.mjs} +2 -2
  187. package/dist/client/{eventRegistration-qJVFqBWo.mjs → eventRegistration-DJnR0yFH.mjs} +8 -8
  188. package/dist/client/{form-BvAcKiuu.mjs → form-CH1lCusH.mjs} +2 -2
  189. package/dist/client/{headerSection-NvAiEHAg.mjs → headerSection-CBPFZoku.mjs} +3 -3
  190. package/dist/client/hooks.mjs +2958 -1848
  191. package/dist/client/{locationMap-D0j52xfI.mjs → locationMap-Cx1hBg5n.mjs} +3 -3
  192. package/dist/client/{media-BCW_g2cQ.mjs → media-DHtSxS5V.mjs} +61 -56
  193. package/dist/client/{newsletterForm-B8_i93qH.mjs → newsletterForm-1ZL_ti9u.mjs} +3 -3
  194. package/dist/client/{pricing-DIYnyHOk.mjs → pricing-BIbzmV0G.mjs} +1 -1
  195. package/dist/client/{productDetail-w0eAHkuQ.mjs → productDetail-BTh5gHBV.mjs} +3 -3
  196. package/dist/client/{productList-Vsguq5Xt.mjs → productList-Cc7OMrqN.mjs} +3 -3
  197. package/dist/client/{propsDecoder-CD2pJ1WB.mjs → propsDecoder-oR-M-ISN.mjs} +192 -59
  198. package/dist/client/rendering/client.mjs +2031 -1239
  199. package/dist/client/rendering/islands.mjs +20 -20
  200. package/dist/client/rendering.mjs +4889 -1712
  201. package/dist/client/{shop-Dgxs-Q8F.mjs → shop-CQ_YbCnn.mjs} +8 -8
  202. package/dist/client/{shop-commerce-DJAhsTaT.mjs → shop-commerce-BM-AoD3M.mjs} +6 -6
  203. package/dist/client/{shop-commerce-store-CfCXltXW.mjs → shop-commerce-store-DFMQhU5p.mjs} +1 -1
  204. package/dist/client/{shopShared-CmDcPXUt.mjs → shopShared-D0SKO2Cu.mjs} +3 -3
  205. package/dist/client/{useDiscountCodeUi-CirrT2bM.mjs → useDiscountCodeUi-CrG4_ARt.mjs} +1 -1
  206. package/dist/client/{useEventFilters-CNZxlCcO.mjs → useEventFilters-CxK8Fekl.mjs} +6 -6
  207. package/dist/client/{usePolling-Baks_iBI.mjs → usePolling-jcVryNgZ.mjs} +2 -2
  208. package/dist/preview-next/before-render.mjs +40 -1
  209. package/dist/preview-next/client/runtime.mjs +4 -4
  210. package/dist/preview-next/middleware.mjs +40 -1
  211. package/dist/{previewAuthStore-CfquDtTh.mjs → previewAuthStore-BPcJPzdz.mjs} +40 -1
  212. package/dist/{sdk-runtime-BfNiFeNJ.mjs → sdk-runtime-C39Rsk-x.mjs} +2 -2
  213. package/dist/{separator-BhAR_Xmf.mjs → separator-BoMSQnsP.mjs} +1 -1
  214. package/dist/server/components.mjs +4661 -2162
  215. package/dist/server/config-validation.mjs +2728 -1847
  216. package/dist/server/config.mjs +2728 -1847
  217. package/dist/server/data.mjs +3381 -1915
  218. package/dist/server/index.mjs +46 -4
  219. package/dist/server/next/middleware.mjs +1 -1
  220. package/dist/server/next.mjs +5555 -2225
  221. package/dist/server/page-converter.mjs +3058 -2182
  222. package/dist/server/prebuild.mjs +1 -1
  223. package/dist/server/rendering/server.mjs +4850 -2125
  224. package/dist/server/rendering.mjs +5664 -2464
  225. package/dist/server/routing.mjs +3027 -1509
  226. package/dist/server/server.mjs +3844 -2217
  227. package/dist/server/theme-bridge.mjs +9121 -7765
  228. package/dist/server/theme.mjs +152 -59
  229. package/dist/styles/index.css +1217 -31
  230. package/package.json +1 -1
  231. package/dist/_dts/preview-next/src/client/preview/typographySelectionInput.d.ts +0 -16
  232. package/dist/_dts/theme-core/src/palette/historicalThemePalette.d.ts +0 -44
  233. package/dist/_dts/theme-core/src/palette/legacyRuntimeProjection.d.ts +0 -55
  234. package/dist/_dts/theme-core/src/palette/legacySelection.d.ts +0 -45
  235. package/dist/_dts/theme-core/src/palette/paletteCompatibilityTransition.d.ts +0 -232
  236. package/dist/_dts/theme-core/src/site-styles/typographyPresets.d.ts +0 -234
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { $i as getFooterLayout, $l as CollapsibleContent, A as openRow, Ai as getCuratedSiteStyle, B as setPaletteColors, Bc as buttonPersonalities, Bd as Maximize2, Bt as ManifestForm, C as createStyleConfiguratorState, D as markApplyFailed, Dl as SelectItem, E as isPreviewingChange, Ed as Tablet, El as SelectContent, F as previewHeaderLayout, Fl as Input, Gt as createStoreBackend, H as syncStyleConfiguratorState, Ht as createManifestValidation, I as previewHeaderStyle, Il as Tabs, Ji as evaluateTypographyFontSlot, L as previewSiteStyle, Ll as TabsContent, M as previewButtonPersonality, Ml as Popover, Mt as deriveRouteMetadataPresentation, N as previewCuratedPalette, Nl as PopoverContent, Nt as resolvePageRouteSlug, O as markApplySaved, Oi as curatedSiteStyles, Ol as SelectTrigger, P as previewFooterLook, Pl as PopoverTrigger, Pt as resolveContentTitle, Pu as cn, Q as resolvePreviewSidebarTab, Qi as findFooterLook, Ql as Collapsible, R as previewTypographyPreset, Rd as Monitor, Rl as TabsList, S as cancelPreview, T as dismissWarnings, Tl as Select, U as toggleRow, Ut as FormRenderer, V as styleConfiguratorHistoryEntryForPersistedSelection, Vc as findButtonPersonality, Vd as LoaderCircle, W as typographySelectionSnapshotFromFlat, Xi as typographyPresets, Yi as findTypographyPreset, Z as createOptimisticActionRunner, Zi as requireEntriesForCuratedChoices, _ as usePreviewDesignCoordinator, _a as paletteColorsSchema, _t as useEditorUiStore, ad as assertNever$1, b as applyResolvedSelections, bd as X$1, c as selectionSnapshotFromApplySiteStyleSelectionResponse, d as buildSiteChromeLookModels, dt as useUISelector, du as DropdownMenuContent, ea as getHeaderLayout, en as listEntryPageContentSlots, eu as CollapsibleTrigger, f as cycleSiteChromeLookId, ff as ChevronRight, fu as DropdownMenuItem, ga as paletteColorsEqual, h as planSiteChromeLookSelection, ha as paletteColorEntries, hu as DropdownMenuTrigger, j as paletteMutationCapability, ju as Button, k as markApplySaving, kd as Smartphone, kl as SelectValue, l as planSiteStylePreviewChange, lu as Surface, m as planSiteChromeFooterLayoutSelection, ma as findCuratedPalette, mt as useEntryController, mu as DropdownMenuSeparator, n as useSiteChromeCustomizeOptional, p as cycleableSiteChromeOptions, pa as curatedPalettes, pf as ChevronLeft, pu as DropdownMenuLabel, s as buildApplySiteStyleSelectionBody, ta as getHeaderStyle, u as resolveStyleConfiguratorInitialSnapshot, ut as ContentFieldsForm, uu as DropdownMenu, vt as useEditorUiStoreInstance, w as deriveStyleConfiguratorCapabilities, x as applyTypographyDraftPatch, z as resetTypographyToSaved, zl as TabsTrigger, zu as unsafeAsEditorBlockId } from "./SiteChromeCustomizeContext-Cx0NMdyv.mjs";
3
- import { d as unwrapResponse, f as getDashboardUrl, r as usePreviewAuth } from "./previewAuthStore-CfquDtTh.mjs";
4
- import { S as isAdapterReferenceKind, T as CalloutDescription, _ as usePreviewBlockCustomizeSelectionOptional, b as previewWidgetOverrides, d as buildPreviewDashboardSetupUrl, f as resolvePreviewDashboardSetupHandoff, g as canInspectSiteStyle, h as canApplySiteStyle, m as resolvePreviewSidebarChromeModel, p as resolvePreviewEditorGuidanceModel, v as useBlockCustomizeControls, w as Callout, x as BlockEditor, y as PreviewFormProvider } from "./SdkPreviewModeRuntime-Iq12HjL7.mjs";
5
- import { t as Separator } from "./separator-BhAR_Xmf.mjs";
2
+ import { A as markApplySaved, B as previewHeaderStyle, Bl as Popover, C as committedPaletteForSnapshot, Cf as ChevronLeft, Cu as DropdownMenuTrigger, D as dismissWarnings, E as deriveStyleConfiguratorCapabilities, F as prepareStalePaletteRetry, Fd as Tablet, Fl as SelectTrigger, G as setPaletteColors, Gl as TabsContent, H as previewSiteStyle, Hl as PopoverTrigger, Hu as cn, I as previewButtonPersonality, Ii as getCuratedSiteStyle, Il as SelectValue, It as deriveRouteMetadataPresentation, J as toggleRow, Jd as Monitor, K as styleConfiguratorHistoryEntryForPersistedSelection, Kc as buttonPersonalities, Kl as TabsList, Kt as createManifestValidation, L as previewCuratedPalette, Lt as resolvePageRouteSlug, M as openRow, Ml as Select, N as paletteMutationCapability, Nl as SelectContent, O as isPreviewingChange, P as palettePreviewProjection, Pi as curatedSiteStyles, Pl as SelectItem, R as previewFooterLook, Rd as Smartphone, Rt as resolveContentTitle, S as cancelPreview, Sf as ChevronRight, St as useEditorUiStoreInstance, Su as DropdownMenuSeparator, T as createStyleConfiguratorState, Tf as Check, U as reconcilePaletteLibraryActive, Ul as Input, V as previewSitePalette, Vl as PopoverContent, W as resetTypographyToSaved, Wd as Plus, Wl as Tabs, Wt as ManifestForm, Xd as Maximize2, Yt as createStoreBackend, Zd as LoaderCircle, _ as usePreviewDesignCoordinator, _a as findCuratedPalette, _u as Surface, b as applyResolvedSelections, ba as paletteColorsEqual, bu as DropdownMenuItem, c as selectionSnapshotFromApplySiteStyleSelectionResponse, cu as CollapsibleTrigger, d as cycleSiteChromeLookId, ea as requireEntriesForCuratedChoices, f as cycleableSiteChromeOptions, ga as curatedPalettes, h as planSiteStylePreviewChange, ht as useUISelector, ia as getHeaderStyle, in as listEntryPageContentSlots, j as markApplySaving, k as markApplyFailed, kd as X$1, l as resolveStyleConfiguratorInitialSnapshot, m as planSiteChromeLookSelection, md as assertNever$1, mt as ContentFieldsForm, n as useSiteChromeCustomizeOptional, na as getFooterLayout, nt as resolvePreviewSidebarTab, ou as Collapsible, p as planSiteChromeFooterLayoutSelection, q as syncStyleConfiguratorState, qc as findButtonPersonality, ql as TabsTrigger, qt as FormRenderer, qu as unsafeAsEditorBlockId, ra as getHeaderLayout, s as buildApplySiteStyleSelectionBody, su as CollapsibleContent, ta as findFooterLook, tt as createOptimisticActionRunner, u as buildSiteChromeLookModels, va as buildPaletteLibraryReadModel, vt as useEntryController, vu as DropdownMenu, w as createPalette, x as applyTypographyDraftPatch, xa as paletteColorsSchema, xt as useEditorUiStore, xu as DropdownMenuLabel, ya as paletteColorEntries, yu as DropdownMenuContent, z as previewHeaderLayout, zd as SlidersHorizontal, zl as SearchInput, zu as Button } from "./SiteChromeCustomizeContext-C_sizso-.mjs";
3
+ import { d as unwrapResponse, f as getDashboardUrl, r as usePreviewAuth } from "./previewAuthStore-BPcJPzdz.mjs";
4
+ import { C as buildFontPreviewUrl, D as Callout, O as CalloutDescription, S as isAdapterReferenceKind, T as getSiteFontsStorageUrl, _ as usePreviewBlockCustomizeSelectionOptional, b as previewWidgetOverrides, d as buildPreviewDashboardSetupUrl, f as resolvePreviewDashboardSetupHandoff, g as canInspectSiteStyle, h as canApplySiteStyle, m as resolvePreviewSidebarChromeModel, p as resolvePreviewEditorGuidanceModel, v as useBlockCustomizeControls, w as generateFontFaceCss, x as BlockEditor, y as PreviewFormProvider } from "./SdkPreviewModeRuntime-CVB7urtl.mjs";
5
+ import { t as Separator } from "./separator-BoMSQnsP.mjs";
6
6
  import React, { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, useTransition } from "react";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { useQuery, useQueryClient } from "@tanstack/react-query";
@@ -1564,7 +1564,7 @@ function formatBlockKindLabel(blockKind) {
1564
1564
  }
1565
1565
  //#endregion
1566
1566
  //#region ../preview-next/src/client/preview/PageBlockEditorPanel.tsx
1567
- function PageBlockEditorPanel({ siteId, pageId, dashboardBaseUrl, theme, apiClient, bearerToken, supabaseUrl, blockItems, uiStoreForBlockEditor, blockEditorRef, onBlocksChange, metadataPanel, linkedEntrySection, designPanel, designOpenRequestId = null, sdkConfig, mediaAssets, bookingOfferingPresets, viewportMode, viewportWidth, zoom, onViewportModeChange, onViewportWidthChange, onZoomChange }) {
1567
+ function PageBlockEditorPanel({ siteId, pageId, dashboardBaseUrl, theme, apiClient, bearerToken, supabaseUrl, blockItems, uiStoreForBlockEditor, blockEditorRef, onBlocksChange, metadataPanel, linkedEntrySection, designPanel, designOpenRequestId = null, designTakeoverActive = false, sdkConfig, mediaAssets, bookingOfferingPresets, viewportMode, viewportWidth, zoom, onViewportModeChange, onViewportWidthChange, onZoomChange }) {
1568
1568
  const customizeSelection = usePreviewBlockCustomizeSelectionOptional();
1569
1569
  const [designOpen, setDesignOpen] = useState(false);
1570
1570
  const [customizeOpen, setCustomizeOpen] = useState(false);
@@ -1627,35 +1627,48 @@ function PageBlockEditorPanel({ siteId, pageId, dashboardBaseUrl, theme, apiClie
1627
1627
  bookingOfferingPresets,
1628
1628
  sdkConfig: sdkConfig ?? null,
1629
1629
  children: /* @__PURE__ */ jsxs("div", {
1630
- className: "min-h-0 flex-1 space-y-0 overflow-auto",
1630
+ className: `min-h-0 flex-1 space-y-0 ${designTakeoverActive ? "flex flex-col overflow-hidden" : "overflow-auto"}`,
1631
+ "data-design-takeover-active": designTakeoverActive ? "true" : "false",
1631
1632
  children: [
1632
- linkedEntrySection ? /* @__PURE__ */ jsx(PreviewSidebarSection, {
1633
- label: linkedEntrySection.label,
1634
- openRequestId: linkedEntrySection.openRequestId,
1635
- children: linkedEntrySection.content
1633
+ linkedEntrySection ? /* @__PURE__ */ jsx("div", {
1634
+ hidden: designTakeoverActive,
1635
+ "data-design-takeover-sibling": true,
1636
+ children: /* @__PURE__ */ jsx(PreviewSidebarSection, {
1637
+ label: linkedEntrySection.label,
1638
+ openRequestId: linkedEntrySection.openRequestId,
1639
+ children: linkedEntrySection.content
1640
+ })
1636
1641
  }) : null,
1637
1642
  designPanel ? /* @__PURE__ */ jsx(Collapsible, {
1638
- open: designOpen,
1639
- onOpenChange: setDesignOpen,
1643
+ className: designTakeoverActive ? "flex min-h-0 flex-1 flex-col overflow-hidden" : void 0,
1644
+ open: designTakeoverActive || designOpen,
1645
+ onOpenChange: (open) => {
1646
+ if (!designTakeoverActive) setDesignOpen(open);
1647
+ },
1640
1648
  children: /* @__PURE__ */ jsxs("div", {
1641
1649
  ref: designSectionRef,
1642
- className: "border-b",
1650
+ className: designTakeoverActive ? "flex min-h-0 flex-1 flex-col overflow-hidden" : "border-b",
1643
1651
  children: [/* @__PURE__ */ jsx(CollapsibleTrigger, {
1644
1652
  asChild: true,
1645
1653
  children: /* @__PURE__ */ jsxs("div", {
1646
- className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40",
1647
- children: [/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${designOpen ? "rotate-90" : ""}` }), /* @__PURE__ */ jsx("span", {
1654
+ className: `flex shrink-0 cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40 ${designTakeoverActive ? "border-b" : ""}`,
1655
+ children: [/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${designTakeoverActive || designOpen ? "rotate-90" : ""}` }), /* @__PURE__ */ jsx("span", {
1648
1656
  className: "text-sm font-semibold",
1649
1657
  children: "Design"
1650
1658
  })]
1651
1659
  })
1652
- }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", {
1653
- className: "px-4 pb-4",
1654
- children: designPanel
1655
- }) })]
1660
+ }), /* @__PURE__ */ jsx(CollapsibleContent, {
1661
+ className: designTakeoverActive ? "min-h-0 flex-1 data-[state=open]:h-full data-[state=open]:overflow-hidden" : void 0,
1662
+ children: /* @__PURE__ */ jsx("div", {
1663
+ className: designTakeoverActive ? "h-full min-h-0" : "px-4 pb-4",
1664
+ children: designPanel
1665
+ })
1666
+ })]
1656
1667
  })
1657
1668
  }) : null,
1658
1669
  /* @__PURE__ */ jsx(Collapsible, {
1670
+ hidden: designTakeoverActive,
1671
+ "data-design-takeover-sibling": true,
1659
1672
  open: customizeOpen,
1660
1673
  onOpenChange: setCustomizeOpen,
1661
1674
  children: /* @__PURE__ */ jsxs("div", {
@@ -1682,6 +1695,8 @@ function PageBlockEditorPanel({ siteId, pageId, dashboardBaseUrl, theme, apiClie
1682
1695
  })
1683
1696
  }),
1684
1697
  /* @__PURE__ */ jsx(Collapsible, {
1698
+ hidden: designTakeoverActive,
1699
+ "data-design-takeover-sibling": true,
1685
1700
  open: contentOpen,
1686
1701
  onOpenChange: setContentOpen,
1687
1702
  children: /* @__PURE__ */ jsxs("div", {
@@ -1718,6 +1733,8 @@ function PageBlockEditorPanel({ siteId, pageId, dashboardBaseUrl, theme, apiClie
1718
1733
  })
1719
1734
  }),
1720
1735
  metadataPanel ? /* @__PURE__ */ jsx(Collapsible, {
1736
+ hidden: designTakeoverActive,
1737
+ "data-design-takeover-sibling": true,
1721
1738
  open: detailsOpen,
1722
1739
  onOpenChange: setDetailsOpen,
1723
1740
  children: /* @__PURE__ */ jsxs("div", {
@@ -2061,712 +2078,516 @@ function ContrastSwatch({ hex, label }) {
2061
2078
  });
2062
2079
  }
2063
2080
  //#endregion
2064
- //#region ../preview-next/src/client/preview/StyleConfigurator.tsx
2065
- const MAX_FONT_RESULTS_WHEN_SEARCHING = 12;
2066
- const MAX_FONT_RESULTS_WHEN_BROWSING = 8;
2067
- const PALETTE_BRAND_ROLES = [
2081
+ //#region ../preview-next/src/client/preview/typographyFontCatalog.ts
2082
+ const typographyFontCategories = [
2068
2083
  {
2069
- key: "primary",
2070
- label: "Primary"
2084
+ value: "all",
2085
+ label: "All"
2071
2086
  },
2072
2087
  {
2073
- key: "primaryForeground",
2074
- label: "Primary text"
2088
+ value: "sans-serif",
2089
+ label: "Sans"
2075
2090
  },
2076
2091
  {
2077
- key: "secondary",
2078
- label: "Secondary"
2092
+ value: "serif",
2093
+ label: "Serif"
2079
2094
  },
2080
2095
  {
2081
- key: "accent",
2082
- label: "Accent"
2083
- }
2084
- ];
2085
- const PALETTE_FEEDBACK_ROLES = [
2086
- {
2087
- key: "success",
2088
- label: "Success"
2096
+ value: "display",
2097
+ label: "Display"
2089
2098
  },
2090
2099
  {
2091
- key: "warning",
2092
- label: "Warning"
2100
+ value: "handwriting",
2101
+ label: "Handwriting"
2093
2102
  },
2094
2103
  {
2095
- key: "destructive",
2096
- label: "Destructive"
2104
+ value: "monospace",
2105
+ label: "Mono"
2097
2106
  }
2098
2107
  ];
2099
- const PALETTE_SURFACE_ROLES = [
2100
- {
2101
- key: "background",
2102
- label: "Background"
2103
- },
2104
- {
2105
- key: "surface",
2106
- label: "Surface"
2107
- },
2108
- {
2109
- key: "surfaceAlt",
2110
- label: "Alternate surface"
2111
- },
2112
- {
2113
- key: "muted",
2114
- label: "Muted surface"
2115
- },
2116
- {
2117
- key: "text",
2118
- label: "Text"
2119
- },
2120
- {
2121
- key: "border",
2122
- label: "Border"
2108
+ function buildTypographyFontCatalog(input) {
2109
+ const normalizedQuery = input.query.trim().toLocaleLowerCase();
2110
+ const site = input.siteFonts.map(siteFontOption).filter((option) => fontOptionMatches(option, normalizedQuery, input.category)).sort(compareFontOptionsAlphabetically);
2111
+ const siteFamilies = new Set(input.siteFonts.map((font) => familyKey(font.familyName)));
2112
+ return {
2113
+ site,
2114
+ google: input.googleFonts.map(googleFontOption).filter((option) => !siteFamilies.has(familyKey(option.family))).filter((option) => fontOptionMatches(option, normalizedQuery, input.category)).sort(compareGoogleOptions)
2115
+ };
2116
+ }
2117
+ function visibleTypographyFontOptions(input) {
2118
+ const initial = input.options.slice(0, input.limit);
2119
+ if (input.limit <= 0 || initial.some((option) => familyKey(option.family) === familyKey(input.selectedFamily))) return initial;
2120
+ const selected = input.options.find((option) => familyKey(option.family) === familyKey(input.selectedFamily));
2121
+ if (!selected) return initial;
2122
+ return [...initial.slice(0, Math.max(0, input.limit - 1)), selected];
2123
+ }
2124
+ function fontOptionForFamily(family, catalog) {
2125
+ const key = familyKey(primaryFontFamily(family));
2126
+ const site = catalog.siteFonts.find((font) => familyKey(font.familyName) === key);
2127
+ if (site) return siteFontOption(site);
2128
+ const google = catalog.googleFonts.find((font) => familyKey(font.family) === key);
2129
+ return google ? googleFontOption(google) : null;
2130
+ }
2131
+ function primaryFontFamily(cssFamily) {
2132
+ return cssFamily.split(",")[0]?.trim().replace(/^["']|["']$/gu, "") || cssFamily;
2133
+ }
2134
+ function siteFontOption(font) {
2135
+ return {
2136
+ source: "site",
2137
+ family: font.familyName,
2138
+ label: font.displayName ?? font.familyName,
2139
+ category: normalizeCategory(font.category),
2140
+ siteFont: font
2141
+ };
2142
+ }
2143
+ function googleFontOption(font) {
2144
+ return {
2145
+ source: "google",
2146
+ family: font.family,
2147
+ label: font.family,
2148
+ category: normalizeCategory(font.category),
2149
+ googleFont: font
2150
+ };
2151
+ }
2152
+ function normalizeCategory(category) {
2153
+ switch (category) {
2154
+ case "sans-serif":
2155
+ case "serif":
2156
+ case "display":
2157
+ case "handwriting":
2158
+ case "monospace": return category;
2159
+ default: return null;
2123
2160
  }
2124
- ];
2125
- function StyleConfigurator({ siteId, apiClient, theme, selectionId, selectionVersion = null, selectionUpdatedAt, initialSelections, headerSelectionContext, footerSelectionContext, expandedRowRequest = null, siteStyleAccess }) {
2126
- const router = useRouter();
2127
- const previewDesign = usePreviewDesignCoordinator();
2128
- const siteChromeCustomize = useSiteChromeCustomizeOptional();
2129
- const setSiteStyleStagedPreviewActive = siteChromeCustomize?.siteStyleHistory?.setStagedPreviewActive;
2130
- const { applyFailed, applyStarted, applySucceeded, clearPreview, previewTheme } = previewDesign;
2131
- const [isRefreshPending, startRefreshTransition] = useTransition();
2132
- const baseSnapshot = useMemo(() => resolveStyleConfiguratorInitialSnapshot({
2133
- theme,
2134
- initialSelections: {
2135
- ...initialSelections,
2136
- selectionVersion: initialSelections.selectionVersion ?? selectionVersion ?? 0,
2137
- selectionUpdatedAt: initialSelections.selectionUpdatedAt ?? selectionUpdatedAt ?? null
2138
- },
2139
- headerSelectionContext,
2140
- footerSelectionContext
2141
- }), [
2142
- footerSelectionContext,
2143
- headerSelectionContext,
2144
- theme,
2145
- initialSelections,
2146
- selectionUpdatedAt,
2147
- selectionVersion
2148
- ]);
2149
- const [state, setState] = useState(() => baseSnapshot ? createStyleConfiguratorState(baseSnapshot) : null);
2150
- const hasLoadedState = state !== null;
2151
- const hasStagedSiteStylePreview = state ? isPreviewingChange(state) : false;
2152
- const stateRef = useRef(state);
2153
- const clearPreviewRef = useRef(clearPreview);
2154
- const runnerRef = useRef(createOptimisticActionRunner());
2155
- const appliedExpandedRowRequestIdRef = useRef(null);
2156
- useEffect(() => {
2157
- stateRef.current = state;
2158
- }, [state]);
2159
- useEffect(() => {
2160
- clearPreviewRef.current = clearPreview;
2161
- }, [clearPreview]);
2161
+ }
2162
+ function fontOptionMatches(option, normalizedQuery, category) {
2163
+ const matchesQuery = normalizedQuery.length === 0 || option.family.toLocaleLowerCase().includes(normalizedQuery) || option.label.toLocaleLowerCase().includes(normalizedQuery);
2164
+ const matchesCategory = category === "all" || option.category === category;
2165
+ return matchesQuery && matchesCategory;
2166
+ }
2167
+ function compareGoogleOptions(left, right) {
2168
+ if (left.source !== "google" || right.source !== "google") return compareFontOptionsAlphabetically(left, right);
2169
+ return (left.googleFont.popularity ?? Number.POSITIVE_INFINITY) - (right.googleFont.popularity ?? Number.POSITIVE_INFINITY) || compareFontOptionsAlphabetically(left, right);
2170
+ }
2171
+ function compareFontOptionsAlphabetically(left, right) {
2172
+ return left.label.localeCompare(right.label);
2173
+ }
2174
+ function familyKey(family) {
2175
+ return family.trim().toLocaleLowerCase();
2176
+ }
2177
+ //#endregion
2178
+ //#region ../preview-next/src/client/preview/StyleConfigurator.typography.tsx
2179
+ const typographyOverviewSurface = { kind: "overview" };
2180
+ function useTypographyFontCatalog(apiClient, siteId, enabled) {
2181
+ const [google, setGoogle] = useState({
2182
+ kind: "idle",
2183
+ value: []
2184
+ });
2185
+ const [site, setSite] = useState({
2186
+ kind: "idle",
2187
+ value: []
2188
+ });
2189
+ const googleRequestRef = useRef(null);
2190
+ const siteRequestRef = useRef(null);
2162
2191
  useEffect(() => {
2163
- if (!setSiteStyleStagedPreviewActive) return;
2164
- setSiteStyleStagedPreviewActive(hasStagedSiteStylePreview);
2192
+ if (!enabled) return;
2193
+ if (googleRequestRef.current?.apiClient === apiClient) return;
2194
+ let cancelled = false;
2195
+ googleRequestRef.current = {
2196
+ apiClient,
2197
+ status: "loading"
2198
+ };
2199
+ setGoogle((current) => ({
2200
+ kind: "loading",
2201
+ value: current.value
2202
+ }));
2203
+ async function loadGoogleFonts() {
2204
+ try {
2205
+ const value = unwrapResponse(await apiClient({ endpoint: "listFonts" }));
2206
+ if (!cancelled) {
2207
+ googleRequestRef.current = {
2208
+ apiClient,
2209
+ status: "loaded"
2210
+ };
2211
+ setGoogle({
2212
+ kind: "loaded",
2213
+ value: Array.isArray(value) ? value : []
2214
+ });
2215
+ }
2216
+ } catch (error) {
2217
+ if (!cancelled) {
2218
+ googleRequestRef.current = null;
2219
+ setGoogle({
2220
+ kind: "failed",
2221
+ value: [],
2222
+ message: errorMessage(error, "Unable to load Google Fonts.")
2223
+ });
2224
+ }
2225
+ }
2226
+ }
2227
+ loadGoogleFonts();
2165
2228
  return () => {
2166
- setSiteStyleStagedPreviewActive(false);
2229
+ cancelled = true;
2230
+ if (googleRequestRef.current?.status === "loading") googleRequestRef.current = null;
2167
2231
  };
2168
- }, [hasStagedSiteStylePreview, setSiteStyleStagedPreviewActive]);
2169
- useEffect(() => {
2170
- if (!baseSnapshot) return;
2171
- setState((current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot));
2172
- }, [baseSnapshot]);
2232
+ }, [apiClient, enabled]);
2173
2233
  useEffect(() => {
2174
- if (!state || !baseSnapshot) {
2175
- clearPreview("style-configurator");
2176
- return;
2177
- }
2178
- if (!isPreviewingChange(state)) {
2179
- clearPreview("style-configurator");
2180
- return;
2181
- }
2182
- const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
2183
- if (!siteStyle) {
2184
- clearPreview("style-configurator");
2185
- return;
2234
+ if (!enabled) return;
2235
+ if (siteRequestRef.current?.apiClient === apiClient && siteRequestRef.current.siteId === siteId) return;
2236
+ let cancelled = false;
2237
+ siteRequestRef.current = {
2238
+ apiClient,
2239
+ siteId,
2240
+ status: "loading"
2241
+ };
2242
+ setSite((current) => ({
2243
+ kind: "loading",
2244
+ value: current.value
2245
+ }));
2246
+ async function loadSiteFonts() {
2247
+ try {
2248
+ const value = unwrapResponse(await apiClient({
2249
+ endpoint: "listSiteFonts",
2250
+ params: { siteId }
2251
+ }));
2252
+ if (!cancelled) {
2253
+ siteRequestRef.current = {
2254
+ apiClient,
2255
+ siteId,
2256
+ status: "loaded"
2257
+ };
2258
+ setSite({
2259
+ kind: "loaded",
2260
+ value: Array.isArray(value?.fonts) ? value.fonts : []
2261
+ });
2262
+ }
2263
+ } catch (error) {
2264
+ if (!cancelled) {
2265
+ siteRequestRef.current = null;
2266
+ setSite({
2267
+ kind: "failed",
2268
+ value: [],
2269
+ message: errorMessage(error, "Unable to load your fonts.")
2270
+ });
2271
+ }
2272
+ }
2186
2273
  }
2187
- previewTheme("style-configurator", planSiteStylePreviewChange({
2188
- siteStyle,
2189
- selection: state.previewed,
2190
- baseTheme: theme,
2191
- headerSelectionContext,
2192
- footerSelectionContext
2193
- }).themeOverlay, "site-style");
2194
- }, [
2195
- state,
2196
- baseSnapshot,
2197
- clearPreview,
2198
- previewTheme,
2199
- headerSelectionContext,
2200
- footerSelectionContext,
2201
- theme
2202
- ]);
2203
- useEffect(() => {
2204
- const runner = runnerRef.current;
2274
+ loadSiteFonts();
2205
2275
  return () => {
2206
- runner.invalidate();
2207
- clearPreviewRef.current("style-configurator");
2276
+ cancelled = true;
2277
+ if (siteRequestRef.current?.status === "loading") siteRequestRef.current = null;
2208
2278
  };
2209
- }, []);
2210
- useEffect(() => {
2211
- if (!expandedRowRequest) return;
2212
- if (appliedExpandedRowRequestIdRef.current === expandedRowRequest.id) return;
2213
- setState((current) => {
2214
- if (!current) return current;
2215
- appliedExpandedRowRequestIdRef.current = expandedRowRequest.id;
2216
- return openRow(current, expandedRowForPreviewDesignRow(expandedRowRequest.row));
2217
- });
2218
- }, [expandedRowRequest, hasLoadedState]);
2219
- const handlePreviewSiteStyle = useCallback((siteStyleId) => {
2220
- setState((current) => {
2221
- if (!current) return current;
2222
- const next = previewSiteStyle(current, siteStyleId);
2223
- const siteStyle = getCuratedSiteStyle(siteStyleId);
2224
- if (!siteStyle) return next;
2225
- const result = planSiteStylePreviewChange({
2226
- siteStyle,
2227
- selection: next.previewed,
2228
- baseTheme: theme,
2229
- headerSelectionContext,
2230
- footerSelectionContext
2231
- });
2232
- return applyResolvedSelections(next, {
2233
- resolved: result.resolvedSelections,
2234
- ...result.resolvedSelections.typographySelection.kind === "custom" ? { typographySelection: {
2235
- kind: "custom",
2236
- typography: result.themeOverlay.typography
2237
- } } : {},
2238
- warnings: result.warnings
2239
- });
2240
- });
2241
2279
  }, [
2242
- footerSelectionContext,
2243
- headerSelectionContext,
2244
- theme
2280
+ apiClient,
2281
+ enabled,
2282
+ siteId
2245
2283
  ]);
2246
- const handlePreviewPersonality = useCallback((id) => {
2247
- setState((current) => current ? previewButtonPersonality(current, id) : current);
2248
- }, []);
2249
- const handlePreviewHeaderLayout = useCallback((id) => {
2250
- if (!id) return;
2251
- setState((current) => current ? previewHeaderLayout(current, id) : current);
2252
- }, []);
2253
- const handlePreviewHeaderStyle = useCallback((id) => {
2254
- if (!id) return;
2255
- setState((current) => current ? previewHeaderStyle(current, id) : current);
2256
- }, []);
2257
- const handlePreviewFooterLook = useCallback((id) => {
2258
- const plan = planSiteChromeLookSelection({
2259
- surface: "footer",
2260
- lookId: id
2261
- });
2262
- if (plan.kind !== "footer") return;
2263
- setState((current) => current ? previewFooterLook(current, plan.next.footerLookId) : current);
2264
- }, []);
2265
- const handlePreviewFooterLayout = useCallback((id) => {
2266
- if (!id) return;
2267
- setState((current) => {
2268
- if (!current) return current;
2269
- const siteStyle = getCuratedSiteStyle(current.previewed.siteStyleId);
2270
- if (!siteStyle) return current;
2271
- const models = buildSiteChromeLookModels({
2272
- state: current,
2273
- siteStyle,
2274
- headerSelectionContext,
2275
- footerSelectionContext
2276
- });
2277
- const nextLookId = planSiteChromeFooterLayoutSelection({
2278
- layoutId: id,
2279
- currentLookId: current.previewed.footerLookId ?? models.footer.renderedLookId
2280
- });
2281
- return nextLookId ? previewFooterLook(current, nextLookId) : current;
2282
- });
2283
- }, [footerSelectionContext, headerSelectionContext]);
2284
- const handlePreviewPalette = useCallback((id) => {
2285
- const curated = findCuratedPalette(id);
2286
- if (!curated) return;
2287
- setState((current) => current ? previewCuratedPalette(current, {
2288
- paletteId: curated.id,
2289
- colors: curated.colors,
2290
- mode: curated.mode
2291
- }) : current);
2292
- }, []);
2293
- const handlePreviewTypographyPreset = useCallback((id) => {
2294
- setState((current) => current ? previewTypographyPreset(current, id) : current);
2295
- }, []);
2296
- const patchTypography = useCallback((patch) => {
2297
- setState((current) => {
2298
- if (!current) return current;
2299
- const siteStyle = getCuratedSiteStyle(current.previewed.siteStyleId);
2300
- if (!siteStyle) return current;
2301
- return applyTypographyDraftPatch(current, planSiteStylePreviewChange({
2302
- siteStyle,
2303
- selection: current.previewed,
2304
- baseTheme: theme,
2305
- headerSelectionContext,
2306
- footerSelectionContext
2307
- }).themeOverlay.typography, patch);
2308
- });
2309
- }, [
2310
- footerSelectionContext,
2311
- headerSelectionContext,
2312
- theme
2313
- ]);
2314
- const handleHeadingTypographyChange = useCallback((patch) => {
2315
- patchTypography({
2316
- kind: "heading",
2317
- patch
2318
- });
2319
- }, [patchTypography]);
2320
- const handleBodyTypographyChange = useCallback((patch) => {
2321
- patchTypography({
2322
- kind: "body",
2323
- patch
2324
- });
2325
- }, [patchTypography]);
2326
- const handleTypographyScaleChange = useCallback((scale) => {
2327
- patchTypography({
2328
- kind: "scale",
2329
- scale
2330
- });
2331
- }, [patchTypography]);
2332
- const handleResetTypography = useCallback(() => {
2333
- setState((current) => current ? resetTypographyToSaved(current) : current);
2334
- }, []);
2335
- const handleTokenOverride = useCallback((role, hex) => {
2336
- setState((current) => {
2337
- if (!current) return current;
2338
- const palette = current.previewed.palette;
2339
- if (palette.kind === "invalid") return current;
2340
- if (!(role in palette.colors)) return current;
2341
- const nextColors = paletteColorsSchema.safeParse({
2342
- ...palette.colors,
2343
- [role]: hex
2344
- });
2345
- return nextColors.success ? setPaletteColors(current, nextColors.data) : current;
2346
- });
2347
- }, []);
2348
- const handleResetOverrides = useCallback(() => {
2349
- setState((current) => {
2350
- if (!current || current.previewed.palette.kind !== "migrated-curated") return current;
2351
- const curated = findCuratedPalette(current.previewed.palette.paletteId);
2352
- return curated ? setPaletteColors(current, curated.colors) : current;
2353
- });
2354
- }, []);
2355
- const handleToggleRow = useCallback((row) => {
2356
- setState((current) => current ? toggleRow(current, row) : current);
2357
- }, []);
2358
- const handleCancel = useCallback(() => {
2359
- setState((current) => current ? cancelPreview(current) : current);
2360
- }, []);
2361
- const handleDismissWarnings = useCallback(() => {
2362
- setState((current) => current ? dismissWarnings(current) : current);
2363
- }, []);
2364
- const handleApply = useCallback(async () => {
2365
- const live = stateRef.current;
2366
- if (!live || !isPreviewingChange(live)) return;
2367
- if (!deriveStyleConfiguratorCapabilities(live).canApplyStaged) return;
2368
- const previewed = live.previewed;
2369
- const siteStyle = getCuratedSiteStyle(previewed.siteStyleId);
2370
- if (!siteStyle) return;
2371
- const compiled = planSiteStylePreviewChange({
2372
- siteStyle,
2373
- selection: previewed,
2374
- baseTheme: theme,
2375
- headerSelectionContext,
2376
- footerSelectionContext
2377
- });
2378
- await runnerRef.current.run({
2379
- optimistic: () => {
2380
- applyStarted("style-configurator");
2381
- setState((current) => current ? markApplySaving(current) : current);
2382
- },
2383
- network: async () => {
2384
- const body = buildApplySiteStyleSelectionBody({
2385
- base: live.base,
2386
- previewed: {
2387
- ...previewed,
2388
- buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
2389
- headerLayoutId: compiled.resolvedSelections.headerLayoutId,
2390
- headerStyleId: compiled.resolvedSelections.headerStyleId,
2391
- headerLookId: compiled.resolvedSelections.headerLookId,
2392
- typographySelection: compiled.resolvedSelections.typographySelection.kind === "custom" ? {
2393
- kind: "custom",
2394
- typography: compiled.themeOverlay.typography
2395
- } : typographySelectionSnapshotFromFlat(compiled.resolvedSelections)
2396
- },
2397
- selectionId
2398
- });
2399
- if (!body.ok) throw new Error(paletteApplyPlanErrorMessage(body.error));
2400
- return unwrapResponse(await apiClient({
2401
- endpoint: "applySiteStyleSelection",
2402
- params: { siteId },
2403
- body: body.body
2404
- }));
2405
- },
2406
- onSuccess: (saved) => {
2407
- const persisted = selectionSnapshotFromApplySiteStyleSelectionResponse(saved, previewed);
2408
- const historyEntry = styleConfiguratorHistoryEntryForPersistedSelection(live, persisted);
2409
- setState((current) => current ? markApplySaved(current, persisted) : current);
2410
- if (historyEntry) siteChromeCustomize?.siteStyleHistory?.recordAppliedSelection(historyEntry);
2411
- applySucceeded("style-configurator");
2412
- startRefreshTransition(() => {
2413
- router.refresh();
2414
- });
2415
- },
2416
- onError: (error) => {
2417
- applyFailed("style-configurator");
2418
- const applyError = classifyStyleApplyError(error);
2419
- if (applyError.kind === "stale") startRefreshTransition(() => {
2420
- router.refresh();
2421
- });
2422
- const message = applyError.message;
2423
- setState((current) => current ? markApplyFailed(current, message, applyError.kind === "stale" ? "stale" : "retryable") : current);
2424
- }
2425
- });
2426
- }, [
2427
- apiClient,
2428
- footerSelectionContext,
2429
- headerSelectionContext,
2430
- applyFailed,
2431
- applyStarted,
2432
- applySucceeded,
2433
- router,
2434
- selectionId,
2435
- siteChromeCustomize?.siteStyleHistory,
2436
- siteId,
2437
- startRefreshTransition,
2438
- theme
2439
- ]);
2440
- if (!state || !baseSnapshot) return /* @__PURE__ */ jsx("p", {
2441
- className: "text-sm text-muted-foreground",
2442
- children: "Loading design controls..."
2443
- });
2444
- const applyAllowed = canApplySiteStyle(siteStyleAccess);
2445
- const stagedCapabilities = deriveStyleConfiguratorCapabilities(state);
2446
- const previewingChange = isPreviewingChange(state);
2447
- const saving = stagedCapabilities.isApplyingStaged || isRefreshPending;
2448
- const aggregateMutationControlsDisabled = saving || !stagedCapabilities.canPreviewStaged;
2449
- const currentSiteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
2450
- if (!currentSiteStyle) return /* @__PURE__ */ jsx("p", {
2451
- className: "text-sm text-muted-foreground",
2452
- children: "This site uses a Site Style that cannot be configured here."
2453
- });
2454
- const currentPersonality = findButtonPersonality(state.previewed.buttonPersonalityId);
2455
- const currentPalette = state.previewed.palette;
2456
- const paletteMutation = paletteMutationCapability(state);
2457
- const paletteControlsDisabled = saving || paletteMutation.kind !== "editable";
2458
- const currentCuratedPalette = currentPalette.kind === "migrated-curated" ? findCuratedPalette(currentPalette.paletteId) : null;
2459
- const currentTypographyPreset = state.previewed.typographySelection.kind === "preset" && state.previewed.typographySelection.presetId ? findTypographyPreset(state.previewed.typographySelection.presetId) : null;
2460
- const currentTypography = planSiteStylePreviewChange({
2461
- siteStyle: currentSiteStyle,
2462
- selection: state.previewed,
2463
- baseTheme: theme,
2464
- headerSelectionContext,
2465
- footerSelectionContext
2466
- }).themeOverlay.typography;
2467
- const chromeLookModels = buildSiteChromeLookModels({
2468
- state,
2469
- siteStyle: currentSiteStyle,
2470
- headerSelectionContext,
2471
- footerSelectionContext
2472
- });
2473
- const currentHeaderLayout = getHeaderLayout(state.previewed.headerLayoutId);
2474
- const currentHeaderStyle = getHeaderStyle(state.previewed.headerStyleId);
2475
- const currentFooterLayout = getFooterLayout(chromeLookModels.footerLayout.renderedLayoutId);
2476
- const currentFooterLook = state.previewed.footerLookId ? findFooterLook(state.previewed.footerLookId) : findFooterLook(chromeLookModels.footer.renderedLookId);
2477
- const headerSurfaceState = siteChromeCustomize?.getSurfaceState("header") ?? { kind: "unavailable" };
2284
+ return {
2285
+ google,
2286
+ site
2287
+ };
2288
+ }
2289
+ function TypographyOverviewContent({ typography, catalog, onChooseFont, onOpenAdvanced, onReset, disabled }) {
2478
2290
  return /* @__PURE__ */ jsxs("div", {
2479
2291
  className: "space-y-3",
2480
2292
  children: [
2481
- state.warnings.length > 0 ? /* @__PURE__ */ jsx(WarningNotice, {
2482
- warnings: state.warnings,
2483
- onDismiss: handleDismissWarnings
2484
- }) : null,
2485
- /* @__PURE__ */ jsx(SiteStyleRow, {
2486
- expanded: state.expandedRow === "style",
2487
- onToggle: () => handleToggleRow("style"),
2488
- current: currentSiteStyle,
2489
- onPreview: handlePreviewSiteStyle,
2490
- disabled: aggregateMutationControlsDisabled
2293
+ /* @__PURE__ */ jsxs("div", {
2294
+ className: "space-y-2",
2295
+ children: [/* @__PURE__ */ jsx(TypographyRoleSummary, {
2296
+ slot: "heading",
2297
+ family: typography.headings.default.family,
2298
+ catalog,
2299
+ onChoose: () => onChooseFont("heading"),
2300
+ disabled
2301
+ }), /* @__PURE__ */ jsx(TypographyRoleSummary, {
2302
+ slot: "body",
2303
+ family: typography.body.family,
2304
+ catalog,
2305
+ onChoose: () => onChooseFont("body"),
2306
+ disabled
2307
+ })]
2491
2308
  }),
2492
- /* @__PURE__ */ jsx(SiteChromeLookRow, {
2493
- label: "Header layout",
2494
- ariaLabel: "Header layout",
2495
- defaultDescription: "Choose the header structure.",
2496
- expanded: state.expandedRow === "header-layout",
2497
- onToggle: () => handleToggleRow("header-layout"),
2498
- currentId: state.previewed.headerLayoutId,
2499
- currentLabel: currentHeaderLayout?.label ?? state.previewed.headerLayoutId,
2500
- scopeDescription: "Changes the header structure on every page.",
2501
- options: chromeLookModels.headerLayout.options,
2502
- diagnostics: [],
2503
- onCycle: (direction) => {
2504
- const next = cycleSiteChromeLookId(chromeLookModels.headerLayout.options, state.previewed.headerLayoutId, direction);
2505
- if (next !== void 0) handlePreviewHeaderLayout(next);
2506
- },
2507
- onPreview: handlePreviewHeaderLayout,
2508
- disabled: aggregateMutationControlsDisabled
2309
+ /* @__PURE__ */ jsxs("div", {
2310
+ className: "flex items-center gap-2 border-t pt-3",
2311
+ children: [/* @__PURE__ */ jsxs(Button, {
2312
+ type: "button",
2313
+ size: "sm",
2314
+ variant: "outline",
2315
+ className: "flex-1 justify-between",
2316
+ onClick: onOpenAdvanced,
2317
+ disabled,
2318
+ children: [/* @__PURE__ */ jsxs("span", {
2319
+ className: "inline-flex items-center gap-2",
2320
+ children: [/* @__PURE__ */ jsx(SlidersHorizontal, {
2321
+ className: "h-3.5 w-3.5",
2322
+ "aria-hidden": "true"
2323
+ }), "Advanced typography"]
2324
+ }), /* @__PURE__ */ jsx(ChevronRight, {
2325
+ className: "h-3.5 w-3.5",
2326
+ "aria-hidden": "true"
2327
+ })]
2328
+ }), /* @__PURE__ */ jsx(Button, {
2329
+ type: "button",
2330
+ size: "sm",
2331
+ variant: "ghost",
2332
+ onClick: onReset,
2333
+ disabled,
2334
+ children: "Reset"
2335
+ })]
2509
2336
  }),
2510
- /* @__PURE__ */ jsx(SiteChromeLookRow, {
2511
- label: "Header look",
2512
- ariaLabel: "Header look",
2513
- defaultDescription: "Choose the header visual style.",
2514
- expanded: state.expandedRow === "header-style",
2515
- onToggle: () => handleToggleRow("header-style"),
2516
- currentId: state.previewed.headerStyleId,
2517
- currentLabel: currentHeaderStyle?.label ?? state.previewed.headerStyleId,
2518
- scopeDescription: "Changes the header surface and link style.",
2519
- options: chromeLookModels.headerStyle.options,
2520
- diagnostics: [],
2521
- onCycle: (direction) => {
2522
- const next = cycleSiteChromeLookId(chromeLookModels.headerStyle.options, state.previewed.headerStyleId, direction);
2523
- if (next !== void 0) handlePreviewHeaderStyle(next);
2524
- },
2525
- onPreview: handlePreviewHeaderStyle,
2526
- disabled: aggregateMutationControlsDisabled,
2527
- extraControls: isHeaderMenuSurfaceState(headerSurfaceState) ? /* @__PURE__ */ jsx(HeaderMenuControls, {
2528
- state: headerSurfaceState,
2529
- disabled: saving,
2530
- onMenuFontChange: siteChromeCustomize?.setHeaderMenuFont,
2531
- onMenuFontSizeChange: siteChromeCustomize?.setHeaderMenuFontSize
2532
- }) : null
2337
+ /* @__PURE__ */ jsx(SiteFontFaceStyles, { siteFonts: catalog.site.value })
2338
+ ]
2339
+ });
2340
+ }
2341
+ function TypographyRoleSummary({ slot, family, catalog, onChoose, disabled }) {
2342
+ const primaryFamily = primaryFontFamily(family);
2343
+ const option = fontOptionForFamily(primaryFamily, {
2344
+ googleFonts: catalog.google.value,
2345
+ siteFonts: catalog.site.value
2346
+ });
2347
+ const label = slot === "heading" ? "Heading" : "Body";
2348
+ return /* @__PURE__ */ jsxs("button", {
2349
+ type: "button",
2350
+ className: "group flex w-full items-center gap-3 rounded-md border bg-background px-3 py-3 text-left transition hover:border-foreground/30 hover:bg-accent/20 disabled:cursor-not-allowed disabled:opacity-50",
2351
+ "aria-label": `Choose ${label.toLocaleLowerCase()} font, currently ${primaryFamily}`,
2352
+ onClick: onChoose,
2353
+ disabled,
2354
+ children: [
2355
+ /* @__PURE__ */ jsx(FontPreviewText, {
2356
+ family: primaryFamily,
2357
+ source: option?.source ?? "unknown",
2358
+ className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-muted/70 text-xl",
2359
+ children: slot === "heading" ? "Hh" : "Aa"
2533
2360
  }),
2534
- /* @__PURE__ */ jsx(SiteChromeLookRow, {
2535
- label: "Footer layout",
2536
- ariaLabel: "Footer layout",
2537
- defaultDescription: "Choose the footer structure.",
2538
- expanded: state.expandedRow === "footer-layout",
2539
- onToggle: () => handleToggleRow("footer-layout"),
2540
- currentId: chromeLookModels.footerLayout.renderedLayoutId,
2541
- currentLabel: currentFooterLayout?.label ?? chromeLookModels.footerLayout.renderedLayoutId,
2542
- scopeDescription: "Changes the footer structure on every page.",
2543
- options: chromeLookModels.footerLayout.options,
2544
- diagnostics: [],
2545
- onCycle: (direction) => {
2546
- const next = cycleSiteChromeLookId(chromeLookModels.footerLayout.options, chromeLookModels.footerLayout.renderedLayoutId, direction);
2547
- if (next !== void 0) handlePreviewFooterLayout(next);
2548
- },
2549
- onPreview: handlePreviewFooterLayout,
2550
- disabled: aggregateMutationControlsDisabled
2551
- }),
2552
- /* @__PURE__ */ jsx(SiteChromeLookRow, {
2553
- label: "Footer look",
2554
- ariaLabel: "Footer look",
2555
- defaultDescription: "Use the footer look chosen for this site style.",
2556
- expanded: state.expandedRow === "footer",
2557
- onToggle: () => handleToggleRow("footer"),
2558
- currentId: state.previewed.footerLookId,
2559
- currentLabel: state.previewed.footerLookId ? currentFooterLook?.label ?? "Unknown footer Look" : `${currentFooterLook?.label ?? "Site style"} (default)`,
2560
- scopeDescription: "Changes the footer on every page.",
2561
- options: chromeLookModels.footer.options,
2562
- diagnostics: chromeLookModels.footer.diagnostics,
2563
- onCycle: (direction) => {
2564
- const next = cycleSiteChromeLookId(chromeLookModels.footer.options, state.previewed.footerLookId, direction);
2565
- if (next !== void 0) handlePreviewFooterLook(next);
2566
- },
2567
- onPreview: handlePreviewFooterLook,
2568
- disabled: aggregateMutationControlsDisabled
2569
- }),
2570
- /* @__PURE__ */ jsx(PersonalityRow, {
2571
- expanded: state.expandedRow === "buttons",
2572
- onToggle: () => handleToggleRow("buttons"),
2573
- siteStyle: currentSiteStyle,
2574
- currentId: state.previewed.buttonPersonalityId,
2575
- currentLabel: currentPersonality?.name ?? state.previewed.buttonPersonalityId,
2576
- onPreview: handlePreviewPersonality,
2577
- disabled: aggregateMutationControlsDisabled
2361
+ /* @__PURE__ */ jsxs("span", {
2362
+ className: "min-w-0 flex-1",
2363
+ children: [/* @__PURE__ */ jsx("span", {
2364
+ className: "block text-xs font-medium text-muted-foreground",
2365
+ children: label
2366
+ }), /* @__PURE__ */ jsx(FontPreviewText, {
2367
+ family: primaryFamily,
2368
+ source: option?.source ?? "unknown",
2369
+ className: "block truncate text-base",
2370
+ children: primaryFamily
2371
+ })]
2578
2372
  }),
2579
- /* @__PURE__ */ jsx(TypographyRow, {
2580
- expanded: state.expandedRow === "typography",
2581
- onToggle: () => handleToggleRow("typography"),
2582
- apiClient,
2583
- siteStyle: currentSiteStyle,
2584
- typography: currentTypography,
2585
- currentPresetId: state.previewed.typographySelection.kind === "preset" ? state.previewed.typographySelection.presetId : null,
2586
- currentLabel: currentTypographyPreset?.name ?? "Custom typography",
2587
- onPreviewPreset: handlePreviewTypographyPreset,
2588
- onHeadingChange: handleHeadingTypographyChange,
2589
- onBodyChange: handleBodyTypographyChange,
2590
- onScaleChange: handleTypographyScaleChange,
2591
- onReset: handleResetTypography,
2592
- disabled: aggregateMutationControlsDisabled
2373
+ /* @__PURE__ */ jsx(ChevronRight, {
2374
+ className: "h-4 w-4 shrink-0 text-muted-foreground transition group-hover:text-foreground",
2375
+ "aria-hidden": "true"
2376
+ })
2377
+ ]
2378
+ });
2379
+ }
2380
+ function TypographyFontPickerTakeover({ slot, value, catalog, onBack, onChange, disabled }) {
2381
+ const [query, setQuery] = useState("");
2382
+ const [category, setCategory] = useState("all");
2383
+ const [visibleGoogleCount, setVisibleGoogleCount] = useState(24);
2384
+ const selectedFamily = primaryFontFamily(value);
2385
+ const catalogResult = useMemo(() => buildTypographyFontCatalog({
2386
+ googleFonts: catalog.google.value,
2387
+ siteFonts: catalog.site.value,
2388
+ query,
2389
+ category
2390
+ }), [
2391
+ catalog.google.value,
2392
+ catalog.site.value,
2393
+ category,
2394
+ query
2395
+ ]);
2396
+ const searching = query.trim().length > 0;
2397
+ const visibleGoogle = searching ? catalogResult.google : visibleTypographyFontOptions({
2398
+ options: catalogResult.google,
2399
+ selectedFamily,
2400
+ limit: visibleGoogleCount
2401
+ });
2402
+ const hasMore = !searching && visibleGoogle.length < catalogResult.google.length;
2403
+ const title = slot === "heading" ? "Heading font" : "Body font";
2404
+ useEffect(() => {
2405
+ setVisibleGoogleCount(24);
2406
+ }, [category, slot]);
2407
+ return /* @__PURE__ */ jsxs("section", {
2408
+ "aria-label": title,
2409
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden bg-background",
2410
+ children: [
2411
+ /* @__PURE__ */ jsxs("header", {
2412
+ className: "shrink-0 space-y-3 border-b p-3",
2413
+ children: [
2414
+ /* @__PURE__ */ jsxs("div", {
2415
+ className: "flex items-center gap-2",
2416
+ children: [/* @__PURE__ */ jsx(Button, {
2417
+ type: "button",
2418
+ size: "icon",
2419
+ variant: "ghost",
2420
+ className: "h-8 w-8 shrink-0",
2421
+ onClick: onBack,
2422
+ "aria-label": "Back to typography",
2423
+ children: /* @__PURE__ */ jsx(ChevronLeft, {
2424
+ className: "h-4 w-4",
2425
+ "aria-hidden": "true"
2426
+ })
2427
+ }), /* @__PURE__ */ jsxs("div", {
2428
+ className: "min-w-0",
2429
+ children: [/* @__PURE__ */ jsx("h2", {
2430
+ className: "text-sm font-semibold",
2431
+ children: title
2432
+ }), /* @__PURE__ */ jsxs("p", {
2433
+ className: "truncate text-xs text-muted-foreground",
2434
+ children: ["Current: ", selectedFamily]
2435
+ })]
2436
+ })]
2437
+ }),
2438
+ /* @__PURE__ */ jsx(SearchInput, {
2439
+ value: query,
2440
+ onChange: (event) => setQuery(event.currentTarget.value),
2441
+ placeholder: "Search fonts",
2442
+ "aria-label": `Search ${slot} fonts`,
2443
+ disabled
2444
+ }),
2445
+ /* @__PURE__ */ jsx("div", {
2446
+ role: "group",
2447
+ "aria-label": "Font category",
2448
+ className: "flex gap-1 overflow-x-auto pb-0.5",
2449
+ children: typographyFontCategories.map((option) => /* @__PURE__ */ jsx(Button, {
2450
+ type: "button",
2451
+ size: "sm",
2452
+ variant: category === option.value ? "secondary" : "ghost",
2453
+ className: "h-7 shrink-0 px-2 text-xs",
2454
+ "aria-pressed": category === option.value,
2455
+ onClick: () => setCategory(option.value),
2456
+ disabled,
2457
+ children: option.label
2458
+ }, option.value))
2459
+ })
2460
+ ]
2593
2461
  }),
2594
- /* @__PURE__ */ jsx(PaletteRow, {
2595
- expanded: state.expandedRow === "palette",
2596
- onToggle: () => handleToggleRow("palette"),
2597
- siteStyle: currentSiteStyle,
2598
- currentId: currentCuratedPalette?.id ?? null,
2599
- currentLabel: currentCuratedPalette?.name ?? (currentPalette.kind === "migrated-site" ? "Site palette" : "Custom palette"),
2600
- currentSwatch: currentCuratedPalette?.colors.primary ?? null,
2601
- onPreview: handlePreviewPalette,
2602
- mutationCapability: paletteMutation,
2603
- disabled: paletteControlsDisabled
2462
+ /* @__PURE__ */ jsxs("div", {
2463
+ "data-typography-font-catalog-scroll": true,
2464
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain p-2",
2465
+ children: [
2466
+ /* @__PURE__ */ jsx(FontCatalogFeedback, { catalog }),
2467
+ catalogResult.site.length > 0 ? /* @__PURE__ */ jsx(FontOptionSection, {
2468
+ label: "Your fonts",
2469
+ children: catalogResult.site.map((option) => /* @__PURE__ */ jsx(TypographyFontOptionRow, {
2470
+ option,
2471
+ selected: familyMatches(selectedFamily, option.family),
2472
+ onSelect: () => onChange(option.family),
2473
+ disabled
2474
+ }, `site:${option.family}`))
2475
+ }) : null,
2476
+ visibleGoogle.length > 0 ? /* @__PURE__ */ jsxs(FontOptionSection, {
2477
+ label: searching ? "Matching fonts" : "Popular fonts",
2478
+ children: [visibleGoogle.map((option) => /* @__PURE__ */ jsx(TypographyFontOptionRow, {
2479
+ option,
2480
+ selected: familyMatches(selectedFamily, option.family),
2481
+ onSelect: () => onChange(option.family),
2482
+ disabled
2483
+ }, `google:${option.family}`)), hasMore ? /* @__PURE__ */ jsx(Button, {
2484
+ type: "button",
2485
+ size: "sm",
2486
+ variant: "ghost",
2487
+ className: "mt-1 w-full",
2488
+ onClick: () => setVisibleGoogleCount((count) => count + 24),
2489
+ disabled,
2490
+ children: "Show more fonts"
2491
+ }) : null]
2492
+ }) : catalog.google.kind === "loaded" && catalog.site.kind === "loaded" ? /* @__PURE__ */ jsx("p", {
2493
+ className: "px-2 py-8 text-center text-sm text-muted-foreground",
2494
+ children: "No fonts match your search."
2495
+ }) : null
2496
+ ]
2604
2497
  }),
2605
- currentPalette.kind !== "invalid" ? /* @__PURE__ */ jsx(ColoursRow, {
2606
- expanded: state.expandedRow === "colours",
2607
- onToggle: () => handleToggleRow("colours"),
2608
- palette: currentPalette.colors,
2609
- isCustom: !currentCuratedPalette || currentPalette.mode !== currentCuratedPalette.mode || !paletteColorsEqual(currentPalette.colors, currentCuratedPalette.colors),
2610
- onTokenChange: handleTokenOverride,
2611
- onReset: handleResetOverrides,
2612
- disabled: paletteControlsDisabled,
2613
- mutationCapability: paletteMutation
2614
- }) : null,
2615
- previewingChange ? /* @__PURE__ */ jsxs("div", {
2616
- className: "flex items-center gap-2 pt-2",
2617
- children: [/* @__PURE__ */ jsx(Button, {
2618
- type: "button",
2619
- size: "sm",
2620
- variant: "outline",
2621
- onClick: handleCancel,
2622
- disabled: !stagedCapabilities.canCancelStaged || saving,
2623
- children: "Cancel"
2624
- }), applyAllowed ? /* @__PURE__ */ jsx(Button, {
2625
- type: "button",
2626
- size: "sm",
2627
- onClick: handleApply,
2628
- disabled: !stagedCapabilities.canApplyStaged || saving,
2629
- children: saving ? "Applying…" : "Apply"
2630
- }) : /* @__PURE__ */ jsx("span", {
2631
- className: "text-xs text-muted-foreground",
2632
- children: "Preview only — ask an admin to apply."
2633
- })]
2634
- }) : null,
2635
- state.saveStatus.kind === "saved" ? /* @__PURE__ */ jsx("p", {
2636
- className: "text-xs text-success",
2637
- children: "Saved."
2638
- }) : null,
2639
- state.saveStatus.kind === "failed" ? /* @__PURE__ */ jsx("p", {
2640
- className: "text-xs text-destructive",
2641
- children: state.saveStatus.message
2642
- }) : null
2498
+ /* @__PURE__ */ jsx(SiteFontFaceStyles, { siteFonts: catalog.site.value })
2643
2499
  ]
2644
2500
  });
2645
2501
  }
2646
- function StyleRowFrame({ label, currentLabel, swatch, expanded, onToggle, children }) {
2647
- return /* @__PURE__ */ jsx(Collapsible, {
2648
- open: expanded,
2649
- onOpenChange: onToggle,
2650
- children: /* @__PURE__ */ jsxs("div", {
2651
- className: "rounded-md border bg-background",
2652
- children: [/* @__PURE__ */ jsx(CollapsibleTrigger, {
2653
- asChild: true,
2654
- children: /* @__PURE__ */ jsxs("button", {
2655
- type: "button",
2656
- className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-accent/30",
2657
- "aria-expanded": expanded,
2658
- children: [
2659
- /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-4 w-4 transition", expanded && "rotate-90") }),
2660
- /* @__PURE__ */ jsxs("div", {
2661
- className: "flex-1 min-w-0",
2662
- children: [/* @__PURE__ */ jsx("div", {
2663
- className: "text-xs uppercase tracking-wide text-muted-foreground",
2664
- children: label
2665
- }), /* @__PURE__ */ jsx("div", {
2666
- className: "truncate text-sm font-medium",
2667
- children: currentLabel
2668
- })]
2669
- }),
2670
- swatch ? /* @__PURE__ */ jsx("span", {
2671
- className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
2672
- style: { backgroundColor: swatch },
2673
- "aria-hidden": "true"
2674
- }) : null
2675
- ]
2676
- })
2677
- }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", {
2678
- className: "border-t px-3 py-3",
2679
- children
2680
- }) })]
2681
- })
2502
+ function FontOptionSection({ label, children }) {
2503
+ return /* @__PURE__ */ jsxs("section", {
2504
+ "aria-label": label,
2505
+ className: "mb-3 last:mb-0",
2506
+ children: [/* @__PURE__ */ jsx("h3", {
2507
+ className: "sticky top-0 z-[1] bg-background px-2 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground",
2508
+ children: label
2509
+ }), /* @__PURE__ */ jsx("div", {
2510
+ className: "space-y-0.5",
2511
+ children
2512
+ })]
2682
2513
  });
2683
2514
  }
2684
- function SiteStyleRow({ expanded, onToggle, current, onPreview, disabled }) {
2685
- return /* @__PURE__ */ jsx(StyleRowFrame, {
2686
- label: "Site style",
2687
- currentLabel: current.name,
2688
- expanded,
2689
- onToggle,
2690
- children: /* @__PURE__ */ jsx("ul", {
2691
- role: "radiogroup",
2692
- "aria-label": "Site style",
2693
- className: "list-none space-y-1 p-0",
2694
- children: curatedSiteStyles.map((style) => /* @__PURE__ */ jsx(PickerOption, {
2695
- selected: style.id === current.id,
2696
- label: style.name,
2697
- description: style.description,
2698
- onSelect: () => onPreview(style.id),
2699
- disabled
2700
- }, style.id))
2701
- })
2515
+ function TypographyFontOptionRow({ option, selected, onSelect, disabled }) {
2516
+ return /* @__PURE__ */ jsxs("button", {
2517
+ type: "button",
2518
+ "aria-pressed": selected,
2519
+ "aria-label": option.label,
2520
+ className: cn("flex min-h-11 w-full items-center gap-3 rounded-md border border-transparent px-3 py-2 text-left transition disabled:cursor-not-allowed disabled:opacity-50", selected ? "border-primary/60 bg-primary/10" : "hover:border-border hover:bg-accent/30"),
2521
+ onClick: onSelect,
2522
+ disabled,
2523
+ children: [/* @__PURE__ */ jsx(FontPreviewText, {
2524
+ family: option.family,
2525
+ source: option.source,
2526
+ className: "min-w-0 flex-1 truncate text-base",
2527
+ children: option.label
2528
+ }), selected ? /* @__PURE__ */ jsx(Check, {
2529
+ className: "h-4 w-4 shrink-0 text-primary",
2530
+ "aria-hidden": "true"
2531
+ }) : null]
2702
2532
  });
2703
2533
  }
2704
- function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel, onPreview, disabled }) {
2705
- return /* @__PURE__ */ jsx(StyleRowFrame, {
2706
- label: "Button style",
2707
- currentLabel,
2708
- expanded,
2709
- onToggle,
2710
- children: /* @__PURE__ */ jsx("ul", {
2711
- role: "radiogroup",
2712
- "aria-label": "Button style",
2713
- className: "list-none space-y-1 p-0",
2714
- children: useMemo(() => requireEntriesForCuratedChoices(buttonPersonalities, siteStyle.buttonPersonalityChoices), [siteStyle.buttonPersonalityChoices]).map((personality) => /* @__PURE__ */ jsx(PickerOption, {
2715
- selected: personality.id === currentId,
2716
- label: personality.name,
2717
- description: personality.description,
2718
- onSelect: () => onPreview(personality.id),
2719
- disabled
2720
- }, personality.id))
2721
- })
2534
+ function FontCatalogFeedback({ catalog }) {
2535
+ if ((catalog.google.kind === "loading" || catalog.google.kind === "idle") && catalog.google.value.length === 0) return /* @__PURE__ */ jsx("p", {
2536
+ className: "px-2 py-6 text-center text-sm text-muted-foreground",
2537
+ children: "Loading font catalog…"
2722
2538
  });
2539
+ const failureMessages = [catalog.site.kind === "failed" ? catalog.site.message : null, catalog.google.kind === "failed" ? catalog.google.message : null].filter((message) => message !== null);
2540
+ if (failureMessages.length > 0) return /* @__PURE__ */ jsx("div", {
2541
+ className: "mb-2 space-y-1",
2542
+ children: failureMessages.map((message) => /* @__PURE__ */ jsx("p", {
2543
+ className: "rounded-md bg-muted/60 px-3 py-2 text-xs text-muted-foreground",
2544
+ children: message
2545
+ }, message))
2546
+ });
2547
+ return null;
2723
2548
  }
2724
- function TypographyRow({ expanded, onToggle, apiClient, siteStyle, typography, currentPresetId, currentLabel, onPreviewPreset, onHeadingChange, onBodyChange, onScaleChange, onReset, disabled }) {
2725
- const [headingOpen, setHeadingOpen] = useState(true);
2726
- const [bodyOpen, setBodyOpen] = useState(false);
2727
- const options = useMemo(() => requireEntriesForCuratedChoices(typographyPresets, siteStyle.typographyPresetChoices), [siteStyle.typographyPresetChoices]);
2728
- const fontCatalog = useFontCatalog(apiClient, expanded);
2729
- const fontPolicyPreset = (currentPresetId ? findTypographyPreset(currentPresetId) : null) ?? options[0] ?? typographyPresets[0];
2730
- if (!fontPolicyPreset) return null;
2731
- return /* @__PURE__ */ jsxs(StyleRowFrame, {
2732
- label: "Typography",
2733
- currentLabel,
2734
- expanded,
2735
- onToggle,
2736
- children: [/* @__PURE__ */ jsxs("section", {
2737
- "aria-label": "Start from preset",
2738
- className: "space-y-2",
2739
- children: [/* @__PURE__ */ jsx("div", {
2740
- className: "text-xs font-medium text-muted-foreground",
2741
- children: "Start from preset"
2742
- }), /* @__PURE__ */ jsx("ul", {
2743
- role: "radiogroup",
2744
- "aria-label": "Typography preset",
2745
- className: "space-y-1",
2746
- children: options.map((preset) => /* @__PURE__ */ jsx(TypographyPresetOption, {
2747
- preset,
2748
- selected: preset.id === currentPresetId,
2749
- onSelect: () => onPreviewPreset(preset.id),
2750
- disabled
2751
- }, preset.id))
2752
- })]
2753
- }), /* @__PURE__ */ jsxs("div", {
2754
- className: "mt-3 space-y-2 border-t pt-3",
2755
- children: [
2756
- /* @__PURE__ */ jsxs(TypographyDisclosure, {
2549
+ function TypographyAdvancedTakeover({ typography, catalog, onBack, onHeadingChange, onBodyChange, onScaleChange, disabled }) {
2550
+ const headingOption = fontOptionForFamily(typography.headings.default.family, {
2551
+ googleFonts: catalog.google.value,
2552
+ siteFonts: catalog.site.value
2553
+ });
2554
+ const bodyOption = fontOptionForFamily(typography.body.family, {
2555
+ googleFonts: catalog.google.value,
2556
+ siteFonts: catalog.site.value
2557
+ });
2558
+ const headingWeightWarning = unsupportedWeightMessage(headingOption, typography.headings.default.weight, typography.headings.default.italic === true ? "italic" : "normal");
2559
+ const bodyWeightWarning = unsupportedWeightMessage(bodyOption, typography.body.weight, "normal");
2560
+ return /* @__PURE__ */ jsxs("section", {
2561
+ "aria-label": "Advanced typography",
2562
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden bg-background",
2563
+ children: [
2564
+ /* @__PURE__ */ jsxs("header", {
2565
+ className: "flex items-center gap-2 border-b p-3",
2566
+ children: [/* @__PURE__ */ jsx(Button, {
2567
+ type: "button",
2568
+ size: "icon",
2569
+ variant: "ghost",
2570
+ className: "h-8 w-8 shrink-0",
2571
+ onClick: onBack,
2572
+ "aria-label": "Back to typography",
2573
+ children: /* @__PURE__ */ jsx(ChevronLeft, {
2574
+ className: "h-4 w-4",
2575
+ "aria-hidden": "true"
2576
+ })
2577
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
2578
+ className: "text-sm font-semibold",
2579
+ children: "Advanced typography"
2580
+ }), /* @__PURE__ */ jsx("p", {
2581
+ className: "text-xs text-muted-foreground",
2582
+ children: "Fine-tune the type system."
2583
+ })] })]
2584
+ }),
2585
+ /* @__PURE__ */ jsxs("div", {
2586
+ className: "min-h-0 flex-1 space-y-2 overflow-y-auto p-3",
2587
+ children: [/* @__PURE__ */ jsxs(TypographyDisclosure, {
2757
2588
  label: "Heading",
2758
- open: headingOpen,
2759
- onOpenChange: setHeadingOpen,
2589
+ defaultOpen: true,
2760
2590
  children: [
2761
- /* @__PURE__ */ jsx(TypographyFontSlotPicker, {
2762
- label: "Heading font",
2763
- slot: "heading",
2764
- preset: fontPolicyPreset,
2765
- catalog: fontCatalog,
2766
- value: typography.headings.default.family,
2767
- onChange: (family) => onHeadingChange({ family }),
2768
- disabled
2769
- }),
2770
2591
  /* @__PURE__ */ jsx(TypographySelectControl, {
2771
2592
  label: "Weight",
2772
2593
  value: typography.headings.default.weight,
@@ -2774,6 +2595,7 @@ function TypographyRow({ expanded, onToggle, apiClient, siteStyle, typography, c
2774
2595
  onChange: (weight) => onHeadingChange({ weight }),
2775
2596
  disabled
2776
2597
  }),
2598
+ headingWeightWarning ? /* @__PURE__ */ jsx(ContextualFontWarning, { message: headingWeightWarning }) : null,
2777
2599
  /* @__PURE__ */ jsx(TypographySelectControl, {
2778
2600
  label: "Line height",
2779
2601
  value: typography.headings.default.lineHeight,
@@ -2781,6 +2603,13 @@ function TypographyRow({ expanded, onToggle, apiClient, siteStyle, typography, c
2781
2603
  onChange: (lineHeight) => onHeadingChange({ lineHeight }),
2782
2604
  disabled
2783
2605
  }),
2606
+ /* @__PURE__ */ jsx(TypographySelectControl, {
2607
+ label: "Letter spacing",
2608
+ value: typography.headings.default.letterSpacing,
2609
+ options: typographyLetterSpacingOptions,
2610
+ onChange: (letterSpacing) => onHeadingChange({ letterSpacing }),
2611
+ disabled
2612
+ }),
2784
2613
  /* @__PURE__ */ jsx(TypographySelectControl, {
2785
2614
  label: "Scale",
2786
2615
  value: normalizeTypographyScaleForControl(typography.scale),
@@ -2788,27 +2617,31 @@ function TypographyRow({ expanded, onToggle, apiClient, siteStyle, typography, c
2788
2617
  onChange: onScaleChange,
2789
2618
  disabled
2790
2619
  }),
2620
+ /* @__PURE__ */ jsx(HeadingCaseControl, {
2621
+ value: typography.headings.default.case,
2622
+ onChange: (value) => onHeadingChange({ case: value }),
2623
+ disabled
2624
+ }),
2625
+ /* @__PURE__ */ jsxs("button", {
2626
+ type: "button",
2627
+ className: "flex w-full items-center justify-between rounded-md border px-3 py-2 text-sm disabled:cursor-not-allowed disabled:opacity-50",
2628
+ "aria-pressed": typography.headings.default.italic === true,
2629
+ onClick: () => onHeadingChange({ italic: typography.headings.default.italic === true ? null : true }),
2630
+ disabled,
2631
+ children: ["Italic headings", /* @__PURE__ */ jsx("span", {
2632
+ className: "text-xs text-muted-foreground",
2633
+ children: typography.headings.default.italic === true ? "On" : "Default"
2634
+ })]
2635
+ }),
2791
2636
  /* @__PURE__ */ jsx(TypographyColorTokenControl, {
2792
- value: typography.headings.default.colorToken ?? "text",
2637
+ value: normalizeHeadingColorTokenForControl(typography.headings.default.colorToken),
2793
2638
  onChange: (colorToken) => onHeadingChange({ colorToken: colorToken === "text" ? null : colorToken }),
2794
2639
  disabled
2795
2640
  })
2796
2641
  ]
2797
- }),
2798
- /* @__PURE__ */ jsxs(TypographyDisclosure, {
2642
+ }), /* @__PURE__ */ jsxs(TypographyDisclosure, {
2799
2643
  label: "Body",
2800
- open: bodyOpen,
2801
- onOpenChange: setBodyOpen,
2802
2644
  children: [
2803
- /* @__PURE__ */ jsx(TypographyFontSlotPicker, {
2804
- label: "Body font",
2805
- slot: "body",
2806
- preset: fontPolicyPreset,
2807
- catalog: fontCatalog,
2808
- value: typography.body.family,
2809
- onChange: (family) => onBodyChange({ family }),
2810
- disabled
2811
- }),
2812
2645
  /* @__PURE__ */ jsx(TypographySelectControl, {
2813
2646
  label: "Size",
2814
2647
  value: typography.body.size,
@@ -2823,41 +2656,300 @@ function TypographyRow({ expanded, onToggle, apiClient, siteStyle, typography, c
2823
2656
  onChange: (weight) => onBodyChange({ weight }),
2824
2657
  disabled
2825
2658
  }),
2659
+ bodyWeightWarning ? /* @__PURE__ */ jsx(ContextualFontWarning, { message: bodyWeightWarning }) : null,
2826
2660
  /* @__PURE__ */ jsx(TypographySelectControl, {
2827
2661
  label: "Line height",
2828
2662
  value: typography.body.lineHeight,
2829
2663
  options: typographyLineHeightOptions,
2830
2664
  onChange: (lineHeight) => onBodyChange({ lineHeight }),
2831
2665
  disabled
2666
+ }),
2667
+ /* @__PURE__ */ jsx(TypographySelectControl, {
2668
+ label: "Letter spacing",
2669
+ value: typography.body.letterSpacing,
2670
+ options: typographyLetterSpacingOptions,
2671
+ onChange: (letterSpacing) => onBodyChange({ letterSpacing }),
2672
+ disabled
2832
2673
  })
2833
2674
  ]
2834
- }),
2835
- /* @__PURE__ */ jsx(Button, {
2836
- type: "button",
2837
- size: "sm",
2838
- variant: "outline",
2839
- onClick: onReset,
2840
- disabled,
2841
- children: "Reset typography"
2842
- })
2843
- ]
2844
- })]
2675
+ })]
2676
+ }),
2677
+ /* @__PURE__ */ jsx(SiteFontFaceStyles, { siteFonts: catalog.site.value })
2678
+ ]
2845
2679
  });
2846
2680
  }
2847
- const typographyWeightOptions = [
2848
- {
2849
- value: "light",
2850
- label: "Light"
2851
- },
2852
- {
2853
- value: "regular",
2854
- label: "Regular"
2855
- },
2856
- {
2857
- value: "medium",
2858
- label: "Medium"
2859
- },
2860
- {
2681
+ function TypographyDisclosure({ label, defaultOpen = false, children }) {
2682
+ const [open, setOpen] = useState(defaultOpen);
2683
+ return /* @__PURE__ */ jsx(Collapsible, {
2684
+ open,
2685
+ onOpenChange: setOpen,
2686
+ children: /* @__PURE__ */ jsxs("div", {
2687
+ className: "rounded-md border bg-muted/10",
2688
+ children: [/* @__PURE__ */ jsx(CollapsibleTrigger, {
2689
+ asChild: true,
2690
+ children: /* @__PURE__ */ jsxs("button", {
2691
+ type: "button",
2692
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left text-sm font-medium hover:bg-accent/30",
2693
+ "aria-expanded": open,
2694
+ children: [/* @__PURE__ */ jsx(ChevronRight, {
2695
+ className: cn("h-4 w-4 transition", open && "rotate-90"),
2696
+ "aria-hidden": "true"
2697
+ }), label]
2698
+ })
2699
+ }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", {
2700
+ className: "space-y-3 border-t px-3 py-3",
2701
+ children
2702
+ }) })]
2703
+ })
2704
+ });
2705
+ }
2706
+ function TypographySelectControl({ label, value, options, onChange, disabled }) {
2707
+ return /* @__PURE__ */ jsxs("label", {
2708
+ className: "block space-y-1",
2709
+ children: [/* @__PURE__ */ jsx("span", {
2710
+ className: "text-xs font-medium text-muted-foreground",
2711
+ children: label
2712
+ }), /* @__PURE__ */ jsxs(Select, {
2713
+ value,
2714
+ onValueChange: (next) => {
2715
+ const option = options.find((candidate) => candidate.value === next);
2716
+ if (option) onChange(option.value);
2717
+ },
2718
+ disabled,
2719
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
2720
+ size: "sm",
2721
+ className: "w-full",
2722
+ "aria-label": label,
2723
+ children: /* @__PURE__ */ jsx(SelectValue, {})
2724
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
2725
+ value: option.value,
2726
+ children: option.label
2727
+ }, option.value)) })]
2728
+ })]
2729
+ });
2730
+ }
2731
+ function HeadingCaseControl({ value, onChange, disabled }) {
2732
+ const options = [
2733
+ {
2734
+ value: "default",
2735
+ label: "Default",
2736
+ resolved: null
2737
+ },
2738
+ {
2739
+ value: "normal",
2740
+ label: "Normal",
2741
+ resolved: "normal"
2742
+ },
2743
+ {
2744
+ value: "uppercase",
2745
+ label: "Uppercase",
2746
+ resolved: "uppercase"
2747
+ },
2748
+ {
2749
+ value: "smallCaps",
2750
+ label: "Small caps",
2751
+ resolved: "smallCaps"
2752
+ }
2753
+ ];
2754
+ return /* @__PURE__ */ jsxs("label", {
2755
+ className: "block space-y-1",
2756
+ children: [/* @__PURE__ */ jsx("span", {
2757
+ className: "text-xs font-medium text-muted-foreground",
2758
+ children: "Case"
2759
+ }), /* @__PURE__ */ jsxs(Select, {
2760
+ value: value ?? "default",
2761
+ onValueChange: (next) => {
2762
+ const option = options.find((candidate) => candidate.value === next);
2763
+ if (option) onChange(option.resolved);
2764
+ },
2765
+ disabled,
2766
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
2767
+ size: "sm",
2768
+ className: "w-full",
2769
+ "aria-label": "Heading case",
2770
+ children: /* @__PURE__ */ jsx(SelectValue, {})
2771
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
2772
+ value: option.value,
2773
+ children: option.label
2774
+ }, option.value)) })]
2775
+ })]
2776
+ });
2777
+ }
2778
+ function TypographyColorTokenControl({ value, onChange, disabled }) {
2779
+ return /* @__PURE__ */ jsxs("section", {
2780
+ className: "space-y-2",
2781
+ "aria-label": "Default heading color",
2782
+ children: [/* @__PURE__ */ jsxs("div", { children: [
2783
+ /* @__PURE__ */ jsx("div", {
2784
+ className: "text-xs font-medium text-muted-foreground",
2785
+ children: "Default heading color"
2786
+ }),
2787
+ /* @__PURE__ */ jsx("p", {
2788
+ className: "text-[11px] text-muted-foreground",
2789
+ children: "Sections may override this for readability."
2790
+ }),
2791
+ value.kind === "existing" ? /* @__PURE__ */ jsxs("p", {
2792
+ className: "mt-1 text-[11px] text-muted-foreground",
2793
+ children: [
2794
+ "Current: ",
2795
+ formatColorTokenLabel(value.value),
2796
+ " (outside quick choices)"
2797
+ ]
2798
+ }) : null
2799
+ ] }), /* @__PURE__ */ jsx("div", {
2800
+ className: "grid grid-cols-2 gap-1",
2801
+ children: headingColorTokenOptions.map((option) => /* @__PURE__ */ jsx(Button, {
2802
+ type: "button",
2803
+ size: "sm",
2804
+ variant: value.kind === "curated" && value.value === option.value ? "secondary" : "outline",
2805
+ className: "justify-start text-xs",
2806
+ "aria-pressed": value.kind === "curated" && value.value === option.value,
2807
+ onClick: () => onChange(option.value),
2808
+ disabled,
2809
+ children: option.label
2810
+ }, option.value))
2811
+ })]
2812
+ });
2813
+ }
2814
+ function ContextualFontWarning({ message }) {
2815
+ return /* @__PURE__ */ jsx("p", {
2816
+ className: "rounded-md bg-warning/10 px-2 py-1.5 text-xs text-muted-foreground",
2817
+ children: message
2818
+ });
2819
+ }
2820
+ function FontPreviewText({ family, source, className, children }) {
2821
+ const elementRef = useRef(null);
2822
+ const [visible, setVisible] = useState(() => typeof globalThis.IntersectionObserver !== "function");
2823
+ useEffect(() => {
2824
+ if (visible || source !== "google") return;
2825
+ const element = elementRef.current;
2826
+ if (!element || typeof globalThis.IntersectionObserver !== "function") {
2827
+ setVisible(true);
2828
+ return;
2829
+ }
2830
+ const observer = new globalThis.IntersectionObserver((entries) => {
2831
+ if (!entries.some((entry) => entry.isIntersecting)) return;
2832
+ setVisible(true);
2833
+ observer.disconnect();
2834
+ }, { rootMargin: "160px 0px" });
2835
+ observer.observe(element);
2836
+ return () => observer.disconnect();
2837
+ }, [source, visible]);
2838
+ useEffect(() => {
2839
+ if (!visible || source !== "google") return;
2840
+ ensureGoogleFontPreviewLink(family);
2841
+ }, [
2842
+ family,
2843
+ source,
2844
+ visible
2845
+ ]);
2846
+ return /* @__PURE__ */ jsx("span", {
2847
+ ref: elementRef,
2848
+ className,
2849
+ style: source === "site" || source === "google" ? { fontFamily: quoteFontFamily(family) } : void 0,
2850
+ children
2851
+ });
2852
+ }
2853
+ function SiteFontFaceStyles({ siteFonts }) {
2854
+ const css = useMemo(() => {
2855
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
2856
+ if (!supabaseUrl || siteFonts.length === 0) return "";
2857
+ return generateFontFaceCss([...siteFonts], getSiteFontsStorageUrl(supabaseUrl));
2858
+ }, [siteFonts]);
2859
+ return css ? /* @__PURE__ */ jsx("style", {
2860
+ "data-typography-site-fonts": true,
2861
+ children: css
2862
+ }) : null;
2863
+ }
2864
+ function ensureGoogleFontPreviewLink(family) {
2865
+ const url = buildFontPreviewUrl(family);
2866
+ if (Array.from(document.head.querySelectorAll("link[data-font-preview]")).some((link) => link.href === url)) return;
2867
+ const link = document.createElement("link");
2868
+ link.rel = "stylesheet";
2869
+ link.href = url;
2870
+ link.dataset.fontPreview = family;
2871
+ document.head.appendChild(link);
2872
+ }
2873
+ function unsupportedWeightMessage(option, weight, style) {
2874
+ if (!option) return null;
2875
+ const numericWeight = semanticFontWeight(weight);
2876
+ if (option.source === "site") {
2877
+ const font = option.siteFont;
2878
+ if (font.isVariable && font.variableWeightMin !== null && font.variableWeightMax !== null) return numericWeight >= font.variableWeightMin && numericWeight <= font.variableWeightMax && font.files.some((file) => file.style === style) ? null : unsupportedFontStyleMessage(option.label, style);
2879
+ return font.files.some((file) => file.weight === numericWeight && file.style === style) ? null : unsupportedFontStyleMessage(option.label, style);
2880
+ }
2881
+ const variants = new Set(option.googleFont.variants);
2882
+ return (style === "italic" ? variants.has(numericWeight === 400 ? "italic" : `${numericWeight}italic`) : variants.has(String(numericWeight)) || numericWeight === 400 && variants.has("regular")) ? null : unsupportedFontStyleMessage(option.label, style);
2883
+ }
2884
+ function unsupportedFontStyleMessage(family, style) {
2885
+ return style === "italic" ? `${family} may not include italics at this weight.` : `${family} may not include this weight.`;
2886
+ }
2887
+ function semanticFontWeight(weight) {
2888
+ switch (weight) {
2889
+ case "light": return 300;
2890
+ case "regular": return 400;
2891
+ case "medium": return 500;
2892
+ case "semibold": return 600;
2893
+ case "bold": return 700;
2894
+ }
2895
+ }
2896
+ function normalizeTypographyScaleForControl(scale) {
2897
+ switch (scale) {
2898
+ case "compact":
2899
+ case "balanced":
2900
+ case "spacious": return scale;
2901
+ case "minorThird": return "compact";
2902
+ case "majorThird": return "balanced";
2903
+ case "perfectFourth": return "spacious";
2904
+ }
2905
+ }
2906
+ function normalizeHeadingColorTokenForControl(value) {
2907
+ switch (value) {
2908
+ case "primary":
2909
+ case "secondary":
2910
+ case "accent": return {
2911
+ kind: "curated",
2912
+ value
2913
+ };
2914
+ case "text":
2915
+ case void 0:
2916
+ case null: return {
2917
+ kind: "curated",
2918
+ value: "text"
2919
+ };
2920
+ default: return {
2921
+ kind: "existing",
2922
+ value
2923
+ };
2924
+ }
2925
+ }
2926
+ function formatColorTokenLabel(value) {
2927
+ const words = value.replace(/([a-z])([A-Z])/gu, "$1 $2").replace(/[-_]+/gu, " ").toLocaleLowerCase();
2928
+ return `${words.charAt(0).toLocaleUpperCase()}${words.slice(1)}`;
2929
+ }
2930
+ function familyMatches(left, right) {
2931
+ return left.toLocaleLowerCase() === right.toLocaleLowerCase();
2932
+ }
2933
+ function quoteFontFamily(family) {
2934
+ return `"${family.replace(/["\\]/gu, "")}", sans-serif`;
2935
+ }
2936
+ function errorMessage(error, fallback) {
2937
+ return error instanceof Error && error.message ? error.message : fallback;
2938
+ }
2939
+ const typographyWeightOptions = [
2940
+ {
2941
+ value: "light",
2942
+ label: "Light"
2943
+ },
2944
+ {
2945
+ value: "regular",
2946
+ label: "Regular"
2947
+ },
2948
+ {
2949
+ value: "medium",
2950
+ label: "Medium"
2951
+ },
2952
+ {
2861
2953
  value: "semibold",
2862
2954
  label: "Semibold"
2863
2955
  },
@@ -2880,6 +2972,20 @@ const typographyLineHeightOptions = [
2880
2972
  label: "Loose"
2881
2973
  }
2882
2974
  ];
2975
+ const typographyLetterSpacingOptions = [
2976
+ {
2977
+ value: "tight",
2978
+ label: "Tight"
2979
+ },
2980
+ {
2981
+ value: "normal",
2982
+ label: "Normal"
2983
+ },
2984
+ {
2985
+ value: "loose",
2986
+ label: "Loose"
2987
+ }
2988
+ ];
2883
2989
  const bodySizeOptions = [
2884
2990
  {
2885
2991
  value: "md",
@@ -2911,7 +3017,7 @@ const typographyScaleOptions = [
2911
3017
  const headingColorTokenOptions = [
2912
3018
  {
2913
3019
  value: "text",
2914
- label: "Text"
3020
+ label: "Default text"
2915
3021
  },
2916
3022
  {
2917
3023
  value: "primary",
@@ -2926,400 +3032,768 @@ const headingColorTokenOptions = [
2926
3032
  label: "Accent"
2927
3033
  }
2928
3034
  ];
2929
- function TypographyDisclosure({ label, open, onOpenChange, children }) {
2930
- return /* @__PURE__ */ jsx(Collapsible, {
2931
- open,
2932
- onOpenChange,
2933
- children: /* @__PURE__ */ jsxs("div", {
2934
- className: "rounded-md border bg-muted/10",
2935
- children: [/* @__PURE__ */ jsx(CollapsibleTrigger, {
2936
- asChild: true,
2937
- children: /* @__PURE__ */ jsxs("button", {
2938
- type: "button",
2939
- className: "flex w-full items-center gap-2 px-3 py-2 text-left text-sm font-medium hover:bg-accent/30",
2940
- "aria-expanded": open,
2941
- children: [/* @__PURE__ */ jsx(ChevronRight, {
2942
- className: cn("h-4 w-4 transition", open && "rotate-90"),
2943
- "aria-hidden": "true"
2944
- }), /* @__PURE__ */ jsx("span", { children: label })]
2945
- })
2946
- }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", {
2947
- className: "space-y-3 border-t px-3 py-3",
2948
- children
2949
- }) })]
2950
- })
3035
+ //#endregion
3036
+ //#region ../preview-next/src/client/preview/StyleConfigurator.tsx
3037
+ const PALETTE_BRAND_ROLES = [
3038
+ {
3039
+ key: "primary",
3040
+ label: "Primary"
3041
+ },
3042
+ {
3043
+ key: "primaryForeground",
3044
+ label: "Primary text"
3045
+ },
3046
+ {
3047
+ key: "secondary",
3048
+ label: "Secondary"
3049
+ },
3050
+ {
3051
+ key: "accent",
3052
+ label: "Accent"
3053
+ }
3054
+ ];
3055
+ const PALETTE_FEEDBACK_ROLES = [
3056
+ {
3057
+ key: "success",
3058
+ label: "Success"
3059
+ },
3060
+ {
3061
+ key: "warning",
3062
+ label: "Warning"
3063
+ },
3064
+ {
3065
+ key: "destructive",
3066
+ label: "Destructive"
3067
+ }
3068
+ ];
3069
+ const PALETTE_SURFACE_ROLES = [
3070
+ {
3071
+ key: "background",
3072
+ label: "Background"
3073
+ },
3074
+ {
3075
+ key: "surface",
3076
+ label: "Surface"
3077
+ },
3078
+ {
3079
+ key: "surfaceAlt",
3080
+ label: "Alternate surface"
3081
+ },
3082
+ {
3083
+ key: "muted",
3084
+ label: "Muted surface"
3085
+ },
3086
+ {
3087
+ key: "text",
3088
+ label: "Text"
3089
+ },
3090
+ {
3091
+ key: "border",
3092
+ label: "Border"
3093
+ }
3094
+ ];
3095
+ function StyleConfigurator({ siteId, apiClient, theme, selectionId, selectionVersion = null, selectionUpdatedAt, initialSelections, headerSelectionContext, footerSelectionContext, expandedRowRequest = null, onFocusedSurfaceChange, siteStyleAccess }) {
3096
+ const router = useRouter();
3097
+ const previewDesign = usePreviewDesignCoordinator();
3098
+ const siteChromeCustomize = useSiteChromeCustomizeOptional();
3099
+ const setSiteStyleStagedPreviewActive = siteChromeCustomize?.siteStyleHistory?.setStagedPreviewActive;
3100
+ const { applyFailed, applyStarted, applySucceeded, clearPreview, previewTheme } = previewDesign;
3101
+ const [isRefreshPending, startRefreshTransition] = useTransition();
3102
+ const baseSnapshot = useMemo(() => resolveStyleConfiguratorInitialSnapshot({
3103
+ theme,
3104
+ initialSelections: {
3105
+ ...initialSelections,
3106
+ selectionVersion: initialSelections.selectionVersion ?? selectionVersion ?? 0,
3107
+ selectionUpdatedAt: initialSelections.selectionUpdatedAt ?? selectionUpdatedAt ?? null
3108
+ },
3109
+ headerSelectionContext,
3110
+ footerSelectionContext
3111
+ }), [
3112
+ footerSelectionContext,
3113
+ headerSelectionContext,
3114
+ theme,
3115
+ initialSelections,
3116
+ selectionUpdatedAt,
3117
+ selectionVersion
3118
+ ]);
3119
+ const [state, setState] = useState(() => baseSnapshot ? createStyleConfiguratorState(baseSnapshot) : null);
3120
+ const [typographySurface, setTypographySurface] = useState(typographyOverviewSurface);
3121
+ const fontCatalog = useTypographyFontCatalog(apiClient, siteId, state?.expandedRow === "typography");
3122
+ const hasLoadedState = state !== null;
3123
+ const hasStagedSiteStylePreview = state ? isPreviewingChange(state) : false;
3124
+ const hasFocusedTypographySurface = state?.expandedRow === "typography" && typographySurface.kind !== "overview";
3125
+ const stateRef = useRef(state);
3126
+ const clearPreviewRef = useRef(clearPreview);
3127
+ const runnerRef = useRef(createOptimisticActionRunner());
3128
+ const appliedExpandedRowRequestIdRef = useRef(null);
3129
+ useEffect(() => {
3130
+ stateRef.current = state;
3131
+ }, [state]);
3132
+ useEffect(() => {
3133
+ if (state?.expandedRow !== "typography" && typographySurface.kind !== "overview") setTypographySurface(typographyOverviewSurface);
3134
+ }, [state?.expandedRow, typographySurface.kind]);
3135
+ useEffect(() => {
3136
+ onFocusedSurfaceChange?.(hasFocusedTypographySurface);
3137
+ return () => {
3138
+ if (hasFocusedTypographySurface) onFocusedSurfaceChange?.(false);
3139
+ };
3140
+ }, [hasFocusedTypographySurface, onFocusedSurfaceChange]);
3141
+ useEffect(() => {
3142
+ clearPreviewRef.current = clearPreview;
3143
+ }, [clearPreview]);
3144
+ useEffect(() => {
3145
+ if (!setSiteStyleStagedPreviewActive) return;
3146
+ setSiteStyleStagedPreviewActive(hasStagedSiteStylePreview);
3147
+ return () => {
3148
+ setSiteStyleStagedPreviewActive(false);
3149
+ };
3150
+ }, [hasStagedSiteStylePreview, setSiteStyleStagedPreviewActive]);
3151
+ useEffect(() => {
3152
+ if (!baseSnapshot) return;
3153
+ setState((current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot));
3154
+ }, [baseSnapshot]);
3155
+ useEffect(() => {
3156
+ if (!state || !baseSnapshot) {
3157
+ clearPreview("style-configurator");
3158
+ return;
3159
+ }
3160
+ if (!isPreviewingChange(state)) {
3161
+ clearPreview("style-configurator");
3162
+ return;
3163
+ }
3164
+ const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
3165
+ if (!siteStyle) {
3166
+ clearPreview("style-configurator");
3167
+ return;
3168
+ }
3169
+ previewTheme("style-configurator", planSiteStylePreviewChange({
3170
+ siteStyle,
3171
+ selection: state.previewed,
3172
+ baseTheme: theme,
3173
+ headerSelectionContext,
3174
+ footerSelectionContext
3175
+ }).themeOverlay, "site-style");
3176
+ }, [
3177
+ state,
3178
+ baseSnapshot,
3179
+ clearPreview,
3180
+ previewTheme,
3181
+ headerSelectionContext,
3182
+ footerSelectionContext,
3183
+ theme
3184
+ ]);
3185
+ useEffect(() => {
3186
+ const runner = runnerRef.current;
3187
+ return () => {
3188
+ runner.invalidate();
3189
+ clearPreviewRef.current("style-configurator");
3190
+ };
3191
+ }, []);
3192
+ useEffect(() => {
3193
+ if (!expandedRowRequest) return;
3194
+ if (appliedExpandedRowRequestIdRef.current === expandedRowRequest.id) return;
3195
+ setState((current) => {
3196
+ if (!current) return current;
3197
+ appliedExpandedRowRequestIdRef.current = expandedRowRequest.id;
3198
+ return openRow(current, expandedRowForPreviewDesignRow(expandedRowRequest.row));
3199
+ });
3200
+ }, [expandedRowRequest, hasLoadedState]);
3201
+ const handlePreviewSiteStyle = useCallback((siteStyleId) => {
3202
+ setState((current) => {
3203
+ if (!current) return current;
3204
+ const next = previewSiteStyle(current, siteStyleId);
3205
+ const siteStyle = getCuratedSiteStyle(siteStyleId);
3206
+ if (!siteStyle) return next;
3207
+ const result = planSiteStylePreviewChange({
3208
+ siteStyle,
3209
+ selection: next.previewed,
3210
+ baseTheme: theme,
3211
+ headerSelectionContext,
3212
+ footerSelectionContext
3213
+ });
3214
+ return applyResolvedSelections(next, {
3215
+ resolved: {
3216
+ ...result.resolvedSelections,
3217
+ typography: result.themeOverlay.typography
3218
+ },
3219
+ warnings: result.warnings
3220
+ });
3221
+ });
3222
+ }, [
3223
+ footerSelectionContext,
3224
+ headerSelectionContext,
3225
+ theme
3226
+ ]);
3227
+ const handlePreviewPersonality = useCallback((id) => {
3228
+ setState((current) => current ? previewButtonPersonality(current, id) : current);
3229
+ }, []);
3230
+ const handlePreviewHeaderLayout = useCallback((id) => {
3231
+ if (!id) return;
3232
+ setState((current) => current ? previewHeaderLayout(current, id) : current);
3233
+ }, []);
3234
+ const handlePreviewHeaderStyle = useCallback((id) => {
3235
+ if (!id) return;
3236
+ setState((current) => current ? previewHeaderStyle(current, id) : current);
3237
+ }, []);
3238
+ const handlePreviewFooterLook = useCallback((id) => {
3239
+ const plan = planSiteChromeLookSelection({
3240
+ surface: "footer",
3241
+ lookId: id
3242
+ });
3243
+ if (plan.kind !== "footer") return;
3244
+ setState((current) => current ? previewFooterLook(current, plan.next.footerLookId) : current);
3245
+ }, []);
3246
+ const handlePreviewFooterLayout = useCallback((id) => {
3247
+ if (!id) return;
3248
+ setState((current) => {
3249
+ if (!current) return current;
3250
+ const siteStyle = getCuratedSiteStyle(current.previewed.siteStyleId);
3251
+ if (!siteStyle) return current;
3252
+ const models = buildSiteChromeLookModels({
3253
+ state: current,
3254
+ siteStyle,
3255
+ headerSelectionContext,
3256
+ footerSelectionContext
3257
+ });
3258
+ const nextLookId = planSiteChromeFooterLayoutSelection({
3259
+ layoutId: id,
3260
+ currentLookId: current.previewed.footerLookId ?? models.footer.renderedLookId
3261
+ });
3262
+ return nextLookId ? previewFooterLook(current, nextLookId) : current;
3263
+ });
3264
+ }, [footerSelectionContext, headerSelectionContext]);
3265
+ const handlePreviewPaletteItem = useCallback((item) => {
3266
+ setState((current) => {
3267
+ if (!current) return current;
3268
+ if (item.kind === "curated") return previewCuratedPalette(current, item.valueKey);
3269
+ return previewSitePalette(current, {
3270
+ paletteId: item.paletteId,
3271
+ colors: item.colors,
3272
+ mode: item.mode,
3273
+ version: item.version
3274
+ });
3275
+ });
3276
+ }, []);
3277
+ const handleCreatePalette = useCallback(() => {
3278
+ setState((current) => current ? createPalette(current) : current);
3279
+ }, []);
3280
+ const handlePaletteLibraryLoaded = useCallback((response) => {
3281
+ setState((current) => current ? reconcilePaletteLibraryActive(current, response.active, response.palettes) : current);
3282
+ }, []);
3283
+ const patchTypography = useCallback((patch) => {
3284
+ setState((current) => {
3285
+ if (!current) return current;
3286
+ const siteStyle = getCuratedSiteStyle(current.previewed.siteStyleId);
3287
+ if (!siteStyle) return current;
3288
+ return applyTypographyDraftPatch(current, planSiteStylePreviewChange({
3289
+ siteStyle,
3290
+ selection: current.previewed,
3291
+ baseTheme: theme,
3292
+ headerSelectionContext,
3293
+ footerSelectionContext
3294
+ }).themeOverlay.typography, patch);
3295
+ });
3296
+ }, [
3297
+ footerSelectionContext,
3298
+ headerSelectionContext,
3299
+ theme
3300
+ ]);
3301
+ const handleHeadingTypographyChange = useCallback((patch) => {
3302
+ patchTypography({
3303
+ kind: "heading",
3304
+ patch
3305
+ });
3306
+ }, [patchTypography]);
3307
+ const handleBodyTypographyChange = useCallback((patch) => {
3308
+ patchTypography({
3309
+ kind: "body",
3310
+ patch
3311
+ });
3312
+ }, [patchTypography]);
3313
+ const handleTypographyScaleChange = useCallback((scale) => {
3314
+ patchTypography({
3315
+ kind: "scale",
3316
+ scale
3317
+ });
3318
+ }, [patchTypography]);
3319
+ const handleResetTypography = useCallback(() => {
3320
+ setState((current) => current ? resetTypographyToSaved(current) : current);
3321
+ }, []);
3322
+ const handleTokenOverride = useCallback((role, hex) => {
3323
+ setState((current) => {
3324
+ if (!current) return current;
3325
+ const palette = palettePreviewProjection(current.previewed.palette);
3326
+ if (palette.kind === "invalid") return current;
3327
+ if (!(role in palette.colors)) return current;
3328
+ const nextColors = paletteColorsSchema.safeParse({
3329
+ ...palette.colors,
3330
+ [role]: hex
3331
+ });
3332
+ return nextColors.success ? setPaletteColors(current, nextColors.data) : current;
3333
+ });
3334
+ }, []);
3335
+ const handleResetOverrides = useCallback(() => {
3336
+ setState((current) => {
3337
+ if (!current) return current;
3338
+ const palette = palettePreviewProjection(current.previewed.palette);
3339
+ const sourceValueKey = palette.kind === "curated" ? palette.valueKey : palette.kind === "curated-fork" ? palette.sourceValueKey : null;
3340
+ if (!sourceValueKey) return current;
3341
+ const curated = findCuratedPalette(sourceValueKey);
3342
+ return curated ? setPaletteColors(current, curated.colors) : current;
3343
+ });
3344
+ }, []);
3345
+ const handleToggleRow = useCallback((row) => {
3346
+ if (row === "typography") setTypographySurface(typographyOverviewSurface);
3347
+ setState((current) => current ? toggleRow(current, row) : current);
3348
+ }, []);
3349
+ const handleCancel = useCallback(() => {
3350
+ setTypographySurface(typographyOverviewSurface);
3351
+ setState((current) => current ? cancelPreview(current) : current);
3352
+ }, []);
3353
+ const handleDismissWarnings = useCallback(() => {
3354
+ setState((current) => current ? dismissWarnings(current) : current);
3355
+ }, []);
3356
+ const handleApply = useCallback(async (retry = false) => {
3357
+ const current = stateRef.current;
3358
+ let live = current;
3359
+ if (current && retry) try {
3360
+ const paletteAuthority = await apiClient({
3361
+ endpoint: "listSiteThemePalettes",
3362
+ params: { siteId }
3363
+ }).then(unwrapResponse);
3364
+ live = prepareStalePaletteRetry(reconcilePaletteLibraryActive(current, paletteAuthority.active, paletteAuthority.palettes), { sitePalettes: paletteAuthority.palettes });
3365
+ } catch (error) {
3366
+ const message = error instanceof Error ? error.message : "Unable to refresh palette authority. Your palette draft is still here.";
3367
+ setState((latest) => latest ? markApplyFailed(latest, message, "stale") : latest);
3368
+ return;
3369
+ }
3370
+ if (!live || !isPreviewingChange(live)) return;
3371
+ if (!deriveStyleConfiguratorCapabilities(live).canApplyStaged) return;
3372
+ if (live !== current) {
3373
+ stateRef.current = live;
3374
+ setState(live);
3375
+ }
3376
+ const previewed = live.previewed;
3377
+ const siteStyle = getCuratedSiteStyle(previewed.siteStyleId);
3378
+ if (!siteStyle) return;
3379
+ const compiled = planSiteStylePreviewChange({
3380
+ siteStyle,
3381
+ selection: previewed,
3382
+ baseTheme: theme,
3383
+ headerSelectionContext,
3384
+ footerSelectionContext
3385
+ });
3386
+ await runnerRef.current.run({
3387
+ optimistic: () => {
3388
+ applyStarted("style-configurator");
3389
+ setState((current) => current ? markApplySaving(current) : current);
3390
+ },
3391
+ network: async () => {
3392
+ const body = buildApplySiteStyleSelectionBody({
3393
+ base: live.base,
3394
+ previewed: {
3395
+ ...previewed,
3396
+ buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
3397
+ headerLayoutId: compiled.resolvedSelections.headerLayoutId,
3398
+ headerStyleId: compiled.resolvedSelections.headerStyleId,
3399
+ headerLookId: compiled.resolvedSelections.headerLookId,
3400
+ typography: compiled.themeOverlay.typography
3401
+ },
3402
+ selectionId
3403
+ });
3404
+ if (!body.ok) throw new Error(paletteApplyPlanErrorMessage(body.error));
3405
+ return unwrapResponse(await apiClient({
3406
+ endpoint: "applySiteStyleSelection",
3407
+ params: { siteId },
3408
+ body: body.body
3409
+ }));
3410
+ },
3411
+ onSuccess: (saved) => {
3412
+ const persisted = selectionSnapshotFromApplySiteStyleSelectionResponse(saved, previewed);
3413
+ const historyEntry = styleConfiguratorHistoryEntryForPersistedSelection(live, persisted);
3414
+ setState((current) => current ? markApplySaved(current, persisted) : current);
3415
+ if (historyEntry) siteChromeCustomize?.siteStyleHistory?.recordAppliedSelection(historyEntry);
3416
+ applySucceeded("style-configurator");
3417
+ startRefreshTransition(() => {
3418
+ router.refresh();
3419
+ });
3420
+ },
3421
+ onError: (error) => {
3422
+ applyFailed("style-configurator");
3423
+ const applyError = classifyStyleApplyError(error);
3424
+ if (applyError.kind === "stale") startRefreshTransition(() => {
3425
+ router.refresh();
3426
+ });
3427
+ const message = applyError.message;
3428
+ setState((current) => current ? markApplyFailed(current, message, applyError.kind === "stale" ? "stale" : "retryable") : current);
3429
+ }
3430
+ });
3431
+ }, [
3432
+ apiClient,
3433
+ footerSelectionContext,
3434
+ headerSelectionContext,
3435
+ applyFailed,
3436
+ applyStarted,
3437
+ applySucceeded,
3438
+ router,
3439
+ selectionId,
3440
+ siteChromeCustomize?.siteStyleHistory,
3441
+ siteId,
3442
+ startRefreshTransition,
3443
+ theme
3444
+ ]);
3445
+ if (!state || !baseSnapshot) return /* @__PURE__ */ jsx("p", {
3446
+ className: "text-sm text-muted-foreground",
3447
+ children: "Loading design controls..."
2951
3448
  });
2952
- }
2953
- function TypographySelectControl({ label, value, options, onChange, disabled }) {
2954
- return /* @__PURE__ */ jsxs("label", {
2955
- className: "block space-y-1",
2956
- children: [/* @__PURE__ */ jsx("span", {
2957
- className: "text-xs font-medium text-muted-foreground",
2958
- children: label
2959
- }), /* @__PURE__ */ jsx("select", {
2960
- value,
2961
- onChange: (event) => onChange(event.currentTarget.value),
2962
- disabled,
2963
- className: "h-8 w-full rounded-md border bg-background px-2 text-sm outline-none focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
2964
- children: options.map((option) => /* @__PURE__ */ jsx("option", {
2965
- value: option.value,
2966
- children: option.label
2967
- }, option.value))
2968
- })]
3449
+ const applyAllowed = canApplySiteStyle(siteStyleAccess);
3450
+ const stagedCapabilities = deriveStyleConfiguratorCapabilities(state);
3451
+ const previewingChange = isPreviewingChange(state);
3452
+ const saving = stagedCapabilities.isApplyingStaged || isRefreshPending;
3453
+ const aggregateMutationControlsDisabled = saving || !stagedCapabilities.canPreviewStaged;
3454
+ const currentSiteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
3455
+ if (!currentSiteStyle) return /* @__PURE__ */ jsx("p", {
3456
+ className: "text-sm text-muted-foreground",
3457
+ children: "This site uses a Site Style that cannot be configured here."
2969
3458
  });
2970
- }
2971
- function TypographyColorTokenControl({ value, onChange, disabled }) {
2972
- return /* @__PURE__ */ jsxs("section", {
2973
- className: "space-y-2",
2974
- "aria-label": "Default heading color",
2975
- children: [/* @__PURE__ */ jsxs("div", {
2976
- className: "flex items-center justify-between gap-2",
2977
- children: [/* @__PURE__ */ jsx("span", {
2978
- className: "text-xs font-medium text-muted-foreground",
2979
- children: "Default heading color"
2980
- }), /* @__PURE__ */ jsx("span", {
2981
- className: "text-[11px] text-muted-foreground",
2982
- children: "Sections may override"
2983
- })]
3459
+ const currentPersonality = findButtonPersonality(state.previewed.buttonPersonalityId);
3460
+ const currentPalette = palettePreviewProjection(state.previewed.palette);
3461
+ const paletteMutation = paletteMutationCapability(state);
3462
+ const paletteControlsDisabled = saving || !stagedCapabilities.canPreviewStaged || paletteMutation.kind !== "editable";
3463
+ const currentCuratedValueKey = currentPalette.kind === "curated" ? currentPalette.valueKey : currentPalette.kind === "curated-fork" ? currentPalette.sourceValueKey : null;
3464
+ const currentCuratedPalette = currentCuratedValueKey ? findCuratedPalette(currentCuratedValueKey) : null;
3465
+ const paletteIsCustom = currentPalette.kind !== "invalid" && (!currentCuratedPalette || currentPalette.mode !== currentCuratedPalette.mode || !paletteColorsEqual(currentPalette.colors, currentCuratedPalette.colors));
3466
+ const currentTypography = planSiteStylePreviewChange({
3467
+ siteStyle: currentSiteStyle,
3468
+ selection: state.previewed,
3469
+ baseTheme: theme,
3470
+ headerSelectionContext,
3471
+ footerSelectionContext
3472
+ }).themeOverlay.typography;
3473
+ const chromeLookModels = buildSiteChromeLookModels({
3474
+ state,
3475
+ siteStyle: currentSiteStyle,
3476
+ headerSelectionContext,
3477
+ footerSelectionContext
3478
+ });
3479
+ const currentHeaderLayout = getHeaderLayout(state.previewed.headerLayoutId);
3480
+ const currentHeaderStyle = getHeaderStyle(state.previewed.headerStyleId);
3481
+ const currentFooterLayout = getFooterLayout(chromeLookModels.footerLayout.renderedLayoutId);
3482
+ const currentFooterLook = state.previewed.footerLookId ? findFooterLook(state.previewed.footerLookId) : findFooterLook(chromeLookModels.footer.renderedLookId);
3483
+ const headerSurfaceState = siteChromeCustomize?.getSurfaceState("header") ?? { kind: "unavailable" };
3484
+ if (state.expandedRow === "typography" && typographySurface.kind !== "overview") return /* @__PURE__ */ jsxs("div", {
3485
+ className: "flex h-full min-h-0 flex-col",
3486
+ "data-style-configurator-focused-surface": true,
3487
+ children: [typographySurface.kind === "font-picker" ? /* @__PURE__ */ jsx(TypographyFontPickerTakeover, {
3488
+ slot: typographySurface.slot,
3489
+ value: typographySurface.slot === "heading" ? currentTypography.headings.default.family : currentTypography.body.family,
3490
+ catalog: fontCatalog,
3491
+ onBack: () => setTypographySurface(typographyOverviewSurface),
3492
+ onChange: (family) => typographySurface.slot === "heading" ? handleHeadingTypographyChange({ family }) : handleBodyTypographyChange({ family }),
3493
+ disabled: aggregateMutationControlsDisabled
3494
+ }) : /* @__PURE__ */ jsx(TypographyAdvancedTakeover, {
3495
+ typography: currentTypography,
3496
+ catalog: fontCatalog,
3497
+ onBack: () => setTypographySurface(typographyOverviewSurface),
3498
+ onHeadingChange: handleHeadingTypographyChange,
3499
+ onBodyChange: handleBodyTypographyChange,
3500
+ onScaleChange: handleTypographyScaleChange,
3501
+ disabled: aggregateMutationControlsDisabled
2984
3502
  }), /* @__PURE__ */ jsx("div", {
2985
- className: "grid grid-cols-2 gap-1",
2986
- children: headingColorTokenOptions.map((option) => /* @__PURE__ */ jsx("button", {
2987
- type: "button",
2988
- className: cn("rounded-md border px-2 py-1.5 text-left text-xs transition disabled:cursor-not-allowed disabled:opacity-50", value === option.value ? "border-primary bg-primary/10" : "border-border hover:bg-muted/60"),
2989
- "aria-pressed": value === option.value,
2990
- onClick: () => onChange(option.value),
2991
- disabled,
2992
- children: option.label
2993
- }, option.value))
3503
+ className: "shrink-0 px-3 pb-3",
3504
+ children: /* @__PURE__ */ jsx(StyleConfiguratorSaveControls, {
3505
+ previewingChange,
3506
+ applyAllowed,
3507
+ saving,
3508
+ canCancel: stagedCapabilities.canCancelStaged,
3509
+ canApply: stagedCapabilities.canApplyStaged,
3510
+ saveStatus: state.saveStatus,
3511
+ onCancel: handleCancel,
3512
+ onApply: () => void handleApply(),
3513
+ onRetry: () => void handleApply(true)
3514
+ })
2994
3515
  })]
2995
3516
  });
2996
- }
2997
- function normalizeTypographyScaleForControl(scale) {
2998
- switch (scale) {
2999
- case "compact":
3000
- case "balanced":
3001
- case "spacious": return scale;
3002
- case "minorThird": return "compact";
3003
- case "majorThird": return "balanced";
3004
- case "perfectFourth": return "spacious";
3005
- default: return "balanced";
3006
- }
3007
- }
3008
- function TypographyPresetOption({ preset, selected, onSelect, disabled }) {
3009
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
3010
- type: "button",
3011
- role: "radio",
3012
- "aria-checked": selected,
3013
- onClick: onSelect,
3014
- disabled,
3015
- className: cn("w-full rounded-md border px-3 py-2 text-left transition disabled:cursor-not-allowed disabled:opacity-50", selected ? "border-primary bg-primary/5" : "border-border hover:bg-muted/60"),
3016
- children: /* @__PURE__ */ jsxs("div", {
3017
- className: "flex items-start justify-between gap-3",
3018
- children: [/* @__PURE__ */ jsxs("div", {
3019
- className: "min-w-0",
3020
- children: [/* @__PURE__ */ jsx("div", {
3021
- className: "text-sm font-medium",
3022
- children: preset.name
3023
- }), /* @__PURE__ */ jsx("div", {
3024
- className: "text-xs text-muted-foreground",
3025
- children: preset.description
3026
- })]
3027
- }), /* @__PURE__ */ jsxs("div", {
3028
- className: "shrink-0 text-right",
3029
- children: [/* @__PURE__ */ jsx("div", {
3030
- className: "text-[11px] font-semibold uppercase tracking-wide",
3031
- children: preset.defaultFonts.heading.split(",")[0]
3032
- }), /* @__PURE__ */ jsx("div", {
3033
- className: "text-[11px] text-muted-foreground",
3034
- children: preset.defaultFonts.body.split(",")[0]
3035
- })]
3036
- })]
3037
- })
3038
- }) });
3039
- }
3040
- function TypographyFontSlotPicker({ label, slot, preset, catalog, value, onChange, disabled }) {
3041
- const [query, setQuery] = useState("");
3042
- const [manualFamilyDraft, setManualFamilyDraft] = useState(value);
3043
- const defaultFont = primaryFontFamily(preset.defaultFonts[slot]);
3044
- const selectedFamily = primaryFontFamily(value);
3045
- useEffect(() => {
3046
- setManualFamilyDraft(value);
3047
- }, [value]);
3048
- const commitManualFamilyDraft = useCallback(() => {
3049
- const normalized = normalizeFontFamilyOverride(manualFamilyDraft);
3050
- if (!normalized) {
3051
- setManualFamilyDraft(value);
3052
- return;
3053
- }
3054
- onChange(normalized);
3055
- }, [
3056
- manualFamilyDraft,
3057
- onChange,
3058
- value
3059
- ]);
3060
- const recordsByFamily = useMemo(() => indexFontsByFamily(catalog.fonts), [catalog.fonts]);
3061
- const selectedFit = evaluateTypographyFontSlot({
3062
- slot,
3063
- policy: preset.slotPolicies[slot],
3064
- capability: capabilityForFamily(selectedFamily, recordsByFamily)
3065
- });
3066
- const recommendedFamilies = preset.recommendedFonts[slot];
3067
- const recommendedOptions = useMemo(() => uniqueFontFamilies(recommendedFamilies), [recommendedFamilies]);
3068
- const browseOptions = useMemo(() => {
3069
- const normalizedQuery = query.trim().toLowerCase();
3070
- return [...normalizedQuery.length > 0 ? catalog.fonts.filter((font) => font.family.toLowerCase().includes(normalizedQuery)) : catalog.fonts].sort((a, b) => sortFontForPresetSlot(a, b, slot, preset, recordsByFamily)).slice(0, normalizedQuery.length > 0 ? MAX_FONT_RESULTS_WHEN_SEARCHING : MAX_FONT_RESULTS_WHEN_BROWSING);
3071
- }, [
3072
- catalog.fonts,
3073
- preset,
3074
- query,
3075
- recordsByFamily,
3076
- slot
3077
- ]);
3078
- return /* @__PURE__ */ jsxs("section", {
3079
- className: "space-y-2",
3080
- "aria-label": label,
3517
+ return /* @__PURE__ */ jsxs("div", {
3518
+ className: "space-y-3",
3081
3519
  children: [
3082
- /* @__PURE__ */ jsxs("div", {
3083
- className: "flex items-center justify-between gap-3",
3084
- children: [/* @__PURE__ */ jsxs("div", {
3085
- className: "min-w-0",
3086
- children: [/* @__PURE__ */ jsx("div", {
3087
- className: "text-xs font-medium text-muted-foreground",
3088
- children: label
3089
- }), /* @__PURE__ */ jsx("div", {
3090
- className: "truncate text-sm",
3091
- children: selectedFamily
3092
- })]
3093
- }), /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit: selectedFit })]
3520
+ state.warnings.length > 0 ? /* @__PURE__ */ jsx(WarningNotice, {
3521
+ warnings: state.warnings,
3522
+ onDismiss: handleDismissWarnings
3523
+ }) : null,
3524
+ /* @__PURE__ */ jsx(SiteStyleRow, {
3525
+ expanded: state.expandedRow === "style",
3526
+ onToggle: () => handleToggleRow("style"),
3527
+ current: currentSiteStyle,
3528
+ onPreview: handlePreviewSiteStyle,
3529
+ disabled: aggregateMutationControlsDisabled
3094
3530
  }),
3095
- /* @__PURE__ */ jsxs("div", {
3096
- className: "grid grid-cols-1 gap-1",
3097
- children: [/* @__PURE__ */ jsxs("button", {
3098
- type: "button",
3099
- className: cn("rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50", selectedFamily === defaultFont ? "border-foreground bg-accent/40" : "border-transparent hover:border-foreground/40 hover:bg-accent/20"),
3100
- onClick: () => onChange(preset.defaultFonts[slot]),
3101
- disabled,
3102
- children: [/* @__PURE__ */ jsx("span", {
3103
- className: "font-medium",
3104
- children: "Use recipe font"
3105
- }), /* @__PURE__ */ jsx("span", {
3106
- className: "ml-2 text-xs text-muted-foreground",
3107
- children: defaultFont
3108
- })]
3109
- }), recommendedOptions.map((family) => /* @__PURE__ */ jsx(FontFamilyOption, {
3110
- family,
3111
- selected: selectedFamily === family,
3112
- fit: evaluateTypographyFontSlot({
3113
- slot,
3114
- policy: preset.slotPolicies[slot],
3115
- capability: capabilityForFamily(family, recordsByFamily)
3116
- }),
3117
- onSelect: () => onChange(family),
3118
- disabled
3119
- }, family))]
3531
+ /* @__PURE__ */ jsx(SiteChromeLookRow, {
3532
+ label: "Header layout",
3533
+ ariaLabel: "Header layout",
3534
+ defaultDescription: "Choose the header structure.",
3535
+ expanded: state.expandedRow === "header-layout",
3536
+ onToggle: () => handleToggleRow("header-layout"),
3537
+ currentId: state.previewed.headerLayoutId,
3538
+ currentLabel: currentHeaderLayout?.label ?? state.previewed.headerLayoutId,
3539
+ scopeDescription: "Changes the header structure on every page.",
3540
+ options: chromeLookModels.headerLayout.options,
3541
+ diagnostics: [],
3542
+ onCycle: (direction) => {
3543
+ const next = cycleSiteChromeLookId(chromeLookModels.headerLayout.options, state.previewed.headerLayoutId, direction);
3544
+ if (next !== void 0) handlePreviewHeaderLayout(next);
3545
+ },
3546
+ onPreview: handlePreviewHeaderLayout,
3547
+ disabled: aggregateMutationControlsDisabled
3548
+ }),
3549
+ /* @__PURE__ */ jsx(SiteChromeLookRow, {
3550
+ label: "Header look",
3551
+ ariaLabel: "Header look",
3552
+ defaultDescription: "Choose the header visual style.",
3553
+ expanded: state.expandedRow === "header-style",
3554
+ onToggle: () => handleToggleRow("header-style"),
3555
+ currentId: state.previewed.headerStyleId,
3556
+ currentLabel: currentHeaderStyle?.label ?? state.previewed.headerStyleId,
3557
+ scopeDescription: "Changes the header surface and link style.",
3558
+ options: chromeLookModels.headerStyle.options,
3559
+ diagnostics: [],
3560
+ onCycle: (direction) => {
3561
+ const next = cycleSiteChromeLookId(chromeLookModels.headerStyle.options, state.previewed.headerStyleId, direction);
3562
+ if (next !== void 0) handlePreviewHeaderStyle(next);
3563
+ },
3564
+ onPreview: handlePreviewHeaderStyle,
3565
+ disabled: aggregateMutationControlsDisabled,
3566
+ extraControls: isHeaderMenuSurfaceState(headerSurfaceState) ? /* @__PURE__ */ jsx(HeaderMenuControls, {
3567
+ state: headerSurfaceState,
3568
+ disabled: saving,
3569
+ onMenuFontChange: siteChromeCustomize?.setHeaderMenuFont,
3570
+ onMenuFontSizeChange: siteChromeCustomize?.setHeaderMenuFontSize
3571
+ }) : null
3572
+ }),
3573
+ /* @__PURE__ */ jsx(SiteChromeLookRow, {
3574
+ label: "Footer layout",
3575
+ ariaLabel: "Footer layout",
3576
+ defaultDescription: "Choose the footer structure.",
3577
+ expanded: state.expandedRow === "footer-layout",
3578
+ onToggle: () => handleToggleRow("footer-layout"),
3579
+ currentId: chromeLookModels.footerLayout.renderedLayoutId,
3580
+ currentLabel: currentFooterLayout?.label ?? chromeLookModels.footerLayout.renderedLayoutId,
3581
+ scopeDescription: "Changes the footer structure on every page.",
3582
+ options: chromeLookModels.footerLayout.options,
3583
+ diagnostics: [],
3584
+ onCycle: (direction) => {
3585
+ const next = cycleSiteChromeLookId(chromeLookModels.footerLayout.options, chromeLookModels.footerLayout.renderedLayoutId, direction);
3586
+ if (next !== void 0) handlePreviewFooterLayout(next);
3587
+ },
3588
+ onPreview: handlePreviewFooterLayout,
3589
+ disabled: aggregateMutationControlsDisabled
3590
+ }),
3591
+ /* @__PURE__ */ jsx(SiteChromeLookRow, {
3592
+ label: "Footer look",
3593
+ ariaLabel: "Footer look",
3594
+ defaultDescription: "Use the footer look chosen for this site style.",
3595
+ expanded: state.expandedRow === "footer",
3596
+ onToggle: () => handleToggleRow("footer"),
3597
+ currentId: state.previewed.footerLookId,
3598
+ currentLabel: state.previewed.footerLookId ? currentFooterLook?.label ?? "Unknown footer Look" : `${currentFooterLook?.label ?? "Site style"} (default)`,
3599
+ scopeDescription: "Changes the footer on every page.",
3600
+ options: chromeLookModels.footer.options,
3601
+ diagnostics: chromeLookModels.footer.diagnostics,
3602
+ onCycle: (direction) => {
3603
+ const next = cycleSiteChromeLookId(chromeLookModels.footer.options, state.previewed.footerLookId, direction);
3604
+ if (next !== void 0) handlePreviewFooterLook(next);
3605
+ },
3606
+ onPreview: handlePreviewFooterLook,
3607
+ disabled: aggregateMutationControlsDisabled
3608
+ }),
3609
+ /* @__PURE__ */ jsx(PersonalityRow, {
3610
+ expanded: state.expandedRow === "buttons",
3611
+ onToggle: () => handleToggleRow("buttons"),
3612
+ siteStyle: currentSiteStyle,
3613
+ currentId: state.previewed.buttonPersonalityId,
3614
+ currentLabel: currentPersonality?.name ?? state.previewed.buttonPersonalityId,
3615
+ onPreview: handlePreviewPersonality,
3616
+ disabled: aggregateMutationControlsDisabled
3617
+ }),
3618
+ /* @__PURE__ */ jsx(TypographyRow, {
3619
+ expanded: state.expandedRow === "typography",
3620
+ onToggle: () => handleToggleRow("typography"),
3621
+ typography: currentTypography,
3622
+ catalog: fontCatalog,
3623
+ currentLabel: `${primaryFontFamily(currentTypography.headings.default.family)} / ${primaryFontFamily(currentTypography.body.family)}`,
3624
+ onChooseFont: (slot) => setTypographySurface({
3625
+ kind: "font-picker",
3626
+ slot
3627
+ }),
3628
+ onOpenAdvanced: () => setTypographySurface({ kind: "advanced" }),
3629
+ onReset: handleResetTypography,
3630
+ disabled: aggregateMutationControlsDisabled
3120
3631
  }),
3121
- /* @__PURE__ */ jsxs("div", {
3122
- className: "space-y-2 rounded-md border bg-muted/20 p-2",
3123
- children: [
3124
- /* @__PURE__ */ jsx("input", {
3125
- type: "search",
3126
- value: query,
3127
- "aria-label": `Search ${slot} fonts`,
3128
- placeholder: `Search ${slot} fonts`,
3129
- onChange: (event) => setQuery(event.currentTarget.value),
3130
- disabled: disabled || catalog.kind === "loading",
3131
- className: "h-8 w-full rounded-md border bg-background px-2 text-sm outline-none focus:border-primary disabled:cursor-not-allowed disabled:opacity-50"
3132
- }),
3133
- catalog.kind === "loading" ? /* @__PURE__ */ jsx("p", {
3134
- className: "text-xs text-muted-foreground",
3135
- children: "Loading font catalog..."
3136
- }) : catalog.kind === "failed" ? /* @__PURE__ */ jsx("p", {
3137
- className: "text-xs text-muted-foreground",
3138
- children: catalog.message
3139
- }) : null,
3140
- browseOptions.length > 0 ? /* @__PURE__ */ jsx("div", {
3141
- className: "max-h-56 space-y-1 overflow-y-auto pr-1",
3142
- children: browseOptions.map((font) => /* @__PURE__ */ jsx(FontFamilyOption, {
3143
- family: font.family,
3144
- selected: selectedFamily === font.family,
3145
- fit: evaluateTypographyFontSlot({
3146
- slot,
3147
- policy: preset.slotPolicies[slot],
3148
- capability: capabilityFromFontRecord(font)
3149
- }),
3150
- onSelect: () => onChange(font.family),
3151
- disabled
3152
- }, font.family))
3153
- }) : catalog.kind === "loaded" ? /* @__PURE__ */ jsx("p", {
3154
- className: "text-xs text-muted-foreground",
3155
- children: "No matching Google Fonts."
3156
- }) : null
3157
- ]
3632
+ /* @__PURE__ */ jsx(PaletteRow, {
3633
+ expanded: state.expandedRow === "palette",
3634
+ onToggle: () => handleToggleRow("palette"),
3635
+ apiClient,
3636
+ siteId,
3637
+ siteStyle: currentSiteStyle,
3638
+ palette: currentPalette,
3639
+ committedSelection: committedPaletteForSnapshot(state.base.palette)?.selection ?? null,
3640
+ onPreview: handlePreviewPaletteItem,
3641
+ onCreate: handleCreatePalette,
3642
+ onLibraryLoaded: handlePaletteLibraryLoaded,
3643
+ isCustom: paletteIsCustom,
3644
+ onTokenChange: handleTokenOverride,
3645
+ onReset: handleResetOverrides,
3646
+ mutationCapability: paletteMutation,
3647
+ disabled: saving || !applyAllowed,
3648
+ mutationDisabled: paletteControlsDisabled || !applyAllowed
3158
3649
  }),
3159
- /* @__PURE__ */ jsxs("label", {
3160
- className: "block space-y-1",
3161
- children: [/* @__PURE__ */ jsx("span", {
3162
- className: "text-xs text-muted-foreground",
3163
- children: "Brand or uploaded font name"
3164
- }), /* @__PURE__ */ jsx("input", {
3165
- type: "text",
3166
- value: manualFamilyDraft,
3167
- placeholder: "Enter any font family",
3168
- onChange: (event) => setManualFamilyDraft(event.currentTarget.value),
3169
- onBlur: commitManualFamilyDraft,
3170
- onKeyDown: (event) => {
3171
- if (event.key !== "Enter") return;
3172
- event.currentTarget.blur();
3173
- },
3174
- disabled,
3175
- className: "h-8 w-full rounded-md border bg-background px-2 text-sm outline-none focus:border-primary disabled:cursor-not-allowed disabled:opacity-50"
3176
- })]
3650
+ /* @__PURE__ */ jsx(StyleConfiguratorSaveControls, {
3651
+ previewingChange,
3652
+ applyAllowed,
3653
+ saving,
3654
+ canCancel: stagedCapabilities.canCancelStaged,
3655
+ canApply: stagedCapabilities.canApplyStaged,
3656
+ saveStatus: state.saveStatus,
3657
+ onCancel: handleCancel,
3658
+ onApply: () => void handleApply(),
3659
+ onRetry: () => void handleApply(true)
3177
3660
  })
3178
3661
  ]
3179
3662
  });
3180
3663
  }
3181
- function FontFamilyOption({ family, selected, fit, onSelect, disabled }) {
3182
- return /* @__PURE__ */ jsxs("button", {
3183
- type: "button",
3184
- className: cn("flex w-full items-center justify-between gap-3 rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50", selected ? "border-foreground bg-accent/40" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20"),
3185
- onClick: onSelect,
3186
- disabled,
3187
- children: [/* @__PURE__ */ jsx("span", {
3188
- className: "truncate font-medium",
3189
- children: family
3190
- }), /* @__PURE__ */ jsx(TypographyFontFitBadge, { fit })]
3191
- });
3664
+ function StyleConfiguratorSaveControls({ previewingChange, applyAllowed, saving, canCancel, canApply, saveStatus, onCancel, onApply, onRetry }) {
3665
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
3666
+ previewingChange ? /* @__PURE__ */ jsxs("div", {
3667
+ className: "flex items-center gap-2 pt-2",
3668
+ children: [/* @__PURE__ */ jsx(Button, {
3669
+ type: "button",
3670
+ size: "sm",
3671
+ variant: "outline",
3672
+ onClick: onCancel,
3673
+ disabled: !canCancel || saving,
3674
+ children: "Cancel"
3675
+ }), applyAllowed ? /* @__PURE__ */ jsx(Button, {
3676
+ type: "button",
3677
+ size: "sm",
3678
+ onClick: onApply,
3679
+ disabled: !canApply || saving,
3680
+ children: saving ? "Applying…" : "Apply"
3681
+ }) : /* @__PURE__ */ jsx("span", {
3682
+ className: "text-xs text-muted-foreground",
3683
+ children: "Preview only — ask an admin to apply."
3684
+ })]
3685
+ }) : null,
3686
+ saveStatus.kind === "saved" ? /* @__PURE__ */ jsx("p", {
3687
+ className: "text-xs text-success",
3688
+ children: "Saved."
3689
+ }) : null,
3690
+ saveStatus.kind === "failed" ? /* @__PURE__ */ jsxs("div", {
3691
+ role: "status",
3692
+ className: "flex items-center gap-2 text-xs text-destructive",
3693
+ children: [/* @__PURE__ */ jsx("span", { children: saveStatus.message }), saveStatus.failureKind === "stale" && previewingChange && applyAllowed ? /* @__PURE__ */ jsx(Button, {
3694
+ type: "button",
3695
+ size: "sm",
3696
+ variant: "outline",
3697
+ onClick: onRetry,
3698
+ disabled: saving,
3699
+ children: "Retry"
3700
+ }) : null]
3701
+ }) : null
3702
+ ] });
3192
3703
  }
3193
- function TypographyFontFitBadge({ fit }) {
3194
- return /* @__PURE__ */ jsx("span", {
3195
- className: cn("shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium", fit.kind === "recommended" && "bg-emerald-100 text-emerald-800", fit.kind === "compatible" && "bg-sky-100 text-sky-800", fit.kind === "limited" && "bg-amber-100 text-amber-900", fit.kind === "risky" && "bg-rose-100 text-rose-800", fit.kind === "unknown" && "bg-muted text-muted-foreground"),
3196
- title: fit.reason,
3197
- children: typographyFontFitLabel(fit.kind)
3704
+ function StyleRowFrame({ label, currentLabel, swatch, expanded, onToggle, children }) {
3705
+ return /* @__PURE__ */ jsx(Collapsible, {
3706
+ open: expanded,
3707
+ onOpenChange: onToggle,
3708
+ children: /* @__PURE__ */ jsxs("div", {
3709
+ className: "rounded-md border bg-background",
3710
+ children: [/* @__PURE__ */ jsx(CollapsibleTrigger, {
3711
+ asChild: true,
3712
+ children: /* @__PURE__ */ jsxs("button", {
3713
+ type: "button",
3714
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-accent/30",
3715
+ "aria-expanded": expanded,
3716
+ children: [
3717
+ /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-4 w-4 transition", expanded && "rotate-90") }),
3718
+ /* @__PURE__ */ jsxs("div", {
3719
+ className: "flex-1 min-w-0",
3720
+ children: [/* @__PURE__ */ jsx("div", {
3721
+ className: "text-xs uppercase tracking-wide text-muted-foreground",
3722
+ children: label
3723
+ }), /* @__PURE__ */ jsx("div", {
3724
+ className: "truncate text-sm font-medium",
3725
+ children: currentLabel
3726
+ })]
3727
+ }),
3728
+ swatch ? /* @__PURE__ */ jsx("span", {
3729
+ className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
3730
+ style: { backgroundColor: swatch },
3731
+ "aria-hidden": "true"
3732
+ }) : null
3733
+ ]
3734
+ })
3735
+ }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", {
3736
+ className: "border-t px-3 py-3",
3737
+ children
3738
+ }) })]
3739
+ })
3198
3740
  });
3199
3741
  }
3200
- function useFontCatalog(apiClient, enabled) {
3201
- const [catalog, setCatalog] = useState({
3202
- kind: "idle",
3203
- fonts: []
3742
+ function SiteStyleRow({ expanded, onToggle, current, onPreview, disabled }) {
3743
+ return /* @__PURE__ */ jsx(StyleRowFrame, {
3744
+ label: "Site style",
3745
+ currentLabel: current.name,
3746
+ expanded,
3747
+ onToggle,
3748
+ children: /* @__PURE__ */ jsx("ul", {
3749
+ role: "radiogroup",
3750
+ "aria-label": "Site style",
3751
+ className: "list-none space-y-1 p-0",
3752
+ children: curatedSiteStyles.map((style) => /* @__PURE__ */ jsx(PickerOption, {
3753
+ selected: style.id === current.id,
3754
+ label: style.name,
3755
+ description: style.description,
3756
+ onSelect: () => onPreview(style.id),
3757
+ disabled
3758
+ }, style.id))
3759
+ })
3204
3760
  });
3205
- const requestedClientRef = useRef(null);
3206
- useEffect(() => {
3207
- if (!enabled) return;
3208
- if (requestedClientRef.current === apiClient) return;
3209
- requestedClientRef.current = apiClient;
3210
- let cancelled = false;
3211
- setCatalog({
3212
- kind: "loading",
3213
- fonts: []
3214
- });
3215
- async function loadFonts() {
3216
- try {
3217
- const fonts = unwrapResponse(await apiClient({ endpoint: "listFonts" }));
3218
- if (!cancelled) setCatalog({
3219
- kind: "loaded",
3220
- fonts: Array.isArray(fonts) ? fonts : []
3221
- });
3222
- } catch (error) {
3223
- if (!cancelled) setCatalog({
3224
- kind: "failed",
3225
- fonts: [],
3226
- message: error instanceof Error ? error.message : "Unable to load the font catalog."
3227
- });
3228
- }
3229
- }
3230
- loadFonts();
3231
- return () => {
3232
- cancelled = true;
3233
- };
3234
- }, [apiClient, enabled]);
3235
- return catalog;
3236
- }
3237
- function indexFontsByFamily(fonts) {
3238
- return new Map(fonts.map((font) => [font.family.toLowerCase(), font]));
3239
- }
3240
- function capabilityForFamily(family, recordsByFamily) {
3241
- const record = recordsByFamily.get(family.toLowerCase());
3242
- if (record) return capabilityFromFontRecord(record);
3243
- return {
3244
- source: "unknown",
3245
- family,
3246
- category: "unknown",
3247
- variants: []
3248
- };
3249
- }
3250
- function capabilityFromFontRecord(font) {
3251
- return {
3252
- source: "google",
3253
- family: font.family,
3254
- category: normalizeGoogleFontCategory(font.category),
3255
- variants: font.variants
3256
- };
3257
- }
3258
- function normalizeGoogleFontCategory(category) {
3259
- switch (category) {
3260
- case "serif":
3261
- case "sans-serif":
3262
- case "display":
3263
- case "handwriting":
3264
- case "monospace": return category;
3265
- default: return "unknown";
3266
- }
3267
- }
3268
- function sortFontForPresetSlot(a, b, slot, preset, recordsByFamily) {
3269
- const aFit = evaluateTypographyFontSlot({
3270
- slot,
3271
- policy: preset.slotPolicies[slot],
3272
- capability: capabilityForFamily(a.family, recordsByFamily)
3273
- });
3274
- const bFit = evaluateTypographyFontSlot({
3275
- slot,
3276
- policy: preset.slotPolicies[slot],
3277
- capability: capabilityForFamily(b.family, recordsByFamily)
3278
- });
3279
- const fitDelta = typographyFontFitRank(aFit.kind) - typographyFontFitRank(bFit.kind);
3280
- if (fitDelta !== 0) return fitDelta;
3281
- const popularityDelta = (a.popularity ?? Infinity) - (b.popularity ?? Infinity);
3282
- if (popularityDelta !== 0) return popularityDelta;
3283
- return a.family.localeCompare(b.family);
3284
- }
3285
- function typographyFontFitRank(kind) {
3286
- switch (kind) {
3287
- case "recommended": return 0;
3288
- case "compatible": return 1;
3289
- case "limited": return 2;
3290
- case "risky": return 3;
3291
- case "unknown": return 4;
3292
- default: return kind;
3293
- }
3294
- }
3295
- function typographyFontFitLabel(kind) {
3296
- switch (kind) {
3297
- case "recommended": return "Recommended";
3298
- case "compatible": return "Compatible";
3299
- case "limited": return "Limited";
3300
- case "risky": return "Risky";
3301
- case "unknown": return "Unknown";
3302
- default: return kind;
3303
- }
3304
- }
3305
- function primaryFontFamily(cssFamily) {
3306
- return cssFamily.split(",")[0]?.trim().replace(/^["']|["']$/gu, "") || cssFamily;
3307
3761
  }
3308
- function uniqueFontFamilies(families) {
3309
- const seen = /* @__PURE__ */ new Set();
3310
- const result = [];
3311
- for (const family of families) {
3312
- const normalized = primaryFontFamily(family);
3313
- const key = normalized.toLowerCase();
3314
- if (seen.has(key)) continue;
3315
- seen.add(key);
3316
- result.push(normalized);
3317
- }
3318
- return result;
3762
+ function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel, onPreview, disabled }) {
3763
+ return /* @__PURE__ */ jsx(StyleRowFrame, {
3764
+ label: "Button style",
3765
+ currentLabel,
3766
+ expanded,
3767
+ onToggle,
3768
+ children: /* @__PURE__ */ jsx("ul", {
3769
+ role: "radiogroup",
3770
+ "aria-label": "Button style",
3771
+ className: "list-none space-y-1 p-0",
3772
+ children: useMemo(() => requireEntriesForCuratedChoices(buttonPersonalities, siteStyle.buttonPersonalityChoices), [siteStyle.buttonPersonalityChoices]).map((personality) => /* @__PURE__ */ jsx(PickerOption, {
3773
+ selected: personality.id === currentId,
3774
+ label: personality.name,
3775
+ description: personality.description,
3776
+ onSelect: () => onPreview(personality.id),
3777
+ disabled
3778
+ }, personality.id))
3779
+ })
3780
+ });
3319
3781
  }
3320
- function normalizeFontFamilyOverride(value) {
3321
- const trimmed = value.trim();
3322
- return trimmed.length > 0 ? trimmed : null;
3782
+ function TypographyRow({ expanded, onToggle, typography, catalog, currentLabel, onChooseFont, onOpenAdvanced, onReset, disabled }) {
3783
+ return /* @__PURE__ */ jsx(StyleRowFrame, {
3784
+ label: "Typography",
3785
+ currentLabel,
3786
+ expanded,
3787
+ onToggle,
3788
+ children: /* @__PURE__ */ jsx(TypographyOverviewContent, {
3789
+ typography,
3790
+ catalog,
3791
+ onChooseFont,
3792
+ onOpenAdvanced,
3793
+ onReset,
3794
+ disabled
3795
+ })
3796
+ });
3323
3797
  }
3324
3798
  function SiteChromeLookRow({ label, ariaLabel, defaultDescription, catalog, options = [{
3325
3799
  id: null,
@@ -3417,93 +3891,254 @@ function HeaderMenuControls({ state, disabled, onMenuFontChange, onMenuFontSizeC
3417
3891
  })]
3418
3892
  });
3419
3893
  }
3420
- function PaletteRow({ expanded, onToggle, siteStyle, currentId, currentLabel, currentSwatch, onPreview, mutationCapability, disabled }) {
3421
- const options = useMemo(() => {
3422
- const recommended = requireEntriesForCuratedChoices(curatedPalettes, siteStyle.paletteRecommendations);
3423
- if (recommended.some((variant) => variant.id === currentId)) return recommended;
3424
- const current = currentId ? findCuratedPalette(currentId) : null;
3425
- return current ? [current, ...recommended] : recommended;
3426
- }, [currentId, siteStyle.paletteRecommendations]);
3894
+ function PaletteRow({ expanded, onToggle, apiClient, siteId, siteStyle, palette, committedSelection, onPreview, onCreate, onLibraryLoaded, isCustom, onTokenChange, onReset, mutationCapability, disabled, mutationDisabled }) {
3895
+ const [library, setLibrary] = useState({
3896
+ kind: "idle",
3897
+ palettes: []
3898
+ });
3899
+ const optionRefs = useRef([]);
3900
+ const activeSelection = selectionForPaletteSnapshot(palette) ?? committedSelection;
3901
+ const libraryModel = useMemo(() => buildPaletteLibraryReadModel({
3902
+ active: activeSelection ? {
3903
+ kind: "available",
3904
+ selection: activeSelection
3905
+ } : { kind: "invalid" },
3906
+ sitePalettes: library.palettes,
3907
+ curatedPalettes,
3908
+ recommendations: siteStyle.paletteRecommendations.map((recommendation) => recommendation.id)
3909
+ }), [
3910
+ activeSelection,
3911
+ library.palettes,
3912
+ siteStyle.paletteRecommendations
3913
+ ]);
3914
+ useEffect(() => {
3915
+ if (!expanded || library.kind !== "idle") return;
3916
+ setLibrary((current) => ({
3917
+ kind: "loading",
3918
+ palettes: current.palettes
3919
+ }));
3920
+ }, [expanded, library.kind]);
3921
+ useEffect(() => {
3922
+ if (library.kind !== "loading") return;
3923
+ let cancelled = false;
3924
+ apiClient({
3925
+ endpoint: "listSiteThemePalettes",
3926
+ params: { siteId }
3927
+ }).then(unwrapResponse).then((response) => {
3928
+ if (cancelled) return;
3929
+ setLibrary({
3930
+ kind: "loaded",
3931
+ palettes: response.palettes
3932
+ });
3933
+ onLibraryLoaded(response);
3934
+ }).catch((error) => {
3935
+ if (cancelled) return;
3936
+ setLibrary((current) => ({
3937
+ kind: "failed",
3938
+ palettes: current.palettes,
3939
+ message: error instanceof Error ? error.message : "Unable to load palettes."
3940
+ }));
3941
+ });
3942
+ return () => {
3943
+ cancelled = true;
3944
+ };
3945
+ }, [
3946
+ apiClient,
3947
+ library.kind,
3948
+ onLibraryLoaded,
3949
+ siteId
3950
+ ]);
3951
+ const items = libraryModel?.ok ? [
3952
+ ...libraryModel.model.yours,
3953
+ ...libraryModel.model.suggested,
3954
+ ...libraryModel.model.more
3955
+ ] : [];
3956
+ const currentLabel = palette.kind === "new-site" ? "New palette" : "Selected palette";
3957
+ const onOptionKeyDown = (event, index) => {
3958
+ if (items.length === 0) return;
3959
+ const direction = event.key === "ArrowDown" || event.key === "ArrowRight" ? 1 : event.key === "ArrowUp" || event.key === "ArrowLeft" ? -1 : 0;
3960
+ if (direction === 0) return;
3961
+ event.preventDefault();
3962
+ const nextIndex = (index + direction + items.length) % items.length;
3963
+ const next = items[nextIndex];
3964
+ if (!next) return;
3965
+ onPreview(next);
3966
+ optionRefs.current[nextIndex]?.focus();
3967
+ };
3427
3968
  return /* @__PURE__ */ jsxs(StyleRowFrame, {
3428
3969
  label: "Palette",
3429
3970
  currentLabel,
3430
- swatch: currentSwatch,
3971
+ swatch: palette.kind === "invalid" ? null : palette.colors.primary,
3431
3972
  expanded,
3432
3973
  onToggle,
3433
3974
  children: [
3434
- /* @__PURE__ */ jsx("p", {
3435
- className: "mb-3 text-xs text-muted-foreground",
3436
- children: "These palette bases are recommended for the current Site Style. Choose a base here, then edit exact colours in Palette colours."
3437
- }),
3438
3975
  mutationCapability.kind === "blocked" && mutationCapability.reason === "invalid-palette" ? /* @__PURE__ */ jsx("div", {
3439
3976
  role: "status",
3440
3977
  className: "mb-3 flex items-center justify-between gap-3 rounded-md border border-muted px-3 py-2 text-xs text-muted-foreground",
3441
3978
  children: /* @__PURE__ */ jsx("span", { children: mutationCapability.message })
3442
3979
  }) : null,
3443
- /* @__PURE__ */ jsx("ul", {
3980
+ library.kind === "loading" ? /* @__PURE__ */ jsx("p", {
3981
+ className: "text-xs text-muted-foreground",
3982
+ children: "Loading palettes..."
3983
+ }) : null,
3984
+ library.kind === "failed" ? /* @__PURE__ */ jsxs("div", {
3985
+ role: "status",
3986
+ className: "space-y-2 text-xs text-muted-foreground",
3987
+ children: [/* @__PURE__ */ jsx("p", { children: library.message }), /* @__PURE__ */ jsx(Button, {
3988
+ type: "button",
3989
+ size: "sm",
3990
+ variant: "outline",
3991
+ onClick: () => setLibrary({
3992
+ kind: "idle",
3993
+ palettes: library.palettes
3994
+ }),
3995
+ children: "Retry loading palettes"
3996
+ })]
3997
+ }) : null,
3998
+ libraryModel.ok ? /* @__PURE__ */ jsxs("div", {
3444
3999
  role: "radiogroup",
3445
- "aria-label": "Palette",
3446
- className: "list-none space-y-1 p-0",
3447
- children: options.map((variant) => {
3448
- const primaryHex = variant.colors.primary;
3449
- return /* @__PURE__ */ jsx(PickerOption, {
3450
- selected: variant.id === currentId,
3451
- label: variant.name,
3452
- description: variant.description,
3453
- swatch: primaryHex,
3454
- onSelect: () => onPreview(variant.id),
4000
+ "aria-label": "Palette library",
4001
+ className: "space-y-3",
4002
+ children: [
4003
+ /* @__PURE__ */ jsx(PaletteLibraryGroup, {
4004
+ label: "Your palettes",
4005
+ items: libraryModel.model.yours,
4006
+ offset: 0,
4007
+ optionRefs,
4008
+ onPreview,
4009
+ onOptionKeyDown,
3455
4010
  disabled
3456
- }, variant.id);
3457
- })
3458
- })
4011
+ }),
4012
+ /* @__PURE__ */ jsx(PaletteLibraryGroup, {
4013
+ label: "Suggested for this style",
4014
+ items: libraryModel.model.suggested,
4015
+ offset: libraryModel.model.yours.length,
4016
+ optionRefs,
4017
+ onPreview,
4018
+ onOptionKeyDown,
4019
+ disabled
4020
+ }),
4021
+ /* @__PURE__ */ jsx(PaletteLibraryGroup, {
4022
+ label: "More palettes",
4023
+ items: libraryModel.model.more,
4024
+ offset: libraryModel.model.yours.length + libraryModel.model.suggested.length,
4025
+ optionRefs,
4026
+ onPreview,
4027
+ onOptionKeyDown,
4028
+ disabled
4029
+ })
4030
+ ]
4031
+ }) : /* @__PURE__ */ jsx("p", {
4032
+ role: "status",
4033
+ className: "text-xs text-muted-foreground",
4034
+ children: "Palette details are unavailable."
4035
+ }),
4036
+ /* @__PURE__ */ jsxs(Button, {
4037
+ type: "button",
4038
+ size: "sm",
4039
+ variant: "outline",
4040
+ className: "mt-3",
4041
+ onClick: onCreate,
4042
+ disabled: mutationDisabled,
4043
+ children: [/* @__PURE__ */ jsx(Plus, {
4044
+ "aria-hidden": "true",
4045
+ className: "size-3.5"
4046
+ }), "Create palette"]
4047
+ }),
4048
+ palette.kind !== "invalid" ? /* @__PURE__ */ jsx(PaletteTokenControls, {
4049
+ palette: palette.colors,
4050
+ isCustom,
4051
+ onTokenChange,
4052
+ onReset,
4053
+ disabled: mutationDisabled
4054
+ }) : null
3459
4055
  ]
3460
4056
  });
3461
4057
  }
3462
- function ColoursRow({ expanded, onToggle, palette, isCustom, onTokenChange, onReset, mutationCapability, disabled }) {
3463
- const mergedPalette = useMemo(() => paletteColorEntries(palette), [palette]);
3464
- return /* @__PURE__ */ jsx(StyleRowFrame, {
3465
- label: "Palette colours",
3466
- currentLabel: isCustom ? "Custom palette colours" : "Using selected palette",
3467
- expanded,
3468
- onToggle,
3469
- children: /* @__PURE__ */ jsxs("div", {
3470
- className: "space-y-3",
3471
- children: [
3472
- /* @__PURE__ */ jsx("p", {
3473
- className: "text-xs text-muted-foreground",
3474
- children: "Customise the palette tokens used across the site, including backgrounds, surfaces, text and borders."
3475
- }),
3476
- mutationCapability.kind === "blocked" ? /* @__PURE__ */ jsx("div", {
3477
- role: "status",
3478
- className: "flex items-center justify-between gap-3 rounded-md border border-muted px-3 py-2 text-xs text-muted-foreground",
3479
- children: /* @__PURE__ */ jsx("span", { children: mutationCapability.message })
3480
- }) : null,
3481
- /* @__PURE__ */ jsx("fieldset", {
3482
- disabled,
3483
- className: "min-w-0 border-0 p-0",
3484
- children: /* @__PURE__ */ jsx(PaletteTokenEditor, {
3485
- palette: mergedPalette,
3486
- onChange: onTokenChange,
3487
- brandRoles: PALETTE_BRAND_ROLES,
3488
- feedbackRoles: PALETTE_FEEDBACK_ROLES,
3489
- surfaceRoles: PALETTE_SURFACE_ROLES,
3490
- showContrastChecks: false,
3491
- disabled
3492
- })
3493
- }),
3494
- isCustom ? /* @__PURE__ */ jsx("div", {
3495
- className: "flex justify-end",
3496
- children: /* @__PURE__ */ jsx(Button, {
3497
- type: "button",
3498
- variant: "outline",
3499
- size: "sm",
3500
- onClick: onReset,
3501
- disabled,
3502
- children: "Reset to variant"
4058
+ function PaletteLibraryGroup({ label, items, offset, optionRefs, onPreview, onOptionKeyDown, disabled }) {
4059
+ if (items.length === 0) return null;
4060
+ return /* @__PURE__ */ jsxs("section", {
4061
+ "aria-label": label,
4062
+ children: [/* @__PURE__ */ jsx("h3", {
4063
+ className: "mb-1.5 text-xs font-medium text-muted-foreground",
4064
+ children: label
4065
+ }), /* @__PURE__ */ jsx("div", {
4066
+ className: "grid grid-cols-4 gap-2",
4067
+ children: items.map((item, index) => /* @__PURE__ */ jsxs("button", {
4068
+ ref: (element) => {
4069
+ optionRefs.current[offset + index] = element;
4070
+ },
4071
+ type: "button",
4072
+ role: "radio",
4073
+ "aria-label": item.accessibleLabel,
4074
+ "aria-checked": item.selected,
4075
+ disabled,
4076
+ onClick: () => onPreview(item),
4077
+ onKeyDown: (event) => onOptionKeyDown(event, offset + index),
4078
+ className: cn("relative h-10 overflow-hidden rounded-md border p-0.5 outline-none transition focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", item.selected ? "border-foreground ring-1 ring-foreground" : "border-border hover:border-foreground/60"),
4079
+ children: [/* @__PURE__ */ jsx("span", {
4080
+ className: "grid h-full grid-cols-5 overflow-hidden rounded-[3px]",
4081
+ "aria-hidden": "true",
4082
+ children: item.swatch.map((color, swatchIndex) => /* @__PURE__ */ jsx("span", { style: { backgroundColor: color } }, `${paletteLibraryItemKey(item)}:${swatchIndex}`))
4083
+ }), item.selected ? /* @__PURE__ */ jsx("span", {
4084
+ className: "absolute right-1 top-1 grid size-4 place-items-center rounded-full bg-foreground text-background",
4085
+ children: /* @__PURE__ */ jsx(Check, {
4086
+ "aria-hidden": "true",
4087
+ className: "size-3"
3503
4088
  })
3504
- }) : null
3505
- ]
3506
- })
4089
+ }) : null]
4090
+ }, paletteLibraryItemKey(item)))
4091
+ })]
4092
+ });
4093
+ }
4094
+ function paletteLibraryItemKey(item) {
4095
+ return item.kind === "curated" ? `curated:${item.valueKey}` : `site:${item.paletteId}`;
4096
+ }
4097
+ function selectionForPaletteSnapshot(palette) {
4098
+ switch (palette.kind) {
4099
+ case "curated": return {
4100
+ kind: "curated",
4101
+ valueKey: palette.valueKey
4102
+ };
4103
+ case "curated-fork": return {
4104
+ kind: "curated",
4105
+ valueKey: palette.sourceValueKey
4106
+ };
4107
+ case "site": return {
4108
+ kind: "site",
4109
+ paletteId: palette.paletteId
4110
+ };
4111
+ case "new-site":
4112
+ case "invalid": return null;
4113
+ default: return assertNever$1(palette);
4114
+ }
4115
+ }
4116
+ function PaletteTokenControls({ palette, isCustom, onTokenChange, onReset, disabled }) {
4117
+ return /* @__PURE__ */ jsxs("div", {
4118
+ className: "mt-4 space-y-3 border-t pt-4",
4119
+ children: [/* @__PURE__ */ jsx("fieldset", {
4120
+ disabled,
4121
+ className: "min-w-0 border-0 p-0",
4122
+ children: /* @__PURE__ */ jsx(PaletteTokenEditor, {
4123
+ palette: useMemo(() => paletteColorEntries(palette), [palette]),
4124
+ onChange: onTokenChange,
4125
+ brandRoles: PALETTE_BRAND_ROLES,
4126
+ feedbackRoles: PALETTE_FEEDBACK_ROLES,
4127
+ surfaceRoles: PALETTE_SURFACE_ROLES,
4128
+ showContrastChecks: false,
4129
+ disabled
4130
+ })
4131
+ }), isCustom ? /* @__PURE__ */ jsx("div", {
4132
+ className: "flex justify-end",
4133
+ children: /* @__PURE__ */ jsx(Button, {
4134
+ type: "button",
4135
+ variant: "outline",
4136
+ size: "sm",
4137
+ onClick: onReset,
4138
+ disabled,
4139
+ children: "Reset to variant"
4140
+ })
4141
+ }) : null]
3507
4142
  });
3508
4143
  }
3509
4144
  function PickerOption({ selected, label, description, swatch, onSelect, disabled }) {
@@ -3544,6 +4179,10 @@ function classifyStyleApplyError(error) {
3544
4179
  if (isRecord(error)) {
3545
4180
  const status = error.status;
3546
4181
  const code = error.code;
4182
+ if (code === "PALETTE_CAP_REACHED") return {
4183
+ kind: "palette-cap",
4184
+ message: "This site has reached its palette limit. Your palette draft is still here."
4185
+ };
3547
4186
  if (status === 409 || code === "resource:conflict") return {
3548
4187
  kind: "stale",
3549
4188
  message: "This design changed elsewhere. Refreshing the latest version now."
@@ -3583,16 +4222,11 @@ function WarningNotice({ warnings, onDismiss }) {
3583
4222
  });
3584
4223
  }
3585
4224
  function describeWarning(warning) {
3586
- switch (warning.kind) {
3587
- case "rejected-personality": return `Button style changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
3588
- case "rejected-palette": return `Palette changed to ${warning.fellBackTo} because ${warning.requested} is not available.`;
3589
- case "rejected-typography-preset": return `Typography changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
3590
- default: return warning;
3591
- }
4225
+ return `Button style changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
3592
4226
  }
3593
4227
  //#endregion
3594
4228
  //#region ../preview-next/src/client/preview/PreviewEditorSidebar.tsx
3595
- function PreviewEditorSidebar({ surfaceId, siteId, dashboardBaseUrl = null, pageId, entryData, contentTypeSchema, referenceContentTypeKeys = [], draftContent, currentTemplate, theme, themeId = null, themeSelectionId = null, themeSelectionVersion = null, themeSelectionUpdatedAt = null, themeSelectionButtonPersonalityId = null, runtimePaletteState = null, themeSelectionPaletteVariantId = null, themeSelectionPaletteOverrides = null, themeSelectionHeaderLayoutId = null, themeSelectionHeaderStyleId = null, themeSelectionTypographyPresetId = null, themeSelectionTypographyBodyFontOverride = null, themeSelectionTypographyHeadingFontOverride = null, themeSelectionHeaderLookId = null, themeSelectionFooterLookId = null, themeSelectionHeaderContext, themeSelectionFooterContext, designRowRequest = null, apiClient, bearerToken, supabaseUrl, entrySubrouteContext, assistantPanel, sdkConfig, mediaAssets, bookingOfferingPresets, siteStyleAccess = { kind: "hidden" }, activeTab, onTabChange, onClose, viewportMode, viewportWidth, zoom, onViewportModeChange, onViewportWidthChange, onZoomChange }) {
4229
+ function PreviewEditorSidebar({ surfaceId, siteId, dashboardBaseUrl = null, pageId, entryData, contentTypeSchema, referenceContentTypeKeys = [], draftContent, currentTemplate, theme, themeId = null, themeSelectionId = null, themeSelectionVersion = null, themeSelectionUpdatedAt = null, themeSelectionButtonPersonalityId = null, runtimePaletteState, themeSelectionHeaderLayoutId = null, themeSelectionHeaderStyleId = null, themeSelectionHeaderLookId = null, themeSelectionFooterLookId = null, themeSelectionHeaderContext, themeSelectionFooterContext, designRowRequest = null, apiClient, bearerToken, supabaseUrl, entrySubrouteContext, assistantPanel, sdkConfig, mediaAssets, bookingOfferingPresets, siteStyleAccess = { kind: "hidden" }, activeTab, onTabChange, onClose, viewportMode, viewportWidth, zoom, onViewportModeChange, onViewportWidthChange, onZoomChange }) {
3596
4230
  const controller = useEntryController();
3597
4231
  const controllerBlocks = controller.blocks;
3598
4232
  const uiStore = useEditorUiStoreInstance();
@@ -3603,6 +4237,7 @@ function PreviewEditorSidebar({ surfaceId, siteId, dashboardBaseUrl = null, page
3603
4237
  const focusRequest = useUISelector(uiStore, (s) => s.focusRequest);
3604
4238
  const activeLinkedEntrySnapshot = useUISelector(uiStore, (s) => s.linkedEntries.getSelectedSnapshot());
3605
4239
  const previewInspection = useUISelector(uiStore, (s) => s.previewInspection);
4240
+ const [designTakeoverActive, setDesignTakeoverActive] = useState(false);
3606
4241
  const { canEdit } = usePreviewAuth();
3607
4242
  const dashboardSetupHandoff = useMemo(() => resolvePreviewDashboardSetupHandoff(), []);
3608
4243
  const dashboardContinueUrl = useMemo(() => buildPreviewDashboardSetupUrl(dashboardBaseUrl, dashboardSetupHandoff), [dashboardBaseUrl, dashboardSetupHandoff]);
@@ -3719,6 +4354,7 @@ function PreviewEditorSidebar({ surfaceId, siteId, dashboardBaseUrl = null, page
3719
4354
  onBlocksChange: handleBlocksChange,
3720
4355
  metadataPanel: metadataSectionContent,
3721
4356
  designOpenRequestId: designRowRequest?.id ?? null,
4357
+ designTakeoverActive,
3722
4358
  designPanel: canInspectSiteStyle(siteStyleAccess) ? /* @__PURE__ */ jsx(StyleConfigurator, {
3723
4359
  siteId,
3724
4360
  apiClient,
@@ -3732,19 +4368,15 @@ function PreviewEditorSidebar({ surfaceId, siteId, dashboardBaseUrl = null, page
3732
4368
  selectionUpdatedAt: themeSelectionUpdatedAt,
3733
4369
  buttonPersonalityId: themeSelectionButtonPersonalityId,
3734
4370
  runtimePaletteState,
3735
- paletteVariantId: themeSelectionPaletteVariantId,
3736
- paletteOverrides: themeSelectionPaletteOverrides,
3737
4371
  headerLayoutId: themeSelectionHeaderLayoutId,
3738
4372
  headerStyleId: themeSelectionHeaderStyleId,
3739
- typographyPresetId: themeSelectionTypographyPresetId,
3740
- typographyBodyFontOverride: themeSelectionTypographyBodyFontOverride,
3741
- typographyHeadingFontOverride: themeSelectionTypographyHeadingFontOverride,
3742
4373
  headerLookId: themeSelectionHeaderLookId,
3743
4374
  footerLookId: themeSelectionFooterLookId
3744
4375
  },
3745
4376
  headerSelectionContext: themeSelectionHeaderContext,
3746
4377
  footerSelectionContext: themeSelectionFooterContext,
3747
4378
  expandedRowRequest: designRowRequest,
4379
+ onFocusedSurfaceChange: setDesignTakeoverActive,
3748
4380
  siteStyleAccess
3749
4381
  }) : null,
3750
4382
  sdkConfig: sdkConfig ?? null,