@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
@@ -1011,7 +1011,7 @@ function parseEventPageSurfaceDestinationMode(value) {
1011
1011
  return value === "cms-managed" || value === "system-managed" ? value : void 0;
1012
1012
  }
1013
1013
  function parseEventPageSurfaceSubject(value) {
1014
- if (!isRecord$44(value)) return err$3({ kind: "notObject" });
1014
+ if (!isRecord$45(value)) return err$3({ kind: "notObject" });
1015
1015
  switch (value.kind) {
1016
1016
  case "eventSeries": {
1017
1017
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -1066,7 +1066,7 @@ function parseEventSeriesIdList(values) {
1066
1066
  }
1067
1067
  return ok$3(parsed);
1068
1068
  }
1069
- function isRecord$44(value) {
1069
+ function isRecord$45(value) {
1070
1070
  return typeof value === "object" && value !== null && !Array.isArray(value);
1071
1071
  }
1072
1072
  var init_event_page_surface_subject = __esmMin((() => {
@@ -1518,7 +1518,76 @@ var init_booking_date_time = __esmMin((() => {
1518
1518
  }));
1519
1519
  //#endregion
1520
1520
  //#region ../core/src/booking-consent.ts
1521
- var init_booking_consent = __esmMin((() => {})), init_dated_offering_settlement = __esmMin((() => {
1521
+ var init_booking_consent = __esmMin((() => {}));
1522
+ //#endregion
1523
+ //#region ../core/src/appointment-service-categories.ts
1524
+ /**
1525
+ * Groups services under their categories.
1526
+ *
1527
+ * A service whose `categoryId` names no category in `categories` lands in
1528
+ * Other. The composite foreign key keeps that from happening with stored data,
1529
+ * so this is only the total, safe answer for a caller holding a stale list.
1530
+ */
1531
+ function layoutServicesByCategory(categories, services, options) {
1532
+ if (categories.length === 0) return {
1533
+ kind: "uncategorized-site",
1534
+ services: [...services]
1535
+ };
1536
+ const buckets = new Map(categories.map((category) => [category.id, []]));
1537
+ const other = [];
1538
+ for (const service of services) {
1539
+ const bucket = service.categoryId === null ? void 0 : buckets.get(service.categoryId);
1540
+ if (bucket !== void 0) bucket.push(service);
1541
+ else other.push(service);
1542
+ }
1543
+ const groups = [];
1544
+ for (const category of categories) {
1545
+ const groupServices = buckets.get(category.id) ?? [];
1546
+ if (groupServices.length === 0 && !options.keepEmptyGroups) continue;
1547
+ groups.push({
1548
+ category,
1549
+ services: groupServices
1550
+ });
1551
+ }
1552
+ if (groups.length === 0 && !options.keepEmptyGroups) return {
1553
+ kind: "uncategorized-site",
1554
+ services: other
1555
+ };
1556
+ return {
1557
+ kind: "categorized-site",
1558
+ groups,
1559
+ other
1560
+ };
1561
+ }
1562
+ /**
1563
+ * Selects the public view for an optional category filter.
1564
+ *
1565
+ * It takes the category and service lists rather than a prebuilt layout
1566
+ * because the public layout has already dropped empty categories, and then an
1567
+ * empty category (`single` with no services) could not be told apart from a
1568
+ * deleted one (`fallback-all`).
1569
+ */
1570
+ function selectCategoryView(categories, services, filterCategoryId) {
1571
+ const publicLayout = () => layoutServicesByCategory(categories, services, { keepEmptyGroups: false });
1572
+ if (filterCategoryId === null || filterCategoryId === void 0) return {
1573
+ kind: "all",
1574
+ layout: publicLayout()
1575
+ };
1576
+ const category = categories.find((candidate) => candidate.id === filterCategoryId);
1577
+ if (category === void 0) return {
1578
+ kind: "fallback-all",
1579
+ missingCategoryId: filterCategoryId,
1580
+ layout: publicLayout()
1581
+ };
1582
+ return {
1583
+ kind: "single",
1584
+ group: {
1585
+ category,
1586
+ services: services.filter((service) => service.categoryId === filterCategoryId)
1587
+ }
1588
+ };
1589
+ }
1590
+ var init_appointment_service_categories = __esmMin((() => {})), init_dated_offering_settlement = __esmMin((() => {
1522
1591
  init_result$1();
1523
1592
  }));
1524
1593
  var init_person_records = __esmMin((() => {
@@ -1572,7 +1641,7 @@ var init_public_profile = __esmMin((() => {
1572
1641
  var init_public_experience = __esmMin((() => {}));
1573
1642
  //#endregion
1574
1643
  //#region ../core/src/rich-text.ts
1575
- function isRecord$42(value) {
1644
+ function isRecord$43(value) {
1576
1645
  return typeof value === "object" && value !== null && !Array.isArray(value);
1577
1646
  }
1578
1647
  function hasOnlyKeys(value, allowed) {
@@ -1583,10 +1652,10 @@ function isDenseArrayOf(value, predicate) {
1583
1652
  return true;
1584
1653
  }
1585
1654
  function isTipTapMark(value) {
1586
- return isRecord$42(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$42(value.attrs));
1655
+ return isRecord$43(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$43(value.attrs));
1587
1656
  }
1588
1657
  function isTipTapNode(value) {
1589
- if (!isRecord$42(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$42(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;
1658
+ if (!isRecord$43(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$43(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;
1590
1659
  if (value.type === "text") return typeof value.text === "string" && value.text.length > 0 && value.content === void 0;
1591
1660
  return value.text === void 0;
1592
1661
  }
@@ -1637,10 +1706,10 @@ var init_mailing_list_identifiers = __esmMin((() => {
1637
1706
  //#endregion
1638
1707
  //#region ../core/src/mailing-list-targeting-parser.ts
1639
1708
  function parseMailingListPlacementTargeting(value) {
1640
- if (!isRecord$41(value)) return targetingError("shape");
1709
+ if (!isRecord$42(value)) return targetingError("shape");
1641
1710
  if (value.mode === "default") return ok$3({ mode: "default" });
1642
1711
  if (value.mode !== "custom") return targetingError("mode");
1643
- if (!isRecord$41(value.selection)) return targetingError("selection");
1712
+ if (!isRecord$42(value.selection)) return targetingError("selection");
1644
1713
  switch (value.provider) {
1645
1714
  case "native": return parseNativeTargeting(value.selection);
1646
1715
  case "brevo": return parseBrevoTargeting(value.selection);
@@ -1649,7 +1718,7 @@ function parseMailingListPlacementTargeting(value) {
1649
1718
  }
1650
1719
  }
1651
1720
  function parseMailingListVisitorSelection(value) {
1652
- if (!isRecord$41(value)) return visitorSelectionError$1("shape");
1721
+ if (!isRecord$42(value)) return visitorSelectionError$1("shape");
1653
1722
  switch (value.provider) {
1654
1723
  case "native": {
1655
1724
  const topicIds = parseVisitorIds(value.topicIds, parseNewsletterListId);
@@ -1782,7 +1851,7 @@ function parseVisitorIds(value, parseId) {
1782
1851
  }
1783
1852
  return parsed;
1784
1853
  }
1785
- function isRecord$41(value) {
1854
+ function isRecord$42(value) {
1786
1855
  return typeof value === "object" && value !== null && !Array.isArray(value);
1787
1856
  }
1788
1857
  function targetingError(field) {
@@ -1833,11 +1902,11 @@ var init_footer_navigation_layout = __esmMin((() => {}));
1833
1902
  var init_footer_composition_types = __esmMin((() => {}));
1834
1903
  //#endregion
1835
1904
  //#region ../core/src/mailing-list-setup.ts
1836
- function isRecord$38(value) {
1905
+ function isRecord$39(value) {
1837
1906
  return typeof value === "object" && value !== null && !Array.isArray(value);
1838
1907
  }
1839
1908
  function parseMailingListProviderScope(value) {
1840
- if (!isRecord$38(value)) return err$3("invalid_mailing_list_provider_scope");
1909
+ if (!isRecord$39(value)) return err$3("invalid_mailing_list_provider_scope");
1841
1910
  if (value.provider === "native") return ok$3({ provider: "native" });
1842
1911
  if (value.provider === "brevo") {
1843
1912
  const accountId = parseBrevoAccountId(value.accountId);
@@ -1864,7 +1933,7 @@ var init_mailing_list_setup = __esmMin((() => {
1864
1933
  //#endregion
1865
1934
  //#region ../core/src/mailing-list-setup-parser.ts
1866
1935
  function parseMailingListGlobalForm(value) {
1867
- if (!isRecord$37(value)) return formError("shape");
1936
+ if (!isRecord$38(value)) return formError("shape");
1868
1937
  const fields = parseGlobalFields(value.fields);
1869
1938
  if (fields === null) return formError("fields");
1870
1939
  if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
@@ -1883,7 +1952,7 @@ function parseMailingListGlobalForm(value) {
1883
1952
  });
1884
1953
  }
1885
1954
  function parseGlobalFields(value) {
1886
- if (!isRecord$37(value) || typeof value.preset !== "string") return null;
1955
+ if (!isRecord$38(value) || typeof value.preset !== "string") return null;
1887
1956
  switch (value.preset) {
1888
1957
  case "emailOnly": return { preset: "emailOnly" };
1889
1958
  case "emailName": return isRequirement(value.name) ? {
@@ -1920,7 +1989,7 @@ function parseOptionalString(value) {
1920
1989
  function isRequirement(value) {
1921
1990
  return value === "required" || value === "optional";
1922
1991
  }
1923
- function isRecord$37(value) {
1992
+ function isRecord$38(value) {
1924
1993
  return typeof value === "object" && value !== null && !Array.isArray(value);
1925
1994
  }
1926
1995
  function formError(field) {
@@ -1978,7 +2047,7 @@ var init_mailing_list_targeting = __esmMin((() => {
1978
2047
  //#endregion
1979
2048
  //#region ../core/src/mailing-list-runtime.ts
1980
2049
  function parseMailingListRuntimeContext(value) {
1981
- if (!isRecord$36(value)) return invalidRuntimeContext();
2050
+ if (!isRecord$37(value)) return invalidRuntimeContext();
1982
2051
  if (value.tag === "not_configured") return ok$3({ tag: "not_configured" });
1983
2052
  if (value.tag !== "configured") return invalidRuntimeContext();
1984
2053
  const revision = parseMailingListSetupRevision(value.revision);
@@ -1993,7 +2062,7 @@ function parseMailingListRuntimeContext(value) {
1993
2062
  });
1994
2063
  }
1995
2064
  function parseRuntimeProvider(value) {
1996
- if (!isRecord$36(value)) return null;
2065
+ if (!isRecord$37(value)) return null;
1997
2066
  switch (value.provider) {
1998
2067
  case "native": return parseNativeRuntimeProvider(value);
1999
2068
  case "brevo": return parseBrevoRuntimeProvider(value);
@@ -2025,7 +2094,7 @@ function parseMailchimpRuntimeProvider(value) {
2025
2094
  if (defaultAudienceId === null) return null;
2026
2095
  const audiences = [];
2027
2096
  for (const audience of value.audiences) {
2028
- if (!isRecord$36(audience)) return null;
2097
+ if (!isRecord$37(audience)) return null;
2029
2098
  const id = parseMailchimpAudienceId$1(audience.id);
2030
2099
  const name = parseName(audience.name);
2031
2100
  if (id === null || name === null || !Array.isArray(audience.interests)) return null;
@@ -2046,7 +2115,7 @@ function parseMailchimpRuntimeProvider(value) {
2046
2115
  function parseMailchimpInterests(value) {
2047
2116
  const interests = [];
2048
2117
  for (const interest of value) {
2049
- if (!isRecord$36(interest)) return null;
2118
+ if (!isRecord$37(interest)) return null;
2050
2119
  const id = parseMailchimpInterestId(interest.id);
2051
2120
  const name = parseName(interest.name);
2052
2121
  const categoryName = parseName(interest.categoryName);
@@ -2063,7 +2132,7 @@ function parseNamedItems(value, parseId) {
2063
2132
  if (!Array.isArray(value)) return null;
2064
2133
  const items = [];
2065
2134
  for (const item of value) {
2066
- if (!isRecord$36(item)) return null;
2135
+ if (!isRecord$37(item)) return null;
2067
2136
  const id = parseId(item.id);
2068
2137
  const name = parseName(item.name);
2069
2138
  if (id === null || name === null) return null;
@@ -2079,7 +2148,7 @@ function parseName(value) {
2079
2148
  const name = value.trim();
2080
2149
  return name.length > 0 ? name : null;
2081
2150
  }
2082
- function isRecord$36(value) {
2151
+ function isRecord$37(value) {
2083
2152
  return typeof value === "object" && value !== null && !Array.isArray(value);
2084
2153
  }
2085
2154
  function invalidRuntimeContext() {
@@ -2670,6 +2739,7 @@ var init_src$2 = __esmMin((() => {
2670
2739
  init_booking_instalments();
2671
2740
  init_booking_date_time();
2672
2741
  init_booking_consent();
2742
+ init_appointment_service_categories();
2673
2743
  init_dated_offering_settlement();
2674
2744
  init_person_records();
2675
2745
  init_rectification();
@@ -2854,11 +2924,11 @@ var init_storagePathDomain = __esmMin((() => {}));
2854
2924
  var init_fileAssetLegacyProjection = __esmMin((() => {}));
2855
2925
  //#endregion
2856
2926
  //#region ../media-core/src/typeGuards.ts
2857
- function isRecord$34(value) {
2927
+ function isRecord$35(value) {
2858
2928
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2859
2929
  }
2860
2930
  function asRecord$3(value) {
2861
- return isRecord$34(value) ? value : void 0;
2931
+ return isRecord$35(value) ? value : void 0;
2862
2932
  }
2863
2933
  var init_typeGuards$1 = __esmMin((() => {}));
2864
2934
  function isFileJsonObject(value) {
@@ -2868,7 +2938,7 @@ function isFileJsonValue(value) {
2868
2938
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
2869
2939
  if (typeof value === "number") return Number.isFinite(value);
2870
2940
  if (Array.isArray(value)) return value.every(isFileJsonValue);
2871
- return isRecord$34(value) && isFileJsonObject(value);
2941
+ return isRecord$35(value) && isFileJsonObject(value);
2872
2942
  }
2873
2943
  var init_filePersistence = __esmMin((() => {
2874
2944
  init_src$2();
@@ -3234,7 +3304,7 @@ function isCmsMediaObjectType(value) {
3234
3304
  return typeof value === "string" && CMS_MEDIA_OBJECT_TYPES.includes(value);
3235
3305
  }
3236
3306
  function isCmsMediaObject(value) {
3237
- if (!isRecord$34(value)) return false;
3307
+ if (!isRecord$35(value)) return false;
3238
3308
  const type = value.type;
3239
3309
  if (!isCmsMediaObjectType(type)) return false;
3240
3310
  if ("attrs" in value) return false;
@@ -3245,7 +3315,7 @@ function collectMediaObjects(value, out, location = "") {
3245
3315
  value.forEach((item, index) => collectMediaObjects(item, out, `${location}[${index}]`));
3246
3316
  return;
3247
3317
  }
3248
- if (!isRecord$34(value)) return;
3318
+ if (!isRecord$35(value)) return;
3249
3319
  if (isCmsMediaObject(value)) out.push({
3250
3320
  obj: value,
3251
3321
  location
@@ -3291,8 +3361,8 @@ function walkAndCollectImageAttrs(value, out, location = "") {
3291
3361
  value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
3292
3362
  return;
3293
3363
  }
3294
- if (!isRecord$34(value)) return;
3295
- if (value.type === "image" && isRecord$34(value.attrs)) out.push({
3364
+ if (!isRecord$35(value)) return;
3365
+ if (value.type === "image" && isRecord$35(value.attrs)) out.push({
3296
3366
  attrs: value.attrs,
3297
3367
  location: location ? `${location}.attrs` : "attrs"
3298
3368
  });
@@ -3352,7 +3422,7 @@ function mediaIdentifierOrFallback(current, fallback) {
3352
3422
  var init_renderableMediaAsset = __esmMin((() => {}));
3353
3423
  //#endregion
3354
3424
  //#region ../media-core/src/contentMediaAudit.ts
3355
- function isRecord$33(value) {
3425
+ function isRecord$34(value) {
3356
3426
  return typeof value === "object" && value !== null && !Array.isArray(value);
3357
3427
  }
3358
3428
  function summarizeContentMediaValue(value) {
@@ -3363,7 +3433,7 @@ function summarizeContentMediaValue(value) {
3363
3433
  }
3364
3434
  if (value === null) return "null";
3365
3435
  if (Array.isArray(value)) return `array(${value.length})`;
3366
- if (isRecord$33(value)) return `object(${Object.keys(value).sort().join(",")})`;
3436
+ if (isRecord$34(value)) return `object(${Object.keys(value).sort().join(",")})`;
3367
3437
  return typeof value;
3368
3438
  }
3369
3439
  function auditMediaValue(context, path, allowedMediaTypes, value) {
@@ -3387,9 +3457,9 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
3387
3457
  allowedMediaTypes
3388
3458
  }];
3389
3459
  }
3390
- const hasValidIdentifier = isRecord$33(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
3391
- const hasValidExplicitType = isRecord$33(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
3392
- const hasRetiredEnvironmentField = isRecord$33(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
3460
+ const hasValidIdentifier = isRecord$34(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
3461
+ const hasValidExplicitType = isRecord$34(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
3462
+ const hasRetiredEnvironmentField = isRecord$34(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
3393
3463
  if (hasValidIdentifier && hasValidExplicitType && !hasRetiredEnvironmentField) return [];
3394
3464
  return [{
3395
3465
  tag: "malformed-object",
@@ -3400,7 +3470,7 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
3400
3470
  }];
3401
3471
  }
3402
3472
  function auditFields(context, fields, value, parentPath) {
3403
- if (!isRecord$33(value)) return [];
3473
+ if (!isRecord$34(value)) return [];
3404
3474
  const findings = [];
3405
3475
  for (const field of fields) {
3406
3476
  if (!(field.id in value)) continue;
@@ -3417,7 +3487,7 @@ function auditFields(context, fields, value, parentPath) {
3417
3487
  if (!Array.isArray(fieldValue)) break;
3418
3488
  for (const [index, item] of fieldValue.entries()) {
3419
3489
  const itemPath = [...fieldPath, index];
3420
- if (!isRecord$33(item)) {
3490
+ if (!isRecord$34(item)) {
3421
3491
  findings.push({
3422
3492
  tag: "missing-schema",
3423
3493
  context,
@@ -3451,7 +3521,7 @@ var init_contentMediaAudit = __esmMin((() => {
3451
3521
  }));
3452
3522
  //#endregion
3453
3523
  //#region ../media-core/src/contentMediaAuditFieldAdapter.ts
3454
- function isRecord$32(value) {
3524
+ function isRecord$33(value) {
3455
3525
  return typeof value === "object" && value !== null && !Array.isArray(value);
3456
3526
  }
3457
3527
  function unknownArray(value) {
@@ -3460,7 +3530,7 @@ function unknownArray(value) {
3460
3530
  function fieldsArray(value) {
3461
3531
  const directFields = unknownArray(value);
3462
3532
  if (directFields) return directFields;
3463
- if (!isRecord$32(value)) return null;
3533
+ if (!isRecord$33(value)) return null;
3464
3534
  return unknownArray(value.fields);
3465
3535
  }
3466
3536
  function nestedFields$1(field) {
@@ -3525,7 +3595,7 @@ function mergeFields(fields) {
3525
3595
  }
3526
3596
  function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds = ["document"]) {
3527
3597
  return mergeFields(rawFields.flatMap((rawField) => {
3528
- if (!isRecord$32(rawField)) return [];
3598
+ if (!isRecord$33(rawField)) return [];
3529
3599
  const { id, type } = rawField;
3530
3600
  if (typeof id !== "string" || !id || typeof type !== "string") return [];
3531
3601
  if (type === "media" || type === "file") return [{
@@ -3533,7 +3603,7 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
3533
3603
  id,
3534
3604
  allowedMediaTypes: allowedMediaTypes(rawField, type, defaultFileKinds)
3535
3605
  }];
3536
- if (flattenRepeaterGroups && type === "group" && isRecord$32(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields$1(rawField), false, defaultFileKinds);
3606
+ if (flattenRepeaterGroups && type === "group" && isRecord$33(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields$1(rawField), false, defaultFileKinds);
3537
3607
  if (type === "group" || type === "modal") return [{
3538
3608
  kind: "object",
3539
3609
  id,
@@ -3542,14 +3612,14 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
3542
3612
  if (type === "tabGroup") return [{
3543
3613
  kind: "object",
3544
3614
  id,
3545
- fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$32(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
3615
+ fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$33(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
3546
3616
  }];
3547
3617
  if (type !== "repeater") return [];
3548
- if (isRecord$32(rawField.itemTypes)) return [{
3618
+ if (isRecord$33(rawField.itemTypes)) return [{
3549
3619
  kind: "repeater",
3550
3620
  id,
3551
3621
  mode: "variants",
3552
- variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$32(itemType) && Array.isArray(itemType.fields) ? [{
3622
+ variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$33(itemType) && Array.isArray(itemType.fields) ? [{
3553
3623
  typeId,
3554
3624
  fields: adaptFields(itemType.fields, true, defaultFileKinds)
3555
3625
  }] : []),
@@ -8420,7 +8490,7 @@ var init_resolvedButtonCss = __esmMin((() => {
8420
8490
  }));
8421
8491
  //#endregion
8422
8492
  //#region ../theme-core/src/buttons/generateButtonCss.ts
8423
- function rule$1(selector, declarations) {
8493
+ function rule$2(selector, declarations) {
8424
8494
  return {
8425
8495
  selectors: typeof selector === "string" ? [selector] : selector,
8426
8496
  declarations
@@ -8594,7 +8664,7 @@ function variantAffordanceRule(variant, selector) {
8594
8664
  const maskUrl = affordanceIconMaskUrl(affordance.iconId);
8595
8665
  if (!maskUrl) return null;
8596
8666
  const mask = `url("${maskUrl}") no-repeat center / contain`;
8597
- return rule$1(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
8667
+ return rule$2(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
8598
8668
  ["content", "\"\""],
8599
8669
  ["display", "inline-block"],
8600
8670
  ["width", "1em"],
@@ -8633,7 +8703,7 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
8633
8703
  const borderWidth = BORDER_WIDTH_MAP$4[variant.border.widthClass] || "1px";
8634
8704
  decls.push(["border", `${borderWidth} solid currentColor`]);
8635
8705
  }
8636
- return rule$1(selector, decls);
8706
+ return rule$2(selector, decls);
8637
8707
  }
8638
8708
  /**
8639
8709
  * Resolve a colour for a button that does not own its own background.
@@ -8644,13 +8714,13 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
8644
8714
  */
8645
8715
  function variantStateColourRule(variantId, themeId, state, colour) {
8646
8716
  const baseSelector = `:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}`;
8647
- return rule$1(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
8717
+ return rule$2(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
8648
8718
  role: variantId,
8649
8719
  state
8650
8720
  }));
8651
8721
  }
8652
8722
  function variantFocusVisibleRule(variantId, themeId, focusIndicator) {
8653
- return rule$1(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
8723
+ return rule$2(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
8654
8724
  }
8655
8725
  function variantEffectsCss(variant, effects, themeId, tokens, theme) {
8656
8726
  if (!effects) return "";
@@ -8676,10 +8746,10 @@ function variantDisabledRules(variantId, themeId, colour) {
8676
8746
  `${base}:focus`
8677
8747
  ];
8678
8748
  const pseudoSelectors = [`${base}::before`, `${base}::after`];
8679
- return [rule$1(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
8749
+ return [rule$2(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
8680
8750
  role: variantId,
8681
8751
  state: "disabled"
8682
- }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$1(pseudoSelectors, [
8752
+ }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$2(pseudoSelectors, [
8683
8753
  ["opacity", "0"],
8684
8754
  ["animation", "none"],
8685
8755
  ["pointer-events", "none"]
@@ -8694,7 +8764,7 @@ function variantSizeRules(variant, variantId, themeSizes, themeId) {
8694
8764
  const decls = [];
8695
8765
  for (const decl of paddingDeclarations(config.padding, borderWidth)) decls.push(decl);
8696
8766
  if (config.fontSize) decls.push(["font-size", FONT_SIZE_MAP[config.fontSize] || "1rem"]);
8697
- rules.push(rule$1(selector, decls));
8767
+ rules.push(rule$2(selector, decls));
8698
8768
  }
8699
8769
  return rules;
8700
8770
  }
@@ -12073,6 +12143,15 @@ function asSiteStyleId(value) {
12073
12143
  function asDesignSystemTemplateId(value) {
12074
12144
  return brandNonEmptyString(value, "DesignSystemTemplateId");
12075
12145
  }
12146
+ function toNonEmptyReadonlyArray(values) {
12147
+ const [first, ...rest] = values;
12148
+ return first === void 0 ? null : [first, ...rest];
12149
+ }
12150
+ function requireNonEmptyReadonlyArray(values, message = "Expected a non-empty array.") {
12151
+ const result = toNonEmptyReadonlyArray(values);
12152
+ if (!result) throw new Error(message);
12153
+ return result;
12154
+ }
12076
12155
  function makeBoundaryId(previousBlockId, nextBlockId) {
12077
12156
  return asDesignBoundaryId(`boundary:${encodeURIComponent(previousBlockId)}:${encodeURIComponent(nextBlockId)}`);
12078
12157
  }
@@ -12180,7 +12259,8 @@ var init_types$8 = __esmMin((() => {
12180
12259
  "swap"
12181
12260
  ];
12182
12261
  }));
12183
- var logoTextConfidenceValues, logoAssetShapeValues, logoKindValues, logoDensityValues, logoContainsTextSchema, logoAnalysisSourceSchema, brandDisplayModeValues, logoAccommodationPatternValues, logoSizePolicyValues, defaultNoLogoFacts, sizeOrder, init_logoBranding = __esmMin((() => {
12262
+ var logoTextConfidenceValues, logoAssetShapeValues, logoKindValues, logoDensityValues, logoContainsTextSchema, logoAnalysisSourceSchema, brandDisplayModeValues, logoAccommodationPatternValues, logoSizePolicyValues, defaultNoLogoFacts, sizeOrder;
12263
+ var init_logoBranding = __esmMin((() => {
12184
12264
  init_types$8();
12185
12265
  logoTextConfidenceValues = [
12186
12266
  "low",
@@ -12270,26 +12350,44 @@ var logoTextConfidenceValues, logoAssetShapeValues, logoKindValues, logoDensityV
12270
12350
  "generous",
12271
12351
  "display"
12272
12352
  ];
12273
- })), themeV2RuntimeStyleFieldNames, themeV2BlockSpecificRawStyleFieldNames, themeV2NormalEditorRawStyleFieldNames, pageDesignEditorBackgroundOverrideOwner, siteChromeBackgroundOverrideOwner;
12353
+ }));
12354
+ /**
12355
+ * A `styleGroup` in the retired `site-style-transition-*` namespace is residue
12356
+ * from the old per-block transition path (issue 1422). Any other value is
12357
+ * authored and must survive.
12358
+ */
12359
+ function isStaleTransitionStyleGroup(value) {
12360
+ return typeof value === "string" && value.startsWith(staleTransitionStyleGroupPrefix);
12361
+ }
12362
+ /** Drops a stale transition `styleGroup`, leaving authored values untouched. */
12363
+ function stripStaleTransitionStyleGroup(content) {
12364
+ if (!isStaleTransitionStyleGroup(content.styleGroup)) return { ...content };
12365
+ const { styleGroup: _stale, ...rest } = content;
12366
+ return rest;
12367
+ }
12368
+ var themeV2RuntimeStyleFieldNames, themeV2BlockSpecificRawStyleFieldNames, themeV2NormalEditorRawStyleFieldNames, pageDesignEditorBackgroundOverrideOwner, siteChromeBackgroundOverrideOwner, staleTransitionStyleGroupPrefix;
12274
12369
  var init_rawStyleFields = __esmMin((() => {
12275
12370
  themeV2RuntimeStyleFieldNames = [
12276
12371
  "_frameStyles",
12277
12372
  "_sectionStyles",
12278
12373
  "_containerStyles",
12279
- "_cardStyles",
12280
- "_transitionStyles",
12281
- "styleGroup"
12374
+ "_cardStyles"
12282
12375
  ];
12283
12376
  themeV2BlockSpecificRawStyleFieldNames = [
12284
12377
  "_contentCardStyles",
12285
12378
  "_heroLayoutStyles",
12286
12379
  "_textCardStyles"
12287
12380
  ];
12288
- themeV2NormalEditorRawStyleFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
12381
+ themeV2NormalEditorRawStyleFieldNames = [
12382
+ ...themeV2RuntimeStyleFieldNames,
12383
+ ...themeV2BlockSpecificRawStyleFieldNames,
12384
+ "styleGroup"
12385
+ ];
12289
12386
  new Set(themeV2NormalEditorRawStyleFieldNames);
12290
12387
  [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
12291
12388
  pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
12292
12389
  siteChromeBackgroundOverrideOwner = "site-chrome-editor";
12390
+ staleTransitionStyleGroupPrefix = "site-style-transition-";
12293
12391
  })), headerVariantValues, legacyHeaderVariants, legacyHeaderVariantValues, headerVariantWithLegacyAliasSchema;
12294
12392
  var init_legacyHeaderVariant = __esmMin((() => {
12295
12393
  headerVariantValues = [
@@ -12513,7 +12611,7 @@ var init_interactionPalette = __esmMin((() => {
12513
12611
  //#endregion
12514
12612
  //#region ../theme-core/src/boxBackgroundSchema.ts
12515
12613
  function normalizeAuthoringOpacity(value) {
12516
- if (!isRecord$31(value)) return value;
12614
+ if (!isRecord$32(value)) return value;
12517
12615
  return {
12518
12616
  ...value,
12519
12617
  opacity: normalizeOpacity(value.opacity)
@@ -12522,7 +12620,7 @@ function normalizeAuthoringOpacity(value) {
12522
12620
  function normalizeOpacity(value) {
12523
12621
  return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
12524
12622
  }
12525
- function isRecord$31(value) {
12623
+ function isRecord$32(value) {
12526
12624
  return typeof value === "object" && value !== null && !Array.isArray(value);
12527
12625
  }
12528
12626
  var boxBackgroundOverlaySchema, commonSchema, mediaPresentationSchema, boxBackgroundSchema;
@@ -13341,7 +13439,7 @@ var init_media$1 = __esmMin((() => {
13341
13439
  /**
13342
13440
  * Strict record guard: excludes null, primitives, and arrays.
13343
13441
  */
13344
- function isRecord$30(value) {
13442
+ function isRecord$31(value) {
13345
13443
  return typeof value === "object" && value !== null && !Array.isArray(value);
13346
13444
  }
13347
13445
  /**
@@ -16153,17 +16251,280 @@ var init_cardRoleVocabulary = __esmMin((() => {
16153
16251
  Object.keys(legacyItemSurfaceCardRoles);
16154
16252
  cardRoleWithLegacyAliasSchema = z$1.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
16155
16253
  }));
16156
- var init_compositionBudget = __esmMin((() => {
16157
- init_types$8();
16158
- }));
16159
- var defaultHeaderBrandPreferences;
16160
- var init_compilationPlan = __esmMin((() => {
16161
- defaultHeaderBrandPreferences = {
16162
- brandDisplay: "auto",
16163
- logoSize: "auto"
16254
+ //#endregion
16255
+ //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
16256
+ function parseCanonicalRuntimeBackground(content) {
16257
+ const background = readRecord(readRecord(content._sectionStyles)?.background);
16258
+ if (!background) return { kind: "none" };
16259
+ const parsed = boxBackgroundSchema.safeParse(background);
16260
+ if (!parsed.success) return { kind: "none" };
16261
+ return parsed.data.contentOwner === "page-design-editor" ? {
16262
+ kind: "page-design-override",
16263
+ background: parsed.data
16264
+ } : {
16265
+ kind: "block-authored",
16266
+ background: parsed.data
16164
16267
  };
16268
+ }
16269
+ function readRecord(value) {
16270
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
16271
+ }
16272
+ var init_runtimeBackgroundResolution = __esmMin((() => {
16273
+ init_boxBackgroundSchema();
16274
+ init_surfaces();
16275
+ init_rawStyleFields();
16276
+ }));
16277
+ //#endregion
16278
+ //#region ../theme-core/src/site-styles/transitionCatalogue.ts
16279
+ /** CSS `clip-path` value for an angle shape, as `generateLayoutCss` draws it. */
16280
+ function anglePolygonCss(polygon) {
16281
+ return `polygon(0 ${polygon.startTopPercent}%, 100% ${polygon.endTopPercent}%, 100% 100%, 0 100%)`;
16282
+ }
16283
+ var transitionCatalogueFamilies, mediumHeight, compactHeight, largeHeight, transitionCatalogueEntries;
16284
+ var init_transitionCatalogue = __esmMin((() => {
16285
+ init_types$8();
16286
+ transitionCatalogueFamilies = [
16287
+ {
16288
+ id: "gradient",
16289
+ label: "Gradient"
16290
+ },
16291
+ {
16292
+ id: "wave",
16293
+ label: "Wave"
16294
+ },
16295
+ {
16296
+ id: "angle",
16297
+ label: "Angle"
16298
+ }
16299
+ ];
16300
+ mediumHeight = "6rem + 2px";
16301
+ compactHeight = "4.5rem + 2px";
16302
+ largeHeight = "7rem + 2px";
16303
+ transitionCatalogueEntries = defineExactCatalog()([
16304
+ {
16305
+ intent: "soft-fade-short",
16306
+ optionId: asBoundaryOptionId("break:soft"),
16307
+ family: "gradient",
16308
+ variantId: "soft",
16309
+ label: "Soft",
16310
+ variantLabel: "Soft",
16311
+ treatment: {
16312
+ kind: "soft-fade",
16313
+ height: "medium",
16314
+ curve: "soft"
16315
+ },
16316
+ size: "medium",
16317
+ heightExpression: mediumHeight,
16318
+ shape: { kind: "fade" }
16319
+ },
16320
+ {
16321
+ intent: "soft-fade-long",
16322
+ optionId: asBoundaryOptionId("break:spacious"),
16323
+ family: "gradient",
16324
+ variantId: "spacious",
16325
+ label: "Spacious",
16326
+ variantLabel: "Spacious",
16327
+ treatment: {
16328
+ kind: "soft-fade",
16329
+ height: "large",
16330
+ curve: "spacious"
16331
+ },
16332
+ size: "spacious",
16333
+ heightExpression: "10rem",
16334
+ shape: { kind: "fade" }
16335
+ },
16336
+ {
16337
+ intent: "soft-fade-expansive",
16338
+ optionId: asBoundaryOptionId("break:expansive"),
16339
+ family: "gradient",
16340
+ variantId: "expansive",
16341
+ label: "Expansive",
16342
+ variantLabel: "Expansive",
16343
+ treatment: {
16344
+ kind: "soft-fade",
16345
+ height: "expansive",
16346
+ curve: "expansive"
16347
+ },
16348
+ size: "expansive",
16349
+ heightExpression: "14rem",
16350
+ shape: { kind: "fade" }
16351
+ },
16352
+ {
16353
+ intent: "wave-edge-balanced",
16354
+ optionId: asBoundaryOptionId("break:wave-balanced"),
16355
+ family: "wave",
16356
+ variantId: "balanced",
16357
+ label: "Balanced wave",
16358
+ variantLabel: "Balanced",
16359
+ treatment: {
16360
+ kind: "wave-edge",
16361
+ height: "medium",
16362
+ amplitude: "balanced",
16363
+ rhythm: "double",
16364
+ symmetry: "symmetrical"
16365
+ },
16366
+ size: "medium",
16367
+ heightExpression: mediumHeight,
16368
+ shape: {
16369
+ kind: "wave",
16370
+ mask: "balanced"
16371
+ }
16372
+ },
16373
+ {
16374
+ intent: "wave-edge",
16375
+ optionId: asBoundaryOptionId("break:wave"),
16376
+ family: "wave",
16377
+ variantId: "classic",
16378
+ label: "Classic wave",
16379
+ variantLabel: "Classic",
16380
+ treatment: {
16381
+ kind: "wave-edge",
16382
+ height: "medium",
16383
+ amplitude: "balanced",
16384
+ rhythm: "double",
16385
+ symmetry: "asymmetrical"
16386
+ },
16387
+ size: "medium",
16388
+ heightExpression: mediumHeight,
16389
+ shape: {
16390
+ kind: "wave",
16391
+ mask: "classic"
16392
+ }
16393
+ },
16394
+ {
16395
+ intent: "wave-edge-gentle",
16396
+ optionId: asBoundaryOptionId("break:wave-gentle"),
16397
+ family: "wave",
16398
+ variantId: "gentle",
16399
+ label: "Gentle wave",
16400
+ variantLabel: "Gentle",
16401
+ treatment: {
16402
+ kind: "wave-edge",
16403
+ height: "compact",
16404
+ amplitude: "subtle",
16405
+ rhythm: "single",
16406
+ symmetry: "symmetrical"
16407
+ },
16408
+ size: "compact",
16409
+ heightExpression: compactHeight,
16410
+ shape: {
16411
+ kind: "wave",
16412
+ mask: "gentle"
16413
+ }
16414
+ },
16415
+ {
16416
+ intent: "wave-edge-bold",
16417
+ optionId: asBoundaryOptionId("break:wave-bold"),
16418
+ family: "wave",
16419
+ variantId: "bold",
16420
+ label: "Bold wave",
16421
+ variantLabel: "Bold",
16422
+ treatment: {
16423
+ kind: "wave-edge",
16424
+ height: "large",
16425
+ amplitude: "bold",
16426
+ rhythm: "double",
16427
+ symmetry: "symmetrical"
16428
+ },
16429
+ size: "large",
16430
+ heightExpression: largeHeight,
16431
+ shape: {
16432
+ kind: "wave",
16433
+ mask: "bold"
16434
+ }
16435
+ },
16436
+ {
16437
+ intent: "wave-edge-drift",
16438
+ optionId: asBoundaryOptionId("break:wave-drift"),
16439
+ family: "wave",
16440
+ variantId: "drift",
16441
+ label: "Drift wave",
16442
+ variantLabel: "Drift",
16443
+ treatment: {
16444
+ kind: "wave-edge",
16445
+ height: "medium",
16446
+ amplitude: "balanced",
16447
+ rhythm: "double",
16448
+ symmetry: "drift"
16449
+ },
16450
+ size: "medium",
16451
+ heightExpression: mediumHeight,
16452
+ shape: {
16453
+ kind: "wave",
16454
+ mask: "drift"
16455
+ }
16456
+ },
16457
+ {
16458
+ intent: "angle-edge-subtle",
16459
+ optionId: asBoundaryOptionId("break:angle-subtle"),
16460
+ family: "angle",
16461
+ variantId: "subtle",
16462
+ label: "Subtle angle",
16463
+ variantLabel: "Subtle",
16464
+ treatment: {
16465
+ kind: "angle-edge",
16466
+ height: "compact",
16467
+ slope: "subtle",
16468
+ direction: "forward"
16469
+ },
16470
+ size: "compact",
16471
+ heightExpression: compactHeight,
16472
+ shape: {
16473
+ kind: "angle",
16474
+ polygon: {
16475
+ startTopPercent: 34,
16476
+ endTopPercent: 18
16477
+ }
16478
+ }
16479
+ },
16480
+ {
16481
+ intent: "angle-edge",
16482
+ optionId: asBoundaryOptionId("break:angle-balanced"),
16483
+ family: "angle",
16484
+ variantId: "balanced",
16485
+ label: "Balanced angle",
16486
+ variantLabel: "Balanced",
16487
+ treatment: {
16488
+ kind: "angle-edge",
16489
+ height: "medium",
16490
+ slope: "balanced",
16491
+ direction: "forward"
16492
+ },
16493
+ size: "medium",
16494
+ heightExpression: mediumHeight,
16495
+ shape: {
16496
+ kind: "angle",
16497
+ polygon: {
16498
+ startTopPercent: 42,
16499
+ endTopPercent: 8
16500
+ }
16501
+ }
16502
+ },
16503
+ {
16504
+ intent: "angle-edge-steep",
16505
+ optionId: asBoundaryOptionId("break:angle-steep"),
16506
+ family: "angle",
16507
+ variantId: "steep",
16508
+ label: "Steep angle",
16509
+ variantLabel: "Steep",
16510
+ treatment: {
16511
+ kind: "angle-edge",
16512
+ height: "large",
16513
+ slope: "steep",
16514
+ direction: "forward"
16515
+ },
16516
+ size: "large",
16517
+ heightExpression: largeHeight,
16518
+ shape: {
16519
+ kind: "angle",
16520
+ polygon: {
16521
+ startTopPercent: 54,
16522
+ endTopPercent: 0
16523
+ }
16524
+ }
16525
+ }
16526
+ ]);
16165
16527
  }));
16166
- var init_schemaVersionedJson = __esmMin((() => {}));
16167
16528
  //#endregion
16168
16529
  //#region ../theme-core/src/site-styles/sectionTransitions.ts
16169
16530
  function boundaryRuntimeMetrics(family, size, heightExpression) {
@@ -16174,62 +16535,268 @@ function boundaryRuntimeMetrics(family, size, heightExpression) {
16174
16535
  reserveCss: `calc((${heightExpression}) / 2)`
16175
16536
  };
16176
16537
  }
16177
- function boundaryRuntimeMetricsForTransition(transition) {
16178
- return sectionBoundaryRuntimeMetricsByTransition[transition];
16538
+ /**
16539
+ * The catalogue's compile-time coverage check guarantees an entry per
16540
+ * decorative intent, so a miss here is a broken invariant, not bad input.
16541
+ */
16542
+ function requireCatalogueValue(values, intent) {
16543
+ const value = values.get(intent);
16544
+ if (value === void 0) throw new Error(`Transition catalogue has no entry for ${intent}`);
16545
+ return value;
16546
+ }
16547
+ function boundaryRuntimeMetricsForDecorativeTransition(transition) {
16548
+ return requireCatalogueValue(metricsByIntent, transition);
16549
+ }
16550
+ function boundaryOptionForTransitionIntent(intent) {
16551
+ return requireCatalogueValue(boundaryOptionByIntent, intent);
16552
+ }
16553
+ function transitionVariantForEntry(entry) {
16554
+ return {
16555
+ familyId: entry.family,
16556
+ variantId: entry.variantId,
16557
+ label: entry.variantLabel,
16558
+ boundaryOption: boundaryOptionForTransitionIntent(entry.intent)
16559
+ };
16560
+ }
16561
+ function stableTreatmentKey(treatment) {
16562
+ switch (treatment.kind) {
16563
+ case "none": return "none";
16564
+ case "soft-fade": return `${treatment.kind}:${treatment.height}:${treatment.curve}`;
16565
+ case "wave-edge": return [
16566
+ treatment.kind,
16567
+ treatment.height,
16568
+ treatment.amplitude,
16569
+ treatment.rhythm,
16570
+ treatment.symmetry
16571
+ ].join(":");
16572
+ case "angle-edge": return [
16573
+ treatment.kind,
16574
+ treatment.height,
16575
+ treatment.slope,
16576
+ treatment.direction
16577
+ ].join(":");
16578
+ case "legacy-unsupported": return `${treatment.kind}:${treatment.boundaryOptionId}`;
16579
+ }
16179
16580
  }
16180
- var sectionBoundaryRuntimeMetricsByTransition, noneBoundaryOption, softBoundaryOption, spaciousBoundaryOption;
16581
+ var metricsByIntent, noneBoundaryOption, boundaryOptionByIntent, softBoundaryOption, spaciousBoundaryOption, boundaryOptions, noneTransitionVariant, sectionTransitionFamilies;
16181
16582
  var init_sectionTransitions = __esmMin((() => {
16182
16583
  init_types$8();
16183
- sectionBoundaryRuntimeMetricsByTransition = {
16184
- none: null,
16185
- "soft-fade-short": boundaryRuntimeMetrics("gradient", "medium", "6rem + 2px"),
16186
- "soft-fade-long": boundaryRuntimeMetrics("gradient", "spacious", "10rem"),
16187
- "soft-fade-expansive": boundaryRuntimeMetrics("gradient", "expansive", "14rem"),
16188
- "wave-edge": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16189
- "wave-edge-balanced": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16190
- "wave-edge-gentle": boundaryRuntimeMetrics("wave", "compact", "4.5rem + 2px"),
16191
- "wave-edge-bold": boundaryRuntimeMetrics("wave", "large", "7rem + 2px"),
16192
- "wave-edge-drift": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16193
- "angle-edge": boundaryRuntimeMetrics("angle", "medium", "6rem + 2px"),
16194
- "angle-edge-subtle": boundaryRuntimeMetrics("angle", "compact", "4.5rem + 2px"),
16195
- "angle-edge-steep": boundaryRuntimeMetrics("angle", "large", "7rem + 2px")
16196
- };
16584
+ init_transitionCatalogue();
16585
+ new Map(transitionCatalogueEntries.map((entry) => [entry.intent, entry]));
16586
+ metricsByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, boundaryRuntimeMetrics(entry.family, entry.size, entry.heightExpression)]));
16197
16587
  noneBoundaryOption = {
16198
16588
  id: asBoundaryOptionId("break:none"),
16199
16589
  label: "None",
16200
16590
  transitionAfter: "none",
16201
16591
  treatment: { kind: "none" }
16202
16592
  };
16203
- softBoundaryOption = {
16204
- id: asBoundaryOptionId("break:soft"),
16205
- label: "Soft",
16206
- transitionAfter: "soft-fade-short",
16207
- treatment: {
16208
- kind: "soft-fade",
16209
- height: "medium",
16210
- curve: "soft"
16593
+ boundaryOptionByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, {
16594
+ id: entry.optionId,
16595
+ label: entry.label,
16596
+ transitionAfter: entry.intent,
16597
+ treatment: entry.treatment
16598
+ }]));
16599
+ softBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-short");
16600
+ spaciousBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-long");
16601
+ boundaryOptionForTransitionIntent("soft-fade-expansive");
16602
+ boundaryOptionForTransitionIntent("wave-edge");
16603
+ boundaryOptionForTransitionIntent("wave-edge-balanced");
16604
+ boundaryOptionForTransitionIntent("wave-edge-gentle");
16605
+ boundaryOptionForTransitionIntent("wave-edge-bold");
16606
+ boundaryOptionForTransitionIntent("wave-edge-drift");
16607
+ boundaryOptionForTransitionIntent("angle-edge-subtle");
16608
+ boundaryOptionForTransitionIntent("angle-edge");
16609
+ boundaryOptionForTransitionIntent("angle-edge-steep");
16610
+ boundaryOptions = [noneBoundaryOption, ...transitionCatalogueEntries.map((entry) => boundaryOptionForTransitionIntent(entry.intent))];
16611
+ noneTransitionVariant = {
16612
+ familyId: "none",
16613
+ variantId: "none",
16614
+ label: "None",
16615
+ boundaryOption: noneBoundaryOption
16616
+ };
16617
+ sectionTransitionFamilies = [{
16618
+ id: "none",
16619
+ label: "None",
16620
+ variants: [noneTransitionVariant]
16621
+ }, ...transitionCatalogueFamilies.map((family) => ({
16622
+ id: family.id,
16623
+ label: family.label,
16624
+ variants: requireNonEmptyReadonlyArray(transitionCatalogueEntries.filter((entry) => entry.family === family.id).map(transitionVariantForEntry), `Transition family ${family.id} has no catalogue entries.`)
16625
+ }))];
16626
+ new Map(boundaryOptions.map((option) => [option.id, option]));
16627
+ new Map(boundaryOptions.map((option) => [stableTreatmentKey(option.treatment), option]));
16628
+ new Map(sectionTransitionFamilies.flatMap((family) => family.variants.map((variant) => [variant.boundaryOption.id, variant])));
16629
+ new Map(sectionTransitionFamilies.map((family) => [family.id, family]));
16630
+ }));
16631
+ //#endregion
16632
+ //#region ../theme-core/src/site-styles/sectionBoundaryComposition.ts
16633
+ /**
16634
+ * Classifies a section from its final render content. `_sectionStyles`
16635
+ * already holds the effective background (Look default, page-design override
16636
+ * and block-authored media merged by `resolveSectionBackground`), so this only
16637
+ * reads it and never re-decides precedence. A missing or unparseable
16638
+ * background is the page background, as it is for the renderer.
16639
+ */
16640
+ function boundaryEndpointForSectionContent(content) {
16641
+ const background = parseCanonicalRuntimeBackground(content);
16642
+ if (background.kind === "none") return {
16643
+ kind: "paint",
16644
+ token: backgroundToken
16645
+ };
16646
+ const effective = background.background;
16647
+ switch (effective.type) {
16648
+ case "color": {
16649
+ if (effective.color === null || effective.color === void 0) return {
16650
+ kind: "paint",
16651
+ token: backgroundToken
16652
+ };
16653
+ const token = parseThemeColorToken(effective.color);
16654
+ return token ? {
16655
+ kind: "paint",
16656
+ token
16657
+ } : {
16658
+ kind: "unpainted",
16659
+ reason: "literal-color"
16660
+ };
16661
+ }
16662
+ case "gradient": return {
16663
+ kind: "unpainted",
16664
+ reason: "gradient"
16665
+ };
16666
+ case "image":
16667
+ case "video": return {
16668
+ kind: "unpainted",
16669
+ reason: "media"
16670
+ };
16671
+ default: return assertNever$12(effective, "Unhandled section background type");
16672
+ }
16673
+ }
16674
+ /**
16675
+ * Reads both facts from final render content in one place. `_frameStyles` is
16676
+ * the compiled frame; content without one (no page design, an SDK block)
16677
+ * draws a plain frame.
16678
+ */
16679
+ function sectionFactsForContent(content) {
16680
+ return {
16681
+ endpoint: boundaryEndpointForSectionContent(content),
16682
+ frame: frameKindForContent(content)
16683
+ };
16684
+ }
16685
+ function frameKindForContent(content) {
16686
+ const frameStyles = content._frameStyles;
16687
+ const kind = typeof frameStyles === "object" && frameStyles !== null && "kind" in frameStyles ? frameStyles.kind : null;
16688
+ return kind === "panel" || kind === "flush-panel" ? kind : "plain";
16689
+ }
16690
+ /**
16691
+ * The frame rule: a flush panel owns the edge it runs to, so no break is
16692
+ * drawn next to one. The single home for this rule; the generated
16693
+ * pipeline's budget and the site-style lab ask it with their plan frames.
16694
+ */
16695
+ function frameNotShownReason(previous, next) {
16696
+ return previous === "flush-panel" || next === "flush-panel" ? "flush-panel" : null;
16697
+ }
16698
+ /**
16699
+ * Decides whether and how a join draws. The frame rule wins before paint is
16700
+ * considered; two unpainted sides show nothing.
16701
+ */
16702
+ function composeBoundary(previous, next) {
16703
+ const frameReason = frameNotShownReason(previous.frame, next.frame);
16704
+ if (frameReason) return {
16705
+ kind: "not-shown",
16706
+ reason: frameReason
16707
+ };
16708
+ const from = previous.endpoint;
16709
+ const to = next.endpoint;
16710
+ if (from.kind === "paint") return {
16711
+ kind: "shown",
16712
+ composition: to.kind === "paint" ? {
16713
+ kind: "paint-to-paint",
16714
+ from: from.token,
16715
+ to: to.token
16716
+ } : {
16717
+ kind: "paint-to-unpainted",
16718
+ from: from.token
16211
16719
  }
16212
16720
  };
16213
- spaciousBoundaryOption = {
16214
- id: asBoundaryOptionId("break:spacious"),
16215
- label: "Spacious",
16216
- transitionAfter: "soft-fade-long",
16217
- treatment: {
16218
- kind: "soft-fade",
16219
- height: "large",
16220
- curve: "spacious"
16721
+ return to.kind === "paint" ? {
16722
+ kind: "shown",
16723
+ composition: {
16724
+ kind: "unpainted-to-paint",
16725
+ to: to.token
16221
16726
  }
16727
+ } : {
16728
+ kind: "not-shown",
16729
+ reason: "both-unpainted"
16730
+ };
16731
+ }
16732
+ /**
16733
+ * Placement, reserves and paint for a shown boundary, derived from the
16734
+ * composition and the catalogue's metrics for the treatment.
16735
+ */
16736
+ function boundaryLayout(intent, composition) {
16737
+ const metrics = boundaryRuntimeMetricsForDecorativeTransition(intent);
16738
+ switch (composition.kind) {
16739
+ case "paint-to-paint": return {
16740
+ placement: "centred",
16741
+ reserves: {
16742
+ before: metrics.reserveCss,
16743
+ after: metrics.reserveCss
16744
+ },
16745
+ paint: {
16746
+ band: composition.from,
16747
+ shape: composition.to
16748
+ }
16749
+ };
16750
+ case "unpainted-to-paint": return {
16751
+ placement: "previous",
16752
+ reserves: {
16753
+ before: metrics.heightCss,
16754
+ after: null
16755
+ },
16756
+ paint: {
16757
+ band: null,
16758
+ shape: composition.to
16759
+ }
16760
+ };
16761
+ case "paint-to-unpainted": return {
16762
+ placement: "next",
16763
+ reserves: {
16764
+ before: null,
16765
+ after: metrics.heightCss
16766
+ },
16767
+ paint: {
16768
+ band: null,
16769
+ shape: composition.from
16770
+ }
16771
+ };
16772
+ default: return assertNever$12(composition, "Unhandled boundary composition");
16773
+ }
16774
+ }
16775
+ function requireThemeColorToken$1(value) {
16776
+ const token = parseThemeColorToken(value);
16777
+ if (!token) throw new Error(`Expected a theme color token. Received: ${value}`);
16778
+ return token;
16779
+ }
16780
+ var backgroundToken;
16781
+ var init_sectionBoundaryComposition = __esmMin((() => {
16782
+ init_themeColorTokens();
16783
+ init_runtimeBackgroundResolution();
16784
+ init_sectionTransitions();
16785
+ init_types$8();
16786
+ backgroundToken = requireThemeColorToken$1("background");
16787
+ }));
16788
+ var init_compositionBudget = __esmMin((() => {
16789
+ init_types$8();
16790
+ init_sectionBoundaryComposition();
16791
+ }));
16792
+ var defaultHeaderBrandPreferences;
16793
+ var init_compilationPlan = __esmMin((() => {
16794
+ defaultHeaderBrandPreferences = {
16795
+ brandDisplay: "auto",
16796
+ logoSize: "auto"
16222
16797
  };
16223
- asBoundaryOptionId("break:expansive");
16224
- asBoundaryOptionId("break:wave");
16225
- asBoundaryOptionId("break:wave-balanced");
16226
- asBoundaryOptionId("break:wave-gentle");
16227
- asBoundaryOptionId("break:wave-bold");
16228
- asBoundaryOptionId("break:wave-drift");
16229
- asBoundaryOptionId("break:angle-subtle");
16230
- asBoundaryOptionId("break:angle-balanced");
16231
- asBoundaryOptionId("break:angle-steep");
16232
16798
  }));
16799
+ var init_schemaVersionedJson = __esmMin((() => {}));
16233
16800
  function addDuplicateStringIssues(ctx, input) {
16234
16801
  const seen = /* @__PURE__ */ new Set();
16235
16802
  const duplicates = /* @__PURE__ */ new Set();
@@ -16258,14 +16825,11 @@ function validateUniquePageDesignPins(intent, ctx) {
16258
16825
  label: "block Look pin"
16259
16826
  });
16260
16827
  addDuplicateStringIssues(ctx, {
16261
- values: intent.boundaryPins.map((pin) => boundaryKey$1(pin.previousBlockId, pin.nextBlockId)),
16828
+ values: intent.boundaryPins.map((pin) => makeBoundaryId(pin.previousBlockId, pin.nextBlockId)),
16262
16829
  path: ["boundaryPins"],
16263
16830
  label: "boundary pin"
16264
16831
  });
16265
16832
  }
16266
- function boundaryKey$1(previousBlockId, nextBlockId) {
16267
- return makeBoundaryId(previousBlockId, nextBlockId);
16268
- }
16269
16833
  var pageDesignUserPinnedAppearanceChoiceSchema, pageDesignUserPinnedBoundaryChoiceSchema, pageDesignBoundaryTreatmentSchema, pageDesignUserPinnedBoundaryTreatmentChoiceSchema, pageDesignBlockLookPinSchema, pageDesignBoundaryPinSchema, legacyPageDesignBoundaryPinSchema;
16270
16834
  var init_pageDesignIntent = __esmMin((() => {
16271
16835
  init_types$8();
@@ -19332,6 +19896,33 @@ var init_designState = __esmMin((() => {
19332
19896
  authority: styleAuthoritySchema
19333
19897
  }).strict();
19334
19898
  }));
19899
+ var init_pageFlowPresets = __esmMin((() => {
19900
+ init_sectionTransitions();
19901
+ init_types$8();
19902
+ asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
19903
+ })), init_lookResolution = __esmMin((() => {
19904
+ init_appearancePresets();
19905
+ init_types$8();
19906
+ }));
19907
+ var init_siteStyleBlockPolicies = __esmMin((() => {
19908
+ init_lookResolution();
19909
+ init_appearancePresets();
19910
+ init_pageDesignIntent();
19911
+ init_types$8();
19912
+ asPageFlowPresetId("generated-default");
19913
+ asSiteStyleId("site-style:south-west-twerk-school");
19914
+ asAppearancePresetId("hero-brand-scrim-fixed-frame");
19915
+ asLayoutVariantKey("classic");
19916
+ asLayoutVariantKey("micro");
19917
+ asBoundaryOptionId("break:wave");
19918
+ }));
19919
+ var init_joinTreatment = __esmMin((() => {
19920
+ init_pageFlowPresets();
19921
+ init_pageDesignIntent();
19922
+ init_sectionTransitions();
19923
+ init_siteStyleBlockPolicies();
19924
+ init_types$8();
19925
+ }));
19335
19926
  var init_imageSurfaceBackground = __esmMin((() => {
19336
19927
  init_types$8();
19337
19928
  }));
@@ -19364,37 +19955,6 @@ function uniformSectionForeground(token) {
19364
19955
  supportingColor: token
19365
19956
  };
19366
19957
  }
19367
- function compileTransitionRuntime(input) {
19368
- return {
19369
- treatment: input.treatment,
19370
- from: input.from,
19371
- to: input.to,
19372
- cssVars: {
19373
- from: input.from.kind === "surface" && input.from.surface === "hero-image" ? "transparent" : transitionEndpointCssValue(input.from),
19374
- to: transitionEndpointCssValue(input.to)
19375
- }
19376
- };
19377
- }
19378
- function transitionEndpointCssValue(endpoint) {
19379
- switch (endpoint.kind) {
19380
- case "color-token": return themeColorTokenCssValue(endpoint.token);
19381
- case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
19382
- default: return assertNever$12(endpoint);
19383
- }
19384
- }
19385
- function tokenForTransitionSurface(surface) {
19386
- switch (surface) {
19387
- case "base":
19388
- case "hero-image": return "background";
19389
- case "muted-band": return "muted";
19390
- case "accent-band": return "accent";
19391
- case "brand-depth-band": return "primary-800";
19392
- default: return assertNever$12(surface);
19393
- }
19394
- }
19395
- function themeColorTokenCssValue(token) {
19396
- return `rgb(var(--tb-${token}))`;
19397
- }
19398
19958
  var defaultDesignRuntimeRules, defaultComponentRuntimeRules;
19399
19959
  var init_runtimeCompiler = __esmMin((() => {
19400
19960
  init_schema$2();
@@ -19403,8 +19963,9 @@ var init_runtimeCompiler = __esmMin((() => {
19403
19963
  init_styles();
19404
19964
  init_designState();
19405
19965
  init_types$8();
19966
+ init_joinTreatment();
19967
+ init_rawStyleFields();
19406
19968
  init_accessibleSurfaces();
19407
- init_sectionTransitions();
19408
19969
  init_tokens();
19409
19970
  init_gradients();
19410
19971
  init_imageSurfaceBackground();
@@ -19441,8 +20002,7 @@ var init_runtimeCompiler = __esmMin((() => {
19441
20002
  color: "surface",
19442
20003
  opacity: .88
19443
20004
  }
19444
- },
19445
- transitionClassName: (transition, fromSurface, toSurface) => transition === "none" ? null : `site-style-transition-${transition}-from-${fromSurface}-to-${toSurface}`
20005
+ }
19446
20006
  });
19447
20007
  defaultComponentRuntimeRules = defineComponentRuntimeRules({
19448
20008
  panelFrames: {
@@ -21181,22 +21741,12 @@ var init_curatedSiteStyles = __esmMin((() => {
21181
21741
  ];
21182
21742
  curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id, style]));
21183
21743
  }));
21184
- var init_pageDesignEditorCapabilities = __esmMin((() => {})), init_lookResolution = __esmMin((() => {
21185
- init_appearancePresets();
21186
- init_types$8();
21187
- }));
21188
- var init_siteStyleBlockPolicies = __esmMin((() => {
21189
- init_lookResolution();
21190
- init_appearancePresets();
21744
+ var init_pageDesignEditorCapabilities = __esmMin((() => {}));
21745
+ var init_pageDesignEditorBoundaries = __esmMin((() => {
21191
21746
  init_pageDesignIntent();
21192
- init_types$8();
21747
+ init_joinTreatment();
21193
21748
  init_sectionTransitions();
21194
- asPageFlowPresetId("generated-default");
21195
- asSiteStyleId("site-style:south-west-twerk-school");
21196
- asAppearancePresetId("hero-brand-scrim-fixed-frame");
21197
- asLayoutVariantKey("classic");
21198
- asLayoutVariantKey("micro");
21199
- asBoundaryOptionId("break:wave");
21749
+ init_types$8();
21200
21750
  }));
21201
21751
  var init_pageDesignEditorModel = __esmMin((() => {
21202
21752
  init_curatedSiteStyles();
@@ -21205,8 +21755,9 @@ var init_pageDesignEditorModel = __esmMin((() => {
21205
21755
  init_siteStyleBlockPolicies();
21206
21756
  init_pageDesignIntent();
21207
21757
  init_types$8();
21758
+ init_pageFlowPresets();
21759
+ init_pageDesignEditorBoundaries();
21208
21760
  init_sectionTransitions();
21209
- asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
21210
21761
  })), init_concretePalette = __esmMin((() => {
21211
21762
  init_colors();
21212
21763
  })), blockDesignFallback;
@@ -21215,7 +21766,6 @@ var init_resolver = __esmMin((() => {
21215
21766
  blockDesignFallback = {
21216
21767
  sectionSurface: "base",
21217
21768
  contentFrame: plainFrame,
21218
- transitionAfter: "none",
21219
21769
  sectionHeight: "default",
21220
21770
  heroLegibility: "none",
21221
21771
  imageSurfaceTreatment: { kind: "none" },
@@ -21226,7 +21776,7 @@ var init_resolver = __esmMin((() => {
21226
21776
  accordionIconTreatment: null,
21227
21777
  composition: null
21228
21778
  };
21229
- blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
21779
+ blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
21230
21780
  }));
21231
21781
  function parseSiteChromeBackgroundColorToken(value) {
21232
21782
  const token = parseBackgroundAppearanceColorToken(value);
@@ -21340,11 +21890,6 @@ var init_headerRecompile = __esmMin((() => {
21340
21890
  init_headerBrandPresentation();
21341
21891
  init_siteChromeNavPreferences();
21342
21892
  }));
21343
- var init_runtimeBackgroundResolution = __esmMin((() => {
21344
- init_boxBackgroundSchema();
21345
- init_surfaces();
21346
- init_rawStyleFields();
21347
- }));
21348
21893
  var init_previewRuntimeCapabilities = __esmMin((() => {
21349
21894
  init_types$8();
21350
21895
  }));
@@ -21363,38 +21908,9 @@ var init_buttonSelectionCompiler = __esmMin((() => {
21363
21908
  var init_previewCustomization = __esmMin((() => {
21364
21909
  init_types$8();
21365
21910
  }));
21366
- function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
21367
- const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
21368
- const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
21369
- if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
21370
- const transition = compileTransitionRuntime({
21371
- treatment: boundary.treatment,
21372
- from: fromSurface,
21373
- to: toSurface
21374
- });
21375
- return {
21376
- ...boundary,
21377
- from: transition.from,
21378
- to: transition.to,
21379
- cssVars: transition.cssVars
21380
- };
21381
- }
21382
- function contentOwnedBackgroundEndpoint(content) {
21383
- const background = readRecord(readRecord(content?._sectionStyles)?.background);
21384
- if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
21385
- const token = parseThemeColorToken(background.color);
21386
- if (!token) return null;
21387
- return {
21388
- kind: "color-token",
21389
- token
21390
- };
21391
- }
21392
- function readRecord(value) {
21393
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
21394
- }
21395
21911
  var init_previewCompiler = __esmMin((() => {
21912
+ init_sectionTransitions();
21396
21913
  init_schema$2();
21397
- init_tokens();
21398
21914
  init_curated();
21399
21915
  init_concretePalette();
21400
21916
  init_curatedSiteStyles();
@@ -21404,7 +21920,6 @@ var init_previewCompiler = __esmMin((() => {
21404
21920
  init_headerRecompile();
21405
21921
  init_siteChromeNavPreferences();
21406
21922
  init_runtimeCompiler();
21407
- init_rawStyleFields();
21408
21923
  init_runtimeBackgroundResolution();
21409
21924
  init_previewRuntimeCapabilities();
21410
21925
  init_siteStyleThemeCompiler();
@@ -21414,6 +21929,7 @@ var init_previewCompiler = __esmMin((() => {
21414
21929
  init_pageDesignIntent();
21415
21930
  init_previewCustomization();
21416
21931
  init_curatedChoices();
21932
+ init_joinTreatment();
21417
21933
  init_types$8();
21418
21934
  }));
21419
21935
  var init_contentTemplateRuntimeCompiler = __esmMin((() => {
@@ -21515,6 +22031,7 @@ var init_generatedDesignPipeline = __esmMin((() => {
21515
22031
  init_curatedSiteStyles();
21516
22032
  init_lookResolution();
21517
22033
  init_headerOpeningSection();
22034
+ init_joinTreatment();
21518
22035
  init_resolver();
21519
22036
  init_runtimeCompiler();
21520
22037
  init_types$8();
@@ -21883,6 +22400,7 @@ var init_site_styles = __esmMin((() => {
21883
22400
  init_headerRecompile();
21884
22401
  init_headerOpeningForeground();
21885
22402
  init_headerOpeningSection();
22403
+ init_joinTreatment();
21886
22404
  init_lookResolution();
21887
22405
  init_lookOptionGroups();
21888
22406
  init_logoBranding();
@@ -21902,6 +22420,7 @@ var init_site_styles = __esmMin((() => {
21902
22420
  init_runtimeCompiler();
21903
22421
  init_runtimeBackgroundResolution();
21904
22422
  init_sectionTransitions();
22423
+ init_sectionBoundaryComposition();
21905
22424
  init_siteChromeBackgrounds();
21906
22425
  init_siteChromeNavPreferences();
21907
22426
  init_siteStyleThemeCompiler();
@@ -21909,6 +22428,7 @@ var init_site_styles = __esmMin((() => {
21909
22428
  init_sourceCatalogs();
21910
22429
  init_styleTags();
21911
22430
  init_tokenRecipes();
22431
+ init_transitionCatalogue();
21912
22432
  init_siteStyleTypography();
21913
22433
  init_themeParity();
21914
22434
  init_types$8();
@@ -24187,7 +24707,8 @@ var init_schema = __esmMin((() => {
24187
24707
  "eventCategories",
24188
24708
  "eventSeries",
24189
24709
  "venues",
24190
- "staffMembers"
24710
+ "staffMembers",
24711
+ "appointmentServiceCategories"
24191
24712
  ];
24192
24713
  mediaDeliverySchema = z$1.object({
24193
24714
  sizes: z$1.string().optional(),
@@ -28356,7 +28877,7 @@ function successSubmissionFeedback(message) {
28356
28877
  message
28357
28878
  };
28358
28879
  }
28359
- function isRecord$28(value) {
28880
+ function isRecord$29(value) {
28360
28881
  return typeof value === "object" && value !== null && !Array.isArray(value);
28361
28882
  }
28362
28883
  function deepEqualJsonValue(left, right) {
@@ -28366,8 +28887,8 @@ function deepEqualJsonValue(left, right) {
28366
28887
  if (left.length !== right.length) return false;
28367
28888
  return left.every((item, index) => deepEqualJsonValue(item, right[index]));
28368
28889
  }
28369
- if (isRecord$28(left) || isRecord$28(right)) {
28370
- if (!isRecord$28(left) || !isRecord$28(right)) return false;
28890
+ if (isRecord$29(left) || isRecord$29(right)) {
28891
+ if (!isRecord$29(left) || !isRecord$29(right)) return false;
28371
28892
  const leftKeys = Object.keys(left).sort();
28372
28893
  if (!deepEqualJsonValue(leftKeys, Object.keys(right).sort())) return false;
28373
28894
  return leftKeys.every((key) => deepEqualJsonValue(left[key], right[key]));
@@ -28549,12 +29070,12 @@ function resolveBookingSiteId(siteId, form) {
28549
29070
  return siteId || form?.siteId || "";
28550
29071
  }
28551
29072
  function normalizeBookingFormConfig(form) {
28552
- if (!isRecord$27(form)) return null;
29073
+ if (!isRecord$28(form)) return null;
28553
29074
  const id = readNonEmptyString(form.id);
28554
29075
  const name = readNonEmptyString(form.name);
28555
29076
  if (!id || !name) return null;
28556
- const schemaSource = isRecord$27(form.schema) ? form.schema : isRecord$27(form.schemaJson) ? form.schemaJson : null;
28557
- const settingsSource = isRecord$27(form.settings) ? form.settings : isRecord$27(form.settingsJson) ? form.settingsJson : null;
29077
+ const schemaSource = isRecord$28(form.schema) ? form.schema : isRecord$28(form.schemaJson) ? form.schemaJson : null;
29078
+ const settingsSource = isRecord$28(form.settings) ? form.settings : isRecord$28(form.settingsJson) ? form.settingsJson : null;
28558
29079
  return {
28559
29080
  id,
28560
29081
  name,
@@ -28574,8 +29095,8 @@ function normalizeBookingServices(services) {
28574
29095
  };
28575
29096
  if (!services) return [];
28576
29097
  if (Array.isArray(services)) return services.filter(isBookingFormService).map(normalizeService);
28577
- if (isRecord$27(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
28578
- return isRecord$27(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
29098
+ if (isRecord$28(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
29099
+ return isRecord$28(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
28579
29100
  }
28580
29101
  function normalizeBookingSchema(value) {
28581
29102
  if (!value) return void 0;
@@ -28586,7 +29107,7 @@ function normalizeBookingSchema(value) {
28586
29107
  };
28587
29108
  }
28588
29109
  function readBookingFormField(value) {
28589
- if (!isRecord$27(value)) return null;
29110
+ if (!isRecord$28(value)) return null;
28590
29111
  const id = readNonEmptyString(value.id);
28591
29112
  const label = readNonEmptyString(value.label);
28592
29113
  const type = readBookingFieldType(value.type);
@@ -28629,7 +29150,7 @@ function readBookingFieldOptions(value) {
28629
29150
  return options.length > 0 ? options : void 0;
28630
29151
  }
28631
29152
  function readBookingFieldOption(value) {
28632
- if (!isRecord$27(value)) return null;
29153
+ if (!isRecord$28(value)) return null;
28633
29154
  const optionValue = readNonEmptyString(value.value);
28634
29155
  const label = readNonEmptyString(value.label);
28635
29156
  return optionValue && label ? {
@@ -28649,7 +29170,7 @@ function normalizeBookingSettings(value) {
28649
29170
  };
28650
29171
  }
28651
29172
  function isBookingFormService(value) {
28652
- return isRecord$27(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
29173
+ return isRecord$28(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
28653
29174
  }
28654
29175
  function collectBookingCustomFields(data) {
28655
29176
  const customFields = {};
@@ -28699,7 +29220,7 @@ function readStringArray(value) {
28699
29220
  const strings = value.filter((item) => typeof item === "string");
28700
29221
  return strings.length > 0 ? strings : void 0;
28701
29222
  }
28702
- function isRecord$27(value) {
29223
+ function isRecord$28(value) {
28703
29224
  return typeof value === "object" && value !== null;
28704
29225
  }
28705
29226
  function getBookingServicePaymentSelectionMode(service) {
@@ -28900,6 +29421,32 @@ var init_booking_form_state = __esmMin((() => {
28900
29421
  init_booking_form_consent();
28901
29422
  }));
28902
29423
  //#endregion
29424
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-categories.ts
29425
+ function isRecord$27(value) {
29426
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29427
+ }
29428
+ function readCategory(value) {
29429
+ if (!isRecord$27(value)) return [];
29430
+ const { id, name, description } = value;
29431
+ if (typeof id !== "string" || id.trim() === "" || typeof name !== "string") return [];
29432
+ return [{
29433
+ id,
29434
+ name,
29435
+ description: typeof description === "string" ? description : null
29436
+ }];
29437
+ }
29438
+ /**
29439
+ * The ordered service categories carried by a catalogue-shaped services
29440
+ * source (`{ services, categories }`). Every other shape has none, which
29441
+ * reads as a site without categories. Order is preserved: the database owns
29442
+ * it.
29443
+ */
29444
+ function normalizeBookingServiceCategories(services) {
29445
+ if (!isRecord$27(services) || !Array.isArray(services.categories)) return [];
29446
+ return services.categories.flatMap(readCategory);
29447
+ }
29448
+ var init_booking_form_service_categories = __esmMin((() => {}));
29449
+ //#endregion
28903
29450
  //#region ../logger/src/noop.ts
28904
29451
  var noop, logger;
28905
29452
  var init_noop = __esmMin((() => {
@@ -28996,11 +29543,12 @@ function buildBookingFormInteractiveIslandProps(props) {
28996
29543
  source: props
28997
29544
  });
28998
29545
  }
28999
- var blockBookingPaymentTermsAvailabilitySchema, bookingServiceSchema, bookingServicesSourceSchema, bookingConfigSchema, bookingSourceSchema, bookingDisplaySchema, bookingHydrationSchema, bookingRenderSchema, bookingFormIslandMetaCodec, bookingFormIslandMetaKeys, bookingFormBlockRuntime, bookingFormInteractiveIslandPropsCodec;
29546
+ var blockBookingPaymentTermsAvailabilitySchema, bookingServiceSchema, bookingServiceCategorySchema, bookingServicesSourceSchema, bookingConfigSchema, bookingSourceSchema, bookingDisplaySchema, bookingHydrationSchema, bookingRenderSchema, bookingFormIslandMetaCodec, bookingFormIslandMetaKeys, bookingFormBlockRuntime, bookingFormInteractiveIslandPropsCodec;
29000
29547
  var init_booking_form_interactive = __esmMin((() => {
29001
29548
  init_islands();
29002
29549
  init_runtime();
29003
29550
  init_booking_form_state();
29551
+ init_booking_form_service_categories();
29004
29552
  init_booking_runtime_sources();
29005
29553
  init_bookingPaymentTerms();
29006
29554
  init_bookingConsent();
@@ -29023,11 +29571,20 @@ var init_booking_form_interactive = __esmMin((() => {
29023
29571
  priceCurrency: z$1.string().nullable().optional(),
29024
29572
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
29025
29573
  paymentTermsAvailability: blockBookingPaymentTermsAvailabilitySchema.optional(),
29026
- consent: blockBookingConsentSchema.optional()
29574
+ consent: blockBookingConsentSchema.optional(),
29575
+ categoryId: z$1.string().nullable().optional()
29576
+ });
29577
+ bookingServiceCategorySchema = z$1.object({
29578
+ id: z$1.string(),
29579
+ name: z$1.string(),
29580
+ description: z$1.string().nullable()
29027
29581
  });
29028
29582
  bookingServicesSourceSchema = z$1.union([
29029
29583
  z$1.array(bookingServiceSchema),
29030
- z$1.object({ services: z$1.array(bookingServiceSchema).optional() }),
29584
+ z$1.object({
29585
+ services: z$1.array(bookingServiceSchema).optional(),
29586
+ categories: z$1.array(bookingServiceCategorySchema).optional()
29587
+ }),
29031
29588
  z$1.record(z$1.string(), bookingServiceSchema)
29032
29589
  ]);
29033
29590
  bookingConfigSchema = z$1.object({
@@ -29080,6 +29637,7 @@ var init_booking_form_interactive = __esmMin((() => {
29080
29637
  rebookContext: z$1.object({ kind: z$1.literal("portal_rebook") }).nullable().optional(),
29081
29638
  form: bookingConfigSchema.nullable(),
29082
29639
  services: z$1.array(bookingServiceSchema),
29640
+ serviceCategories: z$1.array(bookingServiceCategorySchema).optional(),
29083
29641
  spamProtectionEnabled: z$1.boolean().optional()
29084
29642
  });
29085
29643
  bookingRenderSchema = z$1.object({
@@ -29115,6 +29673,7 @@ var init_booking_form_interactive = __esmMin((() => {
29115
29673
  rebookContext: source.rebookContext ?? null,
29116
29674
  form,
29117
29675
  services,
29676
+ serviceCategories: normalizeBookingServiceCategories(source.services),
29118
29677
  spamProtectionEnabled: source.spamProtectionEnabled
29119
29678
  }
29120
29679
  };
@@ -99337,6 +99896,7 @@ var init_booking_form_headless_state = __esmMin((() => {
99337
99896
  init_src$2();
99338
99897
  init_dist$1();
99339
99898
  init_booking_form_state();
99899
+ init_booking_form_service_categories();
99340
99900
  init_booking_form_validationErrors();
99341
99901
  init_booking_form_field_values();
99342
99902
  unsafeAsBookingFieldId = (id) => id;
@@ -99901,6 +100461,7 @@ function useHeadlessBookingFlow(options) {
99901
100461
  data,
99902
100462
  services: {
99903
100463
  items: options.services,
100464
+ categories: readServiceCategories(options),
99904
100465
  selected: selectedService,
99905
100466
  defaultServiceId,
99906
100467
  requiresSelection: Boolean(options.requiresServiceSelection)
@@ -99965,6 +100526,9 @@ function useHeadlessBookingFlow(options) {
99965
100526
  actions
99966
100527
  };
99967
100528
  }
100529
+ function readServiceCategories(options) {
100530
+ return options.serviceCategories ?? EMPTY_SERVICE_CATEGORIES;
100531
+ }
99968
100532
  function navigateToCheckout(checkoutUrl) {
99969
100533
  window.location.href = checkoutUrl;
99970
100534
  }
@@ -99978,6 +100542,7 @@ function useLatestRef(value) {
99978
100542
  }, [value]);
99979
100543
  return ref;
99980
100544
  }
100545
+ var EMPTY_SERVICE_CATEGORIES;
99981
100546
  var init_useHeadlessBookingFlow = __esmMin((() => {
99982
100547
  init_client();
99983
100548
  init_src();
@@ -99993,6 +100558,7 @@ var init_useHeadlessBookingFlow = __esmMin((() => {
99993
100558
  init_bookingPaymentOptions();
99994
100559
  init_useHeadlessBookingFlow_state();
99995
100560
  init_booking_form_validationErrors();
100561
+ EMPTY_SERVICE_CATEGORIES = [];
99996
100562
  }));
99997
100563
  //#endregion
99998
100564
  //#region ../blocks/src/system/runtime/hooks/usePolling.state.ts
@@ -100842,10 +101408,102 @@ var init_BookingConsentAgreement = __esmMin((() => {
100842
101408
  init_RichText();
100843
101409
  }));
100844
101410
  //#endregion
100845
- //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
101411
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-choices.tsx
100846
101412
  function optionCardClassName(isSelected) {
100847
101413
  return `rb-option-card${isSelected ? " rb-option-card--selected" : ""}`;
100848
101414
  }
101415
+ function SelectedCheck() {
101416
+ return /* @__PURE__ */ jsx("div", {
101417
+ className: "rb-option-check",
101418
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
101419
+ });
101420
+ }
101421
+ /**
101422
+ * The service buttons. A site with no categories keeps its single grid; a
101423
+ * categorised site gets a heading per category, then "Other", above the same
101424
+ * grid (issue 1403). Empty categories are skipped, and there are no intros
101425
+ * here, to keep the step compact.
101426
+ */
101427
+ function DefaultServiceChoices({ booking }) {
101428
+ const layout = layoutServicesByCategory(booking.services.categories, booking.services.items.map((service) => ({
101429
+ service,
101430
+ categoryId: service.categoryId ?? null
101431
+ })), { keepEmptyGroups: false });
101432
+ switch (layout.kind) {
101433
+ case "uncategorized-site": return /* @__PURE__ */ jsxs("div", {
101434
+ className: "rb-reg-stack rb-reg-stack--sm",
101435
+ children: [/* @__PURE__ */ jsx("span", {
101436
+ className: "ab-subtitle",
101437
+ children: "Service"
101438
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
101439
+ booking,
101440
+ choices: layout.services
101441
+ })]
101442
+ });
101443
+ case "categorized-site": return /* @__PURE__ */ jsx("div", {
101444
+ className: "rb-reg-stack",
101445
+ children: [...layout.groups.map((group) => ({
101446
+ key: group.category.id,
101447
+ heading: group.category.name,
101448
+ choices: group.services
101449
+ })), ...layout.other.length > 0 ? [{
101450
+ key: "other",
101451
+ heading: "Other",
101452
+ choices: layout.other
101453
+ }] : []].map((group) => /* @__PURE__ */ jsxs("div", {
101454
+ className: "rb-reg-stack rb-reg-stack--sm",
101455
+ "data-booking-service-group": group.key,
101456
+ children: [/* @__PURE__ */ jsx("h3", {
101457
+ className: "ab-subtitle",
101458
+ children: group.heading
101459
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
101460
+ booking,
101461
+ choices: group.choices
101462
+ })]
101463
+ }, group.key))
101464
+ });
101465
+ default: return assertNever$11(layout);
101466
+ }
101467
+ }
101468
+ function ServiceChoiceGrid({ booking, choices }) {
101469
+ const selectedServiceId = booking.services.selected?.id;
101470
+ return /* @__PURE__ */ jsx("div", {
101471
+ className: "rb-reg-grid",
101472
+ children: choices.map(({ service }) => {
101473
+ const isSelected = selectedServiceId === service.id;
101474
+ return /* @__PURE__ */ jsxs("button", {
101475
+ type: "button",
101476
+ onClick: () => booking.actions.selectService(service.id),
101477
+ className: optionCardClassName(isSelected),
101478
+ "aria-pressed": isSelected,
101479
+ children: [/* @__PURE__ */ jsxs("div", {
101480
+ className: "rb-flex-1",
101481
+ children: [
101482
+ /* @__PURE__ */ jsx("div", {
101483
+ className: "ab-subtitle",
101484
+ children: service.title
101485
+ }),
101486
+ service.description ? /* @__PURE__ */ jsx("div", {
101487
+ className: "ab-text rb-on-surface--muted",
101488
+ children: service.description
101489
+ }) : null,
101490
+ service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
101491
+ className: "ab-text rb-on-surface--muted",
101492
+ children: [service.durationMinutes, " minutes"]
101493
+ }) : null
101494
+ ]
101495
+ }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
101496
+ }, service.id);
101497
+ })
101498
+ });
101499
+ }
101500
+ var init_booking_form_service_choices = __esmMin((() => {
101501
+ init_src$2();
101502
+ init_dist$1();
101503
+ init_CheckIcon$1();
101504
+ }));
101505
+ //#endregion
101506
+ //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
100849
101507
  function formatBookingAmount(amount, currency) {
100850
101508
  return formatCurrencyAmount(amount, currency, { decimals: "auto" });
100851
101509
  }
@@ -100936,7 +101594,6 @@ function DefaultBookingStepContent({ booking, errorSummaryId }) {
100936
101594
  function DefaultServiceSelectionStep({ booking }) {
100937
101595
  const services = booking.services.items;
100938
101596
  const resources = booking.resources.items;
100939
- const selectedServiceId = booking.services.selected?.id;
100940
101597
  const selectedResourceId = booking.resources.selectedResourceId;
100941
101598
  const showServiceSelect = booking.services.requiresSelection || services.length > 1;
100942
101599
  const showResourceSelect = booking.resources.requiresSelection || resources.length > 1;
@@ -100951,41 +101608,7 @@ function DefaultServiceSelectionStep({ booking }) {
100951
101608
  className: "ab-text rb-on-surface--muted",
100952
101609
  children: "Choose the service you'd like to book"
100953
101610
  })] }),
100954
- showServiceSelect ? /* @__PURE__ */ jsxs("div", {
100955
- className: "rb-reg-stack rb-reg-stack--sm",
100956
- children: [/* @__PURE__ */ jsx("span", {
100957
- className: "ab-subtitle",
100958
- children: "Service"
100959
- }), /* @__PURE__ */ jsx("div", {
100960
- className: "rb-reg-grid",
100961
- children: services.map((service) => {
100962
- const isSelected = selectedServiceId === service.id;
100963
- return /* @__PURE__ */ jsxs("button", {
100964
- type: "button",
100965
- onClick: () => booking.actions.selectService(service.id),
100966
- className: optionCardClassName(isSelected),
100967
- "aria-pressed": isSelected,
100968
- children: [/* @__PURE__ */ jsxs("div", {
100969
- className: "rb-flex-1",
100970
- children: [
100971
- /* @__PURE__ */ jsx("div", {
100972
- className: "ab-subtitle",
100973
- children: service.title
100974
- }),
100975
- service.description ? /* @__PURE__ */ jsx("div", {
100976
- className: "ab-text rb-on-surface--muted",
100977
- children: service.description
100978
- }) : null,
100979
- service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
100980
- className: "ab-text rb-on-surface--muted",
100981
- children: [service.durationMinutes, " minutes"]
100982
- }) : null
100983
- ]
100984
- }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
100985
- }, service.id);
100986
- })
100987
- })]
100988
- }) : null,
101611
+ showServiceSelect ? /* @__PURE__ */ jsx(DefaultServiceChoices, { booking }) : null,
100989
101612
  booking.resources.state.tag === "loading" ? /* @__PURE__ */ jsxs("div", {
100990
101613
  className: "ab-center",
100991
101614
  children: [/* @__PURE__ */ jsx(SpinnerNode, {
@@ -101564,12 +102187,6 @@ function DefaultBookingErrorSummary({ errors, id }) {
101564
102187
  children: [errors.formError ? /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: errors.formError }) }) : null, Object.entries(errors.fieldErrors).map(([key, message]) => /* @__PURE__ */ jsx("p", { children: message }, key))]
101565
102188
  });
101566
102189
  }
101567
- function SelectedCheck() {
101568
- return /* @__PURE__ */ jsx("div", {
101569
- className: "rb-option-check",
101570
- children: /* @__PURE__ */ jsx(CheckIcon, {})
101571
- });
101572
- }
101573
102190
  var init_booking_form_default_ui = __esmMin((() => {
101574
102191
  init_src$2();
101575
102192
  init_ThemedButton();
@@ -101579,12 +102196,12 @@ var init_booking_form_default_ui = __esmMin((() => {
101579
102196
  init_DatePicker();
101580
102197
  init_TimeSlotSelector();
101581
102198
  init_spinner();
101582
- init_CheckIcon$1();
101583
102199
  init_StepIndicator$1();
101584
102200
  init_FeedbackMessage();
101585
102201
  init_DiscountCodeDisclosure();
101586
102202
  init_FieldValidationMessage();
101587
102203
  init_BookingConsentAgreement();
102204
+ init_booking_form_service_choices();
101588
102205
  }));
101589
102206
  //#endregion
101590
102207
  //#region ../blocks/src/system/runtime/shared/utils/checkoutUrls.ts
@@ -102015,6 +102632,7 @@ var init_booking_form_client = __esmMin((() => {
102015
102632
  formId: render.hydration.formId ?? form?.id ?? "booking-form",
102016
102633
  form,
102017
102634
  services,
102635
+ serviceCategories: render.hydration.serviceCategories,
102018
102636
  initialServiceId,
102019
102637
  initialResourceId,
102020
102638
  initialData: render.hydration.initialData ?? void 0,
@@ -108844,7 +109462,7 @@ var init_event_registration_client = __esmMin((() => {
108844
109462
  * Loads Leaflet CSS/JS from CDN to avoid bundling in the blocks package.
108845
109463
  */
108846
109464
  function isLeafletApi(value) {
108847
- if (!isRecord$30(value)) return false;
109465
+ if (!isRecord$31(value)) return false;
108848
109466
  return typeof value.map === "function" && typeof value.tileLayer === "function" && typeof value.marker === "function";
108849
109467
  }
108850
109468
  function loadLeaflet() {
@@ -114887,6 +115505,16 @@ var init_newsletter_form_client_state = __esmMin((() => {
114887
115505
  }));
114888
115506
  //#endregion
114889
115507
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.submission.ts
115508
+ /**
115509
+ * Logs a refusal with its reason so a QA session can tell the cases apart in
115510
+ * the console, since the visitor-facing copy deliberately does not.
115511
+ */
115512
+ function reportNewsletterSignupRefusal(reason, detail = {}) {
115513
+ console.warn("[newsletter-form] Signup refused before sending", {
115514
+ reason,
115515
+ ...detail
115516
+ });
115517
+ }
114890
115518
  function mapNewsletterFormFieldsToSubscriberInput(fields) {
114891
115519
  return {
114892
115520
  email: readSubmittedText(fields.email),
@@ -114907,7 +115535,7 @@ function resolveNewsletterSubmitTarget(input) {
114907
115535
  case "signed-footer-source": return publishedSourceTarget(input.sourceRef);
114908
115536
  case "preview": return {
114909
115537
  tag: "unavailable",
114910
- reason: "missing_block_source"
115538
+ reason: "preview_only_source"
114911
115539
  };
114912
115540
  default: return assertNever$20(input);
114913
115541
  }
@@ -114969,13 +115597,23 @@ function NewsletterFormClient({ render }) {
114969
115597
  }
114970
115598
  const submitTarget = resolveNewsletterSubmitTarget(render.hydration.target);
114971
115599
  if (submitTarget.tag === "unavailable") {
115600
+ reportNewsletterSignupRefusal(submitTarget.reason, { target: render.hydration.target });
114972
115601
  dispatchSubmission({
114973
115602
  type: "failed",
114974
115603
  error: newsletterFormError(SIGNUPS_PAUSED_MESSAGE)
114975
115604
  });
114976
115605
  return;
114977
115606
  }
114978
- if (render.hydration.setupRevision === null || render.display.availability === "unavailable") {
115607
+ if (render.hydration.setupRevision === null) {
115608
+ reportNewsletterSignupRefusal("setup_not_configured");
115609
+ dispatchSubmission({
115610
+ type: "failed",
115611
+ error: newsletterFormError(SIGNUPS_PAUSED_MESSAGE)
115612
+ });
115613
+ return;
115614
+ }
115615
+ if (render.display.availability === "unavailable") {
115616
+ reportNewsletterSignupRefusal("destination_unavailable", { setupRevision: render.hydration.setupRevision });
114979
115617
  dispatchSubmission({
114980
115618
  type: "failed",
114981
115619
  error: newsletterFormError(SIGNUPS_PAUSED_MESSAGE)
@@ -114985,6 +115623,7 @@ function NewsletterFormClient({ render }) {
114985
115623
  const selection = readVisitorSelection(formData, render.hydration);
114986
115624
  const selectionError = visitorSelectionError(selection);
114987
115625
  if (selectionError) {
115626
+ if (selection.tag === "unusable") reportNewsletterSignupRefusal("choices_unusable", { choiceProvider: render.hydration.choiceProvider });
114988
115627
  dispatchSubmission({
114989
115628
  type: "failed",
114990
115629
  error: selectionError
@@ -117471,16 +118110,16 @@ const interpolateRichTextTokensOptionsSchema = z$1.object({ tokens: z$1.array(su
117471
118110
  const richTextTokenPattern = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
117472
118111
  function getSiteName(context) {
117473
118112
  const viewModel = context.viewModel;
117474
- if (!isRecord$30(viewModel)) return null;
118113
+ if (!isRecord$31(viewModel)) return null;
117475
118114
  const site = viewModel.site;
117476
- if (isRecord$30(site) && typeof site.title === "string") {
118115
+ if (isRecord$31(site) && typeof site.title === "string") {
117477
118116
  const title = site.title.trim();
117478
118117
  return title.length > 0 ? title : null;
117479
118118
  }
117480
118119
  const root = viewModel.$root;
117481
- if (isRecord$30(root)) {
118120
+ if (isRecord$31(root)) {
117482
118121
  const rootSite = root.site;
117483
- if (isRecord$30(rootSite) && typeof rootSite.title === "string") {
118122
+ if (isRecord$31(rootSite) && typeof rootSite.title === "string") {
117484
118123
  const title = rootSite.title.trim();
117485
118124
  if (title.length > 0) return title;
117486
118125
  }
@@ -117502,7 +118141,7 @@ function replaceTokenText(input, allowedTokens, tokenValues) {
117502
118141
  });
117503
118142
  }
117504
118143
  function visitRichTextNode(node, allowedTokens, tokenValues) {
117505
- if (!isRecord$30(node)) return node;
118144
+ if (!isRecord$31(node)) return node;
117506
118145
  let changed = false;
117507
118146
  const nextNode = { ...node };
117508
118147
  if (nextNode.type === "text" && typeof nextNode.text === "string") {
@@ -117523,7 +118162,7 @@ function visitRichTextNode(node, allowedTokens, tokenValues) {
117523
118162
  return changed ? nextNode : node;
117524
118163
  }
117525
118164
  function interpolateRichTextTokens(value, options, context) {
117526
- if (!isRecord$30(value)) return value;
118165
+ if (!isRecord$31(value)) return value;
117527
118166
  return visitRichTextNode(value, new Set(options.tokens), {
117528
118167
  year: String((/* @__PURE__ */ new Date()).getFullYear()),
117529
118168
  site_name: getSiteName(context)
@@ -118987,18 +119626,43 @@ const publicAppointmentServiceSchema = z$1.object({
118987
119626
  pricing: publicPricingSchema.optional(),
118988
119627
  priceAmount: z$1.number().nullable(),
118989
119628
  priceCurrency: z$1.string().nullable(),
118990
- location: publicLocationSchema
119629
+ location: publicLocationSchema,
119630
+ categoryId: z$1.string().nullable().optional().transform((value) => value ?? null)
119631
+ });
119632
+ const publicServiceCategorySchema = z$1.object({
119633
+ id: z$1.string().min(1),
119634
+ name: z$1.string(),
119635
+ description: z$1.string().nullable().optional().transform((value) => value ?? null)
118991
119636
  });
118992
- const publicAppointmentServicesSchema = z$1.array(publicAppointmentServiceSchema);
119637
+ /**
119638
+ * The loader hands over the catalogue `{ services, categories }` (issue 1403).
119639
+ * A bare services array is still accepted and read as a site with no
119640
+ * categories, so callers from before categories keep the flat list.
119641
+ */
119642
+ const publicAppointmentServicesSchema = z$1.union([z$1.array(publicAppointmentServiceSchema).transform((services) => ({
119643
+ services,
119644
+ categories: []
119645
+ })), z$1.object({
119646
+ services: z$1.array(publicAppointmentServiceSchema),
119647
+ categories: z$1.array(publicServiceCategorySchema)
119648
+ })]);
118993
119649
  const EMPTY_MESSAGE = "No appointments are available right now.";
118994
119650
  const ERROR_MESSAGE = "Appointments are temporarily unavailable. Please try again shortly.";
119651
+ const OTHER_GROUP_KEY = "other";
119652
+ const OTHER_GROUP_HEADING = "Other";
118995
119653
  function parseServices(value) {
118996
119654
  const result = publicAppointmentServicesSchema.safeParse(value);
118997
119655
  return result.success ? {
118998
119656
  kind: "valid",
118999
- services: result.data
119657
+ services: result.data.services,
119658
+ categories: result.data.categories
119000
119659
  } : { kind: "invalid" };
119001
119660
  }
119661
+ function readCategoryFilter(value) {
119662
+ if (typeof value !== "string") return null;
119663
+ const trimmed = value.trim();
119664
+ return trimmed.length > 0 ? trimmed : null;
119665
+ }
119002
119666
  function formatAppointmentDuration(durationMinutes) {
119003
119667
  return `${durationMinutes} min`;
119004
119668
  }
@@ -119072,8 +119736,44 @@ function buildRows(services, pathname, servicePathPrefix) {
119072
119736
  detailsHref: servicePathPrefix && service.slug ? `${servicePathPrefix.replace(/\/$/, "")}/${encodeURIComponent(service.slug)}` : null
119073
119737
  }));
119074
119738
  }
119739
+ const EMPTY_VIEW = {
119740
+ kind: "empty",
119741
+ message: EMPTY_MESSAGE
119742
+ };
119743
+ function categoryGroup(category, services, toRows) {
119744
+ return {
119745
+ key: category.id,
119746
+ heading: category.name,
119747
+ intro: category.description,
119748
+ rows: toRows(services)
119749
+ };
119750
+ }
119751
+ function viewFromLayout(layout, toRows) {
119752
+ switch (layout.kind) {
119753
+ case "uncategorized-site": return layout.services.length === 0 ? EMPTY_VIEW : {
119754
+ kind: "items",
119755
+ rows: toRows(layout.services)
119756
+ };
119757
+ case "categorized-site": {
119758
+ const groups = layout.groups.map((group) => categoryGroup(group.category, group.services, toRows));
119759
+ const other = layout.other.length === 0 ? [] : [{
119760
+ key: OTHER_GROUP_KEY,
119761
+ heading: OTHER_GROUP_HEADING,
119762
+ intro: null,
119763
+ rows: toRows(layout.other)
119764
+ }];
119765
+ const allGroups = [...groups, ...other];
119766
+ return allGroups.length === 0 ? EMPTY_VIEW : {
119767
+ kind: "groups",
119768
+ groups: allGroups
119769
+ };
119770
+ }
119771
+ default: return assertNever$20(layout);
119772
+ }
119773
+ }
119075
119774
  function buildAppointmentServicesView(input) {
119076
- if (!input.pathname) return {
119775
+ const pathname = input.pathname;
119776
+ if (!pathname) return {
119077
119777
  kind: "error",
119078
119778
  message: ERROR_MESSAGE
119079
119779
  };
@@ -119082,76 +119782,128 @@ function buildAppointmentServicesView(input) {
119082
119782
  kind: "error",
119083
119783
  message: ERROR_MESSAGE
119084
119784
  };
119085
- if (parsed.services.length === 0) return {
119086
- kind: "empty",
119087
- message: EMPTY_MESSAGE
119088
- };
119089
- return {
119090
- kind: "items",
119091
- rows: buildRows(parsed.services, input.pathname, input.servicePathPrefix ?? null)
119092
- };
119785
+ const toRows = (services) => buildRows(services, pathname, input.servicePathPrefix ?? null);
119786
+ const view = selectCategoryView(parsed.categories, parsed.services, readCategoryFilter(input.categoryId));
119787
+ switch (view.kind) {
119788
+ case "single": return view.group.services.length === 0 ? EMPTY_VIEW : {
119789
+ kind: "groups",
119790
+ groups: [categoryGroup(view.group.category, view.group.services, toRows)]
119791
+ };
119792
+ case "all":
119793
+ case "fallback-all": return viewFromLayout(view.layout, toRows);
119794
+ default: return assertNever$20(view);
119795
+ }
119093
119796
  }
119094
- function AppointmentServicesNode({ sectionHeading, services, pathname, servicePathPrefix, className }) {
119797
+ function AppointmentServiceRows({ rows, RowTitle }) {
119798
+ return /* @__PURE__ */ jsx("div", {
119799
+ className: "appointment-services-rows rb-flex rb-flex-col",
119800
+ children: rows.map((row) => /* @__PURE__ */ jsxs("article", {
119801
+ className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
119802
+ children: [
119803
+ /* @__PURE__ */ jsxs("div", {
119804
+ className: "appointment-services-row-main rb-min-w-0",
119805
+ children: [
119806
+ /* @__PURE__ */ jsx(RowTitle, {
119807
+ className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
119808
+ children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
119809
+ href: row.detailsHref,
119810
+ children: row.title
119811
+ }) : row.title
119812
+ }),
119813
+ row.description ? /* @__PURE__ */ jsx("p", {
119814
+ className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
119815
+ children: row.description
119816
+ }) : null,
119817
+ /* @__PURE__ */ jsxs("p", {
119818
+ className: "appointment-services-row-meta rb-caption status-muted",
119819
+ children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
119820
+ })
119821
+ ]
119822
+ }),
119823
+ row.priceLabel ? /* @__PURE__ */ jsx("span", {
119824
+ className: "appointment-services-row-price rb-text-lg",
119825
+ children: row.priceLabel
119826
+ }) : null,
119827
+ /* @__PURE__ */ jsx(ThemedButtonLink, {
119828
+ variant: "secondary",
119829
+ size: "sm",
119830
+ extraClassName: "appointment-services-row-action",
119831
+ href: row.href,
119832
+ label: "Check times",
119833
+ screenReaderSuffix: ` for ${row.title}`
119834
+ })
119835
+ ]
119836
+ }, row.id))
119837
+ });
119838
+ }
119839
+ function AppointmentServiceGroups({ groups, hasSectionHeading, idPrefix }) {
119840
+ const GroupHeading = hasSectionHeading ? "h3" : "h2";
119841
+ const RowTitle = hasSectionHeading ? "h4" : "h3";
119842
+ return /* @__PURE__ */ jsx("div", {
119843
+ className: "appointment-services-groups rb-flex rb-flex-col rb-gap-10",
119844
+ children: groups.map((group) => {
119845
+ const headingId = `${idPrefix}-group-${group.key}`;
119846
+ return /* @__PURE__ */ jsxs("section", {
119847
+ className: "appointment-services-group rb-flex rb-flex-col rb-gap-2",
119848
+ "aria-labelledby": headingId,
119849
+ "data-appointment-services-group": group.key,
119850
+ children: [/* @__PURE__ */ jsxs("div", {
119851
+ className: "appointment-services-group-heading",
119852
+ children: [/* @__PURE__ */ jsx(GroupHeading, {
119853
+ id: headingId,
119854
+ className: "appointment-services-group-title rb-text-sm rb-font-semibold rb-uppercase rb-tracking-wide",
119855
+ children: group.heading
119856
+ }), group.intro ? /* @__PURE__ */ jsx("p", {
119857
+ className: "appointment-services-group-intro rb-text-sm rb-leading-6 rb-max-w-prose status-muted",
119858
+ children: group.intro
119859
+ }) : null]
119860
+ }), /* @__PURE__ */ jsx(AppointmentServiceRows, {
119861
+ rows: group.rows,
119862
+ RowTitle
119863
+ })]
119864
+ }, group.key);
119865
+ })
119866
+ });
119867
+ }
119868
+ function AppointmentServicesNode({ sectionHeading, categoryId, services, pathname, servicePathPrefix, blockId, className }) {
119095
119869
  const view = buildAppointmentServicesView({
119096
119870
  services,
119871
+ categoryId,
119097
119872
  pathname: typeof pathname === "string" ? pathname : void 0,
119098
119873
  servicePathPrefix: typeof servicePathPrefix === "string" ? servicePathPrefix : null
119099
119874
  });
119100
119875
  const rootClassName = `appointment-services ${className ?? ""}`.trim();
119101
- const RowTitle = sectionHeading ? "h3" : "h2";
119102
- if (view.kind === "error" || view.kind === "empty") return /* @__PURE__ */ jsx("div", {
119103
- className: rootClassName,
119104
- "data-appointment-services-state": view.kind,
119105
- children: /* @__PURE__ */ jsx("p", {
119106
- className: "rb-caption status-muted",
119107
- role: "status",
119108
- children: view.message
119109
- })
119110
- });
119111
- return /* @__PURE__ */ jsx("div", {
119112
- className: `${rootClassName} rb-flex rb-flex-col`,
119113
- "data-appointment-services-state": "populated",
119114
- children: /* @__PURE__ */ jsx("div", {
119115
- className: "appointment-services-rows rb-flex rb-flex-col",
119116
- children: view.rows.map((row) => /* @__PURE__ */ jsxs("article", {
119117
- className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
119118
- children: [
119119
- /* @__PURE__ */ jsxs("div", {
119120
- className: "appointment-services-row-main rb-min-w-0",
119121
- children: [
119122
- /* @__PURE__ */ jsx(RowTitle, {
119123
- className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
119124
- children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
119125
- href: row.detailsHref,
119126
- children: row.title
119127
- }) : row.title
119128
- }),
119129
- row.description ? /* @__PURE__ */ jsx("p", {
119130
- className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
119131
- children: row.description
119132
- }) : null,
119133
- /* @__PURE__ */ jsxs("p", {
119134
- className: "appointment-services-row-meta rb-caption status-muted",
119135
- children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
119136
- })
119137
- ]
119138
- }),
119139
- row.priceLabel ? /* @__PURE__ */ jsx("span", {
119140
- className: "appointment-services-row-price rb-text-lg",
119141
- children: row.priceLabel
119142
- }) : null,
119143
- /* @__PURE__ */ jsx(ThemedButtonLink, {
119144
- variant: "secondary",
119145
- size: "sm",
119146
- extraClassName: "appointment-services-row-action",
119147
- href: row.href,
119148
- label: "Check times",
119149
- screenReaderSuffix: ` for ${row.title}`
119150
- })
119151
- ]
119152
- }, row.id))
119153
- })
119154
- });
119876
+ const hasSectionHeading = Boolean(sectionHeading);
119877
+ switch (view.kind) {
119878
+ case "error":
119879
+ case "empty": return /* @__PURE__ */ jsx("div", {
119880
+ className: rootClassName,
119881
+ "data-appointment-services-state": view.kind,
119882
+ children: /* @__PURE__ */ jsx("p", {
119883
+ className: "rb-caption status-muted",
119884
+ role: "status",
119885
+ children: view.message
119886
+ })
119887
+ });
119888
+ case "items": return /* @__PURE__ */ jsx("div", {
119889
+ className: `${rootClassName} rb-flex rb-flex-col`,
119890
+ "data-appointment-services-state": "populated",
119891
+ children: /* @__PURE__ */ jsx(AppointmentServiceRows, {
119892
+ rows: view.rows,
119893
+ RowTitle: hasSectionHeading ? "h3" : "h2"
119894
+ })
119895
+ });
119896
+ case "groups": return /* @__PURE__ */ jsx("div", {
119897
+ className: `${rootClassName} rb-flex rb-flex-col`,
119898
+ "data-appointment-services-state": "populated",
119899
+ children: /* @__PURE__ */ jsx(AppointmentServiceGroups, {
119900
+ groups: view.groups,
119901
+ hasSectionHeading,
119902
+ idPrefix: `appointment-services-${blockId ?? "block"}`
119903
+ })
119904
+ });
119905
+ default: return assertNever$20(view);
119906
+ }
119155
119907
  }
119156
119908
  //#endregion
119157
119909
  //#region ../blocks/src/system/runtime/nodes/booking-form.server.tsx
@@ -127352,9 +128104,127 @@ function generateFooterBottomTextLinkStyles(footer, themeId, tokens) {
127352
128104
  return cssChunks.join("\n");
127353
128105
  }
127354
128106
  //#endregion
128107
+ //#region ../theme-core/src/layout/boundaryCss.ts
128108
+ init_sectionTransitions();
128109
+ init_transitionCatalogue();
128110
+ /**
128111
+ * Section-break CSS, generated from the transition catalogue (issue 1422).
128112
+ *
128113
+ * The renderer emits one element per shown join:
128114
+ *
128115
+ * <div data-block-boundary data-boundary-treatment="wave-edge-gentle"
128116
+ * data-boundary-placement="previous"
128117
+ * style="--boundary-shape-paint: …; --boundary-band-paint: …">
128118
+ *
128119
+ * Every selector matches an exact attribute value, so there is no reliance on
128120
+ * prefix ordering in the cascade and nothing reads colours out of a string.
128121
+ * The shape layer is `::after`; the band layer (paint → paint only) is
128122
+ * `::before`.
128123
+ */
128124
+ const waveMaskSvgPaths = {
128125
+ classic: "M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z",
128126
+ balanced: "M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z",
128127
+ gentle: "M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z",
128128
+ bold: "M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z",
128129
+ drift: "M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z"
128130
+ };
128131
+ function waveMaskDataUrl(mask) {
128132
+ return svgDataUrl(`<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='${waveMaskSvgPaths[mask]}' fill='white'/></svg>`);
128133
+ }
128134
+ /**
128135
+ * How each placement anchors the layers against the zero-height boundary
128136
+ * element that sits exactly on the join.
128137
+ *
128138
+ * - `centred` straddles the join, band under shape.
128139
+ * - `previous` sits above the join, inside the previous section.
128140
+ * - `next` sits below the join, inside the next section, with the shape
128141
+ * flipped so it reads as the mirror of `previous`.
128142
+ */
128143
+ const placementRules = {
128144
+ centred: {
128145
+ shape: ["top: 0;", "transform: translateY(-50%);"],
128146
+ band: ["top: 0;", "transform: translateY(-50%);"]
128147
+ },
128148
+ previous: {
128149
+ shape: ["bottom: 0;"],
128150
+ band: null
128151
+ },
128152
+ next: {
128153
+ shape: ["top: 0;", "transform: scaleY(-1);"],
128154
+ band: null
128155
+ }
128156
+ };
128157
+ function generateBoundaryCss(selector) {
128158
+ const boundary = `${selector} [data-block-boundary]`;
128159
+ return [
128160
+ rule$1(boundary, [
128161
+ "position: relative;",
128162
+ "z-index: 3;",
128163
+ "height: 0;",
128164
+ "pointer-events: none;"
128165
+ ]),
128166
+ 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));"]),
128167
+ 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));"]),
128168
+ rule$1(`${boundary}::before,\n${boundary}::after`, [
128169
+ "content: '';",
128170
+ "position: absolute;",
128171
+ "left: 0;",
128172
+ "right: 0;",
128173
+ "height: var(--boundary-height);",
128174
+ "pointer-events: none;"
128175
+ ]),
128176
+ rule$1(`${boundary}::before`, ["z-index: 0;", "background: var(--boundary-band-paint, transparent);"]),
128177
+ rule$1(`${boundary}::after`, ["z-index: 1;"]),
128178
+ ...placementCss(boundary),
128179
+ ...transitionCatalogueEntries.flatMap((entry) => treatmentCss(boundary, entry))
128180
+ ].join("\n\n");
128181
+ }
128182
+ function placementCss(boundary) {
128183
+ return Object.keys(placementRules).flatMap((placement) => {
128184
+ const placed = `${boundary}[data-boundary-placement='${placement}']`;
128185
+ const rules = placementRules[placement];
128186
+ return [rule$1(`${placed}::after`, rules.shape), rules.band ? rule$1(`${placed}::before`, rules.band) : rule$1(`${placed}::before`, ["content: none;"])];
128187
+ });
128188
+ }
128189
+ function treatmentCss(boundary, entry) {
128190
+ const selected = `${boundary}[data-boundary-treatment='${entry.intent}']`;
128191
+ return [rule$1(selected, [`--boundary-height: ${boundaryRuntimeMetricsForDecorativeTransition(entry.intent).heightCss};`]), rule$1(`${selected}::after`, shapeDeclarations(entry))];
128192
+ }
128193
+ function shapeDeclarations(entry) {
128194
+ switch (entry.shape.kind) {
128195
+ case "fade": return [
128196
+ "/* Fallback for browsers without color-mix(). */",
128197
+ "background: linear-gradient(to bottom, transparent 0%, var(--boundary-shape-paint) 100%);",
128198
+ "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%);"
128199
+ ];
128200
+ case "wave": {
128201
+ const mask = waveMaskDataUrl(entry.shape.mask);
128202
+ return [
128203
+ "background: var(--boundary-shape-paint);",
128204
+ `mask-image: url("${mask}");`,
128205
+ "mask-repeat: no-repeat;",
128206
+ "mask-size: 100% 100%;",
128207
+ `-webkit-mask-image: url("${mask}");`,
128208
+ "-webkit-mask-repeat: no-repeat;",
128209
+ "-webkit-mask-size: 100% 100%;"
128210
+ ];
128211
+ }
128212
+ case "angle": return [
128213
+ "height: calc(var(--boundary-height) + 2px);",
128214
+ "background: var(--boundary-shape-paint);",
128215
+ `clip-path: ${anglePolygonCss(entry.shape.polygon)};`
128216
+ ];
128217
+ }
128218
+ }
128219
+ function rule$1(selector, declarations) {
128220
+ return `${selector} {\n ${declarations.join("\n ")}\n}`;
128221
+ }
128222
+ function svgDataUrl(svg) {
128223
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
128224
+ }
128225
+ //#endregion
127355
128226
  //#region ../theme-core/src/layout/generateLayoutCss.ts
127356
128227
  init_breakpoints();
127357
- init_sectionTransitions();
127358
128228
  /**
127359
128229
  * Generate layout CSS for theme scope (background, text color)
127360
128230
  *
@@ -127378,7 +128248,7 @@ function generateLayoutCss(options) {
127378
128248
  if (heroTypographyCss) cssChunks.push(heroTypographyCss);
127379
128249
  const bodyTextTypographyCss = generateBodyTextTypographyCss(themeId, theme);
127380
128250
  if (bodyTextTypographyCss) cssChunks.push(bodyTextTypographyCss);
127381
- cssChunks.push(generateSiteStyleTransitionCss(themeId));
128251
+ cssChunks.push(generateBoundaryCss(selector));
127382
128252
  return cssChunks.join("\n\n");
127383
128253
  }
127384
128254
  function toPaddingValue(preset) {
@@ -127389,193 +128259,6 @@ function toPaddingValue(preset) {
127389
128259
  relaxed: "1.5rem"
127390
128260
  }[preset];
127391
128261
  }
127392
- 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>");
127393
- 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>");
127394
- 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>");
127395
- 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>");
127396
- 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>");
127397
- function generateSiteStyleTransitionCss(themeId) {
127398
- const selector = `:where([data-theme-scope="${themeId}"])`;
127399
- const compactBoundaryHeight = requiredBoundaryHeightCss("wave-edge-gentle");
127400
- const mediumBoundaryHeight = requiredBoundaryHeightCss("wave-edge");
127401
- const largeBoundaryHeight = requiredBoundaryHeightCss("wave-edge-bold");
127402
- return `${selector} [data-style-group^='site-style-transition-'] {
127403
- position: relative;
127404
- z-index: 2;
127405
- --site-style-transition-from: transparent;
127406
- --site-style-transition-to: rgb(var(--tb-background));
127407
- }
127408
-
127409
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-'] {
127410
- position: relative;
127411
- z-index: 3;
127412
- height: 0;
127413
- pointer-events: none;
127414
- --site-style-boundary-height: ${mediumBoundaryHeight};
127415
- }
127416
-
127417
- ${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section']) {
127418
- padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));
127419
- }
127420
-
127421
- ${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section']) {
127422
- padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));
127423
- }
127424
-
127425
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::before,
127426
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::before,
127427
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::before {
127428
- content: '';
127429
- position: absolute;
127430
- left: 0;
127431
- right: 0;
127432
- top: 0;
127433
- transform: translateY(-50%);
127434
- z-index: 0;
127435
- height: var(--site-style-boundary-height);
127436
- background: var(--site-style-transition-from);
127437
- pointer-events: none;
127438
- }
127439
-
127440
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after,
127441
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after,
127442
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
127443
- content: '';
127444
- position: absolute;
127445
- left: 0;
127446
- right: 0;
127447
- top: 0;
127448
- transform: translateY(-50%);
127449
- z-index: 1;
127450
- height: var(--site-style-boundary-height);
127451
- pointer-events: none;
127452
- }
127453
-
127454
- ${selector} [data-style-group*='-from-base-'] {
127455
- --site-style-transition-from: rgb(var(--tb-background));
127456
- }
127457
-
127458
- ${selector} [data-style-group*='-from-muted-band-'] {
127459
- --site-style-transition-from: rgb(var(--tb-muted));
127460
- }
127461
-
127462
- ${selector} [data-style-group*='-from-accent-band-'] {
127463
- --site-style-transition-from: rgb(var(--tb-accent));
127464
- }
127465
-
127466
- ${selector} [data-style-group*='-from-brand-depth-band-'] {
127467
- --site-style-transition-from: rgb(var(--tb-primary-800));
127468
- }
127469
-
127470
- ${selector} [data-style-group$='-to-base'],
127471
- ${selector} [data-style-group$='-to-hero-image'] {
127472
- --site-style-transition-to: rgb(var(--tb-background));
127473
- }
127474
-
127475
- ${selector} [data-style-group$='-to-muted-band'] {
127476
- --site-style-transition-to: rgb(var(--tb-muted));
127477
- }
127478
-
127479
- ${selector} [data-style-group$='-to-accent-band'] {
127480
- --site-style-transition-to: rgb(var(--tb-accent));
127481
- }
127482
-
127483
- ${selector} [data-style-group$='-to-brand-depth-band'] {
127484
- --site-style-transition-to: rgb(var(--tb-primary-800));
127485
- }
127486
-
127487
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
127488
- /* Fallback for browsers without color-mix(). */
127489
- background: linear-gradient(
127490
- to bottom,
127491
- transparent 0%,
127492
- var(--site-style-transition-to) 100%
127493
- );
127494
- background: linear-gradient(
127495
- to bottom,
127496
- transparent 0%,
127497
- color-mix(in srgb, var(--site-style-transition-to) 16%, transparent) 34%,
127498
- color-mix(in srgb, var(--site-style-transition-to) 68%, transparent) 78%,
127499
- var(--site-style-transition-to) 100%
127500
- );
127501
- }
127502
-
127503
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-long-'] {
127504
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-long")};
127505
- }
127506
-
127507
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-expansive-'] {
127508
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-expansive")};
127509
- }
127510
-
127511
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after {
127512
- height: calc(var(--site-style-boundary-height) + 2px);
127513
- background: var(--site-style-transition-to);
127514
- clip-path: polygon(0 42%, 100% 8%, 100% 100%, 0 100%);
127515
- }
127516
-
127517
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-'] {
127518
- --site-style-boundary-height: ${compactBoundaryHeight};
127519
- }
127520
-
127521
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-']::after {
127522
- clip-path: polygon(0 34%, 100% 18%, 100% 100%, 0 100%);
127523
- }
127524
-
127525
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-'] {
127526
- --site-style-boundary-height: ${largeBoundaryHeight};
127527
- }
127528
-
127529
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-']::after {
127530
- clip-path: polygon(0 54%, 100% 0%, 100% 100%, 0 100%);
127531
- }
127532
-
127533
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after {
127534
- background: var(--site-style-transition-to);
127535
- mask-image: url("${classicWaveTransitionMask}");
127536
- mask-repeat: no-repeat;
127537
- mask-size: 100% 100%;
127538
- -webkit-mask-image: url("${classicWaveTransitionMask}");
127539
- -webkit-mask-repeat: no-repeat;
127540
- -webkit-mask-size: 100% 100%;
127541
- }
127542
-
127543
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-balanced-']::after {
127544
- mask-image: url("${balancedWaveTransitionMask}");
127545
- -webkit-mask-image: url("${balancedWaveTransitionMask}");
127546
- }
127547
-
127548
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-'] {
127549
- --site-style-boundary-height: ${compactBoundaryHeight};
127550
- }
127551
-
127552
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-']::after {
127553
- mask-image: url("${gentleWaveTransitionMask}");
127554
- -webkit-mask-image: url("${gentleWaveTransitionMask}");
127555
- }
127556
-
127557
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-'] {
127558
- --site-style-boundary-height: ${largeBoundaryHeight};
127559
- }
127560
-
127561
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-']::after {
127562
- mask-image: url("${boldWaveTransitionMask}");
127563
- -webkit-mask-image: url("${boldWaveTransitionMask}");
127564
- }
127565
-
127566
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-drift-']::after {
127567
- mask-image: url("${driftWaveTransitionMask}");
127568
- -webkit-mask-image: url("${driftWaveTransitionMask}");
127569
- }`;
127570
- }
127571
- function requiredBoundaryHeightCss(transition) {
127572
- const metrics = boundaryRuntimeMetricsForTransition(transition);
127573
- if (!metrics) throw new Error(`Missing boundary runtime metrics for ${transition}`);
127574
- return metrics.heightCss;
127575
- }
127576
- function svgDataUrl(svg) {
127577
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
127578
- }
127579
128262
  function generateHeroTypographyCss(themeId, theme) {
127580
128263
  const typography = theme.hero?.typography;
127581
128264
  if (!typography) return "";
@@ -127594,24 +128277,23 @@ function generateHeroTypographyCss(themeId, theme) {
127594
128277
  if (microHeadlineRules.length > 0) chunks.push(`${microHeadlineSelector} {\n ${microHeadlineRules.join("\n ")}\n}`);
127595
128278
  const microSubheadlineRules = buildTypographyRules("subheadline", typography.microSubheadlineSize, typography.microSubheadlineLineHeight);
127596
128279
  if (microSubheadlineRules.length > 0) chunks.push(`${microSubheadlineSelector} {\n ${microSubheadlineRules.join("\n ")}\n}`);
127597
- if (typography.responsive?.tablet) {
127598
- const tabletHeadlineRules = buildTypographyRules("headline", typography.responsive.tablet.headlineSize, typography.responsive.tablet.headlineLineHeight);
127599
- const tabletSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.tablet.subheadlineSize, typography.responsive.tablet.subheadlineLineHeight);
127600
- const tabletChunks = [];
127601
- if (tabletHeadlineRules.length > 0) tabletChunks.push(`${headlineSelector} {\n ${tabletHeadlineRules.join("\n ")}\n }`);
127602
- if (tabletSubheadlineRules.length > 0) tabletChunks.push(`${subheadlineSelector} {\n ${tabletSubheadlineRules.join("\n ")}\n }`);
127603
- if (tabletChunks.length > 0) chunks.push(`@media ${MQ_TABLET_ONLY} {\n ${tabletChunks.join("\n\n ")}\n}`);
127604
- }
127605
- if (typography.responsive?.mobile) {
127606
- const mobileHeadlineRules = buildTypographyRules("headline", typography.responsive.mobile.headlineSize, typography.responsive.mobile.headlineLineHeight);
127607
- const mobileSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.mobile.subheadlineSize, typography.responsive.mobile.subheadlineLineHeight);
127608
- const mobileChunks = [];
127609
- if (mobileHeadlineRules.length > 0) mobileChunks.push(`${headlineSelector} {\n ${mobileHeadlineRules.join("\n ")}\n }`);
127610
- if (mobileSubheadlineRules.length > 0) mobileChunks.push(`${subheadlineSelector} {\n ${mobileSubheadlineRules.join("\n ")}\n }`);
127611
- if (mobileChunks.length > 0) chunks.push(`@media ${MQ_SM_DOWN} {\n ${mobileChunks.join("\n\n ")}\n}`);
127612
- }
128280
+ const selectors = {
128281
+ headline: headlineSelector,
128282
+ subheadline: subheadlineSelector
128283
+ };
128284
+ const responsiveChunks = [responsiveHeroTypographyCss(MQ_TABLET_ONLY, selectors, typography.responsive?.tablet), responsiveHeroTypographyCss(MQ_SM_DOWN, selectors, typography.responsive?.mobile)];
128285
+ for (const chunk of responsiveChunks) if (chunk) chunks.push(chunk);
127613
128286
  return chunks.join("\n\n");
127614
128287
  }
128288
+ function responsiveHeroTypographyCss(media, selectors, overrides) {
128289
+ if (!overrides) return null;
128290
+ const headlineRules = buildTypographyRules("headline", overrides.headlineSize, overrides.headlineLineHeight);
128291
+ const subheadlineRules = buildTypographyRules("subheadline", overrides.subheadlineSize, overrides.subheadlineLineHeight);
128292
+ const mediaChunks = [];
128293
+ if (headlineRules.length > 0) mediaChunks.push(`${selectors.headline} {\n ${headlineRules.join("\n ")}\n }`);
128294
+ if (subheadlineRules.length > 0) mediaChunks.push(`${selectors.subheadline} {\n ${subheadlineRules.join("\n ")}\n }`);
128295
+ return mediaChunks.length > 0 ? `@media ${media} {\n ${mediaChunks.join("\n\n ")}\n}` : null;
128296
+ }
127615
128297
  function buildTypographyRules(role, size, lineHeight) {
127616
128298
  const rules = [];
127617
128299
  if (size) rules.push(`font-size: ${toHeroSize(role, size)};`);
@@ -129603,72 +130285,173 @@ function requireThemeResources(resources, blockKind) {
129603
130285
  return resources;
129604
130286
  }
129605
130287
  //#endregion
129606
- //#region ../blocks/src/pageRendererBoundaries.tsx
129607
- init_typeGuards();
129608
- const boundaryKey = (previousBlockId, nextBlockId) => `${previousBlockId}\u0000${nextBlockId}`;
129609
- function isSiteStyleTransitionStyleGroup(value) {
129610
- return value?.startsWith("site-style-transition-") === true;
130288
+ //#region ../blocks/src/pageRenderJoins.ts
130289
+ init_site_styles();
130290
+ /**
130291
+ * Re-points joins across omitted blocks. Pure over joins.
130292
+ *
130293
+ * Joins are keyed by adjacent block-id pairs, so dropping B from [A, B, C]
130294
+ * removes the A→C join as well as B's own, because no A→C join was ever
130295
+ * resolved. For each surviving pair the original join is kept when the pair
130296
+ * was already adjacent. Across a gap, the treatment comes from the join
130297
+ * entering the omitted run (A→first omitted), falling back to the one leaving
130298
+ * it (last omitted→C). Nothing visual is carried: the survivors are composed
130299
+ * from their own content afterwards.
130300
+ *
130301
+ * `survivors` are compared by identity against `originalBlocks`.
130302
+ */
130303
+ function retargetJoinsAcrossOmissions(input) {
130304
+ const { joins, originalBlocks, survivingBlocks } = input;
130305
+ if (joins.length === 0) return joins;
130306
+ if (originalBlocks.every((block) => survivingBlocks.has(block))) return joins;
130307
+ const byPair = joinsByPair(joins);
130308
+ const retargeted = [];
130309
+ let previousSurvivor = null;
130310
+ let run = [];
130311
+ for (const block of originalBlocks) {
130312
+ if (!survivingBlocks.has(block)) {
130313
+ run.push(block);
130314
+ continue;
130315
+ }
130316
+ if (previousSurvivor?.id && block.id) {
130317
+ const join = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : joinAcrossRun(byPair, previousSurvivor.id, block.id, run);
130318
+ if (join) retargeted.push(join);
130319
+ }
130320
+ previousSurvivor = block;
130321
+ run = [];
130322
+ }
130323
+ return retargeted;
129611
130324
  }
129612
- function boundaryMapForAdjacentBlockIds(boundaries) {
129613
- return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
130325
+ function joinAcrossRun(byPair, previousBlockId, nextBlockId, run) {
130326
+ const firstOmittedId = run[0]?.id;
130327
+ const lastOmittedId = run[run.length - 1]?.id;
130328
+ const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
130329
+ const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
130330
+ const donor = entering ?? exiting;
130331
+ return donor ? {
130332
+ previousBlockId,
130333
+ nextBlockId,
130334
+ treatment: donor.treatment
130335
+ } : void 0;
129614
130336
  }
129615
- function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
129616
- const previousBlockId = previousBlock?.id ?? null;
129617
- const nextBlockId = nextBlock?.id ?? null;
129618
- if (!previousBlockId || !nextBlockId) return null;
129619
- return boundaries.get(boundaryKey(previousBlockId, nextBlockId)) ?? null;
130337
+ /**
130338
+ * The single composition site. For every adjacent pair of survivors with ids,
130339
+ * finds its join and composes it from both survivors' final content: what
130340
+ * each paints with and the frame each draws.
130341
+ */
130342
+ function composePageJoins(input) {
130343
+ const byPair = joinsByPair(input.joins);
130344
+ return input.survivors.flatMap((previous, index) => {
130345
+ const next = input.survivors[index + 1];
130346
+ if (!next || !previous.id || !next.id) return [];
130347
+ const join = byPair.get(pairKey(previous.id, next.id));
130348
+ return [{
130349
+ previousBlockId: previous.id,
130350
+ nextBlockId: next.id,
130351
+ join: join ? composeJoin(join, previous, next) : null
130352
+ }];
130353
+ });
130354
+ }
130355
+ function composeJoin(join, previous, next) {
130356
+ return {
130357
+ boundaryId: pairKey(join.previousBlockId, join.nextBlockId),
130358
+ previousBlockId: join.previousBlockId,
130359
+ nextBlockId: join.nextBlockId,
130360
+ treatment: join.treatment,
130361
+ outcome: composeBoundary(sectionFactsForContent(previous.content), sectionFactsForContent(next.content))
130362
+ };
130363
+ }
130364
+ /** Placement, reserves and paint for a join that draws; null otherwise. */
130365
+ function renderedJoinLayout(join) {
130366
+ if (!join || join.outcome.kind !== "shown") return null;
130367
+ return boundaryLayout(join.treatment, join.outcome.composition);
130368
+ }
130369
+ /** Joins with a missing id on either side cannot sit between two blocks. */
130370
+ function joinsByPair(joins) {
130371
+ return new Map(joins.flatMap((join) => join.previousBlockId && join.nextBlockId ? [[pairKey(join.previousBlockId, join.nextBlockId), join]] : []));
130372
+ }
130373
+ /** Slot lookup key for an adjacent pair, in the one boundary-id scheme. */
130374
+ function joinSlotKey(previousBlockId, nextBlockId) {
130375
+ return pairKey(previousBlockId, nextBlockId);
129620
130376
  }
129621
- function renderBoundaryAfterBlock(input) {
129622
- const previousBlockId = input.block.id;
129623
- const nextBlockId = input.nextBlock?.id ?? null;
130377
+ function pairKey(previousBlockId, nextBlockId) {
130378
+ return makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId));
130379
+ }
130380
+ //#endregion
130381
+ //#region ../blocks/src/pageRendererBoundaries.tsx
130382
+ function joinSlotsByPair(slots) {
130383
+ return new Map(slots.map((slot) => [joinSlotKey(slot.previousBlockId, slot.nextBlockId), slot]));
130384
+ }
130385
+ function joinSlotBetween(previousBlockId, nextBlockId, slots) {
129624
130386
  if (!previousBlockId || !nextBlockId) return null;
129625
- const boundary = input.boundaryByAdjacentBlockIds.get(boundaryKey(previousBlockId, nextBlockId));
129626
- if (!boundary) return null;
129627
- const rendered = /* @__PURE__ */ jsx("div", {
130387
+ return slots.get(joinSlotKey(previousBlockId, nextBlockId)) ?? null;
130388
+ }
130389
+ /**
130390
+ * The markup at one join: the painted boundary element when the join draws,
130391
+ * then whatever `renderJoin` adds. Null when there is nothing to render.
130392
+ */
130393
+ function renderJoinSlot(input) {
130394
+ const { slot } = input;
130395
+ if (!slot) return null;
130396
+ const layout = renderedJoinLayout(slot.join);
130397
+ const painted = slot.join && layout ? /* @__PURE__ */ jsx("div", {
129628
130398
  "aria-hidden": "true",
129629
130399
  "data-block-boundary": "",
129630
- "data-boundary-id": boundary.boundaryId,
129631
- "data-boundary-previous-block-id": boundary.previousBlockId,
129632
- "data-boundary-next-block-id": boundary.nextBlockId,
129633
- "data-style-group": boundary.styleGroup,
129634
- style: boundaryCssVarsStyle(boundary)
129635
- }, `${input.blockKey}:boundary`);
129636
- return input.wrapBoundary ? input.wrapBoundary(boundary, rendered) : rendered;
130400
+ "data-boundary-id": slot.join.boundaryId,
130401
+ "data-boundary-previous-block-id": slot.previousBlockId,
130402
+ "data-boundary-next-block-id": slot.nextBlockId,
130403
+ "data-boundary-treatment": slot.join.treatment,
130404
+ "data-boundary-placement": layout.placement,
130405
+ style: boundaryPaintStyle(layout.paint)
130406
+ }, `${input.blockKey}:boundary`) : null;
130407
+ const extra = input.renderJoin?.({
130408
+ previousBlockId: slot.previousBlockId,
130409
+ nextBlockId: slot.nextBlockId,
130410
+ join: slot.join
130411
+ });
130412
+ if (!painted && (extra === null || extra === void 0)) return null;
130413
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [painted, extra] }, `${input.blockKey}:join`);
129637
130414
  }
129638
- function boundaryCssVarsStyle(boundary) {
129639
- return {
129640
- "--site-style-transition-from": boundary.cssVars.from,
129641
- "--site-style-transition-to": boundary.cssVars.to
129642
- };
130415
+ /**
130416
+ * The one render-edge place a theme token becomes CSS.
130417
+ */
130418
+ function themeTokenCss(token) {
130419
+ return `rgb(var(--tb-${token}))`;
129643
130420
  }
129644
- function boundaryReserveCssVarsStyle(input) {
129645
- const before = input.before?.metrics.reserveCss ?? null;
129646
- const after = input.after?.metrics.reserveCss ?? null;
129647
- if (!before && !after) return void 0;
130421
+ function boundaryPaintStyle(paint) {
129648
130422
  return {
129649
- ...before ? { "--site-style-boundary-before-reserve": before } : {},
129650
- ...after ? { "--site-style-boundary-after-reserve": after } : {}
130423
+ "--boundary-shape-paint": themeTokenCss(paint.shape),
130424
+ ...paint.band ? { "--boundary-band-paint": themeTokenCss(paint.band) } : {}
129651
130425
  };
129652
130426
  }
129653
- function transitionCssVarsStyle(content) {
129654
- const transitionStyles = isObjectRecord$2(content._transitionStyles) ? content._transitionStyles : null;
129655
- const cssVars = isObjectRecord$2(transitionStyles?.cssVars) ? transitionStyles.cssVars : null;
129656
- const from = typeof cssVars?.from === "string" ? cssVars.from : null;
129657
- const to = typeof cssVars?.to === "string" ? cssVars.to : null;
129658
- if (!from && !to) return void 0;
129659
- return {
129660
- ...from ? { "--site-style-transition-from": from } : {},
129661
- ...to ? { "--site-style-transition-to": to } : {}
130427
+ /**
130428
+ * Reserve attributes and variables for a block wrapper, from the joins on
130429
+ * either side. A join's `before` reserve sits inside its previous section, so
130430
+ * it pads the bottom of the block above the join; its `after` reserve pads the
130431
+ * top of the block below. Joins that do not draw reserve nothing.
130432
+ */
130433
+ function blockBoundaryReserves(input) {
130434
+ const top = reserveOnSide(input.before, "after");
130435
+ const bottom = reserveOnSide(input.after, "before");
130436
+ const style = {
130437
+ ...top ? { "--site-style-boundary-before-reserve": top.length } : {},
130438
+ ...bottom ? { "--site-style-boundary-after-reserve": bottom.length } : {}
129662
130439
  };
129663
- }
129664
- function mergeStyleObjects(left, right) {
129665
- if (!left) return right;
129666
- if (!right) return left;
129667
130440
  return {
129668
- ...left,
129669
- ...right
130441
+ topPlacement: top?.placement,
130442
+ bottomPlacement: bottom?.placement,
130443
+ style: top || bottom ? style : void 0
129670
130444
  };
129671
130445
  }
130446
+ /** The reserve one join needs on one side of itself, if it draws at all. */
130447
+ function reserveOnSide(slot, side) {
130448
+ const layout = renderedJoinLayout(slot?.join ?? null);
130449
+ const length = layout?.reserves[side] ?? null;
130450
+ return layout && length ? {
130451
+ length,
130452
+ placement: layout.placement
130453
+ } : null;
130454
+ }
129672
130455
  function pageBlockPosition(index, blockCount) {
129673
130456
  if (blockCount <= 1) return "only";
129674
130457
  if (index === 0) return "first";
@@ -134500,6 +135283,7 @@ init_styledSection();
134500
135283
  init_fragments$1();
134501
135284
  init_boxStyles();
134502
135285
  init_defineBlock();
135286
+ init_schema();
134503
135287
  const appointmentServicesBlockDefinition = {
134504
135288
  manifest: createBlockManifest({
134505
135289
  id: "block.appointment-services",
@@ -134525,6 +135309,15 @@ const appointmentServicesBlockDefinition = {
134525
135309
  required: false,
134526
135310
  ui: { intent: "content" }
134527
135311
  },
135312
+ createSiteOptionSelectField({
135313
+ id: "categoryId",
135314
+ label: "Category",
135315
+ description: "Show one category (with its heading and intro), or leave it on All categories to show everything grouped.",
135316
+ required: false,
135317
+ multiple: false,
135318
+ optionSource: "appointmentServiceCategories",
135319
+ ui: { intent: "content" }
135320
+ }),
134528
135321
  sectionStylesField({
134529
135322
  id: "_sectionStyles",
134530
135323
  label: "Section styles",
@@ -134559,6 +135352,7 @@ const appointmentServicesBlockDefinition = {
134559
135352
  richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
134560
135353
  el("appointment-services", {
134561
135354
  sectionHeading: { $bind: { from: "content.heading" } },
135355
+ categoryId: { $bind: { from: "content.categoryId" } },
134562
135356
  services: { $bind: { from: "data.services" } },
134563
135357
  pathname: { $bind: { from: "$root.pathname" } },
134564
135358
  servicePathPrefix: { $bind: { from: "$root.appointmentServicePathPrefix" } }
@@ -139308,9 +140102,8 @@ function sourceLabel(source) {
139308
140102
  }
139309
140103
  //#endregion
139310
140104
  //#region ../blocks/src/pageRenderPlan.ts
139311
- init_site_styles();
139312
- init_previewCompiler();
139313
140105
  init_typeGuards();
140106
+ init_site_styles();
139314
140107
  /**
139315
140108
  * Decides, once per page render, which blocks can render and what happens to
139316
140109
  * the ones that cannot.
@@ -139353,11 +140146,10 @@ function buildPageRenderPlan(input) {
139353
140146
  return {
139354
140147
  entries,
139355
140148
  omitted,
139356
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
139357
- boundaries: input.runtimeBoundaries,
140149
+ runtimeJoins: retargetJoinsAcrossOmissions({
140150
+ joins: input.runtimeJoins ?? [],
139358
140151
  originalBlocks: input.page.blocks,
139359
- survivingBlocks: new Set(entries.map((entry) => entry.block)),
139360
- previewStage: input.previewStage
140152
+ survivingBlocks: new Set(entries.map((entry) => entry.block))
139361
140153
  })
139362
140154
  };
139363
140155
  }
@@ -139459,86 +140251,15 @@ function getBlockOverride(blockKind, overrides) {
139459
140251
  * For draft/preview: draftContent takes priority over content.
139460
140252
  * For published: content is the source of truth.
139461
140253
  */
140254
+ /**
140255
+ * The content a block renders with. Every render path (public, preview,
140256
+ * templates, SDK) chooses content here, so this is also where a stale
140257
+ * `site-style-transition-*` styleGroup is dropped; authored values survive.
140258
+ */
139462
140259
  function getRenderableContent(block, previewStage = "published") {
139463
140260
  const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
139464
- return isObjectRecord$2(source) ? source : {};
139465
- }
139466
- /**
139467
- * Re-points section boundaries across omitted blocks.
139468
- *
139469
- * Boundaries are keyed by adjacent block-id *pairs*, so dropping B from
139470
- * [A, B, C] does not merely remove B's boundaries — it removes the boundary
139471
- * between the survivors, because no `A→C` key was ever compiled.
139472
- *
139473
- * Per decision D2 the treatment for a merged gap comes from the boundary
139474
- * entering the omitted run (`A→firstOmitted`), falling back to the one leaving
139475
- * it (`lastOmitted→C`). The destination surface is a separate question: only
139476
- * the exiting boundary knows what C looks like, so the merged boundary ends on
139477
- * that endpoint rather than on the omitted block's. In the exiting-only
139478
- * fallback the origin remains an approximation: `from` is the omitted block's
139479
- * surface, because no compiled boundary carries A's. The hosted path no longer
139480
- * needs this at all (site-renderer admits blocks before compiling boundaries);
139481
- * it remains the safety net for an SDK whose compiled boundaries predate an
139482
- * omission.
139483
- */
139484
- function retargetBoundariesAcrossOmissions(input) {
139485
- const { boundaries, originalBlocks, survivingBlocks, previewStage } = input;
139486
- if (boundaries === void 0) return void 0;
139487
- if (boundaries.length === 0) return boundaries;
139488
- if (!originalBlocks.some((block) => !survivingBlocks.has(block))) return boundaries;
139489
- const byPair = new Map(boundaries.map((boundary) => [pairKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
139490
- const retargeted = [];
139491
- let previousSurvivor = null;
139492
- let run = [];
139493
- for (const block of originalBlocks) {
139494
- if (!survivingBlocks.has(block)) {
139495
- run.push(block);
139496
- continue;
139497
- }
139498
- if (previousSurvivor?.id && block.id) {
139499
- const boundary = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : mergedBoundary({
139500
- byPair,
139501
- previousBlock: previousSurvivor,
139502
- previousBlockId: previousSurvivor.id,
139503
- nextBlock: block,
139504
- nextBlockId: block.id,
139505
- run,
139506
- previewStage
139507
- });
139508
- if (boundary) retargeted.push(boundary);
139509
- }
139510
- previousSurvivor = block;
139511
- run = [];
139512
- }
139513
- return retargeted;
139514
- }
139515
- function mergedBoundary(input) {
139516
- const { byPair, previousBlock, previousBlockId, nextBlock, nextBlockId, run, previewStage } = input;
139517
- const firstOmittedId = run[0]?.id;
139518
- const lastOmittedId = run[run.length - 1]?.id;
139519
- const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
139520
- const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
139521
- const donor = entering ?? exiting;
139522
- if (!donor) return void 0;
139523
- const transition = compileTransitionRuntime({
139524
- treatment: donor.treatment,
139525
- from: donor.from,
139526
- to: exiting?.to ?? donor.to
139527
- });
139528
- return retargetCompiledBoundaryRuntimeForContentAdjacency({
139529
- ...donor,
139530
- boundaryId: makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId)),
139531
- previousBlockId,
139532
- nextBlockId,
139533
- from: transition.from,
139534
- to: transition.to,
139535
- cssVars: transition.cssVars
139536
- }, {
139537
- currentContent: getRenderableContent(previousBlock, previewStage),
139538
- nextContent: getRenderableContent(nextBlock, previewStage)
139539
- });
140261
+ return isObjectRecord$2(source) ? stripStaleTransitionStyleGroup(source) : {};
139540
140262
  }
139541
- const pairKey = boundaryKey;
139542
140263
  //#endregion
139543
140264
  //#region ../blocks/src/system/manifest/hydrateLinks.ts
139544
140265
  init_typeGuards();
@@ -139575,7 +140296,7 @@ function hydrateResolvedBookingConsentValue(value, context) {
139575
140296
  const hydratedItems = value.map((item) => hydrateResolvedBookingConsentValue(item, context));
139576
140297
  return hydratedItems.every((item, index) => item === value[index]) ? value : hydratedItems;
139577
140298
  }
139578
- if (!isRecord$30(value)) return value;
140299
+ if (!isRecord$31(value)) return value;
139579
140300
  if (isBookingConsentCarrier(value)) {
139580
140301
  const agreement = hydrateValue(value.agreement, context);
139581
140302
  return agreement === value.agreement ? value : {
@@ -139595,7 +140316,7 @@ function hydrateValue(value, context) {
139595
140316
  const resolvedItems = value.map((item) => hydrateValue(item, context));
139596
140317
  return resolvedItems.every((item, index) => item === value[index]) ? value : resolvedItems;
139597
140318
  }
139598
- if (isRecord$30(value)) {
140319
+ if (isRecord$31(value)) {
139599
140320
  if (isInternalLink$1(value)) return hydrateInternalLink(value, context.routes);
139600
140321
  if (isPageLink$1(value)) return hydratePageLink(value, context);
139601
140322
  if (isEntryLink$1(value)) return hydrateEntryLink(value, context);
@@ -139661,7 +140382,7 @@ function hydrateEntryLink(link, context) {
139661
140382
  init_typeGuards();
139662
140383
  function resolveBlockData$1(blockId, dataLoaders, dataContext, routes) {
139663
140384
  const raw = blockId ? dataContext?.resolvedData?.[blockId] : void 0;
139664
- if (isRecord$30(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
140385
+ if (isRecord$31(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
139665
140386
  return dataLoaders && typeof dataLoaders === "object" && Object.keys(dataLoaders).length > 0 ? {} : void 0;
139666
140387
  }
139667
140388
  //#endregion
@@ -139696,23 +140417,44 @@ function preparePageRender(input) {
139696
140417
  prepared
139697
140418
  });
139698
140419
  }
139699
- if (constructionFailures.length === 0 || input.audience === "authoring") return {
139700
- entries,
139701
- constructionFailures,
139702
- runtimeBoundaries: input.plan.runtimeBoundaries
139703
- };
139704
- const survivors = entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
140420
+ const survivors = input.audience === "authoring" ? entries : entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
139705
140421
  return {
139706
140422
  entries: survivors,
139707
140423
  constructionFailures,
139708
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
139709
- boundaries: input.plan.runtimeBoundaries,
139710
- originalBlocks: input.plan.entries.map((planned) => planned.block),
139711
- survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block)),
139712
- previewStage: input.previewStage
139713
- })
140424
+ joins: composeSurvivorJoins(input.plan, survivors, input.themeResources)
139714
140425
  };
139715
140426
  }
140427
+ /**
140428
+ * The only place joins are composed. Plan-time omissions were already
140429
+ * re-pointed by the plan; construction failures are re-pointed here by the
140430
+ * same primitive, so `[A, B, C]` losing B still gets an A→C join. Each
140431
+ * survivor is then classified from its final content.
140432
+ */
140433
+ function composeSurvivorJoins(plan, survivors, themeResources) {
140434
+ return composePageJoins({
140435
+ joins: retargetJoinsAcrossOmissions({
140436
+ joins: plan.runtimeJoins,
140437
+ originalBlocks: plan.entries.map((planned) => planned.block),
140438
+ survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block))
140439
+ }),
140440
+ survivors: survivors.map(({ entry }) => ({
140441
+ id: entry.block.id,
140442
+ kind: entry.block.kind,
140443
+ content: boundaryClassificationContent(entry, themeResources)
140444
+ }))
140445
+ });
140446
+ }
140447
+ /**
140448
+ * The content a join is classified from must be the content that paints the
140449
+ * section. The default block renderer layers the theme's per-block style
140450
+ * defaults (`theme.blockOverrides[kind].styles`, background included) under
140451
+ * the block's own `_sectionStyles`, so classify that same projection. An
140452
+ * override component renders its own markup from the unmerged content.
140453
+ */
140454
+ function boundaryClassificationContent(entry, themeResources) {
140455
+ if (entry.kind !== "renderable" || entry.override) return entry.renderableContent;
140456
+ return mergeContentWithBlockStyleOverrides(entry.renderableContent, themeResources.kind === "available" ? themeResources.config : void 0, entry.block.kind);
140457
+ }
139716
140458
  function prepareEntry(entry, input) {
139717
140459
  if (entry.kind !== "renderable") return { kind: "plan-decided" };
139718
140460
  const draftContent = previewableEditorContent(entry);
@@ -139921,7 +140663,7 @@ function EditorPageRenderer$1(props) {
139921
140663
  });
139922
140664
  }
139923
140665
  function PageRendererCore(props) {
139924
- const { page, audience, runtimeBoundaries, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
140666
+ const { page, audience, runtimeJoins, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
139925
140667
  const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
139926
140668
  dataContext?.imageResolveContext,
139927
140669
  dataContext?.mediaAssets,
@@ -139937,7 +140679,7 @@ function PageRendererCore(props) {
139937
140679
  blocks: []
139938
140680
  },
139939
140681
  audience,
139940
- runtimeBoundaries,
140682
+ runtimeJoins,
139941
140683
  previewStage,
139942
140684
  sdkConfig,
139943
140685
  customBlocks,
@@ -139945,7 +140687,7 @@ function PageRendererCore(props) {
139945
140687
  }), [
139946
140688
  page,
139947
140689
  audience,
139948
- runtimeBoundaries,
140690
+ runtimeJoins,
139949
140691
  previewStage,
139950
140692
  sdkConfig,
139951
140693
  customBlocks,
@@ -139962,19 +140704,16 @@ function PageRendererCore(props) {
139962
140704
  dataContext,
139963
140705
  routeMap,
139964
140706
  themeResources,
139965
- imageResolveContext,
139966
- previewStage
140707
+ imageResolveContext
139967
140708
  }), [
139968
140709
  plan,
139969
140710
  audience,
139970
140711
  dataContext,
139971
140712
  routeMap,
139972
140713
  themeResources,
139973
- imageResolveContext,
139974
- previewStage
140714
+ imageResolveContext
139975
140715
  ]);
139976
- const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(preparation.runtimeBoundaries ?? []), [preparation.runtimeBoundaries]);
139977
- const usesRuntimeBoundaries = preparation.runtimeBoundaries !== void 0;
140716
+ const joinSlots = React$1.useMemo(() => joinSlotsByPair(preparation.joins), [preparation.joins]);
139978
140717
  reportAndReplayOmissions({
139979
140718
  plan,
139980
140719
  preparation,
@@ -139992,10 +140731,9 @@ function PageRendererCore(props) {
139992
140731
  themeResources,
139993
140732
  imageResolveContext,
139994
140733
  resolvedRegistry,
139995
- boundaryByAdjacentBlockIds,
139996
- usesRuntimeBoundaries,
140734
+ joinSlots,
139997
140735
  wrapBlock: props.wrapBlock,
139998
- wrapBoundary: props.wrapBoundary
140736
+ renderJoin: props.renderJoin
139999
140737
  })) });
140000
140738
  }
140001
140739
  /**
@@ -140084,27 +140822,25 @@ function requireEntryBlockOverride(entry, themeResources) {
140084
140822
  }
140085
140823
  /**
140086
140824
  * Renders one planned block: its wrapper, style-group data attributes,
140087
- * boundary reserve space and any boundary or transition sibling that follows.
140825
+ * boundary reserve space and the join markup that follows it.
140088
140826
  *
140089
140827
  * Extracted from PageRendererCore so the component stays about wiring the plan
140090
140828
  * rather than also owning per-block markup.
140091
140829
  */
140092
140830
  function renderPlanEntry(input) {
140093
- const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, boundaryByAdjacentBlockIds, usesRuntimeBoundaries, wrapBlock, wrapBoundary } = input;
140831
+ const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, joinSlots, wrapBlock, renderJoin } = input;
140094
140832
  const entry = input.entry.entry;
140095
140833
  const block = entry.block;
140096
140834
  const blockPosition = pageBlockPosition(index, entries.length);
140097
140835
  const blockKey = blockKeyFor(block, index);
140098
140836
  const previousBlock = entries[index - 1]?.entry.block ?? null;
140099
140837
  const nextBlock = entries[index + 1]?.entry.block ?? null;
140100
- const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
140101
- const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
140102
- const boundaryElement = renderBoundaryAfterBlock({
140103
- block,
140104
- nextBlock,
140838
+ const joinBefore = joinSlotBetween(previousBlock?.id, block.id, joinSlots);
140839
+ const joinAfter = joinSlotBetween(block.id, nextBlock?.id, joinSlots);
140840
+ const boundaryElement = renderJoinSlot({
140841
+ slot: joinAfter,
140105
140842
  blockKey,
140106
- boundaryByAdjacentBlockIds,
140107
- wrapBoundary
140843
+ renderJoin
140108
140844
  });
140109
140845
  const cleanKind = block.kind.replace(/^block\./, "");
140110
140846
  const managesOwnWrapper = entry.kind === "renderable" && entry.managesOwnWrapper;
@@ -140127,11 +140863,10 @@ function renderPlanEntry(input) {
140127
140863
  const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
140128
140864
  const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
140129
140865
  const blockComposition = readBlockComposition(renderableContent.blockComposition);
140130
- const transitionStyle = transitionCssVarsStyle(renderableContent);
140131
- const blockStyle = mergeStyleObjects(transitionStyle, boundaryReserveCssVarsStyle({
140132
- before: boundaryBefore,
140133
- after: boundaryAfter
140134
- }));
140866
+ const reserves = blockBoundaryReserves({
140867
+ before: joinBefore,
140868
+ after: joinAfter
140869
+ });
140135
140870
  const blockElement = /* @__PURE__ */ jsx("div", {
140136
140871
  className: wrapperClassName,
140137
140872
  "data-block": cleanKind,
@@ -140143,21 +140878,14 @@ function renderPlanEntry(input) {
140143
140878
  "data-block-composition": blockComposition?.treatment,
140144
140879
  "data-block-composition-kind": blockComposition?.kind,
140145
140880
  "data-page-block-position": blockPosition,
140146
- "data-site-style-boundary-before": boundaryBefore?.metrics.size,
140147
- "data-site-style-boundary-after": boundaryAfter?.metrics.size,
140881
+ "data-site-style-boundary-before": reserves.topPlacement,
140882
+ "data-site-style-boundary-after": reserves.bottomPlacement,
140148
140883
  "data-style-group": styleGroup,
140149
- style: blockStyle,
140884
+ style: reserves.style,
140150
140885
  children: /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps })
140151
140886
  }, blockKey);
140152
140887
  if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
140153
- if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
140154
- return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, /* @__PURE__ */ jsx("div", {
140155
- "aria-hidden": "true",
140156
- "data-block-transition": "",
140157
- "data-block-transition-for": block.id ?? blockKey,
140158
- "data-style-group": styleGroup,
140159
- style: transitionStyle
140160
- })] }, blockKey);
140888
+ return blockElement;
140161
140889
  }
140162
140890
  function resolvePageRendererImageResolveContext(dataContext) {
140163
140891
  const input = dataContext?.imageResolveContext;
@@ -154015,7 +154743,7 @@ function toBlocksPageRendererDataContext(dataContext) {
154015
154743
  function toBlocksCommonProps(props) {
154016
154744
  return {
154017
154745
  page: props.page,
154018
- runtimeBoundaries: props.runtimeBoundaries,
154746
+ runtimeJoins: props.runtimeJoins,
154019
154747
  dataContext: props.dataContext ? toBlocksPageRendererDataContext(props.dataContext) : void 0,
154020
154748
  routeMap: props.routeMap,
154021
154749
  wrapBlock: props.wrapBlock,
@@ -154139,10 +154867,10 @@ function buildPageRouteRenderContext(dataContext) {
154139
154867
  };
154140
154868
  }
154141
154869
  function PageContent(props) {
154142
- const { page, runtimeBoundaries, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
154870
+ const { page, runtimeJoins, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
154143
154871
  const rendererCommonProps = {
154144
154872
  page,
154145
- runtimeBoundaries,
154873
+ runtimeJoins,
154146
154874
  dataContext: {
154147
154875
  siteId,
154148
154876
  ...buildPageRouteRenderContext(dataContext),
@@ -155984,6 +156712,17 @@ function coerceToString(value) {
155984
156712
  return "";
155985
156713
  }
155986
156714
  /**
156715
+ * The appointment-services catalogue (issue 1403) pairs the services with the
156716
+ * site's ordered categories. Both are needed to group them, so that response
156717
+ * is passed through whole; any other `{ services }` wrapper is unwrapped.
156718
+ */
156719
+ function coerceServicesPayload(record) {
156720
+ return Array.isArray(record.services) && Array.isArray(record.categories) ? {
156721
+ services: record.services,
156722
+ categories: record.categories
156723
+ } : record.services;
156724
+ }
156725
+ /**
155987
156726
  * Extract data from API response.
155988
156727
  * Unwraps common response wrappers: { data }, { form }, { entries }, { services }, { teamMembers }
155989
156728
  *
@@ -155998,7 +156737,7 @@ function coercePayload(payload) {
155998
156737
  if ("data" in record && typeof record.data !== "function") return record.data;
155999
156738
  if ("form" in record && typeof record.form !== "function") return record.form;
156000
156739
  if ("entries" in record && typeof record.entries !== "function") return record.entries;
156001
- if ("services" in record && typeof record.services !== "function") return record.services;
156740
+ if ("services" in record && typeof record.services !== "function") return coerceServicesPayload(record);
156002
156741
  if ("teamMembers" in record && typeof record.teamMembers !== "function") return record.teamMembers;
156003
156742
  }
156004
156743
  return payload;
@@ -156707,6 +157446,75 @@ async function Layout(props) {
156707
157446
  });
156708
157447
  }
156709
157448
  //#endregion
157449
+ //#region ../api/src/defineEndpointDefinitions.ts
157450
+ /**
157451
+ * Validates extracted endpoint descriptors while preserving each literal path,
157452
+ * method, and parameter contract for the typed URL builders.
157453
+ */
157454
+ function defineEndpointDefinitions(definitions) {
157455
+ return definitions;
157456
+ }
157457
+ //#endregion
157458
+ //#region ../api/src/publicNewsletterEndpointDefinitions.ts
157459
+ const PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
157460
+ subscribeNewsletter: {
157461
+ path: "/public/sites/{siteId}/newsletter/subscribe",
157462
+ method: "POST",
157463
+ tags: ["public-newsletter-{siteId}"],
157464
+ auth: "public",
157465
+ responseKind: "json"
157466
+ },
157467
+ confirmNewsletter: {
157468
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
157469
+ method: "GET",
157470
+ tags: ["public-newsletter-{siteId}"],
157471
+ auth: "public",
157472
+ responseKind: "text"
157473
+ },
157474
+ unsubscribeNewsletter: {
157475
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
157476
+ method: "GET",
157477
+ tags: ["public-newsletter-{siteId}"],
157478
+ auth: "public",
157479
+ responseKind: "text"
157480
+ },
157481
+ unsubscribeNewsletterPost: {
157482
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
157483
+ method: "POST",
157484
+ tags: ["public-newsletter-{siteId}"],
157485
+ auth: "public",
157486
+ responseKind: "text"
157487
+ },
157488
+ confirmNewsletterPost: {
157489
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
157490
+ method: "POST",
157491
+ tags: ["public-newsletter-{siteId}"],
157492
+ auth: "public",
157493
+ responseKind: "text"
157494
+ },
157495
+ getNewsletterPreferences: {
157496
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
157497
+ method: "GET",
157498
+ tags: ["public-newsletter-{siteId}"],
157499
+ auth: "public",
157500
+ responseKind: "text"
157501
+ },
157502
+ saveNewsletterPreferences: {
157503
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
157504
+ method: "POST",
157505
+ tags: ["public-newsletter-{siteId}"],
157506
+ auth: "public",
157507
+ responseKind: "text"
157508
+ },
157509
+ getNewsletterArchive: {
157510
+ path: "/public/sites/{siteId}/newsletter/archive/{token}",
157511
+ method: "GET",
157512
+ tags: ["public-newsletter-{siteId}"],
157513
+ auth: "public",
157514
+ responseKind: "text"
157515
+ }
157516
+ });
157517
+ //#endregion
156710
157518
  //#region ../api/src/apiEndpointTypes.ts
156711
157519
  function allowedDemoMutationPolicy() {
156712
157520
  return {
@@ -156796,17 +157604,85 @@ const SDK_ENTRY_READ_ENDPOINTS = {
156796
157604
  }
156797
157605
  };
156798
157606
  //#endregion
156799
- //#region ../api/src/defineEndpointDefinitions.ts
156800
- /**
156801
- * Validates extracted endpoint descriptors while preserving each literal path,
156802
- * method, and parameter contract for the typed URL builders.
156803
- */
156804
- function defineEndpointDefinitions(definitions) {
156805
- return definitions;
156806
- }
156807
- //#endregion
156808
157607
  //#region ../api/src/appointmentEndpointDefinitions.ts
156809
157608
  const APPOINTMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
157609
+ ...defineEndpointDefinitions({
157610
+ listGroupedAppointmentServices: {
157611
+ path: "/sites/{siteId}/bookings/services/grouped",
157612
+ method: "GET",
157613
+ auth: "user",
157614
+ responseKind: "json",
157615
+ errors: ["server:internal_error"]
157616
+ },
157617
+ reorderAppointmentServices: {
157618
+ path: "/sites/{siteId}/bookings/services/order",
157619
+ method: "PUT",
157620
+ auth: "user",
157621
+ responseKind: "json",
157622
+ errors: [
157623
+ "validation:invalid_input",
157624
+ "auth:forbidden",
157625
+ "resource:not_found",
157626
+ "resource:conflict",
157627
+ "server:internal_error"
157628
+ ]
157629
+ },
157630
+ listAppointmentServiceCategories: {
157631
+ path: "/sites/{siteId}/bookings/service-categories",
157632
+ method: "GET",
157633
+ auth: "user",
157634
+ responseKind: "json",
157635
+ errors: ["server:internal_error"]
157636
+ },
157637
+ createAppointmentServiceCategory: {
157638
+ path: "/sites/{siteId}/bookings/service-categories",
157639
+ method: "POST",
157640
+ auth: "user",
157641
+ responseKind: "json",
157642
+ errors: [
157643
+ "validation:invalid_input",
157644
+ "auth:forbidden",
157645
+ "resource:conflict",
157646
+ "server:internal_error"
157647
+ ]
157648
+ },
157649
+ updateAppointmentServiceCategory: {
157650
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
157651
+ method: "PUT",
157652
+ auth: "user",
157653
+ responseKind: "json",
157654
+ errors: [
157655
+ "validation:invalid_input",
157656
+ "auth:forbidden",
157657
+ "resource:not_found",
157658
+ "resource:conflict",
157659
+ "server:internal_error"
157660
+ ]
157661
+ },
157662
+ deleteAppointmentServiceCategory: {
157663
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
157664
+ method: "DELETE",
157665
+ auth: "user",
157666
+ responseKind: "json",
157667
+ errors: [
157668
+ "auth:forbidden",
157669
+ "resource:not_found",
157670
+ "server:internal_error"
157671
+ ]
157672
+ },
157673
+ reorderAppointmentServiceCategories: {
157674
+ path: "/sites/{siteId}/bookings/service-categories/order",
157675
+ method: "PUT",
157676
+ auth: "user",
157677
+ responseKind: "json",
157678
+ errors: [
157679
+ "validation:invalid_input",
157680
+ "auth:forbidden",
157681
+ "resource:conflict",
157682
+ "server:internal_error"
157683
+ ]
157684
+ }
157685
+ }),
156810
157686
  listAppointmentResources: {
156811
157687
  path: "/sites/{siteId}/bookings/resources",
156812
157688
  method: "GET",
@@ -157954,39 +158830,6 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
157954
158830
  auth: "user",
157955
158831
  responseKind: "json",
157956
158832
  demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN
157957
- },
157958
- listNewsletterSendJobs: {
157959
- path: "/sites/{siteId}/newsletter/jobs",
157960
- method: "GET",
157961
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
157962
- auth: "user",
157963
- responseKind: "json"
157964
- },
157965
- retryNewsletterSendJob: {
157966
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/retry",
157967
- method: "POST",
157968
- errors: [
157969
- "validation:invalid_input",
157970
- "resource:not_found",
157971
- "auth:forbidden",
157972
- "server:internal_error"
157973
- ],
157974
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
157975
- auth: "user",
157976
- responseKind: "json"
157977
- },
157978
- cancelNewsletterSendJob: {
157979
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/cancel",
157980
- method: "POST",
157981
- errors: [
157982
- "validation:invalid_input",
157983
- "resource:not_found",
157984
- "auth:forbidden",
157985
- "server:internal_error"
157986
- ],
157987
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
157988
- auth: "user",
157989
- responseKind: "json"
157990
158833
  }
157991
158834
  });
157992
158835
  //#endregion
@@ -158755,6 +159598,7 @@ const BILLING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
158755
159598
  }
158756
159599
  });
158757
159600
  ({
159601
+ ...PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS,
158758
159602
  ...ACCOUNT_ENDPOINT_DEFINITIONS,
158759
159603
  ...PUBLIC_ONBOARDING_ATTEMPT_ENDPOINT_DEFINITIONS,
158760
159604
  ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
@@ -159768,7 +160612,12 @@ const appointmentSetupServiceDraftSchema = z$1.object({
159768
160612
  venue: appointmentSetupVenueChoiceSchema,
159769
160613
  timezone: z$1.string().trim().min(1).nullable(),
159770
160614
  payment: appointmentSetupPaymentDraftSchema,
159771
- assignment: appointmentSetupServiceAssignmentDraftSchema
160615
+ assignment: appointmentSetupServiceAssignmentDraftSchema,
160616
+ /**
160617
+ * The service's category, or null for "Other" (issue 1403). Omitted leaves
160618
+ * an existing service's category alone and puts a new one in "Other".
160619
+ */
160620
+ categoryId: z$1.string().uuid().nullable().optional()
159772
160621
  }).superRefine((service, ctx) => {
159773
160622
  if (service.payment.kind !== "free" && (service.priceAmount === null || service.priceAmount <= 0)) ctx.addIssue({
159774
160623
  code: "custom",
@@ -161368,7 +162217,7 @@ async function loadPageWithDeps(deps, params) {
161368
162217
  });
161369
162218
  return {
161370
162219
  page: pageOutline,
161371
- runtimeBoundaries: pageData.runtimeBoundaries,
162220
+ runtimeJoins: pageData.runtimeJoins,
161372
162221
  theme: requireSdkTheme(support.theme, siteId),
161373
162222
  sdkConfig: support.sdkConfig,
161374
162223
  siteId,
@@ -163648,7 +164497,7 @@ function applyBindings$1(bindings, context) {
163648
164497
  if (value !== void 0 && value !== null) setBindingValue(result, key, value);
163649
164498
  continue;
163650
164499
  }
163651
- if (isRecord$30(bindingSpec)) {
164500
+ if (isRecord$31(bindingSpec)) {
163652
164501
  const nested = applyBindings$1(bindingSpec, context);
163653
164502
  if (Object.keys(nested).length > 0) setBindingValue(result, key, nested);
163654
164503
  }
@@ -163689,7 +164538,7 @@ function mergeResolvedBindings(base, resolved) {
163689
164538
  const merged = { ...base };
163690
164539
  for (const [key, resolvedValue] of Object.entries(resolved)) {
163691
164540
  const baseValue = merged[key];
163692
- merged[key] = isRecord$30(baseValue) && isRecord$30(resolvedValue) ? mergeResolvedBindings(baseValue, resolvedValue) : resolvedValue;
164541
+ merged[key] = isRecord$31(baseValue) && isRecord$31(resolvedValue) ? mergeResolvedBindings(baseValue, resolvedValue) : resolvedValue;
163693
164542
  }
163694
164543
  return merged;
163695
164544
  }
@@ -163714,7 +164563,7 @@ function setBindingValue(target, path, value) {
163714
164563
  current[Number(segment)] = value;
163715
164564
  return;
163716
164565
  }
163717
- if (isRecord$30(current)) current[segment] = value;
164566
+ if (isRecord$31(current)) current[segment] = value;
163718
164567
  return;
163719
164568
  }
163720
164569
  const nextWantsArray = typeof nextSegment === "string" && isIndex(nextSegment);
@@ -163723,15 +164572,15 @@ function setBindingValue(target, path, value) {
163723
164572
  const existing = current[index];
163724
164573
  if (nextWantsArray) {
163725
164574
  if (!Array.isArray(existing)) current[index] = [];
163726
- } else if (!isRecord$30(existing)) current[index] = {};
164575
+ } else if (!isRecord$31(existing)) current[index] = {};
163727
164576
  current = current[index];
163728
164577
  continue;
163729
164578
  }
163730
- if (isRecord$30(current)) {
164579
+ if (isRecord$31(current)) {
163731
164580
  const existing = current[segment];
163732
164581
  if (nextWantsArray) {
163733
164582
  if (!Array.isArray(existing)) current[segment] = [];
163734
- } else if (!isRecord$30(existing)) current[segment] = {};
164583
+ } else if (!isRecord$31(existing)) current[segment] = {};
163735
164584
  current = current[segment];
163736
164585
  continue;
163737
164586
  }
@@ -163769,7 +164618,7 @@ function getPath$1(source, path) {
163769
164618
  const segments = path.split(".").map((segment) => segment.trim()).filter(Boolean);
163770
164619
  let current = source;
163771
164620
  for (const segment of segments) {
163772
- if (!isRecord$30(current)) return void 0;
164621
+ if (!isRecord$31(current)) return void 0;
163773
164622
  current = current[segment];
163774
164623
  }
163775
164624
  return current;
@@ -163802,7 +164651,7 @@ function textToInlineText(value) {
163802
164651
  function extractFirstParagraph$1(value) {
163803
164652
  if (!isRichTextDoc(value)) return null;
163804
164653
  for (const node of value.content) {
163805
- if (!isRecord$30(node)) continue;
164654
+ if (!isRecord$31(node)) continue;
163806
164655
  if (node.type !== "paragraph" || !Array.isArray(node.content)) continue;
163807
164656
  const text = node.content.filter((item) => item.type === "text").map((item) => typeof item.text === "string" ? item.text : "").join("").trim();
163808
164657
  if (text.length > 0) return text;
@@ -163822,7 +164671,7 @@ function formatDate$1(value, options) {
163822
164671
  * Type guard to check if a value is a valid binding descriptor.
163823
164672
  */
163824
164673
  function isBindingDescriptor$1(value) {
163825
- if (!isRecord$30(value) || typeof value.source !== "string") return false;
164674
+ if (!isRecord$31(value) || typeof value.source !== "string") return false;
163826
164675
  if (!BINDING_SOURCES$1.includes(value.source)) return false;
163827
164676
  switch (value.source) {
163828
164677
  case "field":
@@ -163837,7 +164686,7 @@ function isBindingDescriptor$1(value) {
163837
164686
  * Type guard to check if a value is binding-descriptor-like (can be normalized).
163838
164687
  */
163839
164688
  function isBindingDescriptorLike$1(value) {
163840
- return isBindingDescriptor$1(value) || typeof value === "string" || isRecord$30(value) && typeof value.source === "string" && typeof value.path === "string";
164689
+ return isBindingDescriptor$1(value) || typeof value === "string" || isRecord$31(value) && typeof value.source === "string" && typeof value.path === "string";
163841
164690
  }
163842
164691
  /**
163843
164692
  * Normalizes a binding input to a binding descriptor.
@@ -163878,7 +164727,7 @@ const ImagePresets = {
163878
164727
  resize: "contain"
163879
164728
  }
163880
164729
  };
163881
- const isRecord = isRecord$30;
164730
+ const isRecord = isRecord$31;
163882
164731
  const applyBindingsToContent = applyBindingsToContent$1;
163883
164732
  //#endregion
163884
164733
  //#region src/rendering/helpers/templateContent.ts
@@ -164327,7 +165176,7 @@ async function loadContentFromContentResponseWithDeps(deps, params) {
164327
165176
  ogImageId: pageRecord.ogImageId ?? null
164328
165177
  },
164329
165178
  page: pageOutline,
164330
- runtimeBoundaries: pageData.runtimeBoundaries,
165179
+ runtimeJoins: pageData.runtimeJoins,
164331
165180
  theme: support.theme,
164332
165181
  siteId,
164333
165182
  ...support.customBlocks ? { customBlocks: support.customBlocks } : {},