@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
@@ -315,7 +315,7 @@ function parseEventPageSurfaceDestinationMode(value) {
315
315
  return value === "cms-managed" || value === "system-managed" ? value : void 0;
316
316
  }
317
317
  function parseEventPageSurfaceSubject(value) {
318
- if (!isRecord$21(value)) return err({ kind: "notObject" });
318
+ if (!isRecord$22(value)) return err({ kind: "notObject" });
319
319
  switch (value.kind) {
320
320
  case "eventSeries": {
321
321
  const eventSeriesId = parseEventSeriesId(value.eventSeriesId);
@@ -370,7 +370,7 @@ function parseEventSeriesIdList(values) {
370
370
  }
371
371
  return ok$1(parsed);
372
372
  }
373
- function isRecord$21(value) {
373
+ function isRecord$22(value) {
374
374
  return typeof value === "object" && value !== null && !Array.isArray(value);
375
375
  }
376
376
  //#endregion
@@ -596,6 +596,74 @@ function getLocalScheduleDate(date, timeZone) {
596
596
  };
597
597
  }
598
598
  //#endregion
599
+ //#region ../core/src/appointment-service-categories.ts
600
+ /**
601
+ * Groups services under their categories.
602
+ *
603
+ * A service whose `categoryId` names no category in `categories` lands in
604
+ * Other. The composite foreign key keeps that from happening with stored data,
605
+ * so this is only the total, safe answer for a caller holding a stale list.
606
+ */
607
+ function layoutServicesByCategory(categories, services, options) {
608
+ if (categories.length === 0) return {
609
+ kind: "uncategorized-site",
610
+ services: [...services]
611
+ };
612
+ const buckets = new Map(categories.map((category) => [category.id, []]));
613
+ const other = [];
614
+ for (const service of services) {
615
+ const bucket = service.categoryId === null ? void 0 : buckets.get(service.categoryId);
616
+ if (bucket !== void 0) bucket.push(service);
617
+ else other.push(service);
618
+ }
619
+ const groups = [];
620
+ for (const category of categories) {
621
+ const groupServices = buckets.get(category.id) ?? [];
622
+ if (groupServices.length === 0 && !options.keepEmptyGroups) continue;
623
+ groups.push({
624
+ category,
625
+ services: groupServices
626
+ });
627
+ }
628
+ if (groups.length === 0 && !options.keepEmptyGroups) return {
629
+ kind: "uncategorized-site",
630
+ services: other
631
+ };
632
+ return {
633
+ kind: "categorized-site",
634
+ groups,
635
+ other
636
+ };
637
+ }
638
+ /**
639
+ * Selects the public view for an optional category filter.
640
+ *
641
+ * It takes the category and service lists rather than a prebuilt layout
642
+ * because the public layout has already dropped empty categories, and then an
643
+ * empty category (`single` with no services) could not be told apart from a
644
+ * deleted one (`fallback-all`).
645
+ */
646
+ function selectCategoryView(categories, services, filterCategoryId) {
647
+ const publicLayout = () => layoutServicesByCategory(categories, services, { keepEmptyGroups: false });
648
+ if (filterCategoryId === null || filterCategoryId === void 0) return {
649
+ kind: "all",
650
+ layout: publicLayout()
651
+ };
652
+ const category = categories.find((candidate) => candidate.id === filterCategoryId);
653
+ if (category === void 0) return {
654
+ kind: "fallback-all",
655
+ missingCategoryId: filterCategoryId,
656
+ layout: publicLayout()
657
+ };
658
+ return {
659
+ kind: "single",
660
+ group: {
661
+ category,
662
+ services: services.filter((service) => service.categoryId === filterCategoryId)
663
+ }
664
+ };
665
+ }
666
+ //#endregion
599
667
  //#region ../core/src/location.ts
600
668
  function normalizeLocationAddressText(value) {
601
669
  if (typeof value !== "string") return null;
@@ -644,12 +712,12 @@ function parseBrevoListId(value) {
644
712
  }
645
713
  //#endregion
646
714
  //#region ../core/src/mailing-list-setup.ts
647
- function isRecord$20(value) {
715
+ function isRecord$21(value) {
648
716
  return typeof value === "object" && value !== null && !Array.isArray(value);
649
717
  }
650
718
  const DEFAULT_MAILING_LIST_SUCCESS_MESSAGE = "Thanks — you're on the list.";
651
719
  function parseMailingListProviderScope(value) {
652
- if (!isRecord$20(value)) return err("invalid_mailing_list_provider_scope");
720
+ if (!isRecord$21(value)) return err("invalid_mailing_list_provider_scope");
653
721
  if (value.provider === "native") return ok$1({ provider: "native" });
654
722
  if (value.provider === "brevo") {
655
723
  const accountId = parseBrevoAccountId(value.accountId);
@@ -670,7 +738,7 @@ function parseMailingListProviderScope(value) {
670
738
  //#endregion
671
739
  //#region ../core/src/mailing-list-setup-parser.ts
672
740
  function parseMailingListGlobalForm(value) {
673
- if (!isRecord$19(value)) return formError("shape");
741
+ if (!isRecord$20(value)) return formError("shape");
674
742
  const fields = parseGlobalFields(value.fields);
675
743
  if (fields === null) return formError("fields");
676
744
  if (typeof value.includeOptionalPhone !== "boolean") return formError("includeOptionalPhone");
@@ -689,7 +757,7 @@ function parseMailingListGlobalForm(value) {
689
757
  });
690
758
  }
691
759
  function parseGlobalFields(value) {
692
- if (!isRecord$19(value) || typeof value.preset !== "string") return null;
760
+ if (!isRecord$20(value) || typeof value.preset !== "string") return null;
693
761
  switch (value.preset) {
694
762
  case "emailOnly": return { preset: "emailOnly" };
695
763
  case "emailName": return isRequirement(value.name) ? {
@@ -726,7 +794,7 @@ function parseOptionalString(value) {
726
794
  function isRequirement(value) {
727
795
  return value === "required" || value === "optional";
728
796
  }
729
- function isRecord$19(value) {
797
+ function isRecord$20(value) {
730
798
  return typeof value === "object" && value !== null && !Array.isArray(value);
731
799
  }
732
800
  function formError(field) {
@@ -778,7 +846,7 @@ function planMailingListChoicePresentation(targeting) {
778
846
  //#endregion
779
847
  //#region ../core/src/mailing-list-runtime.ts
780
848
  function parseMailingListRuntimeContext(value) {
781
- if (!isRecord$18(value)) return invalidRuntimeContext();
849
+ if (!isRecord$19(value)) return invalidRuntimeContext();
782
850
  if (value.tag === "not_configured") return ok$1({ tag: "not_configured" });
783
851
  if (value.tag !== "configured") return invalidRuntimeContext();
784
852
  const revision = parseMailingListSetupRevision(value.revision);
@@ -793,7 +861,7 @@ function parseMailingListRuntimeContext(value) {
793
861
  });
794
862
  }
795
863
  function parseRuntimeProvider(value) {
796
- if (!isRecord$18(value)) return null;
864
+ if (!isRecord$19(value)) return null;
797
865
  switch (value.provider) {
798
866
  case "native": return parseNativeRuntimeProvider(value);
799
867
  case "brevo": return parseBrevoRuntimeProvider(value);
@@ -825,7 +893,7 @@ function parseMailchimpRuntimeProvider(value) {
825
893
  if (defaultAudienceId === null) return null;
826
894
  const audiences = [];
827
895
  for (const audience of value.audiences) {
828
- if (!isRecord$18(audience)) return null;
896
+ if (!isRecord$19(audience)) return null;
829
897
  const id = parseMailchimpAudienceId(audience.id);
830
898
  const name = parseName(audience.name);
831
899
  if (id === null || name === null || !Array.isArray(audience.interests)) return null;
@@ -846,7 +914,7 @@ function parseMailchimpRuntimeProvider(value) {
846
914
  function parseMailchimpInterests(value) {
847
915
  const interests = [];
848
916
  for (const interest of value) {
849
- if (!isRecord$18(interest)) return null;
917
+ if (!isRecord$19(interest)) return null;
850
918
  const id = parseMailchimpInterestId(interest.id);
851
919
  const name = parseName(interest.name);
852
920
  const categoryName = parseName(interest.categoryName);
@@ -863,7 +931,7 @@ function parseNamedItems(value, parseId) {
863
931
  if (!Array.isArray(value)) return null;
864
932
  const items = [];
865
933
  for (const item of value) {
866
- if (!isRecord$18(item)) return null;
934
+ if (!isRecord$19(item)) return null;
867
935
  const id = parseId(item.id);
868
936
  const name = parseName(item.name);
869
937
  if (id === null || name === null) return null;
@@ -879,7 +947,7 @@ function parseName(value) {
879
947
  const name = value.trim();
880
948
  return name.length > 0 ? name : null;
881
949
  }
882
- function isRecord$18(value) {
950
+ function isRecord$19(value) {
883
951
  return typeof value === "object" && value !== null && !Array.isArray(value);
884
952
  }
885
953
  function invalidRuntimeContext() {
@@ -888,10 +956,10 @@ function invalidRuntimeContext() {
888
956
  //#endregion
889
957
  //#region ../core/src/mailing-list-targeting-parser.ts
890
958
  function parseMailingListPlacementTargeting(value) {
891
- if (!isRecord$17(value)) return targetingError("shape");
959
+ if (!isRecord$18(value)) return targetingError("shape");
892
960
  if (value.mode === "default") return ok$1({ mode: "default" });
893
961
  if (value.mode !== "custom") return targetingError("mode");
894
- if (!isRecord$17(value.selection)) return targetingError("selection");
962
+ if (!isRecord$18(value.selection)) return targetingError("selection");
895
963
  switch (value.provider) {
896
964
  case "native": return parseNativeTargeting(value.selection);
897
965
  case "brevo": return parseBrevoTargeting(value.selection);
@@ -995,7 +1063,7 @@ function parseUniqueIds(value, parseId) {
995
1063
  }
996
1064
  return ok$1(parsed);
997
1065
  }
998
- function isRecord$17(value) {
1066
+ function isRecord$18(value) {
999
1067
  return typeof value === "object" && value !== null && !Array.isArray(value);
1000
1068
  }
1001
1069
  function targetingError(field) {
@@ -1635,11 +1703,11 @@ const getImageUrl = (asset, params = {}, basePath = DEFAULT_BASE_PATH) => {
1635
1703
  };
1636
1704
  //#endregion
1637
1705
  //#region ../media-core/src/typeGuards.ts
1638
- function isRecord$16(value) {
1706
+ function isRecord$17(value) {
1639
1707
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1640
1708
  }
1641
1709
  function asRecord$1(value) {
1642
- return isRecord$16(value) ? value : void 0;
1710
+ return isRecord$17(value) ? value : void 0;
1643
1711
  }
1644
1712
  //#endregion
1645
1713
  //#region ../media-core/src/transformRect.ts
@@ -1775,8 +1843,8 @@ function walkAndCollectImageAttrs(value, out, location = "") {
1775
1843
  value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
1776
1844
  return;
1777
1845
  }
1778
- if (!isRecord$16(value)) return;
1779
- if (value.type === "image" && isRecord$16(value.attrs)) out.push({
1846
+ if (!isRecord$17(value)) return;
1847
+ if (value.type === "image" && isRecord$17(value.attrs)) out.push({
1780
1848
  attrs: value.attrs,
1781
1849
  location: location ? `${location}.attrs` : "attrs"
1782
1850
  });
@@ -2005,7 +2073,8 @@ const siteOptionSources = [
2005
2073
  "eventCategories",
2006
2074
  "eventSeries",
2007
2075
  "venues",
2008
- "staffMembers"
2076
+ "staffMembers",
2077
+ "appointmentServiceCategories"
2009
2078
  ];
2010
2079
  const mediaDeliverySchema = z.object({
2011
2080
  sizes: z.string().optional(),
@@ -4229,6 +4298,15 @@ function asSiteStyleId(value) {
4229
4298
  function asDesignSystemTemplateId(value) {
4230
4299
  return brandNonEmptyString(value, "DesignSystemTemplateId");
4231
4300
  }
4301
+ function toNonEmptyReadonlyArray(values) {
4302
+ const [first, ...rest] = values;
4303
+ return first === void 0 ? null : [first, ...rest];
4304
+ }
4305
+ function requireNonEmptyReadonlyArray(values, message = "Expected a non-empty array.") {
4306
+ const result = toNonEmptyReadonlyArray(values);
4307
+ if (!result) throw new Error(message);
4308
+ return result;
4309
+ }
4232
4310
  function makeBoundaryId(previousBlockId, nextBlockId) {
4233
4311
  return asDesignBoundaryId(`boundary:${encodeURIComponent(previousBlockId)}:${encodeURIComponent(nextBlockId)}`);
4234
4312
  }
@@ -6795,11 +6873,578 @@ function cardRoleFromLegacyVocabulary(value) {
6795
6873
  */
6796
6874
  const cardRoleWithLegacyAliasSchema = z.preprocess((value) => cardRoleFromLegacyVocabulary(value) ?? value, cardRoleSchema);
6797
6875
  //#endregion
6798
- //#region ../theme-core/src/site-styles/compilationPlan.ts
6799
- const defaultHeaderBrandPreferences = {
6800
- brandDisplay: "auto",
6801
- logoSize: "auto"
6802
- };
6876
+ //#region ../theme-core/src/lib/media.ts
6877
+ const hotspotSchema = z.object({
6878
+ x: z.number().min(0).max(1),
6879
+ y: z.number().min(0).max(1),
6880
+ radius: z.number().optional()
6881
+ });
6882
+ const rotationSchema = z.union([
6883
+ z.literal(0),
6884
+ z.literal(90),
6885
+ z.literal(180),
6886
+ z.literal(270)
6887
+ ]);
6888
+ const rectSchema = z.tuple([
6889
+ z.number(),
6890
+ z.number(),
6891
+ z.number(),
6892
+ z.number()
6893
+ ]);
6894
+ const aspectCropSchema = z.object({
6895
+ aspect: z.preprocess((value) => value == null ? void 0 : value, z.string().optional()),
6896
+ rect: z.preprocess((value) => value == null ? void 0 : value, rectSchema.optional()),
6897
+ hotspot: z.preprocess((value) => value == null ? void 0 : value, hotspotSchema.optional()),
6898
+ rotation: z.preprocess((value) => value == null ? void 0 : value, rotationSchema.optional())
6899
+ });
6900
+ const transformSchema = z.object({
6901
+ aspectCrops: z.preprocess((value) => {
6902
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
6903
+ return value;
6904
+ }, z.record(z.string(), aspectCropSchema.catch({}))).optional(),
6905
+ rect: rectSchema.optional(),
6906
+ hotspot: hotspotSchema.optional(),
6907
+ rotation: rotationSchema.optional()
6908
+ });
6909
+ const mediaPosterSchema = z.discriminatedUnion("tag", [z.object({
6910
+ tag: z.literal("storage"),
6911
+ assetId: z.string().optional(),
6912
+ storageBucket: z.string().min(1),
6913
+ storagePath: z.string().min(1)
6914
+ }), z.object({
6915
+ tag: z.literal("external-src"),
6916
+ assetId: z.string().optional(),
6917
+ src: z.string().url()
6918
+ })]);
6919
+ const mediaBaseSchema = z.object({
6920
+ purpose: z.string().optional(),
6921
+ placeholder: z.boolean().default(true).optional(),
6922
+ assetId: z.string().optional(),
6923
+ identifier: z.string().optional(),
6924
+ src: z.string().optional(),
6925
+ alt: z.string().optional(),
6926
+ filename: z.string().optional(),
6927
+ mimeType: z.string().optional(),
6928
+ width: z.number().optional(),
6929
+ height: z.number().optional(),
6930
+ storageBucket: z.string().optional(),
6931
+ storagePath: z.string().optional(),
6932
+ poster: mediaPosterSchema.optional(),
6933
+ transform: transformSchema.optional()
6934
+ });
6935
+ const mediaIdentifierSchema = z.string().trim().min(1).brand();
6936
+ const imageMediaSchema = mediaBaseSchema.extend({ type: z.literal("image") });
6937
+ const videoMediaSchema = mediaBaseSchema.extend({ type: z.literal("video") });
6938
+ const audioMediaSchema = mediaBaseSchema.extend({ type: z.literal("audio") });
6939
+ const documentMediaSchema = mediaBaseSchema.extend({ type: z.literal("document") });
6940
+ const spreadsheetMediaSchema = mediaBaseSchema.extend({ type: z.literal("spreadsheet") });
6941
+ const archiveMediaSchema = mediaBaseSchema.extend({ type: z.literal("archive") });
6942
+ const mediaSchema = z.discriminatedUnion("type", [
6943
+ imageMediaSchema,
6944
+ videoMediaSchema,
6945
+ audioMediaSchema,
6946
+ documentMediaSchema,
6947
+ spreadsheetMediaSchema,
6948
+ archiveMediaSchema
6949
+ ]);
6950
+ const persistableMediaBaseSchema = mediaBaseSchema.extend({ identifier: mediaIdentifierSchema });
6951
+ z.discriminatedUnion("type", [
6952
+ persistableMediaBaseSchema.extend({ type: z.literal("image") }),
6953
+ persistableMediaBaseSchema.extend({ type: z.literal("video") }),
6954
+ persistableMediaBaseSchema.extend({ type: z.literal("audio") }),
6955
+ persistableMediaBaseSchema.extend({ type: z.literal("document") }),
6956
+ persistableMediaBaseSchema.extend({ type: z.literal("spreadsheet") }),
6957
+ persistableMediaBaseSchema.extend({ type: z.literal("archive") })
6958
+ ]);
6959
+ //#endregion
6960
+ //#region ../theme-core/src/site-styles/rawStyleFields.ts
6961
+ const themeV2RuntimeStyleFieldNames = [
6962
+ "_frameStyles",
6963
+ "_sectionStyles",
6964
+ "_containerStyles",
6965
+ "_cardStyles"
6966
+ ];
6967
+ const themeV2BlockSpecificRawStyleFieldNames = [
6968
+ "_contentCardStyles",
6969
+ "_heroLayoutStyles",
6970
+ "_textCardStyles"
6971
+ ];
6972
+ const themeV2NormalEditorRawStyleFieldNames = [
6973
+ ...themeV2RuntimeStyleFieldNames,
6974
+ ...themeV2BlockSpecificRawStyleFieldNames,
6975
+ "styleGroup"
6976
+ ];
6977
+ new Set(themeV2NormalEditorRawStyleFieldNames);
6978
+ [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
6979
+ const pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
6980
+ const siteChromeBackgroundOverrideOwner = "site-chrome-editor";
6981
+ const staleTransitionStyleGroupPrefix = "site-style-transition-";
6982
+ /**
6983
+ * A `styleGroup` in the retired `site-style-transition-*` namespace is residue
6984
+ * from the old per-block transition path (issue 1422). Any other value is
6985
+ * authored and must survive.
6986
+ */
6987
+ function isStaleTransitionStyleGroup(value) {
6988
+ return typeof value === "string" && value.startsWith(staleTransitionStyleGroupPrefix);
6989
+ }
6990
+ /** Drops a stale transition `styleGroup`, leaving authored values untouched. */
6991
+ function stripStaleTransitionStyleGroup(content) {
6992
+ if (!isStaleTransitionStyleGroup(content.styleGroup)) return { ...content };
6993
+ const { styleGroup: _stale, ...rest } = content;
6994
+ return rest;
6995
+ }
6996
+ //#endregion
6997
+ //#region ../theme-core/src/utils/tokenOrColor.ts
6998
+ /**
6999
+ * Runtime color-reference fields intentionally accept any string, including
7000
+ * the legacy empty-string "no color" sentinel. Semantic validation needs
7001
+ * palette context and happens in `parseThemeColorReference`.
7002
+ */
7003
+ const tokenOrCssColorValueSchema = z.string();
7004
+ const CSS_COLOR_FUNCTION_PATTERN = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\(/i;
7005
+ const CSS_HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
7006
+ const CSS_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
7007
+ "transparent",
7008
+ "currentcolor",
7009
+ "inherit",
7010
+ "initial",
7011
+ "revert",
7012
+ "revert-layer",
7013
+ "unset"
7014
+ ]);
7015
+ const COMMON_CSS_NAMED_COLORS = /* @__PURE__ */ new Set([
7016
+ "black",
7017
+ "blue",
7018
+ "gray",
7019
+ "green",
7020
+ "grey",
7021
+ "orange",
7022
+ "pink",
7023
+ "purple",
7024
+ "red",
7025
+ "white",
7026
+ "yellow"
7027
+ ]);
7028
+ function isCssColorLiteral(value) {
7029
+ const trimmed = value.trim();
7030
+ if (!trimmed) return false;
7031
+ return CSS_HEX_COLOR_PATTERN.test(trimmed) || CSS_COLOR_FUNCTION_PATTERN.test(trimmed) || /^var\(/i.test(trimmed) || CSS_COLOR_KEYWORDS.has(trimmed.toLowerCase());
7032
+ }
7033
+ function parseThemeToken(token) {
7034
+ const trimmed = token.trim();
7035
+ const match = trimmed.match(/^(.*?)-(\d{2,3})$/);
7036
+ if (!match) return {
7037
+ role: trimmed,
7038
+ shade: null
7039
+ };
7040
+ const role = match[1] ?? trimmed;
7041
+ const shade = match[2] ? Number(match[2]) : null;
7042
+ return {
7043
+ role,
7044
+ shade: Number.isFinite(shade) ? shade : null
7045
+ };
7046
+ }
7047
+ function parseContextThemeColorToken(value, context) {
7048
+ const parts = parseColorTokenParts(value);
7049
+ if (parts.kind !== "token") return null;
7050
+ if (!(Boolean(context?.palette?.[value.trim()]) || parts.opacityPercent !== null && Boolean(context?.palette?.[parts.token]))) return null;
7051
+ return value.trim();
7052
+ }
7053
+ function isTokenShapedTypo(value) {
7054
+ if (COMMON_CSS_NAMED_COLORS.has(value.toLowerCase())) return false;
7055
+ if (!/^[a-zA-Z][a-zA-Z0-9]*(?:-[a-zA-Z0-9]+)*(?:\/[^/]+)?$/.test(value)) return false;
7056
+ return !isCssColorLiteral(value);
7057
+ }
7058
+ function warnUnknownThemeColorReference(value) {
7059
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
7060
+ if (!isTokenShapedTypo(value)) return;
7061
+ console.warn(`[theme-core] Unknown Theme V2 color token "${value}" treated as literal CSS. Use raw:* for deliberate custom CSS values.`);
7062
+ }
7063
+ function parseThemeColorReference(input, context) {
7064
+ const trimmed = input.trim();
7065
+ if (trimmed.startsWith("raw:")) return {
7066
+ kind: "literal-css",
7067
+ value: trimmed.slice(4)
7068
+ };
7069
+ const contextToken = parseContextThemeColorToken(trimmed, context);
7070
+ if (contextToken) return {
7071
+ kind: "palette-context-token",
7072
+ token: contextToken
7073
+ };
7074
+ const knownToken = parseThemeColorToken(trimmed);
7075
+ if (knownToken) return {
7076
+ kind: "theme-token",
7077
+ token: knownToken
7078
+ };
7079
+ warnUnknownThemeColorReference(trimmed);
7080
+ return {
7081
+ kind: "literal-css",
7082
+ value: trimmed
7083
+ };
7084
+ }
7085
+ function resolveThemeColorReferenceValue(input, context) {
7086
+ const reference = parseThemeColorReference(input, context);
7087
+ return reference.kind === "literal-css" ? reference.value : rgbColorValue(reference.token);
7088
+ }
7089
+ function resolveTokenOrCssColorValue(input, context) {
7090
+ return resolveThemeColorReferenceValue(input, context);
7091
+ }
7092
+ function resolveOnColorForThemeToken(token) {
7093
+ if (!parseThemeColorToken(token)) return null;
7094
+ const parts = parseColorTokenParts(token);
7095
+ if (parts.kind !== "token") return null;
7096
+ const { role, shade } = parseThemeToken(parts.token);
7097
+ const normalizedRole = role.trim();
7098
+ if (!normalizedRole) return null;
7099
+ if (!shade) return rgbColorValue(`on${normalizedRole.charAt(0).toUpperCase() + normalizedRole.slice(1)}`);
7100
+ return shade <= 400 ? "#000000" : "#FFFFFF";
7101
+ }
7102
+ //#endregion
7103
+ //#region ../theme-core/src/boxBackgroundSchema.ts
7104
+ const boxBackgroundOverlaySchema = z.preprocess(normalizeAuthoringOpacity, z.discriminatedUnion("type", [
7105
+ z.object({ type: z.literal("none") }),
7106
+ z.object({
7107
+ type: z.literal("color"),
7108
+ color: tokenOrCssColorValueSchema.nullable().optional(),
7109
+ opacity: z.number().min(0).max(1).nullable().optional()
7110
+ }),
7111
+ z.object({
7112
+ type: z.literal("gradient"),
7113
+ gradient: z.string().nullable().optional(),
7114
+ opacity: z.number().min(0).max(1).nullable().optional()
7115
+ })
7116
+ ]));
7117
+ const commonSchema = z.object({
7118
+ contentOwner: z.literal(pageDesignEditorBackgroundOverrideOwner).optional(),
7119
+ textColor: tokenOrCssColorValueSchema.nullable().optional(),
7120
+ headingColor: tokenOrCssColorValueSchema.nullable().optional(),
7121
+ linkColor: tokenOrCssColorValueSchema.nullable().optional(),
7122
+ supportingColor: tokenOrCssColorValueSchema.nullable().optional()
7123
+ });
7124
+ const mediaPresentationSchema = commonSchema.extend({
7125
+ opacity: z.preprocess(normalizeOpacity, z.number().min(0).max(1).nullable()).optional(),
7126
+ objectFit: z.enum([
7127
+ "fill",
7128
+ "fit",
7129
+ "original",
7130
+ "custom"
7131
+ ]).nullable().optional(),
7132
+ scale: z.string().nullable().optional(),
7133
+ position: z.string().nullable().optional(),
7134
+ motion: z.enum(["static", "fixed-frame"]).nullable().optional(),
7135
+ overflow: z.boolean().nullable().optional(),
7136
+ overlay: boxBackgroundOverlaySchema.nullable().optional()
7137
+ });
7138
+ const boxBackgroundSchema = z.discriminatedUnion("type", [
7139
+ commonSchema.extend({
7140
+ type: z.literal("color"),
7141
+ color: tokenOrCssColorValueSchema.nullable().optional()
7142
+ }),
7143
+ commonSchema.extend({
7144
+ type: z.literal("gradient"),
7145
+ gradient: z.string().nullable().optional(),
7146
+ gradientPresetId: z.string().min(1).nullable().optional()
7147
+ }),
7148
+ mediaPresentationSchema.extend({
7149
+ type: z.literal("image"),
7150
+ image: imageMediaSchema.nullable().optional()
7151
+ }),
7152
+ mediaPresentationSchema.extend({
7153
+ type: z.literal("video"),
7154
+ image: videoMediaSchema.nullable().optional(),
7155
+ showVideoOnMobile: z.boolean().nullable().optional()
7156
+ })
7157
+ ]);
7158
+ function normalizeAuthoringOpacity(value) {
7159
+ if (!isRecord$16(value)) return value;
7160
+ return {
7161
+ ...value,
7162
+ opacity: normalizeOpacity(value.opacity)
7163
+ };
7164
+ }
7165
+ function normalizeOpacity(value) {
7166
+ return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
7167
+ }
7168
+ function isRecord$16(value) {
7169
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7170
+ }
7171
+ //#endregion
7172
+ //#region ../theme-core/src/site-styles/runtimeBackgroundResolution.ts
7173
+ function parseCanonicalRuntimeBackground(content) {
7174
+ const background = readRecord(readRecord(content._sectionStyles)?.background);
7175
+ if (!background) return { kind: "none" };
7176
+ const parsed = boxBackgroundSchema.safeParse(background);
7177
+ if (!parsed.success) return { kind: "none" };
7178
+ return parsed.data.contentOwner === "page-design-editor" ? {
7179
+ kind: "page-design-override",
7180
+ background: parsed.data
7181
+ } : {
7182
+ kind: "block-authored",
7183
+ background: parsed.data
7184
+ };
7185
+ }
7186
+ function readRecord(value) {
7187
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
7188
+ }
7189
+ //#endregion
7190
+ //#region ../theme-core/src/site-styles/transitionCatalogue.ts
7191
+ /**
7192
+ * Family order for pickers. Entries below are listed family by family in this
7193
+ * same order, so the flat option list and the grouped families agree.
7194
+ */
7195
+ const transitionCatalogueFamilies = [
7196
+ {
7197
+ id: "gradient",
7198
+ label: "Gradient"
7199
+ },
7200
+ {
7201
+ id: "wave",
7202
+ label: "Wave"
7203
+ },
7204
+ {
7205
+ id: "angle",
7206
+ label: "Angle"
7207
+ }
7208
+ ];
7209
+ const mediumHeight = "6rem + 2px";
7210
+ const compactHeight = "4.5rem + 2px";
7211
+ const largeHeight = "7rem + 2px";
7212
+ /**
7213
+ * The single source of transition vocabulary. Order is significant: it is the
7214
+ * order of `boundaryOptions` (after `none`) and of each family's variants,
7215
+ * which persisted pins, page-flow presets and the editor picker all rely on.
7216
+ *
7217
+ * Shapes match what `generateLayoutCss` draws today. Structured
7218
+ * amplitude/frequency treatments (issue 826) extend this list later.
7219
+ */
7220
+ const transitionCatalogueEntries = defineExactCatalog()([
7221
+ {
7222
+ intent: "soft-fade-short",
7223
+ optionId: asBoundaryOptionId("break:soft"),
7224
+ family: "gradient",
7225
+ variantId: "soft",
7226
+ label: "Soft",
7227
+ variantLabel: "Soft",
7228
+ treatment: {
7229
+ kind: "soft-fade",
7230
+ height: "medium",
7231
+ curve: "soft"
7232
+ },
7233
+ size: "medium",
7234
+ heightExpression: mediumHeight,
7235
+ shape: { kind: "fade" }
7236
+ },
7237
+ {
7238
+ intent: "soft-fade-long",
7239
+ optionId: asBoundaryOptionId("break:spacious"),
7240
+ family: "gradient",
7241
+ variantId: "spacious",
7242
+ label: "Spacious",
7243
+ variantLabel: "Spacious",
7244
+ treatment: {
7245
+ kind: "soft-fade",
7246
+ height: "large",
7247
+ curve: "spacious"
7248
+ },
7249
+ size: "spacious",
7250
+ heightExpression: "10rem",
7251
+ shape: { kind: "fade" }
7252
+ },
7253
+ {
7254
+ intent: "soft-fade-expansive",
7255
+ optionId: asBoundaryOptionId("break:expansive"),
7256
+ family: "gradient",
7257
+ variantId: "expansive",
7258
+ label: "Expansive",
7259
+ variantLabel: "Expansive",
7260
+ treatment: {
7261
+ kind: "soft-fade",
7262
+ height: "expansive",
7263
+ curve: "expansive"
7264
+ },
7265
+ size: "expansive",
7266
+ heightExpression: "14rem",
7267
+ shape: { kind: "fade" }
7268
+ },
7269
+ {
7270
+ intent: "wave-edge-balanced",
7271
+ optionId: asBoundaryOptionId("break:wave-balanced"),
7272
+ family: "wave",
7273
+ variantId: "balanced",
7274
+ label: "Balanced wave",
7275
+ variantLabel: "Balanced",
7276
+ treatment: {
7277
+ kind: "wave-edge",
7278
+ height: "medium",
7279
+ amplitude: "balanced",
7280
+ rhythm: "double",
7281
+ symmetry: "symmetrical"
7282
+ },
7283
+ size: "medium",
7284
+ heightExpression: mediumHeight,
7285
+ shape: {
7286
+ kind: "wave",
7287
+ mask: "balanced"
7288
+ }
7289
+ },
7290
+ {
7291
+ intent: "wave-edge",
7292
+ optionId: asBoundaryOptionId("break:wave"),
7293
+ family: "wave",
7294
+ variantId: "classic",
7295
+ label: "Classic wave",
7296
+ variantLabel: "Classic",
7297
+ treatment: {
7298
+ kind: "wave-edge",
7299
+ height: "medium",
7300
+ amplitude: "balanced",
7301
+ rhythm: "double",
7302
+ symmetry: "asymmetrical"
7303
+ },
7304
+ size: "medium",
7305
+ heightExpression: mediumHeight,
7306
+ shape: {
7307
+ kind: "wave",
7308
+ mask: "classic"
7309
+ }
7310
+ },
7311
+ {
7312
+ intent: "wave-edge-gentle",
7313
+ optionId: asBoundaryOptionId("break:wave-gentle"),
7314
+ family: "wave",
7315
+ variantId: "gentle",
7316
+ label: "Gentle wave",
7317
+ variantLabel: "Gentle",
7318
+ treatment: {
7319
+ kind: "wave-edge",
7320
+ height: "compact",
7321
+ amplitude: "subtle",
7322
+ rhythm: "single",
7323
+ symmetry: "symmetrical"
7324
+ },
7325
+ size: "compact",
7326
+ heightExpression: compactHeight,
7327
+ shape: {
7328
+ kind: "wave",
7329
+ mask: "gentle"
7330
+ }
7331
+ },
7332
+ {
7333
+ intent: "wave-edge-bold",
7334
+ optionId: asBoundaryOptionId("break:wave-bold"),
7335
+ family: "wave",
7336
+ variantId: "bold",
7337
+ label: "Bold wave",
7338
+ variantLabel: "Bold",
7339
+ treatment: {
7340
+ kind: "wave-edge",
7341
+ height: "large",
7342
+ amplitude: "bold",
7343
+ rhythm: "double",
7344
+ symmetry: "symmetrical"
7345
+ },
7346
+ size: "large",
7347
+ heightExpression: largeHeight,
7348
+ shape: {
7349
+ kind: "wave",
7350
+ mask: "bold"
7351
+ }
7352
+ },
7353
+ {
7354
+ intent: "wave-edge-drift",
7355
+ optionId: asBoundaryOptionId("break:wave-drift"),
7356
+ family: "wave",
7357
+ variantId: "drift",
7358
+ label: "Drift wave",
7359
+ variantLabel: "Drift",
7360
+ treatment: {
7361
+ kind: "wave-edge",
7362
+ height: "medium",
7363
+ amplitude: "balanced",
7364
+ rhythm: "double",
7365
+ symmetry: "drift"
7366
+ },
7367
+ size: "medium",
7368
+ heightExpression: mediumHeight,
7369
+ shape: {
7370
+ kind: "wave",
7371
+ mask: "drift"
7372
+ }
7373
+ },
7374
+ {
7375
+ intent: "angle-edge-subtle",
7376
+ optionId: asBoundaryOptionId("break:angle-subtle"),
7377
+ family: "angle",
7378
+ variantId: "subtle",
7379
+ label: "Subtle angle",
7380
+ variantLabel: "Subtle",
7381
+ treatment: {
7382
+ kind: "angle-edge",
7383
+ height: "compact",
7384
+ slope: "subtle",
7385
+ direction: "forward"
7386
+ },
7387
+ size: "compact",
7388
+ heightExpression: compactHeight,
7389
+ shape: {
7390
+ kind: "angle",
7391
+ polygon: {
7392
+ startTopPercent: 34,
7393
+ endTopPercent: 18
7394
+ }
7395
+ }
7396
+ },
7397
+ {
7398
+ intent: "angle-edge",
7399
+ optionId: asBoundaryOptionId("break:angle-balanced"),
7400
+ family: "angle",
7401
+ variantId: "balanced",
7402
+ label: "Balanced angle",
7403
+ variantLabel: "Balanced",
7404
+ treatment: {
7405
+ kind: "angle-edge",
7406
+ height: "medium",
7407
+ slope: "balanced",
7408
+ direction: "forward"
7409
+ },
7410
+ size: "medium",
7411
+ heightExpression: mediumHeight,
7412
+ shape: {
7413
+ kind: "angle",
7414
+ polygon: {
7415
+ startTopPercent: 42,
7416
+ endTopPercent: 8
7417
+ }
7418
+ }
7419
+ },
7420
+ {
7421
+ intent: "angle-edge-steep",
7422
+ optionId: asBoundaryOptionId("break:angle-steep"),
7423
+ family: "angle",
7424
+ variantId: "steep",
7425
+ label: "Steep angle",
7426
+ variantLabel: "Steep",
7427
+ treatment: {
7428
+ kind: "angle-edge",
7429
+ height: "large",
7430
+ slope: "steep",
7431
+ direction: "forward"
7432
+ },
7433
+ size: "large",
7434
+ heightExpression: largeHeight,
7435
+ shape: {
7436
+ kind: "angle",
7437
+ polygon: {
7438
+ startTopPercent: 54,
7439
+ endTopPercent: 0
7440
+ }
7441
+ }
7442
+ }
7443
+ ]);
7444
+ /** CSS `clip-path` value for an angle shape, as `generateLayoutCss` draws it. */
7445
+ function anglePolygonCss(polygon) {
7446
+ return `polygon(0 ${polygon.startTopPercent}%, 100% ${polygon.endTopPercent}%, 100% 100%, 0 100%)`;
7447
+ }
6803
7448
  //#endregion
6804
7449
  //#region ../theme-core/src/site-styles/sectionTransitions.ts
6805
7450
  function boundaryRuntimeMetrics(family, size, heightExpression) {
@@ -6810,22 +7455,19 @@ function boundaryRuntimeMetrics(family, size, heightExpression) {
6810
7455
  reserveCss: `calc((${heightExpression}) / 2)`
6811
7456
  };
6812
7457
  }
6813
- const sectionBoundaryRuntimeMetricsByTransition = {
6814
- none: null,
6815
- "soft-fade-short": boundaryRuntimeMetrics("gradient", "medium", "6rem + 2px"),
6816
- "soft-fade-long": boundaryRuntimeMetrics("gradient", "spacious", "10rem"),
6817
- "soft-fade-expansive": boundaryRuntimeMetrics("gradient", "expansive", "14rem"),
6818
- "wave-edge": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
6819
- "wave-edge-balanced": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
6820
- "wave-edge-gentle": boundaryRuntimeMetrics("wave", "compact", "4.5rem + 2px"),
6821
- "wave-edge-bold": boundaryRuntimeMetrics("wave", "large", "7rem + 2px"),
6822
- "wave-edge-drift": boundaryRuntimeMetrics("wave", "medium", "6rem + 2px"),
6823
- "angle-edge": boundaryRuntimeMetrics("angle", "medium", "6rem + 2px"),
6824
- "angle-edge-subtle": boundaryRuntimeMetrics("angle", "compact", "4.5rem + 2px"),
6825
- "angle-edge-steep": boundaryRuntimeMetrics("angle", "large", "7rem + 2px")
6826
- };
6827
- function boundaryRuntimeMetricsForTransition(transition) {
6828
- return sectionBoundaryRuntimeMetricsByTransition[transition];
7458
+ new Map(transitionCatalogueEntries.map((entry) => [entry.intent, entry]));
7459
+ const metricsByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, boundaryRuntimeMetrics(entry.family, entry.size, entry.heightExpression)]));
7460
+ /**
7461
+ * The catalogue's compile-time coverage check guarantees an entry per
7462
+ * decorative intent, so a miss here is a broken invariant, not bad input.
7463
+ */
7464
+ function requireCatalogueValue(values, intent) {
7465
+ const value = values.get(intent);
7466
+ if (value === void 0) throw new Error(`Transition catalogue has no entry for ${intent}`);
7467
+ return value;
7468
+ }
7469
+ function boundaryRuntimeMetricsForDecorativeTransition(transition) {
7470
+ return requireCatalogueValue(metricsByIntent, transition);
6829
7471
  }
6830
7472
  const noneBoundaryOption = {
6831
7473
  id: asBoundaryOptionId("break:none"),
@@ -6833,35 +7475,233 @@ const noneBoundaryOption = {
6833
7475
  transitionAfter: "none",
6834
7476
  treatment: { kind: "none" }
6835
7477
  };
6836
- const softBoundaryOption = {
6837
- id: asBoundaryOptionId("break:soft"),
6838
- label: "Soft",
6839
- transitionAfter: "soft-fade-short",
6840
- treatment: {
6841
- kind: "soft-fade",
6842
- height: "medium",
6843
- curve: "soft"
6844
- }
7478
+ const boundaryOptionByIntent = new Map(transitionCatalogueEntries.map((entry) => [entry.intent, {
7479
+ id: entry.optionId,
7480
+ label: entry.label,
7481
+ transitionAfter: entry.intent,
7482
+ treatment: entry.treatment
7483
+ }]));
7484
+ function boundaryOptionForTransitionIntent(intent) {
7485
+ return requireCatalogueValue(boundaryOptionByIntent, intent);
7486
+ }
7487
+ const softBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-short");
7488
+ const spaciousBoundaryOption = boundaryOptionForTransitionIntent("soft-fade-long");
7489
+ boundaryOptionForTransitionIntent("soft-fade-expansive");
7490
+ boundaryOptionForTransitionIntent("wave-edge");
7491
+ boundaryOptionForTransitionIntent("wave-edge-balanced");
7492
+ boundaryOptionForTransitionIntent("wave-edge-gentle");
7493
+ boundaryOptionForTransitionIntent("wave-edge-bold");
7494
+ boundaryOptionForTransitionIntent("wave-edge-drift");
7495
+ boundaryOptionForTransitionIntent("angle-edge-subtle");
7496
+ boundaryOptionForTransitionIntent("angle-edge");
7497
+ boundaryOptionForTransitionIntent("angle-edge-steep");
7498
+ /**
7499
+ * Must remain non-empty; downstream code indexes `[0]` as the documented default.
7500
+ */
7501
+ const boundaryOptions = [noneBoundaryOption, ...transitionCatalogueEntries.map((entry) => boundaryOptionForTransitionIntent(entry.intent))];
7502
+ const noneTransitionVariant = {
7503
+ familyId: "none",
7504
+ variantId: "none",
7505
+ label: "None",
7506
+ boundaryOption: noneBoundaryOption
6845
7507
  };
6846
- const spaciousBoundaryOption = {
6847
- id: asBoundaryOptionId("break:spacious"),
6848
- label: "Spacious",
6849
- transitionAfter: "soft-fade-long",
6850
- treatment: {
6851
- kind: "soft-fade",
6852
- height: "large",
6853
- curve: "spacious"
7508
+ function transitionVariantForEntry(entry) {
7509
+ return {
7510
+ familyId: entry.family,
7511
+ variantId: entry.variantId,
7512
+ label: entry.variantLabel,
7513
+ boundaryOption: boundaryOptionForTransitionIntent(entry.intent)
7514
+ };
7515
+ }
7516
+ const sectionTransitionFamilies = [{
7517
+ id: "none",
7518
+ label: "None",
7519
+ variants: [noneTransitionVariant]
7520
+ }, ...transitionCatalogueFamilies.map((family) => ({
7521
+ id: family.id,
7522
+ label: family.label,
7523
+ variants: requireNonEmptyReadonlyArray(transitionCatalogueEntries.filter((entry) => entry.family === family.id).map(transitionVariantForEntry), `Transition family ${family.id} has no catalogue entries.`)
7524
+ }))];
7525
+ new Map(boundaryOptions.map((option) => [option.id, option]));
7526
+ new Map(boundaryOptions.map((option) => [stableTreatmentKey(option.treatment), option]));
7527
+ new Map(sectionTransitionFamilies.flatMap((family) => family.variants.map((variant) => [variant.boundaryOption.id, variant])));
7528
+ new Map(sectionTransitionFamilies.map((family) => [family.id, family]));
7529
+ function stableTreatmentKey(treatment) {
7530
+ switch (treatment.kind) {
7531
+ case "none": return "none";
7532
+ case "soft-fade": return `${treatment.kind}:${treatment.height}:${treatment.curve}`;
7533
+ case "wave-edge": return [
7534
+ treatment.kind,
7535
+ treatment.height,
7536
+ treatment.amplitude,
7537
+ treatment.rhythm,
7538
+ treatment.symmetry
7539
+ ].join(":");
7540
+ case "angle-edge": return [
7541
+ treatment.kind,
7542
+ treatment.height,
7543
+ treatment.slope,
7544
+ treatment.direction
7545
+ ].join(":");
7546
+ case "legacy-unsupported": return `${treatment.kind}:${treatment.boundaryOptionId}`;
7547
+ }
7548
+ }
7549
+ //#endregion
7550
+ //#region ../theme-core/src/site-styles/sectionBoundaryComposition.ts
7551
+ const backgroundToken = requireThemeColorToken$1("background");
7552
+ /**
7553
+ * Classifies a section from its final render content. `_sectionStyles`
7554
+ * already holds the effective background (Look default, page-design override
7555
+ * and block-authored media merged by `resolveSectionBackground`), so this only
7556
+ * reads it and never re-decides precedence. A missing or unparseable
7557
+ * background is the page background, as it is for the renderer.
7558
+ */
7559
+ function boundaryEndpointForSectionContent(content) {
7560
+ const background = parseCanonicalRuntimeBackground(content);
7561
+ if (background.kind === "none") return {
7562
+ kind: "paint",
7563
+ token: backgroundToken
7564
+ };
7565
+ const effective = background.background;
7566
+ switch (effective.type) {
7567
+ case "color": {
7568
+ if (effective.color === null || effective.color === void 0) return {
7569
+ kind: "paint",
7570
+ token: backgroundToken
7571
+ };
7572
+ const token = parseThemeColorToken(effective.color);
7573
+ return token ? {
7574
+ kind: "paint",
7575
+ token
7576
+ } : {
7577
+ kind: "unpainted",
7578
+ reason: "literal-color"
7579
+ };
7580
+ }
7581
+ case "gradient": return {
7582
+ kind: "unpainted",
7583
+ reason: "gradient"
7584
+ };
7585
+ case "image":
7586
+ case "video": return {
7587
+ kind: "unpainted",
7588
+ reason: "media"
7589
+ };
7590
+ default: return assertNever$3(effective, "Unhandled section background type");
7591
+ }
7592
+ }
7593
+ /**
7594
+ * Reads both facts from final render content in one place. `_frameStyles` is
7595
+ * the compiled frame; content without one (no page design, an SDK block)
7596
+ * draws a plain frame.
7597
+ */
7598
+ function sectionFactsForContent(content) {
7599
+ return {
7600
+ endpoint: boundaryEndpointForSectionContent(content),
7601
+ frame: frameKindForContent(content)
7602
+ };
7603
+ }
7604
+ function frameKindForContent(content) {
7605
+ const frameStyles = content._frameStyles;
7606
+ const kind = typeof frameStyles === "object" && frameStyles !== null && "kind" in frameStyles ? frameStyles.kind : null;
7607
+ return kind === "panel" || kind === "flush-panel" ? kind : "plain";
7608
+ }
7609
+ /**
7610
+ * The frame rule: a flush panel owns the edge it runs to, so no break is
7611
+ * drawn next to one. The single home for this rule; the generated
7612
+ * pipeline's budget and the site-style lab ask it with their plan frames.
7613
+ */
7614
+ function frameNotShownReason(previous, next) {
7615
+ return previous === "flush-panel" || next === "flush-panel" ? "flush-panel" : null;
7616
+ }
7617
+ /**
7618
+ * Decides whether and how a join draws. The frame rule wins before paint is
7619
+ * considered; two unpainted sides show nothing.
7620
+ */
7621
+ function composeBoundary(previous, next) {
7622
+ const frameReason = frameNotShownReason(previous.frame, next.frame);
7623
+ if (frameReason) return {
7624
+ kind: "not-shown",
7625
+ reason: frameReason
7626
+ };
7627
+ const from = previous.endpoint;
7628
+ const to = next.endpoint;
7629
+ if (from.kind === "paint") return {
7630
+ kind: "shown",
7631
+ composition: to.kind === "paint" ? {
7632
+ kind: "paint-to-paint",
7633
+ from: from.token,
7634
+ to: to.token
7635
+ } : {
7636
+ kind: "paint-to-unpainted",
7637
+ from: from.token
7638
+ }
7639
+ };
7640
+ return to.kind === "paint" ? {
7641
+ kind: "shown",
7642
+ composition: {
7643
+ kind: "unpainted-to-paint",
7644
+ to: to.token
7645
+ }
7646
+ } : {
7647
+ kind: "not-shown",
7648
+ reason: "both-unpainted"
7649
+ };
7650
+ }
7651
+ /**
7652
+ * Placement, reserves and paint for a shown boundary, derived from the
7653
+ * composition and the catalogue's metrics for the treatment.
7654
+ */
7655
+ function boundaryLayout(intent, composition) {
7656
+ const metrics = boundaryRuntimeMetricsForDecorativeTransition(intent);
7657
+ switch (composition.kind) {
7658
+ case "paint-to-paint": return {
7659
+ placement: "centred",
7660
+ reserves: {
7661
+ before: metrics.reserveCss,
7662
+ after: metrics.reserveCss
7663
+ },
7664
+ paint: {
7665
+ band: composition.from,
7666
+ shape: composition.to
7667
+ }
7668
+ };
7669
+ case "unpainted-to-paint": return {
7670
+ placement: "previous",
7671
+ reserves: {
7672
+ before: metrics.heightCss,
7673
+ after: null
7674
+ },
7675
+ paint: {
7676
+ band: null,
7677
+ shape: composition.to
7678
+ }
7679
+ };
7680
+ case "paint-to-unpainted": return {
7681
+ placement: "next",
7682
+ reserves: {
7683
+ before: null,
7684
+ after: metrics.heightCss
7685
+ },
7686
+ paint: {
7687
+ band: null,
7688
+ shape: composition.from
7689
+ }
7690
+ };
7691
+ default: return assertNever$3(composition, "Unhandled boundary composition");
6854
7692
  }
7693
+ }
7694
+ function requireThemeColorToken$1(value) {
7695
+ const token = parseThemeColorToken(value);
7696
+ if (!token) throw new Error(`Expected a theme color token. Received: ${value}`);
7697
+ return token;
7698
+ }
7699
+ //#endregion
7700
+ //#region ../theme-core/src/site-styles/compilationPlan.ts
7701
+ const defaultHeaderBrandPreferences = {
7702
+ brandDisplay: "auto",
7703
+ logoSize: "auto"
6855
7704
  };
6856
- asBoundaryOptionId("break:expansive");
6857
- asBoundaryOptionId("break:wave");
6858
- asBoundaryOptionId("break:wave-balanced");
6859
- asBoundaryOptionId("break:wave-gentle");
6860
- asBoundaryOptionId("break:wave-bold");
6861
- asBoundaryOptionId("break:wave-drift");
6862
- asBoundaryOptionId("break:angle-subtle");
6863
- asBoundaryOptionId("break:angle-balanced");
6864
- asBoundaryOptionId("break:angle-steep");
6865
7705
  asBoundaryOptionId("break:unknown");
6866
7706
  const pageDesignUserPinnedAppearanceChoiceSchema = z.object({
6867
7707
  kind: z.literal("user-pinned"),
@@ -6988,14 +7828,11 @@ function validateUniquePageDesignPins(intent, ctx) {
6988
7828
  label: "block Look pin"
6989
7829
  });
6990
7830
  addDuplicateStringIssues(ctx, {
6991
- values: intent.boundaryPins.map((pin) => boundaryKey$1(pin.previousBlockId, pin.nextBlockId)),
7831
+ values: intent.boundaryPins.map((pin) => makeBoundaryId(pin.previousBlockId, pin.nextBlockId)),
6992
7832
  path: ["boundaryPins"],
6993
7833
  label: "boundary pin"
6994
7834
  });
6995
7835
  }
6996
- function boundaryKey$1(previousBlockId, nextBlockId) {
6997
- return makeBoundaryId(previousBlockId, nextBlockId);
6998
- }
6999
7836
  //#endregion
7000
7837
  //#region ../theme-core/src/buttons/types.ts
7001
7838
  /**
@@ -12223,90 +13060,6 @@ function resolveFooterRenderPlan(params) {
12223
13060
  }
12224
13061
  }
12225
13062
  //#endregion
12226
- //#region ../theme-core/src/lib/media.ts
12227
- const hotspotSchema = z.object({
12228
- x: z.number().min(0).max(1),
12229
- y: z.number().min(0).max(1),
12230
- radius: z.number().optional()
12231
- });
12232
- const rotationSchema = z.union([
12233
- z.literal(0),
12234
- z.literal(90),
12235
- z.literal(180),
12236
- z.literal(270)
12237
- ]);
12238
- const rectSchema = z.tuple([
12239
- z.number(),
12240
- z.number(),
12241
- z.number(),
12242
- z.number()
12243
- ]);
12244
- const aspectCropSchema = z.object({
12245
- aspect: z.preprocess((value) => value == null ? void 0 : value, z.string().optional()),
12246
- rect: z.preprocess((value) => value == null ? void 0 : value, rectSchema.optional()),
12247
- hotspot: z.preprocess((value) => value == null ? void 0 : value, hotspotSchema.optional()),
12248
- rotation: z.preprocess((value) => value == null ? void 0 : value, rotationSchema.optional())
12249
- });
12250
- const transformSchema = z.object({
12251
- aspectCrops: z.preprocess((value) => {
12252
- if (!value || typeof value !== "object" || Array.isArray(value)) return;
12253
- return value;
12254
- }, z.record(z.string(), aspectCropSchema.catch({}))).optional(),
12255
- rect: rectSchema.optional(),
12256
- hotspot: hotspotSchema.optional(),
12257
- rotation: rotationSchema.optional()
12258
- });
12259
- const mediaPosterSchema = z.discriminatedUnion("tag", [z.object({
12260
- tag: z.literal("storage"),
12261
- assetId: z.string().optional(),
12262
- storageBucket: z.string().min(1),
12263
- storagePath: z.string().min(1)
12264
- }), z.object({
12265
- tag: z.literal("external-src"),
12266
- assetId: z.string().optional(),
12267
- src: z.string().url()
12268
- })]);
12269
- const mediaBaseSchema = z.object({
12270
- purpose: z.string().optional(),
12271
- placeholder: z.boolean().default(true).optional(),
12272
- assetId: z.string().optional(),
12273
- identifier: z.string().optional(),
12274
- src: z.string().optional(),
12275
- alt: z.string().optional(),
12276
- filename: z.string().optional(),
12277
- mimeType: z.string().optional(),
12278
- width: z.number().optional(),
12279
- height: z.number().optional(),
12280
- storageBucket: z.string().optional(),
12281
- storagePath: z.string().optional(),
12282
- poster: mediaPosterSchema.optional(),
12283
- transform: transformSchema.optional()
12284
- });
12285
- const mediaIdentifierSchema = z.string().trim().min(1).brand();
12286
- const imageMediaSchema = mediaBaseSchema.extend({ type: z.literal("image") });
12287
- const videoMediaSchema = mediaBaseSchema.extend({ type: z.literal("video") });
12288
- const audioMediaSchema = mediaBaseSchema.extend({ type: z.literal("audio") });
12289
- const documentMediaSchema = mediaBaseSchema.extend({ type: z.literal("document") });
12290
- const spreadsheetMediaSchema = mediaBaseSchema.extend({ type: z.literal("spreadsheet") });
12291
- const archiveMediaSchema = mediaBaseSchema.extend({ type: z.literal("archive") });
12292
- const mediaSchema = z.discriminatedUnion("type", [
12293
- imageMediaSchema,
12294
- videoMediaSchema,
12295
- audioMediaSchema,
12296
- documentMediaSchema,
12297
- spreadsheetMediaSchema,
12298
- archiveMediaSchema
12299
- ]);
12300
- const persistableMediaBaseSchema = mediaBaseSchema.extend({ identifier: mediaIdentifierSchema });
12301
- z.discriminatedUnion("type", [
12302
- persistableMediaBaseSchema.extend({ type: z.literal("image") }),
12303
- persistableMediaBaseSchema.extend({ type: z.literal("video") }),
12304
- persistableMediaBaseSchema.extend({ type: z.literal("audio") }),
12305
- persistableMediaBaseSchema.extend({ type: z.literal("document") }),
12306
- persistableMediaBaseSchema.extend({ type: z.literal("spreadsheet") }),
12307
- persistableMediaBaseSchema.extend({ type: z.literal("archive") })
12308
- ]);
12309
- //#endregion
12310
13063
  //#region ../theme-core/src/palette/schema.ts
12311
13064
  const colourHexString = z.string().regex(/^#([0-9A-Fa-f]{3}){1,2}$/);
12312
13065
  const paletteColorSchema = z.object({
@@ -14861,112 +15614,6 @@ function affordanceIconMaskUrl(iconId) {
14861
15614
  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, " ")}`;
14862
15615
  }
14863
15616
  //#endregion
14864
- //#region ../theme-core/src/utils/tokenOrColor.ts
14865
- /**
14866
- * Runtime color-reference fields intentionally accept any string, including
14867
- * the legacy empty-string "no color" sentinel. Semantic validation needs
14868
- * palette context and happens in `parseThemeColorReference`.
14869
- */
14870
- const tokenOrCssColorValueSchema = z.string();
14871
- const CSS_COLOR_FUNCTION_PATTERN = /^(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\(/i;
14872
- const CSS_HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
14873
- const CSS_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
14874
- "transparent",
14875
- "currentcolor",
14876
- "inherit",
14877
- "initial",
14878
- "revert",
14879
- "revert-layer",
14880
- "unset"
14881
- ]);
14882
- const COMMON_CSS_NAMED_COLORS = /* @__PURE__ */ new Set([
14883
- "black",
14884
- "blue",
14885
- "gray",
14886
- "green",
14887
- "grey",
14888
- "orange",
14889
- "pink",
14890
- "purple",
14891
- "red",
14892
- "white",
14893
- "yellow"
14894
- ]);
14895
- function isCssColorLiteral(value) {
14896
- const trimmed = value.trim();
14897
- if (!trimmed) return false;
14898
- return CSS_HEX_COLOR_PATTERN.test(trimmed) || CSS_COLOR_FUNCTION_PATTERN.test(trimmed) || /^var\(/i.test(trimmed) || CSS_COLOR_KEYWORDS.has(trimmed.toLowerCase());
14899
- }
14900
- function parseThemeToken(token) {
14901
- const trimmed = token.trim();
14902
- const match = trimmed.match(/^(.*?)-(\d{2,3})$/);
14903
- if (!match) return {
14904
- role: trimmed,
14905
- shade: null
14906
- };
14907
- const role = match[1] ?? trimmed;
14908
- const shade = match[2] ? Number(match[2]) : null;
14909
- return {
14910
- role,
14911
- shade: Number.isFinite(shade) ? shade : null
14912
- };
14913
- }
14914
- function parseContextThemeColorToken(value, context) {
14915
- const parts = parseColorTokenParts(value);
14916
- if (parts.kind !== "token") return null;
14917
- if (!(Boolean(context?.palette?.[value.trim()]) || parts.opacityPercent !== null && Boolean(context?.palette?.[parts.token]))) return null;
14918
- return value.trim();
14919
- }
14920
- function isTokenShapedTypo(value) {
14921
- if (COMMON_CSS_NAMED_COLORS.has(value.toLowerCase())) return false;
14922
- if (!/^[a-zA-Z][a-zA-Z0-9]*(?:-[a-zA-Z0-9]+)*(?:\/[^/]+)?$/.test(value)) return false;
14923
- return !isCssColorLiteral(value);
14924
- }
14925
- function warnUnknownThemeColorReference(value) {
14926
- if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
14927
- if (!isTokenShapedTypo(value)) return;
14928
- console.warn(`[theme-core] Unknown Theme V2 color token "${value}" treated as literal CSS. Use raw:* for deliberate custom CSS values.`);
14929
- }
14930
- function parseThemeColorReference(input, context) {
14931
- const trimmed = input.trim();
14932
- if (trimmed.startsWith("raw:")) return {
14933
- kind: "literal-css",
14934
- value: trimmed.slice(4)
14935
- };
14936
- const contextToken = parseContextThemeColorToken(trimmed, context);
14937
- if (contextToken) return {
14938
- kind: "palette-context-token",
14939
- token: contextToken
14940
- };
14941
- const knownToken = parseThemeColorToken(trimmed);
14942
- if (knownToken) return {
14943
- kind: "theme-token",
14944
- token: knownToken
14945
- };
14946
- warnUnknownThemeColorReference(trimmed);
14947
- return {
14948
- kind: "literal-css",
14949
- value: trimmed
14950
- };
14951
- }
14952
- function resolveThemeColorReferenceValue(input, context) {
14953
- const reference = parseThemeColorReference(input, context);
14954
- return reference.kind === "literal-css" ? reference.value : rgbColorValue(reference.token);
14955
- }
14956
- function resolveTokenOrCssColorValue(input, context) {
14957
- return resolveThemeColorReferenceValue(input, context);
14958
- }
14959
- function resolveOnColorForThemeToken(token) {
14960
- if (!parseThemeColorToken(token)) return null;
14961
- const parts = parseColorTokenParts(token);
14962
- if (parts.kind !== "token") return null;
14963
- const { role, shade } = parseThemeToken(parts.token);
14964
- const normalizedRole = role.trim();
14965
- if (!normalizedRole) return null;
14966
- if (!shade) return rgbColorValue(`on${normalizedRole.charAt(0).toUpperCase() + normalizedRole.slice(1)}`);
14967
- return shade <= 400 ? "#000000" : "#FFFFFF";
14968
- }
14969
- //#endregion
14970
15617
  //#region ../theme-core/src/buttons/buttonSurfaceContext.ts
14971
15618
  /**
14972
15619
  * Derive only what the rendered section can honestly prove about its backing.
@@ -15368,7 +16015,7 @@ const LETTER_SPACING_MAP = {
15368
16015
  normal: "0",
15369
16016
  loose: "0.05em"
15370
16017
  };
15371
- function rule(selector, declarations) {
16018
+ function rule$1(selector, declarations) {
15372
16019
  return {
15373
16020
  selectors: typeof selector === "string" ? [selector] : selector,
15374
16021
  declarations
@@ -15551,7 +16198,7 @@ function variantAffordanceRule(variant, selector) {
15551
16198
  const maskUrl = affordanceIconMaskUrl(affordance.iconId);
15552
16199
  if (!maskUrl) return null;
15553
16200
  const mask = `url("${maskUrl}") no-repeat center / contain`;
15554
- return rule(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
16201
+ return rule$1(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
15555
16202
  ["content", "\"\""],
15556
16203
  ["display", "inline-block"],
15557
16204
  ["width", "1em"],
@@ -15590,7 +16237,7 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
15590
16237
  const borderWidth = BORDER_WIDTH_MAP[variant.border.widthClass] || "1px";
15591
16238
  decls.push(["border", `${borderWidth} solid currentColor`]);
15592
16239
  }
15593
- return rule(selector, decls);
16240
+ return rule$1(selector, decls);
15594
16241
  }
15595
16242
  /**
15596
16243
  * Resolve a colour for a button that does not own its own background.
@@ -15601,13 +16248,13 @@ function variantBaseRule(variant, selector, global, tokens, theme, colour) {
15601
16248
  */
15602
16249
  function variantStateColourRule(variantId, themeId, state, colour) {
15603
16250
  const baseSelector = `:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}`;
15604
- return rule(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
16251
+ return rule$1(state === "hover" ? [`${baseSelector}:hover`, `${baseSelector}[data-force-hover="true"]`] : `${baseSelector}:${state}`, resolvedButtonColourDeclarations(colour, {
15605
16252
  role: variantId,
15606
16253
  state
15607
16254
  }));
15608
16255
  }
15609
16256
  function variantFocusVisibleRule(variantId, themeId, focusIndicator) {
15610
- return rule(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
16257
+ return rule$1(`:where([data-theme-scope="${themeId}"]) ${themeButtonSelector(variantId)}:focus-visible`, resolvedButtonFocusDeclarations(focusIndicator, variantId));
15611
16258
  }
15612
16259
  function variantEffectsCss(variant, effects, themeId, tokens, theme) {
15613
16260
  if (!effects) return "";
@@ -15633,10 +16280,10 @@ function variantDisabledRules(variantId, themeId, colour) {
15633
16280
  `${base}:focus`
15634
16281
  ];
15635
16282
  const pseudoSelectors = [`${base}::before`, `${base}::after`];
15636
- return [rule(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
16283
+ return [rule$1(disabledSelectors, [...resolvedButtonColourDeclarations(colour, {
15637
16284
  role: variantId,
15638
16285
  state: "disabled"
15639
- }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule(pseudoSelectors, [
16286
+ }), ...RESOLVED_BUTTON_DISABLED_DECLARATIONS]), rule$1(pseudoSelectors, [
15640
16287
  ["opacity", "0"],
15641
16288
  ["animation", "none"],
15642
16289
  ["pointer-events", "none"]
@@ -15651,7 +16298,7 @@ function variantSizeRules(variant, variantId, themeSizes, themeId) {
15651
16298
  const decls = [];
15652
16299
  for (const decl of paddingDeclarations(config.padding, borderWidth)) decls.push(decl);
15653
16300
  if (config.fontSize) decls.push(["font-size", FONT_SIZE_MAP[config.fontSize] || "1rem"]);
15654
- rules.push(rule(selector, decls));
16301
+ rules.push(rule$1(selector, decls));
15655
16302
  }
15656
16303
  return rules;
15657
16304
  }
@@ -16552,26 +17199,6 @@ function getComponentShadowVar(elevation) {
16552
17199
  return `var(--shadow-${elevation})`;
16553
17200
  }
16554
17201
  //#endregion
16555
- //#region ../theme-core/src/site-styles/rawStyleFields.ts
16556
- const themeV2RuntimeStyleFieldNames = [
16557
- "_frameStyles",
16558
- "_sectionStyles",
16559
- "_containerStyles",
16560
- "_cardStyles",
16561
- "_transitionStyles",
16562
- "styleGroup"
16563
- ];
16564
- const themeV2BlockSpecificRawStyleFieldNames = [
16565
- "_contentCardStyles",
16566
- "_heroLayoutStyles",
16567
- "_textCardStyles"
16568
- ];
16569
- const themeV2NormalEditorRawStyleFieldNames = [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
16570
- new Set(themeV2NormalEditorRawStyleFieldNames);
16571
- [...themeV2RuntimeStyleFieldNames, ...themeV2BlockSpecificRawStyleFieldNames];
16572
- const pageDesignEditorBackgroundOverrideOwner = "page-design-editor";
16573
- const siteChromeBackgroundOverrideOwner = "site-chrome-editor";
16574
- //#endregion
16575
17202
  //#region ../theme-core/src/header/legacyHeaderVariant.ts
16576
17203
  /**
16577
17204
  * The header variants the system actually renders.
@@ -16780,75 +17407,6 @@ const headerContainerSchema = z.object({
16780
17407
  opacity: z.number().min(0).max(1).default(.12)
16781
17408
  }).optional();
16782
17409
  //#endregion
16783
- //#region ../theme-core/src/boxBackgroundSchema.ts
16784
- const boxBackgroundOverlaySchema = z.preprocess(normalizeAuthoringOpacity, z.discriminatedUnion("type", [
16785
- z.object({ type: z.literal("none") }),
16786
- z.object({
16787
- type: z.literal("color"),
16788
- color: tokenOrCssColorValueSchema.nullable().optional(),
16789
- opacity: z.number().min(0).max(1).nullable().optional()
16790
- }),
16791
- z.object({
16792
- type: z.literal("gradient"),
16793
- gradient: z.string().nullable().optional(),
16794
- opacity: z.number().min(0).max(1).nullable().optional()
16795
- })
16796
- ]));
16797
- const commonSchema = z.object({
16798
- contentOwner: z.literal(pageDesignEditorBackgroundOverrideOwner).optional(),
16799
- textColor: tokenOrCssColorValueSchema.nullable().optional(),
16800
- headingColor: tokenOrCssColorValueSchema.nullable().optional(),
16801
- linkColor: tokenOrCssColorValueSchema.nullable().optional(),
16802
- supportingColor: tokenOrCssColorValueSchema.nullable().optional()
16803
- });
16804
- const mediaPresentationSchema = commonSchema.extend({
16805
- opacity: z.preprocess(normalizeOpacity, z.number().min(0).max(1).nullable()).optional(),
16806
- objectFit: z.enum([
16807
- "fill",
16808
- "fit",
16809
- "original",
16810
- "custom"
16811
- ]).nullable().optional(),
16812
- scale: z.string().nullable().optional(),
16813
- position: z.string().nullable().optional(),
16814
- motion: z.enum(["static", "fixed-frame"]).nullable().optional(),
16815
- overflow: z.boolean().nullable().optional(),
16816
- overlay: boxBackgroundOverlaySchema.nullable().optional()
16817
- });
16818
- const boxBackgroundSchema = z.discriminatedUnion("type", [
16819
- commonSchema.extend({
16820
- type: z.literal("color"),
16821
- color: tokenOrCssColorValueSchema.nullable().optional()
16822
- }),
16823
- commonSchema.extend({
16824
- type: z.literal("gradient"),
16825
- gradient: z.string().nullable().optional(),
16826
- gradientPresetId: z.string().min(1).nullable().optional()
16827
- }),
16828
- mediaPresentationSchema.extend({
16829
- type: z.literal("image"),
16830
- image: imageMediaSchema.nullable().optional()
16831
- }),
16832
- mediaPresentationSchema.extend({
16833
- type: z.literal("video"),
16834
- image: videoMediaSchema.nullable().optional(),
16835
- showVideoOnMobile: z.boolean().nullable().optional()
16836
- })
16837
- ]);
16838
- function normalizeAuthoringOpacity(value) {
16839
- if (!isRecord$15(value)) return value;
16840
- return {
16841
- ...value,
16842
- opacity: normalizeOpacity(value.opacity)
16843
- };
16844
- }
16845
- function normalizeOpacity(value) {
16846
- return typeof value === "number" && value > 1 && value <= 100 ? value / 100 : value;
16847
- }
16848
- function isRecord$15(value) {
16849
- return typeof value === "object" && value !== null && !Array.isArray(value);
16850
- }
16851
- //#endregion
16852
17410
  //#region ../theme-core/src/schema.ts
16853
17411
  const axesSchema = z.object({
16854
17412
  tone: z.enum([
@@ -17611,6 +18169,13 @@ z.object({
17611
18169
  fields: nonEmptyRuntimeStyleFieldNamesSchema,
17612
18170
  authority: styleAuthoritySchema
17613
18171
  }).strict();
18172
+ asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
18173
+ asPageFlowPresetId("generated-default");
18174
+ asSiteStyleId("site-style:south-west-twerk-school");
18175
+ asAppearancePresetId("hero-brand-scrim-fixed-frame");
18176
+ asLayoutVariantKey("classic");
18177
+ asLayoutVariantKey("micro");
18178
+ asBoundaryOptionId("break:wave");
17614
18179
  //#endregion
17615
18180
  //#region ../theme-core/src/site-styles/runtimeCompiler.ts
17616
18181
  function defineSectionForegroundRuntimeTokens(input) {
@@ -17634,7 +18199,7 @@ function defineComponentRuntimeRules(rules) {
17634
18199
  }
17635
18200
  /**
17636
18201
  * Default design rules translate semantic page-level choices into the current
17637
- * runtime's section background and transition style-group vocabulary.
18202
+ * runtime's section background vocabulary.
17638
18203
  */
17639
18204
  const defaultDesignRuntimeRules = defineDesignRuntimeRules({
17640
18205
  sectionSurfaces: {
@@ -17669,8 +18234,7 @@ const defaultDesignRuntimeRules = defineDesignRuntimeRules({
17669
18234
  color: "surface",
17670
18235
  opacity: .88
17671
18236
  }
17672
- },
17673
- transitionClassName: (transition, fromSurface, toSurface) => transition === "none" ? null : `site-style-transition-${transition}-from-${fromSurface}-to-${toSurface}`
18237
+ }
17674
18238
  });
17675
18239
  /**
17676
18240
  * Default component rules translate frame and item-surface semantics into the
@@ -17724,37 +18288,6 @@ function uniformSectionForeground(token) {
17724
18288
  supportingColor: token
17725
18289
  };
17726
18290
  }
17727
- function compileTransitionRuntime(input) {
17728
- return {
17729
- treatment: input.treatment,
17730
- from: input.from,
17731
- to: input.to,
17732
- cssVars: {
17733
- from: input.from.kind === "surface" && input.from.surface === "hero-image" ? "transparent" : transitionEndpointCssValue(input.from),
17734
- to: transitionEndpointCssValue(input.to)
17735
- }
17736
- };
17737
- }
17738
- function transitionEndpointCssValue(endpoint) {
17739
- switch (endpoint.kind) {
17740
- case "color-token": return themeColorTokenCssValue(endpoint.token);
17741
- case "surface": return themeColorTokenCssValue(tokenForTransitionSurface(endpoint.surface));
17742
- default: return assertNever$3(endpoint);
17743
- }
17744
- }
17745
- function tokenForTransitionSurface(surface) {
17746
- switch (surface) {
17747
- case "base":
17748
- case "hero-image": return "background";
17749
- case "muted-band": return "muted";
17750
- case "accent-band": return "accent";
17751
- case "brand-depth-band": return "primary-800";
17752
- default: return assertNever$3(surface);
17753
- }
17754
- }
17755
- function themeColorTokenCssValue(token) {
17756
- return `rgb(var(--tb-${token}))`;
17757
- }
17758
18291
  //#endregion
17759
18292
  //#region ../theme-core/src/site-styles/curatedSiteStyles.ts
17760
18293
  const SOFT_EARTH_SITE_STYLE_ID_VALUE = "site-style:soft-earth";
@@ -19439,19 +19972,11 @@ const curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id
19439
19972
  function getCuratedSiteStyle(id) {
19440
19973
  return curatedSiteStylesById.get(id) ?? null;
19441
19974
  }
19442
- asPageFlowPresetId("generated-default");
19443
- asSiteStyleId("site-style:south-west-twerk-school");
19444
- asAppearancePresetId("hero-brand-scrim-fixed-frame");
19445
- asLayoutVariantKey("classic");
19446
- asLayoutVariantKey("micro");
19447
- asBoundaryOptionId("break:wave");
19448
- asPageFlowPresetId("flow:quiet"), noneBoundaryOption.id, asPageFlowPresetId("flow:balanced"), softBoundaryOption.id, asPageFlowPresetId("flow:expressive"), spaciousBoundaryOption.id;
19449
19975
  //#endregion
19450
19976
  //#region ../theme-core/src/site-styles/resolver.ts
19451
19977
  const blockDesignFallback = {
19452
19978
  sectionSurface: "base",
19453
19979
  contentFrame: plainFrame,
19454
- transitionAfter: "none",
19455
19980
  sectionHeight: "default",
19456
19981
  heroLegibility: "none",
19457
19982
  imageSurfaceTreatment: { kind: "none" },
@@ -19462,7 +19987,7 @@ const blockDesignFallback = {
19462
19987
  accordionIconTreatment: null,
19463
19988
  composition: null
19464
19989
  };
19465
- blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.transitionAfter, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
19990
+ blockDesignFallback.sectionSurface, blockDesignFallback.contentFrame, blockDesignFallback.sectionHeight, blockDesignFallback.heroLegibility, blockDesignFallback.imageSurfaceTreatment, blockDesignFallback.imageSurfaceMotion, blockDesignFallback.tileTreatment, blockDesignFallback.eventListTreatment, blockDesignFallback.eyebrowTreatment, blockDesignFallback.accordionIconTreatment, blockDesignFallback.composition;
19466
19991
  //#endregion
19467
19992
  //#region ../theme-core/src/site-styles/siteChromeBackgrounds.ts
19468
19993
  const siteChromeBackgroundColorTokens = toSiteChromeBackgroundColorTokens(backgroundAppearanceColorTokenNames);
@@ -19550,37 +20075,6 @@ function readHeaderNavFontSizePreference(value) {
19550
20075
  const headerBrandPresentationValues = ["recommended", "plain"];
19551
20076
  defaultHeaderBrandPreferences.brandDisplay, defaultHeaderBrandPreferences.logoSize, defaultHeaderNavPreferences.typography, defaultHeaderNavPreferences.fontSize, defaultHeaderNavPreferences.interactionPalette;
19552
20077
  //#endregion
19553
- //#region ../theme-core/src/site-styles/previewCompiler.ts
19554
- function retargetCompiledBoundaryRuntimeForContentAdjacency(boundary, adjacency) {
19555
- const fromSurface = contentOwnedBackgroundEndpoint(adjacency.currentContent) ?? boundary.from;
19556
- const toSurface = contentOwnedBackgroundEndpoint(adjacency.nextContent) ?? boundary.to;
19557
- if (fromSurface === boundary.from && toSurface === boundary.to) return boundary;
19558
- const transition = compileTransitionRuntime({
19559
- treatment: boundary.treatment,
19560
- from: fromSurface,
19561
- to: toSurface
19562
- });
19563
- return {
19564
- ...boundary,
19565
- from: transition.from,
19566
- to: transition.to,
19567
- cssVars: transition.cssVars
19568
- };
19569
- }
19570
- function contentOwnedBackgroundEndpoint(content) {
19571
- const background = readRecord(readRecord(content?._sectionStyles)?.background);
19572
- if (background?.contentOwner !== "page-design-editor" || background.type !== "color") return null;
19573
- const token = parseThemeColorToken(background.color);
19574
- if (!token) return null;
19575
- return {
19576
- kind: "color-token",
19577
- token
19578
- };
19579
- }
19580
- function readRecord(value) {
19581
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
19582
- }
19583
- //#endregion
19584
20078
  //#region ../theme-core/src/site-styles/generatedSchemas.ts
19585
20079
  function uniqueNonEmpty(values) {
19586
20080
  const uniqueValues = [...new Set(values)];
@@ -23005,7 +23499,7 @@ const teamMemberFragment = defineFragment({
23005
23499
  //#region ../blocks/src/system/data/linkedContentEntries.ts
23006
23500
  const LINKED_CONTENT_ENTRY_META_KEY = "__rbLinkedContentEntry";
23007
23501
  function coerceLinkedContentEntriesPayload(payload) {
23008
- if (!isRecord$14(payload)) return payload;
23502
+ if (!isRecord$15(payload)) return payload;
23009
23503
  const response = payload;
23010
23504
  if (!Array.isArray(response.entries)) return payload;
23011
23505
  const contentType = parsePublicContentTypeMeta(response.contentType);
@@ -23013,16 +23507,16 @@ function coerceLinkedContentEntriesPayload(payload) {
23013
23507
  return response.entries.map((entry) => enrichLinkedContentEntry(entry, contentType));
23014
23508
  }
23015
23509
  function getLinkedContentEntryMeta(value) {
23016
- if (!isRecord$14(value)) return null;
23510
+ if (!isRecord$15(value)) return null;
23017
23511
  const candidate = value[LINKED_CONTENT_ENTRY_META_KEY];
23018
23512
  return isLinkedContentEntryMeta(candidate) ? candidate : null;
23019
23513
  }
23020
23514
  function buildLinkedContentEditTarget(input) {
23021
23515
  const { entry, sourceBlockId, sourceBlockKind, itemIndex } = input;
23022
- if (!isRecord$14(entry)) return null;
23516
+ if (!isRecord$15(entry)) return null;
23023
23517
  const meta = getLinkedContentEntryMeta(entry);
23024
23518
  if (!meta || meta.support.kind !== "supported") return null;
23025
- const content = isRecord$14(entry.content) ? entry.content : {};
23519
+ const content = isRecord$15(entry.content) ? entry.content : {};
23026
23520
  return {
23027
23521
  kind: "linked-content-entry",
23028
23522
  entryId: meta.entryId,
@@ -23086,12 +23580,12 @@ function resolveLinkedEntryInlineFieldEligibility(input) {
23086
23580
  };
23087
23581
  }
23088
23582
  function enrichLinkedContentEntry(entry, contentType) {
23089
- if (!isRecord$14(entry)) return entry;
23583
+ if (!isRecord$15(entry)) return entry;
23090
23584
  const entryId = typeof entry.id === "string" ? entry.id : null;
23091
23585
  if (!entryId) return entry;
23092
23586
  const title = typeof entry.title === "string" && entry.title.trim().length > 0 ? entry.title : entryId;
23093
23587
  const optimisticHash = typeof entry.optimisticHash === "string" && entry.optimisticHash.length > 0 ? entry.optimisticHash : null;
23094
- const content = isRecord$14(entry.content) ? entry.content : {};
23588
+ const content = isRecord$15(entry.content) ? entry.content : {};
23095
23589
  const meta = {
23096
23590
  kind: "linked-content-entry",
23097
23591
  entryId,
@@ -23187,7 +23681,7 @@ function capitalize(value) {
23187
23681
  return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
23188
23682
  }
23189
23683
  function parsePublicContentTypeMeta(value) {
23190
- if (!isRecord$14(value)) return null;
23684
+ if (!isRecord$15(value)) return null;
23191
23685
  if (typeof value.key !== "string" || value.key.trim().length === 0) return null;
23192
23686
  return {
23193
23687
  key: value.key,
@@ -23197,7 +23691,7 @@ function parsePublicContentTypeMeta(value) {
23197
23691
  };
23198
23692
  }
23199
23693
  function parsePublicContentTypeLinkedEntryEditing(value) {
23200
- if (!isRecord$14(value) || typeof value.kind !== "string") return null;
23694
+ if (!isRecord$15(value) || typeof value.kind !== "string") return null;
23201
23695
  switch (value.kind) {
23202
23696
  case "generic": return { kind: "generic" };
23203
23697
  case "unsupported-domain-backed": return {
@@ -23208,7 +23702,7 @@ function parsePublicContentTypeLinkedEntryEditing(value) {
23208
23702
  }
23209
23703
  }
23210
23704
  function parsePublicContentTypeFieldMeta(value) {
23211
- if (!isRecord$14(value)) return [];
23705
+ if (!isRecord$15(value)) return [];
23212
23706
  if (typeof value.id !== "string" || typeof value.type !== "string") return [];
23213
23707
  return [{
23214
23708
  id: value.id,
@@ -23223,14 +23717,14 @@ function parseStringArray(value) {
23223
23717
  return value.filter((item) => typeof item === "string");
23224
23718
  }
23225
23719
  function isLinkedContentEntryMeta(value) {
23226
- if (!isRecord$14(value)) return false;
23720
+ if (!isRecord$15(value)) return false;
23227
23721
  if (value.kind !== "linked-content-entry") return false;
23228
23722
  if (typeof value.entryId !== "string") return false;
23229
23723
  if (typeof value.contentTypeKey !== "string") return false;
23230
23724
  if (typeof value.contentTypeLabel !== "string") return false;
23231
23725
  if (typeof value.title !== "string") return false;
23232
23726
  if (!(value.optimisticHash === null || typeof value.optimisticHash === "string")) return false;
23233
- if (!isRecord$14(value.contentType)) return false;
23727
+ if (!isRecord$15(value.contentType)) return false;
23234
23728
  if (!isLinkedEntryFieldSourceArray(value.fieldSources)) return false;
23235
23729
  return isLinkedContentEditSupport(value.support);
23236
23730
  }
@@ -23238,7 +23732,7 @@ function isLinkedEntryFieldSourceArray(value) {
23238
23732
  return Array.isArray(value) && value.every(isLinkedEntryFieldSource);
23239
23733
  }
23240
23734
  function isLinkedEntryFieldSource(value) {
23241
- if (!isRecord$14(value)) return false;
23735
+ if (!isRecord$15(value)) return false;
23242
23736
  if (typeof value.displayPath !== "string") return false;
23243
23737
  switch (value.kind) {
23244
23738
  case "content-field": return typeof value.fieldPath === "string" && (value.fieldType === "text" || value.fieldType === "richText");
@@ -23259,7 +23753,7 @@ function isLinkedEntryInlineFieldReadOnlyReason(value) {
23259
23753
  }
23260
23754
  }
23261
23755
  function isLinkedContentEditSupport(value) {
23262
- if (!isRecord$14(value) || typeof value.kind !== "string") return false;
23756
+ if (!isRecord$15(value) || typeof value.kind !== "string") return false;
23263
23757
  switch (value.kind) {
23264
23758
  case "supported": return true;
23265
23759
  case "unsupported-domain-backed":
@@ -23268,7 +23762,7 @@ function isLinkedContentEditSupport(value) {
23268
23762
  default: return false;
23269
23763
  }
23270
23764
  }
23271
- function isRecord$14(value) {
23765
+ function isRecord$15(value) {
23272
23766
  return typeof value === "object" && value !== null && !Array.isArray(value);
23273
23767
  }
23274
23768
  const inlineTextMarkSchema = z.discriminatedUnion("kind", [
@@ -26180,7 +26674,7 @@ function resolveRouteHrefForStage(routeIdValue, routes, stageInput) {
26180
26674
  /**
26181
26675
  * Strict record guard: excludes null, primitives, and arrays.
26182
26676
  */
26183
- function isRecord$13(value) {
26677
+ function isRecord$14(value) {
26184
26678
  return typeof value === "object" && value !== null && !Array.isArray(value);
26185
26679
  }
26186
26680
  /**
@@ -26306,16 +26800,16 @@ const interpolateRichTextTokensOptionsSchema = z.object({ tokens: z.array(suppor
26306
26800
  const richTextTokenPattern = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
26307
26801
  function getSiteName(context) {
26308
26802
  const viewModel = context.viewModel;
26309
- if (!isRecord$13(viewModel)) return null;
26803
+ if (!isRecord$14(viewModel)) return null;
26310
26804
  const site = viewModel.site;
26311
- if (isRecord$13(site) && typeof site.title === "string") {
26805
+ if (isRecord$14(site) && typeof site.title === "string") {
26312
26806
  const title = site.title.trim();
26313
26807
  return title.length > 0 ? title : null;
26314
26808
  }
26315
26809
  const root = viewModel.$root;
26316
- if (isRecord$13(root)) {
26810
+ if (isRecord$14(root)) {
26317
26811
  const rootSite = root.site;
26318
- if (isRecord$13(rootSite) && typeof rootSite.title === "string") {
26812
+ if (isRecord$14(rootSite) && typeof rootSite.title === "string") {
26319
26813
  const title = rootSite.title.trim();
26320
26814
  if (title.length > 0) return title;
26321
26815
  }
@@ -26337,7 +26831,7 @@ function replaceTokenText(input, allowedTokens, tokenValues) {
26337
26831
  });
26338
26832
  }
26339
26833
  function visitRichTextNode(node, allowedTokens, tokenValues) {
26340
- if (!isRecord$13(node)) return node;
26834
+ if (!isRecord$14(node)) return node;
26341
26835
  let changed = false;
26342
26836
  const nextNode = { ...node };
26343
26837
  if (nextNode.type === "text" && typeof nextNode.text === "string") {
@@ -26358,7 +26852,7 @@ function visitRichTextNode(node, allowedTokens, tokenValues) {
26358
26852
  return changed ? nextNode : node;
26359
26853
  }
26360
26854
  function interpolateRichTextTokens(value, options, context) {
26361
- if (!isRecord$13(value)) return value;
26855
+ if (!isRecord$14(value)) return value;
26362
26856
  return visitRichTextNode(value, new Set(options.tokens), {
26363
26857
  year: String((/* @__PURE__ */ new Date()).getFullYear()),
26364
26858
  site_name: getSiteName(context)
@@ -27820,11 +28314,11 @@ var UnsafePathSegmentError = class extends Error {
27820
28314
  function assertSafePathSegments(segments) {
27821
28315
  for (const segment of segments) if (typeof segment === "string" && UNSAFE_PATH_SEGMENTS.has(segment)) throw new UnsafePathSegmentError(segment);
27822
28316
  }
27823
- function isRecord$12(value) {
28317
+ function isRecord$13(value) {
27824
28318
  return typeof value === "object" && value !== null && !Array.isArray(value);
27825
28319
  }
27826
28320
  function isPathContainer(value) {
27827
- return Array.isArray(value) || isRecord$12(value);
28321
+ return Array.isArray(value) || isRecord$13(value);
27828
28322
  }
27829
28323
  function getContainerValue(container, segment) {
27830
28324
  if (typeof segment === "number") return Array.isArray(container) ? container[segment] : void 0;
@@ -29579,7 +30073,7 @@ function parseSiteIconStyleGuideVersionId(value) {
29579
30073
  //#endregion
29580
30074
  //#region ../blocks/src/system/icons/value.ts
29581
30075
  const NO_BLOCK_ICON_VALUE = { kind: "none" };
29582
- function isRecord$11(value) {
30076
+ function isRecord$12(value) {
29583
30077
  return typeof value === "object" && value !== null && !Array.isArray(value);
29584
30078
  }
29585
30079
  function parseBlockIconValue(value) {
@@ -29587,7 +30081,7 @@ function parseBlockIconValue(value) {
29587
30081
  kind: "semantic",
29588
30082
  iconId: value
29589
30083
  };
29590
- if (!isRecord$11(value)) return NO_BLOCK_ICON_VALUE;
30084
+ if (!isRecord$12(value)) return NO_BLOCK_ICON_VALUE;
29591
30085
  switch (value.kind) {
29592
30086
  case "none": return NO_BLOCK_ICON_VALUE;
29593
30087
  case "semantic": return isSemanticIconId(value.iconId) ? {
@@ -29660,7 +30154,7 @@ function buildGeneratedBlockIconRenderAssetLookupFromUnknown(value) {
29660
30154
  if (!Array.isArray(value)) return { kind: "invalid_asset_list" };
29661
30155
  const inputs = [];
29662
30156
  for (const [index, item] of value.entries()) {
29663
- if (!isRecord$11(item)) return {
30157
+ if (!isRecord$12(item)) return {
29664
30158
  kind: "invalid_asset_entry",
29665
30159
  index,
29666
30160
  reason: "not_object"
@@ -30737,18 +31231,43 @@ const publicAppointmentServiceSchema = z.object({
30737
31231
  pricing: publicPricingSchema.optional(),
30738
31232
  priceAmount: z.number().nullable(),
30739
31233
  priceCurrency: z.string().nullable(),
30740
- location: publicLocationSchema
31234
+ location: publicLocationSchema,
31235
+ categoryId: z.string().nullable().optional().transform((value) => value ?? null)
30741
31236
  });
30742
- const publicAppointmentServicesSchema = z.array(publicAppointmentServiceSchema);
31237
+ const publicServiceCategorySchema = z.object({
31238
+ id: z.string().min(1),
31239
+ name: z.string(),
31240
+ description: z.string().nullable().optional().transform((value) => value ?? null)
31241
+ });
31242
+ /**
31243
+ * The loader hands over the catalogue `{ services, categories }` (issue 1403).
31244
+ * A bare services array is still accepted and read as a site with no
31245
+ * categories, so callers from before categories keep the flat list.
31246
+ */
31247
+ const publicAppointmentServicesSchema = z.union([z.array(publicAppointmentServiceSchema).transform((services) => ({
31248
+ services,
31249
+ categories: []
31250
+ })), z.object({
31251
+ services: z.array(publicAppointmentServiceSchema),
31252
+ categories: z.array(publicServiceCategorySchema)
31253
+ })]);
30743
31254
  const EMPTY_MESSAGE = "No appointments are available right now.";
30744
31255
  const ERROR_MESSAGE = "Appointments are temporarily unavailable. Please try again shortly.";
31256
+ const OTHER_GROUP_KEY = "other";
31257
+ const OTHER_GROUP_HEADING = "Other";
30745
31258
  function parseServices(value) {
30746
31259
  const result = publicAppointmentServicesSchema.safeParse(value);
30747
31260
  return result.success ? {
30748
31261
  kind: "valid",
30749
- services: result.data
31262
+ services: result.data.services,
31263
+ categories: result.data.categories
30750
31264
  } : { kind: "invalid" };
30751
31265
  }
31266
+ function readCategoryFilter(value) {
31267
+ if (typeof value !== "string") return null;
31268
+ const trimmed = value.trim();
31269
+ return trimmed.length > 0 ? trimmed : null;
31270
+ }
30752
31271
  function formatAppointmentDuration(durationMinutes) {
30753
31272
  return `${durationMinutes} min`;
30754
31273
  }
@@ -30822,8 +31341,44 @@ function buildRows(services, pathname, servicePathPrefix) {
30822
31341
  detailsHref: servicePathPrefix && service.slug ? `${servicePathPrefix.replace(/\/$/, "")}/${encodeURIComponent(service.slug)}` : null
30823
31342
  }));
30824
31343
  }
31344
+ const EMPTY_VIEW = {
31345
+ kind: "empty",
31346
+ message: EMPTY_MESSAGE
31347
+ };
31348
+ function categoryGroup(category, services, toRows) {
31349
+ return {
31350
+ key: category.id,
31351
+ heading: category.name,
31352
+ intro: category.description,
31353
+ rows: toRows(services)
31354
+ };
31355
+ }
31356
+ function viewFromLayout(layout, toRows) {
31357
+ switch (layout.kind) {
31358
+ case "uncategorized-site": return layout.services.length === 0 ? EMPTY_VIEW : {
31359
+ kind: "items",
31360
+ rows: toRows(layout.services)
31361
+ };
31362
+ case "categorized-site": {
31363
+ const groups = layout.groups.map((group) => categoryGroup(group.category, group.services, toRows));
31364
+ const other = layout.other.length === 0 ? [] : [{
31365
+ key: OTHER_GROUP_KEY,
31366
+ heading: OTHER_GROUP_HEADING,
31367
+ intro: null,
31368
+ rows: toRows(layout.other)
31369
+ }];
31370
+ const allGroups = [...groups, ...other];
31371
+ return allGroups.length === 0 ? EMPTY_VIEW : {
31372
+ kind: "groups",
31373
+ groups: allGroups
31374
+ };
31375
+ }
31376
+ default: return assertNever$4(layout);
31377
+ }
31378
+ }
30825
31379
  function buildAppointmentServicesView(input) {
30826
- if (!input.pathname) return {
31380
+ const pathname = input.pathname;
31381
+ if (!pathname) return {
30827
31382
  kind: "error",
30828
31383
  message: ERROR_MESSAGE
30829
31384
  };
@@ -30832,76 +31387,128 @@ function buildAppointmentServicesView(input) {
30832
31387
  kind: "error",
30833
31388
  message: ERROR_MESSAGE
30834
31389
  };
30835
- if (parsed.services.length === 0) return {
30836
- kind: "empty",
30837
- message: EMPTY_MESSAGE
30838
- };
30839
- return {
30840
- kind: "items",
30841
- rows: buildRows(parsed.services, input.pathname, input.servicePathPrefix ?? null)
30842
- };
31390
+ const toRows = (services) => buildRows(services, pathname, input.servicePathPrefix ?? null);
31391
+ const view = selectCategoryView(parsed.categories, parsed.services, readCategoryFilter(input.categoryId));
31392
+ switch (view.kind) {
31393
+ case "single": return view.group.services.length === 0 ? EMPTY_VIEW : {
31394
+ kind: "groups",
31395
+ groups: [categoryGroup(view.group.category, view.group.services, toRows)]
31396
+ };
31397
+ case "all":
31398
+ case "fallback-all": return viewFromLayout(view.layout, toRows);
31399
+ default: return assertNever$4(view);
31400
+ }
30843
31401
  }
30844
- function AppointmentServicesNode({ sectionHeading, services, pathname, servicePathPrefix, className }) {
31402
+ function AppointmentServiceRows({ rows, RowTitle }) {
31403
+ return /* @__PURE__ */ jsx("div", {
31404
+ className: "appointment-services-rows rb-flex rb-flex-col",
31405
+ children: rows.map((row) => /* @__PURE__ */ jsxs("article", {
31406
+ className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
31407
+ children: [
31408
+ /* @__PURE__ */ jsxs("div", {
31409
+ className: "appointment-services-row-main rb-min-w-0",
31410
+ children: [
31411
+ /* @__PURE__ */ jsx(RowTitle, {
31412
+ className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
31413
+ children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
31414
+ href: row.detailsHref,
31415
+ children: row.title
31416
+ }) : row.title
31417
+ }),
31418
+ row.description ? /* @__PURE__ */ jsx("p", {
31419
+ className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
31420
+ children: row.description
31421
+ }) : null,
31422
+ /* @__PURE__ */ jsxs("p", {
31423
+ className: "appointment-services-row-meta rb-caption status-muted",
31424
+ children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
31425
+ })
31426
+ ]
31427
+ }),
31428
+ row.priceLabel ? /* @__PURE__ */ jsx("span", {
31429
+ className: "appointment-services-row-price rb-text-lg",
31430
+ children: row.priceLabel
31431
+ }) : null,
31432
+ /* @__PURE__ */ jsx(ThemedButtonLink, {
31433
+ variant: "secondary",
31434
+ size: "sm",
31435
+ extraClassName: "appointment-services-row-action",
31436
+ href: row.href,
31437
+ label: "Check times",
31438
+ screenReaderSuffix: ` for ${row.title}`
31439
+ })
31440
+ ]
31441
+ }, row.id))
31442
+ });
31443
+ }
31444
+ function AppointmentServiceGroups({ groups, hasSectionHeading, idPrefix }) {
31445
+ const GroupHeading = hasSectionHeading ? "h3" : "h2";
31446
+ const RowTitle = hasSectionHeading ? "h4" : "h3";
31447
+ return /* @__PURE__ */ jsx("div", {
31448
+ className: "appointment-services-groups rb-flex rb-flex-col rb-gap-10",
31449
+ children: groups.map((group) => {
31450
+ const headingId = `${idPrefix}-group-${group.key}`;
31451
+ return /* @__PURE__ */ jsxs("section", {
31452
+ className: "appointment-services-group rb-flex rb-flex-col rb-gap-2",
31453
+ "aria-labelledby": headingId,
31454
+ "data-appointment-services-group": group.key,
31455
+ children: [/* @__PURE__ */ jsxs("div", {
31456
+ className: "appointment-services-group-heading",
31457
+ children: [/* @__PURE__ */ jsx(GroupHeading, {
31458
+ id: headingId,
31459
+ className: "appointment-services-group-title rb-text-sm rb-font-semibold rb-uppercase rb-tracking-wide",
31460
+ children: group.heading
31461
+ }), group.intro ? /* @__PURE__ */ jsx("p", {
31462
+ className: "appointment-services-group-intro rb-text-sm rb-leading-6 rb-max-w-prose status-muted",
31463
+ children: group.intro
31464
+ }) : null]
31465
+ }), /* @__PURE__ */ jsx(AppointmentServiceRows, {
31466
+ rows: group.rows,
31467
+ RowTitle
31468
+ })]
31469
+ }, group.key);
31470
+ })
31471
+ });
31472
+ }
31473
+ function AppointmentServicesNode({ sectionHeading, categoryId, services, pathname, servicePathPrefix, blockId, className }) {
30845
31474
  const view = buildAppointmentServicesView({
30846
31475
  services,
31476
+ categoryId,
30847
31477
  pathname: typeof pathname === "string" ? pathname : void 0,
30848
31478
  servicePathPrefix: typeof servicePathPrefix === "string" ? servicePathPrefix : null
30849
31479
  });
30850
31480
  const rootClassName = `appointment-services ${className ?? ""}`.trim();
30851
- const RowTitle = sectionHeading ? "h3" : "h2";
30852
- if (view.kind === "error" || view.kind === "empty") return /* @__PURE__ */ jsx("div", {
30853
- className: rootClassName,
30854
- "data-appointment-services-state": view.kind,
30855
- children: /* @__PURE__ */ jsx("p", {
30856
- className: "rb-caption status-muted",
30857
- role: "status",
30858
- children: view.message
30859
- })
30860
- });
30861
- return /* @__PURE__ */ jsx("div", {
30862
- className: `${rootClassName} rb-flex rb-flex-col`,
30863
- "data-appointment-services-state": "populated",
30864
- children: /* @__PURE__ */ jsx("div", {
30865
- className: "appointment-services-rows rb-flex rb-flex-col",
30866
- children: view.rows.map((row) => /* @__PURE__ */ jsxs("article", {
30867
- className: "appointment-services-row rb-border-b rb-border-border rb-py-4",
30868
- children: [
30869
- /* @__PURE__ */ jsxs("div", {
30870
- className: "appointment-services-row-main rb-min-w-0",
30871
- children: [
30872
- /* @__PURE__ */ jsx(RowTitle, {
30873
- className: "appointment-services-row-title rb-text-lg rb-font-medium rb-leading-tight",
30874
- children: row.detailsHref ? /* @__PURE__ */ jsx("a", {
30875
- href: row.detailsHref,
30876
- children: row.title
30877
- }) : row.title
30878
- }),
30879
- row.description ? /* @__PURE__ */ jsx("p", {
30880
- className: "appointment-services-row-description rb-text-sm rb-leading-6 status-muted",
30881
- children: row.description
30882
- }) : null,
30883
- /* @__PURE__ */ jsxs("p", {
30884
- className: "appointment-services-row-meta rb-caption status-muted",
30885
- children: [row.durationLabel, row.locationLabel ? ` · ${row.locationLabel}` : ""]
30886
- })
30887
- ]
30888
- }),
30889
- row.priceLabel ? /* @__PURE__ */ jsx("span", {
30890
- className: "appointment-services-row-price rb-text-lg",
30891
- children: row.priceLabel
30892
- }) : null,
30893
- /* @__PURE__ */ jsx(ThemedButtonLink, {
30894
- variant: "secondary",
30895
- size: "sm",
30896
- extraClassName: "appointment-services-row-action",
30897
- href: row.href,
30898
- label: "Check times",
30899
- screenReaderSuffix: ` for ${row.title}`
30900
- })
30901
- ]
30902
- }, row.id))
30903
- })
30904
- });
31481
+ const hasSectionHeading = Boolean(sectionHeading);
31482
+ switch (view.kind) {
31483
+ case "error":
31484
+ case "empty": return /* @__PURE__ */ jsx("div", {
31485
+ className: rootClassName,
31486
+ "data-appointment-services-state": view.kind,
31487
+ children: /* @__PURE__ */ jsx("p", {
31488
+ className: "rb-caption status-muted",
31489
+ role: "status",
31490
+ children: view.message
31491
+ })
31492
+ });
31493
+ case "items": return /* @__PURE__ */ jsx("div", {
31494
+ className: `${rootClassName} rb-flex rb-flex-col`,
31495
+ "data-appointment-services-state": "populated",
31496
+ children: /* @__PURE__ */ jsx(AppointmentServiceRows, {
31497
+ rows: view.rows,
31498
+ RowTitle: hasSectionHeading ? "h3" : "h2"
31499
+ })
31500
+ });
31501
+ case "groups": return /* @__PURE__ */ jsx("div", {
31502
+ className: `${rootClassName} rb-flex rb-flex-col`,
31503
+ "data-appointment-services-state": "populated",
31504
+ children: /* @__PURE__ */ jsx(AppointmentServiceGroups, {
31505
+ groups: view.groups,
31506
+ hasSectionHeading,
31507
+ idPrefix: `appointment-services-${blockId ?? "block"}`
31508
+ })
31509
+ });
31510
+ default: return assertNever$4(view);
31511
+ }
30905
31512
  }
30906
31513
  //#endregion
30907
31514
  //#region ../blocks/src/contracts/runtime.ts
@@ -30963,12 +31570,12 @@ function toTrimmedString(value) {
30963
31570
  return trimmed ? trimmed : void 0;
30964
31571
  }
30965
31572
  function normalizeBookingFormConfig(form) {
30966
- if (!isRecord$10(form)) return null;
31573
+ if (!isRecord$11(form)) return null;
30967
31574
  const id = readNonEmptyString(form.id);
30968
31575
  const name = readNonEmptyString(form.name);
30969
31576
  if (!id || !name) return null;
30970
- const schemaSource = isRecord$10(form.schema) ? form.schema : isRecord$10(form.schemaJson) ? form.schemaJson : null;
30971
- const settingsSource = isRecord$10(form.settings) ? form.settings : isRecord$10(form.settingsJson) ? form.settingsJson : null;
31577
+ const schemaSource = isRecord$11(form.schema) ? form.schema : isRecord$11(form.schemaJson) ? form.schemaJson : null;
31578
+ const settingsSource = isRecord$11(form.settings) ? form.settings : isRecord$11(form.settingsJson) ? form.settingsJson : null;
30972
31579
  return {
30973
31580
  id,
30974
31581
  name,
@@ -30988,8 +31595,8 @@ function normalizeBookingServices(services) {
30988
31595
  };
30989
31596
  if (!services) return [];
30990
31597
  if (Array.isArray(services)) return services.filter(isBookingFormService).map(normalizeService);
30991
- if (isRecord$10(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
30992
- return isRecord$10(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
31598
+ if (isRecord$11(services) && "services" in services) return Array.isArray(services.services) ? services.services.filter(isBookingFormService).map(normalizeService) : [];
31599
+ return isRecord$11(services) ? Object.values(services).filter(isBookingFormService).map(normalizeService) : [];
30993
31600
  }
30994
31601
  function normalizeBookingSchema(value) {
30995
31602
  if (!value) return void 0;
@@ -31000,7 +31607,7 @@ function normalizeBookingSchema(value) {
31000
31607
  };
31001
31608
  }
31002
31609
  function readBookingFormField(value) {
31003
- if (!isRecord$10(value)) return null;
31610
+ if (!isRecord$11(value)) return null;
31004
31611
  const id = readNonEmptyString(value.id);
31005
31612
  const label = readNonEmptyString(value.label);
31006
31613
  const type = readBookingFieldType(value.type);
@@ -31043,7 +31650,7 @@ function readBookingFieldOptions(value) {
31043
31650
  return options.length > 0 ? options : void 0;
31044
31651
  }
31045
31652
  function readBookingFieldOption(value) {
31046
- if (!isRecord$10(value)) return null;
31653
+ if (!isRecord$11(value)) return null;
31047
31654
  const optionValue = readNonEmptyString(value.value);
31048
31655
  const label = readNonEmptyString(value.label);
31049
31656
  return optionValue && label ? {
@@ -31063,7 +31670,7 @@ function normalizeBookingSettings(value) {
31063
31670
  };
31064
31671
  }
31065
31672
  function isBookingFormService(value) {
31066
- return isRecord$10(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
31673
+ return isRecord$11(value) && typeof value.id === "string" && typeof value.siteId === "string" && typeof value.title === "string" && (value.consent === void 0 || blockBookingConsentSchema.safeParse(value.consent).success);
31067
31674
  }
31068
31675
  function readNonEmptyString(value) {
31069
31676
  if (typeof value !== "string") return null;
@@ -31081,10 +31688,35 @@ function readStringArray(value) {
31081
31688
  const strings = value.filter((item) => typeof item === "string");
31082
31689
  return strings.length > 0 ? strings : void 0;
31083
31690
  }
31084
- function isRecord$10(value) {
31691
+ function isRecord$11(value) {
31085
31692
  return typeof value === "object" && value !== null;
31086
31693
  }
31087
31694
  //#endregion
31695
+ //#region ../blocks/src/system/runtime/nodes/booking-form.service-categories.ts
31696
+ function isRecord$10(value) {
31697
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31698
+ }
31699
+ function readCategory(value) {
31700
+ if (!isRecord$10(value)) return [];
31701
+ const { id, name, description } = value;
31702
+ if (typeof id !== "string" || id.trim() === "" || typeof name !== "string") return [];
31703
+ return [{
31704
+ id,
31705
+ name,
31706
+ description: typeof description === "string" ? description : null
31707
+ }];
31708
+ }
31709
+ /**
31710
+ * The ordered service categories carried by a catalogue-shaped services
31711
+ * source (`{ services, categories }`). Every other shape has none, which
31712
+ * reads as a site without categories. Order is preserved: the database owns
31713
+ * it.
31714
+ */
31715
+ function normalizeBookingServiceCategories(services) {
31716
+ if (!isRecord$10(services) || !Array.isArray(services.categories)) return [];
31717
+ return services.categories.flatMap(readCategory);
31718
+ }
31719
+ //#endregion
31088
31720
  //#region ../blocks/src/system/runtime/nodes/booking-runtime-sources.ts
31089
31721
  const bookingFieldOptionSchema = z.object({
31090
31722
  value: z.string(),
@@ -31162,11 +31794,20 @@ const bookingServiceSchema = z.object({
31162
31794
  priceCurrency: z.string().nullable().optional(),
31163
31795
  paymentTerms: blockBookingPaymentTermsSchema.optional(),
31164
31796
  paymentTermsAvailability: blockBookingPaymentTermsAvailabilitySchema.optional(),
31165
- consent: blockBookingConsentSchema.optional()
31797
+ consent: blockBookingConsentSchema.optional(),
31798
+ categoryId: z.string().nullable().optional()
31799
+ });
31800
+ const bookingServiceCategorySchema = z.object({
31801
+ id: z.string(),
31802
+ name: z.string(),
31803
+ description: z.string().nullable()
31166
31804
  });
31167
31805
  const bookingServicesSourceSchema = z.union([
31168
31806
  z.array(bookingServiceSchema),
31169
- z.object({ services: z.array(bookingServiceSchema).optional() }),
31807
+ z.object({
31808
+ services: z.array(bookingServiceSchema).optional(),
31809
+ categories: z.array(bookingServiceCategorySchema).optional()
31810
+ }),
31170
31811
  z.record(z.string(), bookingServiceSchema)
31171
31812
  ]);
31172
31813
  const bookingConfigSchema = z.object({
@@ -31219,6 +31860,7 @@ const bookingHydrationSchema = z.object({
31219
31860
  rebookContext: z.object({ kind: z.literal("portal_rebook") }).nullable().optional(),
31220
31861
  form: bookingConfigSchema.nullable(),
31221
31862
  services: z.array(bookingServiceSchema),
31863
+ serviceCategories: z.array(bookingServiceCategorySchema).optional(),
31222
31864
  spamProtectionEnabled: z.boolean().optional()
31223
31865
  });
31224
31866
  const bookingRenderSchema = z.object({
@@ -31263,6 +31905,7 @@ const bookingFormBlockRuntime = defineClientOwnedBlockRuntime({
31263
31905
  rebookContext: source.rebookContext ?? null,
31264
31906
  form,
31265
31907
  services,
31908
+ serviceCategories: normalizeBookingServiceCategories(source.services),
31266
31909
  spamProtectionEnabled: source.spamProtectionEnabled
31267
31910
  }
31268
31911
  };
@@ -45639,6 +46282,123 @@ function generateFooterBottomTextLinkStyles(footer, themeId, tokens) {
45639
46282
  return cssChunks.join("\n");
45640
46283
  }
45641
46284
  //#endregion
46285
+ //#region ../theme-core/src/layout/boundaryCss.ts
46286
+ /**
46287
+ * Section-break CSS, generated from the transition catalogue (issue 1422).
46288
+ *
46289
+ * The renderer emits one element per shown join:
46290
+ *
46291
+ * <div data-block-boundary data-boundary-treatment="wave-edge-gentle"
46292
+ * data-boundary-placement="previous"
46293
+ * style="--boundary-shape-paint: …; --boundary-band-paint: …">
46294
+ *
46295
+ * Every selector matches an exact attribute value, so there is no reliance on
46296
+ * prefix ordering in the cascade and nothing reads colours out of a string.
46297
+ * The shape layer is `::after`; the band layer (paint → paint only) is
46298
+ * `::before`.
46299
+ */
46300
+ const waveMaskSvgPaths = {
46301
+ classic: "M0 72V46C130 55 260 24 420 30C590 36 690 62 860 42C1035 21 1160 47 1305 36C1365 31 1415 33 1440 38V72H0Z",
46302
+ balanced: "M0 72V42C120 24 240 24 360 42C480 60 600 60 720 42C840 24 960 24 1080 42C1200 60 1320 60 1440 42V72H0Z",
46303
+ gentle: "M0 72V43C180 32 300 32 480 43C660 54 780 54 960 43C1140 32 1260 32 1440 43V72H0Z",
46304
+ bold: "M0 72V44C90 14 270 14 360 44C450 74 630 74 720 44C810 14 990 14 1080 44C1170 74 1350 74 1440 44V72H0Z",
46305
+ drift: "M0 72V39C115 31 205 54 340 48C505 41 610 16 760 33C920 51 1015 64 1160 44C1280 27 1375 35 1440 50V72H0Z"
46306
+ };
46307
+ function waveMaskDataUrl(mask) {
46308
+ return svgDataUrl(`<svg viewBox='0 0 1440 72' xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none'><path d='${waveMaskSvgPaths[mask]}' fill='white'/></svg>`);
46309
+ }
46310
+ /**
46311
+ * How each placement anchors the layers against the zero-height boundary
46312
+ * element that sits exactly on the join.
46313
+ *
46314
+ * - `centred` straddles the join, band under shape.
46315
+ * - `previous` sits above the join, inside the previous section.
46316
+ * - `next` sits below the join, inside the next section, with the shape
46317
+ * flipped so it reads as the mirror of `previous`.
46318
+ */
46319
+ const placementRules = {
46320
+ centred: {
46321
+ shape: ["top: 0;", "transform: translateY(-50%);"],
46322
+ band: ["top: 0;", "transform: translateY(-50%);"]
46323
+ },
46324
+ previous: {
46325
+ shape: ["bottom: 0;"],
46326
+ band: null
46327
+ },
46328
+ next: {
46329
+ shape: ["top: 0;", "transform: scaleY(-1);"],
46330
+ band: null
46331
+ }
46332
+ };
46333
+ function generateBoundaryCss(selector) {
46334
+ const boundary = `${selector} [data-block-boundary]`;
46335
+ return [
46336
+ rule(boundary, [
46337
+ "position: relative;",
46338
+ "z-index: 3;",
46339
+ "height: 0;",
46340
+ "pointer-events: none;"
46341
+ ]),
46342
+ rule(`${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));"]),
46343
+ rule(`${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));"]),
46344
+ rule(`${boundary}::before,\n${boundary}::after`, [
46345
+ "content: '';",
46346
+ "position: absolute;",
46347
+ "left: 0;",
46348
+ "right: 0;",
46349
+ "height: var(--boundary-height);",
46350
+ "pointer-events: none;"
46351
+ ]),
46352
+ rule(`${boundary}::before`, ["z-index: 0;", "background: var(--boundary-band-paint, transparent);"]),
46353
+ rule(`${boundary}::after`, ["z-index: 1;"]),
46354
+ ...placementCss(boundary),
46355
+ ...transitionCatalogueEntries.flatMap((entry) => treatmentCss(boundary, entry))
46356
+ ].join("\n\n");
46357
+ }
46358
+ function placementCss(boundary) {
46359
+ return Object.keys(placementRules).flatMap((placement) => {
46360
+ const placed = `${boundary}[data-boundary-placement='${placement}']`;
46361
+ const rules = placementRules[placement];
46362
+ return [rule(`${placed}::after`, rules.shape), rules.band ? rule(`${placed}::before`, rules.band) : rule(`${placed}::before`, ["content: none;"])];
46363
+ });
46364
+ }
46365
+ function treatmentCss(boundary, entry) {
46366
+ const selected = `${boundary}[data-boundary-treatment='${entry.intent}']`;
46367
+ return [rule(selected, [`--boundary-height: ${boundaryRuntimeMetricsForDecorativeTransition(entry.intent).heightCss};`]), rule(`${selected}::after`, shapeDeclarations(entry))];
46368
+ }
46369
+ function shapeDeclarations(entry) {
46370
+ switch (entry.shape.kind) {
46371
+ case "fade": return [
46372
+ "/* Fallback for browsers without color-mix(). */",
46373
+ "background: linear-gradient(to bottom, transparent 0%, var(--boundary-shape-paint) 100%);",
46374
+ "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%);"
46375
+ ];
46376
+ case "wave": {
46377
+ const mask = waveMaskDataUrl(entry.shape.mask);
46378
+ return [
46379
+ "background: var(--boundary-shape-paint);",
46380
+ `mask-image: url("${mask}");`,
46381
+ "mask-repeat: no-repeat;",
46382
+ "mask-size: 100% 100%;",
46383
+ `-webkit-mask-image: url("${mask}");`,
46384
+ "-webkit-mask-repeat: no-repeat;",
46385
+ "-webkit-mask-size: 100% 100%;"
46386
+ ];
46387
+ }
46388
+ case "angle": return [
46389
+ "height: calc(var(--boundary-height) + 2px);",
46390
+ "background: var(--boundary-shape-paint);",
46391
+ `clip-path: ${anglePolygonCss(entry.shape.polygon)};`
46392
+ ];
46393
+ }
46394
+ }
46395
+ function rule(selector, declarations) {
46396
+ return `${selector} {\n ${declarations.join("\n ")}\n}`;
46397
+ }
46398
+ function svgDataUrl(svg) {
46399
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
46400
+ }
46401
+ //#endregion
45642
46402
  //#region ../theme-core/src/layout/generateLayoutCss.ts
45643
46403
  /**
45644
46404
  * Generate layout CSS for theme scope (background, text color)
@@ -45663,7 +46423,7 @@ function generateLayoutCss(options) {
45663
46423
  if (heroTypographyCss) cssChunks.push(heroTypographyCss);
45664
46424
  const bodyTextTypographyCss = generateBodyTextTypographyCss(themeId, theme);
45665
46425
  if (bodyTextTypographyCss) cssChunks.push(bodyTextTypographyCss);
45666
- cssChunks.push(generateSiteStyleTransitionCss(themeId));
46426
+ cssChunks.push(generateBoundaryCss(selector));
45667
46427
  return cssChunks.join("\n\n");
45668
46428
  }
45669
46429
  function toPaddingValue(preset) {
@@ -45674,193 +46434,6 @@ function toPaddingValue(preset) {
45674
46434
  relaxed: "1.5rem"
45675
46435
  }[preset];
45676
46436
  }
45677
- 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>");
45678
- 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>");
45679
- 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>");
45680
- 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>");
45681
- 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>");
45682
- function generateSiteStyleTransitionCss(themeId) {
45683
- const selector = `:where([data-theme-scope="${themeId}"])`;
45684
- const compactBoundaryHeight = requiredBoundaryHeightCss("wave-edge-gentle");
45685
- const mediumBoundaryHeight = requiredBoundaryHeightCss("wave-edge");
45686
- const largeBoundaryHeight = requiredBoundaryHeightCss("wave-edge-bold");
45687
- return `${selector} [data-style-group^='site-style-transition-'] {
45688
- position: relative;
45689
- z-index: 2;
45690
- --site-style-transition-from: transparent;
45691
- --site-style-transition-to: rgb(var(--tb-background));
45692
- }
45693
-
45694
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-'] {
45695
- position: relative;
45696
- z-index: 3;
45697
- height: 0;
45698
- pointer-events: none;
45699
- --site-style-boundary-height: ${mediumBoundaryHeight};
45700
- }
45701
-
45702
- ${selector} [data-block][data-site-style-boundary-before] > :where([data-slot='section']) {
45703
- padding-top: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-before-reserve, 0rem));
45704
- }
45705
-
45706
- ${selector} [data-block][data-site-style-boundary-after] > :where([data-slot='section']) {
45707
- padding-bottom: calc(var(--rb-section-pad-y, 0rem) + var(--site-style-boundary-after-reserve, 0rem));
45708
- }
45709
-
45710
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::before,
45711
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::before,
45712
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::before {
45713
- content: '';
45714
- position: absolute;
45715
- left: 0;
45716
- right: 0;
45717
- top: 0;
45718
- transform: translateY(-50%);
45719
- z-index: 0;
45720
- height: var(--site-style-boundary-height);
45721
- background: var(--site-style-transition-from);
45722
- pointer-events: none;
45723
- }
45724
-
45725
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after,
45726
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after,
45727
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
45728
- content: '';
45729
- position: absolute;
45730
- left: 0;
45731
- right: 0;
45732
- top: 0;
45733
- transform: translateY(-50%);
45734
- z-index: 1;
45735
- height: var(--site-style-boundary-height);
45736
- pointer-events: none;
45737
- }
45738
-
45739
- ${selector} [data-style-group*='-from-base-'] {
45740
- --site-style-transition-from: rgb(var(--tb-background));
45741
- }
45742
-
45743
- ${selector} [data-style-group*='-from-muted-band-'] {
45744
- --site-style-transition-from: rgb(var(--tb-muted));
45745
- }
45746
-
45747
- ${selector} [data-style-group*='-from-accent-band-'] {
45748
- --site-style-transition-from: rgb(var(--tb-accent));
45749
- }
45750
-
45751
- ${selector} [data-style-group*='-from-brand-depth-band-'] {
45752
- --site-style-transition-from: rgb(var(--tb-primary-800));
45753
- }
45754
-
45755
- ${selector} [data-style-group$='-to-base'],
45756
- ${selector} [data-style-group$='-to-hero-image'] {
45757
- --site-style-transition-to: rgb(var(--tb-background));
45758
- }
45759
-
45760
- ${selector} [data-style-group$='-to-muted-band'] {
45761
- --site-style-transition-to: rgb(var(--tb-muted));
45762
- }
45763
-
45764
- ${selector} [data-style-group$='-to-accent-band'] {
45765
- --site-style-transition-to: rgb(var(--tb-accent));
45766
- }
45767
-
45768
- ${selector} [data-style-group$='-to-brand-depth-band'] {
45769
- --site-style-transition-to: rgb(var(--tb-primary-800));
45770
- }
45771
-
45772
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade']::after {
45773
- /* Fallback for browsers without color-mix(). */
45774
- background: linear-gradient(
45775
- to bottom,
45776
- transparent 0%,
45777
- var(--site-style-transition-to) 100%
45778
- );
45779
- background: linear-gradient(
45780
- to bottom,
45781
- transparent 0%,
45782
- color-mix(in srgb, var(--site-style-transition-to) 16%, transparent) 34%,
45783
- color-mix(in srgb, var(--site-style-transition-to) 68%, transparent) 78%,
45784
- var(--site-style-transition-to) 100%
45785
- );
45786
- }
45787
-
45788
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-long-'] {
45789
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-long")};
45790
- }
45791
-
45792
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-soft-fade-expansive-'] {
45793
- --site-style-boundary-height: ${requiredBoundaryHeightCss("soft-fade-expansive")};
45794
- }
45795
-
45796
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-']::after {
45797
- height: calc(var(--site-style-boundary-height) + 2px);
45798
- background: var(--site-style-transition-to);
45799
- clip-path: polygon(0 42%, 100% 8%, 100% 100%, 0 100%);
45800
- }
45801
-
45802
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-'] {
45803
- --site-style-boundary-height: ${compactBoundaryHeight};
45804
- }
45805
-
45806
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-subtle-']::after {
45807
- clip-path: polygon(0 34%, 100% 18%, 100% 100%, 0 100%);
45808
- }
45809
-
45810
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-'] {
45811
- --site-style-boundary-height: ${largeBoundaryHeight};
45812
- }
45813
-
45814
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-angle-edge-steep-']::after {
45815
- clip-path: polygon(0 54%, 100% 0%, 100% 100%, 0 100%);
45816
- }
45817
-
45818
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-']::after {
45819
- background: var(--site-style-transition-to);
45820
- mask-image: url("${classicWaveTransitionMask}");
45821
- mask-repeat: no-repeat;
45822
- mask-size: 100% 100%;
45823
- -webkit-mask-image: url("${classicWaveTransitionMask}");
45824
- -webkit-mask-repeat: no-repeat;
45825
- -webkit-mask-size: 100% 100%;
45826
- }
45827
-
45828
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-balanced-']::after {
45829
- mask-image: url("${balancedWaveTransitionMask}");
45830
- -webkit-mask-image: url("${balancedWaveTransitionMask}");
45831
- }
45832
-
45833
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-'] {
45834
- --site-style-boundary-height: ${compactBoundaryHeight};
45835
- }
45836
-
45837
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-gentle-']::after {
45838
- mask-image: url("${gentleWaveTransitionMask}");
45839
- -webkit-mask-image: url("${gentleWaveTransitionMask}");
45840
- }
45841
-
45842
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-'] {
45843
- --site-style-boundary-height: ${largeBoundaryHeight};
45844
- }
45845
-
45846
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-bold-']::after {
45847
- mask-image: url("${boldWaveTransitionMask}");
45848
- -webkit-mask-image: url("${boldWaveTransitionMask}");
45849
- }
45850
-
45851
- ${selector} [data-block-boundary][data-style-group^='site-style-transition-wave-edge-drift-']::after {
45852
- mask-image: url("${driftWaveTransitionMask}");
45853
- -webkit-mask-image: url("${driftWaveTransitionMask}");
45854
- }`;
45855
- }
45856
- function requiredBoundaryHeightCss(transition) {
45857
- const metrics = boundaryRuntimeMetricsForTransition(transition);
45858
- if (!metrics) throw new Error(`Missing boundary runtime metrics for ${transition}`);
45859
- return metrics.heightCss;
45860
- }
45861
- function svgDataUrl(svg) {
45862
- return `data:image/svg+xml,${encodeURIComponent(svg)}`;
45863
- }
45864
46437
  function generateHeroTypographyCss(themeId, theme) {
45865
46438
  const typography = theme.hero?.typography;
45866
46439
  if (!typography) return "";
@@ -45879,24 +46452,23 @@ function generateHeroTypographyCss(themeId, theme) {
45879
46452
  if (microHeadlineRules.length > 0) chunks.push(`${microHeadlineSelector} {\n ${microHeadlineRules.join("\n ")}\n}`);
45880
46453
  const microSubheadlineRules = buildTypographyRules("subheadline", typography.microSubheadlineSize, typography.microSubheadlineLineHeight);
45881
46454
  if (microSubheadlineRules.length > 0) chunks.push(`${microSubheadlineSelector} {\n ${microSubheadlineRules.join("\n ")}\n}`);
45882
- if (typography.responsive?.tablet) {
45883
- const tabletHeadlineRules = buildTypographyRules("headline", typography.responsive.tablet.headlineSize, typography.responsive.tablet.headlineLineHeight);
45884
- const tabletSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.tablet.subheadlineSize, typography.responsive.tablet.subheadlineLineHeight);
45885
- const tabletChunks = [];
45886
- if (tabletHeadlineRules.length > 0) tabletChunks.push(`${headlineSelector} {\n ${tabletHeadlineRules.join("\n ")}\n }`);
45887
- if (tabletSubheadlineRules.length > 0) tabletChunks.push(`${subheadlineSelector} {\n ${tabletSubheadlineRules.join("\n ")}\n }`);
45888
- if (tabletChunks.length > 0) chunks.push(`@media ${MQ_TABLET_ONLY} {\n ${tabletChunks.join("\n\n ")}\n}`);
45889
- }
45890
- if (typography.responsive?.mobile) {
45891
- const mobileHeadlineRules = buildTypographyRules("headline", typography.responsive.mobile.headlineSize, typography.responsive.mobile.headlineLineHeight);
45892
- const mobileSubheadlineRules = buildTypographyRules("subheadline", typography.responsive.mobile.subheadlineSize, typography.responsive.mobile.subheadlineLineHeight);
45893
- const mobileChunks = [];
45894
- if (mobileHeadlineRules.length > 0) mobileChunks.push(`${headlineSelector} {\n ${mobileHeadlineRules.join("\n ")}\n }`);
45895
- if (mobileSubheadlineRules.length > 0) mobileChunks.push(`${subheadlineSelector} {\n ${mobileSubheadlineRules.join("\n ")}\n }`);
45896
- if (mobileChunks.length > 0) chunks.push(`@media ${MQ_SM_DOWN} {\n ${mobileChunks.join("\n\n ")}\n}`);
45897
- }
46455
+ const selectors = {
46456
+ headline: headlineSelector,
46457
+ subheadline: subheadlineSelector
46458
+ };
46459
+ const responsiveChunks = [responsiveHeroTypographyCss(MQ_TABLET_ONLY, selectors, typography.responsive?.tablet), responsiveHeroTypographyCss(MQ_SM_DOWN, selectors, typography.responsive?.mobile)];
46460
+ for (const chunk of responsiveChunks) if (chunk) chunks.push(chunk);
45898
46461
  return chunks.join("\n\n");
45899
46462
  }
46463
+ function responsiveHeroTypographyCss(media, selectors, overrides) {
46464
+ if (!overrides) return null;
46465
+ const headlineRules = buildTypographyRules("headline", overrides.headlineSize, overrides.headlineLineHeight);
46466
+ const subheadlineRules = buildTypographyRules("subheadline", overrides.subheadlineSize, overrides.subheadlineLineHeight);
46467
+ const mediaChunks = [];
46468
+ if (headlineRules.length > 0) mediaChunks.push(`${selectors.headline} {\n ${headlineRules.join("\n ")}\n }`);
46469
+ if (subheadlineRules.length > 0) mediaChunks.push(`${selectors.subheadline} {\n ${subheadlineRules.join("\n ")}\n }`);
46470
+ return mediaChunks.length > 0 ? `@media ${media} {\n ${mediaChunks.join("\n\n ")}\n}` : null;
46471
+ }
45900
46472
  function buildTypographyRules(role, size, lineHeight) {
45901
46473
  const rules = [];
45902
46474
  if (size) rules.push(`font-size: ${toHeroSize(role, size)};`);
@@ -47875,70 +48447,171 @@ function requireThemeResources(resources, blockKind) {
47875
48447
  return resources;
47876
48448
  }
47877
48449
  //#endregion
47878
- //#region ../blocks/src/pageRendererBoundaries.tsx
47879
- const boundaryKey = (previousBlockId, nextBlockId) => `${previousBlockId}\u0000${nextBlockId}`;
47880
- function isSiteStyleTransitionStyleGroup(value) {
47881
- return value?.startsWith("site-style-transition-") === true;
48450
+ //#region ../blocks/src/pageRenderJoins.ts
48451
+ /**
48452
+ * Re-points joins across omitted blocks. Pure over joins.
48453
+ *
48454
+ * Joins are keyed by adjacent block-id pairs, so dropping B from [A, B, C]
48455
+ * removes the A→C join as well as B's own, because no A→C join was ever
48456
+ * resolved. For each surviving pair the original join is kept when the pair
48457
+ * was already adjacent. Across a gap, the treatment comes from the join
48458
+ * entering the omitted run (A→first omitted), falling back to the one leaving
48459
+ * it (last omitted→C). Nothing visual is carried: the survivors are composed
48460
+ * from their own content afterwards.
48461
+ *
48462
+ * `survivors` are compared by identity against `originalBlocks`.
48463
+ */
48464
+ function retargetJoinsAcrossOmissions(input) {
48465
+ const { joins, originalBlocks, survivingBlocks } = input;
48466
+ if (joins.length === 0) return joins;
48467
+ if (originalBlocks.every((block) => survivingBlocks.has(block))) return joins;
48468
+ const byPair = joinsByPair(joins);
48469
+ const retargeted = [];
48470
+ let previousSurvivor = null;
48471
+ let run = [];
48472
+ for (const block of originalBlocks) {
48473
+ if (!survivingBlocks.has(block)) {
48474
+ run.push(block);
48475
+ continue;
48476
+ }
48477
+ if (previousSurvivor?.id && block.id) {
48478
+ const join = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : joinAcrossRun(byPair, previousSurvivor.id, block.id, run);
48479
+ if (join) retargeted.push(join);
48480
+ }
48481
+ previousSurvivor = block;
48482
+ run = [];
48483
+ }
48484
+ return retargeted;
47882
48485
  }
47883
- function boundaryMapForAdjacentBlockIds(boundaries) {
47884
- return new Map(boundaries.map((boundary) => [boundaryKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
48486
+ function joinAcrossRun(byPair, previousBlockId, nextBlockId, run) {
48487
+ const firstOmittedId = run[0]?.id;
48488
+ const lastOmittedId = run[run.length - 1]?.id;
48489
+ const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
48490
+ const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
48491
+ const donor = entering ?? exiting;
48492
+ return donor ? {
48493
+ previousBlockId,
48494
+ nextBlockId,
48495
+ treatment: donor.treatment
48496
+ } : void 0;
47885
48497
  }
47886
- function boundaryBetweenBlocks(previousBlock, nextBlock, boundaries) {
47887
- const previousBlockId = previousBlock?.id ?? null;
47888
- const nextBlockId = nextBlock?.id ?? null;
47889
- if (!previousBlockId || !nextBlockId) return null;
47890
- return boundaries.get(boundaryKey(previousBlockId, nextBlockId)) ?? null;
48498
+ /**
48499
+ * The single composition site. For every adjacent pair of survivors with ids,
48500
+ * finds its join and composes it from both survivors' final content: what
48501
+ * each paints with and the frame each draws.
48502
+ */
48503
+ function composePageJoins(input) {
48504
+ const byPair = joinsByPair(input.joins);
48505
+ return input.survivors.flatMap((previous, index) => {
48506
+ const next = input.survivors[index + 1];
48507
+ if (!next || !previous.id || !next.id) return [];
48508
+ const join = byPair.get(pairKey(previous.id, next.id));
48509
+ return [{
48510
+ previousBlockId: previous.id,
48511
+ nextBlockId: next.id,
48512
+ join: join ? composeJoin(join, previous, next) : null
48513
+ }];
48514
+ });
48515
+ }
48516
+ function composeJoin(join, previous, next) {
48517
+ return {
48518
+ boundaryId: pairKey(join.previousBlockId, join.nextBlockId),
48519
+ previousBlockId: join.previousBlockId,
48520
+ nextBlockId: join.nextBlockId,
48521
+ treatment: join.treatment,
48522
+ outcome: composeBoundary(sectionFactsForContent(previous.content), sectionFactsForContent(next.content))
48523
+ };
48524
+ }
48525
+ /** Placement, reserves and paint for a join that draws; null otherwise. */
48526
+ function renderedJoinLayout(join) {
48527
+ if (!join || join.outcome.kind !== "shown") return null;
48528
+ return boundaryLayout(join.treatment, join.outcome.composition);
48529
+ }
48530
+ /** Joins with a missing id on either side cannot sit between two blocks. */
48531
+ function joinsByPair(joins) {
48532
+ return new Map(joins.flatMap((join) => join.previousBlockId && join.nextBlockId ? [[pairKey(join.previousBlockId, join.nextBlockId), join]] : []));
48533
+ }
48534
+ /** Slot lookup key for an adjacent pair, in the one boundary-id scheme. */
48535
+ function joinSlotKey(previousBlockId, nextBlockId) {
48536
+ return pairKey(previousBlockId, nextBlockId);
48537
+ }
48538
+ function pairKey(previousBlockId, nextBlockId) {
48539
+ return makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId));
48540
+ }
48541
+ //#endregion
48542
+ //#region ../blocks/src/pageRendererBoundaries.tsx
48543
+ function joinSlotsByPair(slots) {
48544
+ return new Map(slots.map((slot) => [joinSlotKey(slot.previousBlockId, slot.nextBlockId), slot]));
47891
48545
  }
47892
- function renderBoundaryAfterBlock(input) {
47893
- const previousBlockId = input.block.id;
47894
- const nextBlockId = input.nextBlock?.id ?? null;
48546
+ function joinSlotBetween(previousBlockId, nextBlockId, slots) {
47895
48547
  if (!previousBlockId || !nextBlockId) return null;
47896
- const boundary = input.boundaryByAdjacentBlockIds.get(boundaryKey(previousBlockId, nextBlockId));
47897
- if (!boundary) return null;
47898
- const rendered = /* @__PURE__ */ jsx("div", {
48548
+ return slots.get(joinSlotKey(previousBlockId, nextBlockId)) ?? null;
48549
+ }
48550
+ /**
48551
+ * The markup at one join: the painted boundary element when the join draws,
48552
+ * then whatever `renderJoin` adds. Null when there is nothing to render.
48553
+ */
48554
+ function renderJoinSlot(input) {
48555
+ const { slot } = input;
48556
+ if (!slot) return null;
48557
+ const layout = renderedJoinLayout(slot.join);
48558
+ const painted = slot.join && layout ? /* @__PURE__ */ jsx("div", {
47899
48559
  "aria-hidden": "true",
47900
48560
  "data-block-boundary": "",
47901
- "data-boundary-id": boundary.boundaryId,
47902
- "data-boundary-previous-block-id": boundary.previousBlockId,
47903
- "data-boundary-next-block-id": boundary.nextBlockId,
47904
- "data-style-group": boundary.styleGroup,
47905
- style: boundaryCssVarsStyle(boundary)
47906
- }, `${input.blockKey}:boundary`);
47907
- return input.wrapBoundary ? input.wrapBoundary(boundary, rendered) : rendered;
47908
- }
47909
- function boundaryCssVarsStyle(boundary) {
47910
- return {
47911
- "--site-style-transition-from": boundary.cssVars.from,
47912
- "--site-style-transition-to": boundary.cssVars.to
47913
- };
48561
+ "data-boundary-id": slot.join.boundaryId,
48562
+ "data-boundary-previous-block-id": slot.previousBlockId,
48563
+ "data-boundary-next-block-id": slot.nextBlockId,
48564
+ "data-boundary-treatment": slot.join.treatment,
48565
+ "data-boundary-placement": layout.placement,
48566
+ style: boundaryPaintStyle(layout.paint)
48567
+ }, `${input.blockKey}:boundary`) : null;
48568
+ const extra = input.renderJoin?.({
48569
+ previousBlockId: slot.previousBlockId,
48570
+ nextBlockId: slot.nextBlockId,
48571
+ join: slot.join
48572
+ });
48573
+ if (!painted && (extra === null || extra === void 0)) return null;
48574
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [painted, extra] }, `${input.blockKey}:join`);
47914
48575
  }
47915
- function boundaryReserveCssVarsStyle(input) {
47916
- const before = input.before?.metrics.reserveCss ?? null;
47917
- const after = input.after?.metrics.reserveCss ?? null;
47918
- if (!before && !after) return void 0;
47919
- return {
47920
- ...before ? { "--site-style-boundary-before-reserve": before } : {},
47921
- ...after ? { "--site-style-boundary-after-reserve": after } : {}
47922
- };
48576
+ /**
48577
+ * The one render-edge place a theme token becomes CSS.
48578
+ */
48579
+ function themeTokenCss(token) {
48580
+ return `rgb(var(--tb-${token}))`;
47923
48581
  }
47924
- function transitionCssVarsStyle(content) {
47925
- const transitionStyles = isObjectRecord$1(content._transitionStyles) ? content._transitionStyles : null;
47926
- const cssVars = isObjectRecord$1(transitionStyles?.cssVars) ? transitionStyles.cssVars : null;
47927
- const from = typeof cssVars?.from === "string" ? cssVars.from : null;
47928
- const to = typeof cssVars?.to === "string" ? cssVars.to : null;
47929
- if (!from && !to) return void 0;
48582
+ function boundaryPaintStyle(paint) {
47930
48583
  return {
47931
- ...from ? { "--site-style-transition-from": from } : {},
47932
- ...to ? { "--site-style-transition-to": to } : {}
48584
+ "--boundary-shape-paint": themeTokenCss(paint.shape),
48585
+ ...paint.band ? { "--boundary-band-paint": themeTokenCss(paint.band) } : {}
47933
48586
  };
47934
48587
  }
47935
- function mergeStyleObjects(left, right) {
47936
- if (!left) return right;
47937
- if (!right) return left;
47938
- return {
47939
- ...left,
47940
- ...right
48588
+ /**
48589
+ * Reserve attributes and variables for a block wrapper, from the joins on
48590
+ * either side. A join's `before` reserve sits inside its previous section, so
48591
+ * it pads the bottom of the block above the join; its `after` reserve pads the
48592
+ * top of the block below. Joins that do not draw reserve nothing.
48593
+ */
48594
+ function blockBoundaryReserves(input) {
48595
+ const top = reserveOnSide(input.before, "after");
48596
+ const bottom = reserveOnSide(input.after, "before");
48597
+ const style = {
48598
+ ...top ? { "--site-style-boundary-before-reserve": top.length } : {},
48599
+ ...bottom ? { "--site-style-boundary-after-reserve": bottom.length } : {}
47941
48600
  };
48601
+ return {
48602
+ topPlacement: top?.placement,
48603
+ bottomPlacement: bottom?.placement,
48604
+ style: top || bottom ? style : void 0
48605
+ };
48606
+ }
48607
+ /** The reserve one join needs on one side of itself, if it draws at all. */
48608
+ function reserveOnSide(slot, side) {
48609
+ const layout = renderedJoinLayout(slot?.join ?? null);
48610
+ const length = layout?.reserves[side] ?? null;
48611
+ return layout && length ? {
48612
+ length,
48613
+ placement: layout.placement
48614
+ } : null;
47942
48615
  }
47943
48616
  function pageBlockPosition(index, blockCount) {
47944
48617
  if (blockCount <= 1) return "only";
@@ -52637,6 +53310,15 @@ const appointmentServicesBlockDefinition = {
52637
53310
  required: false,
52638
53311
  ui: { intent: "content" }
52639
53312
  },
53313
+ createSiteOptionSelectField({
53314
+ id: "categoryId",
53315
+ label: "Category",
53316
+ description: "Show one category (with its heading and intro), or leave it on All categories to show everything grouped.",
53317
+ required: false,
53318
+ multiple: false,
53319
+ optionSource: "appointmentServiceCategories",
53320
+ ui: { intent: "content" }
53321
+ }),
52640
53322
  sectionStylesField({
52641
53323
  id: "_sectionStyles",
52642
53324
  label: "Section styles",
@@ -52671,6 +53353,7 @@ const appointmentServicesBlockDefinition = {
52671
53353
  richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description")),
52672
53354
  el("appointment-services", {
52673
53355
  sectionHeading: { $bind: { from: "content.heading" } },
53356
+ categoryId: { $bind: { from: "content.categoryId" } },
52674
53357
  services: { $bind: { from: "data.services" } },
52675
53358
  pathname: { $bind: { from: "$root.pathname" } },
52676
53359
  servicePathPrefix: { $bind: { from: "$root.appointmentServicePathPrefix" } }
@@ -57517,11 +58200,10 @@ function buildPageRenderPlan(input) {
57517
58200
  return {
57518
58201
  entries,
57519
58202
  omitted,
57520
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
57521
- boundaries: input.runtimeBoundaries,
58203
+ runtimeJoins: retargetJoinsAcrossOmissions({
58204
+ joins: input.runtimeJoins ?? [],
57522
58205
  originalBlocks: input.page.blocks,
57523
- survivingBlocks: new Set(entries.map((entry) => entry.block)),
57524
- previewStage: input.previewStage
58206
+ survivingBlocks: new Set(entries.map((entry) => entry.block))
57525
58207
  })
57526
58208
  };
57527
58209
  }
@@ -57623,86 +58305,15 @@ function getBlockOverride(blockKind, overrides) {
57623
58305
  * For draft/preview: draftContent takes priority over content.
57624
58306
  * For published: content is the source of truth.
57625
58307
  */
58308
+ /**
58309
+ * The content a block renders with. Every render path (public, preview,
58310
+ * templates, SDK) chooses content here, so this is also where a stale
58311
+ * `site-style-transition-*` styleGroup is dropped; authored values survive.
58312
+ */
57626
58313
  function getRenderableContent(block, previewStage = "published") {
57627
58314
  const source = previewStage === "preview" && block.draftContent ? block.draftContent : block.content ?? {};
57628
- return isObjectRecord$1(source) ? source : {};
57629
- }
57630
- /**
57631
- * Re-points section boundaries across omitted blocks.
57632
- *
57633
- * Boundaries are keyed by adjacent block-id *pairs*, so dropping B from
57634
- * [A, B, C] does not merely remove B's boundaries — it removes the boundary
57635
- * between the survivors, because no `A→C` key was ever compiled.
57636
- *
57637
- * Per decision D2 the treatment for a merged gap comes from the boundary
57638
- * entering the omitted run (`A→firstOmitted`), falling back to the one leaving
57639
- * it (`lastOmitted→C`). The destination surface is a separate question: only
57640
- * the exiting boundary knows what C looks like, so the merged boundary ends on
57641
- * that endpoint rather than on the omitted block's. In the exiting-only
57642
- * fallback the origin remains an approximation: `from` is the omitted block's
57643
- * surface, because no compiled boundary carries A's. The hosted path no longer
57644
- * needs this at all (site-renderer admits blocks before compiling boundaries);
57645
- * it remains the safety net for an SDK whose compiled boundaries predate an
57646
- * omission.
57647
- */
57648
- function retargetBoundariesAcrossOmissions(input) {
57649
- const { boundaries, originalBlocks, survivingBlocks, previewStage } = input;
57650
- if (boundaries === void 0) return void 0;
57651
- if (boundaries.length === 0) return boundaries;
57652
- if (!originalBlocks.some((block) => !survivingBlocks.has(block))) return boundaries;
57653
- const byPair = new Map(boundaries.map((boundary) => [pairKey(boundary.previousBlockId, boundary.nextBlockId), boundary]));
57654
- const retargeted = [];
57655
- let previousSurvivor = null;
57656
- let run = [];
57657
- for (const block of originalBlocks) {
57658
- if (!survivingBlocks.has(block)) {
57659
- run.push(block);
57660
- continue;
57661
- }
57662
- if (previousSurvivor?.id && block.id) {
57663
- const boundary = run.length === 0 ? byPair.get(pairKey(previousSurvivor.id, block.id)) : mergedBoundary({
57664
- byPair,
57665
- previousBlock: previousSurvivor,
57666
- previousBlockId: previousSurvivor.id,
57667
- nextBlock: block,
57668
- nextBlockId: block.id,
57669
- run,
57670
- previewStage
57671
- });
57672
- if (boundary) retargeted.push(boundary);
57673
- }
57674
- previousSurvivor = block;
57675
- run = [];
57676
- }
57677
- return retargeted;
58315
+ return isObjectRecord$1(source) ? stripStaleTransitionStyleGroup(source) : {};
57678
58316
  }
57679
- function mergedBoundary(input) {
57680
- const { byPair, previousBlock, previousBlockId, nextBlock, nextBlockId, run, previewStage } = input;
57681
- const firstOmittedId = run[0]?.id;
57682
- const lastOmittedId = run[run.length - 1]?.id;
57683
- const entering = firstOmittedId ? byPair.get(pairKey(previousBlockId, firstOmittedId)) : void 0;
57684
- const exiting = lastOmittedId ? byPair.get(pairKey(lastOmittedId, nextBlockId)) : void 0;
57685
- const donor = entering ?? exiting;
57686
- if (!donor) return void 0;
57687
- const transition = compileTransitionRuntime({
57688
- treatment: donor.treatment,
57689
- from: donor.from,
57690
- to: exiting?.to ?? donor.to
57691
- });
57692
- return retargetCompiledBoundaryRuntimeForContentAdjacency({
57693
- ...donor,
57694
- boundaryId: makeBoundaryId(asDesignBlockId(previousBlockId), asDesignBlockId(nextBlockId)),
57695
- previousBlockId,
57696
- nextBlockId,
57697
- from: transition.from,
57698
- to: transition.to,
57699
- cssVars: transition.cssVars
57700
- }, {
57701
- currentContent: getRenderableContent(previousBlock, previewStage),
57702
- nextContent: getRenderableContent(nextBlock, previewStage)
57703
- });
57704
- }
57705
- const pairKey = boundaryKey;
57706
58317
  //#endregion
57707
58318
  //#region ../blocks/src/system/manifest/hydrateLinks.ts
57708
58319
  /**
@@ -57737,7 +58348,7 @@ function hydrateResolvedBookingConsentValue(value, context) {
57737
58348
  const hydratedItems = value.map((item) => hydrateResolvedBookingConsentValue(item, context));
57738
58349
  return hydratedItems.every((item, index) => item === value[index]) ? value : hydratedItems;
57739
58350
  }
57740
- if (!isRecord$13(value)) return value;
58351
+ if (!isRecord$14(value)) return value;
57741
58352
  if (isBookingConsentCarrier(value)) {
57742
58353
  const agreement = hydrateValue(value.agreement, context);
57743
58354
  return agreement === value.agreement ? value : {
@@ -57757,7 +58368,7 @@ function hydrateValue(value, context) {
57757
58368
  const resolvedItems = value.map((item) => hydrateValue(item, context));
57758
58369
  return resolvedItems.every((item, index) => item === value[index]) ? value : resolvedItems;
57759
58370
  }
57760
- if (isRecord$13(value)) {
58371
+ if (isRecord$14(value)) {
57761
58372
  if (isInternalLink(value)) return hydrateInternalLink(value, context.routes);
57762
58373
  if (isPageLink(value)) return hydratePageLink(value, context);
57763
58374
  if (isEntryLink(value)) return hydrateEntryLink(value, context);
@@ -57822,7 +58433,7 @@ function hydrateEntryLink(link, context) {
57822
58433
  //#region ../blocks/src/system/manifest/resolveBlockData.ts
57823
58434
  function resolveBlockData(blockId, dataLoaders, dataContext, routes) {
57824
58435
  const raw = blockId ? dataContext?.resolvedData?.[blockId] : void 0;
57825
- if (isRecord$13(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
58436
+ if (isRecord$14(raw)) return routes ? hydrateResolvedBookingConsentLinks(raw, routes, dataContext?.pagesByIdentifier, dataContext?.entriesByIdentifier) : raw;
57826
58437
  return dataLoaders && typeof dataLoaders === "object" && Object.keys(dataLoaders).length > 0 ? {} : void 0;
57827
58438
  }
57828
58439
  //#endregion
@@ -57857,23 +58468,44 @@ function preparePageRender(input) {
57857
58468
  prepared
57858
58469
  });
57859
58470
  }
57860
- if (constructionFailures.length === 0 || input.audience === "authoring") return {
57861
- entries,
57862
- constructionFailures,
57863
- runtimeBoundaries: input.plan.runtimeBoundaries
57864
- };
57865
- const survivors = entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
58471
+ const survivors = input.audience === "authoring" ? entries : entries.filter((candidate) => candidate.prepared.kind !== "render-threw");
57866
58472
  return {
57867
58473
  entries: survivors,
57868
58474
  constructionFailures,
57869
- runtimeBoundaries: retargetBoundariesAcrossOmissions({
57870
- boundaries: input.plan.runtimeBoundaries,
57871
- originalBlocks: input.plan.entries.map((planned) => planned.block),
57872
- survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block)),
57873
- previewStage: input.previewStage
57874
- })
58475
+ joins: composeSurvivorJoins(input.plan, survivors, input.themeResources)
57875
58476
  };
57876
58477
  }
58478
+ /**
58479
+ * The only place joins are composed. Plan-time omissions were already
58480
+ * re-pointed by the plan; construction failures are re-pointed here by the
58481
+ * same primitive, so `[A, B, C]` losing B still gets an A→C join. Each
58482
+ * survivor is then classified from its final content.
58483
+ */
58484
+ function composeSurvivorJoins(plan, survivors, themeResources) {
58485
+ return composePageJoins({
58486
+ joins: retargetJoinsAcrossOmissions({
58487
+ joins: plan.runtimeJoins,
58488
+ originalBlocks: plan.entries.map((planned) => planned.block),
58489
+ survivingBlocks: new Set(survivors.map((survivor) => survivor.entry.block))
58490
+ }),
58491
+ survivors: survivors.map(({ entry }) => ({
58492
+ id: entry.block.id,
58493
+ kind: entry.block.kind,
58494
+ content: boundaryClassificationContent(entry, themeResources)
58495
+ }))
58496
+ });
58497
+ }
58498
+ /**
58499
+ * The content a join is classified from must be the content that paints the
58500
+ * section. The default block renderer layers the theme's per-block style
58501
+ * defaults (`theme.blockOverrides[kind].styles`, background included) under
58502
+ * the block's own `_sectionStyles`, so classify that same projection. An
58503
+ * override component renders its own markup from the unmerged content.
58504
+ */
58505
+ function boundaryClassificationContent(entry, themeResources) {
58506
+ if (entry.kind !== "renderable" || entry.override) return entry.renderableContent;
58507
+ return mergeContentWithBlockStyleOverrides(entry.renderableContent, themeResources.kind === "available" ? themeResources.config : void 0, entry.block.kind);
58508
+ }
57877
58509
  function prepareEntry(entry, input) {
57878
58510
  if (entry.kind !== "renderable") return { kind: "plan-decided" };
57879
58511
  const draftContent = previewableEditorContent(entry);
@@ -58062,7 +58694,7 @@ function PageRenderer$1(props) {
58062
58694
  });
58063
58695
  }
58064
58696
  function PageRendererCore(props) {
58065
- const { page, audience, runtimeBoundaries, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
58697
+ const { page, audience, runtimeJoins, dataContext, routeMap, registry, sdkConfig, customBlocks, blockOverrides } = props;
58066
58698
  const imageResolveContext = React$1.useMemo(() => resolvePageRendererImageResolveContext(dataContext), [
58067
58699
  dataContext?.imageResolveContext,
58068
58700
  dataContext?.mediaAssets,
@@ -58078,7 +58710,7 @@ function PageRendererCore(props) {
58078
58710
  blocks: []
58079
58711
  },
58080
58712
  audience,
58081
- runtimeBoundaries,
58713
+ runtimeJoins,
58082
58714
  previewStage,
58083
58715
  sdkConfig,
58084
58716
  customBlocks,
@@ -58086,7 +58718,7 @@ function PageRendererCore(props) {
58086
58718
  }), [
58087
58719
  page,
58088
58720
  audience,
58089
- runtimeBoundaries,
58721
+ runtimeJoins,
58090
58722
  previewStage,
58091
58723
  sdkConfig,
58092
58724
  customBlocks,
@@ -58103,19 +58735,16 @@ function PageRendererCore(props) {
58103
58735
  dataContext,
58104
58736
  routeMap,
58105
58737
  themeResources,
58106
- imageResolveContext,
58107
- previewStage
58738
+ imageResolveContext
58108
58739
  }), [
58109
58740
  plan,
58110
58741
  audience,
58111
58742
  dataContext,
58112
58743
  routeMap,
58113
58744
  themeResources,
58114
- imageResolveContext,
58115
- previewStage
58745
+ imageResolveContext
58116
58746
  ]);
58117
- const boundaryByAdjacentBlockIds = React$1.useMemo(() => boundaryMapForAdjacentBlockIds(preparation.runtimeBoundaries ?? []), [preparation.runtimeBoundaries]);
58118
- const usesRuntimeBoundaries = preparation.runtimeBoundaries !== void 0;
58747
+ const joinSlots = React$1.useMemo(() => joinSlotsByPair(preparation.joins), [preparation.joins]);
58119
58748
  reportAndReplayOmissions({
58120
58749
  plan,
58121
58750
  preparation,
@@ -58133,10 +58762,9 @@ function PageRendererCore(props) {
58133
58762
  themeResources,
58134
58763
  imageResolveContext,
58135
58764
  resolvedRegistry,
58136
- boundaryByAdjacentBlockIds,
58137
- usesRuntimeBoundaries,
58765
+ joinSlots,
58138
58766
  wrapBlock: props.wrapBlock,
58139
- wrapBoundary: props.wrapBoundary
58767
+ renderJoin: props.renderJoin
58140
58768
  })) });
58141
58769
  }
58142
58770
  /**
@@ -58225,27 +58853,25 @@ function requireEntryBlockOverride(entry, themeResources) {
58225
58853
  }
58226
58854
  /**
58227
58855
  * Renders one planned block: its wrapper, style-group data attributes,
58228
- * boundary reserve space and any boundary or transition sibling that follows.
58856
+ * boundary reserve space and the join markup that follows it.
58229
58857
  *
58230
58858
  * Extracted from PageRendererCore so the component stays about wiring the plan
58231
58859
  * rather than also owning per-block markup.
58232
58860
  */
58233
58861
  function renderPlanEntry(input) {
58234
- const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, boundaryByAdjacentBlockIds, usesRuntimeBoundaries, wrapBlock, wrapBoundary } = input;
58862
+ const { index, entries, props, themeResources, imageResolveContext, resolvedRegistry, joinSlots, wrapBlock, renderJoin } = input;
58235
58863
  const entry = input.entry.entry;
58236
58864
  const block = entry.block;
58237
58865
  const blockPosition = pageBlockPosition(index, entries.length);
58238
58866
  const blockKey = blockKeyFor(block, index);
58239
58867
  const previousBlock = entries[index - 1]?.entry.block ?? null;
58240
58868
  const nextBlock = entries[index + 1]?.entry.block ?? null;
58241
- const boundaryBefore = boundaryBetweenBlocks(previousBlock, block, boundaryByAdjacentBlockIds);
58242
- const boundaryAfter = boundaryBetweenBlocks(block, nextBlock, boundaryByAdjacentBlockIds);
58243
- const boundaryElement = renderBoundaryAfterBlock({
58244
- block,
58245
- nextBlock,
58869
+ const joinBefore = joinSlotBetween(previousBlock?.id, block.id, joinSlots);
58870
+ const joinAfter = joinSlotBetween(block.id, nextBlock?.id, joinSlots);
58871
+ const boundaryElement = renderJoinSlot({
58872
+ slot: joinAfter,
58246
58873
  blockKey,
58247
- boundaryByAdjacentBlockIds,
58248
- wrapBoundary
58874
+ renderJoin
58249
58875
  });
58250
58876
  const cleanKind = block.kind.replace(/^block\./, "");
58251
58877
  const managesOwnWrapper = entry.kind === "renderable" && entry.managesOwnWrapper;
@@ -58268,11 +58894,10 @@ function renderPlanEntry(input) {
58268
58894
  const styleGroup = typeof renderableContent.styleGroup === "string" ? renderableContent.styleGroup : void 0;
58269
58895
  const blockLook = typeof renderableContent.blockLook === "string" ? renderableContent.blockLook : void 0;
58270
58896
  const blockComposition = readBlockComposition(renderableContent.blockComposition);
58271
- const transitionStyle = transitionCssVarsStyle(renderableContent);
58272
- const blockStyle = mergeStyleObjects(transitionStyle, boundaryReserveCssVarsStyle({
58273
- before: boundaryBefore,
58274
- after: boundaryAfter
58275
- }));
58897
+ const reserves = blockBoundaryReserves({
58898
+ before: joinBefore,
58899
+ after: joinAfter
58900
+ });
58276
58901
  const blockElement = /* @__PURE__ */ jsx("div", {
58277
58902
  className: wrapperClassName,
58278
58903
  "data-block": cleanKind,
@@ -58284,21 +58909,14 @@ function renderPlanEntry(input) {
58284
58909
  "data-block-composition": blockComposition?.treatment,
58285
58910
  "data-block-composition-kind": blockComposition?.kind,
58286
58911
  "data-page-block-position": blockPosition,
58287
- "data-site-style-boundary-before": boundaryBefore?.metrics.size,
58288
- "data-site-style-boundary-after": boundaryAfter?.metrics.size,
58912
+ "data-site-style-boundary-before": reserves.topPlacement,
58913
+ "data-site-style-boundary-after": reserves.bottomPlacement,
58289
58914
  "data-style-group": styleGroup,
58290
- style: blockStyle,
58915
+ style: reserves.style,
58291
58916
  children: /* @__PURE__ */ jsx(MemoBlockSection, { ...blockSectionProps })
58292
58917
  }, blockKey);
58293
58918
  if (boundaryElement) return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, boundaryElement] }, blockKey);
58294
- if (usesRuntimeBoundaries || !isSiteStyleTransitionStyleGroup(styleGroup)) return blockElement;
58295
- return /* @__PURE__ */ jsxs(React$1.Fragment, { children: [blockElement, /* @__PURE__ */ jsx("div", {
58296
- "aria-hidden": "true",
58297
- "data-block-transition": "",
58298
- "data-block-transition-for": block.id ?? blockKey,
58299
- "data-style-group": styleGroup,
58300
- style: transitionStyle
58301
- })] }, blockKey);
58919
+ return blockElement;
58302
58920
  }
58303
58921
  function resolvePageRendererImageResolveContext(dataContext) {
58304
58922
  const input = dataContext?.imageResolveContext;
@@ -72062,7 +72680,7 @@ function toBlocksPageRendererDataContext(dataContext) {
72062
72680
  function toBlocksCommonProps(props) {
72063
72681
  return {
72064
72682
  page: props.page,
72065
- runtimeBoundaries: props.runtimeBoundaries,
72683
+ runtimeJoins: props.runtimeJoins,
72066
72684
  dataContext: props.dataContext ? toBlocksPageRendererDataContext(props.dataContext) : void 0,
72067
72685
  routeMap: props.routeMap,
72068
72686
  wrapBlock: props.wrapBlock,
@@ -72165,10 +72783,10 @@ function buildPageRouteRenderContext(dataContext) {
72165
72783
  };
72166
72784
  }
72167
72785
  function PageContent(props) {
72168
- const { page, runtimeBoundaries, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
72786
+ const { page, runtimeJoins, siteId, resolvedData, bookingOfferingPresets, mailingList, bookingTimeZone, routeMap, pagesByIdentifier, entriesByIdentifier, wrapBlock, registry, blockOverrides, sdkConfig, customBlocks, supabaseUrl, mediaAssets, apiBaseUrl, portalToken, previewToken, readAuthority, dataContext } = props;
72169
72787
  const rendererCommonProps = {
72170
72788
  page,
72171
- runtimeBoundaries,
72789
+ runtimeJoins,
72172
72790
  dataContext: {
72173
72791
  siteId,
72174
72792
  ...buildPageRouteRenderContext(dataContext),
@@ -72904,6 +73522,17 @@ function coerceToString(value) {
72904
73522
  return "";
72905
73523
  }
72906
73524
  /**
73525
+ * The appointment-services catalogue (issue 1403) pairs the services with the
73526
+ * site's ordered categories. Both are needed to group them, so that response
73527
+ * is passed through whole; any other `{ services }` wrapper is unwrapped.
73528
+ */
73529
+ function coerceServicesPayload(record) {
73530
+ return Array.isArray(record.services) && Array.isArray(record.categories) ? {
73531
+ services: record.services,
73532
+ categories: record.categories
73533
+ } : record.services;
73534
+ }
73535
+ /**
72907
73536
  * Extract data from API response.
72908
73537
  * Unwraps common response wrappers: { data }, { form }, { entries }, { services }, { teamMembers }
72909
73538
  *
@@ -72918,7 +73547,7 @@ function coercePayload(payload) {
72918
73547
  if ("data" in record && typeof record.data !== "function") return record.data;
72919
73548
  if ("form" in record && typeof record.form !== "function") return record.form;
72920
73549
  if ("entries" in record && typeof record.entries !== "function") return record.entries;
72921
- if ("services" in record && typeof record.services !== "function") return record.services;
73550
+ if ("services" in record && typeof record.services !== "function") return coerceServicesPayload(record);
72922
73551
  if ("teamMembers" in record && typeof record.teamMembers !== "function") return record.teamMembers;
72923
73552
  }
72924
73553
  return payload;
@@ -73478,6 +74107,75 @@ async function Layout(props) {
73478
74107
  });
73479
74108
  }
73480
74109
  //#endregion
74110
+ //#region ../api/src/defineEndpointDefinitions.ts
74111
+ /**
74112
+ * Validates extracted endpoint descriptors while preserving each literal path,
74113
+ * method, and parameter contract for the typed URL builders.
74114
+ */
74115
+ function defineEndpointDefinitions(definitions) {
74116
+ return definitions;
74117
+ }
74118
+ //#endregion
74119
+ //#region ../api/src/publicNewsletterEndpointDefinitions.ts
74120
+ const PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
74121
+ subscribeNewsletter: {
74122
+ path: "/public/sites/{siteId}/newsletter/subscribe",
74123
+ method: "POST",
74124
+ tags: ["public-newsletter-{siteId}"],
74125
+ auth: "public",
74126
+ responseKind: "json"
74127
+ },
74128
+ confirmNewsletter: {
74129
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
74130
+ method: "GET",
74131
+ tags: ["public-newsletter-{siteId}"],
74132
+ auth: "public",
74133
+ responseKind: "text"
74134
+ },
74135
+ unsubscribeNewsletter: {
74136
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
74137
+ method: "GET",
74138
+ tags: ["public-newsletter-{siteId}"],
74139
+ auth: "public",
74140
+ responseKind: "text"
74141
+ },
74142
+ unsubscribeNewsletterPost: {
74143
+ path: "/public/sites/{siteId}/newsletter/unsubscribe/{token}",
74144
+ method: "POST",
74145
+ tags: ["public-newsletter-{siteId}"],
74146
+ auth: "public",
74147
+ responseKind: "text"
74148
+ },
74149
+ confirmNewsletterPost: {
74150
+ path: "/public/sites/{siteId}/newsletter/confirm/{token}",
74151
+ method: "POST",
74152
+ tags: ["public-newsletter-{siteId}"],
74153
+ auth: "public",
74154
+ responseKind: "text"
74155
+ },
74156
+ getNewsletterPreferences: {
74157
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
74158
+ method: "GET",
74159
+ tags: ["public-newsletter-{siteId}"],
74160
+ auth: "public",
74161
+ responseKind: "text"
74162
+ },
74163
+ saveNewsletterPreferences: {
74164
+ path: "/public/sites/{siteId}/newsletter/preferences/{token}",
74165
+ method: "POST",
74166
+ tags: ["public-newsletter-{siteId}"],
74167
+ auth: "public",
74168
+ responseKind: "text"
74169
+ },
74170
+ getNewsletterArchive: {
74171
+ path: "/public/sites/{siteId}/newsletter/archive/{token}",
74172
+ method: "GET",
74173
+ tags: ["public-newsletter-{siteId}"],
74174
+ auth: "public",
74175
+ responseKind: "text"
74176
+ }
74177
+ });
74178
+ //#endregion
73481
74179
  //#region ../api/src/apiEndpointTypes.ts
73482
74180
  function allowedDemoMutationPolicy() {
73483
74181
  return {
@@ -73569,17 +74267,85 @@ const SDK_ENTRY_READ_ENDPOINTS = {
73569
74267
  }
73570
74268
  };
73571
74269
  //#endregion
73572
- //#region ../api/src/defineEndpointDefinitions.ts
73573
- /**
73574
- * Validates extracted endpoint descriptors while preserving each literal path,
73575
- * method, and parameter contract for the typed URL builders.
73576
- */
73577
- function defineEndpointDefinitions(definitions) {
73578
- return definitions;
73579
- }
73580
- //#endregion
73581
74270
  //#region ../api/src/appointmentEndpointDefinitions.ts
73582
74271
  const APPOINTMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
74272
+ ...defineEndpointDefinitions({
74273
+ listGroupedAppointmentServices: {
74274
+ path: "/sites/{siteId}/bookings/services/grouped",
74275
+ method: "GET",
74276
+ auth: "user",
74277
+ responseKind: "json",
74278
+ errors: ["server:internal_error"]
74279
+ },
74280
+ reorderAppointmentServices: {
74281
+ path: "/sites/{siteId}/bookings/services/order",
74282
+ method: "PUT",
74283
+ auth: "user",
74284
+ responseKind: "json",
74285
+ errors: [
74286
+ "validation:invalid_input",
74287
+ "auth:forbidden",
74288
+ "resource:not_found",
74289
+ "resource:conflict",
74290
+ "server:internal_error"
74291
+ ]
74292
+ },
74293
+ listAppointmentServiceCategories: {
74294
+ path: "/sites/{siteId}/bookings/service-categories",
74295
+ method: "GET",
74296
+ auth: "user",
74297
+ responseKind: "json",
74298
+ errors: ["server:internal_error"]
74299
+ },
74300
+ createAppointmentServiceCategory: {
74301
+ path: "/sites/{siteId}/bookings/service-categories",
74302
+ method: "POST",
74303
+ auth: "user",
74304
+ responseKind: "json",
74305
+ errors: [
74306
+ "validation:invalid_input",
74307
+ "auth:forbidden",
74308
+ "resource:conflict",
74309
+ "server:internal_error"
74310
+ ]
74311
+ },
74312
+ updateAppointmentServiceCategory: {
74313
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
74314
+ method: "PUT",
74315
+ auth: "user",
74316
+ responseKind: "json",
74317
+ errors: [
74318
+ "validation:invalid_input",
74319
+ "auth:forbidden",
74320
+ "resource:not_found",
74321
+ "resource:conflict",
74322
+ "server:internal_error"
74323
+ ]
74324
+ },
74325
+ deleteAppointmentServiceCategory: {
74326
+ path: "/sites/{siteId}/bookings/service-categories/{categoryId}",
74327
+ method: "DELETE",
74328
+ auth: "user",
74329
+ responseKind: "json",
74330
+ errors: [
74331
+ "auth:forbidden",
74332
+ "resource:not_found",
74333
+ "server:internal_error"
74334
+ ]
74335
+ },
74336
+ reorderAppointmentServiceCategories: {
74337
+ path: "/sites/{siteId}/bookings/service-categories/order",
74338
+ method: "PUT",
74339
+ auth: "user",
74340
+ responseKind: "json",
74341
+ errors: [
74342
+ "validation:invalid_input",
74343
+ "auth:forbidden",
74344
+ "resource:conflict",
74345
+ "server:internal_error"
74346
+ ]
74347
+ }
74348
+ }),
73583
74349
  listAppointmentResources: {
73584
74350
  path: "/sites/{siteId}/bookings/resources",
73585
74351
  method: "GET",
@@ -74727,39 +75493,6 @@ const NEWSLETTER_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
74727
75493
  auth: "user",
74728
75494
  responseKind: "json",
74729
75495
  demoMutation: DEMO_MUTATION_BLOCKED_HIDDEN
74730
- },
74731
- listNewsletterSendJobs: {
74732
- path: "/sites/{siteId}/newsletter/jobs",
74733
- method: "GET",
74734
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
74735
- auth: "user",
74736
- responseKind: "json"
74737
- },
74738
- retryNewsletterSendJob: {
74739
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/retry",
74740
- method: "POST",
74741
- errors: [
74742
- "validation:invalid_input",
74743
- "resource:not_found",
74744
- "auth:forbidden",
74745
- "server:internal_error"
74746
- ],
74747
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
74748
- auth: "user",
74749
- responseKind: "json"
74750
- },
74751
- cancelNewsletterSendJob: {
74752
- path: "/sites/{siteId}/newsletter/jobs/{jobId}/cancel",
74753
- method: "POST",
74754
- errors: [
74755
- "validation:invalid_input",
74756
- "resource:not_found",
74757
- "auth:forbidden",
74758
- "server:internal_error"
74759
- ],
74760
- tags: [TAG_TEMPLATES.site, TAG_TEMPLATES.siteNewsletter],
74761
- auth: "user",
74762
- responseKind: "json"
74763
75496
  }
74764
75497
  });
74765
75498
  //#endregion
@@ -75525,6 +76258,7 @@ const BILLING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
75525
76258
  }
75526
76259
  });
75527
76260
  ({
76261
+ ...PUBLIC_NEWSLETTER_ENDPOINT_DEFINITIONS,
75528
76262
  ...ACCOUNT_ENDPOINT_DEFINITIONS,
75529
76263
  ...PUBLIC_ONBOARDING_ATTEMPT_ENDPOINT_DEFINITIONS,
75530
76264
  ...PUBLIC_EVENT_ENDPOINT_DEFINITIONS,
@@ -76394,7 +77128,12 @@ const appointmentSetupServiceDraftSchema = z.object({
76394
77128
  venue: appointmentSetupVenueChoiceSchema,
76395
77129
  timezone: z.string().trim().min(1).nullable(),
76396
77130
  payment: appointmentSetupPaymentDraftSchema,
76397
- assignment: appointmentSetupServiceAssignmentDraftSchema
77131
+ assignment: appointmentSetupServiceAssignmentDraftSchema,
77132
+ /**
77133
+ * The service's category, or null for "Other" (issue 1403). Omitted leaves
77134
+ * an existing service's category alone and puts a new one in "Other".
77135
+ */
77136
+ categoryId: z.string().uuid().nullable().optional()
76398
77137
  }).superRefine((service, ctx) => {
76399
77138
  if (service.payment.kind !== "free" && (service.priceAmount === null || service.priceAmount <= 0)) ctx.addIssue({
76400
77139
  code: "custom",