@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
@@ -314,7 +314,7 @@ function parseEventPageSurfaceDestinationMode(value) {
314
314
  return value === "cms-managed" || value === "system-managed" ? value : void 0;
315
315
  }
316
316
  function parseEventPageSurfaceSubject(value) {
317
- if (!isRecord$40(value)) return err$3({ kind: "notObject" });
317
+ if (!isRecord$41(value)) return err$3({ kind: "notObject" });
318
318
  switch (value.kind) {
319
319
  case "eventSeries": {
320
320
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -369,7 +369,7 @@ function parseEventSeriesIdList(values) {
369
369
  }
370
370
  return ok$3(parsed);
371
371
  }
372
- function isRecord$40(value) {
372
+ function isRecord$41(value) {
373
373
  return typeof value === "object" && value !== null && !Array.isArray(value);
374
374
  }
375
375
  //#endregion
@@ -595,6 +595,74 @@ function getLocalScheduleDate(date, timeZone) {
595
595
  };
596
596
  }
597
597
  //#endregion
598
+ //#region ../core/src/appointment-service-categories.ts
599
+ /**
600
+ * Groups services under their categories.
601
+ *
602
+ * A service whose `categoryId` names no category in `categories` lands in
603
+ * Other. The composite foreign key keeps that from happening with stored data,
604
+ * so this is only the total, safe answer for a caller holding a stale list.
605
+ */
606
+ function layoutServicesByCategory(categories, services, options) {
607
+ if (categories.length === 0) return {
608
+ kind: "uncategorized-site",
609
+ services: [...services]
610
+ };
611
+ const buckets = new Map(categories.map((category) => [category.id, []]));
612
+ const other = [];
613
+ for (const service of services) {
614
+ const bucket = service.categoryId === null ? void 0 : buckets.get(service.categoryId);
615
+ if (bucket !== void 0) bucket.push(service);
616
+ else other.push(service);
617
+ }
618
+ const groups = [];
619
+ for (const category of categories) {
620
+ const groupServices = buckets.get(category.id) ?? [];
621
+ if (groupServices.length === 0 && !options.keepEmptyGroups) continue;
622
+ groups.push({
623
+ category,
624
+ services: groupServices
625
+ });
626
+ }
627
+ if (groups.length === 0 && !options.keepEmptyGroups) return {
628
+ kind: "uncategorized-site",
629
+ services: other
630
+ };
631
+ return {
632
+ kind: "categorized-site",
633
+ groups,
634
+ other
635
+ };
636
+ }
637
+ /**
638
+ * Selects the public view for an optional category filter.
639
+ *
640
+ * It takes the category and service lists rather than a prebuilt layout
641
+ * because the public layout has already dropped empty categories, and then an
642
+ * empty category (`single` with no services) could not be told apart from a
643
+ * deleted one (`fallback-all`).
644
+ */
645
+ function selectCategoryView(categories, services, filterCategoryId) {
646
+ const publicLayout = () => layoutServicesByCategory(categories, services, { keepEmptyGroups: false });
647
+ if (filterCategoryId === null || filterCategoryId === void 0) return {
648
+ kind: "all",
649
+ layout: publicLayout()
650
+ };
651
+ const category = categories.find((candidate) => candidate.id === filterCategoryId);
652
+ if (category === void 0) return {
653
+ kind: "fallback-all",
654
+ missingCategoryId: filterCategoryId,
655
+ layout: publicLayout()
656
+ };
657
+ return {
658
+ kind: "single",
659
+ group: {
660
+ category,
661
+ services: services.filter((service) => service.categoryId === filterCategoryId)
662
+ }
663
+ };
664
+ }
665
+ //#endregion
598
666
  //#region ../core/src/location.ts
599
667
  function normalizeLocationAddressText(value) {
600
668
  if (typeof value !== "string") return null;
@@ -613,7 +681,7 @@ function toLocationCoordinates(value) {
613
681
  }
614
682
  //#endregion
615
683
  //#region ../core/src/rich-text.ts
616
- function isRecord$38(value) {
684
+ function isRecord$39(value) {
617
685
  return typeof value === "object" && value !== null && !Array.isArray(value);
618
686
  }
619
687
  function hasOnlyKeys(value, allowed) {
@@ -632,10 +700,10 @@ function isDenseArrayOf(value, predicate) {
632
700
  return true;
633
701
  }
634
702
  function isTipTapMark(value) {
635
- return isRecord$38(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$38(value.attrs));
703
+ return isRecord$39(value) && hasOnlyKeys(value, markKeys) && typeof value.type === "string" && value.type.length > 0 && (value.attrs === void 0 || isRecord$39(value.attrs));
636
704
  }
637
705
  function isTipTapNode(value) {
638
- 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;
706
+ 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;
639
707
  if (value.type === "text") return typeof value.text === "string" && value.text.length > 0 && value.content === void 0;
640
708
  return value.text === void 0;
641
709
  }
@@ -672,10 +740,10 @@ function parseBrevoListId(value) {
672
740
  //#endregion
673
741
  //#region ../core/src/mailing-list-targeting-parser.ts
674
742
  function parseMailingListPlacementTargeting(value) {
675
- if (!isRecord$37(value)) return targetingError("shape");
743
+ if (!isRecord$38(value)) return targetingError("shape");
676
744
  if (value.mode === "default") return ok$3({ mode: "default" });
677
745
  if (value.mode !== "custom") return targetingError("mode");
678
- if (!isRecord$37(value.selection)) return targetingError("selection");
746
+ if (!isRecord$38(value.selection)) return targetingError("selection");
679
747
  switch (value.provider) {
680
748
  case "native": return parseNativeTargeting(value.selection);
681
749
  case "brevo": return parseBrevoTargeting(value.selection);
@@ -779,7 +847,7 @@ function parseUniqueIds(value, parseId) {
779
847
  }
780
848
  return ok$3(parsed);
781
849
  }
782
- function isRecord$37(value) {
850
+ function isRecord$38(value) {
783
851
  return typeof value === "object" && value !== null && !Array.isArray(value);
784
852
  }
785
853
  function targetingError(field) {
@@ -802,12 +870,12 @@ new Map([
802
870
  ].map((kind, index) => [kind, index]));
803
871
  //#endregion
804
872
  //#region ../core/src/mailing-list-setup.ts
805
- function isRecord$34(value) {
873
+ function isRecord$35(value) {
806
874
  return typeof value === "object" && value !== null && !Array.isArray(value);
807
875
  }
808
876
  const DEFAULT_MAILING_LIST_SUCCESS_MESSAGE = "Thanks — you're on the list.";
809
877
  function parseMailingListProviderScope(value) {
810
- if (!isRecord$34(value)) return err$3("invalid_mailing_list_provider_scope");
878
+ if (!isRecord$35(value)) return err$3("invalid_mailing_list_provider_scope");
811
879
  if (value.provider === "native") return ok$3({ provider: "native" });
812
880
  if (value.provider === "brevo") {
813
881
  const accountId = parseBrevoAccountId(value.accountId);
@@ -828,7 +896,7 @@ function parseMailingListProviderScope(value) {
828
896
  //#endregion
829
897
  //#region ../core/src/mailing-list-setup-parser.ts
830
898
  function parseMailingListGlobalForm(value) {
831
- if (!isRecord$33(value)) return formError("shape");
899
+ if (!isRecord$34(value)) return formError("shape");
832
900
  const fields = parseGlobalFields(value.fields);
833
901
  if (fields === null) return formError("fields");
834
902
  if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
@@ -847,7 +915,7 @@ function parseMailingListGlobalForm(value) {
847
915
  });
848
916
  }
849
917
  function parseGlobalFields(value) {
850
- if (!isRecord$33(value) || typeof value.preset !== "string") return null;
918
+ if (!isRecord$34(value) || typeof value.preset !== "string") return null;
851
919
  switch (value.preset) {
852
920
  case "emailOnly": return { preset: "emailOnly" };
853
921
  case "emailName": return isRequirement(value.name) ? {
@@ -884,7 +952,7 @@ function parseOptionalString(value) {
884
952
  function isRequirement(value) {
885
953
  return value === "required" || value === "optional";
886
954
  }
887
- function isRecord$33(value) {
955
+ function isRecord$34(value) {
888
956
  return typeof value === "object" && value !== null && !Array.isArray(value);
889
957
  }
890
958
  function formError(field) {
@@ -934,7 +1002,7 @@ function planMailingListChoicePresentation(targeting) {
934
1002
  //#endregion
935
1003
  //#region ../core/src/mailing-list-runtime.ts
936
1004
  function parseMailingListRuntimeContext(value) {
937
- if (!isRecord$32(value)) return invalidRuntimeContext();
1005
+ if (!isRecord$33(value)) return invalidRuntimeContext();
938
1006
  if (value.tag === "not_configured") return ok$3({ tag: "not_configured" });
939
1007
  if (value.tag !== "configured") return invalidRuntimeContext();
940
1008
  const revision = parseMailingListSetupRevision(value.revision);
@@ -949,7 +1017,7 @@ function parseMailingListRuntimeContext(value) {
949
1017
  });
950
1018
  }
951
1019
  function parseRuntimeProvider(value) {
952
- if (!isRecord$32(value)) return null;
1020
+ if (!isRecord$33(value)) return null;
953
1021
  switch (value.provider) {
954
1022
  case "native": return parseNativeRuntimeProvider(value);
955
1023
  case "brevo": return parseBrevoRuntimeProvider(value);
@@ -981,7 +1049,7 @@ function parseMailchimpRuntimeProvider(value) {
981
1049
  if (defaultAudienceId === null) return null;
982
1050
  const audiences = [];
983
1051
  for (const audience of value.audiences) {
984
- if (!isRecord$32(audience)) return null;
1052
+ if (!isRecord$33(audience)) return null;
985
1053
  const id = parseMailchimpAudienceId$1(audience.id);
986
1054
  const name = parseName(audience.name);
987
1055
  if (id === null || name === null || !Array.isArray(audience.interests)) return null;
@@ -1002,7 +1070,7 @@ function parseMailchimpRuntimeProvider(value) {
1002
1070
  function parseMailchimpInterests(value) {
1003
1071
  const interests = [];
1004
1072
  for (const interest of value) {
1005
- if (!isRecord$32(interest)) return null;
1073
+ if (!isRecord$33(interest)) return null;
1006
1074
  const id = parseMailchimpInterestId(interest.id);
1007
1075
  const name = parseName(interest.name);
1008
1076
  const categoryName = parseName(interest.categoryName);
@@ -1019,7 +1087,7 @@ function parseNamedItems(value, parseId) {
1019
1087
  if (!Array.isArray(value)) return null;
1020
1088
  const items = [];
1021
1089
  for (const item of value) {
1022
- if (!isRecord$32(item)) return null;
1090
+ if (!isRecord$33(item)) return null;
1023
1091
  const id = parseId(item.id);
1024
1092
  const name = parseName(item.name);
1025
1093
  if (id === null || name === null) return null;
@@ -1035,7 +1103,7 @@ function parseName(value) {
1035
1103
  const name = value.trim();
1036
1104
  return name.length > 0 ? name : null;
1037
1105
  }
1038
- function isRecord$32(value) {
1106
+ function isRecord$33(value) {
1039
1107
  return typeof value === "object" && value !== null && !Array.isArray(value);
1040
1108
  }
1041
1109
  function invalidRuntimeContext() {
@@ -1595,11 +1663,11 @@ function parseRelativeStoragePath(value) {
1595
1663
  }
1596
1664
  //#endregion
1597
1665
  //#region ../media-core/src/typeGuards.ts
1598
- function isRecord$30(value) {
1666
+ function isRecord$31(value) {
1599
1667
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1600
1668
  }
1601
1669
  function asRecord$3(value) {
1602
- return isRecord$30(value) ? value : void 0;
1670
+ return isRecord$31(value) ? value : void 0;
1603
1671
  }
1604
1672
  function isFileJsonObject(value) {
1605
1673
  return Object.values(value).every(isFileJsonValue);
@@ -1608,7 +1676,7 @@ function isFileJsonValue(value) {
1608
1676
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1609
1677
  if (typeof value === "number") return Number.isFinite(value);
1610
1678
  if (Array.isArray(value)) return value.every(isFileJsonValue);
1611
- return isRecord$30(value) && isFileJsonObject(value);
1679
+ return isRecord$31(value) && isFileJsonObject(value);
1612
1680
  }
1613
1681
  new TextDecoder("latin1");
1614
1682
  //#endregion
@@ -1867,7 +1935,7 @@ function isCmsMediaObjectType(value) {
1867
1935
  return typeof value === "string" && CMS_MEDIA_OBJECT_TYPES.includes(value);
1868
1936
  }
1869
1937
  function isCmsMediaObject(value) {
1870
- if (!isRecord$30(value)) return false;
1938
+ if (!isRecord$31(value)) return false;
1871
1939
  const type = value.type;
1872
1940
  if (!isCmsMediaObjectType(type)) return false;
1873
1941
  if ("attrs" in value) return false;
@@ -1878,7 +1946,7 @@ function collectMediaObjects(value, out, location = "") {
1878
1946
  value.forEach((item, index) => collectMediaObjects(item, out, `${location}[${index}]`));
1879
1947
  return;
1880
1948
  }
1881
- if (!isRecord$30(value)) return;
1949
+ if (!isRecord$31(value)) return;
1882
1950
  if (isCmsMediaObject(value)) out.push({
1883
1951
  obj: value,
1884
1952
  location
@@ -1904,8 +1972,8 @@ function walkAndCollectImageAttrs(value, out, location = "") {
1904
1972
  value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
1905
1973
  return;
1906
1974
  }
1907
- if (!isRecord$30(value)) return;
1908
- if (value.type === "image" && isRecord$30(value.attrs)) out.push({
1975
+ if (!isRecord$31(value)) return;
1976
+ if (value.type === "image" && isRecord$31(value.attrs)) out.push({
1909
1977
  attrs: value.attrs,
1910
1978
  location: location ? `${location}.attrs` : "attrs"
1911
1979
  });
@@ -1952,7 +2020,7 @@ function mediaIdentifierOrFallback(current, fallback) {
1952
2020
  }
1953
2021
  //#endregion
1954
2022
  //#region ../media-core/src/contentMediaAudit.ts
1955
- function isRecord$29(value) {
2023
+ function isRecord$30(value) {
1956
2024
  return typeof value === "object" && value !== null && !Array.isArray(value);
1957
2025
  }
1958
2026
  function summarizeContentMediaValue(value) {
@@ -1963,7 +2031,7 @@ function summarizeContentMediaValue(value) {
1963
2031
  }
1964
2032
  if (value === null) return "null";
1965
2033
  if (Array.isArray(value)) return `array(${value.length})`;
1966
- if (isRecord$29(value)) return `object(${Object.keys(value).sort().join(",")})`;
2034
+ if (isRecord$30(value)) return `object(${Object.keys(value).sort().join(",")})`;
1967
2035
  return typeof value;
1968
2036
  }
1969
2037
  function auditMediaValue(context, path, allowedMediaTypes, value) {
@@ -1987,9 +2055,9 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
1987
2055
  allowedMediaTypes
1988
2056
  }];
1989
2057
  }
1990
- const hasValidIdentifier = isRecord$29(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
1991
- const hasValidExplicitType = isRecord$29(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
1992
- const hasRetiredEnvironmentField = isRecord$29(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
2058
+ const hasValidIdentifier = isRecord$30(value) && typeof value.identifier === "string" && value.identifier.trim().length > 0;
2059
+ const hasValidExplicitType = isRecord$30(value) && (!("type" in value) || typeof value.type === "string" && allowedMediaTypes.includes(value.type));
2060
+ const hasRetiredEnvironmentField = isRecord$30(value) && ("assetId" in value || "storagePath" in value || "storageBucket" in value || "src" in value || "mimeType" in value || looksLikeUuid(value.id));
1993
2061
  if (hasValidIdentifier && hasValidExplicitType && !hasRetiredEnvironmentField) return [];
1994
2062
  return [{
1995
2063
  tag: "malformed-object",
@@ -2000,7 +2068,7 @@ function auditMediaValue(context, path, allowedMediaTypes, value) {
2000
2068
  }];
2001
2069
  }
2002
2070
  function auditFields(context, fields, value, parentPath) {
2003
- if (!isRecord$29(value)) return [];
2071
+ if (!isRecord$30(value)) return [];
2004
2072
  const findings = [];
2005
2073
  for (const field of fields) {
2006
2074
  if (!(field.id in value)) continue;
@@ -2017,7 +2085,7 @@ function auditFields(context, fields, value, parentPath) {
2017
2085
  if (!Array.isArray(fieldValue)) break;
2018
2086
  for (const [index, item] of fieldValue.entries()) {
2019
2087
  const itemPath = [...fieldPath, index];
2020
- if (!isRecord$29(item)) {
2088
+ if (!isRecord$30(item)) {
2021
2089
  findings.push({
2022
2090
  tag: "missing-schema",
2023
2091
  context,
@@ -2056,7 +2124,7 @@ const contentMediaTypes = /* @__PURE__ */ new Set([
2056
2124
  "spreadsheet",
2057
2125
  "archive"
2058
2126
  ]);
2059
- function isRecord$28(value) {
2127
+ function isRecord$29(value) {
2060
2128
  return typeof value === "object" && value !== null && !Array.isArray(value);
2061
2129
  }
2062
2130
  function unknownArray(value) {
@@ -2065,7 +2133,7 @@ function unknownArray(value) {
2065
2133
  function fieldsArray(value) {
2066
2134
  const directFields = unknownArray(value);
2067
2135
  if (directFields) return directFields;
2068
- if (!isRecord$28(value)) return null;
2136
+ if (!isRecord$29(value)) return null;
2069
2137
  return unknownArray(value.fields);
2070
2138
  }
2071
2139
  function nestedFields$1(field) {
@@ -2130,7 +2198,7 @@ function mergeFields(fields) {
2130
2198
  }
2131
2199
  function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds = ["document"]) {
2132
2200
  return mergeFields(rawFields.flatMap((rawField) => {
2133
- if (!isRecord$28(rawField)) return [];
2201
+ if (!isRecord$29(rawField)) return [];
2134
2202
  const { id, type } = rawField;
2135
2203
  if (typeof id !== "string" || !id || typeof type !== "string") return [];
2136
2204
  if (type === "media" || type === "file") return [{
@@ -2138,7 +2206,7 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
2138
2206
  id,
2139
2207
  allowedMediaTypes: allowedMediaTypes(rawField, type, defaultFileKinds)
2140
2208
  }];
2141
- if (flattenRepeaterGroups && type === "group" && isRecord$28(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields$1(rawField), false, defaultFileKinds);
2209
+ if (flattenRepeaterGroups && type === "group" && isRecord$29(rawField.ui) && rawField.ui.flattenInRepeater === true) return adaptFields(nestedFields$1(rawField), false, defaultFileKinds);
2142
2210
  if (type === "group" || type === "modal") return [{
2143
2211
  kind: "object",
2144
2212
  id,
@@ -2147,14 +2215,14 @@ function adaptFields(rawFields, flattenRepeaterGroups = false, defaultFileKinds
2147
2215
  if (type === "tabGroup") return [{
2148
2216
  kind: "object",
2149
2217
  id,
2150
- fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$28(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
2218
+ fields: adaptFields((unknownArray(rawField.tabs) ?? []).flatMap((tab) => isRecord$29(tab) ? unknownArray(tab.fields) ?? [] : []), false, defaultFileKinds)
2151
2219
  }];
2152
2220
  if (type !== "repeater") return [];
2153
- if (isRecord$28(rawField.itemTypes)) return [{
2221
+ if (isRecord$29(rawField.itemTypes)) return [{
2154
2222
  kind: "repeater",
2155
2223
  id,
2156
2224
  mode: "variants",
2157
- variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$28(itemType) && Array.isArray(itemType.fields) ? [{
2225
+ variants: Object.entries(rawField.itemTypes).flatMap(([typeId, itemType]) => isRecord$29(itemType) && Array.isArray(itemType.fields) ? [{
2158
2226
  typeId,
2159
2227
  fields: adaptFields(itemType.fields, true, defaultFileKinds)
2160
2228
  }] : []),
@@ -2463,7 +2531,8 @@ const siteOptionSources = [
2463
2531
  "eventCategories",
2464
2532
  "eventSeries",
2465
2533
  "venues",
2466
- "staffMembers"
2534
+ "staffMembers",
2535
+ "appointmentServiceCategories"
2467
2536
  ];
2468
2537
  const mediaDeliverySchema = z$1.object({
2469
2538
  sizes: z$1.string().optional(),
@@ -5629,6 +5698,15 @@ function asSiteStyleId(value) {
5629
5698
  function asDesignSystemTemplateId(value) {
5630
5699
  return brandNonEmptyString(value, "DesignSystemTemplateId");
5631
5700
  }
5701
+ function toNonEmptyReadonlyArray(values) {
5702
+ const [first, ...rest] = values;
5703
+ return first === void 0 ? null : [first, ...rest];
5704
+ }
5705
+ function requireNonEmptyReadonlyArray(values, message = "Expected a non-empty array.") {
5706
+ const result = toNonEmptyReadonlyArray(values);
5707
+ if (!result) throw new Error(message);
5708
+ return result;
5709
+ }
5632
5710
  function makeBoundaryId(previousBlockId, nextBlockId) {
5633
5711
  return asDesignBoundaryId(`boundary:${encodeURIComponent(previousBlockId)}:${encodeURIComponent(nextBlockId)}`);
5634
5712
  }
@@ -7293,12 +7371,577 @@ function cardRoleFromLegacyVocabulary(value) {
7293
7371
  * the role they meant, everything else fails the parse.
7294
7372
  */
7295
7373
  const cardRoleWithLegacyAliasSchema = z$1.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
7374
+ const hotspotSchema = z$1.object({
7375
+ x: z$1.number().min(0).max(1),
7376
+ y: z$1.number().min(0).max(1),
7377
+ radius: z$1.number().optional()
7378
+ });
7379
+ const rotationSchema = z$1.union([
7380
+ z$1.literal(0),
7381
+ z$1.literal(90),
7382
+ z$1.literal(180),
7383
+ z$1.literal(270)
7384
+ ]);
7385
+ const rectSchema = z$1.tuple([
7386
+ z$1.number(),
7387
+ z$1.number(),
7388
+ z$1.number(),
7389
+ z$1.number()
7390
+ ]);
7391
+ const aspectCropSchema = z$1.object({
7392
+ aspect: z$1.preprocess((value) => value == null ? void 0 : value, z$1.string().optional()),
7393
+ rect: z$1.preprocess((value) => value == null ? void 0 : value, rectSchema.optional()),
7394
+ hotspot: z$1.preprocess((value) => value == null ? void 0 : value, hotspotSchema.optional()),
7395
+ rotation: z$1.preprocess((value) => value == null ? void 0 : value, rotationSchema.optional())
7396
+ });
7397
+ const transformSchema = z$1.object({
7398
+ aspectCrops: z$1.preprocess((value) => {
7399
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
7400
+ return value;
7401
+ }, z$1.record(z$1.string(), aspectCropSchema.catch({}))).optional(),
7402
+ rect: rectSchema.optional(),
7403
+ hotspot: hotspotSchema.optional(),
7404
+ rotation: rotationSchema.optional()
7405
+ });
7406
+ const mediaPosterSchema = z$1.discriminatedUnion("tag", [z$1.object({
7407
+ tag: z$1.literal("storage"),
7408
+ assetId: z$1.string().optional(),
7409
+ storageBucket: z$1.string().min(1),
7410
+ storagePath: z$1.string().min(1)
7411
+ }), z$1.object({
7412
+ tag: z$1.literal("external-src"),
7413
+ assetId: z$1.string().optional(),
7414
+ src: z$1.string().url()
7415
+ })]);
7416
+ const mediaBaseSchema = z$1.object({
7417
+ purpose: z$1.string().optional(),
7418
+ placeholder: z$1.boolean().default(true).optional(),
7419
+ assetId: z$1.string().optional(),
7420
+ identifier: z$1.string().optional(),
7421
+ src: z$1.string().optional(),
7422
+ alt: z$1.string().optional(),
7423
+ filename: z$1.string().optional(),
7424
+ mimeType: z$1.string().optional(),
7425
+ width: z$1.number().optional(),
7426
+ height: z$1.number().optional(),
7427
+ storageBucket: z$1.string().optional(),
7428
+ storagePath: z$1.string().optional(),
7429
+ poster: mediaPosterSchema.optional(),
7430
+ transform: transformSchema.optional()
7431
+ });
7432
+ const mediaIdentifierSchema = z$1.string().trim().min(1).brand();
7433
+ const imageMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("image") });
7434
+ const videoMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("video") });
7435
+ const audioMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("audio") });
7436
+ const documentMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("document") });
7437
+ const spreadsheetMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("spreadsheet") });
7438
+ const archiveMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("archive") });
7439
+ const mediaSchema = z$1.discriminatedUnion("type", [
7440
+ imageMediaSchema,
7441
+ videoMediaSchema,
7442
+ audioMediaSchema,
7443
+ documentMediaSchema,
7444
+ spreadsheetMediaSchema,
7445
+ archiveMediaSchema
7446
+ ]);
7447
+ const persistableMediaBaseSchema = mediaBaseSchema.extend({ identifier: mediaIdentifierSchema });
7448
+ z$1.discriminatedUnion("type", [
7449
+ persistableMediaBaseSchema.extend({ type: z$1.literal("image") }),
7450
+ persistableMediaBaseSchema.extend({ type: z$1.literal("video") }),
7451
+ persistableMediaBaseSchema.extend({ type: z$1.literal("audio") }),
7452
+ persistableMediaBaseSchema.extend({ type: z$1.literal("document") }),
7453
+ persistableMediaBaseSchema.extend({ type: z$1.literal("spreadsheet") }),
7454
+ persistableMediaBaseSchema.extend({ type: z$1.literal("archive") })
7455
+ ]);
7296
7456
  //#endregion
7297
- //#region ../theme-core/src/site-styles/compilationPlan.ts
7298
- const defaultHeaderBrandPreferences = {
7299
- brandDisplay: "auto",
7300
- logoSize: "auto"
7301
- };
7457
+ //#region ../theme-core/src/site-styles/rawStyleFields.ts
7458
+ const themeV2RuntimeStyleFieldNames = [
7459
+ "_frameStyles",
7460
+ "_sectionStyles",
7461
+ "_containerStyles",
7462
+ "_cardStyles"
7463
+ ];
7464
+ const themeV2BlockSpecificRawStyleFieldNames = [
7465
+ "_contentCardStyles",
7466
+ "_heroLayoutStyles",
7467
+ "_textCardStyles"
7468
+ ];
7469
+ const themeV2NormalEditorRawStyleFieldNames = [
7470
+ ...themeV2RuntimeStyleFieldNames,
7471
+ ...themeV2BlockSpecificRawStyleFieldNames,
7472
+ "styleGroup"
7473
+ ];
7474
+ new Set(themeV2NormalEditorRawStyleFieldNames);
7475
+ [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
7476
+ const pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
7477
+ const siteChromeBackgroundOverrideOwner = "site-chrome-editor";
7478
+ const staleTransitionStyleGroupPrefix = "site-style-transition-";
7479
+ /**
7480
+ * A `styleGroup` in the retired `site-style-transition-*` namespace is residue
7481
+ * from the old per-block transition path (issue 1422). Any other value is
7482
+ * authored and must survive.
7483
+ */
7484
+ function isStaleTransitionStyleGroup(value) {
7485
+ return typeof value === "string" && value.startsWith(staleTransitionStyleGroupPrefix);
7486
+ }
7487
+ /** Drops a stale transition `styleGroup`, leaving authored values untouched. */
7488
+ function stripStaleTransitionStyleGroup(content) {
7489
+ if (!isStaleTransitionStyleGroup(content.styleGroup)) return { ...content };
7490
+ const { styleGroup: _stale, ...rest } = content;
7491
+ return rest;
7492
+ }
7493
+ //#endregion
7494
+ //#region ../theme-core/src/utils/tokenOrColor.ts
7495
+ /**
7496
+ * Runtime color-reference fields intentionally accept any string, including
7497
+ * the legacy empty-string "no color" sentinel. Semantic validation needs
7498
+ * palette context and happens in `parseThemeColorReference`.
7499
+ */
7500
+ const tokenOrCssColorValueSchema = z$1.string();
7501
+ const CSS_COLOR_FUNCTION_PATTERN = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\(/i;
7502
+ const CSS_HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
7503
+ const CSS_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
7504
+ "transparent",
7505
+ "currentcolor",
7506
+ "inherit",
7507
+ "initial",
7508
+ "revert",
7509
+ "revert-layer",
7510
+ "unset"
7511
+ ]);
7512
+ const COMMON_CSS_NAMED_COLORS = /* @__PURE__ */ new Set([
7513
+ "black",
7514
+ "blue",
7515
+ "gray",
7516
+ "green",
7517
+ "grey",
7518
+ "orange",
7519
+ "pink",
7520
+ "purple",
7521
+ "red",
7522
+ "white",
7523
+ "yellow"
7524
+ ]);
7525
+ function isCssColorLiteral(value) {
7526
+ const trimmed = value.trim();
7527
+ if (!trimmed) return false;
7528
+ return CSS_HEX_COLOR_PATTERN.test(trimmed) || CSS_COLOR_FUNCTION_PATTERN.test(trimmed) || /^var\(/i.test(trimmed) || CSS_COLOR_KEYWORDS.has(trimmed.toLowerCase());
7529
+ }
7530
+ function parseThemeToken(token) {
7531
+ const trimmed = token.trim();
7532
+ const match = trimmed.match(/^(.*?)-(\d{2,3})$/);
7533
+ if (!match) return {
7534
+ role: trimmed,
7535
+ shade: null
7536
+ };
7537
+ const role = match[1] ?? trimmed;
7538
+ const shade = match[2] ? Number(match[2]) : null;
7539
+ return {
7540
+ role,
7541
+ shade: Number.isFinite(shade) ? shade : null
7542
+ };
7543
+ }
7544
+ function parseContextThemeColorToken(value, context) {
7545
+ const parts = parseColorTokenParts(value);
7546
+ if (parts.kind !== "token") return null;
7547
+ if (!(Boolean(context?.palette?.[value.trim()]) || parts.opacityPercent !== null && Boolean(context?.palette?.[parts.token]))) return null;
7548
+ return value.trim();
7549
+ }
7550
+ function isTokenShapedTypo(value) {
7551
+ if (COMMON_CSS_NAMED_COLORS.has(value.toLowerCase())) return false;
7552
+ if (!/^[a-zA-Z][a-zA-Z0-9]*(?:-[a-zA-Z0-9]+)*(?:\/[^/]+)?$/.test(value)) return false;
7553
+ return !isCssColorLiteral(value);
7554
+ }
7555
+ function warnUnknownThemeColorReference(value) {
7556
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
7557
+ if (!isTokenShapedTypo(value)) return;
7558
+ console.warn(`[theme-core] Unknown Theme V2 color token "${value}" treated as literal CSS. Use raw:* for deliberate custom CSS values.`);
7559
+ }
7560
+ function parseThemeColorReference(input, context) {
7561
+ const trimmed = input.trim();
7562
+ if (trimmed.startsWith("raw:")) return {
7563
+ kind: "literal-css",
7564
+ value: trimmed.slice(4)
7565
+ };
7566
+ const contextToken = parseContextThemeColorToken(trimmed, context);
7567
+ if (contextToken) return {
7568
+ kind: "palette-context-token",
7569
+ token: contextToken
7570
+ };
7571
+ const knownToken = parseThemeColorToken(trimmed);
7572
+ if (knownToken) return {
7573
+ kind: "theme-token",
7574
+ token: knownToken
7575
+ };
7576
+ warnUnknownThemeColorReference(trimmed);
7577
+ return {
7578
+ kind: "literal-css",
7579
+ value: trimmed
7580
+ };
7581
+ }
7582
+ function resolveThemeColorReferenceValue(input, context) {
7583
+ const reference = parseThemeColorReference(input, context);
7584
+ return reference.kind === "literal-css" ? reference.value : rgbColorValue(reference.token);
7585
+ }
7586
+ function resolveTokenOrCssColorValue(input, context) {
7587
+ return resolveThemeColorReferenceValue(input, context);
7588
+ }
7589
+ function resolveOnColorForThemeToken(token) {
7590
+ if (!parseThemeColorToken(token)) return null;
7591
+ const parts = parseColorTokenParts(token);
7592
+ if (parts.kind !== "token") return null;
7593
+ const { role, shade } = parseThemeToken(parts.token);
7594
+ const normalizedRole = role.trim();
7595
+ if (!normalizedRole) return null;
7596
+ if (!shade) return rgbColorValue(`on${normalizedRole.charAt(0).toUpperCase() + normalizedRole.slice(1)}`);
7597
+ return shade <= 400 ? "#000000" : "#FFFFFF";
7598
+ }
7599
+ //#endregion
7600
+ //#region ../theme-core/src/boxBackgroundSchema.ts
7601
+ const boxBackgroundOverlaySchema = z$1.preprocess(normalizeAuthoringOpacity, z$1.discriminatedUnion("type", [
7602
+ z$1.object({ type: z$1.literal("none") }),
7603
+ z$1.object({
7604
+ type: z$1.literal("color"),
7605
+ color: tokenOrCssColorValueSchema.nullable().optional(),
7606
+ opacity: z$1.number().min(0).max(1).nullable().optional()
7607
+ }),
7608
+ z$1.object({
7609
+ type: z$1.literal("gradient"),
7610
+ gradient: z$1.string().nullable().optional(),
7611
+ opacity: z$1.number().min(0).max(1).nullable().optional()
7612
+ })
7613
+ ]));
7614
+ const commonSchema = z$1.object({
7615
+ contentOwner: z$1.literal(pageDesignEditorBackgroundOverrideOwner).optional(),
7616
+ textColor: tokenOrCssColorValueSchema.nullable().optional(),
7617
+ headingColor: tokenOrCssColorValueSchema.nullable().optional(),
7618
+ linkColor: tokenOrCssColorValueSchema.nullable().optional(),
7619
+ supportingColor: tokenOrCssColorValueSchema.nullable().optional()
7620
+ });
7621
+ const mediaPresentationSchema = commonSchema.extend({
7622
+ opacity: z$1.preprocess(normalizeOpacity, z$1.number().min(0).max(1).nullable()).optional(),
7623
+ objectFit: z$1.enum([
7624
+ "fill",
7625
+ "fit",
7626
+ "original",
7627
+ "custom"
7628
+ ]).nullable().optional(),
7629
+ scale: z$1.string().nullable().optional(),
7630
+ position: z$1.string().nullable().optional(),
7631
+ motion: z$1.enum(["static", "fixed-frame"]).nullable().optional(),
7632
+ overflow: z$1.boolean().nullable().optional(),
7633
+ overlay: boxBackgroundOverlaySchema.nullable().optional()
7634
+ });
7635
+ const boxBackgroundSchema = z$1.discriminatedUnion("type", [
7636
+ commonSchema.extend({
7637
+ type: z$1.literal("color"),
7638
+ color: tokenOrCssColorValueSchema.nullable().optional()
7639
+ }),
7640
+ commonSchema.extend({
7641
+ type: z$1.literal("gradient"),
7642
+ gradient: z$1.string().nullable().optional(),
7643
+ gradientPresetId: z$1.string().min(1).nullable().optional()
7644
+ }),
7645
+ mediaPresentationSchema.extend({
7646
+ type: z$1.literal("image"),
7647
+ image: imageMediaSchema.nullable().optional()
7648
+ }),
7649
+ mediaPresentationSchema.extend({
7650
+ type: z$1.literal("video"),
7651
+ image: videoMediaSchema.nullable().optional(),
7652
+ showVideoOnMobile: z$1.boolean().nullable().optional()
7653
+ })
7654
+ ]);
7655
+ function normalizeAuthoringOpacity(value) {
7656
+ if (!isRecord$28(value)) return value;
7657
+ return {
7658
+ ...value,
7659
+ opacity: normalizeOpacity(value.opacity)
7660
+ };
7661
+ }
7662
+ function normalizeOpacity(value) {
7663
+ return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
7664
+ }
7665
+ function isRecord$28(value) {
7666
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7667
+ }
7668
+ //#endregion
7669
+ //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
7670
+ function parseCanonicalRuntimeBackground(content) {
7671
+ const background = readRecord(readRecord(content._sectionStyles)?.background);
7672
+ if (!background) return { kind: "none" };
7673
+ const parsed = boxBackgroundSchema.safeParse(background);
7674
+ if (!parsed.success) return { kind: "none" };
7675
+ return parsed.data.contentOwner === "page-design-editor" ? {
7676
+ kind: "page-design-override",
7677
+ background: parsed.data
7678
+ } : {
7679
+ kind: "block-authored",
7680
+ background: parsed.data
7681
+ };
7682
+ }
7683
+ function readRecord(value) {
7684
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
7685
+ }
7686
+ //#endregion
7687
+ //#region ../theme-core/src/site-styles/transitionCatalogue.ts
7688
+ /**
7689
+ * Family order for pickers. Entries below are listed family by family in this
7690
+ * same order, so the flat option list and the grouped families agree.
7691
+ */
7692
+ const transitionCatalogueFamilies = [
7693
+ {
7694
+ id: "gradient",
7695
+ label: "Gradient"
7696
+ },
7697
+ {
7698
+ id: "wave",
7699
+ label: "Wave"
7700
+ },
7701
+ {
7702
+ id: "angle",
7703
+ label: "Angle"
7704
+ }
7705
+ ];
7706
+ const mediumHeight = "6rem + 2px";
7707
+ const compactHeight = "4.5rem + 2px";
7708
+ const largeHeight = "7rem + 2px";
7709
+ /**
7710
+ * The single source of transition vocabulary. Order is significant: it is the
7711
+ * order of `boundaryOptions` (after `none`) and of each family's variants,
7712
+ * which persisted pins, page-flow presets and the editor picker all rely on.
7713
+ *
7714
+ * Shapes match what `generateLayoutCss` draws today. Structured
7715
+ * amplitude/frequency treatments (issue 826) extend this list later.
7716
+ */
7717
+ const transitionCatalogueEntries = defineExactCatalog()([
7718
+ {
7719
+ intent: "soft-fade-short",
7720
+ optionId: asBoundaryOptionId("break:soft"),
7721
+ family: "gradient",
7722
+ variantId: "soft",
7723
+ label: "Soft",
7724
+ variantLabel: "Soft",
7725
+ treatment: {
7726
+ kind: "soft-fade",
7727
+ height: "medium",
7728
+ curve: "soft"
7729
+ },
7730
+ size: "medium",
7731
+ heightExpression: mediumHeight,
7732
+ shape: { kind: "fade" }
7733
+ },
7734
+ {
7735
+ intent: "soft-fade-long",
7736
+ optionId: asBoundaryOptionId("break:spacious"),
7737
+ family: "gradient",
7738
+ variantId: "spacious",
7739
+ label: "Spacious",
7740
+ variantLabel: "Spacious",
7741
+ treatment: {
7742
+ kind: "soft-fade",
7743
+ height: "large",
7744
+ curve: "spacious"
7745
+ },
7746
+ size: "spacious",
7747
+ heightExpression: "10rem",
7748
+ shape: { kind: "fade" }
7749
+ },
7750
+ {
7751
+ intent: "soft-fade-expansive",
7752
+ optionId: asBoundaryOptionId("break:expansive"),
7753
+ family: "gradient",
7754
+ variantId: "expansive",
7755
+ label: "Expansive",
7756
+ variantLabel: "Expansive",
7757
+ treatment: {
7758
+ kind: "soft-fade",
7759
+ height: "expansive",
7760
+ curve: "expansive"
7761
+ },
7762
+ size: "expansive",
7763
+ heightExpression: "14rem",
7764
+ shape: { kind: "fade" }
7765
+ },
7766
+ {
7767
+ intent: "wave-edge-balanced",
7768
+ optionId: asBoundaryOptionId("break:wave-balanced"),
7769
+ family: "wave",
7770
+ variantId: "balanced",
7771
+ label: "Balanced wave",
7772
+ variantLabel: "Balanced",
7773
+ treatment: {
7774
+ kind: "wave-edge",
7775
+ height: "medium",
7776
+ amplitude: "balanced",
7777
+ rhythm: "double",
7778
+ symmetry: "symmetrical"
7779
+ },
7780
+ size: "medium",
7781
+ heightExpression: mediumHeight,
7782
+ shape: {
7783
+ kind: "wave",
7784
+ mask: "balanced"
7785
+ }
7786
+ },
7787
+ {
7788
+ intent: "wave-edge",
7789
+ optionId: asBoundaryOptionId("break:wave"),
7790
+ family: "wave",
7791
+ variantId: "classic",
7792
+ label: "Classic wave",
7793
+ variantLabel: "Classic",
7794
+ treatment: {
7795
+ kind: "wave-edge",
7796
+ height: "medium",
7797
+ amplitude: "balanced",
7798
+ rhythm: "double",
7799
+ symmetry: "asymmetrical"
7800
+ },
7801
+ size: "medium",
7802
+ heightExpression: mediumHeight,
7803
+ shape: {
7804
+ kind: "wave",
7805
+ mask: "classic"
7806
+ }
7807
+ },
7808
+ {
7809
+ intent: "wave-edge-gentle",
7810
+ optionId: asBoundaryOptionId("break:wave-gentle"),
7811
+ family: "wave",
7812
+ variantId: "gentle",
7813
+ label: "Gentle wave",
7814
+ variantLabel: "Gentle",
7815
+ treatment: {
7816
+ kind: "wave-edge",
7817
+ height: "compact",
7818
+ amplitude: "subtle",
7819
+ rhythm: "single",
7820
+ symmetry: "symmetrical"
7821
+ },
7822
+ size: "compact",
7823
+ heightExpression: compactHeight,
7824
+ shape: {
7825
+ kind: "wave",
7826
+ mask: "gentle"
7827
+ }
7828
+ },
7829
+ {
7830
+ intent: "wave-edge-bold",
7831
+ optionId: asBoundaryOptionId("break:wave-bold"),
7832
+ family: "wave",
7833
+ variantId: "bold",
7834
+ label: "Bold wave",
7835
+ variantLabel: "Bold",
7836
+ treatment: {
7837
+ kind: "wave-edge",
7838
+ height: "large",
7839
+ amplitude: "bold",
7840
+ rhythm: "double",
7841
+ symmetry: "symmetrical"
7842
+ },
7843
+ size: "large",
7844
+ heightExpression: largeHeight,
7845
+ shape: {
7846
+ kind: "wave",
7847
+ mask: "bold"
7848
+ }
7849
+ },
7850
+ {
7851
+ intent: "wave-edge-drift",
7852
+ optionId: asBoundaryOptionId("break:wave-drift"),
7853
+ family: "wave",
7854
+ variantId: "drift",
7855
+ label: "Drift wave",
7856
+ variantLabel: "Drift",
7857
+ treatment: {
7858
+ kind: "wave-edge",
7859
+ height: "medium",
7860
+ amplitude: "balanced",
7861
+ rhythm: "double",
7862
+ symmetry: "drift"
7863
+ },
7864
+ size: "medium",
7865
+ heightExpression: mediumHeight,
7866
+ shape: {
7867
+ kind: "wave",
7868
+ mask: "drift"
7869
+ }
7870
+ },
7871
+ {
7872
+ intent: "angle-edge-subtle",
7873
+ optionId: asBoundaryOptionId("break:angle-subtle"),
7874
+ family: "angle",
7875
+ variantId: "subtle",
7876
+ label: "Subtle angle",
7877
+ variantLabel: "Subtle",
7878
+ treatment: {
7879
+ kind: "angle-edge",
7880
+ height: "compact",
7881
+ slope: "subtle",
7882
+ direction: "forward"
7883
+ },
7884
+ size: "compact",
7885
+ heightExpression: compactHeight,
7886
+ shape: {
7887
+ kind: "angle",
7888
+ polygon: {
7889
+ startTopPercent: 34,
7890
+ endTopPercent: 18
7891
+ }
7892
+ }
7893
+ },
7894
+ {
7895
+ intent: "angle-edge",
7896
+ optionId: asBoundaryOptionId("break:angle-balanced"),
7897
+ family: "angle",
7898
+ variantId: "balanced",
7899
+ label: "Balanced angle",
7900
+ variantLabel: "Balanced",
7901
+ treatment: {
7902
+ kind: "angle-edge",
7903
+ height: "medium",
7904
+ slope: "balanced",
7905
+ direction: "forward"
7906
+ },
7907
+ size: "medium",
7908
+ heightExpression: mediumHeight,
7909
+ shape: {
7910
+ kind: "angle",
7911
+ polygon: {
7912
+ startTopPercent: 42,
7913
+ endTopPercent: 8
7914
+ }
7915
+ }
7916
+ },
7917
+ {
7918
+ intent: "angle-edge-steep",
7919
+ optionId: asBoundaryOptionId("break:angle-steep"),
7920
+ family: "angle",
7921
+ variantId: "steep",
7922
+ label: "Steep angle",
7923
+ variantLabel: "Steep",
7924
+ treatment: {
7925
+ kind: "angle-edge",
7926
+ height: "large",
7927
+ slope: "steep",
7928
+ direction: "forward"
7929
+ },
7930
+ size: "large",
7931
+ heightExpression: largeHeight,
7932
+ shape: {
7933
+ kind: "angle",
7934
+ polygon: {
7935
+ startTopPercent: 54,
7936
+ endTopPercent: 0
7937
+ }
7938
+ }
7939
+ }
7940
+ ]);
7941
+ /** CSS `clip-path` value for an angle shape, as `generateLayoutCss` draws it. */
7942
+ function anglePolygonCss(polygon) {
7943
+ return `polygon(0 ${polygon.startTopPercent}%, 100% ${polygon.endTopPercent}%, 100% 100%, 0 100%)`;
7944
+ }
7302
7945
  //#endregion
7303
7946
  //#region ../theme-core/src/site-styles/sectionTransitions.ts
7304
7947
  function boundaryRuntimeMetrics(family, size, heightExpression) {
@@ -7309,22 +7952,19 @@ function boundaryRuntimeMetrics(family, size, heightExpression) {
7309
7952
  reserveCss: `calc((${heightExpression}) / 2)`
7310
7953
  };
7311
7954
  }
7312
- const sectionBoundaryRuntimeMetricsByTransition = {
7313
- none: null,
7314
- "soft-fade-short": boundaryRuntimeMetrics("gradient", "medium", "6rem + 2px"),
7315
- "soft-fade-long": boundaryRuntimeMetrics("gradient", "spacious", "10rem"),
7316
- "soft-fade-expansive": boundaryRuntimeMetrics("gradient", "expansive", "14rem"),
7317
- "wave-edge": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
7318
- "wave-edge-balanced": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
7319
- "wave-edge-gentle": boundaryRuntimeMetrics("wave", "compact", "4.5rem + 2px"),
7320
- "wave-edge-bold": boundaryRuntimeMetrics("wave", "large", "7rem + 2px"),
7321
- "wave-edge-drift": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
7322
- "angle-edge": boundaryRuntimeMetrics("angle", "medium", "6rem + 2px"),
7323
- "angle-edge-subtle": boundaryRuntimeMetrics("angle", "compact", "4.5rem + 2px"),
7324
- "angle-edge-steep": boundaryRuntimeMetrics("angle", "large", "7rem + 2px")
7325
- };
7326
- function boundaryRuntimeMetricsForTransition(transition) {
7327
- return sectionBoundaryRuntimeMetricsByTransition[transition];
7955
+ new Map(transitionCatalogueEntries.map((entry) => [entry.intent, entry]));
7956
+ const metricsByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, boundaryRuntimeMetrics(entry.family, entry.size, entry.heightExpression)]));
7957
+ /**
7958
+ * The catalogue's compile-time coverage check guarantees an entry per
7959
+ * decorative intent, so a miss here is a broken invariant, not bad input.
7960
+ */
7961
+ function requireCatalogueValue(values, intent) {
7962
+ const value = values.get(intent);
7963
+ if (value === void 0) throw new Error(`Transition catalogue has no entry for ${intent}`);
7964
+ return value;
7965
+ }
7966
+ function boundaryRuntimeMetricsForDecorativeTransition(transition) {
7967
+ return requireCatalogueValue(metricsByIntent, transition);
7328
7968
  }
7329
7969
  const noneBoundaryOption = {
7330
7970
  id: asBoundaryOptionId("break:none"),
@@ -7332,29 +7972,233 @@ const noneBoundaryOption = {
7332
7972
  transitionAfter: "none",
7333
7973
  treatment: { kind: "none" }
7334
7974
  };
7335
- const softBoundaryOption = {
7336
- id: asBoundaryOptionId("break:soft"),
7337
- label: "Soft",
7338
- transitionAfter: "soft-fade-short",
7339
- treatment: {
7340
- kind: "soft-fade",
7341
- height: "medium",
7342
- curve: "soft"
7343
- }
7975
+ const boundaryOptionByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, {
7976
+ id: entry.optionId,
7977
+ label: entry.label,
7978
+ transitionAfter: entry.intent,
7979
+ treatment: entry.treatment
7980
+ }]));
7981
+ function boundaryOptionForTransitionIntent(intent) {
7982
+ return requireCatalogueValue(boundaryOptionByIntent, intent);
7983
+ }
7984
+ const softBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-short");
7985
+ const spaciousBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-long");
7986
+ boundaryOptionForTransitionIntent("soft-fade-expansive");
7987
+ boundaryOptionForTransitionIntent("wave-edge");
7988
+ boundaryOptionForTransitionIntent("wave-edge-balanced");
7989
+ boundaryOptionForTransitionIntent("wave-edge-gentle");
7990
+ boundaryOptionForTransitionIntent("wave-edge-bold");
7991
+ boundaryOptionForTransitionIntent("wave-edge-drift");
7992
+ boundaryOptionForTransitionIntent("angle-edge-subtle");
7993
+ boundaryOptionForTransitionIntent("angle-edge");
7994
+ boundaryOptionForTransitionIntent("angle-edge-steep");
7995
+ /**
7996
+ * Must remain non-empty; downstream code indexes `[0]` as the documented default.
7997
+ */
7998
+ const boundaryOptions = [noneBoundaryOption, ...transitionCatalogueEntries.map((entry) => boundaryOptionForTransitionIntent(entry.intent))];
7999
+ const noneTransitionVariant = {
8000
+ familyId: "none",
8001
+ variantId: "none",
8002
+ label: "None",
8003
+ boundaryOption: noneBoundaryOption
7344
8004
  };
7345
- const spaciousBoundaryOption = {
7346
- id: asBoundaryOptionId("break:spacious"),
7347
- label: "Spacious",
7348
- transitionAfter: "soft-fade-long",
7349
- treatment: {
7350
- kind: "soft-fade",
7351
- height: "large",
7352
- curve: "spacious"
8005
+ function transitionVariantForEntry(entry) {
8006
+ return {
8007
+ familyId: entry.family,
8008
+ variantId: entry.variantId,
8009
+ label: entry.variantLabel,
8010
+ boundaryOption: boundaryOptionForTransitionIntent(entry.intent)
8011
+ };
8012
+ }
8013
+ const sectionTransitionFamilies = [{
8014
+ id: "none",
8015
+ label: "None",
8016
+ variants: [noneTransitionVariant]
8017
+ }, ...transitionCatalogueFamilies.map((family) => ({
8018
+ id: family.id,
8019
+ label: family.label,
8020
+ variants: requireNonEmptyReadonlyArray(transitionCatalogueEntries.filter((entry) => entry.family === family.id).map(transitionVariantForEntry), `Transition family ${family.id} has no catalogue entries.`)
8021
+ }))];
8022
+ new Map(boundaryOptions.map((option) => [option.id, option]));
8023
+ new Map(boundaryOptions.map((option) => [stableTreatmentKey(option.treatment), option]));
8024
+ new Map(sectionTransitionFamilies.flatMap((family) => family.variants.map((variant) => [variant.boundaryOption.id, variant])));
8025
+ new Map(sectionTransitionFamilies.map((family) => [family.id, family]));
8026
+ function stableTreatmentKey(treatment) {
8027
+ switch (treatment.kind) {
8028
+ case "none": return "none";
8029
+ case "soft-fade": return `${treatment.kind}:${treatment.height}:${treatment.curve}`;
8030
+ case "wave-edge": return [
8031
+ treatment.kind,
8032
+ treatment.height,
8033
+ treatment.amplitude,
8034
+ treatment.rhythm,
8035
+ treatment.symmetry
8036
+ ].join(":");
8037
+ case "angle-edge": return [
8038
+ treatment.kind,
8039
+ treatment.height,
8040
+ treatment.slope,
8041
+ treatment.direction
8042
+ ].join(":");
8043
+ case "legacy-unsupported": return `${treatment.kind}:${treatment.boundaryOptionId}`;
8044
+ }
8045
+ }
8046
+ //#endregion
8047
+ //#region ../theme-core/src/site-styles/sectionBoundaryComposition.ts
8048
+ const backgroundToken = requireThemeColorToken$1("background");
8049
+ /**
8050
+ * Classifies a section from its final render content. `_sectionStyles`
8051
+ * already holds the effective background (Look default, page-design override
8052
+ * and block-authored media merged by `resolveSectionBackground`), so this only
8053
+ * reads it and never re-decides precedence. A missing or unparseable
8054
+ * background is the page background, as it is for the renderer.
8055
+ */
8056
+ function boundaryEndpointForSectionContent(content) {
8057
+ const background = parseCanonicalRuntimeBackground(content);
8058
+ if (background.kind === "none") return {
8059
+ kind: "paint",
8060
+ token: backgroundToken
8061
+ };
8062
+ const effective = background.background;
8063
+ switch (effective.type) {
8064
+ case "color": {
8065
+ if (effective.color === null || effective.color === void 0) return {
8066
+ kind: "paint",
8067
+ token: backgroundToken
8068
+ };
8069
+ const token = parseThemeColorToken(effective.color);
8070
+ return token ? {
8071
+ kind: "paint",
8072
+ token
8073
+ } : {
8074
+ kind: "unpainted",
8075
+ reason: "literal-color"
8076
+ };
8077
+ }
8078
+ case "gradient": return {
8079
+ kind: "unpainted",
8080
+ reason: "gradient"
8081
+ };
8082
+ case "image":
8083
+ case "video": return {
8084
+ kind: "unpainted",
8085
+ reason: "media"
8086
+ };
8087
+ default: return assertNever$13(effective, "Unhandled section background type");
7353
8088
  }
8089
+ }
8090
+ /**
8091
+ * Reads both facts from final render content in one place. `_frameStyles` is
8092
+ * the compiled frame; content without one (no page design, an SDK block)
8093
+ * draws a plain frame.
8094
+ */
8095
+ function sectionFactsForContent(content) {
8096
+ return {
8097
+ endpoint: boundaryEndpointForSectionContent(content),
8098
+ frame: frameKindForContent(content)
8099
+ };
8100
+ }
8101
+ function frameKindForContent(content) {
8102
+ const frameStyles = content._frameStyles;
8103
+ const kind = typeof frameStyles === "object" && frameStyles !== null && "kind" in frameStyles ? frameStyles.kind : null;
8104
+ return kind === "panel" || kind === "flush-panel" ? kind : "plain";
8105
+ }
8106
+ /**
8107
+ * The frame rule: a flush panel owns the edge it runs to, so no break is
8108
+ * drawn next to one. The single home for this rule; the generated
8109
+ * pipeline's budget and the site-style lab ask it with their plan frames.
8110
+ */
8111
+ function frameNotShownReason(previous, next) {
8112
+ return previous === "flush-panel" || next === "flush-panel" ? "flush-panel" : null;
8113
+ }
8114
+ /**
8115
+ * Decides whether and how a join draws. The frame rule wins before paint is
8116
+ * considered; two unpainted sides show nothing.
8117
+ */
8118
+ function composeBoundary(previous, next) {
8119
+ const frameReason = frameNotShownReason(previous.frame, next.frame);
8120
+ if (frameReason) return {
8121
+ kind: "not-shown",
8122
+ reason: frameReason
8123
+ };
8124
+ const from = previous.endpoint;
8125
+ const to = next.endpoint;
8126
+ if (from.kind === "paint") return {
8127
+ kind: "shown",
8128
+ composition: to.kind === "paint" ? {
8129
+ kind: "paint-to-paint",
8130
+ from: from.token,
8131
+ to: to.token
8132
+ } : {
8133
+ kind: "paint-to-unpainted",
8134
+ from: from.token
8135
+ }
8136
+ };
8137
+ return to.kind === "paint" ? {
8138
+ kind: "shown",
8139
+ composition: {
8140
+ kind: "unpainted-to-paint",
8141
+ to: to.token
8142
+ }
8143
+ } : {
8144
+ kind: "not-shown",
8145
+ reason: "both-unpainted"
8146
+ };
8147
+ }
8148
+ /**
8149
+ * Placement, reserves and paint for a shown boundary, derived from the
8150
+ * composition and the catalogue's metrics for the treatment.
8151
+ */
8152
+ function boundaryLayout(intent, composition) {
8153
+ const metrics = boundaryRuntimeMetricsForDecorativeTransition(intent);
8154
+ switch (composition.kind) {
8155
+ case "paint-to-paint": return {
8156
+ placement: "centred",
8157
+ reserves: {
8158
+ before: metrics.reserveCss,
8159
+ after: metrics.reserveCss
8160
+ },
8161
+ paint: {
8162
+ band: composition.from,
8163
+ shape: composition.to
8164
+ }
8165
+ };
8166
+ case "unpainted-to-paint": return {
8167
+ placement: "previous",
8168
+ reserves: {
8169
+ before: metrics.heightCss,
8170
+ after: null
8171
+ },
8172
+ paint: {
8173
+ band: null,
8174
+ shape: composition.to
8175
+ }
8176
+ };
8177
+ case "paint-to-unpainted": return {
8178
+ placement: "next",
8179
+ reserves: {
8180
+ before: null,
8181
+ after: metrics.heightCss
8182
+ },
8183
+ paint: {
8184
+ band: null,
8185
+ shape: composition.from
8186
+ }
8187
+ };
8188
+ default: return assertNever$13(composition, "Unhandled boundary composition");
8189
+ }
8190
+ }
8191
+ function requireThemeColorToken$1(value) {
8192
+ const token = parseThemeColorToken(value);
8193
+ if (!token) throw new Error(`Expected a theme color token. Received: ${value}`);
8194
+ return token;
8195
+ }
8196
+ //#endregion
8197
+ //#region ../theme-core/src/site-styles/compilationPlan.ts
8198
+ const defaultHeaderBrandPreferences = {
8199
+ brandDisplay: "auto",
8200
+ logoSize: "auto"
7354
8201
  };
7355
- asBoundaryOptionId("break:expansive");
7356
- asBoundaryOptionId("break:wave");
7357
- asBoundaryOptionId("break:wave-balanced"), asBoundaryOptionId("break:wave-gentle"), asBoundaryOptionId("break:wave-bold"), asBoundaryOptionId("break:wave-drift"), asBoundaryOptionId("break:angle-subtle"), asBoundaryOptionId("break:angle-balanced"), asBoundaryOptionId("break:angle-steep");
7358
8202
  asBoundaryOptionId("break:unknown");
7359
8203
  const pageDesignUserPinnedAppearanceChoiceSchema = z$1.object({
7360
8204
  kind: z$1.literal("user-pinned"),
@@ -7481,14 +8325,11 @@ function validateUniquePageDesignPins(intent, ctx) {
7481
8325
  label: "block Look pin"
7482
8326
  });
7483
8327
  addDuplicateStringIssues(ctx, {
7484
- values: intent.boundaryPins.map((pin) => boundaryKey$1(pin.previousBlockId, pin.nextBlockId)),
8328
+ values: intent.boundaryPins.map((pin) => makeBoundaryId(pin.previousBlockId, pin.nextBlockId)),
7485
8329
  path: ["boundaryPins"],
7486
8330
  label: "boundary pin"
7487
8331
  });
7488
8332
  }
7489
- function boundaryKey$1(previousBlockId, nextBlockId) {
7490
- return makeBoundaryId(previousBlockId, nextBlockId);
7491
- }
7492
8333
  //#endregion
7493
8334
  //#region ../theme-core/src/buttons/types.ts
7494
8335
  /**
@@ -12748,88 +13589,6 @@ function resolveFooterRenderPlan(params) {
12748
13589
  };
12749
13590
  }
12750
13591
  }
12751
- const hotspotSchema = z$1.object({
12752
- x: z$1.number().min(0).max(1),
12753
- y: z$1.number().min(0).max(1),
12754
- radius: z$1.number().optional()
12755
- });
12756
- const rotationSchema = z$1.union([
12757
- z$1.literal(0),
12758
- z$1.literal(90),
12759
- z$1.literal(180),
12760
- z$1.literal(270)
12761
- ]);
12762
- const rectSchema = z$1.tuple([
12763
- z$1.number(),
12764
- z$1.number(),
12765
- z$1.number(),
12766
- z$1.number()
12767
- ]);
12768
- const aspectCropSchema = z$1.object({
12769
- aspect: z$1.preprocess((value) => value == null ? void 0 : value, z$1.string().optional()),
12770
- rect: z$1.preprocess((value) => value == null ? void 0 : value, rectSchema.optional()),
12771
- hotspot: z$1.preprocess((value) => value == null ? void 0 : value, hotspotSchema.optional()),
12772
- rotation: z$1.preprocess((value) => value == null ? void 0 : value, rotationSchema.optional())
12773
- });
12774
- const transformSchema = z$1.object({
12775
- aspectCrops: z$1.preprocess((value) => {
12776
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
12777
- return value;
12778
- }, z$1.record(z$1.string(), aspectCropSchema.catch({}))).optional(),
12779
- rect: rectSchema.optional(),
12780
- hotspot: hotspotSchema.optional(),
12781
- rotation: rotationSchema.optional()
12782
- });
12783
- const mediaPosterSchema = z$1.discriminatedUnion("tag", [z$1.object({
12784
- tag: z$1.literal("storage"),
12785
- assetId: z$1.string().optional(),
12786
- storageBucket: z$1.string().min(1),
12787
- storagePath: z$1.string().min(1)
12788
- }), z$1.object({
12789
- tag: z$1.literal("external-src"),
12790
- assetId: z$1.string().optional(),
12791
- src: z$1.string().url()
12792
- })]);
12793
- const mediaBaseSchema = z$1.object({
12794
- purpose: z$1.string().optional(),
12795
- placeholder: z$1.boolean().default(true).optional(),
12796
- assetId: z$1.string().optional(),
12797
- identifier: z$1.string().optional(),
12798
- src: z$1.string().optional(),
12799
- alt: z$1.string().optional(),
12800
- filename: z$1.string().optional(),
12801
- mimeType: z$1.string().optional(),
12802
- width: z$1.number().optional(),
12803
- height: z$1.number().optional(),
12804
- storageBucket: z$1.string().optional(),
12805
- storagePath: z$1.string().optional(),
12806
- poster: mediaPosterSchema.optional(),
12807
- transform: transformSchema.optional()
12808
- });
12809
- const mediaIdentifierSchema = z$1.string().trim().min(1).brand();
12810
- const imageMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("image") });
12811
- const videoMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("video") });
12812
- const audioMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("audio") });
12813
- const documentMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("document") });
12814
- const spreadsheetMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("spreadsheet") });
12815
- const archiveMediaSchema = mediaBaseSchema.extend({ type: z$1.literal("archive") });
12816
- const mediaSchema = z$1.discriminatedUnion("type", [
12817
- imageMediaSchema,
12818
- videoMediaSchema,
12819
- audioMediaSchema,
12820
- documentMediaSchema,
12821
- spreadsheetMediaSchema,
12822
- archiveMediaSchema
12823
- ]);
12824
- const persistableMediaBaseSchema = mediaBaseSchema.extend({ identifier: mediaIdentifierSchema });
12825
- z$1.discriminatedUnion("type", [
12826
- persistableMediaBaseSchema.extend({ type: z$1.literal("image") }),
12827
- persistableMediaBaseSchema.extend({ type: z$1.literal("video") }),
12828
- persistableMediaBaseSchema.extend({ type: z$1.literal("audio") }),
12829
- persistableMediaBaseSchema.extend({ type: z$1.literal("document") }),
12830
- persistableMediaBaseSchema.extend({ type: z$1.literal("spreadsheet") }),
12831
- persistableMediaBaseSchema.extend({ type: z$1.literal("archive") })
12832
- ]);
12833
13592
  //#endregion
12834
13593
  //#region ../theme-core/src/palette/schema.ts
12835
13594
  const colourHexString = z$1.string().regex(/^#([0-9A-Fa-f]{3}){1,2}$/);
@@ -15385,112 +16144,6 @@ function affordanceIconMaskUrl(iconId) {
15385
16144
  return `data:image/svg+xml,${`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>${paths.map((d) => `<path d='${d}'/>`).join("")}</svg>`.replace(/"/g, "'").replace(/#/g, "%23").replace(/</g, "%3C").replace(/>/g, "%3E").replace(/\s+/g, " ")}`;
15386
16145
  }
15387
16146
  //#endregion
15388
- //#region ../theme-core/src/utils/tokenOrColor.ts
15389
- /**
15390
- * Runtime color-reference fields intentionally accept any string, including
15391
- * the legacy empty-string "no color" sentinel. Semantic validation needs
15392
- * palette context and happens in `parseThemeColorReference`.
15393
- */
15394
- const tokenOrCssColorValueSchema = z$1.string();
15395
- const CSS_COLOR_FUNCTION_PATTERN = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\(/i;
15396
- const CSS_HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
15397
- const CSS_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
15398
- "transparent",
15399
- "currentcolor",
15400
- "inherit",
15401
- "initial",
15402
- "revert",
15403
- "revert-layer",
15404
- "unset"
15405
- ]);
15406
- const COMMON_CSS_NAMED_COLORS = /* @__PURE__ */ new Set([
15407
- "black",
15408
- "blue",
15409
- "gray",
15410
- "green",
15411
- "grey",
15412
- "orange",
15413
- "pink",
15414
- "purple",
15415
- "red",
15416
- "white",
15417
- "yellow"
15418
- ]);
15419
- function isCssColorLiteral(value) {
15420
- const trimmed = value.trim();
15421
- if (!trimmed) return false;
15422
- return CSS_HEX_COLOR_PATTERN.test(trimmed) || CSS_COLOR_FUNCTION_PATTERN.test(trimmed) || /^var\(/i.test(trimmed) || CSS_COLOR_KEYWORDS.has(trimmed.toLowerCase());
15423
- }
15424
- function parseThemeToken(token) {
15425
- const trimmed = token.trim();
15426
- const match = trimmed.match(/^(.*?)-(\d{2,3})$/);
15427
- if (!match) return {
15428
- role: trimmed,
15429
- shade: null
15430
- };
15431
- const role = match[1] ?? trimmed;
15432
- const shade = match[2] ? Number(match[2]) : null;
15433
- return {
15434
- role,
15435
- shade: Number.isFinite(shade) ? shade : null
15436
- };
15437
- }
15438
- function parseContextThemeColorToken(value, context) {
15439
- const parts = parseColorTokenParts(value);
15440
- if (parts.kind !== "token") return null;
15441
- if (!(Boolean(context?.palette?.[value.trim()]) || parts.opacityPercent !== null && Boolean(context?.palette?.[parts.token]))) return null;
15442
- return value.trim();
15443
- }
15444
- function isTokenShapedTypo(value) {
15445
- if (COMMON_CSS_NAMED_COLORS.has(value.toLowerCase())) return false;
15446
- if (!/^[a-zA-Z][a-zA-Z0-9]*(?:-[a-zA-Z0-9]+)*(?:\/[^/]+)?$/.test(value)) return false;
15447
- return !isCssColorLiteral(value);
15448
- }
15449
- function warnUnknownThemeColorReference(value) {
15450
- if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
15451
- if (!isTokenShapedTypo(value)) return;
15452
- console.warn(`[theme-core] Unknown Theme V2 color token "${value}" treated as literal CSS. Use raw:* for deliberate custom CSS values.`);
15453
- }
15454
- function parseThemeColorReference(input, context) {
15455
- const trimmed = input.trim();
15456
- if (trimmed.startsWith("raw:")) return {
15457
- kind: "literal-css",
15458
- value: trimmed.slice(4)
15459
- };
15460
- const contextToken = parseContextThemeColorToken(trimmed, context);
15461
- if (contextToken) return {
15462
- kind: "palette-context-token",
15463
- token: contextToken
15464
- };
15465
- const knownToken = parseThemeColorToken(trimmed);
15466
- if (knownToken) return {
15467
- kind: "theme-token",
15468
- token: knownToken
15469
- };
15470
- warnUnknownThemeColorReference(trimmed);
15471
- return {
15472
- kind: "literal-css",
15473
- value: trimmed
15474
- };
15475
- }
15476
- function resolveThemeColorReferenceValue(input, context) {
15477
- const reference = parseThemeColorReference(input, context);
15478
- return reference.kind === "literal-css" ? reference.value : rgbColorValue(reference.token);
15479
- }
15480
- function resolveTokenOrCssColorValue(input, context) {
15481
- return resolveThemeColorReferenceValue(input, context);
15482
- }
15483
- function resolveOnColorForThemeToken(token) {
15484
- if (!parseThemeColorToken(token)) return null;
15485
- const parts = parseColorTokenParts(token);
15486
- if (parts.kind !== "token") return null;
15487
- const { role, shade } = parseThemeToken(parts.token);
15488
- const normalizedRole = role.trim();
15489
- if (!normalizedRole) return null;
15490
- if (!shade) return rgbColorValue(`on${normalizedRole.charAt(0).toUpperCase() + normalizedRole.slice(1)}`);
15491
- return shade <= 400 ? "#000000" : "#FFFFFF";
15492
- }
15493
- //#endregion
15494
16147
  //#region ../theme-core/src/buttons/buttonSurfaceContext.ts
15495
16148
  /**
15496
16149
  * Derive only what the rendered section can honestly prove about its backing.
@@ -15892,7 +16545,7 @@ const LETTER_SPACING_MAP = {
15892
16545
  normal: "0",
15893
16546
  loose: "0.05em"
15894
16547
  };
15895
- function rule$1(selector, declarations) {
16548
+ function rule$2(selector, declarations) {
15896
16549
  return {
15897
16550
  selectors: typeof selector === "string" ? [selector] : selector,
15898
16551
  declarations
@@ -16075,7 +16728,7 @@ function variantAffordanceRule(variant, selector) {
16075
16728
  const maskUrl = affordanceIconMaskUrl(affordance.iconId);
16076
16729
  if (!maskUrl) return null;
16077
16730
  const mask = `url("${maskUrl}") no-repeat center / contain`;
16078
- return rule$1(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
16731
+ return rule$2(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
16079
16732
  ["content", "\"\""],
16080
16733
  ["display", "inline-block"],
16081
16734
  ["width", "1em"],
@@ -16114,7 +16767,7 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
16114
16767
  const borderWidth = BORDER_WIDTH_MAP$2[variant.border.widthClass] || "1px";
16115
16768
  decls.push(["border", `${borderWidth} solid currentColor`]);
16116
16769
  }
16117
- return rule$1(selector, decls);
16770
+ return rule$2(selector, decls);
16118
16771
  }
16119
16772
  /**
16120
16773
  * Resolve a colour for a button that does not own its own background.
@@ -16125,13 +16778,13 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
16125
16778
  */
16126
16779
  function variantStateColourRule(variantId, themeId, state, colour) {
16127
16780
  const baseSelector = `:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}`;
16128
- return rule$1(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
16781
+ return rule$2(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
16129
16782
  role: variantId,
16130
16783
  state
16131
16784
  }));
16132
16785
  }
16133
16786
  function variantFocusVisibleRule(variantId, themeId, focusIndicator) {
16134
- return rule$1(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
16787
+ return rule$2(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
16135
16788
  }
16136
16789
  function variantEffectsCss(variant, effects, themeId, tokens, theme) {
16137
16790
  if (!effects) return "";
@@ -16157,10 +16810,10 @@ function variantDisabledRules(variantId, themeId, colour) {
16157
16810
  `${base}:focus`
16158
16811
  ];
16159
16812
  const pseudoSelectors = [`${base}::before`, `${base}::after`];
16160
- return [rule$1(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
16813
+ return [rule$2(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
16161
16814
  role: variantId,
16162
16815
  state: "disabled"
16163
- }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$1(pseudoSelectors, [
16816
+ }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$2(pseudoSelectors, [
16164
16817
  ["opacity", "0"],
16165
16818
  ["animation", "none"],
16166
16819
  ["pointer-events", "none"]
@@ -16175,7 +16828,7 @@ function variantSizeRules(variant, variantId, themeSizes, themeId) {
16175
16828
  const decls = [];
16176
16829
  for (const decl of paddingDeclarations(config.padding, borderWidth)) decls.push(decl);
16177
16830
  if (config.fontSize) decls.push(["font-size", FONT_SIZE_MAP[config.fontSize] || "1rem"]);
16178
- rules.push(rule$1(selector, decls));
16831
+ rules.push(rule$2(selector, decls));
16179
16832
  }
16180
16833
  return rules;
16181
16834
  }
@@ -17076,26 +17729,6 @@ function getComponentShadowVar(elevation) {
17076
17729
  return `var(--shadow-${elevation})`;
17077
17730
  }
17078
17731
  //#endregion
17079
- //#region ../theme-core/src/site-styles/rawStyleFields.ts
17080
- const themeV2RuntimeStyleFieldNames = [
17081
- "_frameStyles",
17082
- "_sectionStyles",
17083
- "_containerStyles",
17084
- "_cardStyles",
17085
- "_transitionStyles",
17086
- "styleGroup"
17087
- ];
17088
- const themeV2BlockSpecificRawStyleFieldNames = [
17089
- "_contentCardStyles",
17090
- "_heroLayoutStyles",
17091
- "_textCardStyles"
17092
- ];
17093
- const themeV2NormalEditorRawStyleFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
17094
- new Set(themeV2NormalEditorRawStyleFieldNames);
17095
- [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
17096
- const pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
17097
- const siteChromeBackgroundOverrideOwner = "site-chrome-editor";
17098
- //#endregion
17099
17732
  //#region ../theme-core/src/header/legacyHeaderVariant.ts
17100
17733
  /**
17101
17734
  * The header variants the system actually renders.
@@ -17304,75 +17937,6 @@ const headerContainerSchema = z$1.object({
17304
17937
  opacity: z$1.number().min(0).max(1).default(.12)
17305
17938
  }).optional();
17306
17939
  //#endregion
17307
- //#region ../theme-core/src/boxBackgroundSchema.ts
17308
- const boxBackgroundOverlaySchema = z$1.preprocess(normalizeAuthoringOpacity, z$1.discriminatedUnion("type", [
17309
- z$1.object({ type: z$1.literal("none") }),
17310
- z$1.object({
17311
- type: z$1.literal("color"),
17312
- color: tokenOrCssColorValueSchema.nullable().optional(),
17313
- opacity: z$1.number().min(0).max(1).nullable().optional()
17314
- }),
17315
- z$1.object({
17316
- type: z$1.literal("gradient"),
17317
- gradient: z$1.string().nullable().optional(),
17318
- opacity: z$1.number().min(0).max(1).nullable().optional()
17319
- })
17320
- ]));
17321
- const commonSchema = z$1.object({
17322
- contentOwner: z$1.literal(pageDesignEditorBackgroundOverrideOwner).optional(),
17323
- textColor: tokenOrCssColorValueSchema.nullable().optional(),
17324
- headingColor: tokenOrCssColorValueSchema.nullable().optional(),
17325
- linkColor: tokenOrCssColorValueSchema.nullable().optional(),
17326
- supportingColor: tokenOrCssColorValueSchema.nullable().optional()
17327
- });
17328
- const mediaPresentationSchema = commonSchema.extend({
17329
- opacity: z$1.preprocess(normalizeOpacity, z$1.number().min(0).max(1).nullable()).optional(),
17330
- objectFit: z$1.enum([
17331
- "fill",
17332
- "fit",
17333
- "original",
17334
- "custom"
17335
- ]).nullable().optional(),
17336
- scale: z$1.string().nullable().optional(),
17337
- position: z$1.string().nullable().optional(),
17338
- motion: z$1.enum(["static", "fixed-frame"]).nullable().optional(),
17339
- overflow: z$1.boolean().nullable().optional(),
17340
- overlay: boxBackgroundOverlaySchema.nullable().optional()
17341
- });
17342
- const boxBackgroundSchema = z$1.discriminatedUnion("type", [
17343
- commonSchema.extend({
17344
- type: z$1.literal("color"),
17345
- color: tokenOrCssColorValueSchema.nullable().optional()
17346
- }),
17347
- commonSchema.extend({
17348
- type: z$1.literal("gradient"),
17349
- gradient: z$1.string().nullable().optional(),
17350
- gradientPresetId: z$1.string().min(1).nullable().optional()
17351
- }),
17352
- mediaPresentationSchema.extend({
17353
- type: z$1.literal("image"),
17354
- image: imageMediaSchema.nullable().optional()
17355
- }),
17356
- mediaPresentationSchema.extend({
17357
- type: z$1.literal("video"),
17358
- image: videoMediaSchema.nullable().optional(),
17359
- showVideoOnMobile: z$1.boolean().nullable().optional()
17360
- })
17361
- ]);
17362
- function normalizeAuthoringOpacity(value) {
17363
- if (!isRecord$27(value)) return value;
17364
- return {
17365
- ...value,
17366
- opacity: normalizeOpacity(value.opacity)
17367
- };
17368
- }
17369
- function normalizeOpacity(value) {
17370
- return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
17371
- }
17372
- function isRecord$27(value) {
17373
- return typeof value === "object" && value !== null && !Array.isArray(value);
17374
- }
17375
- //#endregion
17376
17940
  //#region ../theme-core/src/schema.ts
17377
17941
  const axesSchema = z$1.object({
17378
17942
  tone: z$1.enum([
@@ -18133,6 +18697,13 @@ z$1.object({
18133
18697
  fields: nonEmptyRuntimeStyleFieldNamesSchema,
18134
18698
  authority: styleAuthoritySchema
18135
18699
  }).strict();
18700
+ asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
18701
+ asPageFlowPresetId("generated-default");
18702
+ asSiteStyleId("site-style:south-west-twerk-school");
18703
+ asAppearancePresetId("hero-brand-scrim-fixed-frame");
18704
+ asLayoutVariantKey("classic");
18705
+ asLayoutVariantKey("micro");
18706
+ asBoundaryOptionId("break:wave");
18136
18707
  //#endregion
18137
18708
  //#region ../theme-core/src/site-styles/runtimeCompiler.ts
18138
18709
  function defineSectionForegroundRuntimeTokens(input) {
@@ -18156,7 +18727,7 @@ function defineComponentRuntimeRules(rules) {
18156
18727
  }
18157
18728
  /**
18158
18729
  * Default design rules translate semantic page-level choices into the current
18159
- * runtime's section background and transition style-group vocabulary.
18730
+ * runtime's section background vocabulary.
18160
18731
  */
18161
18732
  const defaultDesignRuntimeRules = defineDesignRuntimeRules({
18162
18733
  sectionSurfaces: {
@@ -18191,8 +18762,7 @@ const defaultDesignRuntimeRules = defineDesignRuntimeRules({
18191
18762
  color: "surface",
18192
18763
  opacity: .88
18193
18764
  }
18194
- },
18195
- transitionClassName: (transition, fromSurface, toSurface) => transition === "none" ? null : `site-style-transition-${transition}-from-${fromSurface}-to-${toSurface}`
18765
+ }
18196
18766
  });
18197
18767
  /**
18198
18768
  * Default component rules translate frame and item-surface semantics into the
@@ -18246,37 +18816,6 @@ function uniformSectionForeground(token) {
18246
18816
  supportingColor: token
18247
18817
  };
18248
18818
  }
18249
- function compileTransitionRuntime(input) {
18250
- return {
18251
- treatment: input.treatment,
18252
- from: input.from,
18253
- to: input.to,
18254
- cssVars: {
18255
- from: input.from.kind === "surface" && input.from.surface === "hero-image" ? "transparent" : transitionEndpointCssValue(input.from),
18256
- to: transitionEndpointCssValue(input.to)
18257
- }
18258
- };
18259
- }
18260
- function transitionEndpointCssValue(endpoint) {
18261
- switch (endpoint.kind) {
18262
- case "color-token": return themeColorTokenCssValue(endpoint.token);
18263
- case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
18264
- default: return assertNever$13(endpoint);
18265
- }
18266
- }
18267
- function tokenForTransitionSurface(surface) {
18268
- switch (surface) {
18269
- case "base":
18270
- case "hero-image": return "background";
18271
- case "muted-band": return "muted";
18272
- case "accent-band": return "accent";
18273
- case "brand-depth-band": return "primary-800";
18274
- default: return assertNever$13(surface);
18275
- }
18276
- }
18277
- function themeColorTokenCssValue(token) {
18278
- return `rgb(var(--tb-${token}))`;
18279
- }
18280
18819
  //#endregion
18281
18820
  //#region ../theme-core/src/site-styles/curatedSiteStyles.ts
18282
18821
  const SOFT_EARTH_SITE_STYLE_ID_VALUE = "site-style:soft-earth";
@@ -19961,17 +20500,9 @@ const curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id
19961
20500
  function getCuratedSiteStyle(id) {
19962
20501
  return curatedSiteStylesById.get(id) ?? null;
19963
20502
  }
19964
- asPageFlowPresetId("generated-default");
19965
- asSiteStyleId("site-style:south-west-twerk-school");
19966
- asAppearancePresetId("hero-brand-scrim-fixed-frame");
19967
- asLayoutVariantKey("classic");
19968
- asLayoutVariantKey("micro");
19969
- asBoundaryOptionId("break:wave");
19970
- asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
19971
20503
  const blockDesignFallback = {
19972
20504
  sectionSurface: "base",
19973
20505
  contentFrame: plainFrame,
19974
- transitionAfter: "none",
19975
20506
  sectionHeight: "default",
19976
20507
  heroLegibility: "none",
19977
20508
  imageSurfaceTreatment: { kind: "none" },
@@ -19982,7 +20513,7 @@ const blockDesignFallback = {
19982
20513
  accordionIconTreatment: null,
19983
20514
  composition: null
19984
20515
  };
19985
- blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
20516
+ blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
19986
20517
  //#endregion
19987
20518
  //#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
19988
20519
  const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
@@ -20090,35 +20621,6 @@ function jsonEquals(left, right) {
20090
20621
  return true;
20091
20622
  }
20092
20623
  defaultHeaderBrandPreferences.brandDisplay, defaultHeaderBrandPreferences.logoSize, defaultHeaderNavPreferences.typography, defaultHeaderNavPreferences.fontSize, defaultHeaderNavPreferences.interactionPalette;
20093
- function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
20094
- const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
20095
- const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
20096
- if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
20097
- const transition = compileTransitionRuntime({
20098
- treatment: boundary.treatment,
20099
- from: fromSurface,
20100
- to: toSurface
20101
- });
20102
- return {
20103
- ...boundary,
20104
- from: transition.from,
20105
- to: transition.to,
20106
- cssVars: transition.cssVars
20107
- };
20108
- }
20109
- function contentOwnedBackgroundEndpoint(content) {
20110
- const background = readRecord(readRecord(content?._sectionStyles)?.background);
20111
- if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
20112
- const token = parseThemeColorToken(background.color);
20113
- if (!token) return null;
20114
- return {
20115
- kind: "color-token",
20116
- token
20117
- };
20118
- }
20119
- function readRecord(value) {
20120
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
20121
- }
20122
20624
  //#endregion
20123
20625
  //#region ../theme-core/src/site-styles/generatedSchemas.ts
20124
20626
  function uniqueNonEmpty(values) {
@@ -23838,16 +24340,16 @@ const teamMemberFragment = defineFragment({
23838
24340
  //#region ../blocks/src/system/data/linkedContentEntries.ts
23839
24341
  const LINKED_CONTENT_ENTRY_META_KEY = "__rbLinkedContentEntry";
23840
24342
  function getLinkedContentEntryMeta(value) {
23841
- if (!isRecord$25(value)) return null;
24343
+ if (!isRecord$26(value)) return null;
23842
24344
  const candidate = value[LINKED_CONTENT_ENTRY_META_KEY];
23843
24345
  return isLinkedContentEntryMeta(candidate) ? candidate : null;
23844
24346
  }
23845
24347
  function buildLinkedContentEditTarget(input) {
23846
24348
  const { entry, sourceBlockId, sourceBlockKind, itemIndex } = input;
23847
- if (!isRecord$25(entry)) return null;
24349
+ if (!isRecord$26(entry)) return null;
23848
24350
  const meta = getLinkedContentEntryMeta(entry);
23849
24351
  if (!meta || meta.support.kind !== "supported") return null;
23850
- const content = isRecord$25(entry.content) ? entry.content : {};
24352
+ const content = isRecord$26(entry.content) ? entry.content : {};
23851
24353
  return {
23852
24354
  kind: "linked-content-entry",
23853
24355
  entryId: meta.entryId,
@@ -23919,14 +24421,14 @@ function resolvePreviewLinkedEntryInlineFieldType(fieldType) {
23919
24421
  return null;
23920
24422
  }
23921
24423
  function isLinkedContentEntryMeta(value) {
23922
- if (!isRecord$25(value)) return false;
24424
+ if (!isRecord$26(value)) return false;
23923
24425
  if (value.kind !== "linked-content-entry") return false;
23924
24426
  if (typeof value.entryId !== "string") return false;
23925
24427
  if (typeof value.contentTypeKey !== "string") return false;
23926
24428
  if (typeof value.contentTypeLabel !== "string") return false;
23927
24429
  if (typeof value.title !== "string") return false;
23928
24430
  if (!(value.optimisticHash === null || typeof value.optimisticHash === "string")) return false;
23929
- if (!isRecord$25(value.contentType)) return false;
24431
+ if (!isRecord$26(value.contentType)) return false;
23930
24432
  if (!isLinkedEntryFieldSourceArray(value.fieldSources)) return false;
23931
24433
  return isLinkedContentEditSupport(value.support);
23932
24434
  }
@@ -23934,7 +24436,7 @@ function isLinkedEntryFieldSourceArray(value) {
23934
24436
  return Array.isArray(value) && value.every(isLinkedEntryFieldSource);
23935
24437
  }
23936
24438
  function isLinkedEntryFieldSource(value) {
23937
- if (!isRecord$25(value)) return false;
24439
+ if (!isRecord$26(value)) return false;
23938
24440
  if (typeof value.displayPath !== "string") return false;
23939
24441
  switch (value.kind) {
23940
24442
  case "content-field": return typeof value.fieldPath === "string" && (value.fieldType === "text" || value.fieldType === "richText");
@@ -23955,7 +24457,7 @@ function isLinkedEntryInlineFieldReadOnlyReason(value) {
23955
24457
  }
23956
24458
  }
23957
24459
  function isLinkedContentEditSupport(value) {
23958
- if (!isRecord$25(value) || typeof value.kind !== "string") return false;
24460
+ if (!isRecord$26(value) || typeof value.kind !== "string") return false;
23959
24461
  switch (value.kind) {
23960
24462
  case "supported": return true;
23961
24463
  case "unsupported-domain-backed":
@@ -23964,7 +24466,7 @@ function isLinkedContentEditSupport(value) {
23964
24466
  default: return false;
23965
24467
  }
23966
24468
  }
23967
- function isRecord$25(value) {
24469
+ function isRecord$26(value) {
23968
24470
  return typeof value === "object" && value !== null && !Array.isArray(value);
23969
24471
  }
23970
24472
  const inlineTextMarkSchema = z$1.discriminatedUnion("kind", [
@@ -26897,7 +27399,7 @@ function resolveRouteHrefForStage(routeIdValue, routes, stageInput) {
26897
27399
  /**
26898
27400
  * Strict record guard: excludes null, primitives, and arrays.
26899
27401
  */
26900
- function isRecord$24(value) {
27402
+ function isRecord$25(value) {
26901
27403
  return typeof value === "object" && value !== null && !Array.isArray(value);
26902
27404
  }
26903
27405
  /**
@@ -27021,16 +27523,16 @@ const interpolateRichTextTokensOptionsSchema = z$1.object({ tokens: z$1.array(su
27021
27523
  const richTextTokenPattern = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
27022
27524
  function getSiteName(context) {
27023
27525
  const viewModel = context.viewModel;
27024
- if (!isRecord$24(viewModel)) return null;
27526
+ if (!isRecord$25(viewModel)) return null;
27025
27527
  const site = viewModel.site;
27026
- if (isRecord$24(site) && typeof site.title === "string") {
27528
+ if (isRecord$25(site) && typeof site.title === "string") {
27027
27529
  const title = site.title.trim();
27028
27530
  return title.length > 0 ? title : null;
27029
27531
  }
27030
27532
  const root = viewModel.$root;
27031
- if (isRecord$24(root)) {
27533
+ if (isRecord$25(root)) {
27032
27534
  const rootSite = root.site;
27033
- if (isRecord$24(rootSite) && typeof rootSite.title === "string") {
27535
+ if (isRecord$25(rootSite) && typeof rootSite.title === "string") {
27034
27536
  const title = rootSite.title.trim();
27035
27537
  if (title.length > 0) return title;
27036
27538
  }
@@ -27052,7 +27554,7 @@ function replaceTokenText(input, allowedTokens, tokenValues) {
27052
27554
  });
27053
27555
  }
27054
27556
  function visitRichTextNode(node, allowedTokens, tokenValues) {
27055
- if (!isRecord$24(node)) return node;
27557
+ if (!isRecord$25(node)) return node;
27056
27558
  let changed = false;
27057
27559
  const nextNode = { ...node };
27058
27560
  if (nextNode.type === "text" && typeof nextNode.text === "string") {
@@ -27073,7 +27575,7 @@ function visitRichTextNode(node, allowedTokens, tokenValues) {
27073
27575
  return changed ? nextNode : node;
27074
27576
  }
27075
27577
  function interpolateRichTextTokens(value, options, context) {
27076
- if (!isRecord$24(value)) return value;
27578
+ if (!isRecord$25(value)) return value;
27077
27579
  return visitRichTextNode(value, new Set(options.tokens), {
27078
27580
  year: String((/* @__PURE__ */ new Date()).getFullYear()),
27079
27581
  site_name: getSiteName(context)
@@ -28557,11 +29059,11 @@ var UnsafePathSegmentError = class extends Error {
28557
29059
  function assertSafePathSegments(segments) {
28558
29060
  for (const segment of segments) if (typeof segment === "string" && UNSAFE_PATH_SEGMENTS.has(segment)) throw new UnsafePathSegmentError(segment);
28559
29061
  }
28560
- function isRecord$23(value) {
29062
+ function isRecord$24(value) {
28561
29063
  return typeof value === "object" && value !== null && !Array.isArray(value);
28562
29064
  }
28563
29065
  function isPathContainer(value) {
28564
- return Array.isArray(value) || isRecord$23(value);
29066
+ return Array.isArray(value) || isRecord$24(value);
28565
29067
  }
28566
29068
  function getContainerValue(container, segment) {
28567
29069
  if (typeof segment === "number") return Array.isArray(container) ? container[segment] : void 0;
@@ -30314,7 +30816,7 @@ function parseSiteIconStyleGuideVersionId(value) {
30314
30816
  //#endregion
30315
30817
  //#region ../blocks/src/system/icons/value.ts
30316
30818
  const NO_BLOCK_ICON_VALUE = { kind: "none" };
30317
- function isRecord$22(value) {
30819
+ function isRecord$23(value) {
30318
30820
  return typeof value === "object" && value !== null && !Array.isArray(value);
30319
30821
  }
30320
30822
  function parseBlockIconValue(value) {
@@ -30322,7 +30824,7 @@ function parseBlockIconValue(value) {
30322
30824
  kind: "semantic",
30323
30825
  iconId: value
30324
30826
  };
30325
- if (!isRecord$22(value)) return NO_BLOCK_ICON_VALUE;
30827
+ if (!isRecord$23(value)) return NO_BLOCK_ICON_VALUE;
30326
30828
  switch (value.kind) {
30327
30829
  case "none": return NO_BLOCK_ICON_VALUE;
30328
30830
  case "semantic": return isSemanticIconId(value.iconId) ? {
@@ -30395,7 +30897,7 @@ function buildGeneratedBlockIconRenderAssetLookupFromUnknown(value) {
30395
30897
  if (!Array.isArray(value)) return { kind: "invalid_asset_list" };
30396
30898
  const inputs = [];
30397
30899
  for (const [index, item] of value.entries()) {
30398
- if (!isRecord$22(item)) return {
30900
+ if (!isRecord$23(item)) return {
30399
30901
  kind: "invalid_asset_entry",
30400
30902
  index,
30401
30903
  reason: "not_object"
@@ -30512,11 +31014,11 @@ function resolveFeatureCardGeneratedIconSizePx(value) {
30512
31014
  const parsed = parseFeatureCardGeneratedIconSizePx(value);
30513
31015
  return parsed.ok ? parsed.value : unsafeAsFeatureCardGeneratedIconSizePx(FEATURE_CARD_GENERATED_ICON_SIZE_PX.default);
30514
31016
  }
30515
- function isRecord$21(value) {
31017
+ function isRecord$22(value) {
30516
31018
  return typeof value === "object" && value !== null && !Array.isArray(value);
30517
31019
  }
30518
31020
  function toPlainTextFromTipTap(value) {
30519
- if (!isRecord$21(value)) return "";
31021
+ if (!isRecord$22(value)) return "";
30520
31022
  if (typeof value.text === "string") return value.text;
30521
31023
  const content = value.content;
30522
31024
  if (!Array.isArray(content)) return "";
@@ -31481,18 +31983,43 @@ const publicAppointmentServiceSchema = z$1.object({
31481
31983
  pricing: publicPricingSchema.optional(),
31482
31984
  priceAmount: z$1.number().nullable(),
31483
31985
  priceCurrency: z$1.string().nullable(),
31484
- location: publicLocationSchema
31986
+ location: publicLocationSchema,
31987
+ categoryId: z$1.string().nullable().optional().transform((value) => value ?? null)
31485
31988
  });
31486
- const publicAppointmentServicesSchema = z$1.array(publicAppointmentServiceSchema);
31989
+ const publicServiceCategorySchema = z$1.object({
31990
+ id: z$1.string().min(1),
31991
+ name: z$1.string(),
31992
+ description: z$1.string().nullable().optional().transform((value) => value ?? null)
31993
+ });
31994
+ /**
31995
+ * The loader hands over the catalogue `{ services, categories }` (issue 1403).
31996
+ * A bare services array is still accepted and read as a site with no
31997
+ * categories, so callers from before categories keep the flat list.
31998
+ */
31999
+ const publicAppointmentServicesSchema = z$1.union([z$1.array(publicAppointmentServiceSchema).transform((services) => ({
32000
+ services,
32001
+ categories: []
32002
+ })), z$1.object({
32003
+ services: z$1.array(publicAppointmentServiceSchema),
32004
+ categories: z$1.array(publicServiceCategorySchema)
32005
+ })]);
31487
32006
  const EMPTY_MESSAGE = "No appointments are available right now.";
31488
32007
  const ERROR_MESSAGE = "Appointments are temporarily unavailable. Please try again shortly.";
32008
+ const OTHER_GROUP_KEY = "other";
32009
+ const OTHER_GROUP_HEADING = "Other";
31489
32010
  function parseServices(value) {
31490
32011
  const result = publicAppointmentServicesSchema.safeParse(value);
31491
32012
  return result.success ? {
31492
32013
  kind: "valid",
31493
- services: result.data
32014
+ services: result.data.services,
32015
+ categories: result.data.categories
31494
32016
  } : { kind: "invalid" };
31495
32017
  }
32018
+ function readCategoryFilter(value) {
32019
+ if (typeof value !== "string") return null;
32020
+ const trimmed = value.trim();
32021
+ return trimmed.length > 0 ? trimmed : null;
32022
+ }
31496
32023
  function formatAppointmentDuration(durationMinutes) {
31497
32024
  return `${durationMinutes} min`;
31498
32025
  }
@@ -31566,8 +32093,44 @@ function buildRows(services, pathname, servicePathPrefix) {
31566
32093
  detailsHref: servicePathPrefix && service.slug ? `${servicePathPrefix.replace(/\/$/, "")}/${encodeURIComponent(service.slug)}` : null
31567
32094
  }));
31568
32095
  }
32096
+ const EMPTY_VIEW = {
32097
+ kind: "empty",
32098
+ message: EMPTY_MESSAGE
32099
+ };
32100
+ function categoryGroup(category, services, toRows) {
32101
+ return {
32102
+ key: category.id,
32103
+ heading: category.name,
32104
+ intro: category.description,
32105
+ rows: toRows(services)
32106
+ };
32107
+ }
32108
+ function viewFromLayout(layout, toRows) {
32109
+ switch (layout.kind) {
32110
+ case "uncategorized-site": return layout.services.length === 0 ? EMPTY_VIEW : {
32111
+ kind: "items",
32112
+ rows: toRows(layout.services)
32113
+ };
32114
+ case "categorized-site": {
32115
+ const groups = layout.groups.map((group) => categoryGroup(group.category, group.services, toRows));
32116
+ const other = layout.other.length === 0 ? [] : [{
32117
+ key: OTHER_GROUP_KEY,
32118
+ heading: OTHER_GROUP_HEADING,
32119
+ intro: null,
32120
+ rows: toRows(layout.other)
32121
+ }];
32122
+ const allGroups = [...groups, ...other];
32123
+ return allGroups.length === 0 ? EMPTY_VIEW : {
32124
+ kind: "groups",
32125
+ groups: allGroups
32126
+ };
32127
+ }
32128
+ default: return assertNever$21(layout);
32129
+ }
32130
+ }
31569
32131
  function buildAppointmentServicesView(input) {
31570
- if (!input.pathname) return {
32132
+ const pathname = input.pathname;
32133
+ if (!pathname) return {
31571
32134
  kind: "error",
31572
32135
  message: ERROR_MESSAGE
31573
32136
  };
@@ -31576,76 +32139,128 @@ function buildAppointmentServicesView(input) {
31576
32139
  kind: "error",
31577
32140
  message: ERROR_MESSAGE
31578
32141
  };
31579
- if (parsed.services.length === 0) return {
31580
- kind: "empty",
31581
- message: EMPTY_MESSAGE
31582
- };
31583
- return {
31584
- kind: "items",
31585
- rows: buildRows(parsed.services, input.pathname, input.servicePathPrefix ?? null)
31586
- };
32142
+ const toRows = (services) => buildRows(services, pathname, input.servicePathPrefix ?? null);
32143
+ const view = selectCategoryView(parsed.categories, parsed.services, readCategoryFilter(input.categoryId));
32144
+ switch (view.kind) {
32145
+ case "single": return view.group.services.length === 0 ? EMPTY_VIEW : {
32146
+ kind: "groups",
32147
+ groups: [categoryGroup(view.group.category, view.group.services, toRows)]
32148
+ };
32149
+ case "all":
32150
+ case "fallback-all": return viewFromLayout(view.layout, toRows);
32151
+ default: return assertNever$21(view);
32152
+ }
32153
+ }
32154
+ function AppointmentServiceRows({ rows, RowTitle }) {
32155
+ return /* @__PURE__ */ jsx("div", {
32156
+ className: "appointment-services-rows rb-flex rb-flex-col",
32157
+ children: rows.map((row) => /* @__PURE__ */ jsxs("article", {
32158
+ className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
32159
+ children: [
32160
+ /* @__PURE__ */ jsxs("div", {
32161
+ className: "appointment-services-row-main rb-min-w-0",
32162
+ children: [
32163
+ /* @__PURE__ */ jsx(RowTitle, {
32164
+ className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
32165
+ children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
32166
+ href: row.detailsHref,
32167
+ children: row.title
32168
+ }) : row.title
32169
+ }),
32170
+ row.description ? /* @__PURE__ */ jsx("p", {
32171
+ className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
32172
+ children: row.description
32173
+ }) : null,
32174
+ /* @__PURE__ */ jsxs("p", {
32175
+ className: "appointment-services-row-meta rb-caption status-muted",
32176
+ children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
32177
+ })
32178
+ ]
32179
+ }),
32180
+ row.priceLabel ? /* @__PURE__ */ jsx("span", {
32181
+ className: "appointment-services-row-price rb-text-lg",
32182
+ children: row.priceLabel
32183
+ }) : null,
32184
+ /* @__PURE__ */ jsx(ThemedButtonLink, {
32185
+ variant: "secondary",
32186
+ size: "sm",
32187
+ extraClassName: "appointment-services-row-action",
32188
+ href: row.href,
32189
+ label: "Check times",
32190
+ screenReaderSuffix: ` for ${row.title}`
32191
+ })
32192
+ ]
32193
+ }, row.id))
32194
+ });
31587
32195
  }
31588
- function AppointmentServicesNode({ sectionHeading, services, pathname, servicePathPrefix, className }) {
32196
+ function AppointmentServiceGroups({ groups, hasSectionHeading, idPrefix }) {
32197
+ const GroupHeading = hasSectionHeading ? "h3" : "h2";
32198
+ const RowTitle = hasSectionHeading ? "h4" : "h3";
32199
+ return /* @__PURE__ */ jsx("div", {
32200
+ className: "appointment-services-groups rb-flex rb-flex-col rb-gap-10",
32201
+ children: groups.map((group) => {
32202
+ const headingId = `${idPrefix}-group-${group.key}`;
32203
+ return /* @__PURE__ */ jsxs("section", {
32204
+ className: "appointment-services-group rb-flex rb-flex-col rb-gap-2",
32205
+ "aria-labelledby": headingId,
32206
+ "data-appointment-services-group": group.key,
32207
+ children: [/* @__PURE__ */ jsxs("div", {
32208
+ className: "appointment-services-group-heading",
32209
+ children: [/* @__PURE__ */ jsx(GroupHeading, {
32210
+ id: headingId,
32211
+ className: "appointment-services-group-title rb-text-sm rb-font-semibold rb-uppercase rb-tracking-wide",
32212
+ children: group.heading
32213
+ }), group.intro ? /* @__PURE__ */ jsx("p", {
32214
+ className: "appointment-services-group-intro rb-text-sm rb-leading-6 rb-max-w-prose status-muted",
32215
+ children: group.intro
32216
+ }) : null]
32217
+ }), /* @__PURE__ */ jsx(AppointmentServiceRows, {
32218
+ rows: group.rows,
32219
+ RowTitle
32220
+ })]
32221
+ }, group.key);
32222
+ })
32223
+ });
32224
+ }
32225
+ function AppointmentServicesNode({ sectionHeading, categoryId, services, pathname, servicePathPrefix, blockId, className }) {
31589
32226
  const view = buildAppointmentServicesView({
31590
32227
  services,
32228
+ categoryId,
31591
32229
  pathname: typeof pathname === "string" ? pathname : void 0,
31592
32230
  servicePathPrefix: typeof servicePathPrefix === "string" ? servicePathPrefix : null
31593
32231
  });
31594
32232
  const rootClassName = `appointment-services ${className ?? ""}`.trim();
31595
- const RowTitle = sectionHeading ? "h3" : "h2";
31596
- if (view.kind === "error" || view.kind === "empty") return /* @__PURE__ */ jsx("div", {
31597
- className: rootClassName,
31598
- "data-appointment-services-state": view.kind,
31599
- children: /* @__PURE__ */ jsx("p", {
31600
- className: "rb-caption status-muted",
31601
- role: "status",
31602
- children: view.message
31603
- })
31604
- });
31605
- return /* @__PURE__ */ jsx("div", {
31606
- className: `${rootClassName} rb-flex rb-flex-col`,
31607
- "data-appointment-services-state": "populated",
31608
- children: /* @__PURE__ */ jsx("div", {
31609
- className: "appointment-services-rows rb-flex rb-flex-col",
31610
- children: view.rows.map((row) => /* @__PURE__ */ jsxs("article", {
31611
- className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
31612
- children: [
31613
- /* @__PURE__ */ jsxs("div", {
31614
- className: "appointment-services-row-main rb-min-w-0",
31615
- children: [
31616
- /* @__PURE__ */ jsx(RowTitle, {
31617
- className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
31618
- children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
31619
- href: row.detailsHref,
31620
- children: row.title
31621
- }) : row.title
31622
- }),
31623
- row.description ? /* @__PURE__ */ jsx("p", {
31624
- className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
31625
- children: row.description
31626
- }) : null,
31627
- /* @__PURE__ */ jsxs("p", {
31628
- className: "appointment-services-row-meta rb-caption status-muted",
31629
- children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
31630
- })
31631
- ]
31632
- }),
31633
- row.priceLabel ? /* @__PURE__ */ jsx("span", {
31634
- className: "appointment-services-row-price rb-text-lg",
31635
- children: row.priceLabel
31636
- }) : null,
31637
- /* @__PURE__ */ jsx(ThemedButtonLink, {
31638
- variant: "secondary",
31639
- size: "sm",
31640
- extraClassName: "appointment-services-row-action",
31641
- href: row.href,
31642
- label: "Check times",
31643
- screenReaderSuffix: ` for ${row.title}`
31644
- })
31645
- ]
31646
- }, row.id))
31647
- })
31648
- });
32233
+ const hasSectionHeading = Boolean(sectionHeading);
32234
+ switch (view.kind) {
32235
+ case "error":
32236
+ case "empty": return /* @__PURE__ */ jsx("div", {
32237
+ className: rootClassName,
32238
+ "data-appointment-services-state": view.kind,
32239
+ children: /* @__PURE__ */ jsx("p", {
32240
+ className: "rb-caption status-muted",
32241
+ role: "status",
32242
+ children: view.message
32243
+ })
32244
+ });
32245
+ case "items": return /* @__PURE__ */ jsx("div", {
32246
+ className: `${rootClassName} rb-flex rb-flex-col`,
32247
+ "data-appointment-services-state": "populated",
32248
+ children: /* @__PURE__ */ jsx(AppointmentServiceRows, {
32249
+ rows: view.rows,
32250
+ RowTitle: hasSectionHeading ? "h3" : "h2"
32251
+ })
32252
+ });
32253
+ case "groups": return /* @__PURE__ */ jsx("div", {
32254
+ className: `${rootClassName} rb-flex rb-flex-col`,
32255
+ "data-appointment-services-state": "populated",
32256
+ children: /* @__PURE__ */ jsx(AppointmentServiceGroups, {
32257
+ groups: view.groups,
32258
+ hasSectionHeading,
32259
+ idPrefix: `appointment-services-${blockId ?? "block"}`
32260
+ })
32261
+ });
32262
+ default: return assertNever$21(view);
32263
+ }
31649
32264
  }
31650
32265
  //#endregion
31651
32266
  //#region ../blocks/src/contracts/runtime.ts
@@ -31700,7 +32315,7 @@ function buildInteractiveIslandProps(options) {
31700
32315
  render
31701
32316
  };
31702
32317
  }
31703
- function isRecord$19(value) {
32318
+ function isRecord$20(value) {
31704
32319
  return typeof value === "object" && value !== null && !Array.isArray(value);
31705
32320
  }
31706
32321
  function deepEqualJsonValue(left, right) {
@@ -31710,8 +32325,8 @@ function deepEqualJsonValue(left, right) {
31710
32325
  if (left.length !== right.length) return false;
31711
32326
  return left.every((item, index) => deepEqualJsonValue(item, right[index]));
31712
32327
  }
31713
- if (isRecord$19(left) || isRecord$19(right)) {
31714
- if (!isRecord$19(left) || !isRecord$19(right)) return false;
32328
+ if (isRecord$20(left) || isRecord$20(right)) {
32329
+ if (!isRecord$20(left) || !isRecord$20(right)) return false;
31715
32330
  const leftKeys = Object.keys(left).sort();
31716
32331
  if (!deepEqualJsonValue(leftKeys, Object.keys(right).sort())) return false;
31717
32332
  return leftKeys.every((key) => deepEqualJsonValue(left[key], right[key]));
@@ -31725,12 +32340,12 @@ function toTrimmedString(value) {
31725
32340
  return trimmed ? trimmed : void 0;
31726
32341
  }
31727
32342
  function normalizeBookingFormConfig(form) {
31728
- if (!isRecord$18(form)) return null;
32343
+ if (!isRecord$19(form)) return null;
31729
32344
  const id = readNonEmptyString(form.id);
31730
32345
  const name = readNonEmptyString(form.name);
31731
32346
  if (!id || !name) return null;
31732
- const schemaSource = isRecord$18(form.schema) ? form.schema : isRecord$18(form.schemaJson) ? form.schemaJson : null;
31733
- const settingsSource = isRecord$18(form.settings) ? form.settings : isRecord$18(form.settingsJson) ? form.settingsJson : null;
32347
+ const schemaSource = isRecord$19(form.schema) ? form.schema : isRecord$19(form.schemaJson) ? form.schemaJson : null;
32348
+ const settingsSource = isRecord$19(form.settings) ? form.settings : isRecord$19(form.settingsJson) ? form.settingsJson : null;
31734
32349
  return {
31735
32350
  id,
31736
32351
  name,
@@ -31750,8 +32365,8 @@ function normalizeBookingServices(services) {
31750
32365
  };
31751
32366
  if (!services) return [];
31752
32367
  if (Array.isArray(services)) return services.filter(isBookingFormService).map(normalizeService);
31753
- if (isRecord$18(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
31754
- return isRecord$18(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
32368
+ if (isRecord$19(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
32369
+ return isRecord$19(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
31755
32370
  }
31756
32371
  function normalizeBookingSchema(value) {
31757
32372
  if (!value) return void 0;
@@ -31762,7 +32377,7 @@ function normalizeBookingSchema(value) {
31762
32377
  };
31763
32378
  }
31764
32379
  function readBookingFormField(value) {
31765
- if (!isRecord$18(value)) return null;
32380
+ if (!isRecord$19(value)) return null;
31766
32381
  const id = readNonEmptyString(value.id);
31767
32382
  const label = readNonEmptyString(value.label);
31768
32383
  const type = readBookingFieldType(value.type);
@@ -31805,7 +32420,7 @@ function readBookingFieldOptions(value) {
31805
32420
  return options.length > 0 ? options : void 0;
31806
32421
  }
31807
32422
  function readBookingFieldOption(value) {
31808
- if (!isRecord$18(value)) return null;
32423
+ if (!isRecord$19(value)) return null;
31809
32424
  const optionValue = readNonEmptyString(value.value);
31810
32425
  const label = readNonEmptyString(value.label);
31811
32426
  return optionValue && label ? {
@@ -31825,7 +32440,7 @@ function normalizeBookingSettings(value) {
31825
32440
  };
31826
32441
  }
31827
32442
  function isBookingFormService(value) {
31828
- return isRecord$18(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
32443
+ return isRecord$19(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
31829
32444
  }
31830
32445
  function readNonEmptyString(value) {
31831
32446
  if (typeof value !== "string") return null;
@@ -31843,9 +32458,34 @@ function readStringArray(value) {
31843
32458
  const strings = value.filter((item) => typeof item === "string");
31844
32459
  return strings.length > 0 ? strings : void 0;
31845
32460
  }
31846
- function isRecord$18(value) {
32461
+ function isRecord$19(value) {
31847
32462
  return typeof value === "object" && value !== null;
31848
32463
  }
32464
+ //#endregion
32465
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-categories.ts
32466
+ function isRecord$18(value) {
32467
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32468
+ }
32469
+ function readCategory(value) {
32470
+ if (!isRecord$18(value)) return [];
32471
+ const { id, name, description } = value;
32472
+ if (typeof id !== "string" || id.trim() === "" || typeof name !== "string") return [];
32473
+ return [{
32474
+ id,
32475
+ name,
32476
+ description: typeof description === "string" ? description : null
32477
+ }];
32478
+ }
32479
+ /**
32480
+ * The ordered service categories carried by a catalogue-shaped services
32481
+ * source (`{ services, categories }`). Every other shape has none, which
32482
+ * reads as a site without categories. Order is preserved: the database owns
32483
+ * it.
32484
+ */
32485
+ function normalizeBookingServiceCategories(services) {
32486
+ if (!isRecord$18(services) || !Array.isArray(services.categories)) return [];
32487
+ return services.categories.flatMap(readCategory);
32488
+ }
31849
32489
  const bookingFieldOptionSchema = z$1.object({
31850
32490
  value: z$1.string(),
31851
32491
  label: z$1.string()
@@ -31922,11 +32562,20 @@ const bookingServiceSchema = z$1.object({
31922
32562
  priceCurrency: z$1.string().nullable().optional(),
31923
32563
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
31924
32564
  paymentTermsAvailability: blockBookingPaymentTermsAvailabilitySchema.optional(),
31925
- consent: blockBookingConsentSchema.optional()
32565
+ consent: blockBookingConsentSchema.optional(),
32566
+ categoryId: z$1.string().nullable().optional()
32567
+ });
32568
+ const bookingServiceCategorySchema = z$1.object({
32569
+ id: z$1.string(),
32570
+ name: z$1.string(),
32571
+ description: z$1.string().nullable()
31926
32572
  });
31927
32573
  const bookingServicesSourceSchema = z$1.union([
31928
32574
  z$1.array(bookingServiceSchema),
31929
- z$1.object({ services: z$1.array(bookingServiceSchema).optional() }),
32575
+ z$1.object({
32576
+ services: z$1.array(bookingServiceSchema).optional(),
32577
+ categories: z$1.array(bookingServiceCategorySchema).optional()
32578
+ }),
31930
32579
  z$1.record(z$1.string(), bookingServiceSchema)
31931
32580
  ]);
31932
32581
  const bookingConfigSchema = z$1.object({
@@ -31979,6 +32628,7 @@ const bookingHydrationSchema = z$1.object({
31979
32628
  rebookContext: z$1.object({ kind: z$1.literal("portal_rebook") }).nullable().optional(),
31980
32629
  form: bookingConfigSchema.nullable(),
31981
32630
  services: z$1.array(bookingServiceSchema),
32631
+ serviceCategories: z$1.array(bookingServiceCategorySchema).optional(),
31982
32632
  spamProtectionEnabled: z$1.boolean().optional()
31983
32633
  });
31984
32634
  const bookingRenderSchema = z$1.object({
@@ -32023,6 +32673,7 @@ const bookingFormBlockRuntime = defineClientOwnedBlockRuntime({
32023
32673
  rebookContext: source.rebookContext ?? null,
32024
32674
  form,
32025
32675
  services,
32676
+ serviceCategories: normalizeBookingServiceCategories(source.services),
32026
32677
  spamProtectionEnabled: source.spamProtectionEnabled
32027
32678
  }
32028
32679
  };
@@ -46395,6 +47046,123 @@ function generateFooterBottomTextLinkStyles(footer, themeId, tokens) {
46395
47046
  return cssChunks.join("\n");
46396
47047
  }
46397
47048
  //#endregion
47049
+ //#region ../theme-core/src/layout/boundaryCss.ts
47050
+ /**
47051
+ * Section-break CSS, generated from the transition catalogue (issue 1422).
47052
+ *
47053
+ * The renderer emits one element per shown join:
47054
+ *
47055
+ * <div data-block-boundary data-boundary-treatment="wave-edge-gentle"
47056
+ * data-boundary-placement="previous"
47057
+ * style="--boundary-shape-paint: …; --boundary-band-paint: …">
47058
+ *
47059
+ * Every selector matches an exact attribute value, so there is no reliance on
47060
+ * prefix ordering in the cascade and nothing reads colours out of a string.
47061
+ * The shape layer is `::after`; the band layer (paint → paint only) is
47062
+ * `::before`.
47063
+ */
47064
+ const waveMaskSvgPaths = {
47065
+ classic: "M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z",
47066
+ balanced: "M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z",
47067
+ gentle: "M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z",
47068
+ bold: "M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z",
47069
+ drift: "M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z"
47070
+ };
47071
+ function waveMaskDataUrl(mask) {
47072
+ return svgDataUrl(`<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='${waveMaskSvgPaths[mask]}' fill='white'/></svg>`);
47073
+ }
47074
+ /**
47075
+ * How each placement anchors the layers against the zero-height boundary
47076
+ * element that sits exactly on the join.
47077
+ *
47078
+ * - `centred` straddles the join, band under shape.
47079
+ * - `previous` sits above the join, inside the previous section.
47080
+ * - `next` sits below the join, inside the next section, with the shape
47081
+ * flipped so it reads as the mirror of `previous`.
47082
+ */
47083
+ const placementRules = {
47084
+ centred: {
47085
+ shape: ["top: 0;", "transform: translateY(-50%);"],
47086
+ band: ["top: 0;", "transform: translateY(-50%);"]
47087
+ },
47088
+ previous: {
47089
+ shape: ["bottom: 0;"],
47090
+ band: null
47091
+ },
47092
+ next: {
47093
+ shape: ["top: 0;", "transform: scaleY(-1);"],
47094
+ band: null
47095
+ }
47096
+ };
47097
+ function generateBoundaryCss(selector) {
47098
+ const boundary = `${selector} [data-block-boundary]`;
47099
+ return [
47100
+ rule$1(boundary, [
47101
+ "position: relative;",
47102
+ "z-index: 3;",
47103
+ "height: 0;",
47104
+ "pointer-events: none;"
47105
+ ]),
47106
+ 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));"]),
47107
+ 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));"]),
47108
+ rule$1(`${boundary}::before,\n${boundary}::after`, [
47109
+ "content: '';",
47110
+ "position: absolute;",
47111
+ "left: 0;",
47112
+ "right: 0;",
47113
+ "height: var(--boundary-height);",
47114
+ "pointer-events: none;"
47115
+ ]),
47116
+ rule$1(`${boundary}::before`, ["z-index: 0;", "background: var(--boundary-band-paint, transparent);"]),
47117
+ rule$1(`${boundary}::after`, ["z-index: 1;"]),
47118
+ ...placementCss(boundary),
47119
+ ...transitionCatalogueEntries.flatMap((entry) => treatmentCss(boundary, entry))
47120
+ ].join("\n\n");
47121
+ }
47122
+ function placementCss(boundary) {
47123
+ return Object.keys(placementRules).flatMap((placement) => {
47124
+ const placed = `${boundary}[data-boundary-placement='${placement}']`;
47125
+ const rules = placementRules[placement];
47126
+ return [rule$1(`${placed}::after`, rules.shape), rules.band ? rule$1(`${placed}::before`, rules.band) : rule$1(`${placed}::before`, ["content: none;"])];
47127
+ });
47128
+ }
47129
+ function treatmentCss(boundary, entry) {
47130
+ const selected = `${boundary}[data-boundary-treatment='${entry.intent}']`;
47131
+ return [rule$1(selected, [`--boundary-height: ${boundaryRuntimeMetricsForDecorativeTransition(entry.intent).heightCss};`]), rule$1(`${selected}::after`, shapeDeclarations(entry))];
47132
+ }
47133
+ function shapeDeclarations(entry) {
47134
+ switch (entry.shape.kind) {
47135
+ case "fade": return [
47136
+ "/* Fallback for browsers without color-mix(). */",
47137
+ "background: linear-gradient(to bottom, transparent 0%, var(--boundary-shape-paint) 100%);",
47138
+ "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%);"
47139
+ ];
47140
+ case "wave": {
47141
+ const mask = waveMaskDataUrl(entry.shape.mask);
47142
+ return [
47143
+ "background: var(--boundary-shape-paint);",
47144
+ `mask-image: url("${mask}");`,
47145
+ "mask-repeat: no-repeat;",
47146
+ "mask-size: 100% 100%;",
47147
+ `-webkit-mask-image: url("${mask}");`,
47148
+ "-webkit-mask-repeat: no-repeat;",
47149
+ "-webkit-mask-size: 100% 100%;"
47150
+ ];
47151
+ }
47152
+ case "angle": return [
47153
+ "height: calc(var(--boundary-height) + 2px);",
47154
+ "background: var(--boundary-shape-paint);",
47155
+ `clip-path: ${anglePolygonCss(entry.shape.polygon)};`
47156
+ ];
47157
+ }
47158
+ }
47159
+ function rule$1(selector, declarations) {
47160
+ return `${selector} {\n ${declarations.join("\n ")}\n}`;
47161
+ }
47162
+ function svgDataUrl(svg) {
47163
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
47164
+ }
47165
+ //#endregion
46398
47166
  //#region ../theme-core/src/layout/generateLayoutCss.ts
46399
47167
  /**
46400
47168
  * Generate layout CSS for theme scope (background, text color)
@@ -46419,7 +47187,7 @@ function generateLayoutCss(options) {
46419
47187
  if (heroTypographyCss) cssChunks.push(heroTypographyCss);
46420
47188
  const bodyTextTypographyCss = generateBodyTextTypographyCss(themeId, theme);
46421
47189
  if (bodyTextTypographyCss) cssChunks.push(bodyTextTypographyCss);
46422
- cssChunks.push(generateSiteStyleTransitionCss(themeId));
47190
+ cssChunks.push(generateBoundaryCss(selector));
46423
47191
  return cssChunks.join("\n\n");
46424
47192
  }
46425
47193
  function toPaddingValue(preset) {
@@ -46430,193 +47198,6 @@ function toPaddingValue(preset) {
46430
47198
  relaxed: "1.5rem"
46431
47199
  }[preset];
46432
47200
  }
46433
- 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>");
46434
- 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>");
46435
- 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>");
46436
- 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>");
46437
- 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>");
46438
- function generateSiteStyleTransitionCss(themeId) {
46439
- const selector = `:where([data-theme-scope="${themeId}"])`;
46440
- const compactBoundaryHeight = requiredBoundaryHeightCss("wave-edge-gentle");
46441
- const mediumBoundaryHeight = requiredBoundaryHeightCss("wave-edge");
46442
- const largeBoundaryHeight = requiredBoundaryHeightCss("wave-edge-bold");
46443
- return `${selector} [data-style-group^='site-style-transition-'] {
46444
- position: relative;
46445
- z-index: 2;
46446
- --site-style-transition-from: transparent;
46447
- --site-style-transition-to: rgb(var(--tb-background));
46448
- }
46449
-
46450
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-'] {
46451
- position: relative;
46452
- z-index: 3;
46453
- height: 0;
46454
- pointer-events: none;
46455
- --site-style-boundary-height: ${mediumBoundaryHeight};
46456
- }
46457
-
46458
- ${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section']) {
46459
- padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));
46460
- }
46461
-
46462
- ${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section']) {
46463
- padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));
46464
- }
46465
-
46466
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::before,
46467
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::before,
46468
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::before {
46469
- content: '';
46470
- position: absolute;
46471
- left: 0;
46472
- right: 0;
46473
- top: 0;
46474
- transform: translateY(-50%);
46475
- z-index: 0;
46476
- height: var(--site-style-boundary-height);
46477
- background: var(--site-style-transition-from);
46478
- pointer-events: none;
46479
- }
46480
-
46481
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after,
46482
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after,
46483
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
46484
- content: '';
46485
- position: absolute;
46486
- left: 0;
46487
- right: 0;
46488
- top: 0;
46489
- transform: translateY(-50%);
46490
- z-index: 1;
46491
- height: var(--site-style-boundary-height);
46492
- pointer-events: none;
46493
- }
46494
-
46495
- ${selector} [data-style-group*='-from-base-'] {
46496
- --site-style-transition-from: rgb(var(--tb-background));
46497
- }
46498
-
46499
- ${selector} [data-style-group*='-from-muted-band-'] {
46500
- --site-style-transition-from: rgb(var(--tb-muted));
46501
- }
46502
-
46503
- ${selector} [data-style-group*='-from-accent-band-'] {
46504
- --site-style-transition-from: rgb(var(--tb-accent));
46505
- }
46506
-
46507
- ${selector} [data-style-group*='-from-brand-depth-band-'] {
46508
- --site-style-transition-from: rgb(var(--tb-primary-800));
46509
- }
46510
-
46511
- ${selector} [data-style-group$='-to-base'],
46512
- ${selector} [data-style-group$='-to-hero-image'] {
46513
- --site-style-transition-to: rgb(var(--tb-background));
46514
- }
46515
-
46516
- ${selector} [data-style-group$='-to-muted-band'] {
46517
- --site-style-transition-to: rgb(var(--tb-muted));
46518
- }
46519
-
46520
- ${selector} [data-style-group$='-to-accent-band'] {
46521
- --site-style-transition-to: rgb(var(--tb-accent));
46522
- }
46523
-
46524
- ${selector} [data-style-group$='-to-brand-depth-band'] {
46525
- --site-style-transition-to: rgb(var(--tb-primary-800));
46526
- }
46527
-
46528
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
46529
- /* Fallback for browsers without color-mix(). */
46530
- background: linear-gradient(
46531
- to bottom,
46532
- transparent 0%,
46533
- var(--site-style-transition-to) 100%
46534
- );
46535
- background: linear-gradient(
46536
- to bottom,
46537
- transparent 0%,
46538
- color-mix(in srgb, var(--site-style-transition-to) 16%, transparent) 34%,
46539
- color-mix(in srgb, var(--site-style-transition-to) 68%, transparent) 78%,
46540
- var(--site-style-transition-to) 100%
46541
- );
46542
- }
46543
-
46544
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-long-'] {
46545
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-long")};
46546
- }
46547
-
46548
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-expansive-'] {
46549
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-expansive")};
46550
- }
46551
-
46552
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after {
46553
- height: calc(var(--site-style-boundary-height) + 2px);
46554
- background: var(--site-style-transition-to);
46555
- clip-path: polygon(0 42%, 100% 8%, 100% 100%, 0 100%);
46556
- }
46557
-
46558
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-'] {
46559
- --site-style-boundary-height: ${compactBoundaryHeight};
46560
- }
46561
-
46562
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-']::after {
46563
- clip-path: polygon(0 34%, 100% 18%, 100% 100%, 0 100%);
46564
- }
46565
-
46566
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-'] {
46567
- --site-style-boundary-height: ${largeBoundaryHeight};
46568
- }
46569
-
46570
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-']::after {
46571
- clip-path: polygon(0 54%, 100% 0%, 100% 100%, 0 100%);
46572
- }
46573
-
46574
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after {
46575
- background: var(--site-style-transition-to);
46576
- mask-image: url("${classicWaveTransitionMask}");
46577
- mask-repeat: no-repeat;
46578
- mask-size: 100% 100%;
46579
- -webkit-mask-image: url("${classicWaveTransitionMask}");
46580
- -webkit-mask-repeat: no-repeat;
46581
- -webkit-mask-size: 100% 100%;
46582
- }
46583
-
46584
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-balanced-']::after {
46585
- mask-image: url("${balancedWaveTransitionMask}");
46586
- -webkit-mask-image: url("${balancedWaveTransitionMask}");
46587
- }
46588
-
46589
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-'] {
46590
- --site-style-boundary-height: ${compactBoundaryHeight};
46591
- }
46592
-
46593
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-']::after {
46594
- mask-image: url("${gentleWaveTransitionMask}");
46595
- -webkit-mask-image: url("${gentleWaveTransitionMask}");
46596
- }
46597
-
46598
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-'] {
46599
- --site-style-boundary-height: ${largeBoundaryHeight};
46600
- }
46601
-
46602
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-']::after {
46603
- mask-image: url("${boldWaveTransitionMask}");
46604
- -webkit-mask-image: url("${boldWaveTransitionMask}");
46605
- }
46606
-
46607
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-drift-']::after {
46608
- mask-image: url("${driftWaveTransitionMask}");
46609
- -webkit-mask-image: url("${driftWaveTransitionMask}");
46610
- }`;
46611
- }
46612
- function requiredBoundaryHeightCss(transition) {
46613
- const metrics = boundaryRuntimeMetricsForTransition(transition);
46614
- if (!metrics) throw new Error(`Missing boundary runtime metrics for ${transition}`);
46615
- return metrics.heightCss;
46616
- }
46617
- function svgDataUrl(svg) {
46618
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
46619
- }
46620
47201
  function generateHeroTypographyCss(themeId, theme) {
46621
47202
  const typography = theme.hero?.typography;
46622
47203
  if (!typography) return "";
@@ -46635,24 +47216,23 @@ function generateHeroTypographyCss(themeId, theme) {
46635
47216
  if (microHeadlineRules.length > 0) chunks.push(`${microHeadlineSelector} {\n ${microHeadlineRules.join("\n ")}\n}`);
46636
47217
  const microSubheadlineRules = buildTypographyRules("subheadline", typography.microSubheadlineSize, typography.microSubheadlineLineHeight);
46637
47218
  if (microSubheadlineRules.length > 0) chunks.push(`${microSubheadlineSelector} {\n ${microSubheadlineRules.join("\n ")}\n}`);
46638
- if (typography.responsive?.tablet) {
46639
- const tabletHeadlineRules = buildTypographyRules("headline", typography.responsive.tablet.headlineSize, typography.responsive.tablet.headlineLineHeight);
46640
- const tabletSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.tablet.subheadlineSize, typography.responsive.tablet.subheadlineLineHeight);
46641
- const tabletChunks = [];
46642
- if (tabletHeadlineRules.length > 0) tabletChunks.push(`${headlineSelector} {\n ${tabletHeadlineRules.join("\n ")}\n }`);
46643
- if (tabletSubheadlineRules.length > 0) tabletChunks.push(`${subheadlineSelector} {\n ${tabletSubheadlineRules.join("\n ")}\n }`);
46644
- if (tabletChunks.length > 0) chunks.push(`@media ${MQ_TABLET_ONLY} {\n ${tabletChunks.join("\n\n ")}\n}`);
46645
- }
46646
- if (typography.responsive?.mobile) {
46647
- const mobileHeadlineRules = buildTypographyRules("headline", typography.responsive.mobile.headlineSize, typography.responsive.mobile.headlineLineHeight);
46648
- const mobileSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.mobile.subheadlineSize, typography.responsive.mobile.subheadlineLineHeight);
46649
- const mobileChunks = [];
46650
- if (mobileHeadlineRules.length > 0) mobileChunks.push(`${headlineSelector} {\n ${mobileHeadlineRules.join("\n ")}\n }`);
46651
- if (mobileSubheadlineRules.length > 0) mobileChunks.push(`${subheadlineSelector} {\n ${mobileSubheadlineRules.join("\n ")}\n }`);
46652
- if (mobileChunks.length > 0) chunks.push(`@media ${MQ_SM_DOWN} {\n ${mobileChunks.join("\n\n ")}\n}`);
46653
- }
47219
+ const selectors = {
47220
+ headline: headlineSelector,
47221
+ subheadline: subheadlineSelector
47222
+ };
47223
+ const responsiveChunks = [responsiveHeroTypographyCss(MQ_TABLET_ONLY, selectors, typography.responsive?.tablet), responsiveHeroTypographyCss(MQ_SM_DOWN, selectors, typography.responsive?.mobile)];
47224
+ for (const chunk of responsiveChunks) if (chunk) chunks.push(chunk);
46654
47225
  return chunks.join("\n\n");
46655
47226
  }
47227
+ function responsiveHeroTypographyCss(media, selectors, overrides) {
47228
+ if (!overrides) return null;
47229
+ const headlineRules = buildTypographyRules("headline", overrides.headlineSize, overrides.headlineLineHeight);
47230
+ const subheadlineRules = buildTypographyRules("subheadline", overrides.subheadlineSize, overrides.subheadlineLineHeight);
47231
+ const mediaChunks = [];
47232
+ if (headlineRules.length > 0) mediaChunks.push(`${selectors.headline} {\n ${headlineRules.join("\n ")}\n }`);
47233
+ if (subheadlineRules.length > 0) mediaChunks.push(`${selectors.subheadline} {\n ${subheadlineRules.join("\n ")}\n }`);
47234
+ return mediaChunks.length > 0 ? `@media ${media} {\n ${mediaChunks.join("\n\n ")}\n}` : null;
47235
+ }
46656
47236
  function buildTypographyRules(role, size, lineHeight) {
46657
47237
  const rules = [];
46658
47238
  if (size) rules.push(`font-size: ${toHeroSize(role, size)};`);
@@ -48631,70 +49211,171 @@ function requireThemeResources(resources, blockKind) {
48631
49211
  return resources;
48632
49212
  }
48633
49213
  //#endregion
48634
- //#region ../blocks/src/pageRendererBoundaries.tsx
48635
- const boundaryKey = (previousBlockId, nextBlockId) => `${previousBlockId}\u0000${nextBlockId}`;
48636
- function isSiteStyleTransitionStyleGroup(value) {
48637
- return value?.startsWith("site-style-transition-") === true;
49214
+ //#region ../blocks/src/pageRenderJoins.ts
49215
+ /**
49216
+ * Re-points joins across omitted blocks. Pure over joins.
49217
+ *
49218
+ * Joins are keyed by adjacent block-id pairs, so dropping B from [A, B, C]
49219
+ * removes the A→C join as well as B's own, because no A→C join was ever
49220
+ * resolved. For each surviving pair the original join is kept when the pair
49221
+ * was already adjacent. Across a gap, the treatment comes from the join
49222
+ * entering the omitted run (A→first omitted), falling back to the one leaving
49223
+ * it (last omitted→C). Nothing visual is carried: the survivors are composed
49224
+ * from their own content afterwards.
49225
+ *
49226
+ * `survivors` are compared by identity against `originalBlocks`.
49227
+ */
49228
+ function retargetJoinsAcrossOmissions(input) {
49229
+ const { joins, originalBlocks, survivingBlocks } = input;
49230
+ if (joins.length === 0) return joins;
49231
+ if (originalBlocks.every((block) => survivingBlocks.has(block))) return joins;
49232
+ const byPair = joinsByPair(joins);
49233
+ const retargeted = [];
49234
+ let previousSurvivor = null;
49235
+ let run = [];
49236
+ for (const block of originalBlocks) {
49237
+ if (!survivingBlocks.has(block)) {
49238
+ run.push(block);
49239
+ continue;
49240
+ }
49241
+ if (previousSurvivor?.id && block.id) {
49242
+ const join = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : joinAcrossRun(byPair, previousSurvivor.id, block.id, run);
49243
+ if (join) retargeted.push(join);
49244
+ }
49245
+ previousSurvivor = block;
49246
+ run = [];
49247
+ }
49248
+ return retargeted;
48638
49249
  }
48639
- function boundaryMapForAdjacentBlockIds(boundaries) {
48640
- return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
49250
+ function joinAcrossRun(byPair, previousBlockId, nextBlockId, run) {
49251
+ const firstOmittedId = run[0]?.id;
49252
+ const lastOmittedId = run[run.length - 1]?.id;
49253
+ const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
49254
+ const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
49255
+ const donor = entering ?? exiting;
49256
+ return donor ? {
49257
+ previousBlockId,
49258
+ nextBlockId,
49259
+ treatment: donor.treatment
49260
+ } : void 0;
48641
49261
  }
48642
- function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
48643
- const previousBlockId = previousBlock?.id ?? null;
48644
- const nextBlockId = nextBlock?.id ?? null;
48645
- if (!previousBlockId || !nextBlockId) return null;
48646
- return boundaries.get(boundaryKey(previousBlockId, nextBlockId)) ?? null;
49262
+ /**
49263
+ * The single composition site. For every adjacent pair of survivors with ids,
49264
+ * finds its join and composes it from both survivors' final content: what
49265
+ * each paints with and the frame each draws.
49266
+ */
49267
+ function composePageJoins(input) {
49268
+ const byPair = joinsByPair(input.joins);
49269
+ return input.survivors.flatMap((previous, index) => {
49270
+ const next = input.survivors[index + 1];
49271
+ if (!next || !previous.id || !next.id) return [];
49272
+ const join = byPair.get(pairKey(previous.id, next.id));
49273
+ return [{
49274
+ previousBlockId: previous.id,
49275
+ nextBlockId: next.id,
49276
+ join: join ? composeJoin(join, previous, next) : null
49277
+ }];
49278
+ });
49279
+ }
49280
+ function composeJoin(join, previous, next) {
49281
+ return {
49282
+ boundaryId: pairKey(join.previousBlockId, join.nextBlockId),
49283
+ previousBlockId: join.previousBlockId,
49284
+ nextBlockId: join.nextBlockId,
49285
+ treatment: join.treatment,
49286
+ outcome: composeBoundary(sectionFactsForContent(previous.content), sectionFactsForContent(next.content))
49287
+ };
49288
+ }
49289
+ /** Placement, reserves and paint for a join that draws; null otherwise. */
49290
+ function renderedJoinLayout(join) {
49291
+ if (!join || join.outcome.kind !== "shown") return null;
49292
+ return boundaryLayout(join.treatment, join.outcome.composition);
49293
+ }
49294
+ /** Joins with a missing id on either side cannot sit between two blocks. */
49295
+ function joinsByPair(joins) {
49296
+ return new Map(joins.flatMap((join) => join.previousBlockId && join.nextBlockId ? [[pairKey(join.previousBlockId, join.nextBlockId), join]] : []));
49297
+ }
49298
+ /** Slot lookup key for an adjacent pair, in the one boundary-id scheme. */
49299
+ function joinSlotKey(previousBlockId, nextBlockId) {
49300
+ return pairKey(previousBlockId, nextBlockId);
49301
+ }
49302
+ function pairKey(previousBlockId, nextBlockId) {
49303
+ return makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId));
48647
49304
  }
48648
- function renderBoundaryAfterBlock(input) {
48649
- const previousBlockId = input.block.id;
48650
- const nextBlockId = input.nextBlock?.id ?? null;
49305
+ //#endregion
49306
+ //#region ../blocks/src/pageRendererBoundaries.tsx
49307
+ function joinSlotsByPair(slots) {
49308
+ return new Map(slots.map((slot) => [joinSlotKey(slot.previousBlockId, slot.nextBlockId), slot]));
49309
+ }
49310
+ function joinSlotBetween(previousBlockId, nextBlockId, slots) {
48651
49311
  if (!previousBlockId || !nextBlockId) return null;
48652
- const boundary = input.boundaryByAdjacentBlockIds.get(boundaryKey(previousBlockId, nextBlockId));
48653
- if (!boundary) return null;
48654
- const rendered = /* @__PURE__ */ jsx("div", {
49312
+ return slots.get(joinSlotKey(previousBlockId, nextBlockId)) ?? null;
49313
+ }
49314
+ /**
49315
+ * The markup at one join: the painted boundary element when the join draws,
49316
+ * then whatever `renderJoin` adds. Null when there is nothing to render.
49317
+ */
49318
+ function renderJoinSlot(input) {
49319
+ const { slot } = input;
49320
+ if (!slot) return null;
49321
+ const layout = renderedJoinLayout(slot.join);
49322
+ const painted = slot.join && layout ? /* @__PURE__ */ jsx("div", {
48655
49323
  "aria-hidden": "true",
48656
49324
  "data-block-boundary": "",
48657
- "data-boundary-id": boundary.boundaryId,
48658
- "data-boundary-previous-block-id": boundary.previousBlockId,
48659
- "data-boundary-next-block-id": boundary.nextBlockId,
48660
- "data-style-group": boundary.styleGroup,
48661
- style: boundaryCssVarsStyle(boundary)
48662
- }, `${input.blockKey}:boundary`);
48663
- return input.wrapBoundary ? input.wrapBoundary(boundary, rendered) : rendered;
48664
- }
48665
- function boundaryCssVarsStyle(boundary) {
48666
- return {
48667
- "--site-style-transition-from": boundary.cssVars.from,
48668
- "--site-style-transition-to": boundary.cssVars.to
48669
- };
49325
+ "data-boundary-id": slot.join.boundaryId,
49326
+ "data-boundary-previous-block-id": slot.previousBlockId,
49327
+ "data-boundary-next-block-id": slot.nextBlockId,
49328
+ "data-boundary-treatment": slot.join.treatment,
49329
+ "data-boundary-placement": layout.placement,
49330
+ style: boundaryPaintStyle(layout.paint)
49331
+ }, `${input.blockKey}:boundary`) : null;
49332
+ const extra = input.renderJoin?.({
49333
+ previousBlockId: slot.previousBlockId,
49334
+ nextBlockId: slot.nextBlockId,
49335
+ join: slot.join
49336
+ });
49337
+ if (!painted && (extra === null || extra === void 0)) return null;
49338
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [painted, extra] }, `${input.blockKey}:join`);
48670
49339
  }
48671
- function boundaryReserveCssVarsStyle(input) {
48672
- const before = input.before?.metrics.reserveCss ?? null;
48673
- const after = input.after?.metrics.reserveCss ?? null;
48674
- if (!before && !after) return void 0;
48675
- return {
48676
- ...before ? { "--site-style-boundary-before-reserve": before } : {},
48677
- ...after ? { "--site-style-boundary-after-reserve": after } : {}
48678
- };
49340
+ /**
49341
+ * The one render-edge place a theme token becomes CSS.
49342
+ */
49343
+ function themeTokenCss(token) {
49344
+ return `rgb(var(--tb-${token}))`;
48679
49345
  }
48680
- function transitionCssVarsStyle(content) {
48681
- const transitionStyles = isObjectRecord$1(content._transitionStyles) ? content._transitionStyles : null;
48682
- const cssVars = isObjectRecord$1(transitionStyles?.cssVars) ? transitionStyles.cssVars : null;
48683
- const from = typeof cssVars?.from === "string" ? cssVars.from : null;
48684
- const to = typeof cssVars?.to === "string" ? cssVars.to : null;
48685
- if (!from && !to) return void 0;
49346
+ function boundaryPaintStyle(paint) {
48686
49347
  return {
48687
- ...from ? { "--site-style-transition-from": from } : {},
48688
- ...to ? { "--site-style-transition-to": to } : {}
49348
+ "--boundary-shape-paint": themeTokenCss(paint.shape),
49349
+ ...paint.band ? { "--boundary-band-paint": themeTokenCss(paint.band) } : {}
48689
49350
  };
48690
49351
  }
48691
- function mergeStyleObjects(left, right) {
48692
- if (!left) return right;
48693
- if (!right) return left;
48694
- return {
48695
- ...left,
48696
- ...right
49352
+ /**
49353
+ * Reserve attributes and variables for a block wrapper, from the joins on
49354
+ * either side. A join's `before` reserve sits inside its previous section, so
49355
+ * it pads the bottom of the block above the join; its `after` reserve pads the
49356
+ * top of the block below. Joins that do not draw reserve nothing.
49357
+ */
49358
+ function blockBoundaryReserves(input) {
49359
+ const top = reserveOnSide(input.before, "after");
49360
+ const bottom = reserveOnSide(input.after, "before");
49361
+ const style = {
49362
+ ...top ? { "--site-style-boundary-before-reserve": top.length } : {},
49363
+ ...bottom ? { "--site-style-boundary-after-reserve": bottom.length } : {}
48697
49364
  };
49365
+ return {
49366
+ topPlacement: top?.placement,
49367
+ bottomPlacement: bottom?.placement,
49368
+ style: top || bottom ? style : void 0
49369
+ };
49370
+ }
49371
+ /** The reserve one join needs on one side of itself, if it draws at all. */
49372
+ function reserveOnSide(slot, side) {
49373
+ const layout = renderedJoinLayout(slot?.join ?? null);
49374
+ const length = layout?.reserves[side] ?? null;
49375
+ return layout && length ? {
49376
+ length,
49377
+ placement: layout.placement
49378
+ } : null;
48698
49379
  }
48699
49380
  function pageBlockPosition(index, blockCount) {
48700
49381
  if (blockCount <= 1) return "only";
@@ -53419,6 +54100,15 @@ const appointmentServicesBlockDefinition = {
53419
54100
  required: false,
53420
54101
  ui: { intent: "content" }
53421
54102
  },
54103
+ createSiteOptionSelectField({
54104
+ id: "categoryId",
54105
+ label: "Category",
54106
+ description: "Show one category (with its heading and intro), or leave it on All categories to show everything grouped.",
54107
+ required: false,
54108
+ multiple: false,
54109
+ optionSource: "appointmentServiceCategories",
54110
+ ui: { intent: "content" }
54111
+ }),
53422
54112
  sectionStylesField({
53423
54113
  id: "_sectionStyles",
53424
54114
  label: "Section styles",
@@ -53453,6 +54143,7 @@ const appointmentServicesBlockDefinition = {
53453
54143
  richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
53454
54144
  el("appointment-services", {
53455
54145
  sectionHeading: { $bind: { from: "content.heading" } },
54146
+ categoryId: { $bind: { from: "content.categoryId" } },
53456
54147
  services: { $bind: { from: "data.services" } },
53457
54148
  pathname: { $bind: { from: "$root.pathname" } },
53458
54149
  servicePathPrefix: { $bind: { from: "$root.appointmentServicePathPrefix" } }
@@ -58161,11 +58852,10 @@ function buildPageRenderPlan(input) {
58161
58852
  return {
58162
58853
  entries,
58163
58854
  omitted,
58164
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
58165
- boundaries: input.runtimeBoundaries,
58855
+ runtimeJoins: retargetJoinsAcrossOmissions({
58856
+ joins: input.runtimeJoins ?? [],
58166
58857
  originalBlocks: input.page.blocks,
58167
- survivingBlocks: new Set(entries.map((entry) => entry.block)),
58168
- previewStage: input.previewStage
58858
+ survivingBlocks: new Set(entries.map((entry) => entry.block))
58169
58859
  })
58170
58860
  };
58171
58861
  }
@@ -58267,86 +58957,15 @@ function getBlockOverride(blockKind, overrides) {
58267
58957
  * For draft/preview: draftContent takes priority over content.
58268
58958
  * For published: content is the source of truth.
58269
58959
  */
58960
+ /**
58961
+ * The content a block renders with. Every render path (public, preview,
58962
+ * templates, SDK) chooses content here, so this is also where a stale
58963
+ * `site-style-transition-*` styleGroup is dropped; authored values survive.
58964
+ */
58270
58965
  function getRenderableContent(block, previewStage = "published") {
58271
58966
  const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
58272
- return isObjectRecord$1(source) ? source : {};
58967
+ return isObjectRecord$1(source) ? stripStaleTransitionStyleGroup(source) : {};
58273
58968
  }
58274
- /**
58275
- * Re-points section boundaries across omitted blocks.
58276
- *
58277
- * Boundaries are keyed by adjacent block-id *pairs*, so dropping B from
58278
- * [A, B, C] does not merely remove B's boundaries — it removes the boundary
58279
- * between the survivors, because no `A→C` key was ever compiled.
58280
- *
58281
- * Per decision D2 the treatment for a merged gap comes from the boundary
58282
- * entering the omitted run (`A→firstOmitted`), falling back to the one leaving
58283
- * it (`lastOmitted→C`). The destination surface is a separate question: only
58284
- * the exiting boundary knows what C looks like, so the merged boundary ends on
58285
- * that endpoint rather than on the omitted block's. In the exiting-only
58286
- * fallback the origin remains an approximation: `from` is the omitted block's
58287
- * surface, because no compiled boundary carries A's. The hosted path no longer
58288
- * needs this at all (site-renderer admits blocks before compiling boundaries);
58289
- * it remains the safety net for an SDK whose compiled boundaries predate an
58290
- * omission.
58291
- */
58292
- function retargetBoundariesAcrossOmissions(input) {
58293
- const { boundaries, originalBlocks, survivingBlocks, previewStage } = input;
58294
- if (boundaries === void 0) return void 0;
58295
- if (boundaries.length === 0) return boundaries;
58296
- if (!originalBlocks.some((block) => !survivingBlocks.has(block))) return boundaries;
58297
- const byPair = new Map(boundaries.map((boundary) => [pairKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
58298
- const retargeted = [];
58299
- let previousSurvivor = null;
58300
- let run = [];
58301
- for (const block of originalBlocks) {
58302
- if (!survivingBlocks.has(block)) {
58303
- run.push(block);
58304
- continue;
58305
- }
58306
- if (previousSurvivor?.id && block.id) {
58307
- const boundary = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : mergedBoundary({
58308
- byPair,
58309
- previousBlock: previousSurvivor,
58310
- previousBlockId: previousSurvivor.id,
58311
- nextBlock: block,
58312
- nextBlockId: block.id,
58313
- run,
58314
- previewStage
58315
- });
58316
- if (boundary) retargeted.push(boundary);
58317
- }
58318
- previousSurvivor = block;
58319
- run = [];
58320
- }
58321
- return retargeted;
58322
- }
58323
- function mergedBoundary(input) {
58324
- const { byPair, previousBlock, previousBlockId, nextBlock, nextBlockId, run, previewStage } = input;
58325
- const firstOmittedId = run[0]?.id;
58326
- const lastOmittedId = run[run.length - 1]?.id;
58327
- const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
58328
- const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
58329
- const donor = entering ?? exiting;
58330
- if (!donor) return void 0;
58331
- const transition = compileTransitionRuntime({
58332
- treatment: donor.treatment,
58333
- from: donor.from,
58334
- to: exiting?.to ?? donor.to
58335
- });
58336
- return retargetCompiledBoundaryRuntimeForContentAdjacency({
58337
- ...donor,
58338
- boundaryId: makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId)),
58339
- previousBlockId,
58340
- nextBlockId,
58341
- from: transition.from,
58342
- to: transition.to,
58343
- cssVars: transition.cssVars
58344
- }, {
58345
- currentContent: getRenderableContent(previousBlock, previewStage),
58346
- nextContent: getRenderableContent(nextBlock, previewStage)
58347
- });
58348
- }
58349
- const pairKey = boundaryKey;
58350
58969
  //#endregion
58351
58970
  //#region ../blocks/src/system/manifest/hydrateLinks.ts
58352
58971
  /**
@@ -58381,7 +59000,7 @@ function hydrateResolvedBookingConsentValue(value, context) {
58381
59000
  const hydratedItems = value.map((item) => hydrateResolvedBookingConsentValue(item, context));
58382
59001
  return hydratedItems.every((item, index) => item === value[index]) ? value : hydratedItems;
58383
59002
  }
58384
- if (!isRecord$24(value)) return value;
59003
+ if (!isRecord$25(value)) return value;
58385
59004
  if (isBookingConsentCarrier(value)) {
58386
59005
  const agreement = hydrateValue(value.agreement, context);
58387
59006
  return agreement === value.agreement ? value : {
@@ -58401,7 +59020,7 @@ function hydrateValue(value, context) {
58401
59020
  const resolvedItems = value.map((item) => hydrateValue(item, context));
58402
59021
  return resolvedItems.every((item, index) => item === value[index]) ? value : resolvedItems;
58403
59022
  }
58404
- if (isRecord$24(value)) {
59023
+ if (isRecord$25(value)) {
58405
59024
  if (isInternalLink$1(value)) return hydrateInternalLink(value, context.routes);
58406
59025
  if (isPageLink$1(value)) return hydratePageLink(value, context);
58407
59026
  if (isEntryLink$1(value)) return hydrateEntryLink(value, context);
@@ -58466,7 +59085,7 @@ function hydrateEntryLink(link, context) {
58466
59085
  //#region ../blocks/src/system/manifest/resolveBlockData.ts
58467
59086
  function resolveBlockData$1(blockId, dataLoaders, dataContext, routes) {
58468
59087
  const raw = blockId ? dataContext?.resolvedData?.[blockId] : void 0;
58469
- if (isRecord$24(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
59088
+ if (isRecord$25(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
58470
59089
  return dataLoaders && typeof dataLoaders === "object" && Object.keys(dataLoaders).length > 0 ? {} : void 0;
58471
59090
  }
58472
59091
  //#endregion
@@ -58501,23 +59120,44 @@ function preparePageRender(input) {
58501
59120
  prepared
58502
59121
  });
58503
59122
  }
58504
- if (constructionFailures.length === 0 || input.audience === "authoring") return {
58505
- entries,
58506
- constructionFailures,
58507
- runtimeBoundaries: input.plan.runtimeBoundaries
58508
- };
58509
- const survivors = entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
59123
+ const survivors = input.audience === "authoring" ? entries : entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
58510
59124
  return {
58511
59125
  entries: survivors,
58512
59126
  constructionFailures,
58513
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
58514
- boundaries: input.plan.runtimeBoundaries,
58515
- originalBlocks: input.plan.entries.map((planned) => planned.block),
58516
- survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block)),
58517
- previewStage: input.previewStage
58518
- })
59127
+ joins: composeSurvivorJoins(input.plan, survivors, input.themeResources)
58519
59128
  };
58520
59129
  }
59130
+ /**
59131
+ * The only place joins are composed. Plan-time omissions were already
59132
+ * re-pointed by the plan; construction failures are re-pointed here by the
59133
+ * same primitive, so `[A, B, C]` losing B still gets an A→C join. Each
59134
+ * survivor is then classified from its final content.
59135
+ */
59136
+ function composeSurvivorJoins(plan, survivors, themeResources) {
59137
+ return composePageJoins({
59138
+ joins: retargetJoinsAcrossOmissions({
59139
+ joins: plan.runtimeJoins,
59140
+ originalBlocks: plan.entries.map((planned) => planned.block),
59141
+ survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block))
59142
+ }),
59143
+ survivors: survivors.map(({ entry }) => ({
59144
+ id: entry.block.id,
59145
+ kind: entry.block.kind,
59146
+ content: boundaryClassificationContent(entry, themeResources)
59147
+ }))
59148
+ });
59149
+ }
59150
+ /**
59151
+ * The content a join is classified from must be the content that paints the
59152
+ * section. The default block renderer layers the theme's per-block style
59153
+ * defaults (`theme.blockOverrides[kind].styles`, background included) under
59154
+ * the block's own `_sectionStyles`, so classify that same projection. An
59155
+ * override component renders its own markup from the unmerged content.
59156
+ */
59157
+ function boundaryClassificationContent(entry, themeResources) {
59158
+ if (entry.kind !== "renderable" || entry.override) return entry.renderableContent;
59159
+ return mergeContentWithBlockStyleOverrides(entry.renderableContent, themeResources.kind === "available" ? themeResources.config : void 0, entry.block.kind);
59160
+ }
58521
59161
  function prepareEntry(entry, input) {
58522
59162
  if (entry.kind !== "renderable") return { kind: "plan-decided" };
58523
59163
  const draftContent = previewableEditorContent(entry);
@@ -58706,7 +59346,7 @@ function PageRenderer$1(props) {
58706
59346
  });
58707
59347
  }
58708
59348
  function PageRendererCore(props) {
58709
- const { page, audience, runtimeBoundaries, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
59349
+ const { page, audience, runtimeJoins, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
58710
59350
  const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
58711
59351
  dataContext?.imageResolveContext,
58712
59352
  dataContext?.mediaAssets,
@@ -58722,7 +59362,7 @@ function PageRendererCore(props) {
58722
59362
  blocks: []
58723
59363
  },
58724
59364
  audience,
58725
- runtimeBoundaries,
59365
+ runtimeJoins,
58726
59366
  previewStage,
58727
59367
  sdkConfig,
58728
59368
  customBlocks,
@@ -58730,7 +59370,7 @@ function PageRendererCore(props) {
58730
59370
  }), [
58731
59371
  page,
58732
59372
  audience,
58733
- runtimeBoundaries,
59373
+ runtimeJoins,
58734
59374
  previewStage,
58735
59375
  sdkConfig,
58736
59376
  customBlocks,
@@ -58747,19 +59387,16 @@ function PageRendererCore(props) {
58747
59387
  dataContext,
58748
59388
  routeMap,
58749
59389
  themeResources,
58750
- imageResolveContext,
58751
- previewStage
59390
+ imageResolveContext
58752
59391
  }), [
58753
59392
  plan,
58754
59393
  audience,
58755
59394
  dataContext,
58756
59395
  routeMap,
58757
59396
  themeResources,
58758
- imageResolveContext,
58759
- previewStage
59397
+ imageResolveContext
58760
59398
  ]);
58761
- const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(preparation.runtimeBoundaries ?? []), [preparation.runtimeBoundaries]);
58762
- const usesRuntimeBoundaries = preparation.runtimeBoundaries !== void 0;
59399
+ const joinSlots = React$1.useMemo(() => joinSlotsByPair(preparation.joins), [preparation.joins]);
58763
59400
  reportAndReplayOmissions({
58764
59401
  plan,
58765
59402
  preparation,
@@ -58777,10 +59414,9 @@ function PageRendererCore(props) {
58777
59414
  themeResources,
58778
59415
  imageResolveContext,
58779
59416
  resolvedRegistry,
58780
- boundaryByAdjacentBlockIds,
58781
- usesRuntimeBoundaries,
59417
+ joinSlots,
58782
59418
  wrapBlock: props.wrapBlock,
58783
- wrapBoundary: props.wrapBoundary
59419
+ renderJoin: props.renderJoin
58784
59420
  })) });
58785
59421
  }
58786
59422
  /**
@@ -58869,27 +59505,25 @@ function requireEntryBlockOverride(entry, themeResources) {
58869
59505
  }
58870
59506
  /**
58871
59507
  * Renders one planned block: its wrapper, style-group data attributes,
58872
- * boundary reserve space and any boundary or transition sibling that follows.
59508
+ * boundary reserve space and the join markup that follows it.
58873
59509
  *
58874
59510
  * Extracted from PageRendererCore so the component stays about wiring the plan
58875
59511
  * rather than also owning per-block markup.
58876
59512
  */
58877
59513
  function renderPlanEntry(input) {
58878
- const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, boundaryByAdjacentBlockIds, usesRuntimeBoundaries, wrapBlock, wrapBoundary } = input;
59514
+ const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, joinSlots, wrapBlock, renderJoin } = input;
58879
59515
  const entry = input.entry.entry;
58880
59516
  const block = entry.block;
58881
59517
  const blockPosition = pageBlockPosition(index, entries.length);
58882
59518
  const blockKey = blockKeyFor(block, index);
58883
59519
  const previousBlock = entries[index - 1]?.entry.block ?? null;
58884
59520
  const nextBlock = entries[index + 1]?.entry.block ?? null;
58885
- const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
58886
- const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
58887
- const boundaryElement = renderBoundaryAfterBlock({
58888
- block,
58889
- nextBlock,
59521
+ const joinBefore = joinSlotBetween(previousBlock?.id, block.id, joinSlots);
59522
+ const joinAfter = joinSlotBetween(block.id, nextBlock?.id, joinSlots);
59523
+ const boundaryElement = renderJoinSlot({
59524
+ slot: joinAfter,
58890
59525
  blockKey,
58891
- boundaryByAdjacentBlockIds,
58892
- wrapBoundary
59526
+ renderJoin
58893
59527
  });
58894
59528
  const cleanKind = block.kind.replace(/^block\./, "");
58895
59529
  const managesOwnWrapper = entry.kind === "renderable" && entry.managesOwnWrapper;
@@ -58912,11 +59546,10 @@ function renderPlanEntry(input) {
58912
59546
  const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
58913
59547
  const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
58914
59548
  const blockComposition = readBlockComposition(renderableContent.blockComposition);
58915
- const transitionStyle = transitionCssVarsStyle(renderableContent);
58916
- const blockStyle = mergeStyleObjects(transitionStyle, boundaryReserveCssVarsStyle({
58917
- before: boundaryBefore,
58918
- after: boundaryAfter
58919
- }));
59549
+ const reserves = blockBoundaryReserves({
59550
+ before: joinBefore,
59551
+ after: joinAfter
59552
+ });
58920
59553
  const blockElement = /* @__PURE__ */ jsx("div", {
58921
59554
  className: wrapperClassName,
58922
59555
  "data-block": cleanKind,
@@ -58928,21 +59561,14 @@ function renderPlanEntry(input) {
58928
59561
  "data-block-composition": blockComposition?.treatment,
58929
59562
  "data-block-composition-kind": blockComposition?.kind,
58930
59563
  "data-page-block-position": blockPosition,
58931
- "data-site-style-boundary-before": boundaryBefore?.metrics.size,
58932
- "data-site-style-boundary-after": boundaryAfter?.metrics.size,
59564
+ "data-site-style-boundary-before": reserves.topPlacement,
59565
+ "data-site-style-boundary-after": reserves.bottomPlacement,
58933
59566
  "data-style-group": styleGroup,
58934
- style: blockStyle,
59567
+ style: reserves.style,
58935
59568
  children: /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps })
58936
59569
  }, blockKey);
58937
59570
  if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
58938
- if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
58939
- return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, /* @__PURE__ */ jsx("div", {
58940
- "aria-hidden": "true",
58941
- "data-block-transition": "",
58942
- "data-block-transition-for": block.id ?? blockKey,
58943
- "data-style-group": styleGroup,
58944
- style: transitionStyle
58945
- })] }, blockKey);
59571
+ return blockElement;
58946
59572
  }
58947
59573
  function resolvePageRendererImageResolveContext(dataContext) {
58948
59574
  const input = dataContext?.imageResolveContext;
@@ -72703,7 +73329,7 @@ function toBlocksPageRendererDataContext(dataContext) {
72703
73329
  function toBlocksCommonProps(props) {
72704
73330
  return {
72705
73331
  page: props.page,
72706
- runtimeBoundaries: props.runtimeBoundaries,
73332
+ runtimeJoins: props.runtimeJoins,
72707
73333
  dataContext: props.dataContext ? toBlocksPageRendererDataContext(props.dataContext) : void 0,
72708
73334
  routeMap: props.routeMap,
72709
73335
  wrapBlock: props.wrapBlock,
@@ -72813,10 +73439,10 @@ function buildPageRouteRenderContext(dataContext) {
72813
73439
  };
72814
73440
  }
72815
73441
  function PageContent(props) {
72816
- const { page, runtimeBoundaries, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
73442
+ const { page, runtimeJoins, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
72817
73443
  const rendererCommonProps = {
72818
73444
  page,
72819
- runtimeBoundaries,
73445
+ runtimeJoins,
72820
73446
  dataContext: {
72821
73447
  siteId,
72822
73448
  ...buildPageRouteRenderContext(dataContext),
@@ -75104,6 +75730,75 @@ z$1.object({
75104
75730
  className: z$1.string().nullable().optional()
75105
75731
  });
75106
75732
  //#endregion
75733
+ //#region ../api/src/defineEndpointDefinitions.ts
75734
+ /**
75735
+ * Validates extracted endpoint descriptors while preserving each literal path,
75736
+ * method, and parameter contract for the typed URL builders.
75737
+ */
75738
+ function defineEndpointDefinitions(definitions) {
75739
+ return definitions;
75740
+ }
75741
+ //#endregion
75742
+ //#region ../api/src/publicNewsletterEndpointDefinitions.ts
75743
+ const PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
75744
+ subscribeNewsletter: {
75745
+ path: "/public/sites/{siteId}/newsletter/subscribe",
75746
+ method: "POST",
75747
+ tags: ["public-newsletter-{siteId}"],
75748
+ auth: "public",
75749
+ responseKind: "json"
75750
+ },
75751
+ confirmNewsletter: {
75752
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
75753
+ method: "GET",
75754
+ tags: ["public-newsletter-{siteId}"],
75755
+ auth: "public",
75756
+ responseKind: "text"
75757
+ },
75758
+ unsubscribeNewsletter: {
75759
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
75760
+ method: "GET",
75761
+ tags: ["public-newsletter-{siteId}"],
75762
+ auth: "public",
75763
+ responseKind: "text"
75764
+ },
75765
+ unsubscribeNewsletterPost: {
75766
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
75767
+ method: "POST",
75768
+ tags: ["public-newsletter-{siteId}"],
75769
+ auth: "public",
75770
+ responseKind: "text"
75771
+ },
75772
+ confirmNewsletterPost: {
75773
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
75774
+ method: "POST",
75775
+ tags: ["public-newsletter-{siteId}"],
75776
+ auth: "public",
75777
+ responseKind: "text"
75778
+ },
75779
+ getNewsletterPreferences: {
75780
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
75781
+ method: "GET",
75782
+ tags: ["public-newsletter-{siteId}"],
75783
+ auth: "public",
75784
+ responseKind: "text"
75785
+ },
75786
+ saveNewsletterPreferences: {
75787
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
75788
+ method: "POST",
75789
+ tags: ["public-newsletter-{siteId}"],
75790
+ auth: "public",
75791
+ responseKind: "text"
75792
+ },
75793
+ getNewsletterArchive: {
75794
+ path: "/public/sites/{siteId}/newsletter/archive/{token}",
75795
+ method: "GET",
75796
+ tags: ["public-newsletter-{siteId}"],
75797
+ auth: "public",
75798
+ responseKind: "text"
75799
+ }
75800
+ });
75801
+ //#endregion
75107
75802
  //#region ../api/src/apiEndpointTypes.ts
75108
75803
  function allowedDemoMutationPolicy() {
75109
75804
  return {
@@ -75193,17 +75888,85 @@ const SDK_ENTRY_READ_ENDPOINTS = {
75193
75888
  }
75194
75889
  };
75195
75890
  //#endregion
75196
- //#region ../api/src/defineEndpointDefinitions.ts
75197
- /**
75198
- * Validates extracted endpoint descriptors while preserving each literal path,
75199
- * method, and parameter contract for the typed URL builders.
75200
- */
75201
- function defineEndpointDefinitions(definitions) {
75202
- return definitions;
75203
- }
75204
- //#endregion
75205
75891
  //#region ../api/src/appointmentEndpointDefinitions.ts
75206
75892
  const APPOINTMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
75893
+ ...defineEndpointDefinitions({
75894
+ listGroupedAppointmentServices: {
75895
+ path: "/sites/{siteId}/bookings/services/grouped",
75896
+ method: "GET",
75897
+ auth: "user",
75898
+ responseKind: "json",
75899
+ errors: ["server:internal_error"]
75900
+ },
75901
+ reorderAppointmentServices: {
75902
+ path: "/sites/{siteId}/bookings/services/order",
75903
+ method: "PUT",
75904
+ auth: "user",
75905
+ responseKind: "json",
75906
+ errors: [
75907
+ "validation:invalid_input",
75908
+ "auth:forbidden",
75909
+ "resource:not_found",
75910
+ "resource:conflict",
75911
+ "server:internal_error"
75912
+ ]
75913
+ },
75914
+ listAppointmentServiceCategories: {
75915
+ path: "/sites/{siteId}/bookings/service-categories",
75916
+ method: "GET",
75917
+ auth: "user",
75918
+ responseKind: "json",
75919
+ errors: ["server:internal_error"]
75920
+ },
75921
+ createAppointmentServiceCategory: {
75922
+ path: "/sites/{siteId}/bookings/service-categories",
75923
+ method: "POST",
75924
+ auth: "user",
75925
+ responseKind: "json",
75926
+ errors: [
75927
+ "validation:invalid_input",
75928
+ "auth:forbidden",
75929
+ "resource:conflict",
75930
+ "server:internal_error"
75931
+ ]
75932
+ },
75933
+ updateAppointmentServiceCategory: {
75934
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
75935
+ method: "PUT",
75936
+ auth: "user",
75937
+ responseKind: "json",
75938
+ errors: [
75939
+ "validation:invalid_input",
75940
+ "auth:forbidden",
75941
+ "resource:not_found",
75942
+ "resource:conflict",
75943
+ "server:internal_error"
75944
+ ]
75945
+ },
75946
+ deleteAppointmentServiceCategory: {
75947
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
75948
+ method: "DELETE",
75949
+ auth: "user",
75950
+ responseKind: "json",
75951
+ errors: [
75952
+ "auth:forbidden",
75953
+ "resource:not_found",
75954
+ "server:internal_error"
75955
+ ]
75956
+ },
75957
+ reorderAppointmentServiceCategories: {
75958
+ path: "/sites/{siteId}/bookings/service-categories/order",
75959
+ method: "PUT",
75960
+ auth: "user",
75961
+ responseKind: "json",
75962
+ errors: [
75963
+ "validation:invalid_input",
75964
+ "auth:forbidden",
75965
+ "resource:conflict",
75966
+ "server:internal_error"
75967
+ ]
75968
+ }
75969
+ }),
75207
75970
  listAppointmentResources: {
75208
75971
  path: "/sites/{siteId}/bookings/resources",
75209
75972
  method: "GET",
@@ -76351,39 +77114,6 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
76351
77114
  auth: "user",
76352
77115
  responseKind: "json",
76353
77116
  demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN
76354
- },
76355
- listNewsletterSendJobs: {
76356
- path: "/sites/{siteId}/newsletter/jobs",
76357
- method: "GET",
76358
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
76359
- auth: "user",
76360
- responseKind: "json"
76361
- },
76362
- retryNewsletterSendJob: {
76363
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/retry",
76364
- method: "POST",
76365
- errors: [
76366
- "validation:invalid_input",
76367
- "resource:not_found",
76368
- "auth:forbidden",
76369
- "server:internal_error"
76370
- ],
76371
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
76372
- auth: "user",
76373
- responseKind: "json"
76374
- },
76375
- cancelNewsletterSendJob: {
76376
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/cancel",
76377
- method: "POST",
76378
- errors: [
76379
- "validation:invalid_input",
76380
- "resource:not_found",
76381
- "auth:forbidden",
76382
- "server:internal_error"
76383
- ],
76384
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
76385
- auth: "user",
76386
- responseKind: "json"
76387
77117
  }
76388
77118
  });
76389
77119
  //#endregion
@@ -77149,6 +77879,7 @@ const BILLING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
77149
77879
  }
77150
77880
  });
77151
77881
  ({
77882
+ ...PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS,
77152
77883
  ...ACCOUNT_ENDPOINT_DEFINITIONS,
77153
77884
  ...PUBLIC_ONBOARDING_ATTEMPT_ENDPOINT_DEFINITIONS,
77154
77885
  ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
@@ -78147,7 +78878,12 @@ const appointmentSetupServiceDraftSchema = z$1.object({
78147
78878
  venue: appointmentSetupVenueChoiceSchema,
78148
78879
  timezone: z$1.string().trim().min(1).nullable(),
78149
78880
  payment: appointmentSetupPaymentDraftSchema,
78150
- assignment: appointmentSetupServiceAssignmentDraftSchema
78881
+ assignment: appointmentSetupServiceAssignmentDraftSchema,
78882
+ /**
78883
+ * The service's category, or null for "Other" (issue 1403). Omitted leaves
78884
+ * an existing service's category alone and puts a new one in "Other".
78885
+ */
78886
+ categoryId: z$1.string().uuid().nullable().optional()
78151
78887
  }).superRefine((service, ctx) => {
78152
78888
  if (service.payment.kind !== "free" && (service.priceAmount === null || service.priceAmount <= 0)) ctx.addIssue({
78153
78889
  code: "custom",
@@ -80842,7 +81578,7 @@ function applyBindings$1(bindings, context) {
80842
81578
  if (value !== void 0 && value !== null) setBindingValue(result, key, value);
80843
81579
  continue;
80844
81580
  }
80845
- if (isRecord$24(bindingSpec)) {
81581
+ if (isRecord$25(bindingSpec)) {
80846
81582
  const nested = applyBindings$1(bindingSpec, context);
80847
81583
  if (Object.keys(nested).length > 0) setBindingValue(result, key, nested);
80848
81584
  }
@@ -80853,7 +81589,7 @@ function mergeResolvedBindings(base, resolved) {
80853
81589
  const merged = { ...base };
80854
81590
  for (const [key, resolvedValue] of Object.entries(resolved)) {
80855
81591
  const baseValue = merged[key];
80856
- merged[key] = isRecord$24(baseValue) && isRecord$24(resolvedValue) ? mergeResolvedBindings(baseValue, resolvedValue) : resolvedValue;
81592
+ merged[key] = isRecord$25(baseValue) && isRecord$25(resolvedValue) ? mergeResolvedBindings(baseValue, resolvedValue) : resolvedValue;
80857
81593
  }
80858
81594
  return merged;
80859
81595
  }
@@ -80878,7 +81614,7 @@ function setBindingValue(target, path, value) {
80878
81614
  current[Number(segment)] = value;
80879
81615
  return;
80880
81616
  }
80881
- if (isRecord$24(current)) current[segment] = value;
81617
+ if (isRecord$25(current)) current[segment] = value;
80882
81618
  return;
80883
81619
  }
80884
81620
  const nextWantsArray = typeof nextSegment === "string" && isIndex(nextSegment);
@@ -80887,15 +81623,15 @@ function setBindingValue(target, path, value) {
80887
81623
  const existing = current[index];
80888
81624
  if (nextWantsArray) {
80889
81625
  if (!Array.isArray(existing)) current[index] = [];
80890
- } else if (!isRecord$24(existing)) current[index] = {};
81626
+ } else if (!isRecord$25(existing)) current[index] = {};
80891
81627
  current = current[index];
80892
81628
  continue;
80893
81629
  }
80894
- if (isRecord$24(current)) {
81630
+ if (isRecord$25(current)) {
80895
81631
  const existing = current[segment];
80896
81632
  if (nextWantsArray) {
80897
81633
  if (!Array.isArray(existing)) current[segment] = [];
80898
- } else if (!isRecord$24(existing)) current[segment] = {};
81634
+ } else if (!isRecord$25(existing)) current[segment] = {};
80899
81635
  current = current[segment];
80900
81636
  continue;
80901
81637
  }
@@ -80933,7 +81669,7 @@ function getPath$1(source, path) {
80933
81669
  const segments = path.split(".").map((segment) => segment.trim()).filter(Boolean);
80934
81670
  let current = source;
80935
81671
  for (const segment of segments) {
80936
- if (!isRecord$24(current)) return void 0;
81672
+ if (!isRecord$25(current)) return void 0;
80937
81673
  current = current[segment];
80938
81674
  }
80939
81675
  return current;
@@ -80966,7 +81702,7 @@ function textToInlineText(value) {
80966
81702
  function extractFirstParagraph$1(value) {
80967
81703
  if (!isRichTextDoc(value)) return null;
80968
81704
  for (const node of value.content) {
80969
- if (!isRecord$24(node)) continue;
81705
+ if (!isRecord$25(node)) continue;
80970
81706
  if (node.type !== "paragraph" || !Array.isArray(node.content)) continue;
80971
81707
  const text = node.content.filter((item) => item.type === "text").map((item) => typeof item.text === "string" ? item.text : "").join("").trim();
80972
81708
  if (text.length > 0) return text;
@@ -80986,7 +81722,7 @@ function formatDate$1(value, options) {
80986
81722
  * Type guard to check if a value is a valid binding descriptor.
80987
81723
  */
80988
81724
  function isBindingDescriptor$1(value) {
80989
- if (!isRecord$24(value) || typeof value.source !== "string") return false;
81725
+ if (!isRecord$25(value) || typeof value.source !== "string") return false;
80990
81726
  if (!BINDING_SOURCES$1.includes(value.source)) return false;
80991
81727
  switch (value.source) {
80992
81728
  case "field":
@@ -81001,7 +81737,7 @@ function isBindingDescriptor$1(value) {
81001
81737
  * Type guard to check if a value is binding-descriptor-like (can be normalized).
81002
81738
  */
81003
81739
  function isBindingDescriptorLike$1(value) {
81004
- return isBindingDescriptor$1(value) || typeof value === "string" || isRecord$24(value) && typeof value.source === "string" && typeof value.path === "string";
81740
+ return isBindingDescriptor$1(value) || typeof value === "string" || isRecord$25(value) && typeof value.source === "string" && typeof value.path === "string";
81005
81741
  }
81006
81742
  /**
81007
81743
  * Normalizes a binding input to a binding descriptor.