@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
@@ -940,7 +940,7 @@ function parseEventPageSurfaceDestinationMode(value) {
940
940
  return value === "cms-managed" || value === "system-managed" ? value : void 0;
941
941
  }
942
942
  function parseEventPageSurfaceSubject(value) {
943
- if (!isRecord$40(value)) return err$2({ kind: "notObject" });
943
+ if (!isRecord$41(value)) return err$2({ kind: "notObject" });
944
944
  switch (value.kind) {
945
945
  case "eventSeries": {
946
946
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -995,7 +995,7 @@ function parseEventSeriesIdList(values) {
995
995
  }
996
996
  return ok$2(parsed);
997
997
  }
998
- function isRecord$40(value) {
998
+ function isRecord$41(value) {
999
999
  return typeof value === "object" && value !== null && !Array.isArray(value);
1000
1000
  }
1001
1001
  var init_event_page_surface_subject = __esmMin((() => {
@@ -1447,7 +1447,76 @@ var init_booking_date_time = __esmMin((() => {
1447
1447
  }));
1448
1448
  //#endregion
1449
1449
  //#region ../core/src/booking-consent.ts
1450
- var init_booking_consent = __esmMin((() => {})), init_dated_offering_settlement = __esmMin((() => {
1450
+ var init_booking_consent = __esmMin((() => {}));
1451
+ //#endregion
1452
+ //#region ../core/src/appointment-service-categories.ts
1453
+ /**
1454
+ * Groups services under their categories.
1455
+ *
1456
+ * A service whose `categoryId` names no category in `categories` lands in
1457
+ * Other. The composite foreign key keeps that from happening with stored data,
1458
+ * so this is only the total, safe answer for a caller holding a stale list.
1459
+ */
1460
+ function layoutServicesByCategory(categories, services, options) {
1461
+ if (categories.length === 0) return {
1462
+ kind: "uncategorized-site",
1463
+ services: [...services]
1464
+ };
1465
+ const buckets = new Map(categories.map((category) => [category.id, []]));
1466
+ const other = [];
1467
+ for (const service of services) {
1468
+ const bucket = service.categoryId === null ? void 0 : buckets.get(service.categoryId);
1469
+ if (bucket !== void 0) bucket.push(service);
1470
+ else other.push(service);
1471
+ }
1472
+ const groups = [];
1473
+ for (const category of categories) {
1474
+ const groupServices = buckets.get(category.id) ?? [];
1475
+ if (groupServices.length === 0 && !options.keepEmptyGroups) continue;
1476
+ groups.push({
1477
+ category,
1478
+ services: groupServices
1479
+ });
1480
+ }
1481
+ if (groups.length === 0 && !options.keepEmptyGroups) return {
1482
+ kind: "uncategorized-site",
1483
+ services: other
1484
+ };
1485
+ return {
1486
+ kind: "categorized-site",
1487
+ groups,
1488
+ other
1489
+ };
1490
+ }
1491
+ /**
1492
+ * Selects the public view for an optional category filter.
1493
+ *
1494
+ * It takes the category and service lists rather than a prebuilt layout
1495
+ * because the public layout has already dropped empty categories, and then an
1496
+ * empty category (`single` with no services) could not be told apart from a
1497
+ * deleted one (`fallback-all`).
1498
+ */
1499
+ function selectCategoryView(categories, services, filterCategoryId) {
1500
+ const publicLayout = () => layoutServicesByCategory(categories, services, { keepEmptyGroups: false });
1501
+ if (filterCategoryId === null || filterCategoryId === void 0) return {
1502
+ kind: "all",
1503
+ layout: publicLayout()
1504
+ };
1505
+ const category = categories.find((candidate) => candidate.id === filterCategoryId);
1506
+ if (category === void 0) return {
1507
+ kind: "fallback-all",
1508
+ missingCategoryId: filterCategoryId,
1509
+ layout: publicLayout()
1510
+ };
1511
+ return {
1512
+ kind: "single",
1513
+ group: {
1514
+ category,
1515
+ services: services.filter((service) => service.categoryId === filterCategoryId)
1516
+ }
1517
+ };
1518
+ }
1519
+ var init_appointment_service_categories = __esmMin((() => {})), init_dated_offering_settlement = __esmMin((() => {
1451
1520
  init_result$1();
1452
1521
  }));
1453
1522
  var init_person_records = __esmMin((() => {
@@ -1501,7 +1570,7 @@ var init_public_profile = __esmMin((() => {
1501
1570
  var init_public_experience = __esmMin((() => {}));
1502
1571
  //#endregion
1503
1572
  //#region ../core/src/rich-text.ts
1504
- function isRecord$38(value) {
1573
+ function isRecord$39(value) {
1505
1574
  return typeof value === "object" && value !== null && !Array.isArray(value);
1506
1575
  }
1507
1576
  function hasOnlyKeys(value, allowed) {
@@ -1512,10 +1581,10 @@ function isDenseArrayOf(value, predicate) {
1512
1581
  return true;
1513
1582
  }
1514
1583
  function isTipTapMark(value) {
1515
- return isRecord$38(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$38(value.attrs));
1584
+ return isRecord$39(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$39(value.attrs));
1516
1585
  }
1517
1586
  function isTipTapNode(value) {
1518
- if (!isRecord$38(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$38(value.attrs) || value.marks !== void 0 && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== void 0 && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) return false;
1587
+ if (!isRecord$39(value) || !hasOnlyKeys(value, nodeKeys) || typeof value.type !== "string" || value.type.length === 0 || value.type === "doc" || value.attrs !== void 0 && !isRecord$39(value.attrs) || value.marks !== void 0 && (!Array.isArray(value.marks) || !isDenseArrayOf(value.marks, isTipTapMark)) || value.content !== void 0 && (!Array.isArray(value.content) || !isDenseArrayOf(value.content, isTipTapNode))) return false;
1519
1588
  if (value.type === "text") return typeof value.text === "string" && value.text.length > 0 && value.content === void 0;
1520
1589
  return value.text === void 0;
1521
1590
  }
@@ -1566,10 +1635,10 @@ var init_mailing_list_identifiers = __esmMin((() => {
1566
1635
  //#endregion
1567
1636
  //#region ../core/src/mailing-list-targeting-parser.ts
1568
1637
  function parseMailingListPlacementTargeting(value) {
1569
- if (!isRecord$37(value)) return targetingError("shape");
1638
+ if (!isRecord$38(value)) return targetingError("shape");
1570
1639
  if (value.mode === "default") return ok$2({ mode: "default" });
1571
1640
  if (value.mode !== "custom") return targetingError("mode");
1572
- if (!isRecord$37(value.selection)) return targetingError("selection");
1641
+ if (!isRecord$38(value.selection)) return targetingError("selection");
1573
1642
  switch (value.provider) {
1574
1643
  case "native": return parseNativeTargeting(value.selection);
1575
1644
  case "brevo": return parseBrevoTargeting(value.selection);
@@ -1578,7 +1647,7 @@ function parseMailingListPlacementTargeting(value) {
1578
1647
  }
1579
1648
  }
1580
1649
  function parseMailingListVisitorSelection(value) {
1581
- if (!isRecord$37(value)) return visitorSelectionError$1("shape");
1650
+ if (!isRecord$38(value)) return visitorSelectionError$1("shape");
1582
1651
  switch (value.provider) {
1583
1652
  case "native": {
1584
1653
  const topicIds = parseVisitorIds(value.topicIds, parseNewsletterListId);
@@ -1711,7 +1780,7 @@ function parseVisitorIds(value, parseId) {
1711
1780
  }
1712
1781
  return parsed;
1713
1782
  }
1714
- function isRecord$37(value) {
1783
+ function isRecord$38(value) {
1715
1784
  return typeof value === "object" && value !== null && !Array.isArray(value);
1716
1785
  }
1717
1786
  function targetingError(field) {
@@ -1762,11 +1831,11 @@ var init_footer_navigation_layout = __esmMin((() => {}));
1762
1831
  var init_footer_composition_types = __esmMin((() => {}));
1763
1832
  //#endregion
1764
1833
  //#region ../core/src/mailing-list-setup.ts
1765
- function isRecord$34(value) {
1834
+ function isRecord$35(value) {
1766
1835
  return typeof value === "object" && value !== null && !Array.isArray(value);
1767
1836
  }
1768
1837
  function parseMailingListProviderScope(value) {
1769
- if (!isRecord$34(value)) return err$2("invalid_mailing_list_provider_scope");
1838
+ if (!isRecord$35(value)) return err$2("invalid_mailing_list_provider_scope");
1770
1839
  if (value.provider === "native") return ok$2({ provider: "native" });
1771
1840
  if (value.provider === "brevo") {
1772
1841
  const accountId = parseBrevoAccountId(value.accountId);
@@ -1793,7 +1862,7 @@ var init_mailing_list_setup = __esmMin((() => {
1793
1862
  //#endregion
1794
1863
  //#region ../core/src/mailing-list-setup-parser.ts
1795
1864
  function parseMailingListGlobalForm(value) {
1796
- if (!isRecord$33(value)) return formError("shape");
1865
+ if (!isRecord$34(value)) return formError("shape");
1797
1866
  const fields = parseGlobalFields(value.fields);
1798
1867
  if (fields === null) return formError("fields");
1799
1868
  if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
@@ -1812,7 +1881,7 @@ function parseMailingListGlobalForm(value) {
1812
1881
  });
1813
1882
  }
1814
1883
  function parseGlobalFields(value) {
1815
- if (!isRecord$33(value) || typeof value.preset !== "string") return null;
1884
+ if (!isRecord$34(value) || typeof value.preset !== "string") return null;
1816
1885
  switch (value.preset) {
1817
1886
  case "emailOnly": return { preset: "emailOnly" };
1818
1887
  case "emailName": return isRequirement(value.name) ? {
@@ -1849,7 +1918,7 @@ function parseOptionalString(value) {
1849
1918
  function isRequirement(value) {
1850
1919
  return value === "required" || value === "optional";
1851
1920
  }
1852
- function isRecord$33(value) {
1921
+ function isRecord$34(value) {
1853
1922
  return typeof value === "object" && value !== null && !Array.isArray(value);
1854
1923
  }
1855
1924
  function formError(field) {
@@ -1907,7 +1976,7 @@ var init_mailing_list_targeting = __esmMin((() => {
1907
1976
  //#endregion
1908
1977
  //#region ../core/src/mailing-list-runtime.ts
1909
1978
  function parseMailingListRuntimeContext(value) {
1910
- if (!isRecord$32(value)) return invalidRuntimeContext();
1979
+ if (!isRecord$33(value)) return invalidRuntimeContext();
1911
1980
  if (value.tag === "not_configured") return ok$2({ tag: "not_configured" });
1912
1981
  if (value.tag !== "configured") return invalidRuntimeContext();
1913
1982
  const revision = parseMailingListSetupRevision(value.revision);
@@ -1922,7 +1991,7 @@ function parseMailingListRuntimeContext(value) {
1922
1991
  });
1923
1992
  }
1924
1993
  function parseRuntimeProvider(value) {
1925
- if (!isRecord$32(value)) return null;
1994
+ if (!isRecord$33(value)) return null;
1926
1995
  switch (value.provider) {
1927
1996
  case "native": return parseNativeRuntimeProvider(value);
1928
1997
  case "brevo": return parseBrevoRuntimeProvider(value);
@@ -1954,7 +2023,7 @@ function parseMailchimpRuntimeProvider(value) {
1954
2023
  if (defaultAudienceId === null) return null;
1955
2024
  const audiences = [];
1956
2025
  for (const audience of value.audiences) {
1957
- if (!isRecord$32(audience)) return null;
2026
+ if (!isRecord$33(audience)) return null;
1958
2027
  const id = parseMailchimpAudienceId$1(audience.id);
1959
2028
  const name = parseName(audience.name);
1960
2029
  if (id === null || name === null || !Array.isArray(audience.interests)) return null;
@@ -1975,7 +2044,7 @@ function parseMailchimpRuntimeProvider(value) {
1975
2044
  function parseMailchimpInterests(value) {
1976
2045
  const interests = [];
1977
2046
  for (const interest of value) {
1978
- if (!isRecord$32(interest)) return null;
2047
+ if (!isRecord$33(interest)) return null;
1979
2048
  const id = parseMailchimpInterestId(interest.id);
1980
2049
  const name = parseName(interest.name);
1981
2050
  const categoryName = parseName(interest.categoryName);
@@ -1992,7 +2061,7 @@ function parseNamedItems(value, parseId) {
1992
2061
  if (!Array.isArray(value)) return null;
1993
2062
  const items = [];
1994
2063
  for (const item of value) {
1995
- if (!isRecord$32(item)) return null;
2064
+ if (!isRecord$33(item)) return null;
1996
2065
  const id = parseId(item.id);
1997
2066
  const name = parseName(item.name);
1998
2067
  if (id === null || name === null) return null;
@@ -2008,7 +2077,7 @@ function parseName(value) {
2008
2077
  const name = value.trim();
2009
2078
  return name.length > 0 ? name : null;
2010
2079
  }
2011
- function isRecord$32(value) {
2080
+ function isRecord$33(value) {
2012
2081
  return typeof value === "object" && value !== null && !Array.isArray(value);
2013
2082
  }
2014
2083
  function invalidRuntimeContext() {
@@ -2602,6 +2671,7 @@ var init_src$2 = __esmMin((() => {
2602
2671
  init_booking_instalments();
2603
2672
  init_booking_date_time();
2604
2673
  init_booking_consent();
2674
+ init_appointment_service_categories();
2605
2675
  init_dated_offering_settlement();
2606
2676
  init_person_records();
2607
2677
  init_rectification();
@@ -2786,11 +2856,11 @@ var init_storagePathDomain = __esmMin((() => {}));
2786
2856
  var init_fileAssetLegacyProjection = __esmMin((() => {}));
2787
2857
  //#endregion
2788
2858
  //#region ../media-core/src/typeGuards.ts
2789
- function isRecord$30(value) {
2859
+ function isRecord$31(value) {
2790
2860
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2791
2861
  }
2792
2862
  function asRecord$3(value) {
2793
- return isRecord$30(value) ? value : void 0;
2863
+ return isRecord$31(value) ? value : void 0;
2794
2864
  }
2795
2865
  var init_typeGuards$1 = __esmMin((() => {}));
2796
2866
  function isFileJsonObject(value) {
@@ -2800,7 +2870,7 @@ function isFileJsonValue(value) {
2800
2870
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
2801
2871
  if (typeof value === "number") return Number.isFinite(value);
2802
2872
  if (Array.isArray(value)) return value.every(isFileJsonValue);
2803
- return isRecord$30(value) && isFileJsonObject(value);
2873
+ return isRecord$31(value) && isFileJsonObject(value);
2804
2874
  }
2805
2875
  var init_filePersistence = __esmMin((() => {
2806
2876
  init_src$2();
@@ -3166,7 +3236,7 @@ function isCmsMediaObjectType(value) {
3166
3236
  return typeof value === "string" && CMS_MEDIA_OBJECT_TYPES.includes(value);
3167
3237
  }
3168
3238
  function isCmsMediaObject(value) {
3169
- if (!isRecord$30(value)) return false;
3239
+ if (!isRecord$31(value)) return false;
3170
3240
  const type = value.type;
3171
3241
  if (!isCmsMediaObjectType(type)) return false;
3172
3242
  if ("attrs" in value) return false;
@@ -3177,7 +3247,7 @@ function collectMediaObjects(value, out, location = "") {
3177
3247
  value.forEach((item, index) => collectMediaObjects(item, out, `${location}[${index}]`));
3178
3248
  return;
3179
3249
  }
3180
- if (!isRecord$30(value)) return;
3250
+ if (!isRecord$31(value)) return;
3181
3251
  if (isCmsMediaObject(value)) out.push({
3182
3252
  obj: value,
3183
3253
  location
@@ -3223,8 +3293,8 @@ function walkAndCollectImageAttrs(value, out, location = "") {
3223
3293
  value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
3224
3294
  return;
3225
3295
  }
3226
- if (!isRecord$30(value)) return;
3227
- if (value.type === "image" && isRecord$30(value.attrs)) out.push({
3296
+ if (!isRecord$31(value)) return;
3297
+ if (value.type === "image" && isRecord$31(value.attrs)) out.push({
3228
3298
  attrs: value.attrs,
3229
3299
  location: location ? `${location}.attrs` : "attrs"
3230
3300
  });
@@ -3284,7 +3354,7 @@ function mediaIdentifierOrFallback(current, fallback) {
3284
3354
  var init_renderableMediaAsset = __esmMin((() => {}));
3285
3355
  //#endregion
3286
3356
  //#region ../media-core/src/contentMediaAudit.ts
3287
- function isRecord$29(value) {
3357
+ function isRecord$30(value) {
3288
3358
  return typeof value === "object" && value !== null && !Array.isArray(value);
3289
3359
  }
3290
3360
  function summarizeContentMediaValue(value) {
@@ -3295,7 +3365,7 @@ function summarizeContentMediaValue(value) {
3295
3365
  }
3296
3366
  if (value === null) return "null";
3297
3367
  if (Array.isArray(value)) return `array(${value.length})`;
3298
- if (isRecord$29(value)) return `object(${Object.keys(value).sort().join(",")})`;
3368
+ if (isRecord$30(value)) return `object(${Object.keys(value).sort().join(",")})`;
3299
3369
  return typeof value;
3300
3370
  }
3301
3371
  function auditMediaValue(context, path, allowedMediaTypes, value) {
@@ -3319,9 +3389,9 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
3319
3389
  allowedMediaTypes
3320
3390
  }];
3321
3391
  }
3322
- const hasValidIdentifier = isRecord$29(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
3323
- const hasValidExplicitType = isRecord$29(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
3324
- const hasRetiredEnvironmentField = isRecord$29(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
3392
+ const hasValidIdentifier = isRecord$30(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
3393
+ const hasValidExplicitType = isRecord$30(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
3394
+ const hasRetiredEnvironmentField = isRecord$30(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
3325
3395
  if (hasValidIdentifier && hasValidExplicitType && !hasRetiredEnvironmentField) return [];
3326
3396
  return [{
3327
3397
  tag: "malformed-object",
@@ -3332,7 +3402,7 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
3332
3402
  }];
3333
3403
  }
3334
3404
  function auditFields(context, fields, value, parentPath) {
3335
- if (!isRecord$29(value)) return [];
3405
+ if (!isRecord$30(value)) return [];
3336
3406
  const findings = [];
3337
3407
  for (const field of fields) {
3338
3408
  if (!(field.id in value)) continue;
@@ -3349,7 +3419,7 @@ function auditFields(context, fields, value, parentPath) {
3349
3419
  if (!Array.isArray(fieldValue)) break;
3350
3420
  for (const [index, item] of fieldValue.entries()) {
3351
3421
  const itemPath = [...fieldPath, index];
3352
- if (!isRecord$29(item)) {
3422
+ if (!isRecord$30(item)) {
3353
3423
  findings.push({
3354
3424
  tag: "missing-schema",
3355
3425
  context,
@@ -3383,7 +3453,7 @@ var init_contentMediaAudit = __esmMin((() => {
3383
3453
  }));
3384
3454
  //#endregion
3385
3455
  //#region ../media-core/src/contentMediaAuditFieldAdapter.ts
3386
- function isRecord$28(value) {
3456
+ function isRecord$29(value) {
3387
3457
  return typeof value === "object" && value !== null && !Array.isArray(value);
3388
3458
  }
3389
3459
  function unknownArray(value) {
@@ -3392,7 +3462,7 @@ function unknownArray(value) {
3392
3462
  function fieldsArray(value) {
3393
3463
  const directFields = unknownArray(value);
3394
3464
  if (directFields) return directFields;
3395
- if (!isRecord$28(value)) return null;
3465
+ if (!isRecord$29(value)) return null;
3396
3466
  return unknownArray(value.fields);
3397
3467
  }
3398
3468
  function nestedFields(field) {
@@ -3457,7 +3527,7 @@ function mergeFields(fields) {
3457
3527
  }
3458
3528
  function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds = ["document"]) {
3459
3529
  return mergeFields(rawFields.flatMap((rawField) => {
3460
- if (!isRecord$28(rawField)) return [];
3530
+ if (!isRecord$29(rawField)) return [];
3461
3531
  const { id, type } = rawField;
3462
3532
  if (typeof id !== "string" || !id || typeof type !== "string") return [];
3463
3533
  if (type === "media" || type === "file") return [{
@@ -3465,7 +3535,7 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
3465
3535
  id,
3466
3536
  allowedMediaTypes: allowedMediaTypes(rawField, type, defaultFileKinds)
3467
3537
  }];
3468
- if (flattenRepeaterGroups && type === "group" && isRecord$28(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields(rawField), false, defaultFileKinds);
3538
+ if (flattenRepeaterGroups && type === "group" && isRecord$29(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields(rawField), false, defaultFileKinds);
3469
3539
  if (type === "group" || type === "modal") return [{
3470
3540
  kind: "object",
3471
3541
  id,
@@ -3474,14 +3544,14 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
3474
3544
  if (type === "tabGroup") return [{
3475
3545
  kind: "object",
3476
3546
  id,
3477
- fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$28(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
3547
+ fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$29(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
3478
3548
  }];
3479
3549
  if (type !== "repeater") return [];
3480
- if (isRecord$28(rawField.itemTypes)) return [{
3550
+ if (isRecord$29(rawField.itemTypes)) return [{
3481
3551
  kind: "repeater",
3482
3552
  id,
3483
3553
  mode: "variants",
3484
- variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$28(itemType) && Array.isArray(itemType.fields) ? [{
3554
+ variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$29(itemType) && Array.isArray(itemType.fields) ? [{
3485
3555
  typeId,
3486
3556
  fields: adaptFields(itemType.fields, true, defaultFileKinds)
3487
3557
  }] : []),
@@ -8310,7 +8380,7 @@ var init_resolvedButtonCss = __esmMin((() => {
8310
8380
  }));
8311
8381
  //#endregion
8312
8382
  //#region ../theme-core/src/buttons/generateButtonCss.ts
8313
- function rule$1(selector, declarations) {
8383
+ function rule$2(selector, declarations) {
8314
8384
  return {
8315
8385
  selectors: typeof selector === "string" ? [selector] : selector,
8316
8386
  declarations
@@ -8484,7 +8554,7 @@ function variantAffordanceRule(variant, selector) {
8484
8554
  const maskUrl = affordanceIconMaskUrl(affordance.iconId);
8485
8555
  if (!maskUrl) return null;
8486
8556
  const mask = `url("${maskUrl}") no-repeat center / contain`;
8487
- return rule$1(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
8557
+ return rule$2(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
8488
8558
  ["content", "\"\""],
8489
8559
  ["display", "inline-block"],
8490
8560
  ["width", "1em"],
@@ -8523,7 +8593,7 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
8523
8593
  const borderWidth = BORDER_WIDTH_MAP$4[variant.border.widthClass] || "1px";
8524
8594
  decls.push(["border", `${borderWidth} solid currentColor`]);
8525
8595
  }
8526
- return rule$1(selector, decls);
8596
+ return rule$2(selector, decls);
8527
8597
  }
8528
8598
  /**
8529
8599
  * Resolve a colour for a button that does not own its own background.
@@ -8534,13 +8604,13 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
8534
8604
  */
8535
8605
  function variantStateColourRule(variantId, themeId, state, colour) {
8536
8606
  const baseSelector = `:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}`;
8537
- return rule$1(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
8607
+ return rule$2(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
8538
8608
  role: variantId,
8539
8609
  state
8540
8610
  }));
8541
8611
  }
8542
8612
  function variantFocusVisibleRule(variantId, themeId, focusIndicator) {
8543
- return rule$1(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
8613
+ return rule$2(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
8544
8614
  }
8545
8615
  function variantEffectsCss(variant, effects, themeId, tokens, theme) {
8546
8616
  if (!effects) return "";
@@ -8566,10 +8636,10 @@ function variantDisabledRules(variantId, themeId, colour) {
8566
8636
  `${base}:focus`
8567
8637
  ];
8568
8638
  const pseudoSelectors = [`${base}::before`, `${base}::after`];
8569
- return [rule$1(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
8639
+ return [rule$2(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
8570
8640
  role: variantId,
8571
8641
  state: "disabled"
8572
- }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$1(pseudoSelectors, [
8642
+ }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$2(pseudoSelectors, [
8573
8643
  ["opacity", "0"],
8574
8644
  ["animation", "none"],
8575
8645
  ["pointer-events", "none"]
@@ -8584,7 +8654,7 @@ function variantSizeRules(variant, variantId, themeSizes, themeId) {
8584
8654
  const decls = [];
8585
8655
  for (const decl of paddingDeclarations(config.padding, borderWidth)) decls.push(decl);
8586
8656
  if (config.fontSize) decls.push(["font-size", FONT_SIZE_MAP[config.fontSize] || "1rem"]);
8587
- rules.push(rule$1(selector, decls));
8657
+ rules.push(rule$2(selector, decls));
8588
8658
  }
8589
8659
  return rules;
8590
8660
  }
@@ -11963,6 +12033,15 @@ function asSiteStyleId(value) {
11963
12033
  function asDesignSystemTemplateId(value) {
11964
12034
  return brandNonEmptyString(value, "DesignSystemTemplateId");
11965
12035
  }
12036
+ function toNonEmptyReadonlyArray(values) {
12037
+ const [first, ...rest] = values;
12038
+ return first === void 0 ? null : [first, ...rest];
12039
+ }
12040
+ function requireNonEmptyReadonlyArray(values, message = "Expected a non-empty array.") {
12041
+ const result = toNonEmptyReadonlyArray(values);
12042
+ if (!result) throw new Error(message);
12043
+ return result;
12044
+ }
11966
12045
  function makeBoundaryId(previousBlockId, nextBlockId) {
11967
12046
  return asDesignBoundaryId(`boundary:${encodeURIComponent(previousBlockId)}:${encodeURIComponent(nextBlockId)}`);
11968
12047
  }
@@ -12070,7 +12149,8 @@ var init_types$8 = __esmMin((() => {
12070
12149
  "swap"
12071
12150
  ];
12072
12151
  }));
12073
- var logoTextConfidenceValues, logoAssetShapeValues, logoKindValues, logoDensityValues, logoContainsTextSchema, logoAnalysisSourceSchema, brandDisplayModeValues, logoAccommodationPatternValues, logoSizePolicyValues, defaultNoLogoFacts, sizeOrder, init_logoBranding = __esmMin((() => {
12152
+ var logoTextConfidenceValues, logoAssetShapeValues, logoKindValues, logoDensityValues, logoContainsTextSchema, logoAnalysisSourceSchema, brandDisplayModeValues, logoAccommodationPatternValues, logoSizePolicyValues, defaultNoLogoFacts, sizeOrder;
12153
+ var init_logoBranding = __esmMin((() => {
12074
12154
  init_types$8();
12075
12155
  logoTextConfidenceValues = [
12076
12156
  "low",
@@ -12160,26 +12240,44 @@ var logoTextConfidenceValues, logoAssetShapeValues, logoKindValues, logoDensityV
12160
12240
  "generous",
12161
12241
  "display"
12162
12242
  ];
12163
- })), themeV2RuntimeStyleFieldNames, themeV2BlockSpecificRawStyleFieldNames, themeV2NormalEditorRawStyleFieldNames, pageDesignEditorBackgroundOverrideOwner, siteChromeBackgroundOverrideOwner;
12243
+ }));
12244
+ /**
12245
+ * A `styleGroup` in the retired `site-style-transition-*` namespace is residue
12246
+ * from the old per-block transition path (issue 1422). Any other value is
12247
+ * authored and must survive.
12248
+ */
12249
+ function isStaleTransitionStyleGroup(value) {
12250
+ return typeof value === "string" && value.startsWith(staleTransitionStyleGroupPrefix);
12251
+ }
12252
+ /** Drops a stale transition `styleGroup`, leaving authored values untouched. */
12253
+ function stripStaleTransitionStyleGroup(content) {
12254
+ if (!isStaleTransitionStyleGroup(content.styleGroup)) return { ...content };
12255
+ const { styleGroup: _stale, ...rest } = content;
12256
+ return rest;
12257
+ }
12258
+ var themeV2RuntimeStyleFieldNames, themeV2BlockSpecificRawStyleFieldNames, themeV2NormalEditorRawStyleFieldNames, pageDesignEditorBackgroundOverrideOwner, siteChromeBackgroundOverrideOwner, staleTransitionStyleGroupPrefix;
12164
12259
  var init_rawStyleFields = __esmMin((() => {
12165
12260
  themeV2RuntimeStyleFieldNames = [
12166
12261
  "_frameStyles",
12167
12262
  "_sectionStyles",
12168
12263
  "_containerStyles",
12169
- "_cardStyles",
12170
- "_transitionStyles",
12171
- "styleGroup"
12264
+ "_cardStyles"
12172
12265
  ];
12173
12266
  themeV2BlockSpecificRawStyleFieldNames = [
12174
12267
  "_contentCardStyles",
12175
12268
  "_heroLayoutStyles",
12176
12269
  "_textCardStyles"
12177
12270
  ];
12178
- themeV2NormalEditorRawStyleFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
12271
+ themeV2NormalEditorRawStyleFieldNames = [
12272
+ ...themeV2RuntimeStyleFieldNames,
12273
+ ...themeV2BlockSpecificRawStyleFieldNames,
12274
+ "styleGroup"
12275
+ ];
12179
12276
  new Set(themeV2NormalEditorRawStyleFieldNames);
12180
12277
  [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
12181
12278
  pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
12182
12279
  siteChromeBackgroundOverrideOwner = "site-chrome-editor";
12280
+ staleTransitionStyleGroupPrefix = "site-style-transition-";
12183
12281
  })), headerVariantValues, legacyHeaderVariants, legacyHeaderVariantValues, headerVariantWithLegacyAliasSchema;
12184
12282
  var init_legacyHeaderVariant = __esmMin((() => {
12185
12283
  headerVariantValues = [
@@ -12403,7 +12501,7 @@ var init_interactionPalette = __esmMin((() => {
12403
12501
  //#endregion
12404
12502
  //#region ../theme-core/src/boxBackgroundSchema.ts
12405
12503
  function normalizeAuthoringOpacity(value) {
12406
- if (!isRecord$27(value)) return value;
12504
+ if (!isRecord$28(value)) return value;
12407
12505
  return {
12408
12506
  ...value,
12409
12507
  opacity: normalizeOpacity(value.opacity)
@@ -12412,7 +12510,7 @@ function normalizeAuthoringOpacity(value) {
12412
12510
  function normalizeOpacity(value) {
12413
12511
  return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
12414
12512
  }
12415
- function isRecord$27(value) {
12513
+ function isRecord$28(value) {
12416
12514
  return typeof value === "object" && value !== null && !Array.isArray(value);
12417
12515
  }
12418
12516
  var boxBackgroundOverlaySchema, commonSchema, mediaPresentationSchema, boxBackgroundSchema;
@@ -13231,7 +13329,7 @@ var init_media$1 = __esmMin((() => {
13231
13329
  /**
13232
13330
  * Strict record guard: excludes null, primitives, and arrays.
13233
13331
  */
13234
- function isRecord$26(value) {
13332
+ function isRecord$27(value) {
13235
13333
  return typeof value === "object" && value !== null && !Array.isArray(value);
13236
13334
  }
13237
13335
  /**
@@ -16018,17 +16116,280 @@ var init_cardRoleVocabulary = __esmMin((() => {
16018
16116
  Object.keys(legacyItemSurfaceCardRoles);
16019
16117
  cardRoleWithLegacyAliasSchema = z$1.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
16020
16118
  }));
16021
- var init_compositionBudget = __esmMin((() => {
16022
- init_types$8();
16023
- }));
16024
- var defaultHeaderBrandPreferences;
16025
- var init_compilationPlan = __esmMin((() => {
16026
- defaultHeaderBrandPreferences = {
16027
- brandDisplay: "auto",
16028
- logoSize: "auto"
16119
+ //#endregion
16120
+ //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
16121
+ function parseCanonicalRuntimeBackground(content) {
16122
+ const background = readRecord(readRecord(content._sectionStyles)?.background);
16123
+ if (!background) return { kind: "none" };
16124
+ const parsed = boxBackgroundSchema.safeParse(background);
16125
+ if (!parsed.success) return { kind: "none" };
16126
+ return parsed.data.contentOwner === "page-design-editor" ? {
16127
+ kind: "page-design-override",
16128
+ background: parsed.data
16129
+ } : {
16130
+ kind: "block-authored",
16131
+ background: parsed.data
16029
16132
  };
16133
+ }
16134
+ function readRecord(value) {
16135
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
16136
+ }
16137
+ var init_runtimeBackgroundResolution = __esmMin((() => {
16138
+ init_boxBackgroundSchema();
16139
+ init_surfaces();
16140
+ init_rawStyleFields();
16141
+ }));
16142
+ //#endregion
16143
+ //#region ../theme-core/src/site-styles/transitionCatalogue.ts
16144
+ /** CSS `clip-path` value for an angle shape, as `generateLayoutCss` draws it. */
16145
+ function anglePolygonCss(polygon) {
16146
+ return `polygon(0 ${polygon.startTopPercent}%, 100% ${polygon.endTopPercent}%, 100% 100%, 0 100%)`;
16147
+ }
16148
+ var transitionCatalogueFamilies, mediumHeight, compactHeight, largeHeight, transitionCatalogueEntries;
16149
+ var init_transitionCatalogue = __esmMin((() => {
16150
+ init_types$8();
16151
+ transitionCatalogueFamilies = [
16152
+ {
16153
+ id: "gradient",
16154
+ label: "Gradient"
16155
+ },
16156
+ {
16157
+ id: "wave",
16158
+ label: "Wave"
16159
+ },
16160
+ {
16161
+ id: "angle",
16162
+ label: "Angle"
16163
+ }
16164
+ ];
16165
+ mediumHeight = "6rem + 2px";
16166
+ compactHeight = "4.5rem + 2px";
16167
+ largeHeight = "7rem + 2px";
16168
+ transitionCatalogueEntries = defineExactCatalog()([
16169
+ {
16170
+ intent: "soft-fade-short",
16171
+ optionId: asBoundaryOptionId("break:soft"),
16172
+ family: "gradient",
16173
+ variantId: "soft",
16174
+ label: "Soft",
16175
+ variantLabel: "Soft",
16176
+ treatment: {
16177
+ kind: "soft-fade",
16178
+ height: "medium",
16179
+ curve: "soft"
16180
+ },
16181
+ size: "medium",
16182
+ heightExpression: mediumHeight,
16183
+ shape: { kind: "fade" }
16184
+ },
16185
+ {
16186
+ intent: "soft-fade-long",
16187
+ optionId: asBoundaryOptionId("break:spacious"),
16188
+ family: "gradient",
16189
+ variantId: "spacious",
16190
+ label: "Spacious",
16191
+ variantLabel: "Spacious",
16192
+ treatment: {
16193
+ kind: "soft-fade",
16194
+ height: "large",
16195
+ curve: "spacious"
16196
+ },
16197
+ size: "spacious",
16198
+ heightExpression: "10rem",
16199
+ shape: { kind: "fade" }
16200
+ },
16201
+ {
16202
+ intent: "soft-fade-expansive",
16203
+ optionId: asBoundaryOptionId("break:expansive"),
16204
+ family: "gradient",
16205
+ variantId: "expansive",
16206
+ label: "Expansive",
16207
+ variantLabel: "Expansive",
16208
+ treatment: {
16209
+ kind: "soft-fade",
16210
+ height: "expansive",
16211
+ curve: "expansive"
16212
+ },
16213
+ size: "expansive",
16214
+ heightExpression: "14rem",
16215
+ shape: { kind: "fade" }
16216
+ },
16217
+ {
16218
+ intent: "wave-edge-balanced",
16219
+ optionId: asBoundaryOptionId("break:wave-balanced"),
16220
+ family: "wave",
16221
+ variantId: "balanced",
16222
+ label: "Balanced wave",
16223
+ variantLabel: "Balanced",
16224
+ treatment: {
16225
+ kind: "wave-edge",
16226
+ height: "medium",
16227
+ amplitude: "balanced",
16228
+ rhythm: "double",
16229
+ symmetry: "symmetrical"
16230
+ },
16231
+ size: "medium",
16232
+ heightExpression: mediumHeight,
16233
+ shape: {
16234
+ kind: "wave",
16235
+ mask: "balanced"
16236
+ }
16237
+ },
16238
+ {
16239
+ intent: "wave-edge",
16240
+ optionId: asBoundaryOptionId("break:wave"),
16241
+ family: "wave",
16242
+ variantId: "classic",
16243
+ label: "Classic wave",
16244
+ variantLabel: "Classic",
16245
+ treatment: {
16246
+ kind: "wave-edge",
16247
+ height: "medium",
16248
+ amplitude: "balanced",
16249
+ rhythm: "double",
16250
+ symmetry: "asymmetrical"
16251
+ },
16252
+ size: "medium",
16253
+ heightExpression: mediumHeight,
16254
+ shape: {
16255
+ kind: "wave",
16256
+ mask: "classic"
16257
+ }
16258
+ },
16259
+ {
16260
+ intent: "wave-edge-gentle",
16261
+ optionId: asBoundaryOptionId("break:wave-gentle"),
16262
+ family: "wave",
16263
+ variantId: "gentle",
16264
+ label: "Gentle wave",
16265
+ variantLabel: "Gentle",
16266
+ treatment: {
16267
+ kind: "wave-edge",
16268
+ height: "compact",
16269
+ amplitude: "subtle",
16270
+ rhythm: "single",
16271
+ symmetry: "symmetrical"
16272
+ },
16273
+ size: "compact",
16274
+ heightExpression: compactHeight,
16275
+ shape: {
16276
+ kind: "wave",
16277
+ mask: "gentle"
16278
+ }
16279
+ },
16280
+ {
16281
+ intent: "wave-edge-bold",
16282
+ optionId: asBoundaryOptionId("break:wave-bold"),
16283
+ family: "wave",
16284
+ variantId: "bold",
16285
+ label: "Bold wave",
16286
+ variantLabel: "Bold",
16287
+ treatment: {
16288
+ kind: "wave-edge",
16289
+ height: "large",
16290
+ amplitude: "bold",
16291
+ rhythm: "double",
16292
+ symmetry: "symmetrical"
16293
+ },
16294
+ size: "large",
16295
+ heightExpression: largeHeight,
16296
+ shape: {
16297
+ kind: "wave",
16298
+ mask: "bold"
16299
+ }
16300
+ },
16301
+ {
16302
+ intent: "wave-edge-drift",
16303
+ optionId: asBoundaryOptionId("break:wave-drift"),
16304
+ family: "wave",
16305
+ variantId: "drift",
16306
+ label: "Drift wave",
16307
+ variantLabel: "Drift",
16308
+ treatment: {
16309
+ kind: "wave-edge",
16310
+ height: "medium",
16311
+ amplitude: "balanced",
16312
+ rhythm: "double",
16313
+ symmetry: "drift"
16314
+ },
16315
+ size: "medium",
16316
+ heightExpression: mediumHeight,
16317
+ shape: {
16318
+ kind: "wave",
16319
+ mask: "drift"
16320
+ }
16321
+ },
16322
+ {
16323
+ intent: "angle-edge-subtle",
16324
+ optionId: asBoundaryOptionId("break:angle-subtle"),
16325
+ family: "angle",
16326
+ variantId: "subtle",
16327
+ label: "Subtle angle",
16328
+ variantLabel: "Subtle",
16329
+ treatment: {
16330
+ kind: "angle-edge",
16331
+ height: "compact",
16332
+ slope: "subtle",
16333
+ direction: "forward"
16334
+ },
16335
+ size: "compact",
16336
+ heightExpression: compactHeight,
16337
+ shape: {
16338
+ kind: "angle",
16339
+ polygon: {
16340
+ startTopPercent: 34,
16341
+ endTopPercent: 18
16342
+ }
16343
+ }
16344
+ },
16345
+ {
16346
+ intent: "angle-edge",
16347
+ optionId: asBoundaryOptionId("break:angle-balanced"),
16348
+ family: "angle",
16349
+ variantId: "balanced",
16350
+ label: "Balanced angle",
16351
+ variantLabel: "Balanced",
16352
+ treatment: {
16353
+ kind: "angle-edge",
16354
+ height: "medium",
16355
+ slope: "balanced",
16356
+ direction: "forward"
16357
+ },
16358
+ size: "medium",
16359
+ heightExpression: mediumHeight,
16360
+ shape: {
16361
+ kind: "angle",
16362
+ polygon: {
16363
+ startTopPercent: 42,
16364
+ endTopPercent: 8
16365
+ }
16366
+ }
16367
+ },
16368
+ {
16369
+ intent: "angle-edge-steep",
16370
+ optionId: asBoundaryOptionId("break:angle-steep"),
16371
+ family: "angle",
16372
+ variantId: "steep",
16373
+ label: "Steep angle",
16374
+ variantLabel: "Steep",
16375
+ treatment: {
16376
+ kind: "angle-edge",
16377
+ height: "large",
16378
+ slope: "steep",
16379
+ direction: "forward"
16380
+ },
16381
+ size: "large",
16382
+ heightExpression: largeHeight,
16383
+ shape: {
16384
+ kind: "angle",
16385
+ polygon: {
16386
+ startTopPercent: 54,
16387
+ endTopPercent: 0
16388
+ }
16389
+ }
16390
+ }
16391
+ ]);
16030
16392
  }));
16031
- var init_schemaVersionedJson = __esmMin((() => {}));
16032
16393
  //#endregion
16033
16394
  //#region ../theme-core/src/site-styles/sectionTransitions.ts
16034
16395
  function boundaryRuntimeMetrics(family, size, heightExpression) {
@@ -16039,62 +16400,268 @@ function boundaryRuntimeMetrics(family, size, heightExpression) {
16039
16400
  reserveCss: `calc((${heightExpression}) / 2)`
16040
16401
  };
16041
16402
  }
16042
- function boundaryRuntimeMetricsForTransition(transition) {
16043
- return sectionBoundaryRuntimeMetricsByTransition[transition];
16403
+ /**
16404
+ * The catalogue's compile-time coverage check guarantees an entry per
16405
+ * decorative intent, so a miss here is a broken invariant, not bad input.
16406
+ */
16407
+ function requireCatalogueValue(values, intent) {
16408
+ const value = values.get(intent);
16409
+ if (value === void 0) throw new Error(`Transition catalogue has no entry for ${intent}`);
16410
+ return value;
16411
+ }
16412
+ function boundaryRuntimeMetricsForDecorativeTransition(transition) {
16413
+ return requireCatalogueValue(metricsByIntent, transition);
16414
+ }
16415
+ function boundaryOptionForTransitionIntent(intent) {
16416
+ return requireCatalogueValue(boundaryOptionByIntent, intent);
16417
+ }
16418
+ function transitionVariantForEntry(entry) {
16419
+ return {
16420
+ familyId: entry.family,
16421
+ variantId: entry.variantId,
16422
+ label: entry.variantLabel,
16423
+ boundaryOption: boundaryOptionForTransitionIntent(entry.intent)
16424
+ };
16425
+ }
16426
+ function stableTreatmentKey(treatment) {
16427
+ switch (treatment.kind) {
16428
+ case "none": return "none";
16429
+ case "soft-fade": return `${treatment.kind}:${treatment.height}:${treatment.curve}`;
16430
+ case "wave-edge": return [
16431
+ treatment.kind,
16432
+ treatment.height,
16433
+ treatment.amplitude,
16434
+ treatment.rhythm,
16435
+ treatment.symmetry
16436
+ ].join(":");
16437
+ case "angle-edge": return [
16438
+ treatment.kind,
16439
+ treatment.height,
16440
+ treatment.slope,
16441
+ treatment.direction
16442
+ ].join(":");
16443
+ case "legacy-unsupported": return `${treatment.kind}:${treatment.boundaryOptionId}`;
16444
+ }
16044
16445
  }
16045
- var sectionBoundaryRuntimeMetricsByTransition, noneBoundaryOption, softBoundaryOption, spaciousBoundaryOption;
16446
+ var metricsByIntent, noneBoundaryOption, boundaryOptionByIntent, softBoundaryOption, spaciousBoundaryOption, boundaryOptions, noneTransitionVariant, sectionTransitionFamilies;
16046
16447
  var init_sectionTransitions = __esmMin((() => {
16047
16448
  init_types$8();
16048
- sectionBoundaryRuntimeMetricsByTransition = {
16049
- none: null,
16050
- "soft-fade-short": boundaryRuntimeMetrics("gradient", "medium", "6rem + 2px"),
16051
- "soft-fade-long": boundaryRuntimeMetrics("gradient", "spacious", "10rem"),
16052
- "soft-fade-expansive": boundaryRuntimeMetrics("gradient", "expansive", "14rem"),
16053
- "wave-edge": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16054
- "wave-edge-balanced": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16055
- "wave-edge-gentle": boundaryRuntimeMetrics("wave", "compact", "4.5rem + 2px"),
16056
- "wave-edge-bold": boundaryRuntimeMetrics("wave", "large", "7rem + 2px"),
16057
- "wave-edge-drift": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
16058
- "angle-edge": boundaryRuntimeMetrics("angle", "medium", "6rem + 2px"),
16059
- "angle-edge-subtle": boundaryRuntimeMetrics("angle", "compact", "4.5rem + 2px"),
16060
- "angle-edge-steep": boundaryRuntimeMetrics("angle", "large", "7rem + 2px")
16061
- };
16449
+ init_transitionCatalogue();
16450
+ new Map(transitionCatalogueEntries.map((entry) => [entry.intent, entry]));
16451
+ metricsByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, boundaryRuntimeMetrics(entry.family, entry.size, entry.heightExpression)]));
16062
16452
  noneBoundaryOption = {
16063
16453
  id: asBoundaryOptionId("break:none"),
16064
16454
  label: "None",
16065
16455
  transitionAfter: "none",
16066
16456
  treatment: { kind: "none" }
16067
16457
  };
16068
- softBoundaryOption = {
16069
- id: asBoundaryOptionId("break:soft"),
16070
- label: "Soft",
16071
- transitionAfter: "soft-fade-short",
16072
- treatment: {
16073
- kind: "soft-fade",
16074
- height: "medium",
16075
- curve: "soft"
16458
+ boundaryOptionByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, {
16459
+ id: entry.optionId,
16460
+ label: entry.label,
16461
+ transitionAfter: entry.intent,
16462
+ treatment: entry.treatment
16463
+ }]));
16464
+ softBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-short");
16465
+ spaciousBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-long");
16466
+ boundaryOptionForTransitionIntent("soft-fade-expansive");
16467
+ boundaryOptionForTransitionIntent("wave-edge");
16468
+ boundaryOptionForTransitionIntent("wave-edge-balanced");
16469
+ boundaryOptionForTransitionIntent("wave-edge-gentle");
16470
+ boundaryOptionForTransitionIntent("wave-edge-bold");
16471
+ boundaryOptionForTransitionIntent("wave-edge-drift");
16472
+ boundaryOptionForTransitionIntent("angle-edge-subtle");
16473
+ boundaryOptionForTransitionIntent("angle-edge");
16474
+ boundaryOptionForTransitionIntent("angle-edge-steep");
16475
+ boundaryOptions = [noneBoundaryOption, ...transitionCatalogueEntries.map((entry) => boundaryOptionForTransitionIntent(entry.intent))];
16476
+ noneTransitionVariant = {
16477
+ familyId: "none",
16478
+ variantId: "none",
16479
+ label: "None",
16480
+ boundaryOption: noneBoundaryOption
16481
+ };
16482
+ sectionTransitionFamilies = [{
16483
+ id: "none",
16484
+ label: "None",
16485
+ variants: [noneTransitionVariant]
16486
+ }, ...transitionCatalogueFamilies.map((family) => ({
16487
+ id: family.id,
16488
+ label: family.label,
16489
+ variants: requireNonEmptyReadonlyArray(transitionCatalogueEntries.filter((entry) => entry.family === family.id).map(transitionVariantForEntry), `Transition family ${family.id} has no catalogue entries.`)
16490
+ }))];
16491
+ new Map(boundaryOptions.map((option) => [option.id, option]));
16492
+ new Map(boundaryOptions.map((option) => [stableTreatmentKey(option.treatment), option]));
16493
+ new Map(sectionTransitionFamilies.flatMap((family) => family.variants.map((variant) => [variant.boundaryOption.id, variant])));
16494
+ new Map(sectionTransitionFamilies.map((family) => [family.id, family]));
16495
+ }));
16496
+ //#endregion
16497
+ //#region ../theme-core/src/site-styles/sectionBoundaryComposition.ts
16498
+ /**
16499
+ * Classifies a section from its final render content. `_sectionStyles`
16500
+ * already holds the effective background (Look default, page-design override
16501
+ * and block-authored media merged by `resolveSectionBackground`), so this only
16502
+ * reads it and never re-decides precedence. A missing or unparseable
16503
+ * background is the page background, as it is for the renderer.
16504
+ */
16505
+ function boundaryEndpointForSectionContent(content) {
16506
+ const background = parseCanonicalRuntimeBackground(content);
16507
+ if (background.kind === "none") return {
16508
+ kind: "paint",
16509
+ token: backgroundToken
16510
+ };
16511
+ const effective = background.background;
16512
+ switch (effective.type) {
16513
+ case "color": {
16514
+ if (effective.color === null || effective.color === void 0) return {
16515
+ kind: "paint",
16516
+ token: backgroundToken
16517
+ };
16518
+ const token = parseThemeColorToken(effective.color);
16519
+ return token ? {
16520
+ kind: "paint",
16521
+ token
16522
+ } : {
16523
+ kind: "unpainted",
16524
+ reason: "literal-color"
16525
+ };
16526
+ }
16527
+ case "gradient": return {
16528
+ kind: "unpainted",
16529
+ reason: "gradient"
16530
+ };
16531
+ case "image":
16532
+ case "video": return {
16533
+ kind: "unpainted",
16534
+ reason: "media"
16535
+ };
16536
+ default: return assertNever$12(effective, "Unhandled section background type");
16537
+ }
16538
+ }
16539
+ /**
16540
+ * Reads both facts from final render content in one place. `_frameStyles` is
16541
+ * the compiled frame; content without one (no page design, an SDK block)
16542
+ * draws a plain frame.
16543
+ */
16544
+ function sectionFactsForContent(content) {
16545
+ return {
16546
+ endpoint: boundaryEndpointForSectionContent(content),
16547
+ frame: frameKindForContent(content)
16548
+ };
16549
+ }
16550
+ function frameKindForContent(content) {
16551
+ const frameStyles = content._frameStyles;
16552
+ const kind = typeof frameStyles === "object" && frameStyles !== null && "kind" in frameStyles ? frameStyles.kind : null;
16553
+ return kind === "panel" || kind === "flush-panel" ? kind : "plain";
16554
+ }
16555
+ /**
16556
+ * The frame rule: a flush panel owns the edge it runs to, so no break is
16557
+ * drawn next to one. The single home for this rule; the generated
16558
+ * pipeline's budget and the site-style lab ask it with their plan frames.
16559
+ */
16560
+ function frameNotShownReason(previous, next) {
16561
+ return previous === "flush-panel" || next === "flush-panel" ? "flush-panel" : null;
16562
+ }
16563
+ /**
16564
+ * Decides whether and how a join draws. The frame rule wins before paint is
16565
+ * considered; two unpainted sides show nothing.
16566
+ */
16567
+ function composeBoundary(previous, next) {
16568
+ const frameReason = frameNotShownReason(previous.frame, next.frame);
16569
+ if (frameReason) return {
16570
+ kind: "not-shown",
16571
+ reason: frameReason
16572
+ };
16573
+ const from = previous.endpoint;
16574
+ const to = next.endpoint;
16575
+ if (from.kind === "paint") return {
16576
+ kind: "shown",
16577
+ composition: to.kind === "paint" ? {
16578
+ kind: "paint-to-paint",
16579
+ from: from.token,
16580
+ to: to.token
16581
+ } : {
16582
+ kind: "paint-to-unpainted",
16583
+ from: from.token
16076
16584
  }
16077
16585
  };
16078
- spaciousBoundaryOption = {
16079
- id: asBoundaryOptionId("break:spacious"),
16080
- label: "Spacious",
16081
- transitionAfter: "soft-fade-long",
16082
- treatment: {
16083
- kind: "soft-fade",
16084
- height: "large",
16085
- curve: "spacious"
16586
+ return to.kind === "paint" ? {
16587
+ kind: "shown",
16588
+ composition: {
16589
+ kind: "unpainted-to-paint",
16590
+ to: to.token
16086
16591
  }
16592
+ } : {
16593
+ kind: "not-shown",
16594
+ reason: "both-unpainted"
16595
+ };
16596
+ }
16597
+ /**
16598
+ * Placement, reserves and paint for a shown boundary, derived from the
16599
+ * composition and the catalogue's metrics for the treatment.
16600
+ */
16601
+ function boundaryLayout(intent, composition) {
16602
+ const metrics = boundaryRuntimeMetricsForDecorativeTransition(intent);
16603
+ switch (composition.kind) {
16604
+ case "paint-to-paint": return {
16605
+ placement: "centred",
16606
+ reserves: {
16607
+ before: metrics.reserveCss,
16608
+ after: metrics.reserveCss
16609
+ },
16610
+ paint: {
16611
+ band: composition.from,
16612
+ shape: composition.to
16613
+ }
16614
+ };
16615
+ case "unpainted-to-paint": return {
16616
+ placement: "previous",
16617
+ reserves: {
16618
+ before: metrics.heightCss,
16619
+ after: null
16620
+ },
16621
+ paint: {
16622
+ band: null,
16623
+ shape: composition.to
16624
+ }
16625
+ };
16626
+ case "paint-to-unpainted": return {
16627
+ placement: "next",
16628
+ reserves: {
16629
+ before: null,
16630
+ after: metrics.heightCss
16631
+ },
16632
+ paint: {
16633
+ band: null,
16634
+ shape: composition.from
16635
+ }
16636
+ };
16637
+ default: return assertNever$12(composition, "Unhandled boundary composition");
16638
+ }
16639
+ }
16640
+ function requireThemeColorToken$1(value) {
16641
+ const token = parseThemeColorToken(value);
16642
+ if (!token) throw new Error(`Expected a theme color token. Received: ${value}`);
16643
+ return token;
16644
+ }
16645
+ var backgroundToken;
16646
+ var init_sectionBoundaryComposition = __esmMin((() => {
16647
+ init_themeColorTokens();
16648
+ init_runtimeBackgroundResolution();
16649
+ init_sectionTransitions();
16650
+ init_types$8();
16651
+ backgroundToken = requireThemeColorToken$1("background");
16652
+ }));
16653
+ var init_compositionBudget = __esmMin((() => {
16654
+ init_types$8();
16655
+ init_sectionBoundaryComposition();
16656
+ }));
16657
+ var defaultHeaderBrandPreferences;
16658
+ var init_compilationPlan = __esmMin((() => {
16659
+ defaultHeaderBrandPreferences = {
16660
+ brandDisplay: "auto",
16661
+ logoSize: "auto"
16087
16662
  };
16088
- asBoundaryOptionId("break:expansive");
16089
- asBoundaryOptionId("break:wave");
16090
- asBoundaryOptionId("break:wave-balanced");
16091
- asBoundaryOptionId("break:wave-gentle");
16092
- asBoundaryOptionId("break:wave-bold");
16093
- asBoundaryOptionId("break:wave-drift");
16094
- asBoundaryOptionId("break:angle-subtle");
16095
- asBoundaryOptionId("break:angle-balanced");
16096
- asBoundaryOptionId("break:angle-steep");
16097
16663
  }));
16664
+ var init_schemaVersionedJson = __esmMin((() => {}));
16098
16665
  function addDuplicateStringIssues(ctx, input) {
16099
16666
  const seen = /* @__PURE__ */ new Set();
16100
16667
  const duplicates = /* @__PURE__ */ new Set();
@@ -16123,14 +16690,11 @@ function validateUniquePageDesignPins(intent, ctx) {
16123
16690
  label: "block Look pin"
16124
16691
  });
16125
16692
  addDuplicateStringIssues(ctx, {
16126
- values: intent.boundaryPins.map((pin) => boundaryKey$1(pin.previousBlockId, pin.nextBlockId)),
16693
+ values: intent.boundaryPins.map((pin) => makeBoundaryId(pin.previousBlockId, pin.nextBlockId)),
16127
16694
  path: ["boundaryPins"],
16128
16695
  label: "boundary pin"
16129
16696
  });
16130
16697
  }
16131
- function boundaryKey$1(previousBlockId, nextBlockId) {
16132
- return makeBoundaryId(previousBlockId, nextBlockId);
16133
- }
16134
16698
  var pageDesignUserPinnedAppearanceChoiceSchema, pageDesignUserPinnedBoundaryChoiceSchema, pageDesignBoundaryTreatmentSchema, pageDesignUserPinnedBoundaryTreatmentChoiceSchema, pageDesignBlockLookPinSchema, pageDesignBoundaryPinSchema, legacyPageDesignBoundaryPinSchema;
16135
16699
  var init_pageDesignIntent = __esmMin((() => {
16136
16700
  init_types$8();
@@ -19197,6 +19761,33 @@ var init_designState = __esmMin((() => {
19197
19761
  authority: styleAuthoritySchema
19198
19762
  }).strict();
19199
19763
  }));
19764
+ var init_pageFlowPresets = __esmMin((() => {
19765
+ init_sectionTransitions();
19766
+ init_types$8();
19767
+ asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
19768
+ })), init_lookResolution = __esmMin((() => {
19769
+ init_appearancePresets();
19770
+ init_types$8();
19771
+ }));
19772
+ var init_siteStyleBlockPolicies = __esmMin((() => {
19773
+ init_lookResolution();
19774
+ init_appearancePresets();
19775
+ init_pageDesignIntent();
19776
+ init_types$8();
19777
+ asPageFlowPresetId("generated-default");
19778
+ asSiteStyleId("site-style:south-west-twerk-school");
19779
+ asAppearancePresetId("hero-brand-scrim-fixed-frame");
19780
+ asLayoutVariantKey("classic");
19781
+ asLayoutVariantKey("micro");
19782
+ asBoundaryOptionId("break:wave");
19783
+ }));
19784
+ var init_joinTreatment = __esmMin((() => {
19785
+ init_pageFlowPresets();
19786
+ init_pageDesignIntent();
19787
+ init_sectionTransitions();
19788
+ init_siteStyleBlockPolicies();
19789
+ init_types$8();
19790
+ }));
19200
19791
  var init_imageSurfaceBackground = __esmMin((() => {
19201
19792
  init_types$8();
19202
19793
  }));
@@ -19229,37 +19820,6 @@ function uniformSectionForeground(token) {
19229
19820
  supportingColor: token
19230
19821
  };
19231
19822
  }
19232
- function compileTransitionRuntime(input) {
19233
- return {
19234
- treatment: input.treatment,
19235
- from: input.from,
19236
- to: input.to,
19237
- cssVars: {
19238
- from: input.from.kind === "surface" && input.from.surface === "hero-image" ? "transparent" : transitionEndpointCssValue(input.from),
19239
- to: transitionEndpointCssValue(input.to)
19240
- }
19241
- };
19242
- }
19243
- function transitionEndpointCssValue(endpoint) {
19244
- switch (endpoint.kind) {
19245
- case "color-token": return themeColorTokenCssValue(endpoint.token);
19246
- case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
19247
- default: return assertNever$12(endpoint);
19248
- }
19249
- }
19250
- function tokenForTransitionSurface(surface) {
19251
- switch (surface) {
19252
- case "base":
19253
- case "hero-image": return "background";
19254
- case "muted-band": return "muted";
19255
- case "accent-band": return "accent";
19256
- case "brand-depth-band": return "primary-800";
19257
- default: return assertNever$12(surface);
19258
- }
19259
- }
19260
- function themeColorTokenCssValue(token) {
19261
- return `rgb(var(--tb-${token}))`;
19262
- }
19263
19823
  var defaultDesignRuntimeRules, defaultComponentRuntimeRules;
19264
19824
  var init_runtimeCompiler = __esmMin((() => {
19265
19825
  init_schema$2();
@@ -19268,8 +19828,9 @@ var init_runtimeCompiler = __esmMin((() => {
19268
19828
  init_styles();
19269
19829
  init_designState();
19270
19830
  init_types$8();
19831
+ init_joinTreatment();
19832
+ init_rawStyleFields();
19271
19833
  init_accessibleSurfaces();
19272
- init_sectionTransitions();
19273
19834
  init_tokens();
19274
19835
  init_gradients();
19275
19836
  init_imageSurfaceBackground();
@@ -19306,8 +19867,7 @@ var init_runtimeCompiler = __esmMin((() => {
19306
19867
  color: "surface",
19307
19868
  opacity: .88
19308
19869
  }
19309
- },
19310
- transitionClassName: (transition, fromSurface, toSurface) => transition === "none" ? null : `site-style-transition-${transition}-from-${fromSurface}-to-${toSurface}`
19870
+ }
19311
19871
  });
19312
19872
  defaultComponentRuntimeRules = defineComponentRuntimeRules({
19313
19873
  panelFrames: {
@@ -21046,22 +21606,12 @@ var init_curatedSiteStyles = __esmMin((() => {
21046
21606
  ];
21047
21607
  curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id, style]));
21048
21608
  }));
21049
- var init_pageDesignEditorCapabilities = __esmMin((() => {})), init_lookResolution = __esmMin((() => {
21050
- init_appearancePresets();
21051
- init_types$8();
21052
- }));
21053
- var init_siteStyleBlockPolicies = __esmMin((() => {
21054
- init_lookResolution();
21055
- init_appearancePresets();
21609
+ var init_pageDesignEditorCapabilities = __esmMin((() => {}));
21610
+ var init_pageDesignEditorBoundaries = __esmMin((() => {
21056
21611
  init_pageDesignIntent();
21057
- init_types$8();
21612
+ init_joinTreatment();
21058
21613
  init_sectionTransitions();
21059
- asPageFlowPresetId("generated-default");
21060
- asSiteStyleId("site-style:south-west-twerk-school");
21061
- asAppearancePresetId("hero-brand-scrim-fixed-frame");
21062
- asLayoutVariantKey("classic");
21063
- asLayoutVariantKey("micro");
21064
- asBoundaryOptionId("break:wave");
21614
+ init_types$8();
21065
21615
  }));
21066
21616
  var init_pageDesignEditorModel = __esmMin((() => {
21067
21617
  init_curatedSiteStyles();
@@ -21070,8 +21620,9 @@ var init_pageDesignEditorModel = __esmMin((() => {
21070
21620
  init_siteStyleBlockPolicies();
21071
21621
  init_pageDesignIntent();
21072
21622
  init_types$8();
21623
+ init_pageFlowPresets();
21624
+ init_pageDesignEditorBoundaries();
21073
21625
  init_sectionTransitions();
21074
- asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
21075
21626
  })), init_concretePalette = __esmMin((() => {
21076
21627
  init_colors();
21077
21628
  })), blockDesignFallback;
@@ -21080,7 +21631,6 @@ var init_resolver = __esmMin((() => {
21080
21631
  blockDesignFallback = {
21081
21632
  sectionSurface: "base",
21082
21633
  contentFrame: plainFrame,
21083
- transitionAfter: "none",
21084
21634
  sectionHeight: "default",
21085
21635
  heroLegibility: "none",
21086
21636
  imageSurfaceTreatment: { kind: "none" },
@@ -21091,7 +21641,7 @@ var init_resolver = __esmMin((() => {
21091
21641
  accordionIconTreatment: null,
21092
21642
  composition: null
21093
21643
  };
21094
- blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
21644
+ blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
21095
21645
  }));
21096
21646
  function parseSiteChromeBackgroundColorToken(value) {
21097
21647
  const token = parseBackgroundAppearanceColorToken(value);
@@ -21205,11 +21755,6 @@ var init_headerRecompile = __esmMin((() => {
21205
21755
  init_headerBrandPresentation();
21206
21756
  init_siteChromeNavPreferences();
21207
21757
  }));
21208
- var init_runtimeBackgroundResolution = __esmMin((() => {
21209
- init_boxBackgroundSchema();
21210
- init_surfaces();
21211
- init_rawStyleFields();
21212
- }));
21213
21758
  var init_previewRuntimeCapabilities = __esmMin((() => {
21214
21759
  init_types$8();
21215
21760
  }));
@@ -21228,38 +21773,9 @@ var init_buttonSelectionCompiler = __esmMin((() => {
21228
21773
  var init_previewCustomization = __esmMin((() => {
21229
21774
  init_types$8();
21230
21775
  }));
21231
- function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
21232
- const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
21233
- const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
21234
- if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
21235
- const transition = compileTransitionRuntime({
21236
- treatment: boundary.treatment,
21237
- from: fromSurface,
21238
- to: toSurface
21239
- });
21240
- return {
21241
- ...boundary,
21242
- from: transition.from,
21243
- to: transition.to,
21244
- cssVars: transition.cssVars
21245
- };
21246
- }
21247
- function contentOwnedBackgroundEndpoint(content) {
21248
- const background = readRecord(readRecord(content?._sectionStyles)?.background);
21249
- if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
21250
- const token = parseThemeColorToken(background.color);
21251
- if (!token) return null;
21252
- return {
21253
- kind: "color-token",
21254
- token
21255
- };
21256
- }
21257
- function readRecord(value) {
21258
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
21259
- }
21260
21776
  var init_previewCompiler = __esmMin((() => {
21777
+ init_sectionTransitions();
21261
21778
  init_schema$2();
21262
- init_tokens();
21263
21779
  init_curated();
21264
21780
  init_concretePalette();
21265
21781
  init_curatedSiteStyles();
@@ -21269,7 +21785,6 @@ var init_previewCompiler = __esmMin((() => {
21269
21785
  init_headerRecompile();
21270
21786
  init_siteChromeNavPreferences();
21271
21787
  init_runtimeCompiler();
21272
- init_rawStyleFields();
21273
21788
  init_runtimeBackgroundResolution();
21274
21789
  init_previewRuntimeCapabilities();
21275
21790
  init_siteStyleThemeCompiler();
@@ -21279,6 +21794,7 @@ var init_previewCompiler = __esmMin((() => {
21279
21794
  init_pageDesignIntent();
21280
21795
  init_previewCustomization();
21281
21796
  init_curatedChoices();
21797
+ init_joinTreatment();
21282
21798
  init_types$8();
21283
21799
  }));
21284
21800
  var init_contentTemplateRuntimeCompiler = __esmMin((() => {
@@ -21380,6 +21896,7 @@ var init_generatedDesignPipeline = __esmMin((() => {
21380
21896
  init_curatedSiteStyles();
21381
21897
  init_lookResolution();
21382
21898
  init_headerOpeningSection();
21899
+ init_joinTreatment();
21383
21900
  init_resolver();
21384
21901
  init_runtimeCompiler();
21385
21902
  init_types$8();
@@ -21748,6 +22265,7 @@ var init_site_styles = __esmMin((() => {
21748
22265
  init_headerRecompile();
21749
22266
  init_headerOpeningForeground();
21750
22267
  init_headerOpeningSection();
22268
+ init_joinTreatment();
21751
22269
  init_lookResolution();
21752
22270
  init_lookOptionGroups();
21753
22271
  init_logoBranding();
@@ -21767,6 +22285,7 @@ var init_site_styles = __esmMin((() => {
21767
22285
  init_runtimeCompiler();
21768
22286
  init_runtimeBackgroundResolution();
21769
22287
  init_sectionTransitions();
22288
+ init_sectionBoundaryComposition();
21770
22289
  init_siteChromeBackgrounds();
21771
22290
  init_siteChromeNavPreferences();
21772
22291
  init_siteStyleThemeCompiler();
@@ -21774,6 +22293,7 @@ var init_site_styles = __esmMin((() => {
21774
22293
  init_sourceCatalogs();
21775
22294
  init_styleTags();
21776
22295
  init_tokenRecipes();
22296
+ init_transitionCatalogue();
21777
22297
  init_siteStyleTypography();
21778
22298
  init_themeParity();
21779
22299
  init_types$8();
@@ -24052,7 +24572,8 @@ var init_schema = __esmMin((() => {
24052
24572
  "eventCategories",
24053
24573
  "eventSeries",
24054
24574
  "venues",
24055
- "staffMembers"
24575
+ "staffMembers",
24576
+ "appointmentServiceCategories"
24056
24577
  ];
24057
24578
  mediaDeliverySchema = z$1.object({
24058
24579
  sizes: z$1.string().optional(),
@@ -28221,7 +28742,7 @@ function successSubmissionFeedback(message) {
28221
28742
  message
28222
28743
  };
28223
28744
  }
28224
- function isRecord$24(value) {
28745
+ function isRecord$25(value) {
28225
28746
  return typeof value === "object" && value !== null && !Array.isArray(value);
28226
28747
  }
28227
28748
  function deepEqualJsonValue(left, right) {
@@ -28231,8 +28752,8 @@ function deepEqualJsonValue(left, right) {
28231
28752
  if (left.length !== right.length) return false;
28232
28753
  return left.every((item, index) => deepEqualJsonValue(item, right[index]));
28233
28754
  }
28234
- if (isRecord$24(left) || isRecord$24(right)) {
28235
- if (!isRecord$24(left) || !isRecord$24(right)) return false;
28755
+ if (isRecord$25(left) || isRecord$25(right)) {
28756
+ if (!isRecord$25(left) || !isRecord$25(right)) return false;
28236
28757
  const leftKeys = Object.keys(left).sort();
28237
28758
  if (!deepEqualJsonValue(leftKeys, Object.keys(right).sort())) return false;
28238
28759
  return leftKeys.every((key) => deepEqualJsonValue(left[key], right[key]));
@@ -28414,12 +28935,12 @@ function resolveBookingSiteId(siteId, form) {
28414
28935
  return siteId || form?.siteId || "";
28415
28936
  }
28416
28937
  function normalizeBookingFormConfig(form) {
28417
- if (!isRecord$23(form)) return null;
28938
+ if (!isRecord$24(form)) return null;
28418
28939
  const id = readNonEmptyString(form.id);
28419
28940
  const name = readNonEmptyString(form.name);
28420
28941
  if (!id || !name) return null;
28421
- const schemaSource = isRecord$23(form.schema) ? form.schema : isRecord$23(form.schemaJson) ? form.schemaJson : null;
28422
- const settingsSource = isRecord$23(form.settings) ? form.settings : isRecord$23(form.settingsJson) ? form.settingsJson : null;
28942
+ const schemaSource = isRecord$24(form.schema) ? form.schema : isRecord$24(form.schemaJson) ? form.schemaJson : null;
28943
+ const settingsSource = isRecord$24(form.settings) ? form.settings : isRecord$24(form.settingsJson) ? form.settingsJson : null;
28423
28944
  return {
28424
28945
  id,
28425
28946
  name,
@@ -28439,8 +28960,8 @@ function normalizeBookingServices(services) {
28439
28960
  };
28440
28961
  if (!services) return [];
28441
28962
  if (Array.isArray(services)) return services.filter(isBookingFormService).map(normalizeService);
28442
- if (isRecord$23(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
28443
- return isRecord$23(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
28963
+ if (isRecord$24(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
28964
+ return isRecord$24(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
28444
28965
  }
28445
28966
  function normalizeBookingSchema(value) {
28446
28967
  if (!value) return void 0;
@@ -28451,7 +28972,7 @@ function normalizeBookingSchema(value) {
28451
28972
  };
28452
28973
  }
28453
28974
  function readBookingFormField(value) {
28454
- if (!isRecord$23(value)) return null;
28975
+ if (!isRecord$24(value)) return null;
28455
28976
  const id = readNonEmptyString(value.id);
28456
28977
  const label = readNonEmptyString(value.label);
28457
28978
  const type = readBookingFieldType(value.type);
@@ -28494,7 +29015,7 @@ function readBookingFieldOptions(value) {
28494
29015
  return options.length > 0 ? options : void 0;
28495
29016
  }
28496
29017
  function readBookingFieldOption(value) {
28497
- if (!isRecord$23(value)) return null;
29018
+ if (!isRecord$24(value)) return null;
28498
29019
  const optionValue = readNonEmptyString(value.value);
28499
29020
  const label = readNonEmptyString(value.label);
28500
29021
  return optionValue && label ? {
@@ -28514,7 +29035,7 @@ function normalizeBookingSettings(value) {
28514
29035
  };
28515
29036
  }
28516
29037
  function isBookingFormService(value) {
28517
- return isRecord$23(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
29038
+ return isRecord$24(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
28518
29039
  }
28519
29040
  function collectBookingCustomFields(data) {
28520
29041
  const customFields = {};
@@ -28564,7 +29085,7 @@ function readStringArray(value) {
28564
29085
  const strings = value.filter((item) => typeof item === "string");
28565
29086
  return strings.length > 0 ? strings : void 0;
28566
29087
  }
28567
- function isRecord$23(value) {
29088
+ function isRecord$24(value) {
28568
29089
  return typeof value === "object" && value !== null;
28569
29090
  }
28570
29091
  function getBookingServicePaymentSelectionMode(service) {
@@ -28765,6 +29286,32 @@ var init_booking_form_state = __esmMin((() => {
28765
29286
  init_booking_form_consent();
28766
29287
  }));
28767
29288
  //#endregion
29289
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-categories.ts
29290
+ function isRecord$23(value) {
29291
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29292
+ }
29293
+ function readCategory(value) {
29294
+ if (!isRecord$23(value)) return [];
29295
+ const { id, name, description } = value;
29296
+ if (typeof id !== "string" || id.trim() === "" || typeof name !== "string") return [];
29297
+ return [{
29298
+ id,
29299
+ name,
29300
+ description: typeof description === "string" ? description : null
29301
+ }];
29302
+ }
29303
+ /**
29304
+ * The ordered service categories carried by a catalogue-shaped services
29305
+ * source (`{ services, categories }`). Every other shape has none, which
29306
+ * reads as a site without categories. Order is preserved: the database owns
29307
+ * it.
29308
+ */
29309
+ function normalizeBookingServiceCategories(services) {
29310
+ if (!isRecord$23(services) || !Array.isArray(services.categories)) return [];
29311
+ return services.categories.flatMap(readCategory);
29312
+ }
29313
+ var init_booking_form_service_categories = __esmMin((() => {}));
29314
+ //#endregion
28768
29315
  //#region ../logger/src/noop.ts
28769
29316
  var noop, logger;
28770
29317
  var init_noop = __esmMin((() => {
@@ -28861,11 +29408,12 @@ function buildBookingFormInteractiveIslandProps(props) {
28861
29408
  source: props
28862
29409
  });
28863
29410
  }
28864
- var blockBookingPaymentTermsAvailabilitySchema, bookingServiceSchema, bookingServicesSourceSchema, bookingConfigSchema, bookingSourceSchema, bookingDisplaySchema, bookingHydrationSchema, bookingRenderSchema, bookingFormIslandMetaCodec, bookingFormIslandMetaKeys, bookingFormBlockRuntime, bookingFormInteractiveIslandPropsCodec;
29411
+ var blockBookingPaymentTermsAvailabilitySchema, bookingServiceSchema, bookingServiceCategorySchema, bookingServicesSourceSchema, bookingConfigSchema, bookingSourceSchema, bookingDisplaySchema, bookingHydrationSchema, bookingRenderSchema, bookingFormIslandMetaCodec, bookingFormIslandMetaKeys, bookingFormBlockRuntime, bookingFormInteractiveIslandPropsCodec;
28865
29412
  var init_booking_form_interactive = __esmMin((() => {
28866
29413
  init_islands();
28867
29414
  init_runtime();
28868
29415
  init_booking_form_state();
29416
+ init_booking_form_service_categories();
28869
29417
  init_booking_runtime_sources();
28870
29418
  init_bookingPaymentTerms();
28871
29419
  init_bookingConsent();
@@ -28888,11 +29436,20 @@ var init_booking_form_interactive = __esmMin((() => {
28888
29436
  priceCurrency: z$1.string().nullable().optional(),
28889
29437
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
28890
29438
  paymentTermsAvailability: blockBookingPaymentTermsAvailabilitySchema.optional(),
28891
- consent: blockBookingConsentSchema.optional()
29439
+ consent: blockBookingConsentSchema.optional(),
29440
+ categoryId: z$1.string().nullable().optional()
29441
+ });
29442
+ bookingServiceCategorySchema = z$1.object({
29443
+ id: z$1.string(),
29444
+ name: z$1.string(),
29445
+ description: z$1.string().nullable()
28892
29446
  });
28893
29447
  bookingServicesSourceSchema = z$1.union([
28894
29448
  z$1.array(bookingServiceSchema),
28895
- z$1.object({ services: z$1.array(bookingServiceSchema).optional() }),
29449
+ z$1.object({
29450
+ services: z$1.array(bookingServiceSchema).optional(),
29451
+ categories: z$1.array(bookingServiceCategorySchema).optional()
29452
+ }),
28896
29453
  z$1.record(z$1.string(), bookingServiceSchema)
28897
29454
  ]);
28898
29455
  bookingConfigSchema = z$1.object({
@@ -28945,6 +29502,7 @@ var init_booking_form_interactive = __esmMin((() => {
28945
29502
  rebookContext: z$1.object({ kind: z$1.literal("portal_rebook") }).nullable().optional(),
28946
29503
  form: bookingConfigSchema.nullable(),
28947
29504
  services: z$1.array(bookingServiceSchema),
29505
+ serviceCategories: z$1.array(bookingServiceCategorySchema).optional(),
28948
29506
  spamProtectionEnabled: z$1.boolean().optional()
28949
29507
  });
28950
29508
  bookingRenderSchema = z$1.object({
@@ -28980,6 +29538,7 @@ var init_booking_form_interactive = __esmMin((() => {
28980
29538
  rebookContext: source.rebookContext ?? null,
28981
29539
  form,
28982
29540
  services,
29541
+ serviceCategories: normalizeBookingServiceCategories(source.services),
28983
29542
  spamProtectionEnabled: source.spamProtectionEnabled
28984
29543
  }
28985
29544
  };
@@ -35776,8 +36335,77 @@ var init_contracts = __esmMin((() => {
35776
36335
  init_proof_block_islands();
35777
36336
  }));
35778
36337
  //#endregion
35779
- //#region ../api/src/apiEndpointTypes.ts
36338
+ //#region ../api/src/defineEndpointDefinitions.ts
35780
36339
  init_contracts();
36340
+ /**
36341
+ * Validates extracted endpoint descriptors while preserving each literal path,
36342
+ * method, and parameter contract for the typed URL builders.
36343
+ */
36344
+ function defineEndpointDefinitions(definitions) {
36345
+ return definitions;
36346
+ }
36347
+ //#endregion
36348
+ //#region ../api/src/publicNewsletterEndpointDefinitions.ts
36349
+ const PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
36350
+ subscribeNewsletter: {
36351
+ path: "/public/sites/{siteId}/newsletter/subscribe",
36352
+ method: "POST",
36353
+ tags: ["public-newsletter-{siteId}"],
36354
+ auth: "public",
36355
+ responseKind: "json"
36356
+ },
36357
+ confirmNewsletter: {
36358
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
36359
+ method: "GET",
36360
+ tags: ["public-newsletter-{siteId}"],
36361
+ auth: "public",
36362
+ responseKind: "text"
36363
+ },
36364
+ unsubscribeNewsletter: {
36365
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
36366
+ method: "GET",
36367
+ tags: ["public-newsletter-{siteId}"],
36368
+ auth: "public",
36369
+ responseKind: "text"
36370
+ },
36371
+ unsubscribeNewsletterPost: {
36372
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
36373
+ method: "POST",
36374
+ tags: ["public-newsletter-{siteId}"],
36375
+ auth: "public",
36376
+ responseKind: "text"
36377
+ },
36378
+ confirmNewsletterPost: {
36379
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
36380
+ method: "POST",
36381
+ tags: ["public-newsletter-{siteId}"],
36382
+ auth: "public",
36383
+ responseKind: "text"
36384
+ },
36385
+ getNewsletterPreferences: {
36386
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
36387
+ method: "GET",
36388
+ tags: ["public-newsletter-{siteId}"],
36389
+ auth: "public",
36390
+ responseKind: "text"
36391
+ },
36392
+ saveNewsletterPreferences: {
36393
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
36394
+ method: "POST",
36395
+ tags: ["public-newsletter-{siteId}"],
36396
+ auth: "public",
36397
+ responseKind: "text"
36398
+ },
36399
+ getNewsletterArchive: {
36400
+ path: "/public/sites/{siteId}/newsletter/archive/{token}",
36401
+ method: "GET",
36402
+ tags: ["public-newsletter-{siteId}"],
36403
+ auth: "public",
36404
+ responseKind: "text"
36405
+ }
36406
+ });
36407
+ //#endregion
36408
+ //#region ../api/src/apiEndpointTypes.ts
35781
36409
  function allowedDemoMutationPolicy() {
35782
36410
  return {
35783
36411
  classification: "allowed",
@@ -35866,17 +36494,85 @@ const SDK_ENTRY_READ_ENDPOINTS = {
35866
36494
  }
35867
36495
  };
35868
36496
  //#endregion
35869
- //#region ../api/src/defineEndpointDefinitions.ts
35870
- /**
35871
- * Validates extracted endpoint descriptors while preserving each literal path,
35872
- * method, and parameter contract for the typed URL builders.
35873
- */
35874
- function defineEndpointDefinitions(definitions) {
35875
- return definitions;
35876
- }
35877
- //#endregion
35878
36497
  //#region ../api/src/appointmentEndpointDefinitions.ts
35879
36498
  const APPOINTMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
36499
+ ...defineEndpointDefinitions({
36500
+ listGroupedAppointmentServices: {
36501
+ path: "/sites/{siteId}/bookings/services/grouped",
36502
+ method: "GET",
36503
+ auth: "user",
36504
+ responseKind: "json",
36505
+ errors: ["server:internal_error"]
36506
+ },
36507
+ reorderAppointmentServices: {
36508
+ path: "/sites/{siteId}/bookings/services/order",
36509
+ method: "PUT",
36510
+ auth: "user",
36511
+ responseKind: "json",
36512
+ errors: [
36513
+ "validation:invalid_input",
36514
+ "auth:forbidden",
36515
+ "resource:not_found",
36516
+ "resource:conflict",
36517
+ "server:internal_error"
36518
+ ]
36519
+ },
36520
+ listAppointmentServiceCategories: {
36521
+ path: "/sites/{siteId}/bookings/service-categories",
36522
+ method: "GET",
36523
+ auth: "user",
36524
+ responseKind: "json",
36525
+ errors: ["server:internal_error"]
36526
+ },
36527
+ createAppointmentServiceCategory: {
36528
+ path: "/sites/{siteId}/bookings/service-categories",
36529
+ method: "POST",
36530
+ auth: "user",
36531
+ responseKind: "json",
36532
+ errors: [
36533
+ "validation:invalid_input",
36534
+ "auth:forbidden",
36535
+ "resource:conflict",
36536
+ "server:internal_error"
36537
+ ]
36538
+ },
36539
+ updateAppointmentServiceCategory: {
36540
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
36541
+ method: "PUT",
36542
+ auth: "user",
36543
+ responseKind: "json",
36544
+ errors: [
36545
+ "validation:invalid_input",
36546
+ "auth:forbidden",
36547
+ "resource:not_found",
36548
+ "resource:conflict",
36549
+ "server:internal_error"
36550
+ ]
36551
+ },
36552
+ deleteAppointmentServiceCategory: {
36553
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
36554
+ method: "DELETE",
36555
+ auth: "user",
36556
+ responseKind: "json",
36557
+ errors: [
36558
+ "auth:forbidden",
36559
+ "resource:not_found",
36560
+ "server:internal_error"
36561
+ ]
36562
+ },
36563
+ reorderAppointmentServiceCategories: {
36564
+ path: "/sites/{siteId}/bookings/service-categories/order",
36565
+ method: "PUT",
36566
+ auth: "user",
36567
+ responseKind: "json",
36568
+ errors: [
36569
+ "validation:invalid_input",
36570
+ "auth:forbidden",
36571
+ "resource:conflict",
36572
+ "server:internal_error"
36573
+ ]
36574
+ }
36575
+ }),
35880
36576
  listAppointmentResources: {
35881
36577
  path: "/sites/{siteId}/bookings/resources",
35882
36578
  method: "GET",
@@ -37024,39 +37720,6 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
37024
37720
  auth: "user",
37025
37721
  responseKind: "json",
37026
37722
  demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN
37027
- },
37028
- listNewsletterSendJobs: {
37029
- path: "/sites/{siteId}/newsletter/jobs",
37030
- method: "GET",
37031
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
37032
- auth: "user",
37033
- responseKind: "json"
37034
- },
37035
- retryNewsletterSendJob: {
37036
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/retry",
37037
- method: "POST",
37038
- errors: [
37039
- "validation:invalid_input",
37040
- "resource:not_found",
37041
- "auth:forbidden",
37042
- "server:internal_error"
37043
- ],
37044
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
37045
- auth: "user",
37046
- responseKind: "json"
37047
- },
37048
- cancelNewsletterSendJob: {
37049
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/cancel",
37050
- method: "POST",
37051
- errors: [
37052
- "validation:invalid_input",
37053
- "resource:not_found",
37054
- "auth:forbidden",
37055
- "server:internal_error"
37056
- ],
37057
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
37058
- auth: "user",
37059
- responseKind: "json"
37060
37723
  }
37061
37724
  });
37062
37725
  //#endregion
@@ -37825,6 +38488,7 @@ const BILLING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
37825
38488
  }
37826
38489
  });
37827
38490
  const API_ENDPOINTS$1 = {
38491
+ ...PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS,
37828
38492
  ...ACCOUNT_ENDPOINT_DEFINITIONS,
37829
38493
  ...PUBLIC_ONBOARDING_ATTEMPT_ENDPOINT_DEFINITIONS,
37830
38494
  ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
@@ -40959,41 +41623,6 @@ const API_ENDPOINTS$1 = {
40959
41623
  "server:internal_error"
40960
41624
  ]
40961
41625
  },
40962
- subscribeNewsletter: {
40963
- path: "/public/sites/{siteId}/newsletter/subscribe",
40964
- method: "POST",
40965
- tags: ["public-newsletter-{siteId}"],
40966
- auth: "public",
40967
- responseKind: "json"
40968
- },
40969
- confirmNewsletter: {
40970
- path: "/public/sites/{siteId}/newsletter/confirm/{token}",
40971
- method: "GET",
40972
- tags: ["public-newsletter-{siteId}"],
40973
- auth: "public",
40974
- responseKind: "text"
40975
- },
40976
- unsubscribeNewsletter: {
40977
- path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
40978
- method: "GET",
40979
- tags: ["public-newsletter-{siteId}"],
40980
- auth: "public",
40981
- responseKind: "text"
40982
- },
40983
- unsubscribeNewsletterPost: {
40984
- path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
40985
- method: "POST",
40986
- tags: ["public-newsletter-{siteId}"],
40987
- auth: "public",
40988
- responseKind: "text"
40989
- },
40990
- getNewsletterArchive: {
40991
- path: "/public/sites/{siteId}/newsletter/archive/{token}",
40992
- method: "GET",
40993
- tags: ["public-newsletter-{siteId}"],
40994
- auth: "public",
40995
- responseKind: "text"
40996
- },
40997
41626
  listSiteContentTypes: {
40998
41627
  path: "/sites/{siteId}/content-types",
40999
41628
  method: "GET",
@@ -45742,16 +46371,16 @@ const interpolateRichTextTokensOptionsSchema = z$1.object({ tokens: z$1.array(su
45742
46371
  const richTextTokenPattern = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
45743
46372
  function getSiteName(context) {
45744
46373
  const viewModel = context.viewModel;
45745
- if (!isRecord$26(viewModel)) return null;
46374
+ if (!isRecord$27(viewModel)) return null;
45746
46375
  const site = viewModel.site;
45747
- if (isRecord$26(site) && typeof site.title === "string") {
46376
+ if (isRecord$27(site) && typeof site.title === "string") {
45748
46377
  const title = site.title.trim();
45749
46378
  return title.length > 0 ? title : null;
45750
46379
  }
45751
46380
  const root = viewModel.$root;
45752
- if (isRecord$26(root)) {
46381
+ if (isRecord$27(root)) {
45753
46382
  const rootSite = root.site;
45754
- if (isRecord$26(rootSite) && typeof rootSite.title === "string") {
46383
+ if (isRecord$27(rootSite) && typeof rootSite.title === "string") {
45755
46384
  const title = rootSite.title.trim();
45756
46385
  if (title.length > 0) return title;
45757
46386
  }
@@ -45773,7 +46402,7 @@ function replaceTokenText(input, allowedTokens, tokenValues) {
45773
46402
  });
45774
46403
  }
45775
46404
  function visitRichTextNode(node, allowedTokens, tokenValues) {
45776
- if (!isRecord$26(node)) return node;
46405
+ if (!isRecord$27(node)) return node;
45777
46406
  let changed = false;
45778
46407
  const nextNode = { ...node };
45779
46408
  if (nextNode.type === "text" && typeof nextNode.text === "string") {
@@ -45794,7 +46423,7 @@ function visitRichTextNode(node, allowedTokens, tokenValues) {
45794
46423
  return changed ? nextNode : node;
45795
46424
  }
45796
46425
  function interpolateRichTextTokens(value, options, context) {
45797
- if (!isRecord$26(value)) return value;
46426
+ if (!isRecord$27(value)) return value;
45798
46427
  return visitRichTextNode(value, new Set(options.tokens), {
45799
46428
  year: String((/* @__PURE__ */ new Date()).getFullYear()),
45800
46429
  site_name: getSiteName(context)
@@ -47495,18 +48124,43 @@ const publicAppointmentServiceSchema = z$1.object({
47495
48124
  pricing: publicPricingSchema.optional(),
47496
48125
  priceAmount: z$1.number().nullable(),
47497
48126
  priceCurrency: z$1.string().nullable(),
47498
- location: publicLocationSchema
48127
+ location: publicLocationSchema,
48128
+ categoryId: z$1.string().nullable().optional().transform((value) => value ?? null)
47499
48129
  });
47500
- const publicAppointmentServicesSchema = z$1.array(publicAppointmentServiceSchema);
48130
+ const publicServiceCategorySchema = z$1.object({
48131
+ id: z$1.string().min(1),
48132
+ name: z$1.string(),
48133
+ description: z$1.string().nullable().optional().transform((value) => value ?? null)
48134
+ });
48135
+ /**
48136
+ * The loader hands over the catalogue `{ services, categories }` (issue 1403).
48137
+ * A bare services array is still accepted and read as a site with no
48138
+ * categories, so callers from before categories keep the flat list.
48139
+ */
48140
+ const publicAppointmentServicesSchema = z$1.union([z$1.array(publicAppointmentServiceSchema).transform((services) => ({
48141
+ services,
48142
+ categories: []
48143
+ })), z$1.object({
48144
+ services: z$1.array(publicAppointmentServiceSchema),
48145
+ categories: z$1.array(publicServiceCategorySchema)
48146
+ })]);
47501
48147
  const EMPTY_MESSAGE = "No appointments are available right now.";
47502
48148
  const ERROR_MESSAGE = "Appointments are temporarily unavailable. Please try again shortly.";
48149
+ const OTHER_GROUP_KEY = "other";
48150
+ const OTHER_GROUP_HEADING = "Other";
47503
48151
  function parseServices(value) {
47504
48152
  const result = publicAppointmentServicesSchema.safeParse(value);
47505
48153
  return result.success ? {
47506
48154
  kind: "valid",
47507
- services: result.data
48155
+ services: result.data.services,
48156
+ categories: result.data.categories
47508
48157
  } : { kind: "invalid" };
47509
48158
  }
48159
+ function readCategoryFilter(value) {
48160
+ if (typeof value !== "string") return null;
48161
+ const trimmed = value.trim();
48162
+ return trimmed.length > 0 ? trimmed : null;
48163
+ }
47510
48164
  function formatAppointmentDuration(durationMinutes) {
47511
48165
  return `${durationMinutes} min`;
47512
48166
  }
@@ -47580,8 +48234,44 @@ function buildRows(services, pathname, servicePathPrefix) {
47580
48234
  detailsHref: servicePathPrefix && service.slug ? `${servicePathPrefix.replace(/\/$/, "")}/${encodeURIComponent(service.slug)}` : null
47581
48235
  }));
47582
48236
  }
48237
+ const EMPTY_VIEW = {
48238
+ kind: "empty",
48239
+ message: EMPTY_MESSAGE
48240
+ };
48241
+ function categoryGroup(category, services, toRows) {
48242
+ return {
48243
+ key: category.id,
48244
+ heading: category.name,
48245
+ intro: category.description,
48246
+ rows: toRows(services)
48247
+ };
48248
+ }
48249
+ function viewFromLayout(layout, toRows) {
48250
+ switch (layout.kind) {
48251
+ case "uncategorized-site": return layout.services.length === 0 ? EMPTY_VIEW : {
48252
+ kind: "items",
48253
+ rows: toRows(layout.services)
48254
+ };
48255
+ case "categorized-site": {
48256
+ const groups = layout.groups.map((group) => categoryGroup(group.category, group.services, toRows));
48257
+ const other = layout.other.length === 0 ? [] : [{
48258
+ key: OTHER_GROUP_KEY,
48259
+ heading: OTHER_GROUP_HEADING,
48260
+ intro: null,
48261
+ rows: toRows(layout.other)
48262
+ }];
48263
+ const allGroups = [...groups, ...other];
48264
+ return allGroups.length === 0 ? EMPTY_VIEW : {
48265
+ kind: "groups",
48266
+ groups: allGroups
48267
+ };
48268
+ }
48269
+ default: return assertNever$20(layout);
48270
+ }
48271
+ }
47583
48272
  function buildAppointmentServicesView(input) {
47584
- if (!input.pathname) return {
48273
+ const pathname = input.pathname;
48274
+ if (!pathname) return {
47585
48275
  kind: "error",
47586
48276
  message: ERROR_MESSAGE
47587
48277
  };
@@ -47590,76 +48280,128 @@ function buildAppointmentServicesView(input) {
47590
48280
  kind: "error",
47591
48281
  message: ERROR_MESSAGE
47592
48282
  };
47593
- if (parsed.services.length === 0) return {
47594
- kind: "empty",
47595
- message: EMPTY_MESSAGE
47596
- };
47597
- return {
47598
- kind: "items",
47599
- rows: buildRows(parsed.services, input.pathname, input.servicePathPrefix ?? null)
47600
- };
48283
+ const toRows = (services) => buildRows(services, pathname, input.servicePathPrefix ?? null);
48284
+ const view = selectCategoryView(parsed.categories, parsed.services, readCategoryFilter(input.categoryId));
48285
+ switch (view.kind) {
48286
+ case "single": return view.group.services.length === 0 ? EMPTY_VIEW : {
48287
+ kind: "groups",
48288
+ groups: [categoryGroup(view.group.category, view.group.services, toRows)]
48289
+ };
48290
+ case "all":
48291
+ case "fallback-all": return viewFromLayout(view.layout, toRows);
48292
+ default: return assertNever$20(view);
48293
+ }
48294
+ }
48295
+ function AppointmentServiceRows({ rows, RowTitle }) {
48296
+ return /* @__PURE__ */ jsx("div", {
48297
+ className: "appointment-services-rows rb-flex rb-flex-col",
48298
+ children: rows.map((row) => /* @__PURE__ */ jsxs("article", {
48299
+ className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
48300
+ children: [
48301
+ /* @__PURE__ */ jsxs("div", {
48302
+ className: "appointment-services-row-main rb-min-w-0",
48303
+ children: [
48304
+ /* @__PURE__ */ jsx(RowTitle, {
48305
+ className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
48306
+ children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
48307
+ href: row.detailsHref,
48308
+ children: row.title
48309
+ }) : row.title
48310
+ }),
48311
+ row.description ? /* @__PURE__ */ jsx("p", {
48312
+ className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
48313
+ children: row.description
48314
+ }) : null,
48315
+ /* @__PURE__ */ jsxs("p", {
48316
+ className: "appointment-services-row-meta rb-caption status-muted",
48317
+ children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
48318
+ })
48319
+ ]
48320
+ }),
48321
+ row.priceLabel ? /* @__PURE__ */ jsx("span", {
48322
+ className: "appointment-services-row-price rb-text-lg",
48323
+ children: row.priceLabel
48324
+ }) : null,
48325
+ /* @__PURE__ */ jsx(ThemedButtonLink, {
48326
+ variant: "secondary",
48327
+ size: "sm",
48328
+ extraClassName: "appointment-services-row-action",
48329
+ href: row.href,
48330
+ label: "Check times",
48331
+ screenReaderSuffix: ` for ${row.title}`
48332
+ })
48333
+ ]
48334
+ }, row.id))
48335
+ });
48336
+ }
48337
+ function AppointmentServiceGroups({ groups, hasSectionHeading, idPrefix }) {
48338
+ const GroupHeading = hasSectionHeading ? "h3" : "h2";
48339
+ const RowTitle = hasSectionHeading ? "h4" : "h3";
48340
+ return /* @__PURE__ */ jsx("div", {
48341
+ className: "appointment-services-groups rb-flex rb-flex-col rb-gap-10",
48342
+ children: groups.map((group) => {
48343
+ const headingId = `${idPrefix}-group-${group.key}`;
48344
+ return /* @__PURE__ */ jsxs("section", {
48345
+ className: "appointment-services-group rb-flex rb-flex-col rb-gap-2",
48346
+ "aria-labelledby": headingId,
48347
+ "data-appointment-services-group": group.key,
48348
+ children: [/* @__PURE__ */ jsxs("div", {
48349
+ className: "appointment-services-group-heading",
48350
+ children: [/* @__PURE__ */ jsx(GroupHeading, {
48351
+ id: headingId,
48352
+ className: "appointment-services-group-title rb-text-sm rb-font-semibold rb-uppercase rb-tracking-wide",
48353
+ children: group.heading
48354
+ }), group.intro ? /* @__PURE__ */ jsx("p", {
48355
+ className: "appointment-services-group-intro rb-text-sm rb-leading-6 rb-max-w-prose status-muted",
48356
+ children: group.intro
48357
+ }) : null]
48358
+ }), /* @__PURE__ */ jsx(AppointmentServiceRows, {
48359
+ rows: group.rows,
48360
+ RowTitle
48361
+ })]
48362
+ }, group.key);
48363
+ })
48364
+ });
47601
48365
  }
47602
- function AppointmentServicesNode({ sectionHeading, services, pathname, servicePathPrefix, className }) {
48366
+ function AppointmentServicesNode({ sectionHeading, categoryId, services, pathname, servicePathPrefix, blockId, className }) {
47603
48367
  const view = buildAppointmentServicesView({
47604
48368
  services,
48369
+ categoryId,
47605
48370
  pathname: typeof pathname === "string" ? pathname : void 0,
47606
48371
  servicePathPrefix: typeof servicePathPrefix === "string" ? servicePathPrefix : null
47607
48372
  });
47608
48373
  const rootClassName = `appointment-services ${className ?? ""}`.trim();
47609
- const RowTitle = sectionHeading ? "h3" : "h2";
47610
- if (view.kind === "error" || view.kind === "empty") return /* @__PURE__ */ jsx("div", {
47611
- className: rootClassName,
47612
- "data-appointment-services-state": view.kind,
47613
- children: /* @__PURE__ */ jsx("p", {
47614
- className: "rb-caption status-muted",
47615
- role: "status",
47616
- children: view.message
47617
- })
47618
- });
47619
- return /* @__PURE__ */ jsx("div", {
47620
- className: `${rootClassName} rb-flex rb-flex-col`,
47621
- "data-appointment-services-state": "populated",
47622
- children: /* @__PURE__ */ jsx("div", {
47623
- className: "appointment-services-rows rb-flex rb-flex-col",
47624
- children: view.rows.map((row) => /* @__PURE__ */ jsxs("article", {
47625
- className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
47626
- children: [
47627
- /* @__PURE__ */ jsxs("div", {
47628
- className: "appointment-services-row-main rb-min-w-0",
47629
- children: [
47630
- /* @__PURE__ */ jsx(RowTitle, {
47631
- className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
47632
- children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
47633
- href: row.detailsHref,
47634
- children: row.title
47635
- }) : row.title
47636
- }),
47637
- row.description ? /* @__PURE__ */ jsx("p", {
47638
- className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
47639
- children: row.description
47640
- }) : null,
47641
- /* @__PURE__ */ jsxs("p", {
47642
- className: "appointment-services-row-meta rb-caption status-muted",
47643
- children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
47644
- })
47645
- ]
47646
- }),
47647
- row.priceLabel ? /* @__PURE__ */ jsx("span", {
47648
- className: "appointment-services-row-price rb-text-lg",
47649
- children: row.priceLabel
47650
- }) : null,
47651
- /* @__PURE__ */ jsx(ThemedButtonLink, {
47652
- variant: "secondary",
47653
- size: "sm",
47654
- extraClassName: "appointment-services-row-action",
47655
- href: row.href,
47656
- label: "Check times",
47657
- screenReaderSuffix: ` for ${row.title}`
47658
- })
47659
- ]
47660
- }, row.id))
47661
- })
47662
- });
48374
+ const hasSectionHeading = Boolean(sectionHeading);
48375
+ switch (view.kind) {
48376
+ case "error":
48377
+ case "empty": return /* @__PURE__ */ jsx("div", {
48378
+ className: rootClassName,
48379
+ "data-appointment-services-state": view.kind,
48380
+ children: /* @__PURE__ */ jsx("p", {
48381
+ className: "rb-caption status-muted",
48382
+ role: "status",
48383
+ children: view.message
48384
+ })
48385
+ });
48386
+ case "items": return /* @__PURE__ */ jsx("div", {
48387
+ className: `${rootClassName} rb-flex rb-flex-col`,
48388
+ "data-appointment-services-state": "populated",
48389
+ children: /* @__PURE__ */ jsx(AppointmentServiceRows, {
48390
+ rows: view.rows,
48391
+ RowTitle: hasSectionHeading ? "h3" : "h2"
48392
+ })
48393
+ });
48394
+ case "groups": return /* @__PURE__ */ jsx("div", {
48395
+ className: `${rootClassName} rb-flex rb-flex-col`,
48396
+ "data-appointment-services-state": "populated",
48397
+ children: /* @__PURE__ */ jsx(AppointmentServiceGroups, {
48398
+ groups: view.groups,
48399
+ hasSectionHeading,
48400
+ idPrefix: `appointment-services-${blockId ?? "block"}`
48401
+ })
48402
+ });
48403
+ default: return assertNever$20(view);
48404
+ }
47663
48405
  }
47664
48406
  //#endregion
47665
48407
  //#region ../blocks/src/system/runtime/nodes/booking-form.server.tsx
@@ -57680,9 +58422,127 @@ function generateFooterBottomTextLinkStyles(footer, themeId, tokens) {
57680
58422
  return cssChunks.join("\n");
57681
58423
  }
57682
58424
  //#endregion
58425
+ //#region ../theme-core/src/layout/boundaryCss.ts
58426
+ init_sectionTransitions();
58427
+ init_transitionCatalogue();
58428
+ /**
58429
+ * Section-break CSS, generated from the transition catalogue (issue 1422).
58430
+ *
58431
+ * The renderer emits one element per shown join:
58432
+ *
58433
+ * <div data-block-boundary data-boundary-treatment="wave-edge-gentle"
58434
+ * data-boundary-placement="previous"
58435
+ * style="--boundary-shape-paint: …; --boundary-band-paint: …">
58436
+ *
58437
+ * Every selector matches an exact attribute value, so there is no reliance on
58438
+ * prefix ordering in the cascade and nothing reads colours out of a string.
58439
+ * The shape layer is `::after`; the band layer (paint → paint only) is
58440
+ * `::before`.
58441
+ */
58442
+ const waveMaskSvgPaths = {
58443
+ classic: "M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z",
58444
+ balanced: "M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z",
58445
+ gentle: "M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z",
58446
+ bold: "M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z",
58447
+ drift: "M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z"
58448
+ };
58449
+ function waveMaskDataUrl(mask) {
58450
+ return svgDataUrl(`<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='${waveMaskSvgPaths[mask]}' fill='white'/></svg>`);
58451
+ }
58452
+ /**
58453
+ * How each placement anchors the layers against the zero-height boundary
58454
+ * element that sits exactly on the join.
58455
+ *
58456
+ * - `centred` straddles the join, band under shape.
58457
+ * - `previous` sits above the join, inside the previous section.
58458
+ * - `next` sits below the join, inside the next section, with the shape
58459
+ * flipped so it reads as the mirror of `previous`.
58460
+ */
58461
+ const placementRules = {
58462
+ centred: {
58463
+ shape: ["top: 0;", "transform: translateY(-50%);"],
58464
+ band: ["top: 0;", "transform: translateY(-50%);"]
58465
+ },
58466
+ previous: {
58467
+ shape: ["bottom: 0;"],
58468
+ band: null
58469
+ },
58470
+ next: {
58471
+ shape: ["top: 0;", "transform: scaleY(-1);"],
58472
+ band: null
58473
+ }
58474
+ };
58475
+ function generateBoundaryCss(selector) {
58476
+ const boundary = `${selector} [data-block-boundary]`;
58477
+ return [
58478
+ rule$1(boundary, [
58479
+ "position: relative;",
58480
+ "z-index: 3;",
58481
+ "height: 0;",
58482
+ "pointer-events: none;"
58483
+ ]),
58484
+ 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));"]),
58485
+ 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));"]),
58486
+ rule$1(`${boundary}::before,\n${boundary}::after`, [
58487
+ "content: '';",
58488
+ "position: absolute;",
58489
+ "left: 0;",
58490
+ "right: 0;",
58491
+ "height: var(--boundary-height);",
58492
+ "pointer-events: none;"
58493
+ ]),
58494
+ rule$1(`${boundary}::before`, ["z-index: 0;", "background: var(--boundary-band-paint, transparent);"]),
58495
+ rule$1(`${boundary}::after`, ["z-index: 1;"]),
58496
+ ...placementCss(boundary),
58497
+ ...transitionCatalogueEntries.flatMap((entry) => treatmentCss(boundary, entry))
58498
+ ].join("\n\n");
58499
+ }
58500
+ function placementCss(boundary) {
58501
+ return Object.keys(placementRules).flatMap((placement) => {
58502
+ const placed = `${boundary}[data-boundary-placement='${placement}']`;
58503
+ const rules = placementRules[placement];
58504
+ return [rule$1(`${placed}::after`, rules.shape), rules.band ? rule$1(`${placed}::before`, rules.band) : rule$1(`${placed}::before`, ["content: none;"])];
58505
+ });
58506
+ }
58507
+ function treatmentCss(boundary, entry) {
58508
+ const selected = `${boundary}[data-boundary-treatment='${entry.intent}']`;
58509
+ return [rule$1(selected, [`--boundary-height: ${boundaryRuntimeMetricsForDecorativeTransition(entry.intent).heightCss};`]), rule$1(`${selected}::after`, shapeDeclarations(entry))];
58510
+ }
58511
+ function shapeDeclarations(entry) {
58512
+ switch (entry.shape.kind) {
58513
+ case "fade": return [
58514
+ "/* Fallback for browsers without color-mix(). */",
58515
+ "background: linear-gradient(to bottom, transparent 0%, var(--boundary-shape-paint) 100%);",
58516
+ "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%);"
58517
+ ];
58518
+ case "wave": {
58519
+ const mask = waveMaskDataUrl(entry.shape.mask);
58520
+ return [
58521
+ "background: var(--boundary-shape-paint);",
58522
+ `mask-image: url("${mask}");`,
58523
+ "mask-repeat: no-repeat;",
58524
+ "mask-size: 100% 100%;",
58525
+ `-webkit-mask-image: url("${mask}");`,
58526
+ "-webkit-mask-repeat: no-repeat;",
58527
+ "-webkit-mask-size: 100% 100%;"
58528
+ ];
58529
+ }
58530
+ case "angle": return [
58531
+ "height: calc(var(--boundary-height) + 2px);",
58532
+ "background: var(--boundary-shape-paint);",
58533
+ `clip-path: ${anglePolygonCss(entry.shape.polygon)};`
58534
+ ];
58535
+ }
58536
+ }
58537
+ function rule$1(selector, declarations) {
58538
+ return `${selector} {\n ${declarations.join("\n ")}\n}`;
58539
+ }
58540
+ function svgDataUrl(svg) {
58541
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
58542
+ }
58543
+ //#endregion
57683
58544
  //#region ../theme-core/src/layout/generateLayoutCss.ts
57684
58545
  init_breakpoints();
57685
- init_sectionTransitions();
57686
58546
  /**
57687
58547
  * Generate layout CSS for theme scope (background, text color)
57688
58548
  *
@@ -57706,7 +58566,7 @@ function generateLayoutCss(options) {
57706
58566
  if (heroTypographyCss) cssChunks.push(heroTypographyCss);
57707
58567
  const bodyTextTypographyCss = generateBodyTextTypographyCss(themeId, theme);
57708
58568
  if (bodyTextTypographyCss) cssChunks.push(bodyTextTypographyCss);
57709
- cssChunks.push(generateSiteStyleTransitionCss(themeId));
58569
+ cssChunks.push(generateBoundaryCss(selector));
57710
58570
  return cssChunks.join("\n\n");
57711
58571
  }
57712
58572
  function toPaddingValue(preset) {
@@ -57717,193 +58577,6 @@ function toPaddingValue(preset) {
57717
58577
  relaxed: "1.5rem"
57718
58578
  }[preset];
57719
58579
  }
57720
- 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>");
57721
- 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>");
57722
- 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>");
57723
- 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>");
57724
- 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>");
57725
- function generateSiteStyleTransitionCss(themeId) {
57726
- const selector = `:where([data-theme-scope="${themeId}"])`;
57727
- const compactBoundaryHeight = requiredBoundaryHeightCss("wave-edge-gentle");
57728
- const mediumBoundaryHeight = requiredBoundaryHeightCss("wave-edge");
57729
- const largeBoundaryHeight = requiredBoundaryHeightCss("wave-edge-bold");
57730
- return `${selector} [data-style-group^='site-style-transition-'] {
57731
- position: relative;
57732
- z-index: 2;
57733
- --site-style-transition-from: transparent;
57734
- --site-style-transition-to: rgb(var(--tb-background));
57735
- }
57736
-
57737
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-'] {
57738
- position: relative;
57739
- z-index: 3;
57740
- height: 0;
57741
- pointer-events: none;
57742
- --site-style-boundary-height: ${mediumBoundaryHeight};
57743
- }
57744
-
57745
- ${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section']) {
57746
- padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));
57747
- }
57748
-
57749
- ${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section']) {
57750
- padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));
57751
- }
57752
-
57753
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::before,
57754
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::before,
57755
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::before {
57756
- content: '';
57757
- position: absolute;
57758
- left: 0;
57759
- right: 0;
57760
- top: 0;
57761
- transform: translateY(-50%);
57762
- z-index: 0;
57763
- height: var(--site-style-boundary-height);
57764
- background: var(--site-style-transition-from);
57765
- pointer-events: none;
57766
- }
57767
-
57768
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after,
57769
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after,
57770
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
57771
- content: '';
57772
- position: absolute;
57773
- left: 0;
57774
- right: 0;
57775
- top: 0;
57776
- transform: translateY(-50%);
57777
- z-index: 1;
57778
- height: var(--site-style-boundary-height);
57779
- pointer-events: none;
57780
- }
57781
-
57782
- ${selector} [data-style-group*='-from-base-'] {
57783
- --site-style-transition-from: rgb(var(--tb-background));
57784
- }
57785
-
57786
- ${selector} [data-style-group*='-from-muted-band-'] {
57787
- --site-style-transition-from: rgb(var(--tb-muted));
57788
- }
57789
-
57790
- ${selector} [data-style-group*='-from-accent-band-'] {
57791
- --site-style-transition-from: rgb(var(--tb-accent));
57792
- }
57793
-
57794
- ${selector} [data-style-group*='-from-brand-depth-band-'] {
57795
- --site-style-transition-from: rgb(var(--tb-primary-800));
57796
- }
57797
-
57798
- ${selector} [data-style-group$='-to-base'],
57799
- ${selector} [data-style-group$='-to-hero-image'] {
57800
- --site-style-transition-to: rgb(var(--tb-background));
57801
- }
57802
-
57803
- ${selector} [data-style-group$='-to-muted-band'] {
57804
- --site-style-transition-to: rgb(var(--tb-muted));
57805
- }
57806
-
57807
- ${selector} [data-style-group$='-to-accent-band'] {
57808
- --site-style-transition-to: rgb(var(--tb-accent));
57809
- }
57810
-
57811
- ${selector} [data-style-group$='-to-brand-depth-band'] {
57812
- --site-style-transition-to: rgb(var(--tb-primary-800));
57813
- }
57814
-
57815
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
57816
- /* Fallback for browsers without color-mix(). */
57817
- background: linear-gradient(
57818
- to bottom,
57819
- transparent 0%,
57820
- var(--site-style-transition-to) 100%
57821
- );
57822
- background: linear-gradient(
57823
- to bottom,
57824
- transparent 0%,
57825
- color-mix(in srgb, var(--site-style-transition-to) 16%, transparent) 34%,
57826
- color-mix(in srgb, var(--site-style-transition-to) 68%, transparent) 78%,
57827
- var(--site-style-transition-to) 100%
57828
- );
57829
- }
57830
-
57831
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-long-'] {
57832
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-long")};
57833
- }
57834
-
57835
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-expansive-'] {
57836
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-expansive")};
57837
- }
57838
-
57839
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after {
57840
- height: calc(var(--site-style-boundary-height) + 2px);
57841
- background: var(--site-style-transition-to);
57842
- clip-path: polygon(0 42%, 100% 8%, 100% 100%, 0 100%);
57843
- }
57844
-
57845
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-'] {
57846
- --site-style-boundary-height: ${compactBoundaryHeight};
57847
- }
57848
-
57849
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-']::after {
57850
- clip-path: polygon(0 34%, 100% 18%, 100% 100%, 0 100%);
57851
- }
57852
-
57853
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-'] {
57854
- --site-style-boundary-height: ${largeBoundaryHeight};
57855
- }
57856
-
57857
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-']::after {
57858
- clip-path: polygon(0 54%, 100% 0%, 100% 100%, 0 100%);
57859
- }
57860
-
57861
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after {
57862
- background: var(--site-style-transition-to);
57863
- mask-image: url("${classicWaveTransitionMask}");
57864
- mask-repeat: no-repeat;
57865
- mask-size: 100% 100%;
57866
- -webkit-mask-image: url("${classicWaveTransitionMask}");
57867
- -webkit-mask-repeat: no-repeat;
57868
- -webkit-mask-size: 100% 100%;
57869
- }
57870
-
57871
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-balanced-']::after {
57872
- mask-image: url("${balancedWaveTransitionMask}");
57873
- -webkit-mask-image: url("${balancedWaveTransitionMask}");
57874
- }
57875
-
57876
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-'] {
57877
- --site-style-boundary-height: ${compactBoundaryHeight};
57878
- }
57879
-
57880
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-']::after {
57881
- mask-image: url("${gentleWaveTransitionMask}");
57882
- -webkit-mask-image: url("${gentleWaveTransitionMask}");
57883
- }
57884
-
57885
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-'] {
57886
- --site-style-boundary-height: ${largeBoundaryHeight};
57887
- }
57888
-
57889
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-']::after {
57890
- mask-image: url("${boldWaveTransitionMask}");
57891
- -webkit-mask-image: url("${boldWaveTransitionMask}");
57892
- }
57893
-
57894
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-drift-']::after {
57895
- mask-image: url("${driftWaveTransitionMask}");
57896
- -webkit-mask-image: url("${driftWaveTransitionMask}");
57897
- }`;
57898
- }
57899
- function requiredBoundaryHeightCss(transition) {
57900
- const metrics = boundaryRuntimeMetricsForTransition(transition);
57901
- if (!metrics) throw new Error(`Missing boundary runtime metrics for ${transition}`);
57902
- return metrics.heightCss;
57903
- }
57904
- function svgDataUrl(svg) {
57905
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
57906
- }
57907
58580
  function generateHeroTypographyCss(themeId, theme) {
57908
58581
  const typography = theme.hero?.typography;
57909
58582
  if (!typography) return "";
@@ -57922,24 +58595,23 @@ function generateHeroTypographyCss(themeId, theme) {
57922
58595
  if (microHeadlineRules.length > 0) chunks.push(`${microHeadlineSelector} {\n ${microHeadlineRules.join("\n ")}\n}`);
57923
58596
  const microSubheadlineRules = buildTypographyRules("subheadline", typography.microSubheadlineSize, typography.microSubheadlineLineHeight);
57924
58597
  if (microSubheadlineRules.length > 0) chunks.push(`${microSubheadlineSelector} {\n ${microSubheadlineRules.join("\n ")}\n}`);
57925
- if (typography.responsive?.tablet) {
57926
- const tabletHeadlineRules = buildTypographyRules("headline", typography.responsive.tablet.headlineSize, typography.responsive.tablet.headlineLineHeight);
57927
- const tabletSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.tablet.subheadlineSize, typography.responsive.tablet.subheadlineLineHeight);
57928
- const tabletChunks = [];
57929
- if (tabletHeadlineRules.length > 0) tabletChunks.push(`${headlineSelector} {\n ${tabletHeadlineRules.join("\n ")}\n }`);
57930
- if (tabletSubheadlineRules.length > 0) tabletChunks.push(`${subheadlineSelector} {\n ${tabletSubheadlineRules.join("\n ")}\n }`);
57931
- if (tabletChunks.length > 0) chunks.push(`@media ${MQ_TABLET_ONLY} {\n ${tabletChunks.join("\n\n ")}\n}`);
57932
- }
57933
- if (typography.responsive?.mobile) {
57934
- const mobileHeadlineRules = buildTypographyRules("headline", typography.responsive.mobile.headlineSize, typography.responsive.mobile.headlineLineHeight);
57935
- const mobileSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.mobile.subheadlineSize, typography.responsive.mobile.subheadlineLineHeight);
57936
- const mobileChunks = [];
57937
- if (mobileHeadlineRules.length > 0) mobileChunks.push(`${headlineSelector} {\n ${mobileHeadlineRules.join("\n ")}\n }`);
57938
- if (mobileSubheadlineRules.length > 0) mobileChunks.push(`${subheadlineSelector} {\n ${mobileSubheadlineRules.join("\n ")}\n }`);
57939
- if (mobileChunks.length > 0) chunks.push(`@media ${MQ_SM_DOWN} {\n ${mobileChunks.join("\n\n ")}\n}`);
57940
- }
58598
+ const selectors = {
58599
+ headline: headlineSelector,
58600
+ subheadline: subheadlineSelector
58601
+ };
58602
+ const responsiveChunks = [responsiveHeroTypographyCss(MQ_TABLET_ONLY, selectors, typography.responsive?.tablet), responsiveHeroTypographyCss(MQ_SM_DOWN, selectors, typography.responsive?.mobile)];
58603
+ for (const chunk of responsiveChunks) if (chunk) chunks.push(chunk);
57941
58604
  return chunks.join("\n\n");
57942
58605
  }
58606
+ function responsiveHeroTypographyCss(media, selectors, overrides) {
58607
+ if (!overrides) return null;
58608
+ const headlineRules = buildTypographyRules("headline", overrides.headlineSize, overrides.headlineLineHeight);
58609
+ const subheadlineRules = buildTypographyRules("subheadline", overrides.subheadlineSize, overrides.subheadlineLineHeight);
58610
+ const mediaChunks = [];
58611
+ if (headlineRules.length > 0) mediaChunks.push(`${selectors.headline} {\n ${headlineRules.join("\n ")}\n }`);
58612
+ if (subheadlineRules.length > 0) mediaChunks.push(`${selectors.subheadline} {\n ${subheadlineRules.join("\n ")}\n }`);
58613
+ return mediaChunks.length > 0 ? `@media ${media} {\n ${mediaChunks.join("\n\n ")}\n}` : null;
58614
+ }
57943
58615
  function buildTypographyRules(role, size, lineHeight) {
57944
58616
  const rules = [];
57945
58617
  if (size) rules.push(`font-size: ${toHeroSize(role, size)};`);
@@ -59931,72 +60603,173 @@ function requireThemeResources(resources, blockKind) {
59931
60603
  return resources;
59932
60604
  }
59933
60605
  //#endregion
59934
- //#region ../blocks/src/pageRendererBoundaries.tsx
59935
- init_typeGuards();
59936
- const boundaryKey = (previousBlockId, nextBlockId) => `${previousBlockId}\u0000${nextBlockId}`;
59937
- function isSiteStyleTransitionStyleGroup(value) {
59938
- return value?.startsWith("site-style-transition-") === true;
60606
+ //#region ../blocks/src/pageRenderJoins.ts
60607
+ init_site_styles();
60608
+ /**
60609
+ * Re-points joins across omitted blocks. Pure over joins.
60610
+ *
60611
+ * Joins are keyed by adjacent block-id pairs, so dropping B from [A, B, C]
60612
+ * removes the A→C join as well as B's own, because no A→C join was ever
60613
+ * resolved. For each surviving pair the original join is kept when the pair
60614
+ * was already adjacent. Across a gap, the treatment comes from the join
60615
+ * entering the omitted run (A→first omitted), falling back to the one leaving
60616
+ * it (last omitted→C). Nothing visual is carried: the survivors are composed
60617
+ * from their own content afterwards.
60618
+ *
60619
+ * `survivors` are compared by identity against `originalBlocks`.
60620
+ */
60621
+ function retargetJoinsAcrossOmissions(input) {
60622
+ const { joins, originalBlocks, survivingBlocks } = input;
60623
+ if (joins.length === 0) return joins;
60624
+ if (originalBlocks.every((block) => survivingBlocks.has(block))) return joins;
60625
+ const byPair = joinsByPair(joins);
60626
+ const retargeted = [];
60627
+ let previousSurvivor = null;
60628
+ let run = [];
60629
+ for (const block of originalBlocks) {
60630
+ if (!survivingBlocks.has(block)) {
60631
+ run.push(block);
60632
+ continue;
60633
+ }
60634
+ if (previousSurvivor?.id && block.id) {
60635
+ const join = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : joinAcrossRun(byPair, previousSurvivor.id, block.id, run);
60636
+ if (join) retargeted.push(join);
60637
+ }
60638
+ previousSurvivor = block;
60639
+ run = [];
60640
+ }
60641
+ return retargeted;
59939
60642
  }
59940
- function boundaryMapForAdjacentBlockIds(boundaries) {
59941
- return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
60643
+ function joinAcrossRun(byPair, previousBlockId, nextBlockId, run) {
60644
+ const firstOmittedId = run[0]?.id;
60645
+ const lastOmittedId = run[run.length - 1]?.id;
60646
+ const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
60647
+ const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
60648
+ const donor = entering ?? exiting;
60649
+ return donor ? {
60650
+ previousBlockId,
60651
+ nextBlockId,
60652
+ treatment: donor.treatment
60653
+ } : void 0;
59942
60654
  }
59943
- function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
59944
- const previousBlockId = previousBlock?.id ?? null;
59945
- const nextBlockId = nextBlock?.id ?? null;
59946
- if (!previousBlockId || !nextBlockId) return null;
59947
- return boundaries.get(boundaryKey(previousBlockId, nextBlockId)) ?? null;
60655
+ /**
60656
+ * The single composition site. For every adjacent pair of survivors with ids,
60657
+ * finds its join and composes it from both survivors' final content: what
60658
+ * each paints with and the frame each draws.
60659
+ */
60660
+ function composePageJoins(input) {
60661
+ const byPair = joinsByPair(input.joins);
60662
+ return input.survivors.flatMap((previous, index) => {
60663
+ const next = input.survivors[index + 1];
60664
+ if (!next || !previous.id || !next.id) return [];
60665
+ const join = byPair.get(pairKey(previous.id, next.id));
60666
+ return [{
60667
+ previousBlockId: previous.id,
60668
+ nextBlockId: next.id,
60669
+ join: join ? composeJoin(join, previous, next) : null
60670
+ }];
60671
+ });
59948
60672
  }
59949
- function renderBoundaryAfterBlock(input) {
59950
- const previousBlockId = input.block.id;
59951
- const nextBlockId = input.nextBlock?.id ?? null;
60673
+ function composeJoin(join, previous, next) {
60674
+ return {
60675
+ boundaryId: pairKey(join.previousBlockId, join.nextBlockId),
60676
+ previousBlockId: join.previousBlockId,
60677
+ nextBlockId: join.nextBlockId,
60678
+ treatment: join.treatment,
60679
+ outcome: composeBoundary(sectionFactsForContent(previous.content), sectionFactsForContent(next.content))
60680
+ };
60681
+ }
60682
+ /** Placement, reserves and paint for a join that draws; null otherwise. */
60683
+ function renderedJoinLayout(join) {
60684
+ if (!join || join.outcome.kind !== "shown") return null;
60685
+ return boundaryLayout(join.treatment, join.outcome.composition);
60686
+ }
60687
+ /** Joins with a missing id on either side cannot sit between two blocks. */
60688
+ function joinsByPair(joins) {
60689
+ return new Map(joins.flatMap((join) => join.previousBlockId && join.nextBlockId ? [[pairKey(join.previousBlockId, join.nextBlockId), join]] : []));
60690
+ }
60691
+ /** Slot lookup key for an adjacent pair, in the one boundary-id scheme. */
60692
+ function joinSlotKey(previousBlockId, nextBlockId) {
60693
+ return pairKey(previousBlockId, nextBlockId);
60694
+ }
60695
+ function pairKey(previousBlockId, nextBlockId) {
60696
+ return makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId));
60697
+ }
60698
+ //#endregion
60699
+ //#region ../blocks/src/pageRendererBoundaries.tsx
60700
+ function joinSlotsByPair(slots) {
60701
+ return new Map(slots.map((slot) => [joinSlotKey(slot.previousBlockId, slot.nextBlockId), slot]));
60702
+ }
60703
+ function joinSlotBetween(previousBlockId, nextBlockId, slots) {
59952
60704
  if (!previousBlockId || !nextBlockId) return null;
59953
- const boundary = input.boundaryByAdjacentBlockIds.get(boundaryKey(previousBlockId, nextBlockId));
59954
- if (!boundary) return null;
59955
- const rendered = /* @__PURE__ */ jsx("div", {
60705
+ return slots.get(joinSlotKey(previousBlockId, nextBlockId)) ?? null;
60706
+ }
60707
+ /**
60708
+ * The markup at one join: the painted boundary element when the join draws,
60709
+ * then whatever `renderJoin` adds. Null when there is nothing to render.
60710
+ */
60711
+ function renderJoinSlot(input) {
60712
+ const { slot } = input;
60713
+ if (!slot) return null;
60714
+ const layout = renderedJoinLayout(slot.join);
60715
+ const painted = slot.join && layout ? /* @__PURE__ */ jsx("div", {
59956
60716
  "aria-hidden": "true",
59957
60717
  "data-block-boundary": "",
59958
- "data-boundary-id": boundary.boundaryId,
59959
- "data-boundary-previous-block-id": boundary.previousBlockId,
59960
- "data-boundary-next-block-id": boundary.nextBlockId,
59961
- "data-style-group": boundary.styleGroup,
59962
- style: boundaryCssVarsStyle(boundary)
59963
- }, `${input.blockKey}:boundary`);
59964
- return input.wrapBoundary ? input.wrapBoundary(boundary, rendered) : rendered;
60718
+ "data-boundary-id": slot.join.boundaryId,
60719
+ "data-boundary-previous-block-id": slot.previousBlockId,
60720
+ "data-boundary-next-block-id": slot.nextBlockId,
60721
+ "data-boundary-treatment": slot.join.treatment,
60722
+ "data-boundary-placement": layout.placement,
60723
+ style: boundaryPaintStyle(layout.paint)
60724
+ }, `${input.blockKey}:boundary`) : null;
60725
+ const extra = input.renderJoin?.({
60726
+ previousBlockId: slot.previousBlockId,
60727
+ nextBlockId: slot.nextBlockId,
60728
+ join: slot.join
60729
+ });
60730
+ if (!painted && (extra === null || extra === void 0)) return null;
60731
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [painted, extra] }, `${input.blockKey}:join`);
59965
60732
  }
59966
- function boundaryCssVarsStyle(boundary) {
59967
- return {
59968
- "--site-style-transition-from": boundary.cssVars.from,
59969
- "--site-style-transition-to": boundary.cssVars.to
59970
- };
60733
+ /**
60734
+ * The one render-edge place a theme token becomes CSS.
60735
+ */
60736
+ function themeTokenCss(token) {
60737
+ return `rgb(var(--tb-${token}))`;
59971
60738
  }
59972
- function boundaryReserveCssVarsStyle(input) {
59973
- const before = input.before?.metrics.reserveCss ?? null;
59974
- const after = input.after?.metrics.reserveCss ?? null;
59975
- if (!before && !after) return void 0;
60739
+ function boundaryPaintStyle(paint) {
59976
60740
  return {
59977
- ...before ? { "--site-style-boundary-before-reserve": before } : {},
59978
- ...after ? { "--site-style-boundary-after-reserve": after } : {}
60741
+ "--boundary-shape-paint": themeTokenCss(paint.shape),
60742
+ ...paint.band ? { "--boundary-band-paint": themeTokenCss(paint.band) } : {}
59979
60743
  };
59980
60744
  }
59981
- function transitionCssVarsStyle(content) {
59982
- const transitionStyles = isObjectRecord$2(content._transitionStyles) ? content._transitionStyles : null;
59983
- const cssVars = isObjectRecord$2(transitionStyles?.cssVars) ? transitionStyles.cssVars : null;
59984
- const from = typeof cssVars?.from === "string" ? cssVars.from : null;
59985
- const to = typeof cssVars?.to === "string" ? cssVars.to : null;
59986
- if (!from && !to) return void 0;
59987
- return {
59988
- ...from ? { "--site-style-transition-from": from } : {},
59989
- ...to ? { "--site-style-transition-to": to } : {}
60745
+ /**
60746
+ * Reserve attributes and variables for a block wrapper, from the joins on
60747
+ * either side. A join's `before` reserve sits inside its previous section, so
60748
+ * it pads the bottom of the block above the join; its `after` reserve pads the
60749
+ * top of the block below. Joins that do not draw reserve nothing.
60750
+ */
60751
+ function blockBoundaryReserves(input) {
60752
+ const top = reserveOnSide(input.before, "after");
60753
+ const bottom = reserveOnSide(input.after, "before");
60754
+ const style = {
60755
+ ...top ? { "--site-style-boundary-before-reserve": top.length } : {},
60756
+ ...bottom ? { "--site-style-boundary-after-reserve": bottom.length } : {}
59990
60757
  };
59991
- }
59992
- function mergeStyleObjects(left, right) {
59993
- if (!left) return right;
59994
- if (!right) return left;
59995
60758
  return {
59996
- ...left,
59997
- ...right
60759
+ topPlacement: top?.placement,
60760
+ bottomPlacement: bottom?.placement,
60761
+ style: top || bottom ? style : void 0
59998
60762
  };
59999
60763
  }
60764
+ /** The reserve one join needs on one side of itself, if it draws at all. */
60765
+ function reserveOnSide(slot, side) {
60766
+ const layout = renderedJoinLayout(slot?.join ?? null);
60767
+ const length = layout?.reserves[side] ?? null;
60768
+ return layout && length ? {
60769
+ length,
60770
+ placement: layout.placement
60771
+ } : null;
60772
+ }
60000
60773
  function pageBlockPosition(index, blockCount) {
60001
60774
  if (blockCount <= 1) return "only";
60002
60775
  if (index === 0) return "first";
@@ -64764,6 +65537,7 @@ init_styledSection();
64764
65537
  init_fragments$1();
64765
65538
  init_boxStyles();
64766
65539
  init_defineBlock();
65540
+ init_schema();
64767
65541
  const appointmentServicesBlockDefinition = {
64768
65542
  manifest: createBlockManifest({
64769
65543
  id: "block.appointment-services",
@@ -64789,6 +65563,15 @@ const appointmentServicesBlockDefinition = {
64789
65563
  required: false,
64790
65564
  ui: { intent: "content" }
64791
65565
  },
65566
+ createSiteOptionSelectField({
65567
+ id: "categoryId",
65568
+ label: "Category",
65569
+ description: "Show one category (with its heading and intro), or leave it on All categories to show everything grouped.",
65570
+ required: false,
65571
+ multiple: false,
65572
+ optionSource: "appointmentServiceCategories",
65573
+ ui: { intent: "content" }
65574
+ }),
64792
65575
  sectionStylesField({
64793
65576
  id: "_sectionStyles",
64794
65577
  label: "Section styles",
@@ -64823,6 +65606,7 @@ const appointmentServicesBlockDefinition = {
64823
65606
  richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
64824
65607
  el("appointment-services", {
64825
65608
  sectionHeading: { $bind: { from: "content.heading" } },
65609
+ categoryId: { $bind: { from: "content.categoryId" } },
64826
65610
  services: { $bind: { from: "data.services" } },
64827
65611
  pathname: { $bind: { from: "$root.pathname" } },
64828
65612
  servicePathPrefix: { $bind: { from: "$root.appointmentServicePathPrefix" } }
@@ -69533,9 +70317,8 @@ function sourceLabel(source) {
69533
70317
  }
69534
70318
  //#endregion
69535
70319
  //#region ../blocks/src/pageRenderPlan.ts
69536
- init_site_styles();
69537
- init_previewCompiler();
69538
70320
  init_typeGuards();
70321
+ init_site_styles();
69539
70322
  /**
69540
70323
  * Decides, once per page render, which blocks can render and what happens to
69541
70324
  * the ones that cannot.
@@ -69578,11 +70361,10 @@ function buildPageRenderPlan(input) {
69578
70361
  return {
69579
70362
  entries,
69580
70363
  omitted,
69581
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
69582
- boundaries: input.runtimeBoundaries,
70364
+ runtimeJoins: retargetJoinsAcrossOmissions({
70365
+ joins: input.runtimeJoins ?? [],
69583
70366
  originalBlocks: input.page.blocks,
69584
- survivingBlocks: new Set(entries.map((entry) => entry.block)),
69585
- previewStage: input.previewStage
70367
+ survivingBlocks: new Set(entries.map((entry) => entry.block))
69586
70368
  })
69587
70369
  };
69588
70370
  }
@@ -69684,86 +70466,15 @@ function getBlockOverride(blockKind, overrides) {
69684
70466
  * For draft/preview: draftContent takes priority over content.
69685
70467
  * For published: content is the source of truth.
69686
70468
  */
70469
+ /**
70470
+ * The content a block renders with. Every render path (public, preview,
70471
+ * templates, SDK) chooses content here, so this is also where a stale
70472
+ * `site-style-transition-*` styleGroup is dropped; authored values survive.
70473
+ */
69687
70474
  function getRenderableContent(block, previewStage = "published") {
69688
70475
  const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
69689
- return isObjectRecord$2(source) ? source : {};
69690
- }
69691
- /**
69692
- * Re-points section boundaries across omitted blocks.
69693
- *
69694
- * Boundaries are keyed by adjacent block-id *pairs*, so dropping B from
69695
- * [A, B, C] does not merely remove B's boundaries — it removes the boundary
69696
- * between the survivors, because no `A→C` key was ever compiled.
69697
- *
69698
- * Per decision D2 the treatment for a merged gap comes from the boundary
69699
- * entering the omitted run (`A→firstOmitted`), falling back to the one leaving
69700
- * it (`lastOmitted→C`). The destination surface is a separate question: only
69701
- * the exiting boundary knows what C looks like, so the merged boundary ends on
69702
- * that endpoint rather than on the omitted block's. In the exiting-only
69703
- * fallback the origin remains an approximation: `from` is the omitted block's
69704
- * surface, because no compiled boundary carries A's. The hosted path no longer
69705
- * needs this at all (site-renderer admits blocks before compiling boundaries);
69706
- * it remains the safety net for an SDK whose compiled boundaries predate an
69707
- * omission.
69708
- */
69709
- function retargetBoundariesAcrossOmissions(input) {
69710
- const { boundaries, originalBlocks, survivingBlocks, previewStage } = input;
69711
- if (boundaries === void 0) return void 0;
69712
- if (boundaries.length === 0) return boundaries;
69713
- if (!originalBlocks.some((block) => !survivingBlocks.has(block))) return boundaries;
69714
- const byPair = new Map(boundaries.map((boundary) => [pairKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
69715
- const retargeted = [];
69716
- let previousSurvivor = null;
69717
- let run = [];
69718
- for (const block of originalBlocks) {
69719
- if (!survivingBlocks.has(block)) {
69720
- run.push(block);
69721
- continue;
69722
- }
69723
- if (previousSurvivor?.id && block.id) {
69724
- const boundary = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : mergedBoundary({
69725
- byPair,
69726
- previousBlock: previousSurvivor,
69727
- previousBlockId: previousSurvivor.id,
69728
- nextBlock: block,
69729
- nextBlockId: block.id,
69730
- run,
69731
- previewStage
69732
- });
69733
- if (boundary) retargeted.push(boundary);
69734
- }
69735
- previousSurvivor = block;
69736
- run = [];
69737
- }
69738
- return retargeted;
69739
- }
69740
- function mergedBoundary(input) {
69741
- const { byPair, previousBlock, previousBlockId, nextBlock, nextBlockId, run, previewStage } = input;
69742
- const firstOmittedId = run[0]?.id;
69743
- const lastOmittedId = run[run.length - 1]?.id;
69744
- const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
69745
- const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
69746
- const donor = entering ?? exiting;
69747
- if (!donor) return void 0;
69748
- const transition = compileTransitionRuntime({
69749
- treatment: donor.treatment,
69750
- from: donor.from,
69751
- to: exiting?.to ?? donor.to
69752
- });
69753
- return retargetCompiledBoundaryRuntimeForContentAdjacency({
69754
- ...donor,
69755
- boundaryId: makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId)),
69756
- previousBlockId,
69757
- nextBlockId,
69758
- from: transition.from,
69759
- to: transition.to,
69760
- cssVars: transition.cssVars
69761
- }, {
69762
- currentContent: getRenderableContent(previousBlock, previewStage),
69763
- nextContent: getRenderableContent(nextBlock, previewStage)
69764
- });
70476
+ return isObjectRecord$2(source) ? stripStaleTransitionStyleGroup(source) : {};
69765
70477
  }
69766
- const pairKey = boundaryKey;
69767
70478
  //#endregion
69768
70479
  //#region ../blocks/src/system/manifest/hydrateLinks.ts
69769
70480
  init_typeGuards();
@@ -69800,7 +70511,7 @@ function hydrateResolvedBookingConsentValue(value, context) {
69800
70511
  const hydratedItems = value.map((item) => hydrateResolvedBookingConsentValue(item, context));
69801
70512
  return hydratedItems.every((item, index) => item === value[index]) ? value : hydratedItems;
69802
70513
  }
69803
- if (!isRecord$26(value)) return value;
70514
+ if (!isRecord$27(value)) return value;
69804
70515
  if (isBookingConsentCarrier(value)) {
69805
70516
  const agreement = hydrateValue(value.agreement, context);
69806
70517
  return agreement === value.agreement ? value : {
@@ -69820,7 +70531,7 @@ function hydrateValue(value, context) {
69820
70531
  const resolvedItems = value.map((item) => hydrateValue(item, context));
69821
70532
  return resolvedItems.every((item, index) => item === value[index]) ? value : resolvedItems;
69822
70533
  }
69823
- if (isRecord$26(value)) {
70534
+ if (isRecord$27(value)) {
69824
70535
  if (isInternalLink$1(value)) return hydrateInternalLink(value, context.routes);
69825
70536
  if (isPageLink$1(value)) return hydratePageLink(value, context);
69826
70537
  if (isEntryLink$1(value)) return hydrateEntryLink(value, context);
@@ -69886,7 +70597,7 @@ function hydrateEntryLink(link, context) {
69886
70597
  init_typeGuards();
69887
70598
  function resolveBlockData(blockId, dataLoaders, dataContext, routes) {
69888
70599
  const raw = blockId ? dataContext?.resolvedData?.[blockId] : void 0;
69889
- if (isRecord$26(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
70600
+ if (isRecord$27(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
69890
70601
  return dataLoaders && typeof dataLoaders === "object" && Object.keys(dataLoaders).length > 0 ? {} : void 0;
69891
70602
  }
69892
70603
  //#endregion
@@ -69921,23 +70632,44 @@ function preparePageRender(input) {
69921
70632
  prepared
69922
70633
  });
69923
70634
  }
69924
- if (constructionFailures.length === 0 || input.audience === "authoring") return {
69925
- entries,
69926
- constructionFailures,
69927
- runtimeBoundaries: input.plan.runtimeBoundaries
69928
- };
69929
- const survivors = entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
70635
+ const survivors = input.audience === "authoring" ? entries : entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
69930
70636
  return {
69931
70637
  entries: survivors,
69932
70638
  constructionFailures,
69933
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
69934
- boundaries: input.plan.runtimeBoundaries,
69935
- originalBlocks: input.plan.entries.map((planned) => planned.block),
69936
- survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block)),
69937
- previewStage: input.previewStage
69938
- })
70639
+ joins: composeSurvivorJoins(input.plan, survivors, input.themeResources)
69939
70640
  };
69940
70641
  }
70642
+ /**
70643
+ * The only place joins are composed. Plan-time omissions were already
70644
+ * re-pointed by the plan; construction failures are re-pointed here by the
70645
+ * same primitive, so `[A, B, C]` losing B still gets an A→C join. Each
70646
+ * survivor is then classified from its final content.
70647
+ */
70648
+ function composeSurvivorJoins(plan, survivors, themeResources) {
70649
+ return composePageJoins({
70650
+ joins: retargetJoinsAcrossOmissions({
70651
+ joins: plan.runtimeJoins,
70652
+ originalBlocks: plan.entries.map((planned) => planned.block),
70653
+ survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block))
70654
+ }),
70655
+ survivors: survivors.map(({ entry }) => ({
70656
+ id: entry.block.id,
70657
+ kind: entry.block.kind,
70658
+ content: boundaryClassificationContent(entry, themeResources)
70659
+ }))
70660
+ });
70661
+ }
70662
+ /**
70663
+ * The content a join is classified from must be the content that paints the
70664
+ * section. The default block renderer layers the theme's per-block style
70665
+ * defaults (`theme.blockOverrides[kind].styles`, background included) under
70666
+ * the block's own `_sectionStyles`, so classify that same projection. An
70667
+ * override component renders its own markup from the unmerged content.
70668
+ */
70669
+ function boundaryClassificationContent(entry, themeResources) {
70670
+ if (entry.kind !== "renderable" || entry.override) return entry.renderableContent;
70671
+ return mergeContentWithBlockStyleOverrides(entry.renderableContent, themeResources.kind === "available" ? themeResources.config : void 0, entry.block.kind);
70672
+ }
69941
70673
  function prepareEntry(entry, input) {
69942
70674
  if (entry.kind !== "renderable") return { kind: "plan-decided" };
69943
70675
  const draftContent = previewableEditorContent(entry);
@@ -70128,7 +70860,7 @@ function PageRenderer$1(props) {
70128
70860
  });
70129
70861
  }
70130
70862
  function PageRendererCore(props) {
70131
- const { page, audience, runtimeBoundaries, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
70863
+ const { page, audience, runtimeJoins, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
70132
70864
  const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
70133
70865
  dataContext?.imageResolveContext,
70134
70866
  dataContext?.mediaAssets,
@@ -70144,7 +70876,7 @@ function PageRendererCore(props) {
70144
70876
  blocks: []
70145
70877
  },
70146
70878
  audience,
70147
- runtimeBoundaries,
70879
+ runtimeJoins,
70148
70880
  previewStage,
70149
70881
  sdkConfig,
70150
70882
  customBlocks,
@@ -70152,7 +70884,7 @@ function PageRendererCore(props) {
70152
70884
  }), [
70153
70885
  page,
70154
70886
  audience,
70155
- runtimeBoundaries,
70887
+ runtimeJoins,
70156
70888
  previewStage,
70157
70889
  sdkConfig,
70158
70890
  customBlocks,
@@ -70169,19 +70901,16 @@ function PageRendererCore(props) {
70169
70901
  dataContext,
70170
70902
  routeMap,
70171
70903
  themeResources,
70172
- imageResolveContext,
70173
- previewStage
70904
+ imageResolveContext
70174
70905
  }), [
70175
70906
  plan,
70176
70907
  audience,
70177
70908
  dataContext,
70178
70909
  routeMap,
70179
70910
  themeResources,
70180
- imageResolveContext,
70181
- previewStage
70911
+ imageResolveContext
70182
70912
  ]);
70183
- const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(preparation.runtimeBoundaries ?? []), [preparation.runtimeBoundaries]);
70184
- const usesRuntimeBoundaries = preparation.runtimeBoundaries !== void 0;
70913
+ const joinSlots = React$1.useMemo(() => joinSlotsByPair(preparation.joins), [preparation.joins]);
70185
70914
  reportAndReplayOmissions({
70186
70915
  plan,
70187
70916
  preparation,
@@ -70199,10 +70928,9 @@ function PageRendererCore(props) {
70199
70928
  themeResources,
70200
70929
  imageResolveContext,
70201
70930
  resolvedRegistry,
70202
- boundaryByAdjacentBlockIds,
70203
- usesRuntimeBoundaries,
70931
+ joinSlots,
70204
70932
  wrapBlock: props.wrapBlock,
70205
- wrapBoundary: props.wrapBoundary
70933
+ renderJoin: props.renderJoin
70206
70934
  })) });
70207
70935
  }
70208
70936
  /**
@@ -70291,27 +71019,25 @@ function requireEntryBlockOverride(entry, themeResources) {
70291
71019
  }
70292
71020
  /**
70293
71021
  * Renders one planned block: its wrapper, style-group data attributes,
70294
- * boundary reserve space and any boundary or transition sibling that follows.
71022
+ * boundary reserve space and the join markup that follows it.
70295
71023
  *
70296
71024
  * Extracted from PageRendererCore so the component stays about wiring the plan
70297
71025
  * rather than also owning per-block markup.
70298
71026
  */
70299
71027
  function renderPlanEntry(input) {
70300
- const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, boundaryByAdjacentBlockIds, usesRuntimeBoundaries, wrapBlock, wrapBoundary } = input;
71028
+ const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, joinSlots, wrapBlock, renderJoin } = input;
70301
71029
  const entry = input.entry.entry;
70302
71030
  const block = entry.block;
70303
71031
  const blockPosition = pageBlockPosition(index, entries.length);
70304
71032
  const blockKey = blockKeyFor(block, index);
70305
71033
  const previousBlock = entries[index - 1]?.entry.block ?? null;
70306
71034
  const nextBlock = entries[index + 1]?.entry.block ?? null;
70307
- const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
70308
- const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
70309
- const boundaryElement = renderBoundaryAfterBlock({
70310
- block,
70311
- nextBlock,
71035
+ const joinBefore = joinSlotBetween(previousBlock?.id, block.id, joinSlots);
71036
+ const joinAfter = joinSlotBetween(block.id, nextBlock?.id, joinSlots);
71037
+ const boundaryElement = renderJoinSlot({
71038
+ slot: joinAfter,
70312
71039
  blockKey,
70313
- boundaryByAdjacentBlockIds,
70314
- wrapBoundary
71040
+ renderJoin
70315
71041
  });
70316
71042
  const cleanKind = block.kind.replace(/^block\./, "");
70317
71043
  const managesOwnWrapper = entry.kind === "renderable" && entry.managesOwnWrapper;
@@ -70334,11 +71060,10 @@ function renderPlanEntry(input) {
70334
71060
  const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
70335
71061
  const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
70336
71062
  const blockComposition = readBlockComposition(renderableContent.blockComposition);
70337
- const transitionStyle = transitionCssVarsStyle(renderableContent);
70338
- const blockStyle = mergeStyleObjects(transitionStyle, boundaryReserveCssVarsStyle({
70339
- before: boundaryBefore,
70340
- after: boundaryAfter
70341
- }));
71063
+ const reserves = blockBoundaryReserves({
71064
+ before: joinBefore,
71065
+ after: joinAfter
71066
+ });
70342
71067
  const blockElement = /* @__PURE__ */ jsx("div", {
70343
71068
  className: wrapperClassName,
70344
71069
  "data-block": cleanKind,
@@ -70350,21 +71075,14 @@ function renderPlanEntry(input) {
70350
71075
  "data-block-composition": blockComposition?.treatment,
70351
71076
  "data-block-composition-kind": blockComposition?.kind,
70352
71077
  "data-page-block-position": blockPosition,
70353
- "data-site-style-boundary-before": boundaryBefore?.metrics.size,
70354
- "data-site-style-boundary-after": boundaryAfter?.metrics.size,
71078
+ "data-site-style-boundary-before": reserves.topPlacement,
71079
+ "data-site-style-boundary-after": reserves.bottomPlacement,
70355
71080
  "data-style-group": styleGroup,
70356
- style: blockStyle,
71081
+ style: reserves.style,
70357
71082
  children: /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps })
70358
71083
  }, blockKey);
70359
71084
  if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
70360
- if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
70361
- return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, /* @__PURE__ */ jsx("div", {
70362
- "aria-hidden": "true",
70363
- "data-block-transition": "",
70364
- "data-block-transition-for": block.id ?? blockKey,
70365
- "data-style-group": styleGroup,
70366
- style: transitionStyle
70367
- })] }, blockKey);
71085
+ return blockElement;
70368
71086
  }
70369
71087
  function resolvePageRendererImageResolveContext(dataContext) {
70370
71088
  const input = dataContext?.imageResolveContext;
@@ -85380,6 +86098,17 @@ function coerceToString(value) {
85380
86098
  return "";
85381
86099
  }
85382
86100
  /**
86101
+ * The appointment-services catalogue (issue 1403) pairs the services with the
86102
+ * site's ordered categories. Both are needed to group them, so that response
86103
+ * is passed through whole; any other `{ services }` wrapper is unwrapped.
86104
+ */
86105
+ function coerceServicesPayload(record) {
86106
+ return Array.isArray(record.services) && Array.isArray(record.categories) ? {
86107
+ services: record.services,
86108
+ categories: record.categories
86109
+ } : record.services;
86110
+ }
86111
+ /**
85383
86112
  * Extract data from API response.
85384
86113
  * Unwraps common response wrappers: { data }, { form }, { entries }, { services }, { teamMembers }
85385
86114
  *
@@ -85394,7 +86123,7 @@ function coercePayload(payload) {
85394
86123
  if ("data" in record && typeof record.data !== "function") return record.data;
85395
86124
  if ("form" in record && typeof record.form !== "function") return record.form;
85396
86125
  if ("entries" in record && typeof record.entries !== "function") return record.entries;
85397
- if ("services" in record && typeof record.services !== "function") return record.services;
86126
+ if ("services" in record && typeof record.services !== "function") return coerceServicesPayload(record);
85398
86127
  if ("teamMembers" in record && typeof record.teamMembers !== "function") return record.teamMembers;
85399
86128
  }
85400
86129
  return payload;
@@ -86829,7 +87558,12 @@ const appointmentSetupServiceDraftSchema = z$1.object({
86829
87558
  venue: appointmentSetupVenueChoiceSchema,
86830
87559
  timezone: z$1.string().trim().min(1).nullable(),
86831
87560
  payment: appointmentSetupPaymentDraftSchema,
86832
- assignment: appointmentSetupServiceAssignmentDraftSchema
87561
+ assignment: appointmentSetupServiceAssignmentDraftSchema,
87562
+ /**
87563
+ * The service's category, or null for "Other" (issue 1403). Omitted leaves
87564
+ * an existing service's category alone and puts a new one in "Other".
87565
+ */
87566
+ categoryId: z$1.string().uuid().nullable().optional()
86833
87567
  }).superRefine((service, ctx) => {
86834
87568
  if (service.payment.kind !== "free" && (service.priceAmount === null || service.priceAmount <= 0)) ctx.addIssue({
86835
87569
  code: "custom",
@@ -87933,7 +88667,7 @@ function usePage(params) {
87933
88667
  loading: false,
87934
88668
  error: null,
87935
88669
  page: pageOutline,
87936
- runtimeBoundaries: pageData.runtimeBoundaries,
88670
+ runtimeJoins: pageData.runtimeJoins,
87937
88671
  theme: requireSdkTheme(site.theme, siteId),
87938
88672
  siteId,
87939
88673
  resolvedData,
@@ -88131,7 +88865,7 @@ function useContent(params) {
88131
88865
  error: null,
88132
88866
  type: "page",
88133
88867
  page: pageOutline,
88134
- runtimeBoundaries: pageData.runtimeBoundaries,
88868
+ runtimeJoins: pageData.runtimeJoins,
88135
88869
  entry: null,
88136
88870
  theme: site.theme,
88137
88871
  siteId,
@@ -151556,6 +152290,7 @@ var init_booking_form_headless_state = __esmMin((() => {
151556
152290
  init_src$2();
151557
152291
  init_dist$1();
151558
152292
  init_booking_form_state();
152293
+ init_booking_form_service_categories();
151559
152294
  init_booking_form_validationErrors();
151560
152295
  init_booking_form_field_values();
151561
152296
  unsafeAsBookingFieldId = (id) => id;
@@ -152120,6 +152855,7 @@ function useHeadlessBookingFlow(options) {
152120
152855
  data,
152121
152856
  services: {
152122
152857
  items: options.services,
152858
+ categories: readServiceCategories(options),
152123
152859
  selected: selectedService,
152124
152860
  defaultServiceId,
152125
152861
  requiresSelection: Boolean(options.requiresServiceSelection)
@@ -152184,6 +152920,9 @@ function useHeadlessBookingFlow(options) {
152184
152920
  actions
152185
152921
  };
152186
152922
  }
152923
+ function readServiceCategories(options) {
152924
+ return options.serviceCategories ?? EMPTY_SERVICE_CATEGORIES;
152925
+ }
152187
152926
  function navigateToCheckout(checkoutUrl) {
152188
152927
  window.location.href = checkoutUrl;
152189
152928
  }
@@ -152197,6 +152936,7 @@ function useLatestRef(value) {
152197
152936
  }, [value]);
152198
152937
  return ref;
152199
152938
  }
152939
+ var EMPTY_SERVICE_CATEGORIES;
152200
152940
  var init_useHeadlessBookingFlow = __esmMin((() => {
152201
152941
  init_client();
152202
152942
  init_src();
@@ -152212,6 +152952,7 @@ var init_useHeadlessBookingFlow = __esmMin((() => {
152212
152952
  init_bookingPaymentOptions();
152213
152953
  init_useHeadlessBookingFlow_state();
152214
152954
  init_booking_form_validationErrors();
152955
+ EMPTY_SERVICE_CATEGORIES = [];
152215
152956
  }));
152216
152957
  //#endregion
152217
152958
  //#region ../blocks/src/system/runtime/hooks/usePolling.state.ts
@@ -152896,10 +153637,102 @@ var init_BookingConsentAgreement = __esmMin((() => {
152896
153637
  init_RichText();
152897
153638
  }));
152898
153639
  //#endregion
152899
- //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
153640
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-choices.tsx
152900
153641
  function optionCardClassName(isSelected) {
152901
153642
  return `rb-option-card${isSelected ? " rb-option-card--selected" : ""}`;
152902
153643
  }
153644
+ function SelectedCheck() {
153645
+ return /* @__PURE__ */ jsx("div", {
153646
+ className: "rb-option-check",
153647
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
153648
+ });
153649
+ }
153650
+ /**
153651
+ * The service buttons. A site with no categories keeps its single grid; a
153652
+ * categorised site gets a heading per category, then "Other", above the same
153653
+ * grid (issue 1403). Empty categories are skipped, and there are no intros
153654
+ * here, to keep the step compact.
153655
+ */
153656
+ function DefaultServiceChoices({ booking }) {
153657
+ const layout = layoutServicesByCategory(booking.services.categories, booking.services.items.map((service) => ({
153658
+ service,
153659
+ categoryId: service.categoryId ?? null
153660
+ })), { keepEmptyGroups: false });
153661
+ switch (layout.kind) {
153662
+ case "uncategorized-site": return /* @__PURE__ */ jsxs("div", {
153663
+ className: "rb-reg-stack rb-reg-stack--sm",
153664
+ children: [/* @__PURE__ */ jsx("span", {
153665
+ className: "ab-subtitle",
153666
+ children: "Service"
153667
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
153668
+ booking,
153669
+ choices: layout.services
153670
+ })]
153671
+ });
153672
+ case "categorized-site": return /* @__PURE__ */ jsx("div", {
153673
+ className: "rb-reg-stack",
153674
+ children: [...layout.groups.map((group) => ({
153675
+ key: group.category.id,
153676
+ heading: group.category.name,
153677
+ choices: group.services
153678
+ })), ...layout.other.length > 0 ? [{
153679
+ key: "other",
153680
+ heading: "Other",
153681
+ choices: layout.other
153682
+ }] : []].map((group) => /* @__PURE__ */ jsxs("div", {
153683
+ className: "rb-reg-stack rb-reg-stack--sm",
153684
+ "data-booking-service-group": group.key,
153685
+ children: [/* @__PURE__ */ jsx("h3", {
153686
+ className: "ab-subtitle",
153687
+ children: group.heading
153688
+ }), /* @__PURE__ */ jsx(ServiceChoiceGrid, {
153689
+ booking,
153690
+ choices: group.choices
153691
+ })]
153692
+ }, group.key))
153693
+ });
153694
+ default: return assertNever$11(layout);
153695
+ }
153696
+ }
153697
+ function ServiceChoiceGrid({ booking, choices }) {
153698
+ const selectedServiceId = booking.services.selected?.id;
153699
+ return /* @__PURE__ */ jsx("div", {
153700
+ className: "rb-reg-grid",
153701
+ children: choices.map(({ service }) => {
153702
+ const isSelected = selectedServiceId === service.id;
153703
+ return /* @__PURE__ */ jsxs("button", {
153704
+ type: "button",
153705
+ onClick: () => booking.actions.selectService(service.id),
153706
+ className: optionCardClassName(isSelected),
153707
+ "aria-pressed": isSelected,
153708
+ children: [/* @__PURE__ */ jsxs("div", {
153709
+ className: "rb-flex-1",
153710
+ children: [
153711
+ /* @__PURE__ */ jsx("div", {
153712
+ className: "ab-subtitle",
153713
+ children: service.title
153714
+ }),
153715
+ service.description ? /* @__PURE__ */ jsx("div", {
153716
+ className: "ab-text rb-on-surface--muted",
153717
+ children: service.description
153718
+ }) : null,
153719
+ service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
153720
+ className: "ab-text rb-on-surface--muted",
153721
+ children: [service.durationMinutes, " minutes"]
153722
+ }) : null
153723
+ ]
153724
+ }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
153725
+ }, service.id);
153726
+ })
153727
+ });
153728
+ }
153729
+ var init_booking_form_service_choices = __esmMin((() => {
153730
+ init_src$2();
153731
+ init_dist$1();
153732
+ init_CheckIcon$1();
153733
+ }));
153734
+ //#endregion
153735
+ //#region ../blocks/src/system/runtime/nodes/booking-form.default-ui.tsx
152903
153736
  function formatBookingAmount(amount, currency) {
152904
153737
  return formatCurrencyAmount(amount, currency, { decimals: "auto" });
152905
153738
  }
@@ -152990,7 +153823,6 @@ function DefaultBookingStepContent({ booking, errorSummaryId }) {
152990
153823
  function DefaultServiceSelectionStep({ booking }) {
152991
153824
  const services = booking.services.items;
152992
153825
  const resources = booking.resources.items;
152993
- const selectedServiceId = booking.services.selected?.id;
152994
153826
  const selectedResourceId = booking.resources.selectedResourceId;
152995
153827
  const showServiceSelect = booking.services.requiresSelection || services.length > 1;
152996
153828
  const showResourceSelect = booking.resources.requiresSelection || resources.length > 1;
@@ -153005,41 +153837,7 @@ function DefaultServiceSelectionStep({ booking }) {
153005
153837
  className: "ab-text rb-on-surface--muted",
153006
153838
  children: "Choose the service you'd like to book"
153007
153839
  })] }),
153008
- showServiceSelect ? /* @__PURE__ */ jsxs("div", {
153009
- className: "rb-reg-stack rb-reg-stack--sm",
153010
- children: [/* @__PURE__ */ jsx("span", {
153011
- className: "ab-subtitle",
153012
- children: "Service"
153013
- }), /* @__PURE__ */ jsx("div", {
153014
- className: "rb-reg-grid",
153015
- children: services.map((service) => {
153016
- const isSelected = selectedServiceId === service.id;
153017
- return /* @__PURE__ */ jsxs("button", {
153018
- type: "button",
153019
- onClick: () => booking.actions.selectService(service.id),
153020
- className: optionCardClassName(isSelected),
153021
- "aria-pressed": isSelected,
153022
- children: [/* @__PURE__ */ jsxs("div", {
153023
- className: "rb-flex-1",
153024
- children: [
153025
- /* @__PURE__ */ jsx("div", {
153026
- className: "ab-subtitle",
153027
- children: service.title
153028
- }),
153029
- service.description ? /* @__PURE__ */ jsx("div", {
153030
- className: "ab-text rb-on-surface--muted",
153031
- children: service.description
153032
- }) : null,
153033
- service.durationMinutes ? /* @__PURE__ */ jsxs("div", {
153034
- className: "ab-text rb-on-surface--muted",
153035
- children: [service.durationMinutes, " minutes"]
153036
- }) : null
153037
- ]
153038
- }), isSelected ? /* @__PURE__ */ jsx(SelectedCheck, {}) : null]
153039
- }, service.id);
153040
- })
153041
- })]
153042
- }) : null,
153840
+ showServiceSelect ? /* @__PURE__ */ jsx(DefaultServiceChoices, { booking }) : null,
153043
153841
  booking.resources.state.tag === "loading" ? /* @__PURE__ */ jsxs("div", {
153044
153842
  className: "ab-center",
153045
153843
  children: [/* @__PURE__ */ jsx(SpinnerNode, {
@@ -153618,12 +154416,6 @@ function DefaultBookingErrorSummary({ errors, id }) {
153618
154416
  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))]
153619
154417
  });
153620
154418
  }
153621
- function SelectedCheck() {
153622
- return /* @__PURE__ */ jsx("div", {
153623
- className: "rb-option-check",
153624
- children: /* @__PURE__ */ jsx(CheckIcon, {})
153625
- });
153626
- }
153627
154419
  var init_booking_form_default_ui = __esmMin((() => {
153628
154420
  init_src$2();
153629
154421
  init_ThemedButton();
@@ -153633,12 +154425,12 @@ var init_booking_form_default_ui = __esmMin((() => {
153633
154425
  init_DatePicker();
153634
154426
  init_TimeSlotSelector();
153635
154427
  init_spinner();
153636
- init_CheckIcon$1();
153637
154428
  init_StepIndicator$1();
153638
154429
  init_FeedbackMessage();
153639
154430
  init_DiscountCodeDisclosure();
153640
154431
  init_FieldValidationMessage();
153641
154432
  init_BookingConsentAgreement();
154433
+ init_booking_form_service_choices();
153642
154434
  }));
153643
154435
  //#endregion
153644
154436
  //#region ../blocks/src/system/runtime/shared/utils/checkoutUrls.ts
@@ -154069,6 +154861,7 @@ var init_booking_form_client = __esmMin((() => {
154069
154861
  formId: render.hydration.formId ?? form?.id ?? "booking-form",
154070
154862
  form,
154071
154863
  services,
154864
+ serviceCategories: render.hydration.serviceCategories,
154072
154865
  initialServiceId,
154073
154866
  initialResourceId,
154074
154867
  initialData: render.hydration.initialData ?? void 0,
@@ -160862,7 +161655,7 @@ var init_event_registration_client = __esmMin((() => {
160862
161655
  * Loads Leaflet CSS/JS from CDN to avoid bundling in the blocks package.
160863
161656
  */
160864
161657
  function isLeafletApi(value) {
160865
- if (!isRecord$26(value)) return false;
161658
+ if (!isRecord$27(value)) return false;
160866
161659
  return typeof value.map === "function" && typeof value.tileLayer === "function" && typeof value.marker === "function";
160867
161660
  }
160868
161661
  function loadLeaflet() {
@@ -165143,6 +165936,16 @@ var init_newsletter_form_client_state = __esmMin((() => {
165143
165936
  }));
165144
165937
  //#endregion
165145
165938
  //#region ../blocks/src/system/runtime/nodes/newsletter-form.submission.ts
165939
+ /**
165940
+ * Logs a refusal with its reason so a QA session can tell the cases apart in
165941
+ * the console, since the visitor-facing copy deliberately does not.
165942
+ */
165943
+ function reportNewsletterSignupRefusal(reason, detail = {}) {
165944
+ console.warn("[newsletter-form] Signup refused before sending", {
165945
+ reason,
165946
+ ...detail
165947
+ });
165948
+ }
165146
165949
  function mapNewsletterFormFieldsToSubscriberInput(fields) {
165147
165950
  return {
165148
165951
  email: readSubmittedText(fields.email),
@@ -165163,7 +165966,7 @@ function resolveNewsletterSubmitTarget(input) {
165163
165966
  case "signed-footer-source": return publishedSourceTarget(input.sourceRef);
165164
165967
  case "preview": return {
165165
165968
  tag: "unavailable",
165166
- reason: "missing_block_source"
165969
+ reason: "preview_only_source"
165167
165970
  };
165168
165971
  default: return assertNever$20(input);
165169
165972
  }
@@ -165225,13 +166028,23 @@ function NewsletterFormClient({ render }) {
165225
166028
  }
165226
166029
  const submitTarget = resolveNewsletterSubmitTarget(render.hydration.target);
165227
166030
  if (submitTarget.tag === "unavailable") {
166031
+ reportNewsletterSignupRefusal(submitTarget.reason, { target: render.hydration.target });
166032
+ dispatchSubmission({
166033
+ type: "failed",
166034
+ error: newsletterFormError(SIGNUPS_PAUSED_MESSAGE)
166035
+ });
166036
+ return;
166037
+ }
166038
+ if (render.hydration.setupRevision === null) {
166039
+ reportNewsletterSignupRefusal("setup_not_configured");
165228
166040
  dispatchSubmission({
165229
166041
  type: "failed",
165230
166042
  error: newsletterFormError(SIGNUPS_PAUSED_MESSAGE)
165231
166043
  });
165232
166044
  return;
165233
166045
  }
165234
- if (render.hydration.setupRevision === null || render.display.availability === "unavailable") {
166046
+ if (render.display.availability === "unavailable") {
166047
+ reportNewsletterSignupRefusal("destination_unavailable", { setupRevision: render.hydration.setupRevision });
165235
166048
  dispatchSubmission({
165236
166049
  type: "failed",
165237
166050
  error: newsletterFormError(SIGNUPS_PAUSED_MESSAGE)
@@ -165241,6 +166054,7 @@ function NewsletterFormClient({ render }) {
165241
166054
  const selection = readVisitorSelection(formData, render.hydration);
165242
166055
  const selectionError = visitorSelectionError(selection);
165243
166056
  if (selectionError) {
166057
+ if (selection.tag === "unusable") reportNewsletterSignupRefusal("choices_unusable", { choiceProvider: render.hydration.choiceProvider });
165244
166058
  dispatchSubmission({
165245
166059
  type: "failed",
165246
166060
  error: selectionError
@@ -165857,7 +166671,7 @@ function toBlocksPageRendererDataContext(dataContext) {
165857
166671
  function toBlocksCommonProps(props) {
165858
166672
  return {
165859
166673
  page: props.page,
165860
- runtimeBoundaries: props.runtimeBoundaries,
166674
+ runtimeJoins: props.runtimeJoins,
165861
166675
  dataContext: props.dataContext ? toBlocksPageRendererDataContext(props.dataContext) : void 0,
165862
166676
  routeMap: props.routeMap,
165863
166677
  wrapBlock: props.wrapBlock,
@@ -165958,10 +166772,10 @@ function buildPageRouteRenderContext(dataContext) {
165958
166772
  };
165959
166773
  }
165960
166774
  function PageContent(props) {
165961
- const { page, runtimeBoundaries, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
166775
+ const { page, runtimeJoins, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
165962
166776
  const rendererCommonProps = {
165963
166777
  page,
165964
- runtimeBoundaries,
166778
+ runtimeJoins,
165965
166779
  dataContext: {
165966
166780
  siteId,
165967
166781
  ...buildPageRouteRenderContext(dataContext),
@@ -166172,7 +166986,7 @@ var SimpleCache = class {
166172
166986
  * 1. This constant
166173
166987
  * 2. The "version" field in package.json
166174
166988
  */
166175
- const SDK_VERSION = "0.144.0";
166989
+ const SDK_VERSION = "0.145.0";
166176
166990
  //#endregion
166177
166991
  //#region src/client/error.ts
166178
166992
  let _Symbol$for;
@@ -167679,7 +168493,7 @@ function createRiverbankClientWithDeps(deps, config) {
167679
168493
  const result = await reader({
167680
168494
  cacheKey: plan.cacheKey,
167681
168495
  endpoint: plan.endpoint,
167682
- payloadSchemaVersion: 1
168496
+ payloadSchemaVersion: 2
167683
168497
  });
167684
168498
  if (result.kind === "miss") return {
167685
168499
  kind: "miss",
@@ -167728,7 +168542,7 @@ function createRiverbankClientWithDeps(deps, config) {
167728
168542
  siteId: plan.siteId ?? getRuntimePayloadSiteId(payload),
167729
168543
  freshTtlSec: plan.freshTtlSec,
167730
168544
  staleTtlSec: plan.staleTtlSec,
167731
- payloadSchemaVersion: 1,
168545
+ payloadSchemaVersion: 2,
167732
168546
  writeReason: "live_success"
167733
168547
  });
167734
168548
  } catch {}