@riverbankcms/sdk 0.144.0 → 0.145.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 (162) hide show
  1. package/dist/{PreviewEditorSidebar-CQZNnFiM.mjs → PreviewEditorSidebar-O-V6Pl5a.mjs} +4 -4
  2. package/dist/{PreviewEditorUI-DCNxnmI0.mjs → PreviewEditorUI-1UlX_9cw.mjs} +3 -3
  3. package/dist/{PreviewQueryProvider-BCYICImX.mjs → PreviewQueryProvider-BxGwdOCV.mjs} +1 -1
  4. package/dist/{SdkPreviewModeRuntime-BJolUbr1.mjs → SdkPreviewModeRuntime-DYbA_4iv.mjs} +419 -348
  5. package/dist/{SiteChromeCustomizeContext-CKgEpoxi.mjs → SiteChromeCustomizeContext-CNTKQ9nx.mjs} +2081 -1313
  6. package/dist/_dts/ai/src/contracts/proposals.d.ts +4719 -66
  7. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +429 -6
  8. package/dist/_dts/api/src/appointmentEndpointDefinitions.d.ts +49 -0
  9. package/dist/_dts/api/src/appointmentServiceCategoryEndpoints.d.ts +75 -0
  10. package/dist/_dts/api/src/appointmentServiceContracts.d.ts +80 -0
  11. package/dist/_dts/api/src/appointmentSetup.d.ts +8 -1
  12. package/dist/_dts/api/src/bookingManagementEndpoints.d.ts +2 -12
  13. package/dist/_dts/api/src/communications.d.ts +14 -22
  14. package/dist/_dts/api/src/contentRuntime.d.ts +2 -0
  15. package/dist/_dts/api/src/endpoints.d.ts +105 -58
  16. package/dist/_dts/api/src/indexExtendedTypes.d.ts +1 -1
  17. package/dist/_dts/api/src/launchReadiness.d.ts +2 -0
  18. package/dist/_dts/api/src/newsletterEndpointDefinitions.d.ts +0 -23
  19. package/dist/_dts/api/src/onboarding/confirmationResend.d.ts +3 -0
  20. package/dist/_dts/api/src/publicBookingService.d.ts +35 -1
  21. package/dist/_dts/api/src/publicExperience/newsletterEndpoints.d.ts +13 -0
  22. package/dist/_dts/api/src/publicNewsletterEndpointDefinitions.d.ts +58 -0
  23. package/dist/_dts/api/src/schemas/admin-observability-logs.d.ts +3 -3
  24. package/dist/_dts/api/src/siteManagementEndpoints.d.ts +2 -6
  25. package/dist/_dts/api/src/types.d.ts +2 -1
  26. package/dist/_dts/blocks/src/PageRenderer.d.ts +4 -4
  27. package/dist/_dts/blocks/src/bindings/index.d.ts +2 -2
  28. package/dist/_dts/blocks/src/contracts/proof-block-islands.d.ts +24 -0
  29. package/dist/_dts/blocks/src/index.d.ts +1 -1
  30. package/dist/_dts/blocks/src/pageRenderJoins.d.ts +68 -0
  31. package/dist/_dts/blocks/src/pageRenderPlan.d.ts +8 -27
  32. package/dist/_dts/blocks/src/pageRenderPreparation.d.ts +7 -4
  33. package/dist/_dts/blocks/src/pageRendererBoundaries.d.ts +40 -23
  34. package/dist/_dts/blocks/src/server.d.ts +1 -1
  35. package/dist/_dts/blocks/src/system/blocks/appointment-services.d.ts +2 -0
  36. package/dist/_dts/blocks/src/system/blocks/events/eventSelectionFields.d.ts +2 -2
  37. package/dist/_dts/blocks/src/system/blocks/events/shared/calendarWindow.d.ts +1 -1
  38. package/dist/_dts/blocks/src/system/blocks/location-map.d.ts +2 -2
  39. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +47 -47
  40. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +29 -3
  41. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessBookingFlow.d.ts +8 -1
  42. package/dist/_dts/blocks/src/system/runtime/nodes/appointment-services.d.ts +22 -2
  43. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +3 -1
  44. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.interactive.d.ts +57 -2
  45. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.service-categories.d.ts +29 -0
  46. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.service-choices.d.ts +13 -0
  47. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.state.d.ts +3 -3
  48. package/dist/_dts/blocks/src/system/runtime/nodes/booking-runtime-sources.d.ts +13 -1
  49. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/eventSourceRecovery.d.ts +1 -1
  50. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCollectionContract.d.ts +3 -3
  51. package/dist/_dts/blocks/src/system/runtime/nodes/newsletter-form.submission.d.ts +20 -1
  52. package/dist/_dts/content-editor/src/ui/editorSnapshot.d.ts +2 -0
  53. package/dist/_dts/content-model/src/content-types/schema.d.ts +1 -1
  54. package/dist/_dts/core/src/appointment-service-categories.d.ts +108 -0
  55. package/dist/_dts/core/src/editor-state.d.ts +6 -0
  56. package/dist/_dts/core/src/index.d.ts +1 -0
  57. package/dist/_dts/core/src/public-profile.d.ts +9 -0
  58. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +663 -241
  59. package/dist/_dts/editor-blocks/src/block-editor/types.d.ts +6 -0
  60. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/loaders.d.ts +16 -1
  61. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/types.d.ts +13 -0
  62. package/dist/_dts/editor-blocks/src/widgets/site-option-picker/utils.d.ts +29 -0
  63. package/dist/_dts/preview-next/src/client/blocks/BoundaryControl.d.ts +7 -2
  64. package/dist/_dts/preview-next/src/client/blocks/composer/mergeDrafts.d.ts +1 -1
  65. package/dist/_dts/preview-next/src/client/blocks/composer/types.d.ts +1 -0
  66. package/dist/_dts/preview-next/src/client/blocks/previewProjection/applyPreviewProjections.d.ts +1 -3
  67. package/dist/_dts/preview-next/src/client/blocks/previewProjection/runtimeContentDecoration.d.ts +2 -2
  68. package/dist/_dts/preview-next/src/client/blocks/previewProjection/types.d.ts +2 -2
  69. package/dist/_dts/preview-next/src/client/preview/PreviewEntryControllerProvider.d.ts +1 -0
  70. package/dist/_dts/sdk/src/client/runtimeCacheKeys.d.ts +8 -0
  71. package/dist/_dts/sdk/src/client/types.d.ts +1 -1
  72. package/dist/_dts/sdk/src/contracts/index.d.ts +1 -1
  73. package/dist/_dts/sdk/src/contracts/page.d.ts +7 -2
  74. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +5 -0
  75. package/dist/_dts/sdk/src/manifest/passes/pass1-fields.d.ts +2 -2
  76. package/dist/_dts/sdk/src/public-api/contracts.d.ts +6 -8
  77. package/dist/_dts/sdk/src/rendering/blocks-page-renderer.d.ts +2 -2
  78. package/dist/_dts/sdk/src/rendering/components/pageShared.d.ts +2 -2
  79. package/dist/_dts/sdk/src/rendering/helpers/loadContent.d.ts +1 -1
  80. package/dist/_dts/sdk/src/rendering/hooks/useContent.d.ts +1 -1
  81. package/dist/_dts/sdk/src/version.d.ts +1 -1
  82. package/dist/_dts/surfaces/src/index.d.ts +1 -0
  83. package/dist/_dts/theme-core/src/layout/boundaryCss.d.ts +3 -0
  84. package/dist/_dts/theme-core/src/site-styles/compositionBudget.d.ts +8 -0
  85. package/dist/_dts/theme-core/src/site-styles/contentTemplateRuntimeCompiler.d.ts +3 -2
  86. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +1 -3
  87. package/dist/_dts/theme-core/src/site-styles/generatedDesignPipeline.d.ts +3 -0
  88. package/dist/_dts/theme-core/src/site-styles/index.d.ts +3 -0
  89. package/dist/_dts/theme-core/src/site-styles/joinTreatment.d.ts +132 -0
  90. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorBoundaries.d.ts +31 -0
  91. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorModel.d.ts +33 -29
  92. package/dist/_dts/theme-core/src/site-styles/pageDesignIntent.d.ts +4 -4
  93. package/dist/_dts/theme-core/src/site-styles/pageFlowPresets.d.ts +36 -0
  94. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +4 -10
  95. package/dist/_dts/theme-core/src/site-styles/rawStyleFields.d.ts +17 -4
  96. package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +20 -74
  97. package/dist/_dts/theme-core/src/site-styles/sectionBoundaryComposition.d.ts +95 -0
  98. package/dist/_dts/theme-core/src/site-styles/sectionTransitions.d.ts +80 -518
  99. package/dist/_dts/theme-core/src/site-styles/siteStyleBlockPolicies.d.ts +9 -4
  100. package/dist/_dts/theme-core/src/site-styles/transitionCatalogue.d.ts +320 -0
  101. package/dist/_dts/theme-core/src/site-styles/types.d.ts +3 -18
  102. package/dist/client/{BookingConsentAgreement-DoAILrkN.mjs → BookingConsentAgreement-DlVOmpW4.mjs} +2 -2
  103. package/dist/client/{DiscountCodeDisclosure-X528hUTf.mjs → DiscountCodeDisclosure-T9q623PQ.mjs} +1 -1
  104. package/dist/client/{EventCombined.client-DSBVwq5m.mjs → EventCombined.client-DnsUeONl.mjs} +6 -6
  105. package/dist/client/{EventListing.client-DmFAX7Vb.mjs → EventListing.client-CNBFqJS4.mjs} +2 -2
  106. package/dist/client/{LeafletStaticMap-BAqXdfhq.mjs → LeafletStaticMap-C7higg52.mjs} +1 -1
  107. package/dist/client/{ThemedButton-BwNFVwi4.mjs → ThemedButton-LAJTNrS8.mjs} +1 -1
  108. package/dist/client/{accordion-DRhMdkbd.mjs → accordion-BcNF9TjX.mjs} +1 -1
  109. package/dist/client/{bookingForm-iJ_nCsNF.mjs → bookingForm-BUaQ3S03.mjs} +142 -50
  110. package/dist/client/bookings.mjs +191 -64
  111. package/dist/client/{carousel-D8zuffZr.mjs → carousel-CTdF6e0V.mjs} +1 -1
  112. package/dist/client/{cart-DeMGJViN.mjs → cart-pGtyf_XU.mjs} +3 -3
  113. package/dist/client/{checkout-DXc06Km2.mjs → checkout-Da9WSn7Y.mjs} +3 -3
  114. package/dist/client/client.mjs +1630 -816
  115. package/dist/client/{context-CDLCfDep.mjs → context-BFeY_Wbm.mjs} +1 -1
  116. package/dist/client/{dates-ClFx7Epy.mjs → dates-BhwVSxDg.mjs} +2 -2
  117. package/dist/client/{eventCalendar-BaGpnOGN.mjs → eventCalendar-BAtdnR4s.mjs} +9 -9
  118. package/dist/client/{eventCombined-ywONOx2m.mjs → eventCombined-RksA3tRC.mjs} +4 -4
  119. package/dist/client/{eventDetails-B2LlOTYY.mjs → eventDetails-BB9hB4wR.mjs} +4 -4
  120. package/dist/client/{eventListing-CMgxWpqP.mjs → eventListing-BpJBkviM.mjs} +4 -4
  121. package/dist/client/{eventRegistration-BarLDNHR.mjs → eventRegistration-B18jFq_o.mjs} +8 -8
  122. package/dist/client/{form-BjqKaBJ7.mjs → form-Dodt8Y8t.mjs} +3 -3
  123. package/dist/client/{headerSection-CP4XDwz1.mjs → headerSection-B0qj9ttg.mjs} +3 -3
  124. package/dist/client/hooks.mjs +550 -99
  125. package/dist/client/{locationMap-C5gW-d-w.mjs → locationMap-gn-iMk7a.mjs} +3 -3
  126. package/dist/client/{newsletterForm-afXlVSA0.mjs → newsletterForm-oByLdFkr.mjs} +26 -5
  127. package/dist/client/{pricing-Bl1CAl28.mjs → pricing-BOU0_Hai.mjs} +1 -1
  128. package/dist/client/{productDetail-CvQ3jWQe.mjs → productDetail-D9v1zH2j.mjs} +3 -3
  129. package/dist/client/{productList-U874PGO7.mjs → productList-D-mItx1n.mjs} +3 -3
  130. package/dist/client/{propsDecoder-DPXtCZWP.mjs → propsDecoder-C3TP_z4x.mjs} +72 -33
  131. package/dist/client/rendering/client.mjs +900 -266
  132. package/dist/client/rendering/islands.mjs +17 -17
  133. package/dist/client/rendering.mjs +1637 -788
  134. package/dist/client/{routeIslandApiConfig-NGYiUWXu.mjs → routeIslandApiConfig-DzN1W0sq.mjs} +1 -1
  135. package/dist/client/{scrollToBlock-0-C3DVhK.mjs → scrollToBlock-CWs8EIdo.mjs} +1 -1
  136. package/dist/client/{shop-BXjXhM9P.mjs → shop-Dufw7N7o.mjs} +7 -7
  137. package/dist/client/{shop-commerce-BZ6UlWJ7.mjs → shop-commerce-gklw_hLF.mjs} +6 -6
  138. package/dist/client/{shop-commerce-store-DlSBXb3D.mjs → shop-commerce-store-Sf1eyTaZ.mjs} +1 -1
  139. package/dist/client/{shopShared-kkEOiLPA.mjs → shopShared-B5nyA41S.mjs} +4 -4
  140. package/dist/client/{useEventFilters-zvnnE1ro.mjs → useEventFilters-CLL829vy.mjs} +6 -6
  141. package/dist/preview-next/before-render.mjs +147 -77
  142. package/dist/preview-next/client/runtime.mjs +4 -4
  143. package/dist/preview-next/keepAlive.mjs +147 -77
  144. package/dist/preview-next/middleware.mjs +147 -77
  145. package/dist/{previewAuthStore-JA6-I_sL.mjs → previewAuthStore-BxAFlu-j.mjs} +147 -77
  146. package/dist/{sdk-runtime-DFJlQt3q.mjs → sdk-runtime-D0uZFev7.mjs} +2 -2
  147. package/dist/{separator-o8GHb1xh.mjs → separator-EvdCAn4V.mjs} +1 -1
  148. package/dist/server/components.mjs +1727 -991
  149. package/dist/server/config-validation.mjs +537 -97
  150. package/dist/server/config.mjs +537 -97
  151. package/dist/server/data.mjs +549 -98
  152. package/dist/server/index.mjs +159 -81
  153. package/dist/server/next.mjs +1460 -748
  154. package/dist/server/page-converter.mjs +384 -54
  155. package/dist/server/prebuild.mjs +1 -1
  156. package/dist/server/rendering/server.mjs +1712 -973
  157. package/dist/server/rendering.mjs +1748 -1001
  158. package/dist/server/routing.mjs +624 -131
  159. package/dist/server/server.mjs +554 -138
  160. package/dist/server/theme-bridge.mjs +1192 -27
  161. package/dist/server/theme.mjs +6 -4
  162. package/package.json +2 -1
@@ -68,11 +68,11 @@ var UnsafePathSegmentError = class extends Error {
68
68
  function assertSafePathSegments(segments) {
69
69
  for (const segment of segments) if (typeof segment === "string" && UNSAFE_PATH_SEGMENTS.has(segment)) throw new UnsafePathSegmentError(segment);
70
70
  }
71
- function isRecord$41(value) {
71
+ function isRecord$42(value) {
72
72
  return typeof value === "object" && value !== null && !Array.isArray(value);
73
73
  }
74
74
  function isPathContainer(value) {
75
- return Array.isArray(value) || isRecord$41(value);
75
+ return Array.isArray(value) || isRecord$42(value);
76
76
  }
77
77
  function getContainerValue(container, segment) {
78
78
  if (typeof segment === "number") return Array.isArray(container) ? container[segment] : void 0;
@@ -1402,7 +1402,7 @@ function parseEventPageSurfaceDestinationMode(value) {
1402
1402
  return value === "cms-managed" || value === "system-managed" ? value : void 0;
1403
1403
  }
1404
1404
  function parseEventPageSurfaceSubject(value) {
1405
- if (!isRecord$40(value)) return err$3({ kind: "notObject" });
1405
+ if (!isRecord$41(value)) return err$3({ kind: "notObject" });
1406
1406
  switch (value.kind) {
1407
1407
  case "eventSeries": {
1408
1408
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -1457,7 +1457,7 @@ function parseEventSeriesIdList(values) {
1457
1457
  }
1458
1458
  return ok$3(parsed);
1459
1459
  }
1460
- function isRecord$40(value) {
1460
+ function isRecord$41(value) {
1461
1461
  return typeof value === "object" && value !== null && !Array.isArray(value);
1462
1462
  }
1463
1463
  //#endregion
@@ -1683,6 +1683,74 @@ function getLocalScheduleDate(date, timeZone) {
1683
1683
  };
1684
1684
  }
1685
1685
  //#endregion
1686
+ //#region ../core/src/appointment-service-categories.ts
1687
+ /**
1688
+ * Groups services under their categories.
1689
+ *
1690
+ * A service whose `categoryId` names no category in `categories` lands in
1691
+ * Other. The composite foreign key keeps that from happening with stored data,
1692
+ * so this is only the total, safe answer for a caller holding a stale list.
1693
+ */
1694
+ function layoutServicesByCategory(categories, services, options) {
1695
+ if (categories.length === 0) return {
1696
+ kind: "uncategorized-site",
1697
+ services: [...services]
1698
+ };
1699
+ const buckets = new Map(categories.map((category) => [category.id, []]));
1700
+ const other = [];
1701
+ for (const service of services) {
1702
+ const bucket = service.categoryId === null ? void 0 : buckets.get(service.categoryId);
1703
+ if (bucket !== void 0) bucket.push(service);
1704
+ else other.push(service);
1705
+ }
1706
+ const groups = [];
1707
+ for (const category of categories) {
1708
+ const groupServices = buckets.get(category.id) ?? [];
1709
+ if (groupServices.length === 0 && !options.keepEmptyGroups) continue;
1710
+ groups.push({
1711
+ category,
1712
+ services: groupServices
1713
+ });
1714
+ }
1715
+ if (groups.length === 0 && !options.keepEmptyGroups) return {
1716
+ kind: "uncategorized-site",
1717
+ services: other
1718
+ };
1719
+ return {
1720
+ kind: "categorized-site",
1721
+ groups,
1722
+ other
1723
+ };
1724
+ }
1725
+ /**
1726
+ * Selects the public view for an optional category filter.
1727
+ *
1728
+ * It takes the category and service lists rather than a prebuilt layout
1729
+ * because the public layout has already dropped empty categories, and then an
1730
+ * empty category (`single` with no services) could not be told apart from a
1731
+ * deleted one (`fallback-all`).
1732
+ */
1733
+ function selectCategoryView(categories, services, filterCategoryId) {
1734
+ const publicLayout = () => layoutServicesByCategory(categories, services, { keepEmptyGroups: false });
1735
+ if (filterCategoryId === null || filterCategoryId === void 0) return {
1736
+ kind: "all",
1737
+ layout: publicLayout()
1738
+ };
1739
+ const category = categories.find((candidate) => candidate.id === filterCategoryId);
1740
+ if (category === void 0) return {
1741
+ kind: "fallback-all",
1742
+ missingCategoryId: filterCategoryId,
1743
+ layout: publicLayout()
1744
+ };
1745
+ return {
1746
+ kind: "single",
1747
+ group: {
1748
+ category,
1749
+ services: services.filter((service) => service.categoryId === filterCategoryId)
1750
+ }
1751
+ };
1752
+ }
1753
+ //#endregion
1686
1754
  //#region ../core/src/location.ts
1687
1755
  function normalizeLocationAddressText(value) {
1688
1756
  if (typeof value !== "string") return null;
@@ -1701,7 +1769,7 @@ function toLocationCoordinates(value) {
1701
1769
  }
1702
1770
  //#endregion
1703
1771
  //#region ../core/src/rich-text.ts
1704
- function isRecord$38(value) {
1772
+ function isRecord$39(value) {
1705
1773
  return typeof value === "object" && value !== null && !Array.isArray(value);
1706
1774
  }
1707
1775
  function hasOnlyKeys(value, allowed) {
@@ -1720,10 +1788,10 @@ function isDenseArrayOf(value, predicate) {
1720
1788
  return true;
1721
1789
  }
1722
1790
  function isTipTapMark(value) {
1723
- return isRecord$38(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$38(value.attrs));
1791
+ return isRecord$39(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$39(value.attrs));
1724
1792
  }
1725
1793
  function isTipTapNode(value) {
1726
- if (!isRecord$38(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$38(value.attrs) || value.marks !== void 0 && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== void 0 && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) return false;
1794
+ if (!isRecord$39(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$39(value.attrs) || value.marks !== void 0 && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== void 0 && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) return false;
1727
1795
  if (value.type === "text") return typeof value.text === "string" && value.text.length > 0 && value.content === void 0;
1728
1796
  return value.text === void 0;
1729
1797
  }
@@ -1760,10 +1828,10 @@ function parseBrevoListId(value) {
1760
1828
  //#endregion
1761
1829
  //#region ../core/src/mailing-list-targeting-parser.ts
1762
1830
  function parseMailingListPlacementTargeting(value) {
1763
- if (!isRecord$37(value)) return targetingError("shape");
1831
+ if (!isRecord$38(value)) return targetingError("shape");
1764
1832
  if (value.mode === "default") return ok$3({ mode: "default" });
1765
1833
  if (value.mode !== "custom") return targetingError("mode");
1766
- if (!isRecord$37(value.selection)) return targetingError("selection");
1834
+ if (!isRecord$38(value.selection)) return targetingError("selection");
1767
1835
  switch (value.provider) {
1768
1836
  case "native": return parseNativeTargeting(value.selection);
1769
1837
  case "brevo": return parseBrevoTargeting(value.selection);
@@ -1867,7 +1935,7 @@ function parseUniqueIds(value, parseId) {
1867
1935
  }
1868
1936
  return ok$3(parsed);
1869
1937
  }
1870
- function isRecord$37(value) {
1938
+ function isRecord$38(value) {
1871
1939
  return typeof value === "object" && value !== null && !Array.isArray(value);
1872
1940
  }
1873
1941
  function targetingError(field) {
@@ -1890,12 +1958,12 @@ new Map([
1890
1958
  ].map((kind, index) => [kind, index]));
1891
1959
  //#endregion
1892
1960
  //#region ../core/src/mailing-list-setup.ts
1893
- function isRecord$34(value) {
1961
+ function isRecord$35(value) {
1894
1962
  return typeof value === "object" && value !== null && !Array.isArray(value);
1895
1963
  }
1896
1964
  const DEFAULT_MAILING_LIST_SUCCESS_MESSAGE = "Thanks — you're on the list.";
1897
1965
  function parseMailingListProviderScope(value) {
1898
- if (!isRecord$34(value)) return err$3("invalid_mailing_list_provider_scope");
1966
+ if (!isRecord$35(value)) return err$3("invalid_mailing_list_provider_scope");
1899
1967
  if (value.provider === "native") return ok$3({ provider: "native" });
1900
1968
  if (value.provider === "brevo") {
1901
1969
  const accountId = parseBrevoAccountId(value.accountId);
@@ -1916,7 +1984,7 @@ function parseMailingListProviderScope(value) {
1916
1984
  //#endregion
1917
1985
  //#region ../core/src/mailing-list-setup-parser.ts
1918
1986
  function parseMailingListGlobalForm(value) {
1919
- if (!isRecord$33(value)) return formError("shape");
1987
+ if (!isRecord$34(value)) return formError("shape");
1920
1988
  const fields = parseGlobalFields(value.fields);
1921
1989
  if (fields === null) return formError("fields");
1922
1990
  if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
@@ -1935,7 +2003,7 @@ function parseMailingListGlobalForm(value) {
1935
2003
  });
1936
2004
  }
1937
2005
  function parseGlobalFields(value) {
1938
- if (!isRecord$33(value) || typeof value.preset !== "string") return null;
2006
+ if (!isRecord$34(value) || typeof value.preset !== "string") return null;
1939
2007
  switch (value.preset) {
1940
2008
  case "emailOnly": return { preset: "emailOnly" };
1941
2009
  case "emailName": return isRequirement(value.name) ? {
@@ -1972,7 +2040,7 @@ function parseOptionalString(value) {
1972
2040
  function isRequirement(value) {
1973
2041
  return value === "required" || value === "optional";
1974
2042
  }
1975
- function isRecord$33(value) {
2043
+ function isRecord$34(value) {
1976
2044
  return typeof value === "object" && value !== null && !Array.isArray(value);
1977
2045
  }
1978
2046
  function formError(field) {
@@ -2022,7 +2090,7 @@ function planMailingListChoicePresentation(targeting) {
2022
2090
  //#endregion
2023
2091
  //#region ../core/src/mailing-list-runtime.ts
2024
2092
  function parseMailingListRuntimeContext(value) {
2025
- if (!isRecord$32(value)) return invalidRuntimeContext();
2093
+ if (!isRecord$33(value)) return invalidRuntimeContext();
2026
2094
  if (value.tag === "not_configured") return ok$3({ tag: "not_configured" });
2027
2095
  if (value.tag !== "configured") return invalidRuntimeContext();
2028
2096
  const revision = parseMailingListSetupRevision(value.revision);
@@ -2037,7 +2105,7 @@ function parseMailingListRuntimeContext(value) {
2037
2105
  });
2038
2106
  }
2039
2107
  function parseRuntimeProvider(value) {
2040
- if (!isRecord$32(value)) return null;
2108
+ if (!isRecord$33(value)) return null;
2041
2109
  switch (value.provider) {
2042
2110
  case "native": return parseNativeRuntimeProvider(value);
2043
2111
  case "brevo": return parseBrevoRuntimeProvider(value);
@@ -2069,7 +2137,7 @@ function parseMailchimpRuntimeProvider(value) {
2069
2137
  if (defaultAudienceId === null) return null;
2070
2138
  const audiences = [];
2071
2139
  for (const audience of value.audiences) {
2072
- if (!isRecord$32(audience)) return null;
2140
+ if (!isRecord$33(audience)) return null;
2073
2141
  const id = parseMailchimpAudienceId$1(audience.id);
2074
2142
  const name = parseName(audience.name);
2075
2143
  if (id === null || name === null || !Array.isArray(audience.interests)) return null;
@@ -2090,7 +2158,7 @@ function parseMailchimpRuntimeProvider(value) {
2090
2158
  function parseMailchimpInterests(value) {
2091
2159
  const interests = [];
2092
2160
  for (const interest of value) {
2093
- if (!isRecord$32(interest)) return null;
2161
+ if (!isRecord$33(interest)) return null;
2094
2162
  const id = parseMailchimpInterestId(interest.id);
2095
2163
  const name = parseName(interest.name);
2096
2164
  const categoryName = parseName(interest.categoryName);
@@ -2107,7 +2175,7 @@ function parseNamedItems(value, parseId) {
2107
2175
  if (!Array.isArray(value)) return null;
2108
2176
  const items = [];
2109
2177
  for (const item of value) {
2110
- if (!isRecord$32(item)) return null;
2178
+ if (!isRecord$33(item)) return null;
2111
2179
  const id = parseId(item.id);
2112
2180
  const name = parseName(item.name);
2113
2181
  if (id === null || name === null) return null;
@@ -2123,7 +2191,7 @@ function parseName(value) {
2123
2191
  const name = value.trim();
2124
2192
  return name.length > 0 ? name : null;
2125
2193
  }
2126
- function isRecord$32(value) {
2194
+ function isRecord$33(value) {
2127
2195
  return typeof value === "object" && value !== null && !Array.isArray(value);
2128
2196
  }
2129
2197
  function invalidRuntimeContext() {
@@ -2686,11 +2754,11 @@ function parseRelativeStoragePath(value) {
2686
2754
  }
2687
2755
  //#endregion
2688
2756
  //#region ../media-core/src/typeGuards.ts
2689
- function isRecord$30(value) {
2757
+ function isRecord$31(value) {
2690
2758
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2691
2759
  }
2692
2760
  function asRecord$3(value) {
2693
- return isRecord$30(value) ? value : void 0;
2761
+ return isRecord$31(value) ? value : void 0;
2694
2762
  }
2695
2763
  function isFileJsonObject(value) {
2696
2764
  return Object.values(value).every(isFileJsonValue);
@@ -2699,7 +2767,7 @@ function isFileJsonValue(value) {
2699
2767
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
2700
2768
  if (typeof value === "number") return Number.isFinite(value);
2701
2769
  if (Array.isArray(value)) return value.every(isFileJsonValue);
2702
- return isRecord$30(value) && isFileJsonObject(value);
2770
+ return isRecord$31(value) && isFileJsonObject(value);
2703
2771
  }
2704
2772
  new TextDecoder("latin1");
2705
2773
  //#endregion
@@ -2958,7 +3026,7 @@ function isCmsMediaObjectType(value) {
2958
3026
  return typeof value === "string" && CMS_MEDIA_OBJECT_TYPES.includes(value);
2959
3027
  }
2960
3028
  function isCmsMediaObject(value) {
2961
- if (!isRecord$30(value)) return false;
3029
+ if (!isRecord$31(value)) return false;
2962
3030
  const type = value.type;
2963
3031
  if (!isCmsMediaObjectType(type)) return false;
2964
3032
  if ("attrs" in value) return false;
@@ -2969,7 +3037,7 @@ function collectMediaObjects(value, out, location = "") {
2969
3037
  value.forEach((item, index) => collectMediaObjects(item, out, `${location}[${index}]`));
2970
3038
  return;
2971
3039
  }
2972
- if (!isRecord$30(value)) return;
3040
+ if (!isRecord$31(value)) return;
2973
3041
  if (isCmsMediaObject(value)) out.push({
2974
3042
  obj: value,
2975
3043
  location
@@ -2995,8 +3063,8 @@ function walkAndCollectImageAttrs(value, out, location = "") {
2995
3063
  value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
2996
3064
  return;
2997
3065
  }
2998
- if (!isRecord$30(value)) return;
2999
- if (value.type === "image" && isRecord$30(value.attrs)) out.push({
3066
+ if (!isRecord$31(value)) return;
3067
+ if (value.type === "image" && isRecord$31(value.attrs)) out.push({
3000
3068
  attrs: value.attrs,
3001
3069
  location: location ? `${location}.attrs` : "attrs"
3002
3070
  });
@@ -3043,7 +3111,7 @@ function mediaIdentifierOrFallback(current, fallback) {
3043
3111
  }
3044
3112
  //#endregion
3045
3113
  //#region ../media-core/src/contentMediaAudit.ts
3046
- function isRecord$29(value) {
3114
+ function isRecord$30(value) {
3047
3115
  return typeof value === "object" && value !== null && !Array.isArray(value);
3048
3116
  }
3049
3117
  function summarizeContentMediaValue(value) {
@@ -3054,7 +3122,7 @@ function summarizeContentMediaValue(value) {
3054
3122
  }
3055
3123
  if (value === null) return "null";
3056
3124
  if (Array.isArray(value)) return `array(${value.length})`;
3057
- if (isRecord$29(value)) return `object(${Object.keys(value).sort().join(",")})`;
3125
+ if (isRecord$30(value)) return `object(${Object.keys(value).sort().join(",")})`;
3058
3126
  return typeof value;
3059
3127
  }
3060
3128
  function auditMediaValue(context, path, allowedMediaTypes, value) {
@@ -3078,9 +3146,9 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
3078
3146
  allowedMediaTypes
3079
3147
  }];
3080
3148
  }
3081
- const hasValidIdentifier = isRecord$29(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
3082
- const hasValidExplicitType = isRecord$29(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
3083
- const hasRetiredEnvironmentField = isRecord$29(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
3149
+ const hasValidIdentifier = isRecord$30(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
3150
+ const hasValidExplicitType = isRecord$30(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
3151
+ const hasRetiredEnvironmentField = isRecord$30(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
3084
3152
  if (hasValidIdentifier && hasValidExplicitType && !hasRetiredEnvironmentField) return [];
3085
3153
  return [{
3086
3154
  tag: "malformed-object",
@@ -3091,7 +3159,7 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
3091
3159
  }];
3092
3160
  }
3093
3161
  function auditFields(context, fields, value, parentPath) {
3094
- if (!isRecord$29(value)) return [];
3162
+ if (!isRecord$30(value)) return [];
3095
3163
  const findings = [];
3096
3164
  for (const field of fields) {
3097
3165
  if (!(field.id in value)) continue;
@@ -3108,7 +3176,7 @@ function auditFields(context, fields, value, parentPath) {
3108
3176
  if (!Array.isArray(fieldValue)) break;
3109
3177
  for (const [index, item] of fieldValue.entries()) {
3110
3178
  const itemPath = [...fieldPath, index];
3111
- if (!isRecord$29(item)) {
3179
+ if (!isRecord$30(item)) {
3112
3180
  findings.push({
3113
3181
  tag: "missing-schema",
3114
3182
  context,
@@ -3147,7 +3215,7 @@ const contentMediaTypes = /* @__PURE__ */ new Set([
3147
3215
  "spreadsheet",
3148
3216
  "archive"
3149
3217
  ]);
3150
- function isRecord$28(value) {
3218
+ function isRecord$29(value) {
3151
3219
  return typeof value === "object" && value !== null && !Array.isArray(value);
3152
3220
  }
3153
3221
  function unknownArray(value) {
@@ -3156,7 +3224,7 @@ function unknownArray(value) {
3156
3224
  function fieldsArray(value) {
3157
3225
  const directFields = unknownArray(value);
3158
3226
  if (directFields) return directFields;
3159
- if (!isRecord$28(value)) return null;
3227
+ if (!isRecord$29(value)) return null;
3160
3228
  return unknownArray(value.fields);
3161
3229
  }
3162
3230
  function nestedFields$1(field) {
@@ -3221,7 +3289,7 @@ function mergeFields(fields) {
3221
3289
  }
3222
3290
  function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds = ["document"]) {
3223
3291
  return mergeFields(rawFields.flatMap((rawField) => {
3224
- if (!isRecord$28(rawField)) return [];
3292
+ if (!isRecord$29(rawField)) return [];
3225
3293
  const { id, type } = rawField;
3226
3294
  if (typeof id !== "string" || !id || typeof type !== "string") return [];
3227
3295
  if (type === "media" || type === "file") return [{
@@ -3229,7 +3297,7 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
3229
3297
  id,
3230
3298
  allowedMediaTypes: allowedMediaTypes(rawField, type, defaultFileKinds)
3231
3299
  }];
3232
- if (flattenRepeaterGroups && type === "group" && isRecord$28(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields$1(rawField), false, defaultFileKinds);
3300
+ if (flattenRepeaterGroups && type === "group" && isRecord$29(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields$1(rawField), false, defaultFileKinds);
3233
3301
  if (type === "group" || type === "modal") return [{
3234
3302
  kind: "object",
3235
3303
  id,
@@ -3238,14 +3306,14 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
3238
3306
  if (type === "tabGroup") return [{
3239
3307
  kind: "object",
3240
3308
  id,
3241
- fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$28(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
3309
+ fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$29(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
3242
3310
  }];
3243
3311
  if (type !== "repeater") return [];
3244
- if (isRecord$28(rawField.itemTypes)) return [{
3312
+ if (isRecord$29(rawField.itemTypes)) return [{
3245
3313
  kind: "repeater",
3246
3314
  id,
3247
3315
  mode: "variants",
3248
- variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$28(itemType) && Array.isArray(itemType.fields) ? [{
3316
+ variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$29(itemType) && Array.isArray(itemType.fields) ? [{
3249
3317
  typeId,
3250
3318
  fields: adaptFields(itemType.fields, true, defaultFileKinds)
3251
3319
  }] : []),
@@ -7899,7 +7967,7 @@ const LETTER_SPACING_MAP = {
7899
7967
  normal: "0",
7900
7968
  loose: "0.05em"
7901
7969
  };
7902
- function rule$1(selector, declarations) {
7970
+ function rule$2(selector, declarations) {
7903
7971
  return {
7904
7972
  selectors: typeof selector === "string" ? [selector] : selector,
7905
7973
  declarations
@@ -8082,7 +8150,7 @@ function variantAffordanceRule(variant, selector) {
8082
8150
  const maskUrl = affordanceIconMaskUrl(affordance.iconId);
8083
8151
  if (!maskUrl) return null;
8084
8152
  const mask = `url("${maskUrl}") no-repeat center / contain`;
8085
- return rule$1(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
8153
+ return rule$2(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
8086
8154
  ["content", "\"\""],
8087
8155
  ["display", "inline-block"],
8088
8156
  ["width", "1em"],
@@ -8121,7 +8189,7 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
8121
8189
  const borderWidth = BORDER_WIDTH_MAP$4[variant.border.widthClass] || "1px";
8122
8190
  decls.push(["border", `${borderWidth} solid currentColor`]);
8123
8191
  }
8124
- return rule$1(selector, decls);
8192
+ return rule$2(selector, decls);
8125
8193
  }
8126
8194
  /**
8127
8195
  * Resolve a colour for a button that does not own its own background.
@@ -8132,13 +8200,13 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
8132
8200
  */
8133
8201
  function variantStateColourRule(variantId, themeId, state, colour) {
8134
8202
  const baseSelector = `:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}`;
8135
- return rule$1(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
8203
+ return rule$2(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
8136
8204
  role: variantId,
8137
8205
  state
8138
8206
  }));
8139
8207
  }
8140
8208
  function variantFocusVisibleRule(variantId, themeId, focusIndicator) {
8141
- return rule$1(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
8209
+ return rule$2(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
8142
8210
  }
8143
8211
  function variantEffectsCss(variant, effects, themeId, tokens, theme) {
8144
8212
  if (!effects) return "";
@@ -8164,10 +8232,10 @@ function variantDisabledRules(variantId, themeId, colour) {
8164
8232
  `${base}:focus`
8165
8233
  ];
8166
8234
  const pseudoSelectors = [`${base}::before`, `${base}::after`];
8167
- return [rule$1(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
8235
+ return [rule$2(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
8168
8236
  role: variantId,
8169
8237
  state: "disabled"
8170
- }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$1(pseudoSelectors, [
8238
+ }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$2(pseudoSelectors, [
8171
8239
  ["opacity", "0"],
8172
8240
  ["animation", "none"],
8173
8241
  ["pointer-events", "none"]
@@ -8182,7 +8250,7 @@ function variantSizeRules(variant, variantId, themeSizes, themeId) {
8182
8250
  const decls = [];
8183
8251
  for (const decl of paddingDeclarations(config.padding, borderWidth)) decls.push(decl);
8184
8252
  if (config.fontSize) decls.push(["font-size", FONT_SIZE_MAP[config.fontSize] || "1rem"]);
8185
- rules.push(rule$1(selector, decls));
8253
+ rules.push(rule$2(selector, decls));
8186
8254
  }
8187
8255
  return rules;
8188
8256
  }
@@ -11621,6 +11689,15 @@ function asSiteStyleId(value) {
11621
11689
  function asDesignSystemTemplateId(value) {
11622
11690
  return brandNonEmptyString(value, "DesignSystemTemplateId");
11623
11691
  }
11692
+ function toNonEmptyReadonlyArray(values) {
11693
+ const [first, ...rest] = values;
11694
+ return first === void 0 ? null : [first, ...rest];
11695
+ }
11696
+ function requireNonEmptyReadonlyArray(values, message = "Expected a non-empty array.") {
11697
+ const result = toNonEmptyReadonlyArray(values);
11698
+ if (!result) throw new Error(message);
11699
+ return result;
11700
+ }
11624
11701
  function makeBoundaryId(previousBlockId, nextBlockId) {
11625
11702
  return asDesignBoundaryId(`boundary:${encodeURIComponent(previousBlockId)}:${encodeURIComponent(nextBlockId)}`);
11626
11703
  }
@@ -11825,20 +11902,37 @@ const themeV2RuntimeStyleFieldNames = [
11825
11902
  "_frameStyles",
11826
11903
  "_sectionStyles",
11827
11904
  "_containerStyles",
11828
- "_cardStyles",
11829
- "_transitionStyles",
11830
- "styleGroup"
11905
+ "_cardStyles"
11831
11906
  ];
11832
11907
  const themeV2BlockSpecificRawStyleFieldNames = [
11833
11908
  "_contentCardStyles",
11834
11909
  "_heroLayoutStyles",
11835
11910
  "_textCardStyles"
11836
11911
  ];
11837
- const themeV2NormalEditorRawStyleFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
11912
+ const themeV2NormalEditorRawStyleFieldNames = [
11913
+ ...themeV2RuntimeStyleFieldNames,
11914
+ ...themeV2BlockSpecificRawStyleFieldNames,
11915
+ "styleGroup"
11916
+ ];
11838
11917
  new Set(themeV2NormalEditorRawStyleFieldNames);
11839
11918
  [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
11840
11919
  const pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
11841
11920
  const siteChromeBackgroundOverrideOwner = "site-chrome-editor";
11921
+ const staleTransitionStyleGroupPrefix = "site-style-transition-";
11922
+ /**
11923
+ * A `styleGroup` in the retired `site-style-transition-*` namespace is residue
11924
+ * from the old per-block transition path (issue 1422). Any other value is
11925
+ * authored and must survive.
11926
+ */
11927
+ function isStaleTransitionStyleGroup(value) {
11928
+ return typeof value === "string" && value.startsWith(staleTransitionStyleGroupPrefix);
11929
+ }
11930
+ /** Drops a stale transition `styleGroup`, leaving authored values untouched. */
11931
+ function stripStaleTransitionStyleGroup(content) {
11932
+ if (!isStaleTransitionStyleGroup(content.styleGroup)) return { ...content };
11933
+ const { styleGroup: _stale, ...rest } = content;
11934
+ return rest;
11935
+ }
11842
11936
  //#endregion
11843
11937
  //#region ../theme-core/src/header/legacyHeaderVariant.ts
11844
11938
  /**
@@ -12155,7 +12249,7 @@ const boxBackgroundSchema = z$1.discriminatedUnion("type", [
12155
12249
  })
12156
12250
  ]);
12157
12251
  function normalizeAuthoringOpacity(value) {
12158
- if (!isRecord$27(value)) return value;
12252
+ if (!isRecord$28(value)) return value;
12159
12253
  return {
12160
12254
  ...value,
12161
12255
  opacity: normalizeOpacity(value.opacity)
@@ -12164,7 +12258,7 @@ function normalizeAuthoringOpacity(value) {
12164
12258
  function normalizeOpacity(value) {
12165
12259
  return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
12166
12260
  }
12167
- function isRecord$27(value) {
12261
+ function isRecord$28(value) {
12168
12262
  return typeof value === "object" && value !== null && !Array.isArray(value);
12169
12263
  }
12170
12264
  //#endregion
@@ -12907,7 +13001,7 @@ const PUBLIC_DOWNLOAD_MEDIA_TYPES = [
12907
13001
  /**
12908
13002
  * Strict record guard: excludes null, primitives, and arrays.
12909
13003
  */
12910
- function isRecord$26(value) {
13004
+ function isRecord$27(value) {
12911
13005
  return typeof value === "object" && value !== null && !Array.isArray(value);
12912
13006
  }
12913
13007
  /**
@@ -15628,11 +15722,282 @@ function cardRoleFromLegacyVocabulary(value) {
15628
15722
  */
15629
15723
  const cardRoleWithLegacyAliasSchema = z$1.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
15630
15724
  //#endregion
15631
- //#region ../theme-core/src/site-styles/compilationPlan.ts
15632
- const defaultHeaderBrandPreferences = {
15633
- brandDisplay: "auto",
15634
- logoSize: "auto"
15635
- };
15725
+ //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
15726
+ function parseCanonicalRuntimeBackground(content) {
15727
+ const background = readRecord(readRecord(content._sectionStyles)?.background);
15728
+ if (!background) return { kind: "none" };
15729
+ const parsed = boxBackgroundSchema.safeParse(background);
15730
+ if (!parsed.success) return { kind: "none" };
15731
+ return parsed.data.contentOwner === "page-design-editor" ? {
15732
+ kind: "page-design-override",
15733
+ background: parsed.data
15734
+ } : {
15735
+ kind: "block-authored",
15736
+ background: parsed.data
15737
+ };
15738
+ }
15739
+ function readRecord(value) {
15740
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
15741
+ }
15742
+ //#endregion
15743
+ //#region ../theme-core/src/site-styles/transitionCatalogue.ts
15744
+ /**
15745
+ * Family order for pickers. Entries below are listed family by family in this
15746
+ * same order, so the flat option list and the grouped families agree.
15747
+ */
15748
+ const transitionCatalogueFamilies = [
15749
+ {
15750
+ id: "gradient",
15751
+ label: "Gradient"
15752
+ },
15753
+ {
15754
+ id: "wave",
15755
+ label: "Wave"
15756
+ },
15757
+ {
15758
+ id: "angle",
15759
+ label: "Angle"
15760
+ }
15761
+ ];
15762
+ const mediumHeight = "6rem + 2px";
15763
+ const compactHeight = "4.5rem + 2px";
15764
+ const largeHeight = "7rem + 2px";
15765
+ /**
15766
+ * The single source of transition vocabulary. Order is significant: it is the
15767
+ * order of `boundaryOptions` (after `none`) and of each family's variants,
15768
+ * which persisted pins, page-flow presets and the editor picker all rely on.
15769
+ *
15770
+ * Shapes match what `generateLayoutCss` draws today. Structured
15771
+ * amplitude/frequency treatments (issue 826) extend this list later.
15772
+ */
15773
+ const transitionCatalogueEntries = defineExactCatalog()([
15774
+ {
15775
+ intent: "soft-fade-short",
15776
+ optionId: asBoundaryOptionId("break:soft"),
15777
+ family: "gradient",
15778
+ variantId: "soft",
15779
+ label: "Soft",
15780
+ variantLabel: "Soft",
15781
+ treatment: {
15782
+ kind: "soft-fade",
15783
+ height: "medium",
15784
+ curve: "soft"
15785
+ },
15786
+ size: "medium",
15787
+ heightExpression: mediumHeight,
15788
+ shape: { kind: "fade" }
15789
+ },
15790
+ {
15791
+ intent: "soft-fade-long",
15792
+ optionId: asBoundaryOptionId("break:spacious"),
15793
+ family: "gradient",
15794
+ variantId: "spacious",
15795
+ label: "Spacious",
15796
+ variantLabel: "Spacious",
15797
+ treatment: {
15798
+ kind: "soft-fade",
15799
+ height: "large",
15800
+ curve: "spacious"
15801
+ },
15802
+ size: "spacious",
15803
+ heightExpression: "10rem",
15804
+ shape: { kind: "fade" }
15805
+ },
15806
+ {
15807
+ intent: "soft-fade-expansive",
15808
+ optionId: asBoundaryOptionId("break:expansive"),
15809
+ family: "gradient",
15810
+ variantId: "expansive",
15811
+ label: "Expansive",
15812
+ variantLabel: "Expansive",
15813
+ treatment: {
15814
+ kind: "soft-fade",
15815
+ height: "expansive",
15816
+ curve: "expansive"
15817
+ },
15818
+ size: "expansive",
15819
+ heightExpression: "14rem",
15820
+ shape: { kind: "fade" }
15821
+ },
15822
+ {
15823
+ intent: "wave-edge-balanced",
15824
+ optionId: asBoundaryOptionId("break:wave-balanced"),
15825
+ family: "wave",
15826
+ variantId: "balanced",
15827
+ label: "Balanced wave",
15828
+ variantLabel: "Balanced",
15829
+ treatment: {
15830
+ kind: "wave-edge",
15831
+ height: "medium",
15832
+ amplitude: "balanced",
15833
+ rhythm: "double",
15834
+ symmetry: "symmetrical"
15835
+ },
15836
+ size: "medium",
15837
+ heightExpression: mediumHeight,
15838
+ shape: {
15839
+ kind: "wave",
15840
+ mask: "balanced"
15841
+ }
15842
+ },
15843
+ {
15844
+ intent: "wave-edge",
15845
+ optionId: asBoundaryOptionId("break:wave"),
15846
+ family: "wave",
15847
+ variantId: "classic",
15848
+ label: "Classic wave",
15849
+ variantLabel: "Classic",
15850
+ treatment: {
15851
+ kind: "wave-edge",
15852
+ height: "medium",
15853
+ amplitude: "balanced",
15854
+ rhythm: "double",
15855
+ symmetry: "asymmetrical"
15856
+ },
15857
+ size: "medium",
15858
+ heightExpression: mediumHeight,
15859
+ shape: {
15860
+ kind: "wave",
15861
+ mask: "classic"
15862
+ }
15863
+ },
15864
+ {
15865
+ intent: "wave-edge-gentle",
15866
+ optionId: asBoundaryOptionId("break:wave-gentle"),
15867
+ family: "wave",
15868
+ variantId: "gentle",
15869
+ label: "Gentle wave",
15870
+ variantLabel: "Gentle",
15871
+ treatment: {
15872
+ kind: "wave-edge",
15873
+ height: "compact",
15874
+ amplitude: "subtle",
15875
+ rhythm: "single",
15876
+ symmetry: "symmetrical"
15877
+ },
15878
+ size: "compact",
15879
+ heightExpression: compactHeight,
15880
+ shape: {
15881
+ kind: "wave",
15882
+ mask: "gentle"
15883
+ }
15884
+ },
15885
+ {
15886
+ intent: "wave-edge-bold",
15887
+ optionId: asBoundaryOptionId("break:wave-bold"),
15888
+ family: "wave",
15889
+ variantId: "bold",
15890
+ label: "Bold wave",
15891
+ variantLabel: "Bold",
15892
+ treatment: {
15893
+ kind: "wave-edge",
15894
+ height: "large",
15895
+ amplitude: "bold",
15896
+ rhythm: "double",
15897
+ symmetry: "symmetrical"
15898
+ },
15899
+ size: "large",
15900
+ heightExpression: largeHeight,
15901
+ shape: {
15902
+ kind: "wave",
15903
+ mask: "bold"
15904
+ }
15905
+ },
15906
+ {
15907
+ intent: "wave-edge-drift",
15908
+ optionId: asBoundaryOptionId("break:wave-drift"),
15909
+ family: "wave",
15910
+ variantId: "drift",
15911
+ label: "Drift wave",
15912
+ variantLabel: "Drift",
15913
+ treatment: {
15914
+ kind: "wave-edge",
15915
+ height: "medium",
15916
+ amplitude: "balanced",
15917
+ rhythm: "double",
15918
+ symmetry: "drift"
15919
+ },
15920
+ size: "medium",
15921
+ heightExpression: mediumHeight,
15922
+ shape: {
15923
+ kind: "wave",
15924
+ mask: "drift"
15925
+ }
15926
+ },
15927
+ {
15928
+ intent: "angle-edge-subtle",
15929
+ optionId: asBoundaryOptionId("break:angle-subtle"),
15930
+ family: "angle",
15931
+ variantId: "subtle",
15932
+ label: "Subtle angle",
15933
+ variantLabel: "Subtle",
15934
+ treatment: {
15935
+ kind: "angle-edge",
15936
+ height: "compact",
15937
+ slope: "subtle",
15938
+ direction: "forward"
15939
+ },
15940
+ size: "compact",
15941
+ heightExpression: compactHeight,
15942
+ shape: {
15943
+ kind: "angle",
15944
+ polygon: {
15945
+ startTopPercent: 34,
15946
+ endTopPercent: 18
15947
+ }
15948
+ }
15949
+ },
15950
+ {
15951
+ intent: "angle-edge",
15952
+ optionId: asBoundaryOptionId("break:angle-balanced"),
15953
+ family: "angle",
15954
+ variantId: "balanced",
15955
+ label: "Balanced angle",
15956
+ variantLabel: "Balanced",
15957
+ treatment: {
15958
+ kind: "angle-edge",
15959
+ height: "medium",
15960
+ slope: "balanced",
15961
+ direction: "forward"
15962
+ },
15963
+ size: "medium",
15964
+ heightExpression: mediumHeight,
15965
+ shape: {
15966
+ kind: "angle",
15967
+ polygon: {
15968
+ startTopPercent: 42,
15969
+ endTopPercent: 8
15970
+ }
15971
+ }
15972
+ },
15973
+ {
15974
+ intent: "angle-edge-steep",
15975
+ optionId: asBoundaryOptionId("break:angle-steep"),
15976
+ family: "angle",
15977
+ variantId: "steep",
15978
+ label: "Steep angle",
15979
+ variantLabel: "Steep",
15980
+ treatment: {
15981
+ kind: "angle-edge",
15982
+ height: "large",
15983
+ slope: "steep",
15984
+ direction: "forward"
15985
+ },
15986
+ size: "large",
15987
+ heightExpression: largeHeight,
15988
+ shape: {
15989
+ kind: "angle",
15990
+ polygon: {
15991
+ startTopPercent: 54,
15992
+ endTopPercent: 0
15993
+ }
15994
+ }
15995
+ }
15996
+ ]);
15997
+ /** CSS `clip-path` value for an angle shape, as `generateLayoutCss` draws it. */
15998
+ function anglePolygonCss(polygon) {
15999
+ return `polygon(0 ${polygon.startTopPercent}%, 100% ${polygon.endTopPercent}%, 100% 100%, 0 100%)`;
16000
+ }
15636
16001
  //#endregion
15637
16002
  //#region ../theme-core/src/site-styles/sectionTransitions.ts
15638
16003
  function boundaryRuntimeMetrics(family, size, heightExpression) {
@@ -15643,22 +16008,19 @@ function boundaryRuntimeMetrics(family, size, heightExpression) {
15643
16008
  reserveCss: `calc((${heightExpression}) / 2)`
15644
16009
  };
15645
16010
  }
15646
- const sectionBoundaryRuntimeMetricsByTransition = {
15647
- none: null,
15648
- "soft-fade-short": boundaryRuntimeMetrics("gradient", "medium", "6rem + 2px"),
15649
- "soft-fade-long": boundaryRuntimeMetrics("gradient", "spacious", "10rem"),
15650
- "soft-fade-expansive": boundaryRuntimeMetrics("gradient", "expansive", "14rem"),
15651
- "wave-edge": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
15652
- "wave-edge-balanced": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
15653
- "wave-edge-gentle": boundaryRuntimeMetrics("wave", "compact", "4.5rem + 2px"),
15654
- "wave-edge-bold": boundaryRuntimeMetrics("wave", "large", "7rem + 2px"),
15655
- "wave-edge-drift": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
15656
- "angle-edge": boundaryRuntimeMetrics("angle", "medium", "6rem + 2px"),
15657
- "angle-edge-subtle": boundaryRuntimeMetrics("angle", "compact", "4.5rem + 2px"),
15658
- "angle-edge-steep": boundaryRuntimeMetrics("angle", "large", "7rem + 2px")
15659
- };
15660
- function boundaryRuntimeMetricsForTransition(transition) {
15661
- return sectionBoundaryRuntimeMetricsByTransition[transition];
16011
+ new Map(transitionCatalogueEntries.map((entry) => [entry.intent, entry]));
16012
+ const metricsByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, boundaryRuntimeMetrics(entry.family, entry.size, entry.heightExpression)]));
16013
+ /**
16014
+ * The catalogue's compile-time coverage check guarantees an entry per
16015
+ * decorative intent, so a miss here is a broken invariant, not bad input.
16016
+ */
16017
+ function requireCatalogueValue(values, intent) {
16018
+ const value = values.get(intent);
16019
+ if (value === void 0) throw new Error(`Transition catalogue has no entry for ${intent}`);
16020
+ return value;
16021
+ }
16022
+ function boundaryRuntimeMetricsForDecorativeTransition(transition) {
16023
+ return requireCatalogueValue(metricsByIntent, transition);
15662
16024
  }
15663
16025
  const noneBoundaryOption = {
15664
16026
  id: asBoundaryOptionId("break:none"),
@@ -15666,29 +16028,233 @@ const noneBoundaryOption = {
15666
16028
  transitionAfter: "none",
15667
16029
  treatment: { kind: "none" }
15668
16030
  };
15669
- const softBoundaryOption = {
15670
- id: asBoundaryOptionId("break:soft"),
15671
- label: "Soft",
15672
- transitionAfter: "soft-fade-short",
15673
- treatment: {
15674
- kind: "soft-fade",
15675
- height: "medium",
15676
- curve: "soft"
15677
- }
16031
+ const boundaryOptionByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, {
16032
+ id: entry.optionId,
16033
+ label: entry.label,
16034
+ transitionAfter: entry.intent,
16035
+ treatment: entry.treatment
16036
+ }]));
16037
+ function boundaryOptionForTransitionIntent(intent) {
16038
+ return requireCatalogueValue(boundaryOptionByIntent, intent);
16039
+ }
16040
+ const softBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-short");
16041
+ const spaciousBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-long");
16042
+ boundaryOptionForTransitionIntent("soft-fade-expansive");
16043
+ boundaryOptionForTransitionIntent("wave-edge");
16044
+ boundaryOptionForTransitionIntent("wave-edge-balanced");
16045
+ boundaryOptionForTransitionIntent("wave-edge-gentle");
16046
+ boundaryOptionForTransitionIntent("wave-edge-bold");
16047
+ boundaryOptionForTransitionIntent("wave-edge-drift");
16048
+ boundaryOptionForTransitionIntent("angle-edge-subtle");
16049
+ boundaryOptionForTransitionIntent("angle-edge");
16050
+ boundaryOptionForTransitionIntent("angle-edge-steep");
16051
+ /**
16052
+ * Must remain non-empty; downstream code indexes `[0]` as the documented default.
16053
+ */
16054
+ const boundaryOptions = [noneBoundaryOption, ...transitionCatalogueEntries.map((entry) => boundaryOptionForTransitionIntent(entry.intent))];
16055
+ const noneTransitionVariant = {
16056
+ familyId: "none",
16057
+ variantId: "none",
16058
+ label: "None",
16059
+ boundaryOption: noneBoundaryOption
15678
16060
  };
15679
- const spaciousBoundaryOption = {
15680
- id: asBoundaryOptionId("break:spacious"),
15681
- label: "Spacious",
15682
- transitionAfter: "soft-fade-long",
15683
- treatment: {
15684
- kind: "soft-fade",
15685
- height: "large",
15686
- curve: "spacious"
16061
+ function transitionVariantForEntry(entry) {
16062
+ return {
16063
+ familyId: entry.family,
16064
+ variantId: entry.variantId,
16065
+ label: entry.variantLabel,
16066
+ boundaryOption: boundaryOptionForTransitionIntent(entry.intent)
16067
+ };
16068
+ }
16069
+ const sectionTransitionFamilies = [{
16070
+ id: "none",
16071
+ label: "None",
16072
+ variants: [noneTransitionVariant]
16073
+ }, ...transitionCatalogueFamilies.map((family) => ({
16074
+ id: family.id,
16075
+ label: family.label,
16076
+ variants: requireNonEmptyReadonlyArray(transitionCatalogueEntries.filter((entry) => entry.family === family.id).map(transitionVariantForEntry), `Transition family ${family.id} has no catalogue entries.`)
16077
+ }))];
16078
+ new Map(boundaryOptions.map((option) => [option.id, option]));
16079
+ new Map(boundaryOptions.map((option) => [stableTreatmentKey(option.treatment), option]));
16080
+ new Map(sectionTransitionFamilies.flatMap((family) => family.variants.map((variant) => [variant.boundaryOption.id, variant])));
16081
+ new Map(sectionTransitionFamilies.map((family) => [family.id, family]));
16082
+ function stableTreatmentKey(treatment) {
16083
+ switch (treatment.kind) {
16084
+ case "none": return "none";
16085
+ case "soft-fade": return `${treatment.kind}:${treatment.height}:${treatment.curve}`;
16086
+ case "wave-edge": return [
16087
+ treatment.kind,
16088
+ treatment.height,
16089
+ treatment.amplitude,
16090
+ treatment.rhythm,
16091
+ treatment.symmetry
16092
+ ].join(":");
16093
+ case "angle-edge": return [
16094
+ treatment.kind,
16095
+ treatment.height,
16096
+ treatment.slope,
16097
+ treatment.direction
16098
+ ].join(":");
16099
+ case "legacy-unsupported": return `${treatment.kind}:${treatment.boundaryOptionId}`;
16100
+ }
16101
+ }
16102
+ //#endregion
16103
+ //#region ../theme-core/src/site-styles/sectionBoundaryComposition.ts
16104
+ const backgroundToken = requireThemeColorToken$1("background");
16105
+ /**
16106
+ * Classifies a section from its final render content. `_sectionStyles`
16107
+ * already holds the effective background (Look default, page-design override
16108
+ * and block-authored media merged by `resolveSectionBackground`), so this only
16109
+ * reads it and never re-decides precedence. A missing or unparseable
16110
+ * background is the page background, as it is for the renderer.
16111
+ */
16112
+ function boundaryEndpointForSectionContent(content) {
16113
+ const background = parseCanonicalRuntimeBackground(content);
16114
+ if (background.kind === "none") return {
16115
+ kind: "paint",
16116
+ token: backgroundToken
16117
+ };
16118
+ const effective = background.background;
16119
+ switch (effective.type) {
16120
+ case "color": {
16121
+ if (effective.color === null || effective.color === void 0) return {
16122
+ kind: "paint",
16123
+ token: backgroundToken
16124
+ };
16125
+ const token = parseThemeColorToken(effective.color);
16126
+ return token ? {
16127
+ kind: "paint",
16128
+ token
16129
+ } : {
16130
+ kind: "unpainted",
16131
+ reason: "literal-color"
16132
+ };
16133
+ }
16134
+ case "gradient": return {
16135
+ kind: "unpainted",
16136
+ reason: "gradient"
16137
+ };
16138
+ case "image":
16139
+ case "video": return {
16140
+ kind: "unpainted",
16141
+ reason: "media"
16142
+ };
16143
+ default: return assertNever$12(effective, "Unhandled section background type");
15687
16144
  }
16145
+ }
16146
+ /**
16147
+ * Reads both facts from final render content in one place. `_frameStyles` is
16148
+ * the compiled frame; content without one (no page design, an SDK block)
16149
+ * draws a plain frame.
16150
+ */
16151
+ function sectionFactsForContent(content) {
16152
+ return {
16153
+ endpoint: boundaryEndpointForSectionContent(content),
16154
+ frame: frameKindForContent(content)
16155
+ };
16156
+ }
16157
+ function frameKindForContent(content) {
16158
+ const frameStyles = content._frameStyles;
16159
+ const kind = typeof frameStyles === "object" && frameStyles !== null && "kind" in frameStyles ? frameStyles.kind : null;
16160
+ return kind === "panel" || kind === "flush-panel" ? kind : "plain";
16161
+ }
16162
+ /**
16163
+ * The frame rule: a flush panel owns the edge it runs to, so no break is
16164
+ * drawn next to one. The single home for this rule; the generated
16165
+ * pipeline's budget and the site-style lab ask it with their plan frames.
16166
+ */
16167
+ function frameNotShownReason(previous, next) {
16168
+ return previous === "flush-panel" || next === "flush-panel" ? "flush-panel" : null;
16169
+ }
16170
+ /**
16171
+ * Decides whether and how a join draws. The frame rule wins before paint is
16172
+ * considered; two unpainted sides show nothing.
16173
+ */
16174
+ function composeBoundary(previous, next) {
16175
+ const frameReason = frameNotShownReason(previous.frame, next.frame);
16176
+ if (frameReason) return {
16177
+ kind: "not-shown",
16178
+ reason: frameReason
16179
+ };
16180
+ const from = previous.endpoint;
16181
+ const to = next.endpoint;
16182
+ if (from.kind === "paint") return {
16183
+ kind: "shown",
16184
+ composition: to.kind === "paint" ? {
16185
+ kind: "paint-to-paint",
16186
+ from: from.token,
16187
+ to: to.token
16188
+ } : {
16189
+ kind: "paint-to-unpainted",
16190
+ from: from.token
16191
+ }
16192
+ };
16193
+ return to.kind === "paint" ? {
16194
+ kind: "shown",
16195
+ composition: {
16196
+ kind: "unpainted-to-paint",
16197
+ to: to.token
16198
+ }
16199
+ } : {
16200
+ kind: "not-shown",
16201
+ reason: "both-unpainted"
16202
+ };
16203
+ }
16204
+ /**
16205
+ * Placement, reserves and paint for a shown boundary, derived from the
16206
+ * composition and the catalogue's metrics for the treatment.
16207
+ */
16208
+ function boundaryLayout(intent, composition) {
16209
+ const metrics = boundaryRuntimeMetricsForDecorativeTransition(intent);
16210
+ switch (composition.kind) {
16211
+ case "paint-to-paint": return {
16212
+ placement: "centred",
16213
+ reserves: {
16214
+ before: metrics.reserveCss,
16215
+ after: metrics.reserveCss
16216
+ },
16217
+ paint: {
16218
+ band: composition.from,
16219
+ shape: composition.to
16220
+ }
16221
+ };
16222
+ case "unpainted-to-paint": return {
16223
+ placement: "previous",
16224
+ reserves: {
16225
+ before: metrics.heightCss,
16226
+ after: null
16227
+ },
16228
+ paint: {
16229
+ band: null,
16230
+ shape: composition.to
16231
+ }
16232
+ };
16233
+ case "paint-to-unpainted": return {
16234
+ placement: "next",
16235
+ reserves: {
16236
+ before: null,
16237
+ after: metrics.heightCss
16238
+ },
16239
+ paint: {
16240
+ band: null,
16241
+ shape: composition.from
16242
+ }
16243
+ };
16244
+ default: return assertNever$12(composition, "Unhandled boundary composition");
16245
+ }
16246
+ }
16247
+ function requireThemeColorToken$1(value) {
16248
+ const token = parseThemeColorToken(value);
16249
+ if (!token) throw new Error(`Expected a theme color token. Received: ${value}`);
16250
+ return token;
16251
+ }
16252
+ //#endregion
16253
+ //#region ../theme-core/src/site-styles/compilationPlan.ts
16254
+ const defaultHeaderBrandPreferences = {
16255
+ brandDisplay: "auto",
16256
+ logoSize: "auto"
15688
16257
  };
15689
- asBoundaryOptionId("break:expansive");
15690
- asBoundaryOptionId("break:wave");
15691
- asBoundaryOptionId("break:wave-balanced"), asBoundaryOptionId("break:wave-gentle"), asBoundaryOptionId("break:wave-bold"), asBoundaryOptionId("break:wave-drift"), asBoundaryOptionId("break:angle-subtle"), asBoundaryOptionId("break:angle-balanced"), asBoundaryOptionId("break:angle-steep");
15692
16258
  asBoundaryOptionId("break:unknown");
15693
16259
  const pageDesignUserPinnedAppearanceChoiceSchema = z$1.object({
15694
16260
  kind: z$1.literal("user-pinned"),
@@ -15815,14 +16381,11 @@ function validateUniquePageDesignPins(intent, ctx) {
15815
16381
  label: "block Look pin"
15816
16382
  });
15817
16383
  addDuplicateStringIssues(ctx, {
15818
- values: intent.boundaryPins.map((pin) => boundaryKey$1(pin.previousBlockId, pin.nextBlockId)),
16384
+ values: intent.boundaryPins.map((pin) => makeBoundaryId(pin.previousBlockId, pin.nextBlockId)),
15819
16385
  path: ["boundaryPins"],
15820
16386
  label: "boundary pin"
15821
16387
  });
15822
16388
  }
15823
- function boundaryKey$1(previousBlockId, nextBlockId) {
15824
- return makeBoundaryId(previousBlockId, nextBlockId);
15825
- }
15826
16389
  //#endregion
15827
16390
  //#region ../theme-core/src/site-styles/styleTags.ts
15828
16391
  /**
@@ -18809,6 +19372,13 @@ z$1.object({
18809
19372
  fields: nonEmptyRuntimeStyleFieldNamesSchema,
18810
19373
  authority: styleAuthoritySchema
18811
19374
  }).strict();
19375
+ asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
19376
+ asPageFlowPresetId("generated-default");
19377
+ asSiteStyleId("site-style:south-west-twerk-school");
19378
+ asAppearancePresetId("hero-brand-scrim-fixed-frame");
19379
+ asLayoutVariantKey("classic");
19380
+ asLayoutVariantKey("micro");
19381
+ asBoundaryOptionId("break:wave");
18812
19382
  //#endregion
18813
19383
  //#region ../theme-core/src/site-styles/runtimeCompiler.ts
18814
19384
  function defineSectionForegroundRuntimeTokens(input) {
@@ -18832,7 +19402,7 @@ function defineComponentRuntimeRules(rules) {
18832
19402
  }
18833
19403
  /**
18834
19404
  * Default design rules translate semantic page-level choices into the current
18835
- * runtime's section background and transition style-group vocabulary.
19405
+ * runtime's section background vocabulary.
18836
19406
  */
18837
19407
  const defaultDesignRuntimeRules = defineDesignRuntimeRules({
18838
19408
  sectionSurfaces: {
@@ -18867,8 +19437,7 @@ const defaultDesignRuntimeRules = defineDesignRuntimeRules({
18867
19437
  color: "surface",
18868
19438
  opacity: .88
18869
19439
  }
18870
- },
18871
- transitionClassName: (transition, fromSurface, toSurface) => transition === "none" ? null : `site-style-transition-${transition}-from-${fromSurface}-to-${toSurface}`
19440
+ }
18872
19441
  });
18873
19442
  /**
18874
19443
  * Default component rules translate frame and item-surface semantics into the
@@ -18922,37 +19491,6 @@ function uniformSectionForeground(token) {
18922
19491
  supportingColor: token
18923
19492
  };
18924
19493
  }
18925
- function compileTransitionRuntime(input) {
18926
- return {
18927
- treatment: input.treatment,
18928
- from: input.from,
18929
- to: input.to,
18930
- cssVars: {
18931
- from: input.from.kind === "surface" && input.from.surface === "hero-image" ? "transparent" : transitionEndpointCssValue(input.from),
18932
- to: transitionEndpointCssValue(input.to)
18933
- }
18934
- };
18935
- }
18936
- function transitionEndpointCssValue(endpoint) {
18937
- switch (endpoint.kind) {
18938
- case "color-token": return themeColorTokenCssValue(endpoint.token);
18939
- case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
18940
- default: return assertNever$12(endpoint);
18941
- }
18942
- }
18943
- function tokenForTransitionSurface(surface) {
18944
- switch (surface) {
18945
- case "base":
18946
- case "hero-image": return "background";
18947
- case "muted-band": return "muted";
18948
- case "accent-band": return "accent";
18949
- case "brand-depth-band": return "primary-800";
18950
- default: return assertNever$12(surface);
18951
- }
18952
- }
18953
- function themeColorTokenCssValue(token) {
18954
- return `rgb(var(--tb-${token}))`;
18955
- }
18956
19494
  //#endregion
18957
19495
  //#region ../theme-core/src/site-styles/curatedSiteStyles.ts
18958
19496
  const SOFT_EARTH_SITE_STYLE_ID_VALUE = "site-style:soft-earth";
@@ -20637,17 +21175,9 @@ const curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id
20637
21175
  function getCuratedSiteStyle(id) {
20638
21176
  return curatedSiteStylesById.get(id) ?? null;
20639
21177
  }
20640
- asPageFlowPresetId("generated-default");
20641
- asSiteStyleId("site-style:south-west-twerk-school");
20642
- asAppearancePresetId("hero-brand-scrim-fixed-frame");
20643
- asLayoutVariantKey("classic");
20644
- asLayoutVariantKey("micro");
20645
- asBoundaryOptionId("break:wave");
20646
- asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
20647
21178
  const blockDesignFallback = {
20648
21179
  sectionSurface: "base",
20649
21180
  contentFrame: plainFrame,
20650
- transitionAfter: "none",
20651
21181
  sectionHeight: "default",
20652
21182
  heroLegibility: "none",
20653
21183
  imageSurfaceTreatment: { kind: "none" },
@@ -20658,7 +21188,7 @@ const blockDesignFallback = {
20658
21188
  accordionIconTreatment: null,
20659
21189
  composition: null
20660
21190
  };
20661
- blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
21191
+ blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
20662
21192
  //#endregion
20663
21193
  //#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
20664
21194
  const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
@@ -20766,35 +21296,6 @@ function jsonEquals(left, right) {
20766
21296
  return true;
20767
21297
  }
20768
21298
  defaultHeaderBrandPreferences.brandDisplay, defaultHeaderBrandPreferences.logoSize, defaultHeaderNavPreferences.typography, defaultHeaderNavPreferences.fontSize, defaultHeaderNavPreferences.interactionPalette;
20769
- function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
20770
- const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
20771
- const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
20772
- if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
20773
- const transition = compileTransitionRuntime({
20774
- treatment: boundary.treatment,
20775
- from: fromSurface,
20776
- to: toSurface
20777
- });
20778
- return {
20779
- ...boundary,
20780
- from: transition.from,
20781
- to: transition.to,
20782
- cssVars: transition.cssVars
20783
- };
20784
- }
20785
- function contentOwnedBackgroundEndpoint(content) {
20786
- const background = readRecord(readRecord(content?._sectionStyles)?.background);
20787
- if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
20788
- const token = parseThemeColorToken(background.color);
20789
- if (!token) return null;
20790
- return {
20791
- kind: "color-token",
20792
- token
20793
- };
20794
- }
20795
- function readRecord(value) {
20796
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
20797
- }
20798
21299
  //#endregion
20799
21300
  //#region ../theme-core/src/site-styles/generatedSchemas.ts
20800
21301
  function uniqueNonEmpty(values) {
@@ -23396,7 +23897,8 @@ const siteOptionSources = [
23396
23897
  "eventCategories",
23397
23898
  "eventSeries",
23398
23899
  "venues",
23399
- "staffMembers"
23900
+ "staffMembers",
23901
+ "appointmentServiceCategories"
23400
23902
  ];
23401
23903
  const mediaDeliverySchema = z$1.object({
23402
23904
  sizes: z$1.string().optional(),
@@ -27305,7 +27807,7 @@ function buildInteractiveIslandProps(options) {
27305
27807
  render
27306
27808
  };
27307
27809
  }
27308
- function isRecord$24(value) {
27810
+ function isRecord$25(value) {
27309
27811
  return typeof value === "object" && value !== null && !Array.isArray(value);
27310
27812
  }
27311
27813
  function deepEqualJsonValue(left, right) {
@@ -27315,8 +27817,8 @@ function deepEqualJsonValue(left, right) {
27315
27817
  if (left.length !== right.length) return false;
27316
27818
  return left.every((item, index) => deepEqualJsonValue(item, right[index]));
27317
27819
  }
27318
- if (isRecord$24(left) || isRecord$24(right)) {
27319
- if (!isRecord$24(left) || !isRecord$24(right)) return false;
27820
+ if (isRecord$25(left) || isRecord$25(right)) {
27821
+ if (!isRecord$25(left) || !isRecord$25(right)) return false;
27320
27822
  const leftKeys = Object.keys(left).sort();
27321
27823
  if (!deepEqualJsonValue(leftKeys, Object.keys(right).sort())) return false;
27322
27824
  return leftKeys.every((key) => deepEqualJsonValue(left[key], right[key]));
@@ -27330,12 +27832,12 @@ function toTrimmedString(value) {
27330
27832
  return trimmed ? trimmed : void 0;
27331
27833
  }
27332
27834
  function normalizeBookingFormConfig(form) {
27333
- if (!isRecord$23(form)) return null;
27835
+ if (!isRecord$24(form)) return null;
27334
27836
  const id = readNonEmptyString(form.id);
27335
27837
  const name = readNonEmptyString(form.name);
27336
27838
  if (!id || !name) return null;
27337
- const schemaSource = isRecord$23(form.schema) ? form.schema : isRecord$23(form.schemaJson) ? form.schemaJson : null;
27338
- const settingsSource = isRecord$23(form.settings) ? form.settings : isRecord$23(form.settingsJson) ? form.settingsJson : null;
27839
+ const schemaSource = isRecord$24(form.schema) ? form.schema : isRecord$24(form.schemaJson) ? form.schemaJson : null;
27840
+ const settingsSource = isRecord$24(form.settings) ? form.settings : isRecord$24(form.settingsJson) ? form.settingsJson : null;
27339
27841
  return {
27340
27842
  id,
27341
27843
  name,
@@ -27355,8 +27857,8 @@ function normalizeBookingServices(services) {
27355
27857
  };
27356
27858
  if (!services) return [];
27357
27859
  if (Array.isArray(services)) return services.filter(isBookingFormService).map(normalizeService);
27358
- if (isRecord$23(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
27359
- return isRecord$23(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
27860
+ if (isRecord$24(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
27861
+ return isRecord$24(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
27360
27862
  }
27361
27863
  function normalizeBookingSchema(value) {
27362
27864
  if (!value) return void 0;
@@ -27367,7 +27869,7 @@ function normalizeBookingSchema(value) {
27367
27869
  };
27368
27870
  }
27369
27871
  function readBookingFormField(value) {
27370
- if (!isRecord$23(value)) return null;
27872
+ if (!isRecord$24(value)) return null;
27371
27873
  const id = readNonEmptyString(value.id);
27372
27874
  const label = readNonEmptyString(value.label);
27373
27875
  const type = readBookingFieldType(value.type);
@@ -27410,7 +27912,7 @@ function readBookingFieldOptions(value) {
27410
27912
  return options.length > 0 ? options : void 0;
27411
27913
  }
27412
27914
  function readBookingFieldOption(value) {
27413
- if (!isRecord$23(value)) return null;
27915
+ if (!isRecord$24(value)) return null;
27414
27916
  const optionValue = readNonEmptyString(value.value);
27415
27917
  const label = readNonEmptyString(value.label);
27416
27918
  return optionValue && label ? {
@@ -27430,7 +27932,7 @@ function normalizeBookingSettings(value) {
27430
27932
  };
27431
27933
  }
27432
27934
  function isBookingFormService(value) {
27433
- return isRecord$23(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
27935
+ return isRecord$24(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
27434
27936
  }
27435
27937
  function readNonEmptyString(value) {
27436
27938
  if (typeof value !== "string") return null;
@@ -27448,10 +27950,35 @@ function readStringArray(value) {
27448
27950
  const strings = value.filter((item) => typeof item === "string");
27449
27951
  return strings.length > 0 ? strings : void 0;
27450
27952
  }
27451
- function isRecord$23(value) {
27953
+ function isRecord$24(value) {
27452
27954
  return typeof value === "object" && value !== null;
27453
27955
  }
27454
27956
  //#endregion
27957
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-categories.ts
27958
+ function isRecord$23(value) {
27959
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27960
+ }
27961
+ function readCategory(value) {
27962
+ if (!isRecord$23(value)) return [];
27963
+ const { id, name, description } = value;
27964
+ if (typeof id !== "string" || id.trim() === "" || typeof name !== "string") return [];
27965
+ return [{
27966
+ id,
27967
+ name,
27968
+ description: typeof description === "string" ? description : null
27969
+ }];
27970
+ }
27971
+ /**
27972
+ * The ordered service categories carried by a catalogue-shaped services
27973
+ * source (`{ services, categories }`). Every other shape has none, which
27974
+ * reads as a site without categories. Order is preserved: the database owns
27975
+ * it.
27976
+ */
27977
+ function normalizeBookingServiceCategories(services) {
27978
+ if (!isRecord$23(services) || !Array.isArray(services.categories)) return [];
27979
+ return services.categories.flatMap(readCategory);
27980
+ }
27981
+ //#endregion
27455
27982
  //#region ../logger/src/sanitize.ts
27456
27983
  const DEFAULT_MAX_STRING_LENGTH = 2e3;
27457
27984
  const STRUCTURED_KEYS = /* @__PURE__ */ new Set([
@@ -27644,11 +28171,20 @@ const bookingServiceSchema = z$1.object({
27644
28171
  priceCurrency: z$1.string().nullable().optional(),
27645
28172
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
27646
28173
  paymentTermsAvailability: blockBookingPaymentTermsAvailabilitySchema.optional(),
27647
- consent: blockBookingConsentSchema.optional()
28174
+ consent: blockBookingConsentSchema.optional(),
28175
+ categoryId: z$1.string().nullable().optional()
28176
+ });
28177
+ const bookingServiceCategorySchema = z$1.object({
28178
+ id: z$1.string(),
28179
+ name: z$1.string(),
28180
+ description: z$1.string().nullable()
27648
28181
  });
27649
28182
  const bookingServicesSourceSchema = z$1.union([
27650
28183
  z$1.array(bookingServiceSchema),
27651
- z$1.object({ services: z$1.array(bookingServiceSchema).optional() }),
28184
+ z$1.object({
28185
+ services: z$1.array(bookingServiceSchema).optional(),
28186
+ categories: z$1.array(bookingServiceCategorySchema).optional()
28187
+ }),
27652
28188
  z$1.record(z$1.string(), bookingServiceSchema)
27653
28189
  ]);
27654
28190
  const bookingConfigSchema = z$1.object({
@@ -27701,6 +28237,7 @@ const bookingHydrationSchema = z$1.object({
27701
28237
  rebookContext: z$1.object({ kind: z$1.literal("portal_rebook") }).nullable().optional(),
27702
28238
  form: bookingConfigSchema.nullable(),
27703
28239
  services: z$1.array(bookingServiceSchema),
28240
+ serviceCategories: z$1.array(bookingServiceCategorySchema).optional(),
27704
28241
  spamProtectionEnabled: z$1.boolean().optional()
27705
28242
  });
27706
28243
  const bookingRenderSchema = z$1.object({
@@ -27745,6 +28282,7 @@ const bookingFormBlockRuntime = defineClientOwnedBlockRuntime({
27745
28282
  rebookContext: source.rebookContext ?? null,
27746
28283
  form,
27747
28284
  services,
28285
+ serviceCategories: normalizeBookingServiceCategories(source.services),
27748
28286
  spamProtectionEnabled: source.spamProtectionEnabled
27749
28287
  }
27750
28288
  };
@@ -33851,6 +34389,75 @@ function resolvePassBuyButtonText(buyButtonText, passPurchaseMode) {
33851
34389
  return passPurchaseMode === "add_to_cart" ? "Add to cart" : "Buy Now";
33852
34390
  }
33853
34391
  //#endregion
34392
+ //#region ../api/src/defineEndpointDefinitions.ts
34393
+ /**
34394
+ * Validates extracted endpoint descriptors while preserving each literal path,
34395
+ * method, and parameter contract for the typed URL builders.
34396
+ */
34397
+ function defineEndpointDefinitions(definitions) {
34398
+ return definitions;
34399
+ }
34400
+ //#endregion
34401
+ //#region ../api/src/publicNewsletterEndpointDefinitions.ts
34402
+ const PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
34403
+ subscribeNewsletter: {
34404
+ path: "/public/sites/{siteId}/newsletter/subscribe",
34405
+ method: "POST",
34406
+ tags: ["public-newsletter-{siteId}"],
34407
+ auth: "public",
34408
+ responseKind: "json"
34409
+ },
34410
+ confirmNewsletter: {
34411
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
34412
+ method: "GET",
34413
+ tags: ["public-newsletter-{siteId}"],
34414
+ auth: "public",
34415
+ responseKind: "text"
34416
+ },
34417
+ unsubscribeNewsletter: {
34418
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
34419
+ method: "GET",
34420
+ tags: ["public-newsletter-{siteId}"],
34421
+ auth: "public",
34422
+ responseKind: "text"
34423
+ },
34424
+ unsubscribeNewsletterPost: {
34425
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
34426
+ method: "POST",
34427
+ tags: ["public-newsletter-{siteId}"],
34428
+ auth: "public",
34429
+ responseKind: "text"
34430
+ },
34431
+ confirmNewsletterPost: {
34432
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
34433
+ method: "POST",
34434
+ tags: ["public-newsletter-{siteId}"],
34435
+ auth: "public",
34436
+ responseKind: "text"
34437
+ },
34438
+ getNewsletterPreferences: {
34439
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
34440
+ method: "GET",
34441
+ tags: ["public-newsletter-{siteId}"],
34442
+ auth: "public",
34443
+ responseKind: "text"
34444
+ },
34445
+ saveNewsletterPreferences: {
34446
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
34447
+ method: "POST",
34448
+ tags: ["public-newsletter-{siteId}"],
34449
+ auth: "public",
34450
+ responseKind: "text"
34451
+ },
34452
+ getNewsletterArchive: {
34453
+ path: "/public/sites/{siteId}/newsletter/archive/{token}",
34454
+ method: "GET",
34455
+ tags: ["public-newsletter-{siteId}"],
34456
+ auth: "public",
34457
+ responseKind: "text"
34458
+ }
34459
+ });
34460
+ //#endregion
33854
34461
  //#region ../api/src/apiEndpointTypes.ts
33855
34462
  function allowedDemoMutationPolicy() {
33856
34463
  return {
@@ -33940,17 +34547,85 @@ const SDK_ENTRY_READ_ENDPOINTS = {
33940
34547
  }
33941
34548
  };
33942
34549
  //#endregion
33943
- //#region ../api/src/defineEndpointDefinitions.ts
33944
- /**
33945
- * Validates extracted endpoint descriptors while preserving each literal path,
33946
- * method, and parameter contract for the typed URL builders.
33947
- */
33948
- function defineEndpointDefinitions(definitions) {
33949
- return definitions;
33950
- }
33951
- //#endregion
33952
34550
  //#region ../api/src/appointmentEndpointDefinitions.ts
33953
34551
  const APPOINTMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
34552
+ ...defineEndpointDefinitions({
34553
+ listGroupedAppointmentServices: {
34554
+ path: "/sites/{siteId}/bookings/services/grouped",
34555
+ method: "GET",
34556
+ auth: "user",
34557
+ responseKind: "json",
34558
+ errors: ["server:internal_error"]
34559
+ },
34560
+ reorderAppointmentServices: {
34561
+ path: "/sites/{siteId}/bookings/services/order",
34562
+ method: "PUT",
34563
+ auth: "user",
34564
+ responseKind: "json",
34565
+ errors: [
34566
+ "validation:invalid_input",
34567
+ "auth:forbidden",
34568
+ "resource:not_found",
34569
+ "resource:conflict",
34570
+ "server:internal_error"
34571
+ ]
34572
+ },
34573
+ listAppointmentServiceCategories: {
34574
+ path: "/sites/{siteId}/bookings/service-categories",
34575
+ method: "GET",
34576
+ auth: "user",
34577
+ responseKind: "json",
34578
+ errors: ["server:internal_error"]
34579
+ },
34580
+ createAppointmentServiceCategory: {
34581
+ path: "/sites/{siteId}/bookings/service-categories",
34582
+ method: "POST",
34583
+ auth: "user",
34584
+ responseKind: "json",
34585
+ errors: [
34586
+ "validation:invalid_input",
34587
+ "auth:forbidden",
34588
+ "resource:conflict",
34589
+ "server:internal_error"
34590
+ ]
34591
+ },
34592
+ updateAppointmentServiceCategory: {
34593
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
34594
+ method: "PUT",
34595
+ auth: "user",
34596
+ responseKind: "json",
34597
+ errors: [
34598
+ "validation:invalid_input",
34599
+ "auth:forbidden",
34600
+ "resource:not_found",
34601
+ "resource:conflict",
34602
+ "server:internal_error"
34603
+ ]
34604
+ },
34605
+ deleteAppointmentServiceCategory: {
34606
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
34607
+ method: "DELETE",
34608
+ auth: "user",
34609
+ responseKind: "json",
34610
+ errors: [
34611
+ "auth:forbidden",
34612
+ "resource:not_found",
34613
+ "server:internal_error"
34614
+ ]
34615
+ },
34616
+ reorderAppointmentServiceCategories: {
34617
+ path: "/sites/{siteId}/bookings/service-categories/order",
34618
+ method: "PUT",
34619
+ auth: "user",
34620
+ responseKind: "json",
34621
+ errors: [
34622
+ "validation:invalid_input",
34623
+ "auth:forbidden",
34624
+ "resource:conflict",
34625
+ "server:internal_error"
34626
+ ]
34627
+ }
34628
+ }),
33954
34629
  listAppointmentResources: {
33955
34630
  path: "/sites/{siteId}/bookings/resources",
33956
34631
  method: "GET",
@@ -35098,39 +35773,6 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
35098
35773
  auth: "user",
35099
35774
  responseKind: "json",
35100
35775
  demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN
35101
- },
35102
- listNewsletterSendJobs: {
35103
- path: "/sites/{siteId}/newsletter/jobs",
35104
- method: "GET",
35105
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
35106
- auth: "user",
35107
- responseKind: "json"
35108
- },
35109
- retryNewsletterSendJob: {
35110
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/retry",
35111
- method: "POST",
35112
- errors: [
35113
- "validation:invalid_input",
35114
- "resource:not_found",
35115
- "auth:forbidden",
35116
- "server:internal_error"
35117
- ],
35118
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
35119
- auth: "user",
35120
- responseKind: "json"
35121
- },
35122
- cancelNewsletterSendJob: {
35123
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/cancel",
35124
- method: "POST",
35125
- errors: [
35126
- "validation:invalid_input",
35127
- "resource:not_found",
35128
- "auth:forbidden",
35129
- "server:internal_error"
35130
- ],
35131
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
35132
- auth: "user",
35133
- responseKind: "json"
35134
35776
  }
35135
35777
  });
35136
35778
  //#endregion
@@ -35896,6 +36538,7 @@ const BILLING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
35896
36538
  }
35897
36539
  });
35898
36540
  const API_ENDPOINTS$1 = {
36541
+ ...PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS,
35899
36542
  ...ACCOUNT_ENDPOINT_DEFINITIONS,
35900
36543
  ...PUBLIC_ONBOARDING_ATTEMPT_ENDPOINT_DEFINITIONS,
35901
36544
  ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
@@ -39030,41 +39673,6 @@ const API_ENDPOINTS$1 = {
39030
39673
  "server:internal_error"
39031
39674
  ]
39032
39675
  },
39033
- subscribeNewsletter: {
39034
- path: "/public/sites/{siteId}/newsletter/subscribe",
39035
- method: "POST",
39036
- tags: ["public-newsletter-{siteId}"],
39037
- auth: "public",
39038
- responseKind: "json"
39039
- },
39040
- confirmNewsletter: {
39041
- path: "/public/sites/{siteId}/newsletter/confirm/{token}",
39042
- method: "GET",
39043
- tags: ["public-newsletter-{siteId}"],
39044
- auth: "public",
39045
- responseKind: "text"
39046
- },
39047
- unsubscribeNewsletter: {
39048
- path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
39049
- method: "GET",
39050
- tags: ["public-newsletter-{siteId}"],
39051
- auth: "public",
39052
- responseKind: "text"
39053
- },
39054
- unsubscribeNewsletterPost: {
39055
- path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
39056
- method: "POST",
39057
- tags: ["public-newsletter-{siteId}"],
39058
- auth: "public",
39059
- responseKind: "text"
39060
- },
39061
- getNewsletterArchive: {
39062
- path: "/public/sites/{siteId}/newsletter/archive/{token}",
39063
- method: "GET",
39064
- tags: ["public-newsletter-{siteId}"],
39065
- auth: "public",
39066
- responseKind: "text"
39067
- },
39068
39676
  listSiteContentTypes: {
39069
39677
  path: "/sites/{siteId}/content-types",
39070
39678
  method: "GET",
@@ -43777,16 +44385,16 @@ const interpolateRichTextTokensOptionsSchema = z$1.object({ tokens: z$1.array(su
43777
44385
  const richTextTokenPattern = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
43778
44386
  function getSiteName(context) {
43779
44387
  const viewModel = context.viewModel;
43780
- if (!isRecord$26(viewModel)) return null;
44388
+ if (!isRecord$27(viewModel)) return null;
43781
44389
  const site = viewModel.site;
43782
- if (isRecord$26(site) && typeof site.title === "string") {
44390
+ if (isRecord$27(site) && typeof site.title === "string") {
43783
44391
  const title = site.title.trim();
43784
44392
  return title.length > 0 ? title : null;
43785
44393
  }
43786
44394
  const root = viewModel.$root;
43787
- if (isRecord$26(root)) {
44395
+ if (isRecord$27(root)) {
43788
44396
  const rootSite = root.site;
43789
- if (isRecord$26(rootSite) && typeof rootSite.title === "string") {
44397
+ if (isRecord$27(rootSite) && typeof rootSite.title === "string") {
43790
44398
  const title = rootSite.title.trim();
43791
44399
  if (title.length > 0) return title;
43792
44400
  }
@@ -43808,7 +44416,7 @@ function replaceTokenText(input, allowedTokens, tokenValues) {
43808
44416
  });
43809
44417
  }
43810
44418
  function visitRichTextNode(node, allowedTokens, tokenValues) {
43811
- if (!isRecord$26(node)) return node;
44419
+ if (!isRecord$27(node)) return node;
43812
44420
  let changed = false;
43813
44421
  const nextNode = { ...node };
43814
44422
  if (nextNode.type === "text" && typeof nextNode.text === "string") {
@@ -43829,7 +44437,7 @@ function visitRichTextNode(node, allowedTokens, tokenValues) {
43829
44437
  return changed ? nextNode : node;
43830
44438
  }
43831
44439
  function interpolateRichTextTokens(value, options, context) {
43832
- if (!isRecord$26(value)) return value;
44440
+ if (!isRecord$27(value)) return value;
43833
44441
  return visitRichTextNode(value, new Set(options.tokens), {
43834
44442
  year: String((/* @__PURE__ */ new Date()).getFullYear()),
43835
44443
  site_name: getSiteName(context)
@@ -45381,18 +45989,43 @@ const publicAppointmentServiceSchema = z$1.object({
45381
45989
  pricing: publicPricingSchema.optional(),
45382
45990
  priceAmount: z$1.number().nullable(),
45383
45991
  priceCurrency: z$1.string().nullable(),
45384
- location: publicLocationSchema
45992
+ location: publicLocationSchema,
45993
+ categoryId: z$1.string().nullable().optional().transform((value) => value ?? null)
45385
45994
  });
45386
- const publicAppointmentServicesSchema = z$1.array(publicAppointmentServiceSchema);
45995
+ const publicServiceCategorySchema = z$1.object({
45996
+ id: z$1.string().min(1),
45997
+ name: z$1.string(),
45998
+ description: z$1.string().nullable().optional().transform((value) => value ?? null)
45999
+ });
46000
+ /**
46001
+ * The loader hands over the catalogue `{ services, categories }` (issue 1403).
46002
+ * A bare services array is still accepted and read as a site with no
46003
+ * categories, so callers from before categories keep the flat list.
46004
+ */
46005
+ const publicAppointmentServicesSchema = z$1.union([z$1.array(publicAppointmentServiceSchema).transform((services) => ({
46006
+ services,
46007
+ categories: []
46008
+ })), z$1.object({
46009
+ services: z$1.array(publicAppointmentServiceSchema),
46010
+ categories: z$1.array(publicServiceCategorySchema)
46011
+ })]);
45387
46012
  const EMPTY_MESSAGE = "No appointments are available right now.";
45388
46013
  const ERROR_MESSAGE = "Appointments are temporarily unavailable. Please try again shortly.";
46014
+ const OTHER_GROUP_KEY = "other";
46015
+ const OTHER_GROUP_HEADING = "Other";
45389
46016
  function parseServices(value) {
45390
46017
  const result = publicAppointmentServicesSchema.safeParse(value);
45391
46018
  return result.success ? {
45392
46019
  kind: "valid",
45393
- services: result.data
46020
+ services: result.data.services,
46021
+ categories: result.data.categories
45394
46022
  } : { kind: "invalid" };
45395
46023
  }
46024
+ function readCategoryFilter(value) {
46025
+ if (typeof value !== "string") return null;
46026
+ const trimmed = value.trim();
46027
+ return trimmed.length > 0 ? trimmed : null;
46028
+ }
45396
46029
  function formatAppointmentDuration(durationMinutes) {
45397
46030
  return `${durationMinutes} min`;
45398
46031
  }
@@ -45466,8 +46099,44 @@ function buildRows(services, pathname, servicePathPrefix) {
45466
46099
  detailsHref: servicePathPrefix && service.slug ? `${servicePathPrefix.replace(/\/$/, "")}/${encodeURIComponent(service.slug)}` : null
45467
46100
  }));
45468
46101
  }
46102
+ const EMPTY_VIEW = {
46103
+ kind: "empty",
46104
+ message: EMPTY_MESSAGE
46105
+ };
46106
+ function categoryGroup(category, services, toRows) {
46107
+ return {
46108
+ key: category.id,
46109
+ heading: category.name,
46110
+ intro: category.description,
46111
+ rows: toRows(services)
46112
+ };
46113
+ }
46114
+ function viewFromLayout(layout, toRows) {
46115
+ switch (layout.kind) {
46116
+ case "uncategorized-site": return layout.services.length === 0 ? EMPTY_VIEW : {
46117
+ kind: "items",
46118
+ rows: toRows(layout.services)
46119
+ };
46120
+ case "categorized-site": {
46121
+ const groups = layout.groups.map((group) => categoryGroup(group.category, group.services, toRows));
46122
+ const other = layout.other.length === 0 ? [] : [{
46123
+ key: OTHER_GROUP_KEY,
46124
+ heading: OTHER_GROUP_HEADING,
46125
+ intro: null,
46126
+ rows: toRows(layout.other)
46127
+ }];
46128
+ const allGroups = [...groups, ...other];
46129
+ return allGroups.length === 0 ? EMPTY_VIEW : {
46130
+ kind: "groups",
46131
+ groups: allGroups
46132
+ };
46133
+ }
46134
+ default: return assertNever$20(layout);
46135
+ }
46136
+ }
45469
46137
  function buildAppointmentServicesView(input) {
45470
- if (!input.pathname) return {
46138
+ const pathname = input.pathname;
46139
+ if (!pathname) return {
45471
46140
  kind: "error",
45472
46141
  message: ERROR_MESSAGE
45473
46142
  };
@@ -45476,76 +46145,128 @@ function buildAppointmentServicesView(input) {
45476
46145
  kind: "error",
45477
46146
  message: ERROR_MESSAGE
45478
46147
  };
45479
- if (parsed.services.length === 0) return {
45480
- kind: "empty",
45481
- message: EMPTY_MESSAGE
45482
- };
45483
- return {
45484
- kind: "items",
45485
- rows: buildRows(parsed.services, input.pathname, input.servicePathPrefix ?? null)
45486
- };
46148
+ const toRows = (services) => buildRows(services, pathname, input.servicePathPrefix ?? null);
46149
+ const view = selectCategoryView(parsed.categories, parsed.services, readCategoryFilter(input.categoryId));
46150
+ switch (view.kind) {
46151
+ case "single": return view.group.services.length === 0 ? EMPTY_VIEW : {
46152
+ kind: "groups",
46153
+ groups: [categoryGroup(view.group.category, view.group.services, toRows)]
46154
+ };
46155
+ case "all":
46156
+ case "fallback-all": return viewFromLayout(view.layout, toRows);
46157
+ default: return assertNever$20(view);
46158
+ }
46159
+ }
46160
+ function AppointmentServiceRows({ rows, RowTitle }) {
46161
+ return /* @__PURE__ */ jsx("div", {
46162
+ className: "appointment-services-rows rb-flex rb-flex-col",
46163
+ children: rows.map((row) => /* @__PURE__ */ jsxs("article", {
46164
+ className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
46165
+ children: [
46166
+ /* @__PURE__ */ jsxs("div", {
46167
+ className: "appointment-services-row-main rb-min-w-0",
46168
+ children: [
46169
+ /* @__PURE__ */ jsx(RowTitle, {
46170
+ className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
46171
+ children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
46172
+ href: row.detailsHref,
46173
+ children: row.title
46174
+ }) : row.title
46175
+ }),
46176
+ row.description ? /* @__PURE__ */ jsx("p", {
46177
+ className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
46178
+ children: row.description
46179
+ }) : null,
46180
+ /* @__PURE__ */ jsxs("p", {
46181
+ className: "appointment-services-row-meta rb-caption status-muted",
46182
+ children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
46183
+ })
46184
+ ]
46185
+ }),
46186
+ row.priceLabel ? /* @__PURE__ */ jsx("span", {
46187
+ className: "appointment-services-row-price rb-text-lg",
46188
+ children: row.priceLabel
46189
+ }) : null,
46190
+ /* @__PURE__ */ jsx(ThemedButtonLink, {
46191
+ variant: "secondary",
46192
+ size: "sm",
46193
+ extraClassName: "appointment-services-row-action",
46194
+ href: row.href,
46195
+ label: "Check times",
46196
+ screenReaderSuffix: ` for ${row.title}`
46197
+ })
46198
+ ]
46199
+ }, row.id))
46200
+ });
45487
46201
  }
45488
- function AppointmentServicesNode({ sectionHeading, services, pathname, servicePathPrefix, className }) {
46202
+ function AppointmentServiceGroups({ groups, hasSectionHeading, idPrefix }) {
46203
+ const GroupHeading = hasSectionHeading ? "h3" : "h2";
46204
+ const RowTitle = hasSectionHeading ? "h4" : "h3";
46205
+ return /* @__PURE__ */ jsx("div", {
46206
+ className: "appointment-services-groups rb-flex rb-flex-col rb-gap-10",
46207
+ children: groups.map((group) => {
46208
+ const headingId = `${idPrefix}-group-${group.key}`;
46209
+ return /* @__PURE__ */ jsxs("section", {
46210
+ className: "appointment-services-group rb-flex rb-flex-col rb-gap-2",
46211
+ "aria-labelledby": headingId,
46212
+ "data-appointment-services-group": group.key,
46213
+ children: [/* @__PURE__ */ jsxs("div", {
46214
+ className: "appointment-services-group-heading",
46215
+ children: [/* @__PURE__ */ jsx(GroupHeading, {
46216
+ id: headingId,
46217
+ className: "appointment-services-group-title rb-text-sm rb-font-semibold rb-uppercase rb-tracking-wide",
46218
+ children: group.heading
46219
+ }), group.intro ? /* @__PURE__ */ jsx("p", {
46220
+ className: "appointment-services-group-intro rb-text-sm rb-leading-6 rb-max-w-prose status-muted",
46221
+ children: group.intro
46222
+ }) : null]
46223
+ }), /* @__PURE__ */ jsx(AppointmentServiceRows, {
46224
+ rows: group.rows,
46225
+ RowTitle
46226
+ })]
46227
+ }, group.key);
46228
+ })
46229
+ });
46230
+ }
46231
+ function AppointmentServicesNode({ sectionHeading, categoryId, services, pathname, servicePathPrefix, blockId, className }) {
45489
46232
  const view = buildAppointmentServicesView({
45490
46233
  services,
46234
+ categoryId,
45491
46235
  pathname: typeof pathname === "string" ? pathname : void 0,
45492
46236
  servicePathPrefix: typeof servicePathPrefix === "string" ? servicePathPrefix : null
45493
46237
  });
45494
46238
  const rootClassName = `appointment-services ${className ?? ""}`.trim();
45495
- const RowTitle = sectionHeading ? "h3" : "h2";
45496
- if (view.kind === "error" || view.kind === "empty") return /* @__PURE__ */ jsx("div", {
45497
- className: rootClassName,
45498
- "data-appointment-services-state": view.kind,
45499
- children: /* @__PURE__ */ jsx("p", {
45500
- className: "rb-caption status-muted",
45501
- role: "status",
45502
- children: view.message
45503
- })
45504
- });
45505
- return /* @__PURE__ */ jsx("div", {
45506
- className: `${rootClassName} rb-flex rb-flex-col`,
45507
- "data-appointment-services-state": "populated",
45508
- children: /* @__PURE__ */ jsx("div", {
45509
- className: "appointment-services-rows rb-flex rb-flex-col",
45510
- children: view.rows.map((row) => /* @__PURE__ */ jsxs("article", {
45511
- className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
45512
- children: [
45513
- /* @__PURE__ */ jsxs("div", {
45514
- className: "appointment-services-row-main rb-min-w-0",
45515
- children: [
45516
- /* @__PURE__ */ jsx(RowTitle, {
45517
- className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
45518
- children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
45519
- href: row.detailsHref,
45520
- children: row.title
45521
- }) : row.title
45522
- }),
45523
- row.description ? /* @__PURE__ */ jsx("p", {
45524
- className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
45525
- children: row.description
45526
- }) : null,
45527
- /* @__PURE__ */ jsxs("p", {
45528
- className: "appointment-services-row-meta rb-caption status-muted",
45529
- children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
45530
- })
45531
- ]
45532
- }),
45533
- row.priceLabel ? /* @__PURE__ */ jsx("span", {
45534
- className: "appointment-services-row-price rb-text-lg",
45535
- children: row.priceLabel
45536
- }) : null,
45537
- /* @__PURE__ */ jsx(ThemedButtonLink, {
45538
- variant: "secondary",
45539
- size: "sm",
45540
- extraClassName: "appointment-services-row-action",
45541
- href: row.href,
45542
- label: "Check times",
45543
- screenReaderSuffix: ` for ${row.title}`
45544
- })
45545
- ]
45546
- }, row.id))
45547
- })
45548
- });
46239
+ const hasSectionHeading = Boolean(sectionHeading);
46240
+ switch (view.kind) {
46241
+ case "error":
46242
+ case "empty": return /* @__PURE__ */ jsx("div", {
46243
+ className: rootClassName,
46244
+ "data-appointment-services-state": view.kind,
46245
+ children: /* @__PURE__ */ jsx("p", {
46246
+ className: "rb-caption status-muted",
46247
+ role: "status",
46248
+ children: view.message
46249
+ })
46250
+ });
46251
+ case "items": return /* @__PURE__ */ jsx("div", {
46252
+ className: `${rootClassName} rb-flex rb-flex-col`,
46253
+ "data-appointment-services-state": "populated",
46254
+ children: /* @__PURE__ */ jsx(AppointmentServiceRows, {
46255
+ rows: view.rows,
46256
+ RowTitle: hasSectionHeading ? "h3" : "h2"
46257
+ })
46258
+ });
46259
+ case "groups": return /* @__PURE__ */ jsx("div", {
46260
+ className: `${rootClassName} rb-flex rb-flex-col`,
46261
+ "data-appointment-services-state": "populated",
46262
+ children: /* @__PURE__ */ jsx(AppointmentServiceGroups, {
46263
+ groups: view.groups,
46264
+ hasSectionHeading,
46265
+ idPrefix: `appointment-services-${blockId ?? "block"}`
46266
+ })
46267
+ });
46268
+ default: return assertNever$20(view);
46269
+ }
45549
46270
  }
45550
46271
  //#endregion
45551
46272
  //#region ../blocks/src/system/runtime/nodes/booking-form.server.tsx
@@ -55319,6 +56040,123 @@ function generateFooterBottomTextLinkStyles(footer, themeId, tokens) {
55319
56040
  return cssChunks.join("\n");
55320
56041
  }
55321
56042
  //#endregion
56043
+ //#region ../theme-core/src/layout/boundaryCss.ts
56044
+ /**
56045
+ * Section-break CSS, generated from the transition catalogue (issue 1422).
56046
+ *
56047
+ * The renderer emits one element per shown join:
56048
+ *
56049
+ * <div data-block-boundary data-boundary-treatment="wave-edge-gentle"
56050
+ * data-boundary-placement="previous"
56051
+ * style="--boundary-shape-paint: …; --boundary-band-paint: …">
56052
+ *
56053
+ * Every selector matches an exact attribute value, so there is no reliance on
56054
+ * prefix ordering in the cascade and nothing reads colours out of a string.
56055
+ * The shape layer is `::after`; the band layer (paint → paint only) is
56056
+ * `::before`.
56057
+ */
56058
+ const waveMaskSvgPaths = {
56059
+ classic: "M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z",
56060
+ balanced: "M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z",
56061
+ gentle: "M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z",
56062
+ bold: "M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z",
56063
+ drift: "M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z"
56064
+ };
56065
+ function waveMaskDataUrl(mask) {
56066
+ return svgDataUrl(`<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='${waveMaskSvgPaths[mask]}' fill='white'/></svg>`);
56067
+ }
56068
+ /**
56069
+ * How each placement anchors the layers against the zero-height boundary
56070
+ * element that sits exactly on the join.
56071
+ *
56072
+ * - `centred` straddles the join, band under shape.
56073
+ * - `previous` sits above the join, inside the previous section.
56074
+ * - `next` sits below the join, inside the next section, with the shape
56075
+ * flipped so it reads as the mirror of `previous`.
56076
+ */
56077
+ const placementRules = {
56078
+ centred: {
56079
+ shape: ["top: 0;", "transform: translateY(-50%);"],
56080
+ band: ["top: 0;", "transform: translateY(-50%);"]
56081
+ },
56082
+ previous: {
56083
+ shape: ["bottom: 0;"],
56084
+ band: null
56085
+ },
56086
+ next: {
56087
+ shape: ["top: 0;", "transform: scaleY(-1);"],
56088
+ band: null
56089
+ }
56090
+ };
56091
+ function generateBoundaryCss(selector) {
56092
+ const boundary = `${selector} [data-block-boundary]`;
56093
+ return [
56094
+ rule$1(boundary, [
56095
+ "position: relative;",
56096
+ "z-index: 3;",
56097
+ "height: 0;",
56098
+ "pointer-events: none;"
56099
+ ]),
56100
+ rule$1(`${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section'])`, ["padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));"]),
56101
+ rule$1(`${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section'])`, ["padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));"]),
56102
+ rule$1(`${boundary}::before,\n${boundary}::after`, [
56103
+ "content: '';",
56104
+ "position: absolute;",
56105
+ "left: 0;",
56106
+ "right: 0;",
56107
+ "height: var(--boundary-height);",
56108
+ "pointer-events: none;"
56109
+ ]),
56110
+ rule$1(`${boundary}::before`, ["z-index: 0;", "background: var(--boundary-band-paint, transparent);"]),
56111
+ rule$1(`${boundary}::after`, ["z-index: 1;"]),
56112
+ ...placementCss(boundary),
56113
+ ...transitionCatalogueEntries.flatMap((entry) => treatmentCss(boundary, entry))
56114
+ ].join("\n\n");
56115
+ }
56116
+ function placementCss(boundary) {
56117
+ return Object.keys(placementRules).flatMap((placement) => {
56118
+ const placed = `${boundary}[data-boundary-placement='${placement}']`;
56119
+ const rules = placementRules[placement];
56120
+ return [rule$1(`${placed}::after`, rules.shape), rules.band ? rule$1(`${placed}::before`, rules.band) : rule$1(`${placed}::before`, ["content: none;"])];
56121
+ });
56122
+ }
56123
+ function treatmentCss(boundary, entry) {
56124
+ const selected = `${boundary}[data-boundary-treatment='${entry.intent}']`;
56125
+ return [rule$1(selected, [`--boundary-height: ${boundaryRuntimeMetricsForDecorativeTransition(entry.intent).heightCss};`]), rule$1(`${selected}::after`, shapeDeclarations(entry))];
56126
+ }
56127
+ function shapeDeclarations(entry) {
56128
+ switch (entry.shape.kind) {
56129
+ case "fade": return [
56130
+ "/* Fallback for browsers without color-mix(). */",
56131
+ "background: linear-gradient(to bottom, transparent 0%, var(--boundary-shape-paint) 100%);",
56132
+ "background: linear-gradient(to bottom, transparent 0%, color-mix(in srgb, var(--boundary-shape-paint) 16%, transparent) 34%, color-mix(in srgb, var(--boundary-shape-paint) 68%, transparent) 78%, var(--boundary-shape-paint) 100%);"
56133
+ ];
56134
+ case "wave": {
56135
+ const mask = waveMaskDataUrl(entry.shape.mask);
56136
+ return [
56137
+ "background: var(--boundary-shape-paint);",
56138
+ `mask-image: url("${mask}");`,
56139
+ "mask-repeat: no-repeat;",
56140
+ "mask-size: 100% 100%;",
56141
+ `-webkit-mask-image: url("${mask}");`,
56142
+ "-webkit-mask-repeat: no-repeat;",
56143
+ "-webkit-mask-size: 100% 100%;"
56144
+ ];
56145
+ }
56146
+ case "angle": return [
56147
+ "height: calc(var(--boundary-height) + 2px);",
56148
+ "background: var(--boundary-shape-paint);",
56149
+ `clip-path: ${anglePolygonCss(entry.shape.polygon)};`
56150
+ ];
56151
+ }
56152
+ }
56153
+ function rule$1(selector, declarations) {
56154
+ return `${selector} {\n ${declarations.join("\n ")}\n}`;
56155
+ }
56156
+ function svgDataUrl(svg) {
56157
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
56158
+ }
56159
+ //#endregion
55322
56160
  //#region ../theme-core/src/layout/generateLayoutCss.ts
55323
56161
  /**
55324
56162
  * Generate layout CSS for theme scope (background, text color)
@@ -55343,7 +56181,7 @@ function generateLayoutCss(options) {
55343
56181
  if (heroTypographyCss) cssChunks.push(heroTypographyCss);
55344
56182
  const bodyTextTypographyCss = generateBodyTextTypographyCss(themeId, theme);
55345
56183
  if (bodyTextTypographyCss) cssChunks.push(bodyTextTypographyCss);
55346
- cssChunks.push(generateSiteStyleTransitionCss(themeId));
56184
+ cssChunks.push(generateBoundaryCss(selector));
55347
56185
  return cssChunks.join("\n\n");
55348
56186
  }
55349
56187
  function toPaddingValue(preset) {
@@ -55354,193 +56192,6 @@ function toPaddingValue(preset) {
55354
56192
  relaxed: "1.5rem"
55355
56193
  }[preset];
55356
56194
  }
55357
- const classicWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z' fill='white'/></svg>");
55358
- const balancedWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z' fill='white'/></svg>");
55359
- const gentleWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z' fill='white'/></svg>");
55360
- const boldWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z' fill='white'/></svg>");
55361
- const driftWaveTransitionMask = svgDataUrl("<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z' fill='white'/></svg>");
55362
- function generateSiteStyleTransitionCss(themeId) {
55363
- const selector = `:where([data-theme-scope="${themeId}"])`;
55364
- const compactBoundaryHeight = requiredBoundaryHeightCss("wave-edge-gentle");
55365
- const mediumBoundaryHeight = requiredBoundaryHeightCss("wave-edge");
55366
- const largeBoundaryHeight = requiredBoundaryHeightCss("wave-edge-bold");
55367
- return `${selector} [data-style-group^='site-style-transition-'] {
55368
- position: relative;
55369
- z-index: 2;
55370
- --site-style-transition-from: transparent;
55371
- --site-style-transition-to: rgb(var(--tb-background));
55372
- }
55373
-
55374
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-'] {
55375
- position: relative;
55376
- z-index: 3;
55377
- height: 0;
55378
- pointer-events: none;
55379
- --site-style-boundary-height: ${mediumBoundaryHeight};
55380
- }
55381
-
55382
- ${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section']) {
55383
- padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));
55384
- }
55385
-
55386
- ${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section']) {
55387
- padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));
55388
- }
55389
-
55390
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::before,
55391
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::before,
55392
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::before {
55393
- content: '';
55394
- position: absolute;
55395
- left: 0;
55396
- right: 0;
55397
- top: 0;
55398
- transform: translateY(-50%);
55399
- z-index: 0;
55400
- height: var(--site-style-boundary-height);
55401
- background: var(--site-style-transition-from);
55402
- pointer-events: none;
55403
- }
55404
-
55405
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after,
55406
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after,
55407
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
55408
- content: '';
55409
- position: absolute;
55410
- left: 0;
55411
- right: 0;
55412
- top: 0;
55413
- transform: translateY(-50%);
55414
- z-index: 1;
55415
- height: var(--site-style-boundary-height);
55416
- pointer-events: none;
55417
- }
55418
-
55419
- ${selector} [data-style-group*='-from-base-'] {
55420
- --site-style-transition-from: rgb(var(--tb-background));
55421
- }
55422
-
55423
- ${selector} [data-style-group*='-from-muted-band-'] {
55424
- --site-style-transition-from: rgb(var(--tb-muted));
55425
- }
55426
-
55427
- ${selector} [data-style-group*='-from-accent-band-'] {
55428
- --site-style-transition-from: rgb(var(--tb-accent));
55429
- }
55430
-
55431
- ${selector} [data-style-group*='-from-brand-depth-band-'] {
55432
- --site-style-transition-from: rgb(var(--tb-primary-800));
55433
- }
55434
-
55435
- ${selector} [data-style-group$='-to-base'],
55436
- ${selector} [data-style-group$='-to-hero-image'] {
55437
- --site-style-transition-to: rgb(var(--tb-background));
55438
- }
55439
-
55440
- ${selector} [data-style-group$='-to-muted-band'] {
55441
- --site-style-transition-to: rgb(var(--tb-muted));
55442
- }
55443
-
55444
- ${selector} [data-style-group$='-to-accent-band'] {
55445
- --site-style-transition-to: rgb(var(--tb-accent));
55446
- }
55447
-
55448
- ${selector} [data-style-group$='-to-brand-depth-band'] {
55449
- --site-style-transition-to: rgb(var(--tb-primary-800));
55450
- }
55451
-
55452
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
55453
- /* Fallback for browsers without color-mix(). */
55454
- background: linear-gradient(
55455
- to bottom,
55456
- transparent 0%,
55457
- var(--site-style-transition-to) 100%
55458
- );
55459
- background: linear-gradient(
55460
- to bottom,
55461
- transparent 0%,
55462
- color-mix(in srgb, var(--site-style-transition-to) 16%, transparent) 34%,
55463
- color-mix(in srgb, var(--site-style-transition-to) 68%, transparent) 78%,
55464
- var(--site-style-transition-to) 100%
55465
- );
55466
- }
55467
-
55468
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-long-'] {
55469
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-long")};
55470
- }
55471
-
55472
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-expansive-'] {
55473
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-expansive")};
55474
- }
55475
-
55476
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after {
55477
- height: calc(var(--site-style-boundary-height) + 2px);
55478
- background: var(--site-style-transition-to);
55479
- clip-path: polygon(0 42%, 100% 8%, 100% 100%, 0 100%);
55480
- }
55481
-
55482
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-'] {
55483
- --site-style-boundary-height: ${compactBoundaryHeight};
55484
- }
55485
-
55486
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-']::after {
55487
- clip-path: polygon(0 34%, 100% 18%, 100% 100%, 0 100%);
55488
- }
55489
-
55490
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-'] {
55491
- --site-style-boundary-height: ${largeBoundaryHeight};
55492
- }
55493
-
55494
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-']::after {
55495
- clip-path: polygon(0 54%, 100% 0%, 100% 100%, 0 100%);
55496
- }
55497
-
55498
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after {
55499
- background: var(--site-style-transition-to);
55500
- mask-image: url("${classicWaveTransitionMask}");
55501
- mask-repeat: no-repeat;
55502
- mask-size: 100% 100%;
55503
- -webkit-mask-image: url("${classicWaveTransitionMask}");
55504
- -webkit-mask-repeat: no-repeat;
55505
- -webkit-mask-size: 100% 100%;
55506
- }
55507
-
55508
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-balanced-']::after {
55509
- mask-image: url("${balancedWaveTransitionMask}");
55510
- -webkit-mask-image: url("${balancedWaveTransitionMask}");
55511
- }
55512
-
55513
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-'] {
55514
- --site-style-boundary-height: ${compactBoundaryHeight};
55515
- }
55516
-
55517
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-']::after {
55518
- mask-image: url("${gentleWaveTransitionMask}");
55519
- -webkit-mask-image: url("${gentleWaveTransitionMask}");
55520
- }
55521
-
55522
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-'] {
55523
- --site-style-boundary-height: ${largeBoundaryHeight};
55524
- }
55525
-
55526
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-']::after {
55527
- mask-image: url("${boldWaveTransitionMask}");
55528
- -webkit-mask-image: url("${boldWaveTransitionMask}");
55529
- }
55530
-
55531
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-drift-']::after {
55532
- mask-image: url("${driftWaveTransitionMask}");
55533
- -webkit-mask-image: url("${driftWaveTransitionMask}");
55534
- }`;
55535
- }
55536
- function requiredBoundaryHeightCss(transition) {
55537
- const metrics = boundaryRuntimeMetricsForTransition(transition);
55538
- if (!metrics) throw new Error(`Missing boundary runtime metrics for ${transition}`);
55539
- return metrics.heightCss;
55540
- }
55541
- function svgDataUrl(svg) {
55542
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
55543
- }
55544
56195
  function generateHeroTypographyCss(themeId, theme) {
55545
56196
  const typography = theme.hero?.typography;
55546
56197
  if (!typography) return "";
@@ -55559,24 +56210,23 @@ function generateHeroTypographyCss(themeId, theme) {
55559
56210
  if (microHeadlineRules.length > 0) chunks.push(`${microHeadlineSelector} {\n ${microHeadlineRules.join("\n ")}\n}`);
55560
56211
  const microSubheadlineRules = buildTypographyRules("subheadline", typography.microSubheadlineSize, typography.microSubheadlineLineHeight);
55561
56212
  if (microSubheadlineRules.length > 0) chunks.push(`${microSubheadlineSelector} {\n ${microSubheadlineRules.join("\n ")}\n}`);
55562
- if (typography.responsive?.tablet) {
55563
- const tabletHeadlineRules = buildTypographyRules("headline", typography.responsive.tablet.headlineSize, typography.responsive.tablet.headlineLineHeight);
55564
- const tabletSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.tablet.subheadlineSize, typography.responsive.tablet.subheadlineLineHeight);
55565
- const tabletChunks = [];
55566
- if (tabletHeadlineRules.length > 0) tabletChunks.push(`${headlineSelector} {\n ${tabletHeadlineRules.join("\n ")}\n }`);
55567
- if (tabletSubheadlineRules.length > 0) tabletChunks.push(`${subheadlineSelector} {\n ${tabletSubheadlineRules.join("\n ")}\n }`);
55568
- if (tabletChunks.length > 0) chunks.push(`@media ${MQ_TABLET_ONLY} {\n ${tabletChunks.join("\n\n ")}\n}`);
55569
- }
55570
- if (typography.responsive?.mobile) {
55571
- const mobileHeadlineRules = buildTypographyRules("headline", typography.responsive.mobile.headlineSize, typography.responsive.mobile.headlineLineHeight);
55572
- const mobileSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.mobile.subheadlineSize, typography.responsive.mobile.subheadlineLineHeight);
55573
- const mobileChunks = [];
55574
- if (mobileHeadlineRules.length > 0) mobileChunks.push(`${headlineSelector} {\n ${mobileHeadlineRules.join("\n ")}\n }`);
55575
- if (mobileSubheadlineRules.length > 0) mobileChunks.push(`${subheadlineSelector} {\n ${mobileSubheadlineRules.join("\n ")}\n }`);
55576
- if (mobileChunks.length > 0) chunks.push(`@media ${MQ_SM_DOWN} {\n ${mobileChunks.join("\n\n ")}\n}`);
55577
- }
56213
+ const selectors = {
56214
+ headline: headlineSelector,
56215
+ subheadline: subheadlineSelector
56216
+ };
56217
+ const responsiveChunks = [responsiveHeroTypographyCss(MQ_TABLET_ONLY, selectors, typography.responsive?.tablet), responsiveHeroTypographyCss(MQ_SM_DOWN, selectors, typography.responsive?.mobile)];
56218
+ for (const chunk of responsiveChunks) if (chunk) chunks.push(chunk);
55578
56219
  return chunks.join("\n\n");
55579
56220
  }
56221
+ function responsiveHeroTypographyCss(media, selectors, overrides) {
56222
+ if (!overrides) return null;
56223
+ const headlineRules = buildTypographyRules("headline", overrides.headlineSize, overrides.headlineLineHeight);
56224
+ const subheadlineRules = buildTypographyRules("subheadline", overrides.subheadlineSize, overrides.subheadlineLineHeight);
56225
+ const mediaChunks = [];
56226
+ if (headlineRules.length > 0) mediaChunks.push(`${selectors.headline} {\n ${headlineRules.join("\n ")}\n }`);
56227
+ if (subheadlineRules.length > 0) mediaChunks.push(`${selectors.subheadline} {\n ${subheadlineRules.join("\n ")}\n }`);
56228
+ return mediaChunks.length > 0 ? `@media ${media} {\n ${mediaChunks.join("\n\n ")}\n}` : null;
56229
+ }
55580
56230
  function buildTypographyRules(role, size, lineHeight) {
55581
56231
  const rules = [];
55582
56232
  if (size) rules.push(`font-size: ${toHeroSize(role, size)};`);
@@ -57555,70 +58205,171 @@ function requireThemeResources(resources, blockKind) {
57555
58205
  return resources;
57556
58206
  }
57557
58207
  //#endregion
57558
- //#region ../blocks/src/pageRendererBoundaries.tsx
57559
- const boundaryKey = (previousBlockId, nextBlockId) => `${previousBlockId}\u0000${nextBlockId}`;
57560
- function isSiteStyleTransitionStyleGroup(value) {
57561
- return value?.startsWith("site-style-transition-") === true;
58208
+ //#region ../blocks/src/pageRenderJoins.ts
58209
+ /**
58210
+ * Re-points joins across omitted blocks. Pure over joins.
58211
+ *
58212
+ * Joins are keyed by adjacent block-id pairs, so dropping B from [A, B, C]
58213
+ * removes the A→C join as well as B's own, because no A→C join was ever
58214
+ * resolved. For each surviving pair the original join is kept when the pair
58215
+ * was already adjacent. Across a gap, the treatment comes from the join
58216
+ * entering the omitted run (A→first omitted), falling back to the one leaving
58217
+ * it (last omitted→C). Nothing visual is carried: the survivors are composed
58218
+ * from their own content afterwards.
58219
+ *
58220
+ * `survivors` are compared by identity against `originalBlocks`.
58221
+ */
58222
+ function retargetJoinsAcrossOmissions(input) {
58223
+ const { joins, originalBlocks, survivingBlocks } = input;
58224
+ if (joins.length === 0) return joins;
58225
+ if (originalBlocks.every((block) => survivingBlocks.has(block))) return joins;
58226
+ const byPair = joinsByPair(joins);
58227
+ const retargeted = [];
58228
+ let previousSurvivor = null;
58229
+ let run = [];
58230
+ for (const block of originalBlocks) {
58231
+ if (!survivingBlocks.has(block)) {
58232
+ run.push(block);
58233
+ continue;
58234
+ }
58235
+ if (previousSurvivor?.id && block.id) {
58236
+ const join = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : joinAcrossRun(byPair, previousSurvivor.id, block.id, run);
58237
+ if (join) retargeted.push(join);
58238
+ }
58239
+ previousSurvivor = block;
58240
+ run = [];
58241
+ }
58242
+ return retargeted;
57562
58243
  }
57563
- function boundaryMapForAdjacentBlockIds(boundaries) {
57564
- return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
58244
+ function joinAcrossRun(byPair, previousBlockId, nextBlockId, run) {
58245
+ const firstOmittedId = run[0]?.id;
58246
+ const lastOmittedId = run[run.length - 1]?.id;
58247
+ const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
58248
+ const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
58249
+ const donor = entering ?? exiting;
58250
+ return donor ? {
58251
+ previousBlockId,
58252
+ nextBlockId,
58253
+ treatment: donor.treatment
58254
+ } : void 0;
57565
58255
  }
57566
- function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
57567
- const previousBlockId = previousBlock?.id ?? null;
57568
- const nextBlockId = nextBlock?.id ?? null;
57569
- if (!previousBlockId || !nextBlockId) return null;
57570
- return boundaries.get(boundaryKey(previousBlockId, nextBlockId)) ?? null;
58256
+ /**
58257
+ * The single composition site. For every adjacent pair of survivors with ids,
58258
+ * finds its join and composes it from both survivors' final content: what
58259
+ * each paints with and the frame each draws.
58260
+ */
58261
+ function composePageJoins(input) {
58262
+ const byPair = joinsByPair(input.joins);
58263
+ return input.survivors.flatMap((previous, index) => {
58264
+ const next = input.survivors[index + 1];
58265
+ if (!next || !previous.id || !next.id) return [];
58266
+ const join = byPair.get(pairKey(previous.id, next.id));
58267
+ return [{
58268
+ previousBlockId: previous.id,
58269
+ nextBlockId: next.id,
58270
+ join: join ? composeJoin(join, previous, next) : null
58271
+ }];
58272
+ });
58273
+ }
58274
+ function composeJoin(join, previous, next) {
58275
+ return {
58276
+ boundaryId: pairKey(join.previousBlockId, join.nextBlockId),
58277
+ previousBlockId: join.previousBlockId,
58278
+ nextBlockId: join.nextBlockId,
58279
+ treatment: join.treatment,
58280
+ outcome: composeBoundary(sectionFactsForContent(previous.content), sectionFactsForContent(next.content))
58281
+ };
58282
+ }
58283
+ /** Placement, reserves and paint for a join that draws; null otherwise. */
58284
+ function renderedJoinLayout(join) {
58285
+ if (!join || join.outcome.kind !== "shown") return null;
58286
+ return boundaryLayout(join.treatment, join.outcome.composition);
58287
+ }
58288
+ /** Joins with a missing id on either side cannot sit between two blocks. */
58289
+ function joinsByPair(joins) {
58290
+ return new Map(joins.flatMap((join) => join.previousBlockId && join.nextBlockId ? [[pairKey(join.previousBlockId, join.nextBlockId), join]] : []));
57571
58291
  }
57572
- function renderBoundaryAfterBlock(input) {
57573
- const previousBlockId = input.block.id;
57574
- const nextBlockId = input.nextBlock?.id ?? null;
58292
+ /** Slot lookup key for an adjacent pair, in the one boundary-id scheme. */
58293
+ function joinSlotKey(previousBlockId, nextBlockId) {
58294
+ return pairKey(previousBlockId, nextBlockId);
58295
+ }
58296
+ function pairKey(previousBlockId, nextBlockId) {
58297
+ return makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId));
58298
+ }
58299
+ //#endregion
58300
+ //#region ../blocks/src/pageRendererBoundaries.tsx
58301
+ function joinSlotsByPair(slots) {
58302
+ return new Map(slots.map((slot) => [joinSlotKey(slot.previousBlockId, slot.nextBlockId), slot]));
58303
+ }
58304
+ function joinSlotBetween(previousBlockId, nextBlockId, slots) {
57575
58305
  if (!previousBlockId || !nextBlockId) return null;
57576
- const boundary = input.boundaryByAdjacentBlockIds.get(boundaryKey(previousBlockId, nextBlockId));
57577
- if (!boundary) return null;
57578
- const rendered = /* @__PURE__ */ jsx("div", {
58306
+ return slots.get(joinSlotKey(previousBlockId, nextBlockId)) ?? null;
58307
+ }
58308
+ /**
58309
+ * The markup at one join: the painted boundary element when the join draws,
58310
+ * then whatever `renderJoin` adds. Null when there is nothing to render.
58311
+ */
58312
+ function renderJoinSlot(input) {
58313
+ const { slot } = input;
58314
+ if (!slot) return null;
58315
+ const layout = renderedJoinLayout(slot.join);
58316
+ const painted = slot.join && layout ? /* @__PURE__ */ jsx("div", {
57579
58317
  "aria-hidden": "true",
57580
58318
  "data-block-boundary": "",
57581
- "data-boundary-id": boundary.boundaryId,
57582
- "data-boundary-previous-block-id": boundary.previousBlockId,
57583
- "data-boundary-next-block-id": boundary.nextBlockId,
57584
- "data-style-group": boundary.styleGroup,
57585
- style: boundaryCssVarsStyle(boundary)
57586
- }, `${input.blockKey}:boundary`);
57587
- return input.wrapBoundary ? input.wrapBoundary(boundary, rendered) : rendered;
57588
- }
57589
- function boundaryCssVarsStyle(boundary) {
57590
- return {
57591
- "--site-style-transition-from": boundary.cssVars.from,
57592
- "--site-style-transition-to": boundary.cssVars.to
57593
- };
58319
+ "data-boundary-id": slot.join.boundaryId,
58320
+ "data-boundary-previous-block-id": slot.previousBlockId,
58321
+ "data-boundary-next-block-id": slot.nextBlockId,
58322
+ "data-boundary-treatment": slot.join.treatment,
58323
+ "data-boundary-placement": layout.placement,
58324
+ style: boundaryPaintStyle(layout.paint)
58325
+ }, `${input.blockKey}:boundary`) : null;
58326
+ const extra = input.renderJoin?.({
58327
+ previousBlockId: slot.previousBlockId,
58328
+ nextBlockId: slot.nextBlockId,
58329
+ join: slot.join
58330
+ });
58331
+ if (!painted && (extra === null || extra === void 0)) return null;
58332
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [painted, extra] }, `${input.blockKey}:join`);
57594
58333
  }
57595
- function boundaryReserveCssVarsStyle(input) {
57596
- const before = input.before?.metrics.reserveCss ?? null;
57597
- const after = input.after?.metrics.reserveCss ?? null;
57598
- if (!before && !after) return void 0;
57599
- return {
57600
- ...before ? { "--site-style-boundary-before-reserve": before } : {},
57601
- ...after ? { "--site-style-boundary-after-reserve": after } : {}
57602
- };
58334
+ /**
58335
+ * The one render-edge place a theme token becomes CSS.
58336
+ */
58337
+ function themeTokenCss(token) {
58338
+ return `rgb(var(--tb-${token}))`;
57603
58339
  }
57604
- function transitionCssVarsStyle(content) {
57605
- const transitionStyles = isObjectRecord$1(content._transitionStyles) ? content._transitionStyles : null;
57606
- const cssVars = isObjectRecord$1(transitionStyles?.cssVars) ? transitionStyles.cssVars : null;
57607
- const from = typeof cssVars?.from === "string" ? cssVars.from : null;
57608
- const to = typeof cssVars?.to === "string" ? cssVars.to : null;
57609
- if (!from && !to) return void 0;
58340
+ function boundaryPaintStyle(paint) {
57610
58341
  return {
57611
- ...from ? { "--site-style-transition-from": from } : {},
57612
- ...to ? { "--site-style-transition-to": to } : {}
58342
+ "--boundary-shape-paint": themeTokenCss(paint.shape),
58343
+ ...paint.band ? { "--boundary-band-paint": themeTokenCss(paint.band) } : {}
57613
58344
  };
57614
58345
  }
57615
- function mergeStyleObjects(left, right) {
57616
- if (!left) return right;
57617
- if (!right) return left;
57618
- return {
57619
- ...left,
57620
- ...right
58346
+ /**
58347
+ * Reserve attributes and variables for a block wrapper, from the joins on
58348
+ * either side. A join's `before` reserve sits inside its previous section, so
58349
+ * it pads the bottom of the block above the join; its `after` reserve pads the
58350
+ * top of the block below. Joins that do not draw reserve nothing.
58351
+ */
58352
+ function blockBoundaryReserves(input) {
58353
+ const top = reserveOnSide(input.before, "after");
58354
+ const bottom = reserveOnSide(input.after, "before");
58355
+ const style = {
58356
+ ...top ? { "--site-style-boundary-before-reserve": top.length } : {},
58357
+ ...bottom ? { "--site-style-boundary-after-reserve": bottom.length } : {}
57621
58358
  };
58359
+ return {
58360
+ topPlacement: top?.placement,
58361
+ bottomPlacement: bottom?.placement,
58362
+ style: top || bottom ? style : void 0
58363
+ };
58364
+ }
58365
+ /** The reserve one join needs on one side of itself, if it draws at all. */
58366
+ function reserveOnSide(slot, side) {
58367
+ const layout = renderedJoinLayout(slot?.join ?? null);
58368
+ const length = layout?.reserves[side] ?? null;
58369
+ return layout && length ? {
58370
+ length,
58371
+ placement: layout.placement
58372
+ } : null;
57622
58373
  }
57623
58374
  function pageBlockPosition(index, blockCount) {
57624
58375
  if (blockCount <= 1) return "only";
@@ -62279,6 +63030,15 @@ const appointmentServicesBlockDefinition = {
62279
63030
  required: false,
62280
63031
  ui: { intent: "content" }
62281
63032
  },
63033
+ createSiteOptionSelectField({
63034
+ id: "categoryId",
63035
+ label: "Category",
63036
+ description: "Show one category (with its heading and intro), or leave it on All categories to show everything grouped.",
63037
+ required: false,
63038
+ multiple: false,
63039
+ optionSource: "appointmentServiceCategories",
63040
+ ui: { intent: "content" }
63041
+ }),
62282
63042
  sectionStylesField({
62283
63043
  id: "_sectionStyles",
62284
63044
  label: "Section styles",
@@ -62313,6 +63073,7 @@ const appointmentServicesBlockDefinition = {
62313
63073
  richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
62314
63074
  el("appointment-services", {
62315
63075
  sectionHeading: { $bind: { from: "content.heading" } },
63076
+ categoryId: { $bind: { from: "content.categoryId" } },
62316
63077
  services: { $bind: { from: "data.services" } },
62317
63078
  pathname: { $bind: { from: "$root.pathname" } },
62318
63079
  servicePathPrefix: { $bind: { from: "$root.appointmentServicePathPrefix" } }
@@ -66958,11 +67719,10 @@ function buildPageRenderPlan(input) {
66958
67719
  return {
66959
67720
  entries,
66960
67721
  omitted,
66961
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
66962
- boundaries: input.runtimeBoundaries,
67722
+ runtimeJoins: retargetJoinsAcrossOmissions({
67723
+ joins: input.runtimeJoins ?? [],
66963
67724
  originalBlocks: input.page.blocks,
66964
- survivingBlocks: new Set(entries.map((entry) => entry.block)),
66965
- previewStage: input.previewStage
67725
+ survivingBlocks: new Set(entries.map((entry) => entry.block))
66966
67726
  })
66967
67727
  };
66968
67728
  }
@@ -67064,86 +67824,15 @@ function getBlockOverride(blockKind, overrides) {
67064
67824
  * For draft/preview: draftContent takes priority over content.
67065
67825
  * For published: content is the source of truth.
67066
67826
  */
67827
+ /**
67828
+ * The content a block renders with. Every render path (public, preview,
67829
+ * templates, SDK) chooses content here, so this is also where a stale
67830
+ * `site-style-transition-*` styleGroup is dropped; authored values survive.
67831
+ */
67067
67832
  function getRenderableContent(block, previewStage = "published") {
67068
67833
  const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
67069
- return isObjectRecord$1(source) ? source : {};
67070
- }
67071
- /**
67072
- * Re-points section boundaries across omitted blocks.
67073
- *
67074
- * Boundaries are keyed by adjacent block-id *pairs*, so dropping B from
67075
- * [A, B, C] does not merely remove B's boundaries — it removes the boundary
67076
- * between the survivors, because no `A→C` key was ever compiled.
67077
- *
67078
- * Per decision D2 the treatment for a merged gap comes from the boundary
67079
- * entering the omitted run (`A→firstOmitted`), falling back to the one leaving
67080
- * it (`lastOmitted→C`). The destination surface is a separate question: only
67081
- * the exiting boundary knows what C looks like, so the merged boundary ends on
67082
- * that endpoint rather than on the omitted block's. In the exiting-only
67083
- * fallback the origin remains an approximation: `from` is the omitted block's
67084
- * surface, because no compiled boundary carries A's. The hosted path no longer
67085
- * needs this at all (site-renderer admits blocks before compiling boundaries);
67086
- * it remains the safety net for an SDK whose compiled boundaries predate an
67087
- * omission.
67088
- */
67089
- function retargetBoundariesAcrossOmissions(input) {
67090
- const { boundaries, originalBlocks, survivingBlocks, previewStage } = input;
67091
- if (boundaries === void 0) return void 0;
67092
- if (boundaries.length === 0) return boundaries;
67093
- if (!originalBlocks.some((block) => !survivingBlocks.has(block))) return boundaries;
67094
- const byPair = new Map(boundaries.map((boundary) => [pairKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
67095
- const retargeted = [];
67096
- let previousSurvivor = null;
67097
- let run = [];
67098
- for (const block of originalBlocks) {
67099
- if (!survivingBlocks.has(block)) {
67100
- run.push(block);
67101
- continue;
67102
- }
67103
- if (previousSurvivor?.id && block.id) {
67104
- const boundary = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : mergedBoundary({
67105
- byPair,
67106
- previousBlock: previousSurvivor,
67107
- previousBlockId: previousSurvivor.id,
67108
- nextBlock: block,
67109
- nextBlockId: block.id,
67110
- run,
67111
- previewStage
67112
- });
67113
- if (boundary) retargeted.push(boundary);
67114
- }
67115
- previousSurvivor = block;
67116
- run = [];
67117
- }
67118
- return retargeted;
67834
+ return isObjectRecord$1(source) ? stripStaleTransitionStyleGroup(source) : {};
67119
67835
  }
67120
- function mergedBoundary(input) {
67121
- const { byPair, previousBlock, previousBlockId, nextBlock, nextBlockId, run, previewStage } = input;
67122
- const firstOmittedId = run[0]?.id;
67123
- const lastOmittedId = run[run.length - 1]?.id;
67124
- const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
67125
- const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
67126
- const donor = entering ?? exiting;
67127
- if (!donor) return void 0;
67128
- const transition = compileTransitionRuntime({
67129
- treatment: donor.treatment,
67130
- from: donor.from,
67131
- to: exiting?.to ?? donor.to
67132
- });
67133
- return retargetCompiledBoundaryRuntimeForContentAdjacency({
67134
- ...donor,
67135
- boundaryId: makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId)),
67136
- previousBlockId,
67137
- nextBlockId,
67138
- from: transition.from,
67139
- to: transition.to,
67140
- cssVars: transition.cssVars
67141
- }, {
67142
- currentContent: getRenderableContent(previousBlock, previewStage),
67143
- nextContent: getRenderableContent(nextBlock, previewStage)
67144
- });
67145
- }
67146
- const pairKey = boundaryKey;
67147
67836
  //#endregion
67148
67837
  //#region ../blocks/src/system/manifest/hydrateLinks.ts
67149
67838
  /**
@@ -67178,7 +67867,7 @@ function hydrateResolvedBookingConsentValue(value, context) {
67178
67867
  const hydratedItems = value.map((item) => hydrateResolvedBookingConsentValue(item, context));
67179
67868
  return hydratedItems.every((item, index) => item === value[index]) ? value : hydratedItems;
67180
67869
  }
67181
- if (!isRecord$26(value)) return value;
67870
+ if (!isRecord$27(value)) return value;
67182
67871
  if (isBookingConsentCarrier(value)) {
67183
67872
  const agreement = hydrateValue(value.agreement, context);
67184
67873
  return agreement === value.agreement ? value : {
@@ -67198,7 +67887,7 @@ function hydrateValue(value, context) {
67198
67887
  const resolvedItems = value.map((item) => hydrateValue(item, context));
67199
67888
  return resolvedItems.every((item, index) => item === value[index]) ? value : resolvedItems;
67200
67889
  }
67201
- if (isRecord$26(value)) {
67890
+ if (isRecord$27(value)) {
67202
67891
  if (isInternalLink$1(value)) return hydrateInternalLink(value, context.routes);
67203
67892
  if (isPageLink$1(value)) return hydratePageLink(value, context);
67204
67893
  if (isEntryLink$1(value)) return hydrateEntryLink(value, context);
@@ -67263,7 +67952,7 @@ function hydrateEntryLink(link, context) {
67263
67952
  //#region ../blocks/src/system/manifest/resolveBlockData.ts
67264
67953
  function resolveBlockData$1(blockId, dataLoaders, dataContext, routes) {
67265
67954
  const raw = blockId ? dataContext?.resolvedData?.[blockId] : void 0;
67266
- if (isRecord$26(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
67955
+ if (isRecord$27(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
67267
67956
  return dataLoaders && typeof dataLoaders === "object" && Object.keys(dataLoaders).length > 0 ? {} : void 0;
67268
67957
  }
67269
67958
  //#endregion
@@ -67298,23 +67987,44 @@ function preparePageRender(input) {
67298
67987
  prepared
67299
67988
  });
67300
67989
  }
67301
- if (constructionFailures.length === 0 || input.audience === "authoring") return {
67302
- entries,
67303
- constructionFailures,
67304
- runtimeBoundaries: input.plan.runtimeBoundaries
67305
- };
67306
- const survivors = entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
67990
+ const survivors = input.audience === "authoring" ? entries : entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
67307
67991
  return {
67308
67992
  entries: survivors,
67309
67993
  constructionFailures,
67310
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
67311
- boundaries: input.plan.runtimeBoundaries,
67312
- originalBlocks: input.plan.entries.map((planned) => planned.block),
67313
- survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block)),
67314
- previewStage: input.previewStage
67315
- })
67994
+ joins: composeSurvivorJoins(input.plan, survivors, input.themeResources)
67316
67995
  };
67317
67996
  }
67997
+ /**
67998
+ * The only place joins are composed. Plan-time omissions were already
67999
+ * re-pointed by the plan; construction failures are re-pointed here by the
68000
+ * same primitive, so `[A, B, C]` losing B still gets an A→C join. Each
68001
+ * survivor is then classified from its final content.
68002
+ */
68003
+ function composeSurvivorJoins(plan, survivors, themeResources) {
68004
+ return composePageJoins({
68005
+ joins: retargetJoinsAcrossOmissions({
68006
+ joins: plan.runtimeJoins,
68007
+ originalBlocks: plan.entries.map((planned) => planned.block),
68008
+ survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block))
68009
+ }),
68010
+ survivors: survivors.map(({ entry }) => ({
68011
+ id: entry.block.id,
68012
+ kind: entry.block.kind,
68013
+ content: boundaryClassificationContent(entry, themeResources)
68014
+ }))
68015
+ });
68016
+ }
68017
+ /**
68018
+ * The content a join is classified from must be the content that paints the
68019
+ * section. The default block renderer layers the theme's per-block style
68020
+ * defaults (`theme.blockOverrides[kind].styles`, background included) under
68021
+ * the block's own `_sectionStyles`, so classify that same projection. An
68022
+ * override component renders its own markup from the unmerged content.
68023
+ */
68024
+ function boundaryClassificationContent(entry, themeResources) {
68025
+ if (entry.kind !== "renderable" || entry.override) return entry.renderableContent;
68026
+ return mergeContentWithBlockStyleOverrides(entry.renderableContent, themeResources.kind === "available" ? themeResources.config : void 0, entry.block.kind);
68027
+ }
67318
68028
  function prepareEntry(entry, input) {
67319
68029
  if (entry.kind !== "renderable") return { kind: "plan-decided" };
67320
68030
  const draftContent = previewableEditorContent(entry);
@@ -67503,7 +68213,7 @@ function PageRenderer$1(props) {
67503
68213
  });
67504
68214
  }
67505
68215
  function PageRendererCore(props) {
67506
- const { page, audience, runtimeBoundaries, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
68216
+ const { page, audience, runtimeJoins, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
67507
68217
  const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
67508
68218
  dataContext?.imageResolveContext,
67509
68219
  dataContext?.mediaAssets,
@@ -67519,7 +68229,7 @@ function PageRendererCore(props) {
67519
68229
  blocks: []
67520
68230
  },
67521
68231
  audience,
67522
- runtimeBoundaries,
68232
+ runtimeJoins,
67523
68233
  previewStage,
67524
68234
  sdkConfig,
67525
68235
  customBlocks,
@@ -67527,7 +68237,7 @@ function PageRendererCore(props) {
67527
68237
  }), [
67528
68238
  page,
67529
68239
  audience,
67530
- runtimeBoundaries,
68240
+ runtimeJoins,
67531
68241
  previewStage,
67532
68242
  sdkConfig,
67533
68243
  customBlocks,
@@ -67544,19 +68254,16 @@ function PageRendererCore(props) {
67544
68254
  dataContext,
67545
68255
  routeMap,
67546
68256
  themeResources,
67547
- imageResolveContext,
67548
- previewStage
68257
+ imageResolveContext
67549
68258
  }), [
67550
68259
  plan,
67551
68260
  audience,
67552
68261
  dataContext,
67553
68262
  routeMap,
67554
68263
  themeResources,
67555
- imageResolveContext,
67556
- previewStage
68264
+ imageResolveContext
67557
68265
  ]);
67558
- const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(preparation.runtimeBoundaries ?? []), [preparation.runtimeBoundaries]);
67559
- const usesRuntimeBoundaries = preparation.runtimeBoundaries !== void 0;
68266
+ const joinSlots = React$1.useMemo(() => joinSlotsByPair(preparation.joins), [preparation.joins]);
67560
68267
  reportAndReplayOmissions({
67561
68268
  plan,
67562
68269
  preparation,
@@ -67574,10 +68281,9 @@ function PageRendererCore(props) {
67574
68281
  themeResources,
67575
68282
  imageResolveContext,
67576
68283
  resolvedRegistry,
67577
- boundaryByAdjacentBlockIds,
67578
- usesRuntimeBoundaries,
68284
+ joinSlots,
67579
68285
  wrapBlock: props.wrapBlock,
67580
- wrapBoundary: props.wrapBoundary
68286
+ renderJoin: props.renderJoin
67581
68287
  })) });
67582
68288
  }
67583
68289
  /**
@@ -67666,27 +68372,25 @@ function requireEntryBlockOverride(entry, themeResources) {
67666
68372
  }
67667
68373
  /**
67668
68374
  * Renders one planned block: its wrapper, style-group data attributes,
67669
- * boundary reserve space and any boundary or transition sibling that follows.
68375
+ * boundary reserve space and the join markup that follows it.
67670
68376
  *
67671
68377
  * Extracted from PageRendererCore so the component stays about wiring the plan
67672
68378
  * rather than also owning per-block markup.
67673
68379
  */
67674
68380
  function renderPlanEntry(input) {
67675
- const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, boundaryByAdjacentBlockIds, usesRuntimeBoundaries, wrapBlock, wrapBoundary } = input;
68381
+ const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, joinSlots, wrapBlock, renderJoin } = input;
67676
68382
  const entry = input.entry.entry;
67677
68383
  const block = entry.block;
67678
68384
  const blockPosition = pageBlockPosition(index, entries.length);
67679
68385
  const blockKey = blockKeyFor(block, index);
67680
68386
  const previousBlock = entries[index - 1]?.entry.block ?? null;
67681
68387
  const nextBlock = entries[index + 1]?.entry.block ?? null;
67682
- const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
67683
- const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
67684
- const boundaryElement = renderBoundaryAfterBlock({
67685
- block,
67686
- nextBlock,
68388
+ const joinBefore = joinSlotBetween(previousBlock?.id, block.id, joinSlots);
68389
+ const joinAfter = joinSlotBetween(block.id, nextBlock?.id, joinSlots);
68390
+ const boundaryElement = renderJoinSlot({
68391
+ slot: joinAfter,
67687
68392
  blockKey,
67688
- boundaryByAdjacentBlockIds,
67689
- wrapBoundary
68393
+ renderJoin
67690
68394
  });
67691
68395
  const cleanKind = block.kind.replace(/^block\./, "");
67692
68396
  const managesOwnWrapper = entry.kind === "renderable" && entry.managesOwnWrapper;
@@ -67709,11 +68413,10 @@ function renderPlanEntry(input) {
67709
68413
  const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
67710
68414
  const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
67711
68415
  const blockComposition = readBlockComposition(renderableContent.blockComposition);
67712
- const transitionStyle = transitionCssVarsStyle(renderableContent);
67713
- const blockStyle = mergeStyleObjects(transitionStyle, boundaryReserveCssVarsStyle({
67714
- before: boundaryBefore,
67715
- after: boundaryAfter
67716
- }));
68416
+ const reserves = blockBoundaryReserves({
68417
+ before: joinBefore,
68418
+ after: joinAfter
68419
+ });
67717
68420
  const blockElement = /* @__PURE__ */ jsx("div", {
67718
68421
  className: wrapperClassName,
67719
68422
  "data-block": cleanKind,
@@ -67725,21 +68428,14 @@ function renderPlanEntry(input) {
67725
68428
  "data-block-composition": blockComposition?.treatment,
67726
68429
  "data-block-composition-kind": blockComposition?.kind,
67727
68430
  "data-page-block-position": blockPosition,
67728
- "data-site-style-boundary-before": boundaryBefore?.metrics.size,
67729
- "data-site-style-boundary-after": boundaryAfter?.metrics.size,
68431
+ "data-site-style-boundary-before": reserves.topPlacement,
68432
+ "data-site-style-boundary-after": reserves.bottomPlacement,
67730
68433
  "data-style-group": styleGroup,
67731
- style: blockStyle,
68434
+ style: reserves.style,
67732
68435
  children: /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps })
67733
68436
  }, blockKey);
67734
68437
  if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
67735
- if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
67736
- return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, /* @__PURE__ */ jsx("div", {
67737
- "aria-hidden": "true",
67738
- "data-block-transition": "",
67739
- "data-block-transition-for": block.id ?? blockKey,
67740
- "data-style-group": styleGroup,
67741
- style: transitionStyle
67742
- })] }, blockKey);
68438
+ return blockElement;
67743
68439
  }
67744
68440
  function resolvePageRendererImageResolveContext(dataContext) {
67745
68441
  const input = dataContext?.imageResolveContext;
@@ -82655,6 +83351,17 @@ function coerceToString(value) {
82655
83351
  return "";
82656
83352
  }
82657
83353
  /**
83354
+ * The appointment-services catalogue (issue 1403) pairs the services with the
83355
+ * site's ordered categories. Both are needed to group them, so that response
83356
+ * is passed through whole; any other `{ services }` wrapper is unwrapped.
83357
+ */
83358
+ function coerceServicesPayload(record) {
83359
+ return Array.isArray(record.services) && Array.isArray(record.categories) ? {
83360
+ services: record.services,
83361
+ categories: record.categories
83362
+ } : record.services;
83363
+ }
83364
+ /**
82658
83365
  * Extract data from API response.
82659
83366
  * Unwraps common response wrappers: { data }, { form }, { entries }, { services }, { teamMembers }
82660
83367
  *
@@ -82669,7 +83376,7 @@ function coercePayload(payload) {
82669
83376
  if ("data" in record && typeof record.data !== "function") return record.data;
82670
83377
  if ("form" in record && typeof record.form !== "function") return record.form;
82671
83378
  if ("entries" in record && typeof record.entries !== "function") return record.entries;
82672
- if ("services" in record && typeof record.services !== "function") return record.services;
83379
+ if ("services" in record && typeof record.services !== "function") return coerceServicesPayload(record);
82673
83380
  if ("teamMembers" in record && typeof record.teamMembers !== "function") return record.teamMembers;
82674
83381
  }
82675
83382
  return payload;
@@ -84032,7 +84739,12 @@ const appointmentSetupServiceDraftSchema = z$1.object({
84032
84739
  venue: appointmentSetupVenueChoiceSchema,
84033
84740
  timezone: z$1.string().trim().min(1).nullable(),
84034
84741
  payment: appointmentSetupPaymentDraftSchema,
84035
- assignment: appointmentSetupServiceAssignmentDraftSchema
84742
+ assignment: appointmentSetupServiceAssignmentDraftSchema,
84743
+ /**
84744
+ * The service's category, or null for "Other" (issue 1403). Omitted leaves
84745
+ * an existing service's category alone and puts a new one in "Other".
84746
+ */
84747
+ categoryId: z$1.string().uuid().nullable().optional()
84036
84748
  }).superRefine((service, ctx) => {
84037
84749
  if (service.payment.kind !== "free" && (service.priceAmount === null || service.priceAmount <= 0)) ctx.addIssue({
84038
84750
  code: "custom",
@@ -87338,7 +88050,7 @@ function applyBindings$1(bindings, context) {
87338
88050
  if (value !== void 0 && value !== null) setBindingValue(result, key, value);
87339
88051
  continue;
87340
88052
  }
87341
- if (isRecord$26(bindingSpec)) {
88053
+ if (isRecord$27(bindingSpec)) {
87342
88054
  const nested = applyBindings$1(bindingSpec, context);
87343
88055
  if (Object.keys(nested).length > 0) setBindingValue(result, key, nested);
87344
88056
  }
@@ -87379,7 +88091,7 @@ function mergeResolvedBindings(base, resolved) {
87379
88091
  const merged = { ...base };
87380
88092
  for (const [key, resolvedValue] of Object.entries(resolved)) {
87381
88093
  const baseValue = merged[key];
87382
- merged[key] = isRecord$26(baseValue) && isRecord$26(resolvedValue) ? mergeResolvedBindings(baseValue, resolvedValue) : resolvedValue;
88094
+ merged[key] = isRecord$27(baseValue) && isRecord$27(resolvedValue) ? mergeResolvedBindings(baseValue, resolvedValue) : resolvedValue;
87383
88095
  }
87384
88096
  return merged;
87385
88097
  }
@@ -87404,7 +88116,7 @@ function setBindingValue(target, path, value) {
87404
88116
  current[Number(segment)] = value;
87405
88117
  return;
87406
88118
  }
87407
- if (isRecord$26(current)) current[segment] = value;
88119
+ if (isRecord$27(current)) current[segment] = value;
87408
88120
  return;
87409
88121
  }
87410
88122
  const nextWantsArray = typeof nextSegment === "string" && isIndex(nextSegment);
@@ -87413,15 +88125,15 @@ function setBindingValue(target, path, value) {
87413
88125
  const existing = current[index];
87414
88126
  if (nextWantsArray) {
87415
88127
  if (!Array.isArray(existing)) current[index] = [];
87416
- } else if (!isRecord$26(existing)) current[index] = {};
88128
+ } else if (!isRecord$27(existing)) current[index] = {};
87417
88129
  current = current[index];
87418
88130
  continue;
87419
88131
  }
87420
- if (isRecord$26(current)) {
88132
+ if (isRecord$27(current)) {
87421
88133
  const existing = current[segment];
87422
88134
  if (nextWantsArray) {
87423
88135
  if (!Array.isArray(existing)) current[segment] = [];
87424
- } else if (!isRecord$26(existing)) current[segment] = {};
88136
+ } else if (!isRecord$27(existing)) current[segment] = {};
87425
88137
  current = current[segment];
87426
88138
  continue;
87427
88139
  }
@@ -87459,7 +88171,7 @@ function getPath$1(source, path) {
87459
88171
  const segments = path.split(".").map((segment) => segment.trim()).filter(Boolean);
87460
88172
  let current = source;
87461
88173
  for (const segment of segments) {
87462
- if (!isRecord$26(current)) return void 0;
88174
+ if (!isRecord$27(current)) return void 0;
87463
88175
  current = current[segment];
87464
88176
  }
87465
88177
  return current;
@@ -87492,7 +88204,7 @@ function textToInlineText(value) {
87492
88204
  function extractFirstParagraph$1(value) {
87493
88205
  if (!isRichTextDoc(value)) return null;
87494
88206
  for (const node of value.content) {
87495
- if (!isRecord$26(node)) continue;
88207
+ if (!isRecord$27(node)) continue;
87496
88208
  if (node.type !== "paragraph" || !Array.isArray(node.content)) continue;
87497
88209
  const text = node.content.filter((item) => item.type === "text").map((item) => typeof item.text === "string" ? item.text : "").join("").trim();
87498
88210
  if (text.length > 0) return text;
@@ -87512,7 +88224,7 @@ function formatDate$1(value, options) {
87512
88224
  * Type guard to check if a value is a valid binding descriptor.
87513
88225
  */
87514
88226
  function isBindingDescriptor$1(value) {
87515
- if (!isRecord$26(value) || typeof value.source !== "string") return false;
88227
+ if (!isRecord$27(value) || typeof value.source !== "string") return false;
87516
88228
  if (!BINDING_SOURCES$1.includes(value.source)) return false;
87517
88229
  switch (value.source) {
87518
88230
  case "field":
@@ -87527,7 +88239,7 @@ function isBindingDescriptor$1(value) {
87527
88239
  * Type guard to check if a value is binding-descriptor-like (can be normalized).
87528
88240
  */
87529
88241
  function isBindingDescriptorLike$1(value) {
87530
- return isBindingDescriptor$1(value) || typeof value === "string" || isRecord$26(value) && typeof value.source === "string" && typeof value.path === "string";
88242
+ return isBindingDescriptor$1(value) || typeof value === "string" || isRecord$27(value) && typeof value.source === "string" && typeof value.path === "string";
87531
88243
  }
87532
88244
  /**
87533
88245
  * Normalizes a binding input to a binding descriptor.
@@ -87544,7 +88256,7 @@ function normalizeBindingInput$1(value) {
87544
88256
  return null;
87545
88257
  }
87546
88258
  BINDING_TRANSFORM_VALUES$1.join(", ");
87547
- const isRecord$1 = isRecord$26;
88259
+ const isRecord$1 = isRecord$27;
87548
88260
  const applyBindingsToContent = applyBindingsToContent$1;
87549
88261
  //#endregion
87550
88262
  //#region src/rendering/helpers/templateContent.ts
@@ -87993,7 +88705,7 @@ async function loadContentFromContentResponseWithDeps(deps, params) {
87993
88705
  ogImageId: pageRecord.ogImageId ?? null
87994
88706
  },
87995
88707
  page: pageOutline,
87996
- runtimeBoundaries: pageData.runtimeBoundaries,
88708
+ runtimeJoins: pageData.runtimeJoins,
87997
88709
  theme: support.theme,
87998
88710
  siteId,
87999
88711
  ...support.customBlocks ? { customBlocks: support.customBlocks } : {},
@@ -88088,7 +88800,7 @@ function toBlocksPageRendererDataContext(dataContext) {
88088
88800
  function toBlocksCommonProps(props) {
88089
88801
  return {
88090
88802
  page: props.page,
88091
- runtimeBoundaries: props.runtimeBoundaries,
88803
+ runtimeJoins: props.runtimeJoins,
88092
88804
  dataContext: props.dataContext ? toBlocksPageRendererDataContext(props.dataContext) : void 0,
88093
88805
  routeMap: props.routeMap,
88094
88806
  wrapBlock: props.wrapBlock,
@@ -88187,10 +88899,10 @@ function buildPageRouteRenderContext(dataContext) {
88187
88899
  };
88188
88900
  }
88189
88901
  function PageContent(props) {
88190
- const { page, runtimeBoundaries, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
88902
+ const { page, runtimeJoins, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
88191
88903
  const rendererCommonProps = {
88192
88904
  page,
88193
- runtimeBoundaries,
88905
+ runtimeJoins,
88194
88906
  dataContext: {
88195
88907
  siteId,
88196
88908
  ...buildPageRouteRenderContext(dataContext),
@@ -89405,7 +90117,7 @@ var CircuitOpenError = class extends Error {
89405
90117
  * 1. This constant
89406
90118
  * 2. The "version" field in package.json
89407
90119
  */
89408
- const SDK_VERSION = "0.144.0";
90120
+ const SDK_VERSION = "0.145.0";
89409
90121
  //#endregion
89410
90122
  //#region src/next/content-load-failure.ts
89411
90123
  const SdkContentLoadError = RiverbankContentLoadError;
@@ -91202,7 +91914,7 @@ function createRiverbankClientWithDeps(deps, config) {
91202
91914
  const result = await reader({
91203
91915
  cacheKey: plan.cacheKey,
91204
91916
  endpoint: plan.endpoint,
91205
- payloadSchemaVersion: 1
91917
+ payloadSchemaVersion: 2
91206
91918
  });
91207
91919
  if (result.kind === "miss") return {
91208
91920
  kind: "miss",
@@ -91251,7 +91963,7 @@ function createRiverbankClientWithDeps(deps, config) {
91251
91963
  siteId: plan.siteId ?? getRuntimePayloadSiteId(payload),
91252
91964
  freshTtlSec: plan.freshTtlSec,
91253
91965
  staleTtlSec: plan.staleTtlSec,
91254
- payloadSchemaVersion: 1,
91966
+ payloadSchemaVersion: 2,
91255
91967
  writeReason: "live_success"
91256
91968
  });
91257
91969
  } catch {}