@riverbankcms/sdk 0.104.0 → 0.104.3

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 (129) hide show
  1. package/dist/{PreviewEditorSidebar-ywqcYjTB.mjs → PreviewEditorSidebar-CBMhU9p_.mjs} +3 -3
  2. package/dist/{PreviewEditorUI-DApUNF6g.mjs → PreviewEditorUI-C5KEw4M8.mjs} +2 -2
  3. package/dist/{SdkPreviewModeRuntime-DmHCcIMO.mjs → SdkPreviewModeRuntime-Iq12HjL7.mjs} +1147 -180
  4. package/dist/{SiteChromeCustomizeContext-BV9GU-3C.mjs → SiteChromeCustomizeContext-Cx0NMdyv.mjs} +1818 -951
  5. package/dist/_dts/ai/src/contracts/proposals.d.ts +10 -0
  6. package/dist/_dts/api/src/bookingVerticals.d.ts +64 -1
  7. package/dist/_dts/blocks/src/primitives/layout/Grid.d.ts +12 -8
  8. package/dist/_dts/blocks/src/system/blocks/events/event-calendar.d.ts +3 -0
  9. package/dist/_dts/blocks/src/system/blocks/events/shared/layout.d.ts +11 -1
  10. package/dist/_dts/blocks/src/system/blocks/testimonials.d.ts +7 -1
  11. package/dist/_dts/blocks/src/system/constants/events.d.ts +1 -1
  12. package/dist/_dts/blocks/src/system/fragments/library/testimonialsCarousel.d.ts +12 -0
  13. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +2 -2
  14. package/dist/_dts/blocks/src/system/runtime/nodes/basic.d.ts +14 -0
  15. package/dist/_dts/blocks/src/system/runtime/nodes/buttonAffordance.d.ts +33 -0
  16. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +28 -0
  17. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCapacity.d.ts +32 -0
  18. package/dist/_dts/blocks/src/system/runtime/nodes/shared/contract-schemas.d.ts +1 -0
  19. package/dist/_dts/blocks/src/system/runtime/nodes/svg.d.ts +77 -0
  20. package/dist/_dts/blocks/src/system/transforms/registry/_utils/surfaceForegroundStyles.d.ts +2 -0
  21. package/dist/_dts/blocks/src/system/transforms/registry/boxStyles.d.ts +3 -40
  22. package/dist/_dts/blocks/src/system/transforms/registry/formatting.d.ts +14 -0
  23. package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +11 -0
  24. package/dist/_dts/core/src/branded.d.ts +5 -0
  25. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +18 -0
  26. package/dist/_dts/editor/src/index.d.ts +3 -1
  27. package/dist/_dts/editor/src/primitives/rich-text/RichTextEditorSurface.d.ts +14 -6
  28. package/dist/_dts/editor/src/primitives/rich-text/RichTextToolbar.d.ts +23 -4
  29. package/dist/_dts/editor/src/primitives/rich-text/config.d.ts +6 -0
  30. package/dist/_dts/editor/src/primitives/rich-text/extensions/RichTextLink.d.ts +1 -0
  31. package/dist/_dts/editor/src/primitives/rich-text/paste.d.ts +10 -0
  32. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +2 -2
  33. package/dist/_dts/sdk/src/version.d.ts +1 -1
  34. package/dist/_dts/theme-core/src/ThemeScope.d.ts +1 -1
  35. package/dist/_dts/theme-core/src/buttons/affordanceIcons.d.ts +29 -0
  36. package/dist/_dts/theme-core/src/buttons/generateButtonCss.d.ts +6 -0
  37. package/dist/_dts/theme-core/src/buttons/index.d.ts +1 -0
  38. package/dist/_dts/theme-core/src/buttons/personalities/flat-ink.d.ts +20 -0
  39. package/dist/_dts/theme-core/src/buttons/personalities/index.d.ts +11 -0
  40. package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +4 -0
  41. package/dist/_dts/theme-core/src/buttons/types.d.ts +34 -0
  42. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  43. package/dist/_dts/theme-core/src/gradients/types.d.ts +27 -3
  44. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +51 -15
  45. package/dist/_dts/theme-core/src/palette/curated/brand-led.d.ts +13 -0
  46. package/dist/_dts/theme-core/src/palette/curated/index.d.ts +12 -0
  47. package/dist/_dts/theme-core/src/palette/historicalThemePalette.d.ts +44 -0
  48. package/dist/_dts/theme-core/src/palette/index.d.ts +1 -0
  49. package/dist/_dts/theme-core/src/palette/legacyRuntimeProjection.d.ts +23 -6
  50. package/dist/_dts/theme-core/src/palette/legacySelection.d.ts +13 -2
  51. package/dist/_dts/theme-core/src/palette/paletteCompatibilityTransition.d.ts +0 -9
  52. package/dist/_dts/theme-core/src/palette/persistenceReadSource.d.ts +17 -2
  53. package/dist/_dts/theme-core/src/palette/themePaletteCompatibility.d.ts +3 -4
  54. package/dist/_dts/theme-core/src/runtime/themeCssVarNames.d.ts +1 -0
  55. package/dist/_dts/theme-core/src/schema.d.ts +108 -26
  56. package/dist/_dts/theme-core/src/site-styles/accessibleSurfaces.d.ts +2 -1
  57. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +50 -1
  58. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +2 -2
  59. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -2
  60. package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +56 -1
  61. package/dist/_dts/theme-core/src/site-styles/typographyPresets.d.ts +16 -1
  62. package/dist/_dts/theme-core/src/status/index.d.ts +0 -1
  63. package/dist/_dts/theme-core/src/surfaces/foregroundContract.d.ts +19 -0
  64. package/dist/_dts/theme-core/src/surfaces/foregroundRoles.d.ts +9 -0
  65. package/dist/_dts/theme-core/src/surfaces/index.d.ts +2 -0
  66. package/dist/_dts/ui/src/button.d.ts +1 -1
  67. package/dist/cli/index.mjs +1567 -470
  68. package/dist/client/{DiscountCodeDisclosure-Dit2aItR.mjs → DiscountCodeDisclosure-BXM73_E5.mjs} +1 -1
  69. package/dist/client/{EventCombined.client-BnREyCjf.mjs → EventCombined.client-DT5elP7j.mjs} +4 -4
  70. package/dist/client/{EventListing.client-0uHld51x.mjs → EventListing.client-pTYGfV9C.mjs} +2 -2
  71. package/dist/client/{LeafletStaticMap-DJcvSfmj.mjs → LeafletStaticMap-dQrhlUe3.mjs} +1 -1
  72. package/dist/client/{MagicLinkForm-EAsIktfw.mjs → MagicLinkForm-u6Dul4j5.mjs} +2 -2
  73. package/dist/client/{StepIndicator-CRR17tYI.mjs → StepIndicator-nWqineJ2.mjs} +2 -2
  74. package/dist/client/{accordion-B47HIuuH.mjs → accordion-CSJcTUyH.mjs} +1 -1
  75. package/dist/client/{bookingForm-I1E-8F9P.mjs → bookingForm-C73wOuVa.mjs} +6 -6
  76. package/dist/client/bookings.mjs +137 -1
  77. package/dist/client/{buttons-BbPn8OKA.mjs → buttons-DfYyqt8C.mjs} +138 -2
  78. package/dist/client/{carousel-OlM7SyBs.mjs → carousel-fMBb0JSc.mjs} +1 -1
  79. package/dist/client/{cart-BKpG5jz6.mjs → cart-BtaCwT3S.mjs} +3 -3
  80. package/dist/client/{checkout-Dmmr6qoU.mjs → checkout-DkTwZSnh.mjs} +3 -3
  81. package/dist/client/client.mjs +2336 -544
  82. package/dist/client/{colorStyles-DuW4Fjqd.mjs → colorStyles-Wyl6JujD.mjs} +45 -4
  83. package/dist/client/{courseRegistration-CflZiqGk.mjs → courseRegistration-DnLmapJu.mjs} +7 -7
  84. package/dist/client/{eventCalendar-BvoLWcp-.mjs → eventCalendar-BvLOc9KY.mjs} +165 -5
  85. package/dist/client/{eventCombined-DsrO8t_t.mjs → eventCombined-kShgJPQr.mjs} +2 -2
  86. package/dist/client/{eventDetails-C3hxgcJm.mjs → eventDetails-C5NTM0Vo.mjs} +3 -3
  87. package/dist/client/{eventListing-C-hb237X.mjs → eventListing-B82ZdKP6.mjs} +2 -2
  88. package/dist/client/{eventRegistration-B0gyzDGi.mjs → eventRegistration-qJVFqBWo.mjs} +7 -7
  89. package/dist/client/{form-C_RUi6oJ.mjs → form-BvAcKiuu.mjs} +2 -2
  90. package/dist/client/{headerSection-IKQiD2U0.mjs → headerSection-NvAiEHAg.mjs} +3 -3
  91. package/dist/client/hooks.mjs +1023 -337
  92. package/dist/client/{locationMap-8ZBrMzRJ.mjs → locationMap-D0j52xfI.mjs} +3 -3
  93. package/dist/client/{media-CZJKL7fi.mjs → media-BCW_g2cQ.mjs} +1 -1
  94. package/dist/client/{newsletterForm-CYk3ucwj.mjs → newsletterForm-B8_i93qH.mjs} +2 -2
  95. package/dist/client/{pricing-B5GmvfIH.mjs → pricing-DIYnyHOk.mjs} +1 -1
  96. package/dist/client/{productDetail-BjS2Dqzo.mjs → productDetail-w0eAHkuQ.mjs} +3 -3
  97. package/dist/client/{productList-BOHvC8My.mjs → productList-Vsguq5Xt.mjs} +3 -3
  98. package/dist/client/{propsDecoder-WS6UEJwZ.mjs → propsDecoder-CD2pJ1WB.mjs} +37 -2
  99. package/dist/client/rendering/client.mjs +1723 -762
  100. package/dist/client/rendering/islands.mjs +19 -19
  101. package/dist/client/rendering.mjs +2312 -520
  102. package/dist/client/{shop-CON03-wN.mjs → shop-Dgxs-Q8F.mjs} +6 -6
  103. package/dist/client/{shop-commerce-DlqrFgH8.mjs → shop-commerce-DJAhsTaT.mjs} +6 -6
  104. package/dist/client/{shop-commerce-store-l74-Kne6.mjs → shop-commerce-store-CfCXltXW.mjs} +1 -1
  105. package/dist/client/{shopShared-CQheEXSg.mjs → shopShared-CmDcPXUt.mjs} +2 -2
  106. package/dist/client/{useDiscountCodeUi-v6OluyWl.mjs → useDiscountCodeUi-CirrT2bM.mjs} +1 -1
  107. package/dist/client/{useEventFilters-BqSTcNOf.mjs → useEventFilters-CNZxlCcO.mjs} +5 -5
  108. package/dist/client/{usePolling-DMfADjIF.mjs → usePolling-Baks_iBI.mjs} +2 -2
  109. package/dist/preview-next/client/runtime.mjs +3 -3
  110. package/dist/{sdk-runtime-DWb08Xaz.mjs → sdk-runtime-BfNiFeNJ.mjs} +1 -1
  111. package/dist/{separator--6yjc914.mjs → separator-BhAR_Xmf.mjs} +1 -1
  112. package/dist/server/components.mjs +2301 -531
  113. package/dist/server/config-validation.mjs +1028 -337
  114. package/dist/server/config.mjs +1028 -337
  115. package/dist/server/data.mjs +1023 -337
  116. package/dist/server/index.mjs +1 -1
  117. package/dist/server/next.mjs +2304 -534
  118. package/dist/server/page-converter.mjs +1018 -324
  119. package/dist/server/prebuild.mjs +1 -1
  120. package/dist/server/rendering/server.mjs +2286 -521
  121. package/dist/server/rendering.mjs +2301 -531
  122. package/dist/server/routing.mjs +1100 -338
  123. package/dist/server/server.mjs +1024 -338
  124. package/dist/server/theme-bridge.mjs +1240 -77
  125. package/dist/server/theme.mjs +165 -5
  126. package/dist/styles/index.css +617 -34
  127. package/package.json +1 -1
  128. package/dist/_dts/blocks/src/system/transforms/registry/_utils/sectionForegroundStyles.d.ts +0 -2
  129. package/dist/_dts/theme-core/src/status/contextualColors.d.ts +0 -6
@@ -50,11 +50,11 @@ const PORTAL_TOKEN_COOKIE = "portal_token";
50
50
  //#region ../path-utils/src/index.ts
51
51
  const PATH_SEPARATOR_REGEX$1 = /\./g;
52
52
  const BRACKET_ACCESS_REGEX$1 = /\[([^\]]+)\]/g;
53
- function isRecord$31(value) {
53
+ function isRecord$33(value) {
54
54
  return typeof value === "object" && value !== null && !Array.isArray(value);
55
55
  }
56
56
  function isPathContainer(value) {
57
- return Array.isArray(value) || isRecord$31(value);
57
+ return Array.isArray(value) || isRecord$33(value);
58
58
  }
59
59
  function getContainerValue(container, segment) {
60
60
  if (typeof segment === "number") return Array.isArray(container) ? container[segment] : void 0;
@@ -7698,11 +7698,11 @@ function joinStoragePathSegments(segments) {
7698
7698
  }
7699
7699
  //#endregion
7700
7700
  //#region ../media-core/src/typeGuards.ts
7701
- function isRecord$29(value) {
7701
+ function isRecord$31(value) {
7702
7702
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7703
7703
  }
7704
7704
  function asRecord$3(value) {
7705
- return isRecord$29(value) ? value : void 0;
7705
+ return isRecord$31(value) ? value : void 0;
7706
7706
  }
7707
7707
  function isFileJsonObject(value) {
7708
7708
  return Object.values(value).every(isFileJsonValue);
@@ -7711,7 +7711,7 @@ function isFileJsonValue(value) {
7711
7711
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
7712
7712
  if (typeof value === "number") return Number.isFinite(value);
7713
7713
  if (Array.isArray(value)) return value.every(isFileJsonValue);
7714
- return isRecord$29(value) && isFileJsonObject(value);
7714
+ return isRecord$31(value) && isFileJsonObject(value);
7715
7715
  }
7716
7716
  new TextDecoder("latin1");
7717
7717
  //#endregion
@@ -8015,7 +8015,7 @@ function isCmsMediaObjectType(value) {
8015
8015
  return typeof value === "string" && CMS_MEDIA_OBJECT_TYPES.includes(value);
8016
8016
  }
8017
8017
  function isCmsMediaObject(value) {
8018
- if (!isRecord$29(value)) return false;
8018
+ if (!isRecord$31(value)) return false;
8019
8019
  const type = value.type;
8020
8020
  if (!isCmsMediaObjectType(type)) return false;
8021
8021
  if ("attrs" in value) return false;
@@ -8026,7 +8026,7 @@ function collectMediaObjects(value, out, location = "") {
8026
8026
  value.forEach((item, index) => collectMediaObjects(item, out, `${location}[${index}]`));
8027
8027
  return;
8028
8028
  }
8029
- if (!isRecord$29(value)) return;
8029
+ if (!isRecord$31(value)) return;
8030
8030
  if (isCmsMediaObject(value)) out.push({
8031
8031
  obj: value,
8032
8032
  location
@@ -8040,8 +8040,8 @@ function walkAndCollectImageAttrs(value, out, location = "") {
8040
8040
  value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
8041
8041
  return;
8042
8042
  }
8043
- if (!isRecord$29(value)) return;
8044
- if (value.type === "image" && isRecord$29(value.attrs)) out.push({
8043
+ if (!isRecord$31(value)) return;
8044
+ if (value.type === "image" && isRecord$31(value.attrs)) out.push({
8045
8045
  attrs: value.attrs,
8046
8046
  location: location ? `${location}.attrs` : "attrs"
8047
8047
  });
@@ -8291,6 +8291,9 @@ const ALLOWED_NODE_TYPES = [
8291
8291
  "richText",
8292
8292
  "media",
8293
8293
  "videoEmbed",
8294
+ "icon",
8295
+ "feature-card-icon",
8296
+ "svg",
8294
8297
  "button",
8295
8298
  "link",
8296
8299
  "carousel",
@@ -8327,11 +8330,13 @@ const ALLOWED_TRANSFORM_IDS = [
8327
8330
  "value.jsonArray",
8328
8331
  "value.csv",
8329
8332
  "array.marqueeLoop",
8333
+ "value.mapToken",
8330
8334
  "layout.maxWidthClass",
8331
8335
  "layout.headerRootClass",
8332
8336
  "layout.footerRootClass",
8333
8337
  "layout.headerRootStyle",
8334
8338
  "layout.headerSectionBackground",
8339
+ "layout.classicNavMode",
8335
8340
  "layout.footerRootStyle",
8336
8341
  "ui.headerNavLinkClass",
8337
8342
  "ui.headingClassFromAlignment",
@@ -10190,7 +10195,7 @@ function getSemanticIconComponent(name) {
10190
10195
  //#endregion
10191
10196
  //#region ../blocks/src/system/icons/value.ts
10192
10197
  const NO_BLOCK_ICON_VALUE = { kind: "none" };
10193
- function isRecord$28(value) {
10198
+ function isRecord$30(value) {
10194
10199
  return typeof value === "object" && value !== null && !Array.isArray(value);
10195
10200
  }
10196
10201
  function parseBlockIconValue(value) {
@@ -10198,7 +10203,7 @@ function parseBlockIconValue(value) {
10198
10203
  kind: "semantic",
10199
10204
  iconId: value
10200
10205
  };
10201
- if (!isRecord$28(value)) return NO_BLOCK_ICON_VALUE;
10206
+ if (!isRecord$30(value)) return NO_BLOCK_ICON_VALUE;
10202
10207
  switch (value.kind) {
10203
10208
  case "none": return NO_BLOCK_ICON_VALUE;
10204
10209
  case "semantic": return isSemanticIconId(value.iconId) ? {
@@ -10271,7 +10276,7 @@ function buildGeneratedBlockIconRenderAssetLookupFromUnknown(value) {
10271
10276
  if (!Array.isArray(value)) return { kind: "invalid_asset_list" };
10272
10277
  const inputs = [];
10273
10278
  for (const [index, item] of value.entries()) {
10274
- if (!isRecord$28(item)) return {
10279
+ if (!isRecord$30(item)) return {
10275
10280
  kind: "invalid_asset_entry",
10276
10281
  index,
10277
10282
  reason: "not_object"
@@ -12624,6 +12629,234 @@ function expandPalette(palette) {
12624
12629
  return expanded;
12625
12630
  }
12626
12631
  //#endregion
12632
+ //#region ../theme-core/src/tokens/colorTokenParts.ts
12633
+ function parseOpacityPercent(source) {
12634
+ const trimmed = source.trim();
12635
+ if (!trimmed) return null;
12636
+ if (!/^\d{1,3}(?:\.\d+)?$/.test(trimmed)) return null;
12637
+ const value = Number(trimmed);
12638
+ if (!Number.isFinite(value)) return null;
12639
+ if (value < 0 || value > 100) return null;
12640
+ return value;
12641
+ }
12642
+ function parseColorTokenParts(source) {
12643
+ const trimmed = source.trim();
12644
+ if (trimmed.startsWith("raw:")) return {
12645
+ kind: "raw",
12646
+ value: trimmed.slice(4)
12647
+ };
12648
+ const [token, opacityRaw, extra] = trimmed.split("/");
12649
+ if (!token || extra !== void 0) return {
12650
+ kind: "invalid",
12651
+ token: trimmed
12652
+ };
12653
+ if (opacityRaw === void 0) return {
12654
+ kind: "token",
12655
+ token,
12656
+ opacityPercent: null
12657
+ };
12658
+ const opacityPercent = parseOpacityPercent(opacityRaw);
12659
+ if (opacityPercent === null) return {
12660
+ kind: "invalid",
12661
+ token: trimmed
12662
+ };
12663
+ return {
12664
+ kind: "token",
12665
+ token,
12666
+ opacityPercent
12667
+ };
12668
+ }
12669
+ //#endregion
12670
+ //#region ../theme-core/src/tokens/blockColorTokens.ts
12671
+ const blockColorTokenNames = [
12672
+ "text",
12673
+ "heading",
12674
+ "mutedText",
12675
+ "surface",
12676
+ "background",
12677
+ "foreground",
12678
+ "border",
12679
+ "primary",
12680
+ "secondary",
12681
+ "accent",
12682
+ "success",
12683
+ "warning",
12684
+ "info",
12685
+ "error",
12686
+ "destructive",
12687
+ "scrim",
12688
+ "shadow",
12689
+ "onPrimary",
12690
+ "onSecondary",
12691
+ "onAccent",
12692
+ "onSuccess",
12693
+ "onWarning",
12694
+ "onInfo",
12695
+ "onError",
12696
+ "onDestructive",
12697
+ "onSurface"
12698
+ ];
12699
+ const blockColorTokenNameSet = new Set(blockColorTokenNames);
12700
+ function isBlockColorToken(value) {
12701
+ if (typeof value !== "string") return false;
12702
+ const parts = parseColorTokenParts(value);
12703
+ return parts.kind === "token" && blockColorTokenNameSet.has(parts.token);
12704
+ }
12705
+ function parseBlockColorToken(value) {
12706
+ if (!isBlockColorToken(value)) return null;
12707
+ return value.trim();
12708
+ }
12709
+ //#endregion
12710
+ //#region ../theme-core/src/tokens/themeColorTokens.ts
12711
+ /**
12712
+ * Known palette role token names. This is the low-level source of truth for
12713
+ * palette roles used by palette variants, Theme V2 schemas, and runtime color
12714
+ * reference parsing.
12715
+ */
12716
+ const PALETTE_TOKEN_NAMES = [
12717
+ "primary",
12718
+ "primaryForeground",
12719
+ "secondary",
12720
+ "accent",
12721
+ "success",
12722
+ "warning",
12723
+ "destructive",
12724
+ "background",
12725
+ "surface",
12726
+ "surfaceAlt",
12727
+ "muted",
12728
+ "text",
12729
+ "border"
12730
+ ];
12731
+ const paletteTokenNameSchema = z$1.enum(PALETTE_TOKEN_NAMES);
12732
+ const themeColorBaseTokenNames = [
12733
+ ...PALETTE_TOKEN_NAMES,
12734
+ ...blockColorTokenNames,
12735
+ "white",
12736
+ "neutral",
12737
+ "primaryText",
12738
+ "secondaryForeground",
12739
+ "accentForeground"
12740
+ ];
12741
+ const themeColorBaseTokenSet = new Set(themeColorBaseTokenNames);
12742
+ const themeColorShadeSteps = [
12743
+ "50",
12744
+ "100",
12745
+ "200",
12746
+ "300",
12747
+ "400",
12748
+ "500",
12749
+ "600",
12750
+ "700",
12751
+ "800",
12752
+ "900",
12753
+ "950"
12754
+ ];
12755
+ const shadeStepSet = new Set(themeColorShadeSteps);
12756
+ function isKnownBaseToken(value) {
12757
+ return themeColorBaseTokenSet.has(value);
12758
+ }
12759
+ function isKnownShadeToken(value) {
12760
+ const match = value.match(/^([a-zA-Z][a-zA-Z0-9]*)(?:-([0-9]{2,3}))$/);
12761
+ if (!match) return false;
12762
+ const [, base, shade] = match;
12763
+ return Boolean(base && shade && isKnownBaseToken(base) && shadeStepSet.has(shade));
12764
+ }
12765
+ function isThemeColorTokenBase(value) {
12766
+ return isKnownBaseToken(value) || isKnownShadeToken(value);
12767
+ }
12768
+ function isThemeColorToken(value) {
12769
+ if (typeof value !== "string") return false;
12770
+ const parts = parseColorTokenParts(value);
12771
+ return parts.kind === "token" && isThemeColorTokenBase(parts.token);
12772
+ }
12773
+ function parseThemeColorToken(value) {
12774
+ if (!isThemeColorToken(value)) return null;
12775
+ return value.trim();
12776
+ }
12777
+ const themeColorTokenSchema = z$1.string().min(1).refine((value) => Boolean(isThemeColorToken(value)), "Expected a known Theme V2 color token");
12778
+ //#endregion
12779
+ //#region ../theme-core/src/palette/colors.ts
12780
+ const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/;
12781
+ const NORMALIZABLE_HEX_REGEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
12782
+ const hexColorSchema = z$1.string().regex(HEX_COLOR_REGEX, "Hex must be #RRGGBB (lowercase)").transform((value) => value);
12783
+ const paletteModeSchema = z$1.enum(["light", "dark"]);
12784
+ z$1.string().regex(/^[0-9a-f]{64}$/, "Palette hash must be a lowercase SHA-256 value").transform((value) => value);
12785
+ const paletteColorsSchema = z$1.object({
12786
+ primary: hexColorSchema,
12787
+ primaryForeground: hexColorSchema,
12788
+ secondary: hexColorSchema,
12789
+ accent: hexColorSchema,
12790
+ success: hexColorSchema,
12791
+ warning: hexColorSchema,
12792
+ destructive: hexColorSchema,
12793
+ background: hexColorSchema,
12794
+ surface: hexColorSchema,
12795
+ surfaceAlt: hexColorSchema,
12796
+ muted: hexColorSchema,
12797
+ text: hexColorSchema,
12798
+ border: hexColorSchema
12799
+ }).strict().transform((value) => value);
12800
+ function normalizePaletteHex(value) {
12801
+ if (!NORMALIZABLE_HEX_REGEX.test(value)) return null;
12802
+ const normalized = value.toLowerCase();
12803
+ if (normalized.length === 7) return normalized;
12804
+ const triplet = normalized.slice(1);
12805
+ const red = triplet.charAt(0);
12806
+ const green = triplet.charAt(1);
12807
+ const blue = triplet.charAt(2);
12808
+ return `#${red}${red}${green}${green}${blue}${blue}`;
12809
+ }
12810
+ function paletteColorsFromEntries(entries) {
12811
+ const values = {};
12812
+ for (const entry of entries) {
12813
+ const parsedRole = paletteTokenNameSchema.safeParse(entry.name);
12814
+ if (!parsedRole.success) return {
12815
+ ok: false,
12816
+ error: {
12817
+ kind: "unknown-role",
12818
+ role: entry.name
12819
+ }
12820
+ };
12821
+ const role = parsedRole.data;
12822
+ if (values[role] !== void 0) return {
12823
+ ok: false,
12824
+ error: {
12825
+ kind: "duplicate-role",
12826
+ role
12827
+ }
12828
+ };
12829
+ const hex = normalizePaletteHex(entry.hex);
12830
+ if (!hex) return {
12831
+ ok: false,
12832
+ error: {
12833
+ kind: "invalid-color",
12834
+ role,
12835
+ value: entry.hex
12836
+ }
12837
+ };
12838
+ values[role] = hex;
12839
+ }
12840
+ const missing = PALETTE_TOKEN_NAMES.filter((role) => values[role] === void 0);
12841
+ if (missing.length > 0) return {
12842
+ ok: false,
12843
+ error: {
12844
+ kind: "missing-roles",
12845
+ roles: missing
12846
+ }
12847
+ };
12848
+ return {
12849
+ ok: true,
12850
+ colors: paletteColorsSchema.parse(values)
12851
+ };
12852
+ }
12853
+ function requirePaletteColorsFromEntries(entries) {
12854
+ const result = paletteColorsFromEntries(entries);
12855
+ if (result.ok) return result.colors;
12856
+ throw new Error(`Invalid complete palette: ${result.error.kind}`);
12857
+ }
12858
+ new Set(PALETTE_TOKEN_NAMES);
12859
+ //#endregion
12627
12860
  //#region ../../node_modules/.pnpm/@vibrant+image@4.0.4/node_modules/@vibrant/image/dist/esm/histogram.js
12628
12861
  var Histogram = class {
12629
12862
  constructor(pixels, opts) {
@@ -13654,233 +13887,6 @@ const pipeline = new BasicPipeline().filter.register("default", (r, g, b, a) =>
13654
13887
  //#region ../../node_modules/.pnpm/node-vibrant@4.0.4/node_modules/node-vibrant/dist/esm/browser.js
13655
13888
  Vibrant.use(pipeline);
13656
13889
  //#endregion
13657
- //#region ../theme-core/src/tokens/colorTokenParts.ts
13658
- function parseOpacityPercent(source) {
13659
- const trimmed = source.trim();
13660
- if (!trimmed) return null;
13661
- if (!/^\d{1,3}(?:\.\d+)?$/.test(trimmed)) return null;
13662
- const value = Number(trimmed);
13663
- if (!Number.isFinite(value)) return null;
13664
- if (value < 0 || value > 100) return null;
13665
- return value;
13666
- }
13667
- function parseColorTokenParts(source) {
13668
- const trimmed = source.trim();
13669
- if (trimmed.startsWith("raw:")) return {
13670
- kind: "raw",
13671
- value: trimmed.slice(4)
13672
- };
13673
- const [token, opacityRaw, extra] = trimmed.split("/");
13674
- if (!token || extra !== void 0) return {
13675
- kind: "invalid",
13676
- token: trimmed
13677
- };
13678
- if (opacityRaw === void 0) return {
13679
- kind: "token",
13680
- token,
13681
- opacityPercent: null
13682
- };
13683
- const opacityPercent = parseOpacityPercent(opacityRaw);
13684
- if (opacityPercent === null) return {
13685
- kind: "invalid",
13686
- token: trimmed
13687
- };
13688
- return {
13689
- kind: "token",
13690
- token,
13691
- opacityPercent
13692
- };
13693
- }
13694
- //#endregion
13695
- //#region ../theme-core/src/tokens/blockColorTokens.ts
13696
- const blockColorTokenNames = [
13697
- "text",
13698
- "heading",
13699
- "mutedText",
13700
- "surface",
13701
- "background",
13702
- "foreground",
13703
- "border",
13704
- "primary",
13705
- "secondary",
13706
- "accent",
13707
- "success",
13708
- "warning",
13709
- "info",
13710
- "error",
13711
- "destructive",
13712
- "scrim",
13713
- "shadow",
13714
- "onPrimary",
13715
- "onSecondary",
13716
- "onAccent",
13717
- "onSuccess",
13718
- "onWarning",
13719
- "onInfo",
13720
- "onError",
13721
- "onDestructive",
13722
- "onSurface"
13723
- ];
13724
- const blockColorTokenNameSet = new Set(blockColorTokenNames);
13725
- function isBlockColorToken(value) {
13726
- if (typeof value !== "string") return false;
13727
- const parts = parseColorTokenParts(value);
13728
- return parts.kind === "token" && blockColorTokenNameSet.has(parts.token);
13729
- }
13730
- function parseBlockColorToken(value) {
13731
- if (!isBlockColorToken(value)) return null;
13732
- return value.trim();
13733
- }
13734
- //#endregion
13735
- //#region ../theme-core/src/tokens/themeColorTokens.ts
13736
- /**
13737
- * Known palette role token names. This is the low-level source of truth for
13738
- * palette roles used by palette variants, Theme V2 schemas, and runtime color
13739
- * reference parsing.
13740
- */
13741
- const PALETTE_TOKEN_NAMES = [
13742
- "primary",
13743
- "primaryForeground",
13744
- "secondary",
13745
- "accent",
13746
- "success",
13747
- "warning",
13748
- "destructive",
13749
- "background",
13750
- "surface",
13751
- "surfaceAlt",
13752
- "muted",
13753
- "text",
13754
- "border"
13755
- ];
13756
- const paletteTokenNameSchema = z$1.enum(PALETTE_TOKEN_NAMES);
13757
- const themeColorBaseTokenNames = [
13758
- ...PALETTE_TOKEN_NAMES,
13759
- ...blockColorTokenNames,
13760
- "white",
13761
- "neutral",
13762
- "primaryText",
13763
- "secondaryForeground",
13764
- "accentForeground"
13765
- ];
13766
- const themeColorBaseTokenSet = new Set(themeColorBaseTokenNames);
13767
- const themeColorShadeSteps = [
13768
- "50",
13769
- "100",
13770
- "200",
13771
- "300",
13772
- "400",
13773
- "500",
13774
- "600",
13775
- "700",
13776
- "800",
13777
- "900",
13778
- "950"
13779
- ];
13780
- const shadeStepSet = new Set(themeColorShadeSteps);
13781
- function isKnownBaseToken(value) {
13782
- return themeColorBaseTokenSet.has(value);
13783
- }
13784
- function isKnownShadeToken(value) {
13785
- const match = value.match(/^([a-zA-Z][a-zA-Z0-9]*)(?:-([0-9]{2,3}))$/);
13786
- if (!match) return false;
13787
- const [, base, shade] = match;
13788
- return Boolean(base && shade && isKnownBaseToken(base) && shadeStepSet.has(shade));
13789
- }
13790
- function isThemeColorTokenBase(value) {
13791
- return isKnownBaseToken(value) || isKnownShadeToken(value);
13792
- }
13793
- function isThemeColorToken(value) {
13794
- if (typeof value !== "string") return false;
13795
- const parts = parseColorTokenParts(value);
13796
- return parts.kind === "token" && isThemeColorTokenBase(parts.token);
13797
- }
13798
- function parseThemeColorToken(value) {
13799
- if (!isThemeColorToken(value)) return null;
13800
- return value.trim();
13801
- }
13802
- const themeColorTokenSchema = z$1.string().min(1).refine((value) => Boolean(isThemeColorToken(value)), "Expected a known Theme V2 color token");
13803
- //#endregion
13804
- //#region ../theme-core/src/palette/colors.ts
13805
- const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/;
13806
- const NORMALIZABLE_HEX_REGEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
13807
- const hexColorSchema = z$1.string().regex(HEX_COLOR_REGEX, "Hex must be #RRGGBB (lowercase)").transform((value) => value);
13808
- const paletteModeSchema = z$1.enum(["light", "dark"]);
13809
- z$1.string().regex(/^[0-9a-f]{64}$/, "Palette hash must be a lowercase SHA-256 value").transform((value) => value);
13810
- const paletteColorsSchema = z$1.object({
13811
- primary: hexColorSchema,
13812
- primaryForeground: hexColorSchema,
13813
- secondary: hexColorSchema,
13814
- accent: hexColorSchema,
13815
- success: hexColorSchema,
13816
- warning: hexColorSchema,
13817
- destructive: hexColorSchema,
13818
- background: hexColorSchema,
13819
- surface: hexColorSchema,
13820
- surfaceAlt: hexColorSchema,
13821
- muted: hexColorSchema,
13822
- text: hexColorSchema,
13823
- border: hexColorSchema
13824
- }).strict().transform((value) => value);
13825
- function normalizePaletteHex(value) {
13826
- if (!NORMALIZABLE_HEX_REGEX.test(value)) return null;
13827
- const normalized = value.toLowerCase();
13828
- if (normalized.length === 7) return normalized;
13829
- const triplet = normalized.slice(1);
13830
- const red = triplet.charAt(0);
13831
- const green = triplet.charAt(1);
13832
- const blue = triplet.charAt(2);
13833
- return `#${red}${red}${green}${green}${blue}${blue}`;
13834
- }
13835
- function paletteColorsFromEntries(entries) {
13836
- const values = {};
13837
- for (const entry of entries) {
13838
- const parsedRole = paletteTokenNameSchema.safeParse(entry.name);
13839
- if (!parsedRole.success) return {
13840
- ok: false,
13841
- error: {
13842
- kind: "unknown-role",
13843
- role: entry.name
13844
- }
13845
- };
13846
- const role = parsedRole.data;
13847
- if (values[role] !== void 0) return {
13848
- ok: false,
13849
- error: {
13850
- kind: "duplicate-role",
13851
- role
13852
- }
13853
- };
13854
- const hex = normalizePaletteHex(entry.hex);
13855
- if (!hex) return {
13856
- ok: false,
13857
- error: {
13858
- kind: "invalid-color",
13859
- role,
13860
- value: entry.hex
13861
- }
13862
- };
13863
- values[role] = hex;
13864
- }
13865
- const missing = PALETTE_TOKEN_NAMES.filter((role) => values[role] === void 0);
13866
- if (missing.length > 0) return {
13867
- ok: false,
13868
- error: {
13869
- kind: "missing-roles",
13870
- roles: missing
13871
- }
13872
- };
13873
- return {
13874
- ok: true,
13875
- colors: paletteColorsSchema.parse(values)
13876
- };
13877
- }
13878
- function requirePaletteColorsFromEntries(entries) {
13879
- const result = paletteColorsFromEntries(entries);
13880
- if (result.ok) return result.colors;
13881
- throw new Error(`Invalid complete palette: ${result.error.kind}`);
13882
- }
13883
- //#endregion
13884
13890
  //#region ../theme-core/src/site-styles/styleTags.ts
13885
13891
  /**
13886
13892
  * Descriptive style-axis metadata for button personalities and palette
@@ -14154,6 +14160,71 @@ const brandLedJewel = defineCuratedPalette({
14154
14160
  }
14155
14161
  ]
14156
14162
  });
14163
+ const brandLedSpruce = defineCuratedPalette({
14164
+ id: "brand-led-spruce",
14165
+ name: "Brand-led — Spruce",
14166
+ description: "Spruce-ink primary and moss secondary with an amber accent over a green-mist ground and hairline sage borders.",
14167
+ tags: {
14168
+ tone: "classic",
14169
+ energy: "calm",
14170
+ era: "editorial"
14171
+ },
14172
+ mode: "light",
14173
+ colors: [
14174
+ {
14175
+ name: "primary",
14176
+ hex: "#14312b"
14177
+ },
14178
+ {
14179
+ name: "secondary",
14180
+ hex: "#356a59"
14181
+ },
14182
+ {
14183
+ name: "accent",
14184
+ hex: "#f08a4b"
14185
+ },
14186
+ {
14187
+ name: "success",
14188
+ hex: "#356a59"
14189
+ },
14190
+ {
14191
+ name: "warning",
14192
+ hex: "#f08a4b"
14193
+ },
14194
+ {
14195
+ name: "destructive",
14196
+ hex: "#b5561f"
14197
+ },
14198
+ {
14199
+ name: "background",
14200
+ hex: "#ecefe9"
14201
+ },
14202
+ {
14203
+ name: "surface",
14204
+ hex: "#ffffff"
14205
+ },
14206
+ {
14207
+ name: "surfaceAlt",
14208
+ hex: "#f4f6f1"
14209
+ },
14210
+ {
14211
+ name: "muted",
14212
+ hex: "#5c6a64"
14213
+ },
14214
+ {
14215
+ name: "text",
14216
+ hex: "#1c2a26"
14217
+ },
14218
+ {
14219
+ name: "primaryForeground",
14220
+ hex: "#f4f7f1"
14221
+ },
14222
+ {
14223
+ name: "border",
14224
+ hex: "#d7ddd2"
14225
+ }
14226
+ ]
14227
+ });
14157
14228
  const brandLedBurgundyGold = defineCuratedPalette({
14158
14229
  id: "brand-led-burgundy-gold",
14159
14230
  name: "Brand-led — Burgundy Gold",
@@ -14693,6 +14764,7 @@ const curatedPalettes = [
14693
14764
  brandLedWarm,
14694
14765
  brandLedJewel,
14695
14766
  brandLedBurgundyGold,
14767
+ brandLedSpruce,
14696
14768
  defineCuratedPalette({
14697
14769
  id: "warm-neutral-clay",
14698
14770
  name: "Warm Neutral — Clay",
@@ -15744,6 +15816,10 @@ const buttonBorderSchema = z$1.object({
15744
15816
  widthClass: z$1.string(),
15745
15817
  colorToken: themeColorTokenSchema
15746
15818
  });
15819
+ const buttonVariantAffordanceSchema = z$1.object({
15820
+ iconId: z$1.string().min(1),
15821
+ colorToken: themeColorTokenSchema.optional()
15822
+ });
15747
15823
  const variantShadowSchema = z$1.object({
15748
15824
  elevation: z$1.enum([
15749
15825
  "none",
@@ -15797,7 +15873,8 @@ const buttonVariantSchema = z$1.object({
15797
15873
  border: buttonBorderSchema.optional(),
15798
15874
  hoverBackgroundToken: z$1.string().optional(),
15799
15875
  effects: variantEffectsSchema.optional(),
15800
- sizes: variantSizeOverridesSchema.optional()
15876
+ sizes: variantSizeOverridesSchema.optional(),
15877
+ affordance: buttonVariantAffordanceSchema.optional()
15801
15878
  });
15802
15879
  const buttonSystemSchema = z$1.object({
15803
15880
  global: buttonGlobalSettingsSchema,
@@ -16341,6 +16418,136 @@ const editorialLink = defineButtonPersonality({
16341
16418
  }
16342
16419
  });
16343
16420
  //#endregion
16421
+ //#region ../theme-core/src/buttons/personalities/flat-ink.ts
16422
+ /**
16423
+ * Flat Ink — a solid ink primary, a hairline ghost secondary, and a single
16424
+ * amber accent climax, all completely flat (no drop shadow) with a modest
16425
+ * radius. Reads as grounded, editorial and clinician-precise: the Fulcrum
16426
+ * physiotherapy voice. The primary shifts to the secondary (moss) on hover and
16427
+ * every variant lifts 2px; the amber accent is meant to be used sparingly as
16428
+ * the page's one conversion climax.
16429
+ */
16430
+ const flatInk = defineButtonPersonality({
16431
+ id: "flat-ink",
16432
+ name: "Flat Ink",
16433
+ description: "Flat solid-ink primary, hairline ghost secondary, and a single amber accent climax — modest radius, no shadow.",
16434
+ tags: {
16435
+ tone: "classic",
16436
+ energy: "calm",
16437
+ motion: "subtle",
16438
+ era: "editorial"
16439
+ },
16440
+ buttonSystem: {
16441
+ global: {
16442
+ cornerStyle: "rounded",
16443
+ shadow: "low",
16444
+ textTransform: "none",
16445
+ fontWeight: 600,
16446
+ borderWidth: 1,
16447
+ typography: "body",
16448
+ letterSpacing: "tight",
16449
+ paddingPreset: "default",
16450
+ hoverTransform: "lift",
16451
+ hoverColor: "none"
16452
+ },
16453
+ sizes: {
16454
+ sm: {
16455
+ padding: "0.625rem 1.125rem",
16456
+ fontSize: "text-sm"
16457
+ },
16458
+ md: { padding: "0.8125rem 1.375rem" },
16459
+ lg: {
16460
+ padding: "0.9375rem 1.625rem",
16461
+ fontSize: "text-lg"
16462
+ },
16463
+ xl: {
16464
+ padding: "1.0625rem 2rem",
16465
+ fontSize: "text-xl"
16466
+ }
16467
+ },
16468
+ variants: [
16469
+ {
16470
+ id: "primary",
16471
+ name: "Primary",
16472
+ enabled: true,
16473
+ priority: 1,
16474
+ background: {
16475
+ type: "solid",
16476
+ colorToken: "primary"
16477
+ },
16478
+ textColorToken: "primaryForeground",
16479
+ borderRadius: "rounded-lg",
16480
+ affordance: {
16481
+ iconId: "ArrowRight",
16482
+ colorToken: "accent"
16483
+ },
16484
+ shadow: {
16485
+ elevation: "none",
16486
+ softness: null,
16487
+ position: "bottom"
16488
+ },
16489
+ effects: { hover: [{
16490
+ effectId: "lift",
16491
+ options: { translateY: "-2px" }
16492
+ }, {
16493
+ effectId: "background-on-hover",
16494
+ options: { colorToken: "secondary" }
16495
+ }] }
16496
+ },
16497
+ {
16498
+ id: "secondary",
16499
+ name: "Secondary",
16500
+ enabled: true,
16501
+ priority: 2,
16502
+ background: { type: "transparent" },
16503
+ textColorToken: "primary",
16504
+ borderRadius: "rounded-lg",
16505
+ border: {
16506
+ widthClass: "border",
16507
+ colorToken: "border"
16508
+ },
16509
+ shadow: {
16510
+ elevation: "none",
16511
+ softness: null,
16512
+ position: "bottom"
16513
+ },
16514
+ effects: { hover: [{
16515
+ effectId: "lift",
16516
+ options: { translateY: "-2px" }
16517
+ }, {
16518
+ effectId: "background-on-hover",
16519
+ options: { colorToken: "surfaceAlt" }
16520
+ }] }
16521
+ },
16522
+ {
16523
+ id: "accent",
16524
+ name: "Accent",
16525
+ enabled: true,
16526
+ priority: 3,
16527
+ background: {
16528
+ type: "solid",
16529
+ colorToken: "accent"
16530
+ },
16531
+ textColorToken: "accentForeground",
16532
+ borderRadius: "rounded-lg",
16533
+ shadow: {
16534
+ elevation: "none",
16535
+ softness: null,
16536
+ position: "bottom"
16537
+ },
16538
+ affordance: {
16539
+ iconId: "ArrowRight",
16540
+ colorToken: "accentForeground"
16541
+ },
16542
+ effects: { hover: [{
16543
+ effectId: "lift",
16544
+ options: { translateY: "-2px" }
16545
+ }] }
16546
+ }
16547
+ ]
16548
+ }
16549
+ });
16550
+ //#endregion
16344
16551
  //#region ../theme-core/src/buttons/personalities/ink-stamp.ts
16345
16552
  /**
16346
16553
  * Ink Stamp — solid earthy fills with a bordered, hand-stamped block-print
@@ -17111,6 +17318,7 @@ const buttonPersonalities = [
17111
17318
  editorialLink,
17112
17319
  pebble,
17113
17320
  inkStamp,
17321
+ flatInk,
17114
17322
  brushedWash,
17115
17323
  showtimePill,
17116
17324
  joyStamp,
@@ -18041,6 +18249,13 @@ const legacyHeaderLookAliases = defineExactCatalog()([
18041
18249
  styleId: asHeaderStyleId("minimal")
18042
18250
  }
18043
18251
  },
18252
+ {
18253
+ lookId: asHeaderLookId("classic-centered-nav"),
18254
+ selection: {
18255
+ layoutId: asHeaderLayoutId("classic"),
18256
+ styleId: asHeaderStyleId("fine-line")
18257
+ }
18258
+ },
18044
18259
  {
18045
18260
  lookId: asHeaderLookId("brand-solid"),
18046
18261
  selection: {
@@ -18111,6 +18326,50 @@ function getCompatibleHeaderStyleIds(layoutId) {
18111
18326
  return isHeaderStyleCompatibleWithLayout(getHeaderStyleOrThrow(styleId), layoutId);
18112
18327
  });
18113
18328
  }
18329
+ function applyNavTypography(style, typography) {
18330
+ switch (typography) {
18331
+ case "plain": return style;
18332
+ case "heading": return {
18333
+ ...style,
18334
+ typography: "heading",
18335
+ fontWeight: "medium",
18336
+ letterSpacing: "wide"
18337
+ };
18338
+ case "quiet-caps": return {
18339
+ ...style,
18340
+ textTransform: "uppercase",
18341
+ textSize: "sm",
18342
+ letterSpacing: "wide"
18343
+ };
18344
+ case "brand-caps": return {
18345
+ ...style,
18346
+ textTransform: "uppercase",
18347
+ textSize: "sm",
18348
+ letterSpacing: "wider"
18349
+ };
18350
+ default: return assertNever$19(typography, "Unhandled header nav typography");
18351
+ }
18352
+ }
18353
+ function underlineNavLinkStyle(colorToken, underlineColorToken, typography = "plain") {
18354
+ return applyNavTypography({
18355
+ name: "Underline grow",
18356
+ typography: "body",
18357
+ fontWeight: "medium",
18358
+ textTransform: "none",
18359
+ italic: false,
18360
+ colorToken,
18361
+ padding: "compact",
18362
+ paddingX: "compact",
18363
+ borderRadius: "none",
18364
+ effects: { hover: [{
18365
+ effectId: "nav-underline",
18366
+ options: {
18367
+ style: "grow",
18368
+ ...underlineColorToken ? { colorToken: underlineColorToken } : {}
18369
+ }
18370
+ }] }
18371
+ }, typography);
18372
+ }
18114
18373
  function themedHeaderShadow(theme) {
18115
18374
  switch (theme.shadow.elevation) {
18116
18375
  case "none": return;
@@ -18210,6 +18469,38 @@ function cleanBaseHeader(theme) {
18210
18469
  ctaGap: "default"
18211
18470
  };
18212
18471
  }
18472
+ function classicCenteredNavHeader(theme) {
18473
+ return {
18474
+ ...baseHeader(theme),
18475
+ variant: "classic",
18476
+ navPlacement: "center",
18477
+ positioning: "sticky",
18478
+ shrinkOnScroll: false,
18479
+ background: {
18480
+ type: "color",
18481
+ color: "surface"
18482
+ },
18483
+ textColor: "text",
18484
+ navColor: "text",
18485
+ navStyle: "underline-grow",
18486
+ navWeight: "medium",
18487
+ navLinkStyle: underlineNavLinkStyle("text", "accent"),
18488
+ border: {
18489
+ style: "solid",
18490
+ width: "thin",
18491
+ position: "bottom",
18492
+ colorToken: "border"
18493
+ },
18494
+ ctaTreatment: "primary",
18495
+ ctaVariant: resolveHeaderCtaVariant(theme, "primary"),
18496
+ ctaGap: "default",
18497
+ logoText: {
18498
+ mobileMaxWidth: "sm",
18499
+ mobileWrapLines: 1,
18500
+ hideOnShrink: false
18501
+ }
18502
+ };
18503
+ }
18213
18504
  function serifLineHeader(theme) {
18214
18505
  return {
18215
18506
  ...baseHeader(theme),
@@ -18374,6 +18665,21 @@ const headerLookCatalog = defineExactCatalog()([
18374
18665
  logoAccommodations: inlineClassicLogoAccommodations,
18375
18666
  compile: cleanBaseHeader
18376
18667
  },
18668
+ {
18669
+ id: asHeaderLookId("classic-centered-nav"),
18670
+ label: "Centered nav",
18671
+ visualTreatment: "solid-neutral",
18672
+ layoutBehavior: "sticky",
18673
+ configuration: {
18674
+ structure: "inline-classic",
18675
+ treatment: "solid-neutral",
18676
+ behavior: "sticky"
18677
+ },
18678
+ openingRequirement: "none",
18679
+ maxRecommendedNavItems: 6,
18680
+ logoAccommodations: inlineClassicLogoAccommodations,
18681
+ compile: classicCenteredNavHeader
18682
+ },
18377
18683
  {
18378
18684
  id: asHeaderLookId("brand-solid"),
18379
18685
  label: "Brand solid",
@@ -19536,6 +19842,74 @@ const typographyPresets = [
19536
19842
  }
19537
19843
  }
19538
19844
  }
19845
+ }),
19846
+ defineTypographyPreset({
19847
+ id: "typography-preset:fulcrum",
19848
+ name: "Fulcrum",
19849
+ description: "Newsreader serif headings over an IBM Plex Sans body, with an IBM Plex Mono eyebrow role for uppercase kickers and mono captions.",
19850
+ defaultFonts: {
19851
+ body: "IBM Plex Sans, system-ui, -apple-system, sans-serif",
19852
+ heading: "Newsreader, Georgia, \"Times New Roman\", serif"
19853
+ },
19854
+ recommendedFonts: {
19855
+ body: [
19856
+ "IBM Plex Sans",
19857
+ "Source Sans 3",
19858
+ "Inter"
19859
+ ],
19860
+ heading: [
19861
+ "Newsreader",
19862
+ "Source Serif 4",
19863
+ "Lora"
19864
+ ]
19865
+ },
19866
+ slotPolicies: {
19867
+ body: bodyReadablePolicy,
19868
+ heading: displayHeadingPolicy
19869
+ },
19870
+ typography: {
19871
+ body: {
19872
+ family: "IBM Plex Sans, system-ui, -apple-system, sans-serif",
19873
+ size: "lg",
19874
+ weight: "regular",
19875
+ lineHeight: "relaxed",
19876
+ letterSpacing: "normal"
19877
+ },
19878
+ headings: {
19879
+ default: {
19880
+ family: "Newsreader, Georgia, \"Times New Roman\", serif",
19881
+ weight: "medium",
19882
+ lineHeight: "normal",
19883
+ letterSpacing: "tight",
19884
+ colorToken: "primary",
19885
+ italic: null,
19886
+ case: null
19887
+ },
19888
+ h1: {
19889
+ size: "2xl",
19890
+ weight: "medium",
19891
+ lineHeight: "normal",
19892
+ letterSpacing: "tight",
19893
+ italic: null
19894
+ },
19895
+ h2: {
19896
+ size: "xl",
19897
+ weight: "medium",
19898
+ lineHeight: "normal",
19899
+ letterSpacing: "tight",
19900
+ italic: null
19901
+ },
19902
+ h3: {
19903
+ size: "md",
19904
+ weight: "semibold",
19905
+ lineHeight: "normal",
19906
+ letterSpacing: "normal",
19907
+ italic: null
19908
+ }
19909
+ },
19910
+ scale: "spacious",
19911
+ eyebrow: { family: "IBM Plex Mono, ui-monospace, \"SF Mono\", monospace" }
19912
+ }
19539
19913
  })
19540
19914
  ];
19541
19915
  new Map(typographyPresets.map((preset) => [preset.id, preset]));
@@ -21279,7 +21653,53 @@ function generateDefaultButtonSystem(theme) {
21279
21653
  };
21280
21654
  }
21281
21655
  //#endregion
21656
+ //#region ../theme-core/src/buttons/affordanceIcons.ts
21657
+ /**
21658
+ * Affordance icon path data for CSS-emitted button affordances.
21659
+ *
21660
+ * A button variant may declare a trailing `affordance` icon (see
21661
+ * `ButtonVariantAffordance`). The generated button CSS renders that icon as a
21662
+ * masked `::after` pseudo-element, so the personality supplies it to every
21663
+ * button of the variant without per-node authoring.
21664
+ *
21665
+ * Icons live in the `@riverbankcms/blocks` registry, but the button CSS is
21666
+ * generated here in theme-core, so this module carries a small, curated copy of
21667
+ * the *affordance-eligible* icon path data (arrows/chevrons — the action-oriented
21668
+ * subset). The `d` values below are duplicated verbatim from the matching entries
21669
+ * in `blocks/src/system/icons/svg.tsx`; ids match the registry ids exactly so a
21670
+ * node-level `trailingIcon` and a personality `affordance` share one id space.
21671
+ *
21672
+ * Keep this set intentionally tiny. It is not the full CTA icon set — only the
21673
+ * shapes that read as a trailing affordance.
21674
+ */
21675
+ /** Registry icon id → the SVG path `d` fragments composing that icon. */
21676
+ const AFFORDANCE_ICON_PATHS = {
21677
+ ArrowRight: ["M5 12h14", "m13 6 6 6-6 6"],
21678
+ ArrowLeft: ["M19 12H5", "m11 6-6 6 6 6"],
21679
+ ChevronRight: ["m9 18 6-6-6-6"],
21680
+ ChevronLeft: ["m15 18-6-6 6-6"],
21681
+ ChevronDown: ["m6 9 6 6 6-6"],
21682
+ ArrowUpRight: ["M7 17 17 7", "M7 7h10v10"]
21683
+ };
21684
+ /**
21685
+ * Build a `data:image/svg+xml,...` URL for an affordance icon, suitable for use
21686
+ * as a CSS `mask-image`. The stroke is painted solid black so the mask's alpha
21687
+ * traces the icon outline; the visible colour is supplied separately by the
21688
+ * `::after` rule's `background-color`. Returns `null` for unknown ids.
21689
+ */
21690
+ function affordanceIconMaskUrl(iconId) {
21691
+ const paths = AFFORDANCE_ICON_PATHS[iconId];
21692
+ if (!paths || paths.length === 0) return null;
21693
+ 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, " ")}`;
21694
+ }
21695
+ //#endregion
21282
21696
  //#region ../theme-core/src/buttons/generateButtonCss.ts
21697
+ /**
21698
+ * Class the `button` runtime node adds when it renders its own trailing icon,
21699
+ * so a node-level icon suppresses the personality's CSS `::after` affordance
21700
+ * (node overrides personality). Shared so generator and renderer can't drift.
21701
+ */
21702
+ const AFFORDANCE_SUPPRESS_CLASS = "rb-affordance-off";
21283
21703
  const BUTTON_SIZE_NAMES = buttonSizeNameSchema.options;
21284
21704
  /**
21285
21705
  * Letter spacing token → CSS value.
@@ -21374,10 +21794,40 @@ function variantChunks(variant, global, themeSizes, themeId, tokens, theme) {
21374
21794
  out.push(variantFocusVisibleRule(variantId, themeId));
21375
21795
  const effects = variantEffectsCss(variant, themeId, tokens, theme);
21376
21796
  if (effects) out.push({ raw: effects });
21797
+ const affordance = variantAffordanceRule(variant, selector, tokens);
21798
+ if (affordance) out.push(affordance);
21377
21799
  out.push(...variantDisabledRules(variantId, themeId));
21378
21800
  out.push(...variantSizeRules(variant, variantId, themeSizes, themeId));
21379
21801
  return out;
21380
21802
  }
21803
+ /**
21804
+ * Emit the optional trailing affordance icon as a masked `::after`.
21805
+ *
21806
+ * Only variants that declare `affordance` with a known icon id produce a rule —
21807
+ * every other variant is byte-identical to before. The `:not(.rb-affordance-off)`
21808
+ * guard lets a `button` node that renders its own trailing icon suppress this
21809
+ * one (node overrides personality). Colour comes from the affordance token, or
21810
+ * `currentColor` (the button text colour) when no token is given.
21811
+ */
21812
+ function variantAffordanceRule(variant, selector, tokens) {
21813
+ const affordance = variant.affordance;
21814
+ if (!affordance) return null;
21815
+ const maskUrl = affordanceIconMaskUrl(affordance.iconId);
21816
+ if (!maskUrl) return null;
21817
+ const color = affordance.colorToken ? tokens.getColor(affordance.colorToken) : "currentColor";
21818
+ const mask = `url("${maskUrl}") no-repeat center / contain`;
21819
+ return rule(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
21820
+ ["content", "\"\""],
21821
+ ["display", "inline-block"],
21822
+ ["width", "1em"],
21823
+ ["height", "1em"],
21824
+ ["margin-inline-start", "0.5em"],
21825
+ ["flex", "0 0 auto"],
21826
+ ["background-color", color],
21827
+ ["-webkit-mask", mask],
21828
+ ["mask", mask]
21829
+ ]);
21830
+ }
21381
21831
  function parseButtonVariantIdForCss(value) {
21382
21832
  const variantId = parseThemeButtonVariantId(value);
21383
21833
  if (!variantId) throw new Error("Button variant id must be a non-empty string");
@@ -21759,18 +22209,65 @@ function generateDefaultCardSystem(theme) {
21759
22209
  return { variants: [defaultVariant, alternativeVariant] };
21760
22210
  }
21761
22211
  //#endregion
21762
- //#region ../theme-core/src/status/contextualColors.ts
21763
- const STATUS_MUTED_COLOR_VAR = "--rb-status-muted-color";
21764
- const DEFAULT_STATUS_MUTED_COLOR = "rgb(var(--tb-mutedText))";
21765
- const SURFACE_MUTED_STATUS_COLOR = "color-mix(in oklab, currentColor, transparent 18%)";
21766
- function statusMutedColorValue() {
21767
- return `var(${STATUS_MUTED_COLOR_VAR}, ${DEFAULT_STATUS_MUTED_COLOR})`;
22212
+ //#region ../theme-core/src/surfaces/foregroundRoles.ts
22213
+ const SURFACE_FOREGROUND_ROLES = [
22214
+ "text",
22215
+ "heading",
22216
+ "link",
22217
+ "supporting"
22218
+ ];
22219
+ //#endregion
22220
+ //#region ../theme-core/src/surfaces/foregroundContract.ts
22221
+ const SURFACE_FOREGROUND_CSS_VARS = {
22222
+ text: "--rb-surface-text-color",
22223
+ heading: "--rb-surface-heading-color",
22224
+ link: "--rb-surface-link-color",
22225
+ supporting: "--rb-surface-supporting-color"
22226
+ };
22227
+ const DEFAULT_SURFACE_TEXT_COLOR = "rgb(var(--tb-text, 17 24 39))";
22228
+ const DEFAULT_ROLE_FALLBACKS = {
22229
+ text: DEFAULT_SURFACE_TEXT_COLOR,
22230
+ heading: DEFAULT_SURFACE_TEXT_COLOR,
22231
+ link: "rgb(var(--tb-primary, var(--tb-text, 17 24 39)))",
22232
+ supporting: "rgb(var(--tb-mutedText, 100 116 139))"
22233
+ };
22234
+ function requireSurfaceColorValue(role, value) {
22235
+ const normalized = value.trim();
22236
+ if (normalized.length === 0) throw new RangeError(`Surface foreground ${role} must be a non-empty CSS color.`);
22237
+ return normalized;
21768
22238
  }
21769
- function statusMutedColorDeclaration() {
21770
- return `color: ${statusMutedColorValue()};`;
22239
+ function completeSurfaceForegrounds(input) {
22240
+ const text = requireSurfaceColorValue("text", input.text);
22241
+ return {
22242
+ text,
22243
+ heading: input.heading === void 0 ? text : requireSurfaceColorValue("heading", input.heading),
22244
+ link: input.link === void 0 ? text : requireSurfaceColorValue("link", input.link),
22245
+ supporting: input.supporting === void 0 ? text : requireSurfaceColorValue("supporting", input.supporting)
22246
+ };
22247
+ }
22248
+ function surfaceForegroundValue(role, outsideProviderFallback = DEFAULT_ROLE_FALLBACKS[role]) {
22249
+ const roleVariable = SURFACE_FOREGROUND_CSS_VARS[role];
22250
+ const fallback = requireSurfaceColorValue("fallback", outsideProviderFallback);
22251
+ if (role === "text") return `var(${roleVariable}, ${fallback})`;
22252
+ return `var(${roleVariable}, var(${SURFACE_FOREGROUND_CSS_VARS.text}, ${fallback}))`;
22253
+ }
22254
+ function surfaceForegroundValueWithOpacity(role, opacityPercent) {
22255
+ if (!Number.isFinite(opacityPercent) || opacityPercent < 0 || opacityPercent > 100) throw new RangeError("Surface foreground opacity must be between 0 and 100.");
22256
+ return `color-mix(in srgb, ${surfaceForegroundValue(role)} ${opacityPercent}%, transparent)`;
21771
22257
  }
21772
- function contextualMutedStatusColorDeclaration(value) {
21773
- return `${STATUS_MUTED_COLOR_VAR}: ${value}`;
22258
+ function surfaceForegroundStyle(input) {
22259
+ const foregrounds = completeSurfaceForegrounds(input);
22260
+ return {
22261
+ color: foregrounds.text,
22262
+ [SURFACE_FOREGROUND_CSS_VARS.text]: foregrounds.text,
22263
+ [SURFACE_FOREGROUND_CSS_VARS.heading]: foregrounds.heading,
22264
+ [SURFACE_FOREGROUND_CSS_VARS.link]: foregrounds.link,
22265
+ [SURFACE_FOREGROUND_CSS_VARS.supporting]: foregrounds.supporting
22266
+ };
22267
+ }
22268
+ function surfaceForegroundDeclarations(input) {
22269
+ const foregrounds = completeSurfaceForegrounds(input);
22270
+ return [`color: ${foregrounds.text}`, ...SURFACE_FOREGROUND_ROLES.map((role) => `${SURFACE_FOREGROUND_CSS_VARS[role]}: ${foregrounds[role]}`)];
21774
22271
  }
21775
22272
  //#endregion
21776
22273
  //#region ../theme-core/src/cards/generateCardCss.ts
@@ -21857,8 +22354,10 @@ function generateVariantCSS(variant, themeId, tokens, theme) {
21857
22354
  const direction = GRADIENT_DIRECTION_MAP$1[variant.background.direction] || "to right";
21858
22355
  rules.push(`background: linear-gradient(${direction}, ${fromColor}, ${toColor})`);
21859
22356
  } else rules.push(`background: transparent`);
21860
- if (variant.foregroundToken) rules.push(`color: ${tokens.getColor(variant.foregroundToken)}`);
21861
- rules.push(contextualMutedStatusColorDeclaration(SURFACE_MUTED_STATUS_COLOR));
22357
+ if (variant.background.type !== "transparent") {
22358
+ const foreground = tokens.getColor(variant.foregroundToken ?? "text");
22359
+ rules.push(...surfaceForegroundDeclarations({ text: foreground }));
22360
+ }
21862
22361
  if (variant.accentEdge) {
21863
22362
  const side = ACCENT_EDGE_SIDE_MAP$1[variant.accentEdge.edge];
21864
22363
  const width = ACCENT_EDGE_WIDTH_MAP$1[variant.accentEdge.width];
@@ -22219,11 +22718,31 @@ const gradientStopSchema = z$1.object({
22219
22718
  position: z$1.number().min(0).max(100)
22220
22719
  });
22221
22720
  /**
22222
- * Gradient configuration
22721
+ * Radial gradient shape. Optional; defaults to `circle` (the historical shape)
22722
+ * so existing radial gradients are byte-identical.
22723
+ *
22724
+ * Named `RadialGradientShape` to avoid clashing with `styles/gradients.ts`'s
22725
+ * `GradientShape` (which names the gradient *kind*: linear/radial/conic).
22726
+ */
22727
+ const radialGradientShapeSchema = z$1.enum(["circle", "ellipse"]);
22728
+ /**
22729
+ * Gradient configuration.
22730
+ *
22731
+ * `type` is optional and defaults to `'linear'` so gradients persisted without a
22732
+ * type (or with an explicit `'linear'`) render exactly as before. `'radial'` and
22733
+ * `'conic'` are additive kinds:
22734
+ * - linear: `angle` (degrees)
22735
+ * - radial: optional `shape` (circle/ellipse) + `position`
22736
+ * - conic: optional `angle` (from-angle, degrees) + `position`
22223
22737
  */
22224
22738
  const gradientConfigSchema = z$1.object({
22225
- type: z$1.enum(["linear", "radial"]),
22739
+ type: z$1.enum([
22740
+ "linear",
22741
+ "radial",
22742
+ "conic"
22743
+ ]).default("linear"),
22226
22744
  angle: z$1.number().min(0).max(360).optional(),
22745
+ shape: radialGradientShapeSchema.optional(),
22227
22746
  position: z$1.string().optional(),
22228
22747
  stops: z$1.array(gradientStopSchema).min(2)
22229
22748
  });
@@ -22762,9 +23281,17 @@ function parseToken(source) {
22762
23281
  if (parts.kind === "invalid") return { token: source };
22763
23282
  return {
22764
23283
  token: parts.token,
22765
- alpha: parts.opacityPercent === null ? void 0 : parts.opacityPercent / 100
23284
+ opacityPercent: parts.opacityPercent === null ? void 0 : parts.opacityPercent
22766
23285
  };
22767
23286
  }
23287
+ const SURFACE_TEXT_ROLE_BY_TOKEN = {
23288
+ text: "text",
23289
+ heading: "heading",
23290
+ mutedText: "supporting"
23291
+ };
23292
+ function surfaceRoleForTextToken(token) {
23293
+ return Object.prototype.hasOwnProperty.call(SURFACE_TEXT_ROLE_BY_TOKEN, token) ? SURFACE_TEXT_ROLE_BY_TOKEN[token] : null;
23294
+ }
22768
23295
  /**
22769
23296
  * Converts a theme color token to a CSS rgb value.
22770
23297
  * This is the primary function for applying theme colors to CSS properties.
@@ -22778,11 +23305,26 @@ function parseToken(source) {
22778
23305
  * rgbColorValue('raw:transparent') // => 'transparent'
22779
23306
  */
22780
23307
  function rgbColorValue(token) {
22781
- const { token: baseToken, alpha, raw } = parseToken(token);
23308
+ const { token: baseToken, opacityPercent, raw } = parseToken(token);
22782
23309
  if (raw) return raw;
22783
23310
  const cssVar = `${COLOR_VAR_PREFIX}${baseToken}`;
22784
- if (alpha === void 0) return `rgb(var(${cssVar}))`;
22785
- return `rgb(var(${cssVar}) / ${alpha})`;
23311
+ if (opacityPercent === void 0) return `rgb(var(${cssVar}))`;
23312
+ return `rgb(var(${cssVar}) / ${opacityPercent / 100})`;
23313
+ }
23314
+ /**
23315
+ * Converts a theme color token to a CSS variable reference.
23316
+ * Note: This returns the base variable without rgb() wrapper, ignoring opacity.
23317
+ *
23318
+ * @param token - Theme color token
23319
+ * @returns CSS variable reference
23320
+ *
23321
+ * @example
23322
+ * cssVarValue('primary') // => 'var(--tb-primary)'
23323
+ * cssVarValue('surface/95') // => 'var(--tb-surface)' (opacity ignored)
23324
+ */
23325
+ function cssVarValue(token) {
23326
+ const { token: baseToken } = parseToken(token);
23327
+ return `var(${COLOR_VAR_PREFIX}${baseToken})`;
22786
23328
  }
22787
23329
  /**
22788
23330
  * Creates a backgroundColor style object from a theme token.
@@ -22800,6 +23342,9 @@ function backgroundColorStyle(token) {
22800
23342
  * @returns CSSProperties with color
22801
23343
  */
22802
23344
  function textColorStyle(token) {
23345
+ const parsed = parseToken(token);
23346
+ const surfaceRole = parsed.raw ? null : surfaceRoleForTextToken(parsed.token);
23347
+ if (surfaceRole) return { color: parsed.opacityPercent === void 0 ? surfaceForegroundValue(surfaceRole) : surfaceForegroundValueWithOpacity(surfaceRole, parsed.opacityPercent) };
22803
23348
  return { color: rgbColorValue(token) };
22804
23349
  }
22805
23350
  /**
@@ -23206,6 +23751,7 @@ const typographyScaleSchema = z$1.enum([
23206
23751
  "majorThird",
23207
23752
  "perfectFourth"
23208
23753
  ]);
23754
+ const eyebrowTypographyStyleSchema = z$1.object({ family: z$1.string().min(1) });
23209
23755
  const typographySchema = z$1.object({
23210
23756
  body: bodyTypographyStyleSchema,
23211
23757
  headings: z$1.object({
@@ -23218,7 +23764,8 @@ const typographySchema = z$1.object({
23218
23764
  h6: headingTypographyOverridesSchema.optional()
23219
23765
  }),
23220
23766
  scale: typographyScaleSchema,
23221
- proseLinkStyle: proseLinkStyleSchema.optional()
23767
+ proseLinkStyle: proseLinkStyleSchema.optional(),
23768
+ eyebrow: eyebrowTypographyStyleSchema.optional()
23222
23769
  });
23223
23770
  const spaceSchema = z$1.enum([
23224
23771
  "comfortable",
@@ -23243,7 +23790,8 @@ const shadow = z$1.object({
23243
23790
  "soft",
23244
23791
  "hard"
23245
23792
  ]).nullable(),
23246
- position: z$1.enum(["bottom", "bottom-right"]).default("bottom")
23793
+ position: z$1.enum(["bottom", "bottom-right"]).default("bottom"),
23794
+ colorToken: themeColorTokenSchema.optional()
23247
23795
  });
23248
23796
  const border = z$1.object({
23249
23797
  width: z$1.enum([
@@ -23488,6 +24036,7 @@ const headerSchema = z$1.object({
23488
24036
  navWeight: navFontWeight.default("medium"),
23489
24037
  navTypography: z$1.enum(["body", "heading"]).optional(),
23490
24038
  navTextSize: navLinkStyleSchema.shape.textSize,
24039
+ navPlacement: z$1.enum(["end", "center"]).optional(),
23491
24040
  container: headerContainerSchema,
23492
24041
  border: headerBorderSchema,
23493
24042
  shadow: componentShadowSchema.optional(),
@@ -23979,7 +24528,8 @@ const curatedSiteStyleIdValues = [
23979
24528
  "site-style:bold-launch",
23980
24529
  "site-style:south-west-twerk-school",
23981
24530
  "site-style:kirtan-ornate",
23982
- "site-style:original-joy"
24531
+ "site-style:original-joy",
24532
+ "site-style:fulcrum"
23983
24533
  ];
23984
24534
  const quietBudget = {
23985
24535
  accentDensity: "low",
@@ -25433,6 +25983,104 @@ const curatedSiteStyles = [
25433
25983
  buttonChoice("showtime-pill")
25434
25984
  ],
25435
25985
  paletteRecommendations: [paletteRecommendation("warm-dark-ember")]
25986
+ }),
25987
+ defineCuratedSiteStyle({
25988
+ id: "site-style:fulcrum",
25989
+ name: "Fulcrum",
25990
+ headingDecoration: {
25991
+ family: "short-center-rule",
25992
+ colorToken: "accent"
25993
+ },
25994
+ description: "Spruce-ink and moss with an amber climax, Newsreader serif over IBM Plex Sans, hairline sage borders and flat buttons — a grounded, editorial physiotherapy skin.",
25995
+ generationBrief: "Use a calm, clinician-led voice: precise, reassuring and specific rather than clinical-cold. Lead with the assessment-treatment-rehab journey, mono uppercase eyebrows for practical labels (hours, registrations, appointment windows), Newsreader serif headings over IBM Plex Sans body, and a single amber call to action as the page climax. Keep proof concrete (registrations, same-week appointments, return-to-activity outcomes) and avoid spa or hard-sell wellness language or medical claims.",
25996
+ siteCreatorSelection: {
25997
+ kind: "site_creator_keyword_heuristic",
25998
+ priority: 28
25999
+ },
26000
+ selectionKeywordRules: [
26001
+ {
26002
+ kind: "substring",
26003
+ value: "physiotherap"
26004
+ },
26005
+ {
26006
+ kind: "word",
26007
+ value: "physio"
26008
+ },
26009
+ {
26010
+ kind: "phrase",
26011
+ value: "sports injury"
26012
+ },
26013
+ {
26014
+ kind: "phrase",
26015
+ value: "rehab clinic"
26016
+ },
26017
+ {
26018
+ kind: "substring",
26019
+ value: "musculoskeletal"
26020
+ },
26021
+ {
26022
+ kind: "phrase",
26023
+ value: "sports therapy"
26024
+ }
26025
+ ],
26026
+ tokenRecipes: {
26027
+ palette: "brand-led",
26028
+ contrast: "standard",
26029
+ radius: "rounded",
26030
+ shadow: "subtle",
26031
+ typography: "serif-editorial",
26032
+ spacing: "airy",
26033
+ motion: "subtle"
26034
+ },
26035
+ compositionBudget: quietBudget,
26036
+ typographyPresetChoices: [
26037
+ typographyChoice("typography-preset:fulcrum"),
26038
+ typographyChoice("typography-preset:editorial-serif"),
26039
+ typographyChoice("typography-preset:modern-sans")
26040
+ ],
26041
+ defaultTypographyPresetId: "typography-preset:fulcrum",
26042
+ themeOverrides: {
26043
+ shadow: { colorToken: "primary" },
26044
+ accordions: {
26045
+ background: null,
26046
+ spacing: "comfortable",
26047
+ corners: "square",
26048
+ border: null,
26049
+ shadow: null,
26050
+ icon: {
26051
+ style: "plus-minus",
26052
+ position: "right",
26053
+ size: "md",
26054
+ colorToken: "accent"
26055
+ },
26056
+ trigger: {
26057
+ textColorToken: "primary",
26058
+ fontWeight: "medium",
26059
+ fontFamily: "heading",
26060
+ fontScale: "md"
26061
+ },
26062
+ hover: { textColorToken: "secondary" },
26063
+ content: { textColorToken: "muted" }
26064
+ },
26065
+ testimonialQuote: serifDisplayQuote
26066
+ },
26067
+ recommendedHeaderLookIds: [
26068
+ "classic-centered-nav",
26069
+ "serif-line",
26070
+ "centered-calm",
26071
+ "clean-base"
26072
+ ],
26073
+ recommendedFooterLookIds: ["footer-editorial-columns", "footer-muted-grounding"],
26074
+ buttonPersonalityChoices: [
26075
+ buttonChoice("flat-ink"),
26076
+ buttonChoice("confident-chip"),
26077
+ buttonChoice("editorial-link")
26078
+ ],
26079
+ paletteRecommendations: [
26080
+ paletteRecommendation("brand-led-spruce"),
26081
+ paletteRecommendation("warm-neutral-forest"),
26082
+ paletteRecommendation("soft-natural-sage")
26083
+ ]
25436
26084
  })
25437
26085
  ];
25438
26086
  const curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id, style]));
@@ -27568,7 +28216,7 @@ function resolveBackgroundColorValue(value, context) {
27568
28216
  return resolveThemeColorReferenceValue(value, { palette: context.theme?.palette });
27569
28217
  }
27570
28218
  //#endregion
27571
- //#region ../blocks/src/system/transforms/registry/_utils/sectionForegroundStyles.ts
28219
+ //#region ../blocks/src/system/transforms/registry/_utils/surfaceForegroundStyles.ts
27572
28220
  function readColorToken(input, key) {
27573
28221
  const value = input[key];
27574
28222
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
@@ -27577,18 +28225,17 @@ function resolveOptionalForegroundColor(value, context) {
27577
28225
  if (!value) return null;
27578
28226
  return resolveBackgroundColorValue(value, context);
27579
28227
  }
27580
- function resolveSectionForegroundStyles(value, context) {
28228
+ function resolveSurfaceForegroundStyles(value, context) {
27581
28229
  if (!value || typeof value !== "object") return {};
27582
28230
  const input = value;
27583
28231
  const resolvedText = resolveOptionalForegroundColor(readColorToken(input, "textColor"), context);
27584
28232
  if (!resolvedText) return {};
27585
- return {
27586
- color: resolvedText,
27587
- "--section-text-color": resolvedText,
27588
- "--section-heading-color": resolveOptionalForegroundColor(readColorToken(input, "headingColor"), context) ?? resolvedText,
27589
- "--section-link-color": resolveOptionalForegroundColor(readColorToken(input, "linkColor"), context) ?? resolvedText,
27590
- "--section-supporting-color": resolveOptionalForegroundColor(readColorToken(input, "supportingColor"), context) ?? resolvedText
27591
- };
28233
+ return { ...surfaceForegroundStyle({
28234
+ text: resolvedText,
28235
+ heading: resolveOptionalForegroundColor(readColorToken(input, "headingColor"), context) ?? void 0,
28236
+ link: resolveOptionalForegroundColor(readColorToken(input, "linkColor"), context) ?? void 0,
28237
+ supporting: resolveOptionalForegroundColor(readColorToken(input, "supportingColor"), context) ?? void 0
28238
+ }) };
27592
28239
  }
27593
28240
  //#endregion
27594
28241
  //#region ../blocks/src/system/transforms/registry/boxStyles.ts
@@ -27926,67 +28573,12 @@ const containerStylesResolveBoxClassName = {
27926
28573
  }
27927
28574
  };
27928
28575
  /**
27929
- * Resolves section text color style object.
27930
- *
27931
- * The value should be the `_sectionStyles.background.textColor` token.
27932
- * Returns an empty object when unset so the section inherits text color.
27933
- *
27934
- * Outputs both `color` and `--section-text-color` CSS variable so that
27935
- * headings (which have explicit color rules in typography CSS) can inherit
27936
- * the section's text color via the CSS variable cascade.
27937
- */
27938
- const sectionTextColorStyleTransform = {
27939
- id: "sectionStyles.resolveTextColorStyle",
27940
- kind: "formatter",
27941
- summary: "Resolve section text color token into a style object",
27942
- run: (value, options, context) => {
27943
- const fallbackToken = options?.fallbackToken;
27944
- if (typeof value === "string" && value.trim()) {
27945
- const colorValue = resolveBackgroundColorValue(value.trim(), context);
27946
- return {
27947
- color: colorValue,
27948
- "--section-text-color": colorValue
27949
- };
27950
- }
27951
- if (typeof fallbackToken === "string" && fallbackToken.trim()) {
27952
- const colorValue = resolveBackgroundColorValue(fallbackToken.trim(), context);
27953
- return {
27954
- color: colorValue,
27955
- "--section-text-color": colorValue
27956
- };
27957
- }
27958
- return {};
27959
- }
27960
- };
27961
- /**
27962
- * Resolves section heading color style object.
27963
- *
27964
- * The value should be the `_sectionStyles.background.headingColor` token.
27965
- * Returns an empty object when unset so headings inherit from textColor or default.
27966
- *
27967
- * Outputs `--section-heading-color` CSS variable which typography CSS can use
27968
- * to style headings differently from body text within a section.
27969
- */
27970
- const sectionHeadingColorStyleTransform = {
27971
- id: "sectionStyles.resolveHeadingColorStyle",
27972
- kind: "formatter",
27973
- summary: "Resolve section heading color token into a style object",
27974
- run: (value, options, context) => {
27975
- const fallbackToken = options?.fallbackToken;
27976
- if (typeof value === "string" && value.trim()) return { "--section-heading-color": resolveBackgroundColorValue(value.trim(), context) };
27977
- if (typeof fallbackToken === "string" && fallbackToken.trim()) return { "--section-heading-color": resolveBackgroundColorValue(fallbackToken.trim(), context) };
27978
- return {};
27979
- }
27980
- };
27981
- /**
27982
- * Resolves section foreground colors from the background object.
28576
+ * Resolves surface foreground colors from the background object.
27983
28577
  *
27984
28578
  * The value should be the full `_sectionStyles.background` object.
27985
28579
  * Outputs:
27986
- * - `color` and `--section-text-color` from background.textColor
27987
- * - `--section-heading-color` from background.headingColor (falls back to textColor if not set)
27988
- * - `--section-link-color` from background.linkColor (falls back to textColor if not set)
27989
- * - `--section-supporting-color` from background.supportingColor (falls back to textColor if not set)
28580
+ * - `color` and the complete `--rb-surface-*-color` contract
28581
+ * - optional roles fall back atomically to background.textColor
27990
28582
  *
27991
28583
  * This combined transform replaces separate foreground color transforms
27992
28584
  * when both need to be applied to the same style prop.
@@ -27994,8 +28586,8 @@ const sectionHeadingColorStyleTransform = {
27994
28586
  const sectionColorsStyleTransform = {
27995
28587
  id: "sectionStyles.resolveColorsStyle",
27996
28588
  kind: "formatter",
27997
- summary: "Resolve section foreground colors into a style object",
27998
- run: (value, _options, context) => resolveSectionForegroundStyles(value, context)
28589
+ summary: "Resolve surface foreground colors into a style object",
28590
+ run: (value, _options, context) => resolveSurfaceForegroundStyles(value, context)
27999
28591
  };
28000
28592
  /**
28001
28593
  * Maps semantic gap values to theme-aware gap classes.
@@ -28034,8 +28626,6 @@ const boxStylesTransforms = [
28034
28626
  sectionClassNameTransform,
28035
28627
  containerStylesResolveClassName,
28036
28628
  containerStylesResolveBoxClassName,
28037
- sectionTextColorStyleTransform,
28038
- sectionHeadingColorStyleTransform,
28039
28629
  sectionColorsStyleTransform,
28040
28630
  {
28041
28631
  id: "boxStyles.semanticGap",
@@ -28803,7 +29393,29 @@ const ctaRowFragment = defineFragment({
28803
29393
  fields: [createCtasRepeater({
28804
29394
  label: "Calls to action",
28805
29395
  itemLabel: "CTA",
28806
- maxItems: 3
29396
+ maxItems: 3,
29397
+ button: { variants: [
29398
+ {
29399
+ value: "primary",
29400
+ label: "Primary"
29401
+ },
29402
+ {
29403
+ value: "secondary",
29404
+ label: "Secondary"
29405
+ },
29406
+ {
29407
+ value: "outline",
29408
+ label: "Outline"
29409
+ },
29410
+ {
29411
+ value: "link",
29412
+ label: "Link"
29413
+ },
29414
+ {
29415
+ value: "accent",
29416
+ label: "Accent"
29417
+ }
29418
+ ] }
28807
29419
  })],
28808
29420
  layout: [ctaRow({
28809
29421
  collectionPath: "content.ctas",
@@ -29084,6 +29696,55 @@ function buildGridCardsLayout(className) {
29084
29696
  return el("div", { className }, buildResponsiveGridCards());
29085
29697
  }
29086
29698
  /**
29699
+ * Build a single "featured editorial" testimonial card.
29700
+ *
29701
+ * This card type deliberately renders NO avatar/headshot: an amber quote mark,
29702
+ * the quote body, then the author name and a context line. Colour + surface
29703
+ * treatment is driven entirely by `variantClass` (see testimonials.css):
29704
+ * - `rb-testimonials-fe-card--feature` → spruce-ink card (dark primary bg)
29705
+ * - `rb-testimonials-fe-card--plain` → white surface card
29706
+ *
29707
+ * Extra modifiers (`repeat`, `when`) are forwarded to the card node so the
29708
+ * caller can drive first-vs-rest branching.
29709
+ */
29710
+ function buildFeaturedEditorialCard(variantClass, ...mods) {
29711
+ const quoteMark = text({
29712
+ as: "span",
29713
+ className: "rb-testimonials-fe-qmark",
29714
+ "aria-hidden": "true"
29715
+ }, props({ value: "“" }));
29716
+ const quote = richText({ className: "rb-testimonials-fe-quote rb-max-w-none" }, bind("testimonial.content.body"), when("testimonial.content.body"));
29717
+ const meta = el("div", { className: "rb-testimonials-fe-meta" }, [text({
29718
+ as: "strong",
29719
+ className: "rb-testimonials-fe-name"
29720
+ }, bind("testimonial.content.name", { fallback: "Anonymous" })), text({
29721
+ as: "span",
29722
+ className: "rb-testimonials-fe-context"
29723
+ }, when("testimonial.content.jobTitle"), bind("testimonial.content.jobTitle"))]);
29724
+ return el("div", { className: `rb-testimonials-fe-card ${variantClass}` }, [
29725
+ quoteMark,
29726
+ quote,
29727
+ meta
29728
+ ], ...mods);
29729
+ }
29730
+ /**
29731
+ * Build the "featured editorial" layout: an asymmetric two-column arrangement
29732
+ * where the FIRST testimonial is a large featured (spruce-ink) card and the
29733
+ * remaining testimonials stack as white cards beside it.
29734
+ *
29735
+ * First-vs-rest branching is done without an offset primitive: the featured
29736
+ * card uses `repeat(..., { limit: 1 })` (entry 0 only), and the rest column
29737
+ * repeats over every entry but skips index 0 via `when("tIndex", { equals: 0,
29738
+ * not: true })`. The `$when` guard is re-evaluated per repeated item with the
29739
+ * index scope in context, so entry 0 produces no node in the rest column.
29740
+ */
29741
+ function buildFeaturedEditorialLayout() {
29742
+ return el("div", { className: "rb-testimonials-fe-grid" }, [buildFeaturedEditorialCard("rb-testimonials-fe-card--feature", repeat("data.entries", "testimonial", { limit: 1 })), el("div", { className: "rb-testimonials-fe-col" }, [buildFeaturedEditorialCard("rb-testimonials-fe-card--plain", repeat("data.entries", "testimonial", { indexName: "tIndex" }), when("tIndex", {
29743
+ equals: 0,
29744
+ not: true
29745
+ }))])]);
29746
+ }
29747
+ /**
29087
29748
  * Fragment definition for testimonials carousel.
29088
29749
  * Provides field definitions and data loader configuration.
29089
29750
  *
@@ -32575,7 +33236,7 @@ const defaultLayout = styledSection({
32575
33236
  children: styledContainer({
32576
33237
  baseClassName: "rb-relative rb-isolate",
32577
33238
  children: stack({
32578
- gap: "xl",
33239
+ gap: "lg",
32579
33240
  className: "rb-w-full rb-max-w-4xl rb-mx-auto"
32580
33241
  }, [headingLayout, accordionLayout])
32581
33242
  }),
@@ -32592,7 +33253,7 @@ const questionColumnsLayout = styledSection({
32592
33253
  children: styledContainer({
32593
33254
  baseClassName: "rb-relative rb-isolate",
32594
33255
  children: stack({
32595
- gap: "xl",
33256
+ gap: "lg",
32596
33257
  className: "rb-w-full"
32597
33258
  }, [headingLayout, questionColumnsAccordionLayout])
32598
33259
  }),
@@ -32757,7 +33418,26 @@ const formattingTransforms = [
32757
33418
  arrayLengthTransform,
32758
33419
  jsonArrayTransform,
32759
33420
  csvTransform,
32760
- marqueeLoopTransform
33421
+ marqueeLoopTransform,
33422
+ {
33423
+ id: "value.mapToken",
33424
+ kind: "formatter",
33425
+ summary: "Map a state/enum value to a theme token colour (rgb(var(--tb-<role>)))",
33426
+ schema: z$1.object({
33427
+ /**
33428
+ * Maps an input state/enum value (stringified) to a theme token ROLE name
33429
+ * (e.g. `secondary`, `accent`, `muted`, `border`). The role is wrapped to
33430
+ * `rgb(var(--tb-<role>))` so the result drops straight into a `style` binding.
33431
+ */
33432
+ cases: z$1.record(z$1.string(), z$1.string()),
33433
+ /** Role used when the value matches no case. Omit to yield `undefined`. */
33434
+ fallback: z$1.string().optional()
33435
+ }),
33436
+ run: (value, options) => {
33437
+ const role = options.cases[String(value)] ?? options.fallback;
33438
+ return role ? `rgb(var(--tb-${role}))` : void 0;
33439
+ }
33440
+ }
32761
33441
  ];
32762
33442
  //#endregion
32763
33443
  //#region ../blocks/src/system/transforms/registry/ui.ts
@@ -33401,6 +34081,26 @@ const headerLogoTextVisibleTransform = {
33401
34081
  return resolveHeaderBrandDisplay(asPartialObject(value)) !== "logo-only";
33402
34082
  }
33403
34083
  };
34084
+ /**
34085
+ * Selects which classic-layout nav row should render.
34086
+ *
34087
+ * - `'inline'` → classic variant with the historical logo-left / nav-right row
34088
+ * - `'centered'`→ classic variant with a centred (flex-1) nav and a right CTA
34089
+ * - `'other'` → not a classic variant (centered/floating/transparent handle it)
34090
+ *
34091
+ * Consumed by the site-header block's `$when` gates so the two classic rows are
34092
+ * mutually exclusive without introducing a new top-level header variant.
34093
+ */
34094
+ const classicNavModeTransform = {
34095
+ id: "layout.classicNavMode",
34096
+ kind: "formatter",
34097
+ summary: "Resolve which classic-layout nav row to render",
34098
+ run: (value) => {
34099
+ const header = asPartialObject(value);
34100
+ if (header?.variant !== "classic") return "other";
34101
+ return header?.navPlacement === "center" ? "centered" : "inline";
34102
+ }
34103
+ };
33404
34104
  const maxWidthClassTransform = {
33405
34105
  id: "layout.maxWidthClass",
33406
34106
  kind: "string",
@@ -33583,8 +34283,9 @@ const footerRootStyleTransform = {
33583
34283
  styleParts.push(backgroundColorStyle(token));
33584
34284
  }
33585
34285
  }
33586
- if (visuals.textColorOverride) styleParts.push({ color: visuals.textColorOverride });
33587
- else if (visuals.textColorToken) styleParts.push(textColorStyle(visuals.textColorToken));
34286
+ const foreground = visuals.textColorOverride ?? (visuals.textColorToken ? rgbColorValue(visuals.textColorToken) : null);
34287
+ if (!visuals.isTransparent && foreground) styleParts.push(surfaceForegroundStyle({ text: foreground }));
34288
+ else if (foreground) styleParts.push({ color: foreground });
33588
34289
  styleParts.push(borderColorStyle("border"));
33589
34290
  return mergeStyles(...styleParts);
33590
34291
  }
@@ -33670,18 +34371,16 @@ const footerBottomBarStyleTransform = {
33670
34371
  const styleParts = [];
33671
34372
  const background = bottomBar?.background;
33672
34373
  if (background?.type === "color") styleParts.push(backgroundColorStyle(background.color ?? "surface"));
33673
- if (bottomBar?.textColor) styleParts.push(textColorStyle(bottomBar.textColor));
33674
34374
  const borderColorToken = bottomBar?.borderTop?.colorToken;
33675
34375
  if (borderColorToken) styleParts.push(borderColorStyle(borderColorToken));
33676
34376
  else styleParts.push(borderColorStyle("border"));
33677
34377
  const borderWidth = bottomBar?.borderTop?.width;
33678
34378
  if (borderWidth && borderWidth !== "none") styleParts.push({ borderTopWidth: borderWidth === "thin" ? "1px" : borderWidth === "medium" ? "2px" : "3px" });
33679
34379
  else if (borderWidth === "none") styleParts.push({ borderTopWidth: "0px" });
33680
- if (!bottomBar?.textColor) {
33681
- const visuals = resolveBackgroundVisualStyles(extractBackgroundColorToken(background, "surface"), context.theme);
33682
- if (visuals.textColorOverride) styleParts.push({ color: visuals.textColorOverride });
33683
- else if (visuals.textColorToken) styleParts.push(textColorStyle(visuals.textColorToken));
33684
- }
34380
+ const visuals = resolveBackgroundVisualStyles(extractBackgroundColorToken(background, "surface"), context.theme);
34381
+ const foreground = bottomBar?.textColor ? rgbColorValue(bottomBar.textColor) : visuals.textColorOverride ?? (visuals.textColorToken ? rgbColorValue(visuals.textColorToken) : null);
34382
+ if (background?.type === "color" && foreground) styleParts.push(surfaceForegroundStyle({ text: foreground }));
34383
+ else if (foreground && bottomBar?.textColor) styleParts.push({ color: foreground });
33685
34384
  return mergeStyles(...styleParts);
33686
34385
  }
33687
34386
  };
@@ -33816,6 +34515,7 @@ const layoutTransforms = [
33816
34515
  headerLogoClassTransform,
33817
34516
  headerLogoMediaClassTransform,
33818
34517
  headerLogoTextVisibleTransform,
34518
+ classicNavModeTransform,
33819
34519
  footerRootClassTransform,
33820
34520
  headerRootStyleTransform,
33821
34521
  headerSectionBackgroundTransform,
@@ -34243,7 +34943,18 @@ const classicLayout = inline({
34243
34943
  logoRow,
34244
34944
  createNavRow(`rb-ml-auto ${desktopOnlyDisplay("flex")} rb-gap-6`),
34245
34945
  headerCta
34246
- ], when("$root.theme.header.variant", { equals: "classic" }));
34946
+ ], whenTransformed$1("$root.theme.header", "layout.classicNavMode", "inline"));
34947
+ const classicCenteredNavLayout = inline({
34948
+ className: bindProp("$root.theme.header.maxWidth", {
34949
+ transforms: pipe(tx("layout.maxWidthClass", { base: `rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6` })),
34950
+ fallback: `rb-container rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6`
34951
+ }),
34952
+ align: "center"
34953
+ }, [
34954
+ logoRow,
34955
+ createNavRow(`rb-flex-1 rb-justify-center ${desktopOnlyDisplay("flex")} rb-gap-6`, "center"),
34956
+ headerCta
34957
+ ], whenTransformed$1("$root.theme.header", "layout.classicNavMode", "centered"));
34247
34958
  const centeredLayout = stack({
34248
34959
  gap: "md",
34249
34960
  align: "center",
@@ -34318,6 +35029,7 @@ const siteHeaderBlockDefinition = { manifest: createBlockManifest({
34318
35029
  }, [
34319
35030
  mobileHeaderBar,
34320
35031
  classicLayout,
35032
+ classicCenteredNavLayout,
34321
35033
  centeredLayout,
34322
35034
  transparentLayout,
34323
35035
  floatingLayout,
@@ -34526,7 +35238,7 @@ const footerLayout = section$1({
34526
35238
  style: { $bind: {
34527
35239
  from: "$root.theme.footer",
34528
35240
  transforms: [{ id: "layout.footerRootStyle" }],
34529
- fallback: mergeStyles(backgroundColorStyle("surface"), textColorStyle("text"), borderColorStyle("border"))
35241
+ fallback: mergeStyles(backgroundColorStyle("surface"), surfaceForegroundStyle({ text: "rgb(var(--tb-text))" }), borderColorStyle("border"))
34530
35242
  } }
34531
35243
  }, [
34532
35244
  simpleFooterLayout,
@@ -34580,7 +35292,7 @@ const backgroundImageClassName = "rb-absolute rb-inset-0 rb-z-neg-10 rb-h-full r
34580
35292
  */
34581
35293
  const wrapVariant$2 = (variantLayout) => styledSection({
34582
35294
  children: styledContainer({ children: stack({
34583
- gap: "xl",
35295
+ gap: "lg",
34584
35296
  className: "rb-relative"
34585
35297
  }, [...testimonialsHeadingFragment.layout, variantLayout]) }),
34586
35298
  baseClass: "",
@@ -34606,6 +35318,26 @@ const splitGridLayout = styledSection({
34606
35318
  spacing: "comfortable",
34607
35319
  imageClassName: backgroundImageClassName
34608
35320
  });
35321
+ const featuredEditorialLayout = styledSection({
35322
+ children: styledContainer({
35323
+ baseClassName: "rb-relative rb-isolate",
35324
+ children: stack({
35325
+ gap: "lg",
35326
+ className: "rb-relative"
35327
+ }, [el("div", { className: "rb-testimonials-fe-head" }, [text({
35328
+ as: "h2",
35329
+ className: "rb-text-left",
35330
+ headingDecoration: "off"
35331
+ }, when("content.heading"), bind("content.heading")), el("div", { className: "rb-testimonials-fe-leadin" }, [text({
35332
+ as: "p",
35333
+ className: "rb-prose",
35334
+ style: textColorStyle("mutedText")
35335
+ }, when("content.subheading"), bind("content.subheading"))])]), buildFeaturedEditorialLayout()])
35336
+ }),
35337
+ baseClass: "",
35338
+ spacing: "comfortable",
35339
+ imageClassName: backgroundImageClassName
35340
+ });
34609
35341
  const additionalFields = parseFieldDefinitions([
34610
35342
  ...testimonialsHeadingFragment.fields,
34611
35343
  {
@@ -34693,6 +35425,7 @@ const testimonialsBlockDefinition = {
34693
35425
  carousel: carouselLayout$1,
34694
35426
  grid: gridLayout$1,
34695
35427
  "split-grid": splitGridLayout,
35428
+ "featured-editorial": featuredEditorialLayout,
34696
35429
  highlight: highlightLayout
34697
35430
  },
34698
35431
  defaultVariant: "carousel",
@@ -34712,6 +35445,11 @@ const testimonialsBlockDefinition = {
34712
35445
  description: "Heading on the left with testimonial cards on the right",
34713
35446
  icon: "PanelLeft"
34714
35447
  },
35448
+ "featured-editorial": {
35449
+ label: "Featured editorial",
35450
+ description: "A large featured ink quote beside a column of stacked cards (no avatars)",
35451
+ icon: "Quote"
35452
+ },
34715
35453
  highlight: {
34716
35454
  label: "Highlight",
34717
35455
  description: "A single, centered testimonial with no carousel",
@@ -35235,7 +35973,7 @@ function grid$2(props, children) {
35235
35973
  const cardsLayout = styledSection({
35236
35974
  children: styledContainer({
35237
35975
  rootType: "stack",
35238
- rootProps: { gap: "xl" },
35976
+ rootProps: { gap: "lg" },
35239
35977
  children: [stack({
35240
35978
  gap: "sm",
35241
35979
  className: "rb-feature-cards-heading rb-mx-auto rb-max-w-3xl rb-text-center"
@@ -37218,8 +37956,11 @@ const recoverEmptyDescriptionWhenActive = {
37218
37956
  kind: "recover-empty-when-active",
37219
37957
  placeholder: "Add description"
37220
37958
  };
37221
- function eventSectionHeaderLayoutNodes() {
37222
- return [text({ as: "h2" }, bind("content.heading"), when("content.heading", { authoringPresence: recoverEmptyHeadingWhenActive })), richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description", { authoringPresence: recoverEmptyDescriptionWhenActive }))];
37959
+ function eventSectionHeaderLayoutNodes(options = {}) {
37960
+ const headingNode = text({ as: "h2" }, bind("content.heading"), when("content.heading", { authoringPresence: recoverEmptyHeadingWhenActive }));
37961
+ const descriptionNode = richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description", { authoringPresence: recoverEmptyDescriptionWhenActive }));
37962
+ if (options.splitClassName) return [el("div", { className: options.splitClassName }, [headingNode, descriptionNode])];
37963
+ return [headingNode, descriptionNode];
37223
37964
  }
37224
37965
  //#endregion
37225
37966
  //#region ../blocks/src/system/blocks/shop.shared.ts
@@ -37286,7 +38027,8 @@ const eventCalendarDisplayModeValues = [
37286
38027
  "combined",
37287
38028
  "calendar",
37288
38029
  "list",
37289
- "timetable"
38030
+ "timetable",
38031
+ "week-board"
37290
38032
  ];
37291
38033
  const eventCalendarDefaultViewValues = ["month", "week"];
37292
38034
  const eventCalendarListLayoutValues = [
@@ -39705,6 +40447,40 @@ function getEventAvailability(event) {
39705
40447
  isFull: false
39706
40448
  };
39707
40449
  }
40450
+ /**
40451
+ * Derive the three-state availability status (available / filling / waitlist)
40452
+ * plus a short pill label. Builds on {@link getEventAvailability} so the
40453
+ * capacity math (availableSpots with a `capacity - registeredCount` fallback)
40454
+ * stays consistent with the binary helper used elsewhere.
40455
+ */
40456
+ function getEventAvailabilityStatus(event, options) {
40457
+ const threshold = options?.fillingThreshold ?? 2;
40458
+ const { available, isFull } = getEventAvailability(event);
40459
+ if (available == null) return {
40460
+ status: "available",
40461
+ available: null,
40462
+ isFull: false,
40463
+ label: null
40464
+ };
40465
+ if (isFull) return {
40466
+ status: "waitlist",
40467
+ available: 0,
40468
+ isFull: true,
40469
+ label: event.waitlistEnabled ? "Waitlist" : "Full"
40470
+ };
40471
+ if (available <= threshold) return {
40472
+ status: "filling",
40473
+ available,
40474
+ isFull: false,
40475
+ label: `${available} left`
40476
+ };
40477
+ return {
40478
+ status: "available",
40479
+ available,
40480
+ isFull: false,
40481
+ label: `${available} places`
40482
+ };
40483
+ }
39708
40484
  //#endregion
39709
40485
  //#region ../blocks/src/system/runtime/nodes/events/shared/utils.ts
39710
40486
  /**
@@ -42240,7 +43016,7 @@ const eventCalendarLayout = [styledSection({
42240
43016
  children: styledContainer({
42241
43017
  rootType: "stack",
42242
43018
  rootProps: { gap: "lg" },
42243
- children: [...eventSectionHeaderLayoutNodes(), el("event-calendar", withApiBindings({
43019
+ children: [...eventSectionHeaderLayoutNodes({ splitClassName: "event-section-head event-section-head--split" }), el("event-calendar", withApiBindings({
42244
43020
  events: { $bind: { from: "data.events" } },
42245
43021
  stage: { $bind: {
42246
43022
  from: "$root.previewStage",
@@ -42351,6 +43127,10 @@ const eventCalendarAdditionalFields = [
42351
43127
  value: "timetable",
42352
43128
  label: "Timetable"
42353
43129
  },
43130
+ {
43131
+ value: "week-board",
43132
+ label: "Week board"
43133
+ },
42354
43134
  {
42355
43135
  value: "list",
42356
43136
  label: "List"
@@ -42533,7 +43313,8 @@ const eventCalendarAdditionalFields = [
42533
43313
  oneOf: [
42534
43314
  "combined",
42535
43315
  "calendar",
42536
- "timetable"
43316
+ "timetable",
43317
+ "week-board"
42537
43318
  ]
42538
43319
  }
42539
43320
  }
@@ -42636,7 +43417,8 @@ const eventCalendarAdditionalFields = [
42636
43417
  "combined",
42637
43418
  "calendar",
42638
43419
  "list",
42639
- "timetable"
43420
+ "timetable",
43421
+ "week-board"
42640
43422
  ]
42641
43423
  }
42642
43424
  }
@@ -42671,7 +43453,8 @@ const eventCalendarAdditionalFields = [
42671
43453
  "combined",
42672
43454
  "calendar",
42673
43455
  "list",
42674
- "timetable"
43456
+ "timetable",
43457
+ "week-board"
42675
43458
  ]
42676
43459
  }, {
42677
43460
  field: "showFilters",
@@ -42694,7 +43477,8 @@ const eventCalendarAdditionalFields = [
42694
43477
  "combined",
42695
43478
  "calendar",
42696
43479
  "list",
42697
- "timetable"
43480
+ "timetable",
43481
+ "week-board"
42698
43482
  ]
42699
43483
  }, {
42700
43484
  field: "showFilters",
@@ -42717,7 +43501,8 @@ const eventCalendarAdditionalFields = [
42717
43501
  "combined",
42718
43502
  "calendar",
42719
43503
  "list",
42720
- "timetable"
43504
+ "timetable",
43505
+ "week-board"
42721
43506
  ]
42722
43507
  }, {
42723
43508
  field: "showFilters",
@@ -42739,7 +43524,8 @@ const eventCalendarAdditionalFields = [
42739
43524
  oneOf: [
42740
43525
  "combined",
42741
43526
  "list",
42742
- "timetable"
43527
+ "timetable",
43528
+ "week-board"
42743
43529
  ]
42744
43530
  }, { allOf: [{
42745
43531
  field: "variant",
@@ -42915,6 +43701,7 @@ const eventCalendarVariants = {
42915
43701
  combined: eventCalendarLayout[0],
42916
43702
  calendar: eventCalendarLayout[0],
42917
43703
  timetable: eventCalendarLayout[0],
43704
+ "week-board": eventCalendarLayout[0],
42918
43705
  list: eventCalendarLayout[0]
42919
43706
  };
42920
43707
  const eventCalendarContentShape = {
@@ -42928,6 +43715,7 @@ const eventCalendarContentShape = {
42928
43715
  { value: "combined" },
42929
43716
  { value: "calendar" },
42930
43717
  { value: "timetable" },
43718
+ { value: "week-board" },
42931
43719
  { value: "list" }
42932
43720
  ]
42933
43721
  },
@@ -42994,6 +43782,7 @@ const eventCalendarBlockDefinition = {
42994
43782
  combined: { label: "Combined" },
42995
43783
  calendar: { label: "Calendar" },
42996
43784
  timetable: { label: "Timetable" },
43785
+ "week-board": { label: "Week board" },
42997
43786
  list: { label: "List" }
42998
43787
  }
42999
43788
  }, eventCalendarContentShape),
@@ -48515,6 +49304,12 @@ const systemBlockDefinitions = [
48515
49304
  family: "centered",
48516
49305
  weight: "featured",
48517
49306
  bias: "soft"
49307
+ }),
49308
+ layoutProfile({
49309
+ variantKey: "featured-editorial",
49310
+ family: "split",
49311
+ weight: "featured",
49312
+ bias: "soft"
48518
49313
  })
48519
49314
  ]
48520
49315
  })),
@@ -48647,6 +49442,11 @@ const systemBlockDefinitions = [
48647
49442
  variantKey: "timetable",
48648
49443
  family: "stack",
48649
49444
  weight: "standard"
49445
+ }),
49446
+ layoutProfile({
49447
+ variantKey: "week-board",
49448
+ family: "stack",
49449
+ weight: "standard"
48650
49450
  })
48651
49451
  ]
48652
49452
  })),
@@ -49004,7 +49804,15 @@ function gradientToCss(config, tokens) {
49004
49804
  const stops = config.stops.map((stop) => {
49005
49805
  return `${tokens[stop.color] ?? "#000000"} ${stop.position}%`;
49006
49806
  }).join(", ");
49007
- if (config.type === "radial") return `radial-gradient(circle at ${config.position ?? "center"}, ${stops})`;
49807
+ const type = config.type ?? "linear";
49808
+ if (type === "radial") {
49809
+ const position = config.position ?? "center";
49810
+ return `radial-gradient(${config.shape ?? "circle"} at ${position}, ${stops})`;
49811
+ }
49812
+ if (type === "conic") {
49813
+ const position = config.position ?? "center";
49814
+ return `conic-gradient(from ${config.angle ?? 0}deg at ${position}, ${stops})`;
49815
+ }
49008
49816
  return `linear-gradient(${config.angle ?? 180}deg, ${stops})`;
49009
49817
  }
49010
49818
  //#endregion
@@ -49082,8 +49890,8 @@ const backgroundTransforms = [
49082
49890
  {
49083
49891
  id: "background.resolveColorsStyle",
49084
49892
  kind: "formatter",
49085
- summary: "Resolve section foreground colors from a background object into scoped CSS vars",
49086
- run: (value, _options, context) => resolveSectionForegroundStyles(value, context)
49893
+ summary: "Resolve surface foreground colors from a background object into scoped CSS vars",
49894
+ run: (value, _options, context) => resolveSurfaceForegroundStyles(value, context)
49087
49895
  },
49088
49896
  {
49089
49897
  id: "background.resolveAllowOverflow",
@@ -49700,21 +50508,53 @@ function Inline({ as: Comp = "div", gap = "md", align = "center", justify = "sta
49700
50508
  }
49701
50509
  //#endregion
49702
50510
  //#region ../blocks/src/primitives/layout/Grid.tsx
50511
+ /** Utility class for a numeric column COUNT at a given breakpoint. */
50512
+ const COL_COUNT_CLASS = {
50513
+ base: (n) => `rb-grid-cols-${n}`,
50514
+ sm: (n) => `rb-sm-grid-cols-${n}`,
50515
+ md: (n) => `rb-md-grid-cols-${n}`,
50516
+ lg: (n) => `rb-lg-grid-cols-${n}`,
50517
+ xl: (n) => `rb-xl-grid-cols-${n}`
50518
+ };
50519
+ /** Inline custom property read by the `.rb-grid-tracks` utility per breakpoint. */
50520
+ const TRACK_VAR = {
50521
+ base: "--rb-gtc-base",
50522
+ sm: "--rb-gtc-sm",
50523
+ md: "--rb-gtc-md",
50524
+ lg: "--rb-gtc-lg",
50525
+ xl: "--rb-gtc-xl"
50526
+ };
50527
+ const GRID_BREAKPOINTS = [
50528
+ "base",
50529
+ "sm",
50530
+ "md",
50531
+ "lg",
50532
+ "xl"
50533
+ ];
49703
50534
  function Grid({ as: Comp = "div", cols = {
49704
50535
  base: 1,
49705
50536
  md: 2
49706
- }, gap = "md", className, children, "data-slot": dataSlot, ...rest }) {
49707
- const colsObj = typeof cols === "number" ? { base: cols } : cols;
49708
- const colClasses = [
49709
- colsObj.base && `rb-grid-cols-${colsObj.base}`,
49710
- colsObj.sm && `rb-sm-grid-cols-${colsObj.sm}`,
49711
- colsObj.md && `rb-md-grid-cols-${colsObj.md}`,
49712
- colsObj.lg && `rb-lg-grid-cols-${colsObj.lg}`,
49713
- colsObj.xl && `rb-xl-grid-cols-${colsObj.xl}`
49714
- ];
50537
+ }, gap = "md", className, style, children, "data-slot": dataSlot, ...rest }) {
50538
+ const colsObj = typeof cols === "number" || typeof cols === "string" ? { base: cols } : cols;
50539
+ const colClasses = [];
50540
+ const trackVars = {};
50541
+ let hasTracks = false;
50542
+ for (const bp of GRID_BREAKPOINTS) {
50543
+ const value = colsObj[bp];
50544
+ if (value == null) continue;
50545
+ if (typeof value === "string") {
50546
+ trackVars[TRACK_VAR[bp]] = value;
50547
+ hasTracks = true;
50548
+ } else colClasses.push(COL_COUNT_CLASS[bp](value));
50549
+ }
50550
+ const mergedStyle = hasTracks ? {
50551
+ ...trackVars,
50552
+ ...style
50553
+ } : style;
49715
50554
  return /* @__PURE__ */ jsx(Comp, {
49716
50555
  ...rest,
49717
- className: clsx("rb-grid", colClasses, {
50556
+ style: mergedStyle,
50557
+ className: clsx("rb-grid", hasTracks && "rb-grid-tracks", colClasses, {
49718
50558
  "rb-gap-0": gap === "none",
49719
50559
  "rb-gap-1": gap === "xs",
49720
50560
  "rb-gap-2": gap === "sm",
@@ -49907,119 +50747,6 @@ function Text$1({ as: Comp = "span", children, className = "", path, maxLength:
49907
50747
  });
49908
50748
  }
49909
50749
  //#endregion
49910
- //#region ../blocks/src/system/runtime/nodes/richText-coerce.ts
49911
- function coerceRichTextDoc(value) {
49912
- const raw = unwrapRichTextRoot(value);
49913
- if (typeof raw === "string") return createRichTextDocFromMarkdown(raw);
49914
- if (isRecord$26(raw) && typeof raw.type === "string") return raw;
49915
- return null;
49916
- }
49917
- //#endregion
49918
- //#region ../blocks/src/system/runtime/nodes/renderInlineText.tsx
49919
- function renderInlineText(value) {
49920
- return value.map((run, index) => renderRun(run, index));
49921
- }
49922
- function renderRun(run, key) {
49923
- return (run.marks ?? []).reduceRight((child, mark, index) => renderMark(mark, `${String(key)}-${index}`, child), run.text);
49924
- }
49925
- function renderMark(mark, key, child) {
49926
- switch (mark.kind) {
49927
- case "strong": return /* @__PURE__ */ jsx("strong", { children: child }, key);
49928
- case "emphasis": return /* @__PURE__ */ jsx("em", { children: child }, key);
49929
- case "accent": return /* @__PURE__ */ jsx("span", {
49930
- className: `rb-inline-text-accent rb-inline-text-accent-${mark.tone}`,
49931
- children: child
49932
- }, key);
49933
- case "highlight": return /* @__PURE__ */ jsx("span", {
49934
- className: `rb-inline-text-highlight rb-inline-text-highlight-${mark.tone}`,
49935
- children: child
49936
- }, key);
49937
- }
49938
- }
49939
- //#endregion
49940
- //#region ../blocks/src/system/runtime/nodes/basic.tsx
49941
- const TextNode = ({ value, children, blockId: _blockId, blockKind: _blockKind, blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, inlineTextFallbackPath, ...rest }) => {
49942
- const inlineText = parseInlineTextFromUnknown(value);
49943
- if (inlineText.ok && !isInlineTextEmpty(inlineText.value)) return /* @__PURE__ */ jsx(Text$1, {
49944
- ...rest,
49945
- children: renderInlineText(inlineText.value)
49946
- });
49947
- const fallbackValue = inlineTextFallbackPath ? readStringPath(blockContent, inlineTextFallbackPath) : null;
49948
- return /* @__PURE__ */ jsx(Text$1, {
49949
- ...rest,
49950
- children: fallbackValue ?? textNodeContent(value) ?? children
49951
- });
49952
- };
49953
- function textNodeContent(value) {
49954
- if (value === null || value === void 0) return null;
49955
- if (typeof value === "string" || typeof value === "number") return value;
49956
- return null;
49957
- }
49958
- function readStringPath(source, path) {
49959
- const value = getAtPath(source, path);
49960
- return typeof value === "string" ? value : null;
49961
- }
49962
- const RichTextNode = ({ value, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
49963
- return /* @__PURE__ */ jsx(RichText$1, {
49964
- doc: coerceRichTextDoc(value) ?? void 0,
49965
- ...rest
49966
- });
49967
- };
49968
- const ButtonNode = ({ value, children, href, variantId = "primary", size = "md", disabled, type = "button", target, rel, linkRequired = false, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, className, ...rest }) => {
49969
- const content = value ?? children;
49970
- if (!content) {
49971
- console.warn("[ButtonNode] No content to render - value and children are both empty");
49972
- return null;
49973
- }
49974
- const resolvedHref = typeof href === "string" ? href.trim() : "";
49975
- if (linkRequired && resolvedHref.length === 0) {
49976
- if (process.env.NODE_ENV === "development") console.warn("[ButtonNode] Missing href for link-required button");
49977
- return null;
49978
- }
49979
- const resolvedVariantId = parseThemeButtonVariantId(variantId);
49980
- const combinedClassName = resolvedVariantId ? themeButtonClassName({
49981
- variant: resolvedVariantId,
49982
- size,
49983
- extraClassName: className
49984
- }) : className;
49985
- if (resolvedHref) return /* @__PURE__ */ jsx("a", {
49986
- href: resolvedHref,
49987
- target,
49988
- rel,
49989
- className: combinedClassName,
49990
- ...rest,
49991
- children: content
49992
- });
49993
- return /* @__PURE__ */ jsx("button", {
49994
- type,
49995
- disabled,
49996
- className: combinedClassName,
49997
- ...rest,
49998
- children: content
49999
- });
50000
- };
50001
- const LinkNode = ({ href, children, className, target, rel, linkRequired = false, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
50002
- if (!children) return null;
50003
- const resolvedHref = typeof href === "string" ? href.trim() : "";
50004
- if (resolvedHref.length === 0) {
50005
- if (linkRequired && process.env.NODE_ENV === "development") console.warn("[LinkNode] Missing href for link-required link");
50006
- if (linkRequired) return null;
50007
- return /* @__PURE__ */ jsx("span", {
50008
- className,
50009
- ...rest,
50010
- children
50011
- });
50012
- }
50013
- return /* @__PURE__ */ jsx("a", {
50014
- href: resolvedHref,
50015
- className,
50016
- target,
50017
- rel,
50018
- ...rest,
50019
- children
50020
- });
50021
- };
50022
- //#endregion
50023
50750
  //#region ../blocks/src/system/icons/cta.ts
50024
50751
  /**
50025
50752
  * Curated icon set for CTA/button icons.
@@ -50118,6 +50845,171 @@ function getIconComponent(name) {
50118
50845
  return getCtaIconComponent(name) ?? getSemanticIconComponent(name);
50119
50846
  }
50120
50847
  //#endregion
50848
+ //#region ../blocks/src/system/runtime/nodes/buttonAffordance.ts
50849
+ function normalize(value) {
50850
+ const trimmed = typeof value === "string" ? value.trim() : "";
50851
+ return trimmed.length > 0 ? trimmed : null;
50852
+ }
50853
+ /**
50854
+ * Resolve the trailing affordance a button should render. Node overrides
50855
+ * personality; returns `null` when nothing is configured.
50856
+ */
50857
+ function resolveButtonAffordance(input) {
50858
+ const nodeIcon = normalize(input.trailingIcon);
50859
+ if (nodeIcon) {
50860
+ const colorToken = normalize(input.trailingIconColorToken);
50861
+ return {
50862
+ iconId: nodeIcon,
50863
+ source: "node",
50864
+ ...colorToken ? { colorToken } : {}
50865
+ };
50866
+ }
50867
+ const personality = input.personalityAffordance;
50868
+ const personalityIcon = normalize(personality?.iconId);
50869
+ if (personality && personalityIcon) {
50870
+ const colorToken = normalize(personality.colorToken);
50871
+ return {
50872
+ iconId: personalityIcon,
50873
+ source: "personality",
50874
+ ...colorToken ? { colorToken } : {}
50875
+ };
50876
+ }
50877
+ return null;
50878
+ }
50879
+ //#endregion
50880
+ //#region ../blocks/src/system/runtime/nodes/richText-coerce.ts
50881
+ function coerceRichTextDoc(value) {
50882
+ const raw = unwrapRichTextRoot(value);
50883
+ if (typeof raw === "string") return createRichTextDocFromMarkdown(raw);
50884
+ if (isRecord$26(raw) && typeof raw.type === "string") return raw;
50885
+ return null;
50886
+ }
50887
+ //#endregion
50888
+ //#region ../blocks/src/system/runtime/nodes/renderInlineText.tsx
50889
+ function renderInlineText(value) {
50890
+ return value.map((run, index) => renderRun(run, index));
50891
+ }
50892
+ function renderRun(run, key) {
50893
+ return (run.marks ?? []).reduceRight((child, mark, index) => renderMark(mark, `${String(key)}-${index}`, child), run.text);
50894
+ }
50895
+ function renderMark(mark, key, child) {
50896
+ switch (mark.kind) {
50897
+ case "strong": return /* @__PURE__ */ jsx("strong", { children: child }, key);
50898
+ case "emphasis": return /* @__PURE__ */ jsx("em", { children: child }, key);
50899
+ case "accent": return /* @__PURE__ */ jsx("span", {
50900
+ className: `rb-inline-text-accent rb-inline-text-accent-${mark.tone}`,
50901
+ children: child
50902
+ }, key);
50903
+ case "highlight": return /* @__PURE__ */ jsx("span", {
50904
+ className: `rb-inline-text-highlight rb-inline-text-highlight-${mark.tone}`,
50905
+ children: child
50906
+ }, key);
50907
+ }
50908
+ }
50909
+ //#endregion
50910
+ //#region ../blocks/src/system/runtime/nodes/basic.tsx
50911
+ const TextNode = ({ value, children, blockId: _blockId, blockKind: _blockKind, blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, inlineTextFallbackPath, ...rest }) => {
50912
+ const inlineText = parseInlineTextFromUnknown(value);
50913
+ if (inlineText.ok && !isInlineTextEmpty(inlineText.value)) return /* @__PURE__ */ jsx(Text$1, {
50914
+ ...rest,
50915
+ children: renderInlineText(inlineText.value)
50916
+ });
50917
+ const fallbackValue = inlineTextFallbackPath ? readStringPath(blockContent, inlineTextFallbackPath) : null;
50918
+ return /* @__PURE__ */ jsx(Text$1, {
50919
+ ...rest,
50920
+ children: fallbackValue ?? textNodeContent(value) ?? children
50921
+ });
50922
+ };
50923
+ function textNodeContent(value) {
50924
+ if (value === null || value === void 0) return null;
50925
+ if (typeof value === "string" || typeof value === "number") return value;
50926
+ return null;
50927
+ }
50928
+ function readStringPath(source, path) {
50929
+ const value = getAtPath(source, path);
50930
+ return typeof value === "string" ? value : null;
50931
+ }
50932
+ const RichTextNode = ({ value, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
50933
+ return /* @__PURE__ */ jsx(RichText$1, {
50934
+ doc: coerceRichTextDoc(value) ?? void 0,
50935
+ ...rest
50936
+ });
50937
+ };
50938
+ /**
50939
+ * Render the resolved trailing affordance icon. Colour is applied via a theme
50940
+ * CSS var so it recolours with the palette; the icon stroke uses `currentColor`.
50941
+ */
50942
+ function renderTrailingAffordanceIcon(affordance) {
50943
+ if (!affordance) return null;
50944
+ const Icon = getIconComponent(affordance.iconId);
50945
+ if (!Icon) return null;
50946
+ return /* @__PURE__ */ jsx(Icon, {
50947
+ "aria-hidden": true,
50948
+ className: "rb-ml-2 rb-h-4 rb-w-4 rb-inline-block",
50949
+ style: affordance.colorToken ? { color: `rgb(var(--tb-${affordance.colorToken}))` } : void 0
50950
+ });
50951
+ }
50952
+ const ButtonNode = ({ value, children, href, variantId = "primary", size = "md", disabled, type = "button", target, rel, linkRequired = false, trailingIcon, trailingIconColorToken, personalityAffordance, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, className, ...rest }) => {
50953
+ const content = value ?? children;
50954
+ if (!content) {
50955
+ console.warn("[ButtonNode] No content to render - value and children are both empty");
50956
+ return null;
50957
+ }
50958
+ const affordanceIcon = renderTrailingAffordanceIcon(resolveButtonAffordance({
50959
+ trailingIcon,
50960
+ trailingIconColorToken,
50961
+ personalityAffordance
50962
+ }));
50963
+ const resolvedHref = typeof href === "string" ? href.trim() : "";
50964
+ if (linkRequired && resolvedHref.length === 0) {
50965
+ if (process.env.NODE_ENV === "development") console.warn("[ButtonNode] Missing href for link-required button");
50966
+ return null;
50967
+ }
50968
+ const extraClassName = affordanceIcon ? [className, AFFORDANCE_SUPPRESS_CLASS].filter(Boolean).join(" ") : className;
50969
+ const resolvedVariantId = parseThemeButtonVariantId(variantId);
50970
+ const combinedClassName = resolvedVariantId ? themeButtonClassName({
50971
+ variant: resolvedVariantId,
50972
+ size,
50973
+ extraClassName
50974
+ }) : extraClassName;
50975
+ if (resolvedHref) return /* @__PURE__ */ jsxs("a", {
50976
+ href: resolvedHref,
50977
+ target,
50978
+ rel,
50979
+ className: combinedClassName,
50980
+ ...rest,
50981
+ children: [content, affordanceIcon]
50982
+ });
50983
+ return /* @__PURE__ */ jsxs("button", {
50984
+ type,
50985
+ disabled,
50986
+ className: combinedClassName,
50987
+ ...rest,
50988
+ children: [content, affordanceIcon]
50989
+ });
50990
+ };
50991
+ const LinkNode = ({ href, children, className, target, rel, linkRequired = false, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
50992
+ if (!children) return null;
50993
+ const resolvedHref = typeof href === "string" ? href.trim() : "";
50994
+ if (resolvedHref.length === 0) {
50995
+ if (linkRequired && process.env.NODE_ENV === "development") console.warn("[LinkNode] Missing href for link-required link");
50996
+ if (linkRequired) return null;
50997
+ return /* @__PURE__ */ jsx("span", {
50998
+ className,
50999
+ ...rest,
51000
+ children
51001
+ });
51002
+ }
51003
+ return /* @__PURE__ */ jsx("a", {
51004
+ href: resolvedHref,
51005
+ className,
51006
+ target,
51007
+ rel,
51008
+ ...rest,
51009
+ children
51010
+ });
51011
+ };
51012
+ //#endregion
50121
51013
  //#region ../blocks/src/system/runtime/nodes/icon.tsx
50122
51014
  const IconNode = ({ value, title, className, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
50123
51015
  const parsedValue = parseBlockIconValue(value);
@@ -50815,6 +51707,77 @@ function SpinnerNode({ className, size = "md", color, strokeWidth = 3, srLabel,
50815
51707
  }) : null]
50816
51708
  });
50817
51709
  }
51710
+ //#endregion
51711
+ //#region ../blocks/src/system/runtime/nodes/svg.tsx
51712
+ /**
51713
+ * Turn paint props into a CSS style object. Colour values pass through as-is so
51714
+ * they may reference theme tokens via `var()`, which only resolves in a CSS
51715
+ * property context (not in a bare presentation attribute).
51716
+ */
51717
+ function paintStyle(shape) {
51718
+ const style = {};
51719
+ if (shape.fill !== void 0) style.fill = shape.fill;
51720
+ if (shape.stroke !== void 0) style.stroke = shape.stroke;
51721
+ if (shape.strokeWidth !== void 0) style.strokeWidth = shape.strokeWidth;
51722
+ if (shape.fillOpacity !== void 0) style.fillOpacity = shape.fillOpacity;
51723
+ if (shape.strokeOpacity !== void 0) style.strokeOpacity = shape.strokeOpacity;
51724
+ if (shape.strokeLinecap !== void 0) style.strokeLinecap = shape.strokeLinecap;
51725
+ if (shape.strokeLinejoin !== void 0) style.strokeLinejoin = shape.strokeLinejoin;
51726
+ if (shape.strokeDasharray !== void 0) style.strokeDasharray = shape.strokeDasharray;
51727
+ return style;
51728
+ }
51729
+ function renderShape(shape, key) {
51730
+ const style = paintStyle(shape);
51731
+ switch (shape.kind) {
51732
+ case "path": return /* @__PURE__ */ jsx("path", {
51733
+ d: shape.d,
51734
+ style
51735
+ }, key);
51736
+ case "ellipse": return /* @__PURE__ */ jsx("ellipse", {
51737
+ cx: shape.cx,
51738
+ cy: shape.cy,
51739
+ rx: shape.rx,
51740
+ ry: shape.ry,
51741
+ style
51742
+ }, key);
51743
+ case "circle": return /* @__PURE__ */ jsx("circle", {
51744
+ cx: shape.cx,
51745
+ cy: shape.cy,
51746
+ r: shape.r,
51747
+ style
51748
+ }, key);
51749
+ case "rect": return /* @__PURE__ */ jsx("rect", {
51750
+ x: shape.x,
51751
+ y: shape.y,
51752
+ width: shape.width,
51753
+ height: shape.height,
51754
+ rx: shape.rx,
51755
+ ry: shape.ry,
51756
+ style
51757
+ }, key);
51758
+ case "line": return /* @__PURE__ */ jsx("line", {
51759
+ x1: shape.x1,
51760
+ y1: shape.y1,
51761
+ x2: shape.x2,
51762
+ y2: shape.y2,
51763
+ style
51764
+ }, key);
51765
+ default: return null;
51766
+ }
51767
+ }
51768
+ const SvgNode = ({ blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, viewBox, width, height, className, style, ariaHidden = true, shapes }) => {
51769
+ return /* @__PURE__ */ jsx("svg", {
51770
+ viewBox,
51771
+ width,
51772
+ height,
51773
+ className,
51774
+ style,
51775
+ "aria-hidden": ariaHidden,
51776
+ xmlns: "http://www.w3.org/2000/svg",
51777
+ fill: "none",
51778
+ children: (Array.isArray(shapes) ? shapes : []).map((shape, index) => renderShape(shape, index))
51779
+ });
51780
+ };
50818
51781
  function isRecord$14(value) {
50819
51782
  return typeof value === "object" && value !== null && !Array.isArray(value);
50820
51783
  }
@@ -56049,6 +57012,154 @@ const WeekAgendaView = ({ days, events, showCapacity, emptyMessage = "No events
56049
57012
  });
56050
57013
  };
56051
57014
  //#endregion
57015
+ //#region ../blocks/src/system/runtime/nodes/events/shared/WeekBoardView.tsx
57016
+ const SHORT_DAY_NAMES = [
57017
+ "Sun",
57018
+ "Mon",
57019
+ "Tue",
57020
+ "Wed",
57021
+ "Thu",
57022
+ "Fri",
57023
+ "Sat"
57024
+ ];
57025
+ const LEGEND_ITEMS = [
57026
+ {
57027
+ status: "available",
57028
+ label: "Places available"
57029
+ },
57030
+ {
57031
+ status: "filling",
57032
+ label: "Filling up"
57033
+ },
57034
+ {
57035
+ status: "waitlist",
57036
+ label: "Waitlist"
57037
+ }
57038
+ ];
57039
+ /**
57040
+ * Format an event's start–end as a compact range, merging the shared meridiem.
57041
+ * "6:00 PM" + "7:00 PM" -> "6:00 – 7:00 PM"; "9:30 AM" + "10:15 AM" -> "9:30 – 10:15 AM".
57042
+ */
57043
+ function formatCompactTimeRange(event) {
57044
+ const timeZone = event.timeZone ?? null;
57045
+ const start = formatTime(event.startsAt, { timeZone });
57046
+ const end = formatTime(event.endsAt, { timeZone });
57047
+ const startMeridiem = start.slice(-2);
57048
+ const endMeridiem = end.slice(-2);
57049
+ if ((startMeridiem === "AM" || startMeridiem === "PM") && startMeridiem === endMeridiem) return `${start.slice(0, -3).trim()} – ${end}`;
57050
+ return `${start} – ${end}`;
57051
+ }
57052
+ const WeekBoardCard = ({ event, showCapacity }) => {
57053
+ const linkProps = resolveEventLinkProps(event);
57054
+ const { status, label } = getEventAvailabilityStatus(event);
57055
+ const pill = showCapacity ? label : null;
57056
+ return /* @__PURE__ */ jsxs("a", {
57057
+ ...linkProps,
57058
+ className: "event-week-board-card",
57059
+ "data-status": status,
57060
+ children: [
57061
+ /* @__PURE__ */ jsx("span", {
57062
+ className: "event-week-board-card-title",
57063
+ children: event.title
57064
+ }),
57065
+ /* @__PURE__ */ jsx("span", {
57066
+ className: "event-week-board-card-time",
57067
+ children: formatCompactTimeRange(event)
57068
+ }),
57069
+ pill ? /* @__PURE__ */ jsx("span", {
57070
+ className: "event-week-board-card-pill",
57071
+ "data-status": status,
57072
+ children: pill
57073
+ }) : null
57074
+ ]
57075
+ });
57076
+ };
57077
+ /**
57078
+ * WeekBoardView
57079
+ *
57080
+ * Compact "class day-board": day columns side by side (one card per class per
57081
+ * day), a legend of the three availability states, and a footer CTA. Collapses
57082
+ * to a stacked list on mobile. Repurposes the week-grouped agenda data as a
57083
+ * board rather than an hour-positioned timetable grid.
57084
+ */
57085
+ const WeekBoardView = ({ days, events, showCapacity, emptyMessage = "No classes scheduled this week", title = "The week ahead", footerNote = "New to a class? We'll check it's right for you first.", ctaLabel = "Reserve a place", ctaHref = null, buttonVariant = "outline" }) => {
57086
+ const eventsByDate = groupEventsByDate(events);
57087
+ const hasAnyEvents = days.some((date) => (eventsByDate.get(formatDateKey(date)) ?? []).length > 0);
57088
+ const ctaClassName = themeButtonClassName({
57089
+ variant: buttonVariant,
57090
+ size: "sm"
57091
+ });
57092
+ return /* @__PURE__ */ jsx("div", {
57093
+ className: "event-calendar event-calendar--week-board",
57094
+ "data-block": "event-calendar",
57095
+ children: /* @__PURE__ */ jsxs("div", {
57096
+ className: "event-week-board",
57097
+ style: { "--event-week-board-cols": String(days.length) },
57098
+ children: [
57099
+ /* @__PURE__ */ jsxs("div", {
57100
+ className: "event-week-board-top",
57101
+ children: [/* @__PURE__ */ jsx("span", {
57102
+ className: "event-week-board-title",
57103
+ children: title
57104
+ }), /* @__PURE__ */ jsx("div", {
57105
+ className: "event-week-board-legend",
57106
+ "aria-hidden": "true",
57107
+ children: LEGEND_ITEMS.map((item) => /* @__PURE__ */ jsxs("span", {
57108
+ className: "event-week-board-legend-item",
57109
+ children: [/* @__PURE__ */ jsx("i", {
57110
+ className: "event-week-board-legend-dot",
57111
+ "data-status": item.status
57112
+ }), item.label]
57113
+ }, item.status))
57114
+ })]
57115
+ }),
57116
+ /* @__PURE__ */ jsx("div", {
57117
+ className: "event-week-board-grid",
57118
+ children: days.map((date) => {
57119
+ const dateKey = formatDateKey(date);
57120
+ const dayEvents = eventsByDate.get(dateKey) ?? [];
57121
+ const today = isToday(date);
57122
+ return /* @__PURE__ */ jsxs("div", {
57123
+ className: clsx("event-week-board-day", dayEvents.length === 0 && "event-week-board-day--empty", today && "event-week-board-day--today"),
57124
+ children: [/* @__PURE__ */ jsxs("div", {
57125
+ className: "event-week-board-day-head",
57126
+ children: [/* @__PURE__ */ jsx("span", {
57127
+ className: "event-week-board-day-abbr",
57128
+ children: SHORT_DAY_NAMES[date.getDay()]
57129
+ }), /* @__PURE__ */ jsx("span", {
57130
+ className: "event-week-board-day-num",
57131
+ children: date.getDate()
57132
+ })]
57133
+ }), /* @__PURE__ */ jsx("div", {
57134
+ className: "event-week-board-day-body",
57135
+ children: dayEvents.map((event) => /* @__PURE__ */ jsx(WeekBoardCard, {
57136
+ event,
57137
+ showCapacity
57138
+ }, event.occurrenceId || event.id))
57139
+ })]
57140
+ }, dateKey);
57141
+ })
57142
+ }),
57143
+ /* @__PURE__ */ jsxs("div", {
57144
+ className: "event-week-board-foot",
57145
+ children: [/* @__PURE__ */ jsx("p", {
57146
+ className: "event-week-board-foot-note",
57147
+ children: hasAnyEvents ? footerNote : emptyMessage
57148
+ }), ctaHref ? /* @__PURE__ */ jsx("a", {
57149
+ className: ctaClassName,
57150
+ href: ctaHref,
57151
+ children: ctaLabel
57152
+ }) : /* @__PURE__ */ jsx("button", {
57153
+ type: "button",
57154
+ className: ctaClassName,
57155
+ children: ctaLabel
57156
+ })]
57157
+ })
57158
+ ]
57159
+ })
57160
+ });
57161
+ };
57162
+ //#endregion
56052
57163
  //#region ../blocks/src/system/runtime/nodes/events/shared/timetableModel.ts
56053
57164
  /**
56054
57165
  * Event-domain adapter over @riverbankcms/calendar.
@@ -56483,6 +57594,16 @@ function renderTimetableSsr(display) {
56483
57594
  })]
56484
57595
  });
56485
57596
  }
57597
+ function renderWeekBoardSsr(display) {
57598
+ return /* @__PURE__ */ jsx(WeekBoardView, {
57599
+ days: getWeekCalendarDayTuple(/* @__PURE__ */ new Date(), display.startOfWeek),
57600
+ events: [...display.events],
57601
+ showCapacity: display.showCapacity,
57602
+ emptyMessage: display.emptyMessage,
57603
+ ctaLabel: display.buttonText,
57604
+ buttonVariant: display.buttonVariant
57605
+ });
57606
+ }
56486
57607
  function formatHourLabel(hour) {
56487
57608
  if (hour === 0) return "12 AM";
56488
57609
  if (hour === 12) return "12 PM";
@@ -56509,6 +57630,9 @@ const EventCalendarSSR = (props) => {
56509
57630
  case "timetable":
56510
57631
  content = renderTimetableSsr(display);
56511
57632
  break;
57633
+ case "week-board":
57634
+ content = renderWeekBoardSsr(display);
57635
+ break;
56512
57636
  case "calendar":
56513
57637
  content = renderCalendarSsr(display);
56514
57638
  break;
@@ -57080,6 +58204,7 @@ typedObjectKeys(runtimeBackedBlockServerEntries);
57080
58204
  const serverComponentRegistry = {
57081
58205
  div: DivNode,
57082
58206
  figure: FigureNode,
58207
+ svg: SvgNode,
57083
58208
  section: SectionNode,
57084
58209
  headerSection: header_section_server_default,
57085
58210
  stack: StackNode,
@@ -58546,6 +59671,8 @@ function formatFontFamily(family, fallback = "sans-serif") {
58546
59671
  function generateShadowCssVars(theme) {
58547
59672
  const level = theme.shadow.elevation;
58548
59673
  const soft = theme.shadow.softness !== "crisp";
59674
+ const tintVar = theme.shadow.colorToken ? cssVarValue(theme.shadow.colorToken) : null;
59675
+ const shadowColor = (a) => tintVar ? `rgb(${tintVar} / ${a})` : `rgba(0, 0, 0, ${a})`;
58549
59676
  const base = {
58550
59677
  sm: [{
58551
59678
  y: 1,
@@ -58605,7 +59732,7 @@ function generateShadowCssVars(theme) {
58605
59732
  const blurMul = soft ? 1.2 : 1;
58606
59733
  const alphaMul = soft ? .9 : 1.05;
58607
59734
  const fmt = (layers) => layers.map((l) => {
58608
- return `0 ${Math.round(l.y * scale)}px ${Math.round(l.blur * scale * blurMul)}px ${Math.round(l.spread * scale)}px rgba(0, 0, 0, ${Math.max(0, Math.min(1, +(l.a * alphaMul).toFixed(3)))})`;
59735
+ return `0 ${Math.round(l.y * scale)}px ${Math.round(l.blur * scale * blurMul)}px ${Math.round(l.spread * scale)}px ${shadowColor(Math.max(0, Math.min(1, +(l.a * alphaMul).toFixed(3))))}`;
58609
59736
  }).join(", ");
58610
59737
  return {
58611
59738
  "--shadow-sm": fmt(base.sm),
@@ -58671,7 +59798,13 @@ function generateAccordionCss({ themeId, theme, accordionSystem }) {
58671
59798
  const questionColumnsLastItemSelector = `${questionColumnsItemSelector}:last-child`;
58672
59799
  const questionColumnsTriggerSelector = `${questionColumnsItemSelector} .accordion-trigger`;
58673
59800
  const rootRules = [];
58674
- if (accordionSystem.background) rootRules.push(`background-color: ${rgbColorValue(accordionSystem.background.colorToken)}`);
59801
+ if (accordionSystem.background) {
59802
+ rootRules.push(`background-color: ${rgbColorValue(accordionSystem.background.colorToken)}`);
59803
+ rootRules.push(...surfaceForegroundDeclarations({
59804
+ text: rgbColorValue(accordionSystem.content.textColorToken),
59805
+ heading: rgbColorValue(accordionSystem.trigger.textColorToken)
59806
+ }));
59807
+ }
58675
59808
  const borderRadius = CORNER_MAP$2[accordionSystem.corners ?? theme.corners];
58676
59809
  rootRules.push(`border-radius: ${borderRadius}`);
58677
59810
  if (accordionSystem.border) {
@@ -60255,7 +61388,7 @@ ${root} .status-error { color: rgb(var(--tb-destructive)); }
60255
61388
  ${root} .status-success { color: rgb(var(--tb-success)); }
60256
61389
  ${root} .status-warning { color: rgb(var(--tb-warning)); }
60257
61390
  ${root} .status-info { color: rgb(var(--tb-info)); }
60258
- ${root} .status-muted { ${statusMutedColorDeclaration()} }`);
61391
+ ${root} .status-muted { color: ${surfaceForegroundValue("supporting")}; }`);
60259
61392
  cssBlocks.push(`/* Alert containers */
60260
61393
  ${root} .alert {
60261
61394
  padding: 1rem;
@@ -60393,6 +61526,9 @@ ${root} .step-connector {
60393
61526
  }
60394
61527
  //#endregion
60395
61528
  //#region ../theme-core/src/typography/generateTypographyCss.ts
61529
+ function globalHeadingColorFallback(level) {
61530
+ return `var(--hc-h${level}, var(--hc-heading, ${DEFAULT_SURFACE_TEXT_COLOR}))`;
61531
+ }
60396
61532
  /**
60397
61533
  * Generate typography CSS for heading elements.
60398
61534
  *
@@ -60421,7 +61557,7 @@ ${root} :where(h1) {
60421
61557
  font-style: var(--fi-h1, var(--fi-heading, normal));
60422
61558
  letter-spacing: var(--ls-h1, var(--ls-heading));
60423
61559
  line-height: var(--lh-h1, var(--lh-heading));
60424
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h1, var(--hc-heading, inherit))));
61560
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(1))};
60425
61561
  }
60426
61562
 
60427
61563
  ${root} :where(h2) {
@@ -60430,7 +61566,7 @@ ${root} :where(h2) {
60430
61566
  font-style: var(--fi-h2, var(--fi-heading, normal));
60431
61567
  letter-spacing: var(--ls-h2, var(--ls-heading));
60432
61568
  line-height: var(--lh-h2, var(--lh-heading));
60433
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h2, var(--hc-heading, inherit))));
61569
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(2))};
60434
61570
  }
60435
61571
 
60436
61572
  ${root} :where(h3) {
@@ -60439,7 +61575,7 @@ ${root} :where(h3) {
60439
61575
  font-style: var(--fi-h3, var(--fi-heading, normal));
60440
61576
  letter-spacing: var(--ls-h3, var(--ls-heading));
60441
61577
  line-height: var(--lh-h3, var(--lh-heading));
60442
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h3, var(--hc-heading, inherit))));
61578
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(3))};
60443
61579
  }
60444
61580
 
60445
61581
  ${root} :where(h4) {
@@ -60448,7 +61584,7 @@ ${root} :where(h4) {
60448
61584
  font-style: var(--fi-h4, var(--fi-heading, normal));
60449
61585
  letter-spacing: var(--ls-h4, var(--ls-heading));
60450
61586
  line-height: var(--lh-h4, var(--lh-heading));
60451
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h4, var(--hc-heading, inherit))));
61587
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(4))};
60452
61588
  }
60453
61589
 
60454
61590
  ${root} :where(h5) {
@@ -60457,7 +61593,7 @@ ${root} :where(h5) {
60457
61593
  font-style: var(--fi-h5, var(--fi-heading, normal));
60458
61594
  letter-spacing: var(--ls-h5, var(--ls-heading));
60459
61595
  line-height: var(--lh-h5, var(--lh-heading));
60460
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h5, var(--hc-heading, inherit))));
61596
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(5))};
60461
61597
  }
60462
61598
 
60463
61599
  ${root} :where(h6) {
@@ -60466,7 +61602,7 @@ ${root} :where(h6) {
60466
61602
  font-style: var(--fi-h6, var(--fi-heading, normal));
60467
61603
  letter-spacing: var(--ls-h6, var(--ls-heading));
60468
61604
  line-height: var(--lh-h6, var(--lh-heading));
60469
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h6, var(--hc-heading, inherit))));
61605
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(6))};
60470
61606
  }`);
60471
61607
  cssBlocks.push(`/* Display variant for hero/splash headings */
60472
61608
  ${root} :where(.heading-display) {
@@ -60865,6 +62001,7 @@ const THEME_RADIUS_CSS_VARS = {
60865
62001
  block: "--rb-block-radius",
60866
62002
  button: "--rb-button-radius",
60867
62003
  input: "--rb-input-radius",
62004
+ panel: "--rb-panel-radius",
60868
62005
  legacyCard: "--radius-card",
60869
62006
  legacyControl: "--radius-control"
60870
62007
  };
@@ -60946,6 +62083,10 @@ ${`:where([data-theme-scope="${themeId}"]) :where([data-block="customer-portal"]
60946
62083
  /* Modal (shared rb-modal classes) */
60947
62084
  --rb-modal-overlay-bg: rgb(var(--tb-text, 0 0 0) / 0.35);
60948
62085
  --rb-modal-bg: var(--cp-card-bg);
62086
+ --rb-modal-text: var(--cp-text);
62087
+ --rb-modal-heading: var(--cp-text);
62088
+ --rb-modal-link: var(--cp-primary);
62089
+ --rb-modal-supporting: var(--cp-muted);
60949
62090
  --rb-modal-border: var(--cp-card-border);
60950
62091
  --rb-modal-shadow: var(--shadow-lg, var(--shadow-elev));
60951
62092
  --rb-modal-radius: var(--cp-card-radius);
@@ -61660,6 +62801,7 @@ function buildThemeCssVars(theme) {
61660
62801
  const inputSystem = resolveThemeInputSystem(theme);
61661
62802
  const blockRadius = mapBlockRadius(theme.corners);
61662
62803
  const buttonRadius = mapButtonRadius(theme.corners);
62804
+ const panelRadius = mapPanelRadius(theme.corners);
61663
62805
  const inputRadius = mapInputRadius(inputSystem.base.corners, theme.corners);
61664
62806
  return {
61665
62807
  ...generateCssVars(theme.palette),
@@ -61668,6 +62810,7 @@ function buildThemeCssVars(theme) {
61668
62810
  ...generateShadowCssVars(theme),
61669
62811
  ["--font-heading"]: formatFontFamily(typography.headings.default.family),
61670
62812
  ["--font-body"]: formatFontFamily(typography.body.family),
62813
+ ["--font-eyebrow"]: typography.eyebrow?.family ? formatFontFamily(typography.eyebrow.family) : "var(--font-body)",
61671
62814
  ["--font-weight-heading"]: mapWeight(typography.headings.default.weight),
61672
62815
  ["--font-weight-body"]: mapWeight(typography.body.weight),
61673
62816
  ...typography.headings.h1.weight ? { ["--fw-h1"]: mapWeight(typography.headings.h1.weight) } : {},
@@ -61718,12 +62861,13 @@ function buildThemeCssVars(theme) {
61718
62861
  ["--fs-h3-fluid"]: sized.h3Fluid,
61719
62862
  ["--fs-h1-display"]: sized.h1Display,
61720
62863
  ["--fs-h1-display-fluid"]: sized.h1DisplayFluid,
61721
- ["--shadow-elev"]: mapShadow(shadow.elevation, shadow.softness, shadow.position || "bottom"),
62864
+ ["--shadow-elev"]: mapShadow(shadow.elevation, shadow.softness, shadow.position || "bottom", shadow.colorToken),
61722
62865
  ["--space-mult"]: mapSpaceMult(space),
61723
62866
  ["--motion-duration"]: mapMotionDuration(motion.level),
61724
62867
  ["--motion-ease"]: mapMotionEasing(motion.easing ?? "standard"),
61725
62868
  ["--rt-space-y"]: mapRhythm(space),
61726
62869
  [THEME_RADIUS_CSS_VARS.block]: blockRadius,
62870
+ [THEME_RADIUS_CSS_VARS.panel]: panelRadius,
61727
62871
  ["--rb-block-transition"]: mapBlockTransition(motion.level),
61728
62872
  [THEME_RADIUS_CSS_VARS.button]: buttonRadius,
61729
62873
  ["--rb-border-width"]: mapBorderWidth(border.width),
@@ -61777,20 +62921,22 @@ function mapWeight(w) {
61777
62921
  bold: 700
61778
62922
  }[w];
61779
62923
  }
61780
- function mapShadow(level, softness, position = "bottom") {
62924
+ function mapShadow(level, softness, position = "bottom", colorToken) {
61781
62925
  if (level === "none") return "none";
62926
+ const tintVar = colorToken ? cssVarValue(colorToken) : null;
62927
+ const shadowColor = (a) => tintVar ? `rgb(${tintVar} / ${a})` : `rgba(0,0,0,${a})`;
61782
62928
  const offset = {
61783
62929
  low: 2,
61784
62930
  medium: 4,
61785
62931
  high: 6
61786
62932
  }[level];
61787
62933
  const xOffset = position === "bottom-right" ? offset : 0;
61788
- if (softness === "hard") return `${xOffset}px ${offset}px 0 0 rgba(0,0,0,1)`;
62934
+ if (softness === "hard") return `${xOffset}px ${offset}px 0 0 ${shadowColor("1")}`;
61789
62935
  const soft = softness !== "crisp";
61790
62936
  switch (level) {
61791
- case "low": return soft ? `${xOffset}px 1px 2px rgba(0,0,0,.06)` : `${xOffset}px 1px 1px rgba(0,0,0,.12)`;
61792
- case "medium": return soft ? `${xOffset}px 10px 20px rgba(0,0,0,.08)` : `${xOffset}px 6px 12px rgba(0,0,0,.16)`;
61793
- case "high": return soft ? `${xOffset}px 25px 50px rgba(0,0,0,.18)` : `${xOffset}px 20px 30px rgba(0,0,0,.28)`;
62937
+ case "low": return soft ? `${xOffset}px 1px 2px ${shadowColor(".06")}` : `${xOffset}px 1px 1px ${shadowColor(".12")}`;
62938
+ case "medium": return soft ? `${xOffset}px 10px 20px ${shadowColor(".08")}` : `${xOffset}px 6px 12px ${shadowColor(".16")}`;
62939
+ case "high": return soft ? `${xOffset}px 25px 50px ${shadowColor(".18")}` : `${xOffset}px 20px 30px ${shadowColor(".28")}`;
61794
62940
  }
61795
62941
  }
61796
62942
  function mapSpaceMult(space) {
@@ -61837,6 +62983,21 @@ function mapBlockRadius(corners) {
61837
62983
  }[corners];
61838
62984
  }
61839
62985
  /**
62986
+ * Map corner style to large-panel radius CSS value (Tier 2 `--rb-panel-radius`).
62987
+ * Derived from the same `corners` enum, one step more generous than the
62988
+ * block/card radius so large surfaces can carry a softer corner without a new
62989
+ * schema field. `rounded` lands on 20px (1.25rem), matching the physio mockup's
62990
+ * large-panel radius.
62991
+ */
62992
+ function mapPanelRadius(corners) {
62993
+ return {
62994
+ square: "0",
62995
+ soft: "0.75rem",
62996
+ rounded: "1.25rem",
62997
+ pill: "2rem"
62998
+ }[corners];
62999
+ }
63000
+ /**
61840
63001
  * Map motion level to block transition duration.
61841
63002
  * These are Tier 2 vars used consistently across blocks.
61842
63003
  */
@@ -62838,8 +63999,12 @@ const containerResponsiveThemeCss = `/*
62838
63999
  text-transform: uppercase;
62839
64000
  }
62840
64001
 
62841
- /* Eyebrow text - small uppercase labels for section introductions */
64002
+ /* Eyebrow text - small uppercase labels for section introductions.
64003
+ font-family resolves to the optional eyebrow/mono font role, falling back
64004
+ to the body font when the theme sets none (so existing themes are
64005
+ unchanged). */
62842
64006
  .rb-eyebrow {
64007
+ font-family: var(--font-eyebrow, var(--font-body));
62843
64008
  font-size: 0.875rem;
62844
64009
  font-weight: 600;
62845
64010
  letter-spacing: 0.025em;
@@ -62854,11 +64019,11 @@ const containerResponsiveThemeCss = `/*
62854
64019
  width: fit-content;
62855
64020
  align-items: center;
62856
64021
  justify-content: center;
62857
- border: 1px solid color-mix(in srgb, var(--section-text-color, rgb(var(--tb-text))) 46%, transparent);
64022
+ border: 1px solid color-mix(in srgb, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 46%, transparent);
62858
64023
  border-radius: 9999px;
62859
- background: color-mix(in srgb, var(--section-text-color, rgb(var(--tb-text))) 14%, transparent);
64024
+ background: color-mix(in srgb, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 14%, transparent);
62860
64025
  padding: 0.5rem 1rem;
62861
- color: var(--section-text-color, rgb(var(--tb-text)));
64026
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
62862
64027
  line-height: 1;
62863
64028
  }
62864
64029
  .rb-eyebrow-treatment-pill.rb-eyebrow-treatment-accent {
@@ -63360,6 +64525,58 @@ const containerResponsiveThemeCss = `/*
63360
64525
  }
63361
64526
  }
63362
64527
 
64528
+ }
64529
+
64530
+ /*
64531
+ * Responsive fractional grid tracks (rb-grid-tracks).
64532
+ *
64533
+ * Asymmetric AND responsive grid-template-columns, driven by inline custom
64534
+ * properties the Grid primitive sets per breakpoint from string track
64535
+ * templates:
64536
+ * --rb-gtc-base / --rb-gtc-sm / --rb-gtc-md / --rb-gtc-lg / --rb-gtc-xl
64537
+ * Each breakpoint falls back to the next smaller one, so only the breakpoints
64538
+ * an author actually sets take effect (e.g. base '1fr' + lg '1fr 0.9fr'
64539
+ * collapses to a single column below lg). Authored here (not per node) so the
64540
+ * @media -> @container conversion (container responsive mode) applies too.
64541
+ */
64542
+ .rb-grid-tracks {
64543
+ grid-template-columns: var(--rb-gtc-base, 1fr);
64544
+ }
64545
+ @container rb-site (min-width: 640px) {
64546
+
64547
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
64548
+ .rb-grid-tracks {
64549
+ grid-template-columns: var(--rb-gtc-sm, var(--rb-gtc-base, 1fr));
64550
+ }
64551
+ }
64552
+
64553
+ }
64554
+ @container rb-site (min-width: 768px) {
64555
+
64556
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
64557
+ .rb-grid-tracks {
64558
+ grid-template-columns: var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr)));
64559
+ }
64560
+ }
64561
+
64562
+ }
64563
+ @container rb-site (min-width: 1024px) {
64564
+
64565
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
64566
+ .rb-grid-tracks {
64567
+ grid-template-columns: var(--rb-gtc-lg, var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr))));
64568
+ }
64569
+ }
64570
+
64571
+ }
64572
+ @container rb-site (min-width: 1280px) {
64573
+
64574
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
64575
+ .rb-grid-tracks {
64576
+ grid-template-columns: var(--rb-gtc-xl, var(--rb-gtc-lg, var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr)))));
64577
+ }
64578
+ }
64579
+
63363
64580
  }
63364
64581
 
63365
64582
  /* Alignment */
@@ -64564,6 +65781,11 @@ const containerResponsiveThemeCss = `/*
64564
65781
  :where(.theme-scope) .rb-modal {
64565
65782
  width: min(var(--rb-modal-max-width, 560px), 100%);
64566
65783
  background: var(--rb-modal-bg, rgb(var(--tb-surface)));
65784
+ color: var(--rb-modal-text, rgb(var(--tb-text, 17 24 39)));
65785
+ --rb-surface-text-color: var(--rb-modal-text, rgb(var(--tb-text, 17 24 39)));
65786
+ --rb-surface-heading-color: var(--rb-modal-heading, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
65787
+ --rb-surface-link-color: var(--rb-modal-link, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
65788
+ --rb-surface-supporting-color: var(--rb-modal-supporting, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
64567
65789
  border: 1px solid var(--rb-modal-border, rgb(var(--tb-border)));
64568
65790
  border-radius: var(--rb-modal-radius, var(--radius-card));
64569
65791
  box-shadow: var(--rb-modal-shadow, var(--shadow-lg, var(--shadow-elev)));
@@ -64583,7 +65805,7 @@ const containerResponsiveThemeCss = `/*
64583
65805
  font-family: var(--font-heading);
64584
65806
  font-size: 1.0625rem;
64585
65807
  font-weight: 600;
64586
- color: rgb(var(--tb-text));
65808
+ color: var(--rb-surface-heading-color, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))));
64587
65809
  }
64588
65810
 
64589
65811
  :where(.theme-scope) .rb-modal__close {
@@ -64593,7 +65815,7 @@ const containerResponsiveThemeCss = `/*
64593
65815
  height: 2.25rem;
64594
65816
  padding: 0;
64595
65817
  line-height: 1;
64596
- color: rgb(var(--tb-mutedText));
65818
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
64597
65819
  cursor: pointer;
64598
65820
  display: grid;
64599
65821
  place-items: center;
@@ -64601,7 +65823,7 @@ const containerResponsiveThemeCss = `/*
64601
65823
  }
64602
65824
 
64603
65825
  :where(.theme-scope) .rb-modal__close:hover {
64604
- color: rgb(var(--tb-text));
65826
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
64605
65827
  background: rgba(var(--tb-border), 0.2);
64606
65828
  }
64607
65829
 
@@ -64612,7 +65834,7 @@ const containerResponsiveThemeCss = `/*
64612
65834
 
64613
65835
  :where(.theme-scope) .rb-modal__body {
64614
65836
  padding: 0.875rem 1rem 1rem;
64615
- color: rgb(var(--tb-text));
65837
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
64616
65838
  font-size: 0.9375rem;
64617
65839
  line-height: 1.45;
64618
65840
  }
@@ -64772,7 +65994,7 @@ const containerResponsiveThemeCss = `/*
64772
65994
  & h6 {
64773
65995
  font-family: var(--font-heading);
64774
65996
  /* Color is set by generateTypographyCss.ts with full cascade:
64775
- --section-heading-color -> --section-text-color -> --hc-hN -> --hc-heading -> inherit */
65997
+ --rb-surface-heading-color -> --rb-surface-text-color -> --hc-hN -> --hc-heading -> global text */
64776
65998
  font-weight: var(--font-weight-heading);
64777
65999
  text-transform: var(--tt-heading);
64778
66000
  font-variant-caps: var(--fv-heading);
@@ -64920,7 +66142,7 @@ const containerResponsiveThemeCss = `/*
64920
66142
 
64921
66143
  /* Neutral prose uses theme text color by default */
64922
66144
  :where(.theme-scope) .rb-prose-neutral {
64923
- color: var(--section-text-color, rgb(var(--tb-text)));
66145
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
64924
66146
  }
64925
66147
 
64926
66148
  :where(.theme-scope) .rb-prose p,
@@ -64954,8 +66176,14 @@ const containerResponsiveThemeCss = `/*
64954
66176
 
64955
66177
  :where(.theme-scope) .rb-prose a {
64956
66178
  --rb-prose-effective-link-color: var(
64957
- --section-link-color,
64958
- var(--rb-prose-link-color, rgb(var(--tb-primary)))
66179
+ --rb-surface-link-color,
66180
+ var(
66181
+ --rb-surface-text-color,
66182
+ var(
66183
+ --rb-prose-link-color,
66184
+ rgb(var(--tb-primary, var(--tb-text, 17 24 39)))
66185
+ )
66186
+ )
64959
66187
  );
64960
66188
  color: var(--rb-prose-effective-link-color);
64961
66189
  text-decoration-line: var(--rb-prose-link-decoration-line, underline);
@@ -64966,14 +66194,11 @@ const containerResponsiveThemeCss = `/*
64966
66194
  );
64967
66195
  text-underline-offset: var(--rb-prose-link-underline-offset, 0.14em);
64968
66196
  text-decoration-color: var(
64969
- --section-link-decoration-color,
64970
- var(
64971
- --rb-prose-link-decoration-color,
64972
- color-mix(
64973
- in srgb,
64974
- var(--rb-prose-effective-link-color) 35%,
64975
- transparent
64976
- )
66197
+ --rb-prose-link-decoration-color,
66198
+ color-mix(
66199
+ in srgb,
66200
+ var(--rb-prose-effective-link-color) 35%,
66201
+ transparent
64977
66202
  )
64978
66203
  );
64979
66204
  transition:
@@ -64997,7 +66222,7 @@ const containerResponsiveThemeCss = `/*
64997
66222
 
64998
66223
  :where(.theme-scope) .rb-prose blockquote {
64999
66224
  /* Blockquotes use muted text color */
65000
- color: var(--section-supporting-color, rgb(var(--tb-mutedText)));
66225
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
65001
66226
  border-left-color: rgb(var(--tb-border));
65002
66227
  }
65003
66228
 
@@ -66031,6 +67256,10 @@ const containerResponsiveThemeCss = `/*
66031
67256
  :where(.theme-scope) .card-surface {
66032
67257
  background: rgb(var(--tb-surface));
66033
67258
  color: rgb(var(--tb-onSurface, var(--tb-text)));
67259
+ --rb-surface-text-color: rgb(var(--tb-onSurface, var(--tb-text)));
67260
+ --rb-surface-heading-color: rgb(var(--tb-onSurface, var(--tb-text)));
67261
+ --rb-surface-link-color: rgb(var(--tb-onSurface, var(--tb-text)));
67262
+ --rb-surface-supporting-color: rgb(var(--tb-onSurface, var(--tb-text)));
66034
67263
  border: 1px solid rgb(var(--tb-border));
66035
67264
  border-radius: var(--radius-card);
66036
67265
  box-shadow: var(--shadow-elev);
@@ -66174,6 +67403,10 @@ const containerResponsiveThemeCss = `/*
66174
67403
  padding: clamp(1rem, 2vw, 1.5rem);
66175
67404
  background: var(--rb-reg-surface-bg, rgb(var(--tb-surface, 248 250 252)));
66176
67405
  color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
67406
+ --rb-surface-text-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
67407
+ --rb-surface-heading-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
67408
+ --rb-surface-link-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
67409
+ --rb-surface-supporting-color: var(--rb-reg-on-surface-muted, var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42)))));
66177
67410
  transition: border-color var(--rb-block-transition, 150ms) ease;
66178
67411
  width: 100%;
66179
67412
  max-width: 800px;
@@ -69261,6 +70494,350 @@ const containerResponsiveThemeCss = `/*
69261
70494
  }
69262
70495
  }
69263
70496
 
70497
+ /* -----------------------------------------------------------------------------
70498
+ * Week Board View
70499
+ * Compact "class day-board": day columns side by side, one card per class,
70500
+ * three-state availability (available / filling / waitlist), and a footer CTA.
70501
+ * ----------------------------------------------------------------------------- */
70502
+
70503
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board {
70504
+ /* Status accent tokens — map the three availability states onto theme colours.
70505
+ * available -> secondary, filling -> accent, waitlist -> muted text. */
70506
+ --ec-status-available: rgb(var(--tb-secondary));
70507
+ --ec-status-filling: rgb(var(--tb-accent));
70508
+ --ec-status-waitlist: rgba(var(--tb-text), 0.4);
70509
+ --event-week-board-cols: 7;
70510
+
70511
+ border: 1px solid var(--ec-border);
70512
+ border-radius: var(--rb-block-radius);
70513
+ background-color: var(--ec-surface);
70514
+ color: var(--ec-text);
70515
+ overflow: hidden;
70516
+ box-shadow: 0 14px 34px -24px rgba(var(--tb-text), 0.35);
70517
+ }
70518
+
70519
+ /* Top bar: title + legend */
70520
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-top {
70521
+ display: flex;
70522
+ align-items: center;
70523
+ justify-content: space-between;
70524
+ gap: 1rem;
70525
+ flex-wrap: wrap;
70526
+ padding: 1.1rem 1.5rem;
70527
+ background-color: var(--ec-header-bg);
70528
+ border-bottom: 1px solid var(--ec-border-subtle);
70529
+ }
70530
+
70531
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-title {
70532
+ font-size: 0.8125rem;
70533
+ font-weight: 600;
70534
+ letter-spacing: 0.12em;
70535
+ text-transform: uppercase;
70536
+ color: rgb(var(--tb-primary));
70537
+ }
70538
+
70539
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend {
70540
+ display: flex;
70541
+ align-items: center;
70542
+ gap: 1.1rem;
70543
+ flex-wrap: wrap;
70544
+ font-size: 0.6875rem;
70545
+ letter-spacing: 0.03em;
70546
+ opacity: 0.75;
70547
+ }
70548
+
70549
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-item {
70550
+ display: inline-flex;
70551
+ align-items: center;
70552
+ gap: 0.4rem;
70553
+ }
70554
+
70555
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot {
70556
+ width: 0.55rem;
70557
+ height: 0.55rem;
70558
+ border-radius: 50%;
70559
+ display: inline-block;
70560
+ }
70561
+
70562
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="available"] {
70563
+ background-color: var(--ec-status-available);
70564
+ }
70565
+
70566
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="filling"] {
70567
+ background-color: var(--ec-status-filling);
70568
+ }
70569
+
70570
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="waitlist"] {
70571
+ background-color: var(--ec-status-waitlist);
70572
+ }
70573
+
70574
+ /* Day columns grid */
70575
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-grid {
70576
+ display: grid;
70577
+ grid-template-columns: repeat(var(--event-week-board-cols), minmax(0, 1fr));
70578
+ }
70579
+
70580
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day {
70581
+ display: flex;
70582
+ flex-direction: column;
70583
+ min-height: 15rem;
70584
+ /* Tabular column rule. Driven by the theme border tokens so borderless site
70585
+ * styles (border.width: 'none' -> --rb-border-width: 0) render no grid at all,
70586
+ * while hairline/thin/thick themes get a crisp full-strength divider. */
70587
+ border-right: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
70588
+ }
70589
+
70590
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day:last-child {
70591
+ border-right: none;
70592
+ }
70593
+
70594
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-head {
70595
+ display: flex;
70596
+ align-items: baseline;
70597
+ gap: 0.4rem;
70598
+ padding: 0.9rem 0.9rem 0.65rem;
70599
+ /* Header-row underline — same border-token gating as the column rules. */
70600
+ border-bottom: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
70601
+ }
70602
+
70603
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--today .event-week-board-day-head {
70604
+ background-color: var(--ec-today-bg);
70605
+ }
70606
+
70607
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-abbr {
70608
+ font-size: 0.6875rem;
70609
+ font-weight: 600;
70610
+ letter-spacing: 0.1em;
70611
+ text-transform: uppercase;
70612
+ opacity: 0.65;
70613
+ }
70614
+
70615
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--today .event-week-board-day-abbr {
70616
+ opacity: 1;
70617
+ color: var(--ec-today-text);
70618
+ }
70619
+
70620
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-num {
70621
+ font-size: 0.8125rem;
70622
+ font-weight: 600;
70623
+ opacity: 0.5;
70624
+ }
70625
+
70626
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-body {
70627
+ display: flex;
70628
+ flex-direction: column;
70629
+ gap: 0.65rem;
70630
+ padding: 0.9rem;
70631
+ flex: 1;
70632
+ }
70633
+
70634
+ /* Class card */
70635
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
70636
+ display: flex;
70637
+ flex-direction: column;
70638
+ gap: 0.45rem;
70639
+ padding: 0.8rem 0.85rem;
70640
+ border: 1px solid var(--ec-border-subtle);
70641
+ border-left: 3px solid var(--ec-status-available);
70642
+ border-radius: calc(var(--rb-block-radius) / 1.5);
70643
+ background-color: var(--ec-surface);
70644
+ color: inherit;
70645
+ text-decoration: none;
70646
+ transition:
70647
+ transform var(--rb-block-transition) ease,
70648
+ box-shadow var(--rb-block-transition) ease,
70649
+ border-color var(--rb-block-transition) ease;
70650
+ }
70651
+
70652
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card[data-status="filling"] {
70653
+ border-left-color: var(--ec-status-filling);
70654
+ }
70655
+
70656
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card[data-status="waitlist"] {
70657
+ border-left-color: var(--ec-status-waitlist);
70658
+ }
70659
+
70660
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card:hover {
70661
+ transform: translateY(-2px);
70662
+ box-shadow: 0 6px 16px -8px rgba(var(--tb-text), 0.28);
70663
+ border-color: rgba(var(--tb-primary), 0.4);
70664
+ }
70665
+
70666
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card:focus-visible {
70667
+ outline: 2px solid rgb(var(--tb-primary));
70668
+ outline-offset: 2px;
70669
+ }
70670
+
70671
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-title {
70672
+ font-family: var(--font-heading, inherit);
70673
+ font-size: 0.9375rem;
70674
+ font-weight: 600;
70675
+ line-height: 1.2;
70676
+ }
70677
+
70678
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-time {
70679
+ font-size: 0.75rem;
70680
+ letter-spacing: 0.01em;
70681
+ opacity: 0.7;
70682
+ }
70683
+
70684
+ /* Capacity pill */
70685
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill {
70686
+ align-self: flex-start;
70687
+ display: inline-flex;
70688
+ align-items: center;
70689
+ padding: 0.15rem 0.6rem;
70690
+ border-radius: 999px;
70691
+ font-size: 0.6875rem;
70692
+ font-weight: 600;
70693
+ letter-spacing: 0.02em;
70694
+ /* Available (default): a calm neutral/muted pill — not a coloured outline.
70695
+ * Filling -> amber and waitlist -> muted outline are handled by the
70696
+ * data-status overrides below. */
70697
+ border: 1px solid var(--ec-border);
70698
+ background-color: rgba(var(--tb-text), 0.06);
70699
+ color: rgba(var(--tb-text), 0.62);
70700
+ border-color: var(--ec-border);
70701
+ }
70702
+
70703
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill[data-status="filling"] {
70704
+ background-color: rgba(var(--tb-accent), 0.14);
70705
+ color: var(--ec-status-filling);
70706
+ border-color: rgba(var(--tb-accent), 0.32);
70707
+ }
70708
+
70709
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill[data-status="waitlist"] {
70710
+ background-color: transparent;
70711
+ color: rgba(var(--tb-text), 0.6);
70712
+ border-color: var(--ec-border);
70713
+ }
70714
+
70715
+ /* Footer */
70716
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-foot {
70717
+ display: flex;
70718
+ align-items: center;
70719
+ justify-content: space-between;
70720
+ gap: 1rem;
70721
+ flex-wrap: wrap;
70722
+ padding: 1.1rem 1.5rem;
70723
+ background-color: var(--ec-header-bg);
70724
+ border-top: 1px solid var(--ec-border-subtle);
70725
+ }
70726
+
70727
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-foot-note {
70728
+ margin: 0;
70729
+ font-size: 0.875rem;
70730
+ opacity: 0.75;
70731
+ }
70732
+
70733
+ /* Responsive: collapse day columns to a stacked list on narrow viewports */
70734
+ @container rb-site (max-width: 1023px) {
70735
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
70736
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-grid {
70737
+ grid-template-columns: 1fr;
70738
+ }
70739
+
70740
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day {
70741
+ min-height: 0;
70742
+ border-right: none;
70743
+ border-bottom: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
70744
+ }
70745
+
70746
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day:last-child {
70747
+ border-bottom: none;
70748
+ }
70749
+
70750
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--empty {
70751
+ display: none;
70752
+ }
70753
+
70754
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-head {
70755
+ border-bottom: none;
70756
+ padding-bottom: 0.25rem;
70757
+ }
70758
+
70759
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-body {
70760
+ flex-direction: row;
70761
+ flex-wrap: wrap;
70762
+ padding-top: 0.35rem;
70763
+ }
70764
+
70765
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
70766
+ flex: 1 1 14rem;
70767
+ }
70768
+ }
70769
+ }
70770
+
70771
+ @container rb-site (max-width: 639px) {
70772
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
70773
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend {
70774
+ display: none;
70775
+ }
70776
+
70777
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
70778
+ flex: 1 1 100%;
70779
+ }
70780
+ }
70781
+ }
70782
+
70783
+ /* -----------------------------------------------------------------------------
70784
+ * Week-board section band
70785
+ * Give the whole week-board section (head + panel) a subtle surface band so it
70786
+ * reads as a distinct zone from the page — the "testimonials band" pattern.
70787
+ * Painted as a pseudo-element above the section's fallback background layer
70788
+ * (z-index -10) but behind the content, so no block-content wiring is needed.
70789
+ * Surface + hairline rules are token-referenced: on a borderless site style
70790
+ * (--rb-border-width: 0) the band keeps its surface tint but drops the rules.
70791
+ * ----------------------------------------------------------------------------- */
70792
+ :where(.theme-scope) [data-slot="section"]:has(.event-calendar--week-board)::before {
70793
+ content: "";
70794
+ position: absolute;
70795
+ inset: 0;
70796
+ z-index: -9;
70797
+ background-color: rgb(var(--tb-surfaceAlt));
70798
+ border-block: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
70799
+ pointer-events: none;
70800
+ }
70801
+
70802
+ /* -----------------------------------------------------------------------------
70803
+ * Event section head — split (heading left, intro right)
70804
+ * Rendered by the block frame (outside [data-block="event-calendar"]) via
70805
+ * eventSectionHeaderLayoutNodes({ splitClassName }). Lays the heading and its
70806
+ * supporting intro out as a two-column head on wide viewports, stacking on
70807
+ * narrow ones. The section band above provides the distinct background.
70808
+ * ----------------------------------------------------------------------------- */
70809
+ :where(.theme-scope) .event-section-head--split {
70810
+ display: flex;
70811
+ align-items: flex-end;
70812
+ justify-content: space-between;
70813
+ gap: clamp(1.5rem, 4vw, 3.5rem);
70814
+ flex-wrap: wrap;
70815
+ }
70816
+
70817
+ :where(.theme-scope) .event-section-head--split > :where(h1, h2, h3) {
70818
+ margin: 0;
70819
+ }
70820
+
70821
+ :where(.theme-scope) .event-section-head--split > .rb-prose {
70822
+ flex: 1 1 22rem;
70823
+ max-width: 34rem;
70824
+ margin: 0;
70825
+ }
70826
+
70827
+ @container rb-site (max-width: 767px) {
70828
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
70829
+ :where(.theme-scope) .event-section-head--split {
70830
+ flex-direction: column;
70831
+ align-items: flex-start;
70832
+ gap: 0.75rem;
70833
+ }
70834
+
70835
+ :where(.theme-scope) .event-section-head--split > .rb-prose {
70836
+ flex-basis: auto;
70837
+ }
70838
+ }
70839
+ }
70840
+
69264
70841
  /* -----------------------------------------------------------------------------
69265
70842
  * Event Calendar Modals
69266
70843
  * Modal overlays for event details and day events list
@@ -69688,7 +71265,7 @@ const containerResponsiveThemeCss = `/*
69688
71265
  }
69689
71266
 
69690
71267
  :where(.theme-scope) .rb-inline-text-accent-contrast {
69691
- color: var(--section-text-color, rgb(var(--tb-text)));
71268
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
69692
71269
  }
69693
71270
 
69694
71271
  :where(.theme-scope) .rb-inline-text-highlight {
@@ -69699,7 +71276,7 @@ const containerResponsiveThemeCss = `/*
69699
71276
  }
69700
71277
 
69701
71278
  :where(.theme-scope) .rb-inline-text-highlight-soft {
69702
- background: color-mix(in oklab, var(--section-text-color, rgb(var(--tb-text))) 14%, transparent);
71279
+ background: color-mix(in oklab, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 14%, transparent);
69703
71280
  }
69704
71281
 
69705
71282
  :where(.theme-scope) .rb-inline-text-highlight-accent {
@@ -69718,7 +71295,7 @@ const containerResponsiveThemeCss = `/*
69718
71295
 
69719
71296
  /* Eyebrow - color only (typography via .rb-eyebrow) */
69720
71297
  :where(.theme-scope) :where([data-block="hero"]) .hero-eyebrow:not(.rb-eyebrow-treatment-accent) {
69721
- color: var(--section-text-color, rgb(var(--tb-text)));
71298
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
69722
71299
  }
69723
71300
 
69724
71301
  /* Subheadline - supporting text below headline */
@@ -69728,7 +71305,7 @@ const containerResponsiveThemeCss = `/*
69728
71305
 
69729
71306
  :where(.theme-scope) :where([data-block="hero"]) .hero-subheadline {
69730
71307
  font-size: 1.125rem;
69731
- color: var(--section-supporting-color, var(--section-text-color, rgb(var(--tb-text))));
71308
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
69732
71309
  }
69733
71310
 
69734
71311
  @container rb-site (min-width: 640px) {
@@ -69819,7 +71396,7 @@ const containerResponsiveThemeCss = `/*
69819
71396
 
69820
71397
  /* CTA content/body text */
69821
71398
  :where(.theme-scope) :where([data-block="cta-full"]) .cta-content {
69822
- color: var(--section-supporting-color, rgb(var(--tb-mutedText)));
71399
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
69823
71400
  }
69824
71401
 
69825
71402
  /* CTA button row container */
@@ -69919,7 +71496,7 @@ const containerResponsiveThemeCss = `/*
69919
71496
  /* Form intro text */
69920
71497
  :where(.theme-scope) :where([data-block="form"]) .form-copy-intro {
69921
71498
  font-size: 1rem;
69922
- color: var(--section-text-color, rgb(var(--tb-text)));
71499
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
69923
71500
  }
69924
71501
 
69925
71502
  /* =============================================================================
@@ -70278,6 +71855,10 @@ const containerResponsiveThemeCss = `/*
70278
71855
  --rb-modal-overlay-bg: rgb(var(--tb-text, 0 0 0) / 0.6);
70279
71856
  --rb-modal-shadow: var(--shadow-lg, var(--shadow-elev));
70280
71857
  --rb-modal-z-index: 20000;
71858
+ --rb-modal-text: rgb(var(--tb-text, 17 24 39));
71859
+ --rb-modal-heading: rgb(var(--tb-text, 17 24 39));
71860
+ --rb-modal-link: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
71861
+ --rb-modal-supporting: rgb(var(--tb-mutedText, 100 116 139));
70281
71862
  --shop-card-radius: var(--rb-block-radius, var(--radius-card, 0.75rem));
70282
71863
  --shop-control-radius: var(--rb-input-radius, var(--radius-control, 0.375rem));
70283
71864
  }
@@ -70335,6 +71916,11 @@ const containerResponsiveThemeCss = `/*
70335
71916
  /* Product card */
70336
71917
  :where(.theme-scope) .shop__card {
70337
71918
  background: rgb(var(--tb-surface));
71919
+ color: rgb(var(--tb-text, 17 24 39));
71920
+ --rb-surface-text-color: rgb(var(--tb-text, 17 24 39));
71921
+ --rb-surface-heading-color: rgb(var(--tb-text, 17 24 39));
71922
+ --rb-surface-link-color: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
71923
+ --rb-surface-supporting-color: rgb(var(--tb-mutedText, 100 116 139));
70338
71924
  border: 1px solid rgb(var(--tb-border));
70339
71925
  border-radius: var(--shop-card-radius);
70340
71926
  padding: 1.5rem;
@@ -70489,6 +72075,11 @@ const containerResponsiveThemeCss = `/*
70489
72075
  border: 1px solid rgb(var(--tb-border));
70490
72076
  border-radius: var(--shop-card-radius);
70491
72077
  background: rgb(var(--tb-surface));
72078
+ color: rgb(var(--tb-text, 17 24 39));
72079
+ --rb-surface-text-color: rgb(var(--tb-text, 17 24 39));
72080
+ --rb-surface-heading-color: rgb(var(--tb-text, 17 24 39));
72081
+ --rb-surface-link-color: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
72082
+ --rb-surface-supporting-color: rgb(var(--tb-mutedText, 100 116 139));
70492
72083
  }
70493
72084
 
70494
72085
  :where(.theme-scope) :where([data-block="shop"]) .rb-state-icon--success {
@@ -70561,6 +72152,11 @@ const containerResponsiveThemeCss = `/*
70561
72152
  margin: 0 auto;
70562
72153
  padding: 2rem;
70563
72154
  background: var(--cp-card-bg);
72155
+ color: var(--cp-text);
72156
+ --rb-surface-text-color: var(--cp-text);
72157
+ --rb-surface-heading-color: var(--cp-text);
72158
+ --rb-surface-link-color: var(--cp-primary);
72159
+ --rb-surface-supporting-color: var(--cp-muted);
70564
72160
  border: 1px solid var(--cp-card-border);
70565
72161
  border-radius: var(--cp-card-radius);
70566
72162
  }
@@ -70689,6 +72285,11 @@ const containerResponsiveThemeCss = `/*
70689
72285
  padding: 3rem 2rem;
70690
72286
  text-align: center;
70691
72287
  background: var(--cp-card-bg);
72288
+ color: var(--cp-text);
72289
+ --rb-surface-text-color: var(--cp-text);
72290
+ --rb-surface-heading-color: var(--cp-text);
72291
+ --rb-surface-link-color: var(--cp-primary);
72292
+ --rb-surface-supporting-color: var(--cp-muted);
70692
72293
  border: 1px dashed var(--cp-card-border);
70693
72294
  border-radius: var(--cp-card-radius);
70694
72295
  }
@@ -70701,6 +72302,11 @@ const containerResponsiveThemeCss = `/*
70701
72302
  /* Card */
70702
72303
  :where(.theme-scope) :where([data-block="customer-portal"]) .cp-card {
70703
72304
  background: var(--cp-card-bg);
72305
+ color: var(--cp-text);
72306
+ --rb-surface-text-color: var(--cp-text);
72307
+ --rb-surface-heading-color: var(--cp-text);
72308
+ --rb-surface-link-color: var(--cp-primary);
72309
+ --rb-surface-supporting-color: var(--cp-muted);
70704
72310
  border: 1px solid var(--cp-card-border);
70705
72311
  border-radius: var(--cp-card-radius);
70706
72312
  padding: 1.25rem;
@@ -70865,6 +72471,11 @@ const containerResponsiveThemeCss = `/*
70865
72471
  /* Table */
70866
72472
  :where(.theme-scope) :where([data-block="customer-portal"]) .cp-table-wrap {
70867
72473
  background: var(--cp-card-bg);
72474
+ color: var(--cp-text);
72475
+ --rb-surface-text-color: var(--cp-text);
72476
+ --rb-surface-heading-color: var(--cp-text);
72477
+ --rb-surface-link-color: var(--cp-primary);
72478
+ --rb-surface-supporting-color: var(--cp-muted);
70868
72479
  border: 1px solid var(--cp-card-border);
70869
72480
  border-radius: var(--cp-card-radius);
70870
72481
  }
@@ -71426,6 +73037,143 @@ const containerResponsiveThemeCss = `/*
71426
73037
  }
71427
73038
 
71428
73039
 
73040
+ /* =============================================================================
73041
+ * Variant: featured-editorial
73042
+ *
73043
+ * An asymmetric layout: a large featured (spruce-ink) quote card beside a
73044
+ * column of stacked white cards. Renders NO avatars. Colours are palette
73045
+ * tokens only. Node structure is built in testimonialsCarousel.ts.
73046
+ * ============================================================================= */
73047
+
73048
+ /* Section head: heading left, lead-in line to the right; stacks on mobile. */
73049
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-head {
73050
+ display: flex;
73051
+ flex-direction: column;
73052
+ align-items: flex-start;
73053
+ gap: 0.875rem;
73054
+ }
73055
+
73056
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-leadin {
73057
+ max-width: 30rem;
73058
+ }
73059
+
73060
+ @container rb-site (min-width: 768px) {
73061
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
73062
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-head {
73063
+ flex-direction: row;
73064
+ align-items: flex-end;
73065
+ justify-content: space-between;
73066
+ gap: 1.5rem;
73067
+ }
73068
+ }
73069
+ }
73070
+
73071
+ /* Card grid: featured (wider) beside the stacked column. Single column on
73072
+ * small screens. */
73073
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-grid {
73074
+ display: grid;
73075
+ grid-template-columns: minmax(0, 1fr);
73076
+ gap: 1.25rem;
73077
+ align-items: stretch;
73078
+ }
73079
+
73080
+ @container rb-site (min-width: 1024px) {
73081
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
73082
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-grid {
73083
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
73084
+ }
73085
+ }
73086
+ }
73087
+
73088
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-col {
73089
+ display: grid;
73090
+ gap: 1.25rem;
73091
+ align-content: start;
73092
+ }
73093
+
73094
+ /* Base card (white surface) */
73095
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card {
73096
+ display: flex;
73097
+ flex-direction: column;
73098
+ height: 100%;
73099
+ border-radius: var(--rb-block-radius);
73100
+ padding: 1.875rem 1.875rem 1.625rem;
73101
+ background: rgb(var(--tb-surface));
73102
+ border: 1px solid rgba(var(--tb-border), 0.9);
73103
+ box-shadow: 0 1px 2px rgba(var(--tb-shadow, 0 0 0), 0.05);
73104
+ }
73105
+
73106
+ /* Featured (spruce-ink) card */
73107
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature {
73108
+ padding: 2.5rem;
73109
+ background: rgb(var(--tb-primary));
73110
+ border-color: transparent;
73111
+ color: rgb(var(--tb-onPrimary));
73112
+ }
73113
+
73114
+ /* Amber quote mark, oversized serif */
73115
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-qmark {
73116
+ display: block;
73117
+ height: 1.6rem;
73118
+ font-family: var(--font-heading, Georgia, serif);
73119
+ font-style: italic;
73120
+ font-size: 3.375rem;
73121
+ line-height: 0.6;
73122
+ color: rgb(var(--tb-secondary));
73123
+ }
73124
+
73125
+ /* Quote body. The inner <p> is targeted directly (not via :where) so the
73126
+ * serif display font and sizing win over the theme's base paragraph rule. */
73127
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-quote {
73128
+ flex: 1 1 auto;
73129
+ margin-top: 1.25rem;
73130
+ }
73131
+
73132
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-quote p {
73133
+ margin: 0;
73134
+ font-family: var(--font-heading, Georgia, serif);
73135
+ font-size: 1.15rem;
73136
+ line-height: 1.5;
73137
+ color: rgb(var(--tb-text));
73138
+ }
73139
+
73140
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-quote p {
73141
+ color: rgb(var(--tb-onPrimary));
73142
+ font-size: clamp(1.375rem, 2.4vw, 1.6875rem);
73143
+ line-height: 1.42;
73144
+ letter-spacing: -0.01em;
73145
+ }
73146
+
73147
+ /* Attribution: name (bold) + context line, no avatar */
73148
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-meta {
73149
+ display: flex;
73150
+ flex-direction: column;
73151
+ gap: 0.25rem;
73152
+ margin-top: 1.625rem;
73153
+ }
73154
+
73155
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-name {
73156
+ font-weight: 600;
73157
+ font-size: 0.97rem;
73158
+ color: rgb(var(--tb-text));
73159
+ }
73160
+
73161
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-context {
73162
+ font-size: 0.78rem;
73163
+ letter-spacing: 0.02em;
73164
+ color: rgb(var(--tb-mutedText));
73165
+ }
73166
+
73167
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-name {
73168
+ color: rgb(var(--tb-onPrimary));
73169
+ }
73170
+
73171
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-context {
73172
+ color: rgb(var(--tb-onPrimary));
73173
+ opacity: 0.7;
73174
+ }
73175
+
73176
+
71429
73177
  /* Testimonials carousel tweaks: arrow styling + gutters + equal-height cards */
71430
73178
  :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-carousel.carousel-root .carousel-nav-btn {
71431
73179
  height: var(--rb-testimonial-carousel-arrow-size);
@@ -72572,7 +74320,7 @@ const containerResponsiveThemeCss = `/*
72572
74320
 
72573
74321
  :where(.theme-scope) :where([data-block="newsletter-signup"]) .newsletter-copy-subtitle {
72574
74322
  font-size: 1rem;
72575
- color: var(--section-text-color, rgb(var(--tb-text)));
74323
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
72576
74324
  opacity: 0.85;
72577
74325
  }
72578
74326
 
@@ -72621,7 +74369,7 @@ const containerResponsiveThemeCss = `/*
72621
74369
  margin-top: 0.75rem;
72622
74370
  font-size: 1rem;
72623
74371
  line-height: 1.6;
72624
- color: var(--section-text-color, rgb(var(--tb-text)));
74372
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
72625
74373
  text-align: center;
72626
74374
  }
72627
74375
 
@@ -72703,7 +74451,7 @@ const containerResponsiveThemeCss = `/*
72703
74451
 
72704
74452
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .form-copy-intro,
72705
74453
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .newsletter-copy-subtitle {
72706
- color: var(--section-text-color, rgb(var(--tb-text)));
74454
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
72707
74455
  }
72708
74456
 
72709
74457
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .contact-newsletter-info {
@@ -73101,7 +74849,7 @@ const containerResponsiveThemeCss = `/*
73101
74849
 
73102
74850
  :where(.theme-scope) .event-compact-row-title {
73103
74851
  margin: 0;
73104
- color: var(--section-heading-color, rgb(var(--tb-primary, 0 0 0)));
74852
+ color: var(--rb-surface-heading-color, var(--rb-surface-text-color, rgb(var(--tb-primary, var(--tb-text, 17 24 39)))));
73105
74853
  font-family: var(--font-heading, var(--font-body, inherit));
73106
74854
  font-size: clamp(1.05rem, 1rem + 0.35vw, 1.3rem);
73107
74855
  font-weight: 400;
@@ -73763,27 +75511,41 @@ const containerResponsiveThemeCss = `/*
73763
75511
  background: rgb(var(--tb-surface));
73764
75512
  }
73765
75513
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 1) {
75514
+ background: rgb(var(--tb-surface));
73766
75515
  color: rgb(var(--tb-onSurface));
75516
+ --rb-surface-text-color: rgb(var(--tb-onSurface));
75517
+ --rb-surface-heading-color: rgb(var(--tb-onSurface));
75518
+ --rb-surface-link-color: rgb(var(--tb-onSurface));
75519
+ --rb-surface-supporting-color: rgb(var(--tb-onSurface));
73767
75520
  }
73768
75521
 
73769
75522
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 2) > .rb-bg-surface {
73770
75523
  background: rgb(var(--tb-secondary));
73771
75524
  }
73772
75525
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 2) {
75526
+ background: rgb(var(--tb-secondary));
73773
75527
  color: rgb(var(--tb-onSecondary));
75528
+ --rb-surface-text-color: rgb(var(--tb-onSecondary));
75529
+ --rb-surface-heading-color: rgb(var(--tb-onSecondary));
75530
+ --rb-surface-link-color: rgb(var(--tb-onSecondary));
75531
+ --rb-surface-supporting-color: rgb(var(--tb-onSecondary));
73774
75532
  }
73775
75533
 
73776
75534
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 3) > .rb-bg-surface {
73777
75535
  background: rgb(var(--tb-primary));
73778
75536
  }
73779
75537
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 3) {
75538
+ background: rgb(var(--tb-primary));
73780
75539
  color: rgb(var(--tb-onPrimary));
75540
+ --rb-surface-text-color: rgb(var(--tb-onPrimary));
75541
+ --rb-surface-heading-color: rgb(var(--tb-onPrimary));
75542
+ --rb-surface-link-color: rgb(var(--tb-onPrimary));
75543
+ --rb-surface-supporting-color: rgb(var(--tb-onPrimary));
73781
75544
  }
73782
75545
 
73783
- /* Body copy carries an inline muted colour from the block; force it to the
73784
- * card's on-colour so it reads on every block (inline style needs !important). */
75546
+ /* Keep the intended visual hierarchy; the body color itself now resolves via
75547
+ * the nearest surface provider instead of requiring an inline override. */
73785
75548
  :where(.theme-scope) .rb-card-treatment-color-blocked .rb-feature-card-body {
73786
- color: inherit !important;
73787
75549
  opacity: 0.9;
73788
75550
  }
73789
75551
 
@@ -75326,6 +77088,8 @@ const VERTICAL_IDS = [
75326
77088
  "yoga_wellness",
75327
77089
  "fitness_pt",
75328
77090
  "dance_school",
77091
+ "physiotherapy_physical_health",
77092
+ "massage_bodywork",
75329
77093
  "therapist_practitioner",
75330
77094
  "beauty_spa",
75331
77095
  "consultant_coach",
@@ -75361,6 +77125,12 @@ const VERTICAL_BOOKING_PRESETS = [
75361
77125
  preset("dance_school.workshops", "dance_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
75362
77126
  preset("dance_school.private_lessons", "dance_school", "appointment", "Private lesson", "Private lessons", "One-to-one lessons or coaching sessions."),
75363
77127
  preset("dance_school.shows", "dance_school", "performance", "Show", "Shows", "Ticketed performances, showcases, and recitals."),
77128
+ preset("physiotherapy_physical_health.appointments", "physiotherapy_physical_health", "appointment", "Appointment", "Appointments", "Assessments, treatments, follow-ups, and rehabilitation sessions."),
77129
+ preset("physiotherapy_physical_health.rehab_classes", "physiotherapy_physical_health", "class", "Rehab class", "Rehab classes", "Regular rehabilitation, mobility, strength, or recovery classes."),
77130
+ preset("physiotherapy_physical_health.workshops", "physiotherapy_physical_health", "workshop", "Workshop", "Workshops", "One-off workshops, talks, or practical group sessions."),
77131
+ preset("massage_bodywork.treatments", "massage_bodywork", "appointment", "Treatment", "Treatments", "Massage treatments, bodywork sessions, and one-to-one appointments."),
77132
+ preset("massage_bodywork.group_sessions", "massage_bodywork", "class", "Group session", "Group sessions", "Regular bodywork, somatic movement, or self-massage sessions."),
77133
+ preset("massage_bodywork.workshops", "massage_bodywork", "workshop", "Workshop", "Workshops", "One-off bodywork, self-massage, or somatic practice workshops."),
75364
77134
  preset("therapist_practitioner.appointments", "therapist_practitioner", "appointment", "Appointment", "Appointments", "Sessions, consultations, treatments, or assessments."),
75365
77135
  preset("therapist_practitioner.workshops", "therapist_practitioner", "workshop", "Workshop", "Workshops", "One-off group workshops, talks, or wellbeing sessions."),
75366
77136
  preset("therapist_practitioner.retreats", "therapist_practitioner", "retreat", "Retreat", "Retreats", "Multi-day retreats or immersive group experiences."),
@@ -80717,7 +82487,7 @@ var CircuitOpenError = class extends Error {
80717
82487
  * 1. This constant
80718
82488
  * 2. The "version" field in package.json
80719
82489
  */
80720
- const SDK_VERSION = "0.104.0";
82490
+ const SDK_VERSION = "0.104.3";
80721
82491
  //#endregion
80722
82492
  //#region src/next/content-load-failure.ts
80723
82493
  const SdkContentLoadError = RiverbankContentLoadError;