@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
@@ -494,11 +494,11 @@ function joinStoragePathSegments(segments) {
494
494
  }
495
495
  //#endregion
496
496
  //#region ../media-core/src/typeGuards.ts
497
- function isRecord$29(value) {
497
+ function isRecord$31(value) {
498
498
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
499
499
  }
500
500
  function asRecord$3(value) {
501
- return isRecord$29(value) ? value : void 0;
501
+ return isRecord$31(value) ? value : void 0;
502
502
  }
503
503
  function isFileJsonObject(value) {
504
504
  return Object.values(value).every(isFileJsonValue);
@@ -507,7 +507,7 @@ function isFileJsonValue(value) {
507
507
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
508
508
  if (typeof value === "number") return Number.isFinite(value);
509
509
  if (Array.isArray(value)) return value.every(isFileJsonValue);
510
- return isRecord$29(value) && isFileJsonObject(value);
510
+ return isRecord$31(value) && isFileJsonObject(value);
511
511
  }
512
512
  new TextDecoder("latin1");
513
513
  //#endregion
@@ -811,7 +811,7 @@ function isCmsMediaObjectType(value) {
811
811
  return typeof value === "string" && CMS_MEDIA_OBJECT_TYPES.includes(value);
812
812
  }
813
813
  function isCmsMediaObject(value) {
814
- if (!isRecord$29(value)) return false;
814
+ if (!isRecord$31(value)) return false;
815
815
  const type = value.type;
816
816
  if (!isCmsMediaObjectType(type)) return false;
817
817
  if ("attrs" in value) return false;
@@ -822,7 +822,7 @@ function collectMediaObjects(value, out, location = "") {
822
822
  value.forEach((item, index) => collectMediaObjects(item, out, `${location}[${index}]`));
823
823
  return;
824
824
  }
825
- if (!isRecord$29(value)) return;
825
+ if (!isRecord$31(value)) return;
826
826
  if (isCmsMediaObject(value)) out.push({
827
827
  obj: value,
828
828
  location
@@ -836,8 +836,8 @@ function walkAndCollectImageAttrs(value, out, location = "") {
836
836
  value.forEach((item, index) => walkAndCollectImageAttrs(item, out, `${location}[${index}]`));
837
837
  return;
838
838
  }
839
- if (!isRecord$29(value)) return;
840
- if (value.type === "image" && isRecord$29(value.attrs)) out.push({
839
+ if (!isRecord$31(value)) return;
840
+ if (value.type === "image" && isRecord$31(value.attrs)) out.push({
841
841
  attrs: value.attrs,
842
842
  location: location ? `${location}.attrs` : "attrs"
843
843
  });
@@ -1087,6 +1087,9 @@ const ALLOWED_NODE_TYPES = [
1087
1087
  "richText",
1088
1088
  "media",
1089
1089
  "videoEmbed",
1090
+ "icon",
1091
+ "feature-card-icon",
1092
+ "svg",
1090
1093
  "button",
1091
1094
  "link",
1092
1095
  "carousel",
@@ -1123,11 +1126,13 @@ const ALLOWED_TRANSFORM_IDS = [
1123
1126
  "value.jsonArray",
1124
1127
  "value.csv",
1125
1128
  "array.marqueeLoop",
1129
+ "value.mapToken",
1126
1130
  "layout.maxWidthClass",
1127
1131
  "layout.headerRootClass",
1128
1132
  "layout.footerRootClass",
1129
1133
  "layout.headerRootStyle",
1130
1134
  "layout.headerSectionBackground",
1135
+ "layout.classicNavMode",
1131
1136
  "layout.footerRootStyle",
1132
1137
  "ui.headerNavLinkClass",
1133
1138
  "ui.headingClassFromAlignment",
@@ -2986,7 +2991,7 @@ function getSemanticIconComponent(name) {
2986
2991
  //#endregion
2987
2992
  //#region ../blocks/src/system/icons/value.ts
2988
2993
  const NO_BLOCK_ICON_VALUE = { kind: "none" };
2989
- function isRecord$28(value) {
2994
+ function isRecord$30(value) {
2990
2995
  return typeof value === "object" && value !== null && !Array.isArray(value);
2991
2996
  }
2992
2997
  function parseBlockIconValue(value) {
@@ -2994,7 +2999,7 @@ function parseBlockIconValue(value) {
2994
2999
  kind: "semantic",
2995
3000
  iconId: value
2996
3001
  };
2997
- if (!isRecord$28(value)) return NO_BLOCK_ICON_VALUE;
3002
+ if (!isRecord$30(value)) return NO_BLOCK_ICON_VALUE;
2998
3003
  switch (value.kind) {
2999
3004
  case "none": return NO_BLOCK_ICON_VALUE;
3000
3005
  case "semantic": return isSemanticIconId(value.iconId) ? {
@@ -3067,7 +3072,7 @@ function buildGeneratedBlockIconRenderAssetLookupFromUnknown(value) {
3067
3072
  if (!Array.isArray(value)) return { kind: "invalid_asset_list" };
3068
3073
  const inputs = [];
3069
3074
  for (const [index, item] of value.entries()) {
3070
- if (!isRecord$28(item)) return {
3075
+ if (!isRecord$30(item)) return {
3071
3076
  kind: "invalid_asset_entry",
3072
3077
  index,
3073
3078
  reason: "not_object"
@@ -5420,6 +5425,234 @@ function expandPalette(palette) {
5420
5425
  return expanded;
5421
5426
  }
5422
5427
  //#endregion
5428
+ //#region ../theme-core/src/tokens/colorTokenParts.ts
5429
+ function parseOpacityPercent(source) {
5430
+ const trimmed = source.trim();
5431
+ if (!trimmed) return null;
5432
+ if (!/^\d{1,3}(?:\.\d+)?$/.test(trimmed)) return null;
5433
+ const value = Number(trimmed);
5434
+ if (!Number.isFinite(value)) return null;
5435
+ if (value < 0 || value > 100) return null;
5436
+ return value;
5437
+ }
5438
+ function parseColorTokenParts(source) {
5439
+ const trimmed = source.trim();
5440
+ if (trimmed.startsWith("raw:")) return {
5441
+ kind: "raw",
5442
+ value: trimmed.slice(4)
5443
+ };
5444
+ const [token, opacityRaw, extra] = trimmed.split("/");
5445
+ if (!token || extra !== void 0) return {
5446
+ kind: "invalid",
5447
+ token: trimmed
5448
+ };
5449
+ if (opacityRaw === void 0) return {
5450
+ kind: "token",
5451
+ token,
5452
+ opacityPercent: null
5453
+ };
5454
+ const opacityPercent = parseOpacityPercent(opacityRaw);
5455
+ if (opacityPercent === null) return {
5456
+ kind: "invalid",
5457
+ token: trimmed
5458
+ };
5459
+ return {
5460
+ kind: "token",
5461
+ token,
5462
+ opacityPercent
5463
+ };
5464
+ }
5465
+ //#endregion
5466
+ //#region ../theme-core/src/tokens/blockColorTokens.ts
5467
+ const blockColorTokenNames = [
5468
+ "text",
5469
+ "heading",
5470
+ "mutedText",
5471
+ "surface",
5472
+ "background",
5473
+ "foreground",
5474
+ "border",
5475
+ "primary",
5476
+ "secondary",
5477
+ "accent",
5478
+ "success",
5479
+ "warning",
5480
+ "info",
5481
+ "error",
5482
+ "destructive",
5483
+ "scrim",
5484
+ "shadow",
5485
+ "onPrimary",
5486
+ "onSecondary",
5487
+ "onAccent",
5488
+ "onSuccess",
5489
+ "onWarning",
5490
+ "onInfo",
5491
+ "onError",
5492
+ "onDestructive",
5493
+ "onSurface"
5494
+ ];
5495
+ const blockColorTokenNameSet = new Set(blockColorTokenNames);
5496
+ function isBlockColorToken(value) {
5497
+ if (typeof value !== "string") return false;
5498
+ const parts = parseColorTokenParts(value);
5499
+ return parts.kind === "token" && blockColorTokenNameSet.has(parts.token);
5500
+ }
5501
+ function parseBlockColorToken(value) {
5502
+ if (!isBlockColorToken(value)) return null;
5503
+ return value.trim();
5504
+ }
5505
+ //#endregion
5506
+ //#region ../theme-core/src/tokens/themeColorTokens.ts
5507
+ /**
5508
+ * Known palette role token names. This is the low-level source of truth for
5509
+ * palette roles used by palette variants, Theme V2 schemas, and runtime color
5510
+ * reference parsing.
5511
+ */
5512
+ const PALETTE_TOKEN_NAMES = [
5513
+ "primary",
5514
+ "primaryForeground",
5515
+ "secondary",
5516
+ "accent",
5517
+ "success",
5518
+ "warning",
5519
+ "destructive",
5520
+ "background",
5521
+ "surface",
5522
+ "surfaceAlt",
5523
+ "muted",
5524
+ "text",
5525
+ "border"
5526
+ ];
5527
+ const paletteTokenNameSchema = z$1.enum(PALETTE_TOKEN_NAMES);
5528
+ const themeColorBaseTokenNames = [
5529
+ ...PALETTE_TOKEN_NAMES,
5530
+ ...blockColorTokenNames,
5531
+ "white",
5532
+ "neutral",
5533
+ "primaryText",
5534
+ "secondaryForeground",
5535
+ "accentForeground"
5536
+ ];
5537
+ const themeColorBaseTokenSet = new Set(themeColorBaseTokenNames);
5538
+ const themeColorShadeSteps = [
5539
+ "50",
5540
+ "100",
5541
+ "200",
5542
+ "300",
5543
+ "400",
5544
+ "500",
5545
+ "600",
5546
+ "700",
5547
+ "800",
5548
+ "900",
5549
+ "950"
5550
+ ];
5551
+ const shadeStepSet = new Set(themeColorShadeSteps);
5552
+ function isKnownBaseToken(value) {
5553
+ return themeColorBaseTokenSet.has(value);
5554
+ }
5555
+ function isKnownShadeToken(value) {
5556
+ const match = value.match(/^([a-zA-Z][a-zA-Z0-9]*)(?:-([0-9]{2,3}))$/);
5557
+ if (!match) return false;
5558
+ const [, base, shade] = match;
5559
+ return Boolean(base && shade && isKnownBaseToken(base) && shadeStepSet.has(shade));
5560
+ }
5561
+ function isThemeColorTokenBase(value) {
5562
+ return isKnownBaseToken(value) || isKnownShadeToken(value);
5563
+ }
5564
+ function isThemeColorToken(value) {
5565
+ if (typeof value !== "string") return false;
5566
+ const parts = parseColorTokenParts(value);
5567
+ return parts.kind === "token" && isThemeColorTokenBase(parts.token);
5568
+ }
5569
+ function parseThemeColorToken(value) {
5570
+ if (!isThemeColorToken(value)) return null;
5571
+ return value.trim();
5572
+ }
5573
+ const themeColorTokenSchema = z$1.string().min(1).refine((value) => Boolean(isThemeColorToken(value)), "Expected a known Theme V2 color token");
5574
+ //#endregion
5575
+ //#region ../theme-core/src/palette/colors.ts
5576
+ const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/;
5577
+ const NORMALIZABLE_HEX_REGEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
5578
+ const hexColorSchema = z$1.string().regex(HEX_COLOR_REGEX, "Hex must be #RRGGBB (lowercase)").transform((value) => value);
5579
+ const paletteModeSchema = z$1.enum(["light", "dark"]);
5580
+ z$1.string().regex(/^[0-9a-f]{64}$/, "Palette hash must be a lowercase SHA-256 value").transform((value) => value);
5581
+ const paletteColorsSchema = z$1.object({
5582
+ primary: hexColorSchema,
5583
+ primaryForeground: hexColorSchema,
5584
+ secondary: hexColorSchema,
5585
+ accent: hexColorSchema,
5586
+ success: hexColorSchema,
5587
+ warning: hexColorSchema,
5588
+ destructive: hexColorSchema,
5589
+ background: hexColorSchema,
5590
+ surface: hexColorSchema,
5591
+ surfaceAlt: hexColorSchema,
5592
+ muted: hexColorSchema,
5593
+ text: hexColorSchema,
5594
+ border: hexColorSchema
5595
+ }).strict().transform((value) => value);
5596
+ function normalizePaletteHex(value) {
5597
+ if (!NORMALIZABLE_HEX_REGEX.test(value)) return null;
5598
+ const normalized = value.toLowerCase();
5599
+ if (normalized.length === 7) return normalized;
5600
+ const triplet = normalized.slice(1);
5601
+ const red = triplet.charAt(0);
5602
+ const green = triplet.charAt(1);
5603
+ const blue = triplet.charAt(2);
5604
+ return `#${red}${red}${green}${green}${blue}${blue}`;
5605
+ }
5606
+ function paletteColorsFromEntries(entries) {
5607
+ const values = {};
5608
+ for (const entry of entries) {
5609
+ const parsedRole = paletteTokenNameSchema.safeParse(entry.name);
5610
+ if (!parsedRole.success) return {
5611
+ ok: false,
5612
+ error: {
5613
+ kind: "unknown-role",
5614
+ role: entry.name
5615
+ }
5616
+ };
5617
+ const role = parsedRole.data;
5618
+ if (values[role] !== void 0) return {
5619
+ ok: false,
5620
+ error: {
5621
+ kind: "duplicate-role",
5622
+ role
5623
+ }
5624
+ };
5625
+ const hex = normalizePaletteHex(entry.hex);
5626
+ if (!hex) return {
5627
+ ok: false,
5628
+ error: {
5629
+ kind: "invalid-color",
5630
+ role,
5631
+ value: entry.hex
5632
+ }
5633
+ };
5634
+ values[role] = hex;
5635
+ }
5636
+ const missing = PALETTE_TOKEN_NAMES.filter((role) => values[role] === void 0);
5637
+ if (missing.length > 0) return {
5638
+ ok: false,
5639
+ error: {
5640
+ kind: "missing-roles",
5641
+ roles: missing
5642
+ }
5643
+ };
5644
+ return {
5645
+ ok: true,
5646
+ colors: paletteColorsSchema.parse(values)
5647
+ };
5648
+ }
5649
+ function requirePaletteColorsFromEntries(entries) {
5650
+ const result = paletteColorsFromEntries(entries);
5651
+ if (result.ok) return result.colors;
5652
+ throw new Error(`Invalid complete palette: ${result.error.kind}`);
5653
+ }
5654
+ new Set(PALETTE_TOKEN_NAMES);
5655
+ //#endregion
5423
5656
  //#region ../../node_modules/.pnpm/@vibrant+image@4.0.4/node_modules/@vibrant/image/dist/esm/histogram.js
5424
5657
  var Histogram = class {
5425
5658
  constructor(pixels, opts) {
@@ -6450,233 +6683,6 @@ const pipeline = new BasicPipeline().filter.register("default", (r, g, b, a) =>
6450
6683
  //#region ../../node_modules/.pnpm/node-vibrant@4.0.4/node_modules/node-vibrant/dist/esm/browser.js
6451
6684
  Vibrant.use(pipeline);
6452
6685
  //#endregion
6453
- //#region ../theme-core/src/tokens/colorTokenParts.ts
6454
- function parseOpacityPercent(source) {
6455
- const trimmed = source.trim();
6456
- if (!trimmed) return null;
6457
- if (!/^\d{1,3}(?:\.\d+)?$/.test(trimmed)) return null;
6458
- const value = Number(trimmed);
6459
- if (!Number.isFinite(value)) return null;
6460
- if (value < 0 || value > 100) return null;
6461
- return value;
6462
- }
6463
- function parseColorTokenParts(source) {
6464
- const trimmed = source.trim();
6465
- if (trimmed.startsWith("raw:")) return {
6466
- kind: "raw",
6467
- value: trimmed.slice(4)
6468
- };
6469
- const [token, opacityRaw, extra] = trimmed.split("/");
6470
- if (!token || extra !== void 0) return {
6471
- kind: "invalid",
6472
- token: trimmed
6473
- };
6474
- if (opacityRaw === void 0) return {
6475
- kind: "token",
6476
- token,
6477
- opacityPercent: null
6478
- };
6479
- const opacityPercent = parseOpacityPercent(opacityRaw);
6480
- if (opacityPercent === null) return {
6481
- kind: "invalid",
6482
- token: trimmed
6483
- };
6484
- return {
6485
- kind: "token",
6486
- token,
6487
- opacityPercent
6488
- };
6489
- }
6490
- //#endregion
6491
- //#region ../theme-core/src/tokens/blockColorTokens.ts
6492
- const blockColorTokenNames = [
6493
- "text",
6494
- "heading",
6495
- "mutedText",
6496
- "surface",
6497
- "background",
6498
- "foreground",
6499
- "border",
6500
- "primary",
6501
- "secondary",
6502
- "accent",
6503
- "success",
6504
- "warning",
6505
- "info",
6506
- "error",
6507
- "destructive",
6508
- "scrim",
6509
- "shadow",
6510
- "onPrimary",
6511
- "onSecondary",
6512
- "onAccent",
6513
- "onSuccess",
6514
- "onWarning",
6515
- "onInfo",
6516
- "onError",
6517
- "onDestructive",
6518
- "onSurface"
6519
- ];
6520
- const blockColorTokenNameSet = new Set(blockColorTokenNames);
6521
- function isBlockColorToken(value) {
6522
- if (typeof value !== "string") return false;
6523
- const parts = parseColorTokenParts(value);
6524
- return parts.kind === "token" && blockColorTokenNameSet.has(parts.token);
6525
- }
6526
- function parseBlockColorToken(value) {
6527
- if (!isBlockColorToken(value)) return null;
6528
- return value.trim();
6529
- }
6530
- //#endregion
6531
- //#region ../theme-core/src/tokens/themeColorTokens.ts
6532
- /**
6533
- * Known palette role token names. This is the low-level source of truth for
6534
- * palette roles used by palette variants, Theme V2 schemas, and runtime color
6535
- * reference parsing.
6536
- */
6537
- const PALETTE_TOKEN_NAMES = [
6538
- "primary",
6539
- "primaryForeground",
6540
- "secondary",
6541
- "accent",
6542
- "success",
6543
- "warning",
6544
- "destructive",
6545
- "background",
6546
- "surface",
6547
- "surfaceAlt",
6548
- "muted",
6549
- "text",
6550
- "border"
6551
- ];
6552
- const paletteTokenNameSchema = z$1.enum(PALETTE_TOKEN_NAMES);
6553
- const themeColorBaseTokenNames = [
6554
- ...PALETTE_TOKEN_NAMES,
6555
- ...blockColorTokenNames,
6556
- "white",
6557
- "neutral",
6558
- "primaryText",
6559
- "secondaryForeground",
6560
- "accentForeground"
6561
- ];
6562
- const themeColorBaseTokenSet = new Set(themeColorBaseTokenNames);
6563
- const themeColorShadeSteps = [
6564
- "50",
6565
- "100",
6566
- "200",
6567
- "300",
6568
- "400",
6569
- "500",
6570
- "600",
6571
- "700",
6572
- "800",
6573
- "900",
6574
- "950"
6575
- ];
6576
- const shadeStepSet = new Set(themeColorShadeSteps);
6577
- function isKnownBaseToken(value) {
6578
- return themeColorBaseTokenSet.has(value);
6579
- }
6580
- function isKnownShadeToken(value) {
6581
- const match = value.match(/^([a-zA-Z][a-zA-Z0-9]*)(?:-([0-9]{2,3}))$/);
6582
- if (!match) return false;
6583
- const [, base, shade] = match;
6584
- return Boolean(base && shade && isKnownBaseToken(base) && shadeStepSet.has(shade));
6585
- }
6586
- function isThemeColorTokenBase(value) {
6587
- return isKnownBaseToken(value) || isKnownShadeToken(value);
6588
- }
6589
- function isThemeColorToken(value) {
6590
- if (typeof value !== "string") return false;
6591
- const parts = parseColorTokenParts(value);
6592
- return parts.kind === "token" && isThemeColorTokenBase(parts.token);
6593
- }
6594
- function parseThemeColorToken(value) {
6595
- if (!isThemeColorToken(value)) return null;
6596
- return value.trim();
6597
- }
6598
- const themeColorTokenSchema = z$1.string().min(1).refine((value) => Boolean(isThemeColorToken(value)), "Expected a known Theme V2 color token");
6599
- //#endregion
6600
- //#region ../theme-core/src/palette/colors.ts
6601
- const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/;
6602
- const NORMALIZABLE_HEX_REGEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
6603
- const hexColorSchema = z$1.string().regex(HEX_COLOR_REGEX, "Hex must be #RRGGBB (lowercase)").transform((value) => value);
6604
- const paletteModeSchema = z$1.enum(["light", "dark"]);
6605
- z$1.string().regex(/^[0-9a-f]{64}$/, "Palette hash must be a lowercase SHA-256 value").transform((value) => value);
6606
- const paletteColorsSchema = z$1.object({
6607
- primary: hexColorSchema,
6608
- primaryForeground: hexColorSchema,
6609
- secondary: hexColorSchema,
6610
- accent: hexColorSchema,
6611
- success: hexColorSchema,
6612
- warning: hexColorSchema,
6613
- destructive: hexColorSchema,
6614
- background: hexColorSchema,
6615
- surface: hexColorSchema,
6616
- surfaceAlt: hexColorSchema,
6617
- muted: hexColorSchema,
6618
- text: hexColorSchema,
6619
- border: hexColorSchema
6620
- }).strict().transform((value) => value);
6621
- function normalizePaletteHex(value) {
6622
- if (!NORMALIZABLE_HEX_REGEX.test(value)) return null;
6623
- const normalized = value.toLowerCase();
6624
- if (normalized.length === 7) return normalized;
6625
- const triplet = normalized.slice(1);
6626
- const red = triplet.charAt(0);
6627
- const green = triplet.charAt(1);
6628
- const blue = triplet.charAt(2);
6629
- return `#${red}${red}${green}${green}${blue}${blue}`;
6630
- }
6631
- function paletteColorsFromEntries(entries) {
6632
- const values = {};
6633
- for (const entry of entries) {
6634
- const parsedRole = paletteTokenNameSchema.safeParse(entry.name);
6635
- if (!parsedRole.success) return {
6636
- ok: false,
6637
- error: {
6638
- kind: "unknown-role",
6639
- role: entry.name
6640
- }
6641
- };
6642
- const role = parsedRole.data;
6643
- if (values[role] !== void 0) return {
6644
- ok: false,
6645
- error: {
6646
- kind: "duplicate-role",
6647
- role
6648
- }
6649
- };
6650
- const hex = normalizePaletteHex(entry.hex);
6651
- if (!hex) return {
6652
- ok: false,
6653
- error: {
6654
- kind: "invalid-color",
6655
- role,
6656
- value: entry.hex
6657
- }
6658
- };
6659
- values[role] = hex;
6660
- }
6661
- const missing = PALETTE_TOKEN_NAMES.filter((role) => values[role] === void 0);
6662
- if (missing.length > 0) return {
6663
- ok: false,
6664
- error: {
6665
- kind: "missing-roles",
6666
- roles: missing
6667
- }
6668
- };
6669
- return {
6670
- ok: true,
6671
- colors: paletteColorsSchema.parse(values)
6672
- };
6673
- }
6674
- function requirePaletteColorsFromEntries(entries) {
6675
- const result = paletteColorsFromEntries(entries);
6676
- if (result.ok) return result.colors;
6677
- throw new Error(`Invalid complete palette: ${result.error.kind}`);
6678
- }
6679
- //#endregion
6680
6686
  //#region ../theme-core/src/site-styles/styleTags.ts
6681
6687
  /**
6682
6688
  * Descriptive style-axis metadata for button personalities and palette
@@ -6950,6 +6956,71 @@ const brandLedJewel = defineCuratedPalette({
6950
6956
  }
6951
6957
  ]
6952
6958
  });
6959
+ const brandLedSpruce = defineCuratedPalette({
6960
+ id: "brand-led-spruce",
6961
+ name: "Brand-led — Spruce",
6962
+ description: "Spruce-ink primary and moss secondary with an amber accent over a green-mist ground and hairline sage borders.",
6963
+ tags: {
6964
+ tone: "classic",
6965
+ energy: "calm",
6966
+ era: "editorial"
6967
+ },
6968
+ mode: "light",
6969
+ colors: [
6970
+ {
6971
+ name: "primary",
6972
+ hex: "#14312b"
6973
+ },
6974
+ {
6975
+ name: "secondary",
6976
+ hex: "#356a59"
6977
+ },
6978
+ {
6979
+ name: "accent",
6980
+ hex: "#f08a4b"
6981
+ },
6982
+ {
6983
+ name: "success",
6984
+ hex: "#356a59"
6985
+ },
6986
+ {
6987
+ name: "warning",
6988
+ hex: "#f08a4b"
6989
+ },
6990
+ {
6991
+ name: "destructive",
6992
+ hex: "#b5561f"
6993
+ },
6994
+ {
6995
+ name: "background",
6996
+ hex: "#ecefe9"
6997
+ },
6998
+ {
6999
+ name: "surface",
7000
+ hex: "#ffffff"
7001
+ },
7002
+ {
7003
+ name: "surfaceAlt",
7004
+ hex: "#f4f6f1"
7005
+ },
7006
+ {
7007
+ name: "muted",
7008
+ hex: "#5c6a64"
7009
+ },
7010
+ {
7011
+ name: "text",
7012
+ hex: "#1c2a26"
7013
+ },
7014
+ {
7015
+ name: "primaryForeground",
7016
+ hex: "#f4f7f1"
7017
+ },
7018
+ {
7019
+ name: "border",
7020
+ hex: "#d7ddd2"
7021
+ }
7022
+ ]
7023
+ });
6953
7024
  const brandLedBurgundyGold = defineCuratedPalette({
6954
7025
  id: "brand-led-burgundy-gold",
6955
7026
  name: "Brand-led — Burgundy Gold",
@@ -7489,6 +7560,7 @@ const curatedPalettes = [
7489
7560
  brandLedWarm,
7490
7561
  brandLedJewel,
7491
7562
  brandLedBurgundyGold,
7563
+ brandLedSpruce,
7492
7564
  defineCuratedPalette({
7493
7565
  id: "warm-neutral-clay",
7494
7566
  name: "Warm Neutral — Clay",
@@ -8540,6 +8612,10 @@ const buttonBorderSchema = z$1.object({
8540
8612
  widthClass: z$1.string(),
8541
8613
  colorToken: themeColorTokenSchema
8542
8614
  });
8615
+ const buttonVariantAffordanceSchema = z$1.object({
8616
+ iconId: z$1.string().min(1),
8617
+ colorToken: themeColorTokenSchema.optional()
8618
+ });
8543
8619
  const variantShadowSchema = z$1.object({
8544
8620
  elevation: z$1.enum([
8545
8621
  "none",
@@ -8593,7 +8669,8 @@ const buttonVariantSchema = z$1.object({
8593
8669
  border: buttonBorderSchema.optional(),
8594
8670
  hoverBackgroundToken: z$1.string().optional(),
8595
8671
  effects: variantEffectsSchema.optional(),
8596
- sizes: variantSizeOverridesSchema.optional()
8672
+ sizes: variantSizeOverridesSchema.optional(),
8673
+ affordance: buttonVariantAffordanceSchema.optional()
8597
8674
  });
8598
8675
  const buttonSystemSchema = z$1.object({
8599
8676
  global: buttonGlobalSettingsSchema,
@@ -9137,6 +9214,136 @@ const editorialLink = defineButtonPersonality({
9137
9214
  }
9138
9215
  });
9139
9216
  //#endregion
9217
+ //#region ../theme-core/src/buttons/personalities/flat-ink.ts
9218
+ /**
9219
+ * Flat Ink — a solid ink primary, a hairline ghost secondary, and a single
9220
+ * amber accent climax, all completely flat (no drop shadow) with a modest
9221
+ * radius. Reads as grounded, editorial and clinician-precise: the Fulcrum
9222
+ * physiotherapy voice. The primary shifts to the secondary (moss) on hover and
9223
+ * every variant lifts 2px; the amber accent is meant to be used sparingly as
9224
+ * the page's one conversion climax.
9225
+ */
9226
+ const flatInk = defineButtonPersonality({
9227
+ id: "flat-ink",
9228
+ name: "Flat Ink",
9229
+ description: "Flat solid-ink primary, hairline ghost secondary, and a single amber accent climax — modest radius, no shadow.",
9230
+ tags: {
9231
+ tone: "classic",
9232
+ energy: "calm",
9233
+ motion: "subtle",
9234
+ era: "editorial"
9235
+ },
9236
+ buttonSystem: {
9237
+ global: {
9238
+ cornerStyle: "rounded",
9239
+ shadow: "low",
9240
+ textTransform: "none",
9241
+ fontWeight: 600,
9242
+ borderWidth: 1,
9243
+ typography: "body",
9244
+ letterSpacing: "tight",
9245
+ paddingPreset: "default",
9246
+ hoverTransform: "lift",
9247
+ hoverColor: "none"
9248
+ },
9249
+ sizes: {
9250
+ sm: {
9251
+ padding: "0.625rem 1.125rem",
9252
+ fontSize: "text-sm"
9253
+ },
9254
+ md: { padding: "0.8125rem 1.375rem" },
9255
+ lg: {
9256
+ padding: "0.9375rem 1.625rem",
9257
+ fontSize: "text-lg"
9258
+ },
9259
+ xl: {
9260
+ padding: "1.0625rem 2rem",
9261
+ fontSize: "text-xl"
9262
+ }
9263
+ },
9264
+ variants: [
9265
+ {
9266
+ id: "primary",
9267
+ name: "Primary",
9268
+ enabled: true,
9269
+ priority: 1,
9270
+ background: {
9271
+ type: "solid",
9272
+ colorToken: "primary"
9273
+ },
9274
+ textColorToken: "primaryForeground",
9275
+ borderRadius: "rounded-lg",
9276
+ affordance: {
9277
+ iconId: "ArrowRight",
9278
+ colorToken: "accent"
9279
+ },
9280
+ shadow: {
9281
+ elevation: "none",
9282
+ softness: null,
9283
+ position: "bottom"
9284
+ },
9285
+ effects: { hover: [{
9286
+ effectId: "lift",
9287
+ options: { translateY: "-2px" }
9288
+ }, {
9289
+ effectId: "background-on-hover",
9290
+ options: { colorToken: "secondary" }
9291
+ }] }
9292
+ },
9293
+ {
9294
+ id: "secondary",
9295
+ name: "Secondary",
9296
+ enabled: true,
9297
+ priority: 2,
9298
+ background: { type: "transparent" },
9299
+ textColorToken: "primary",
9300
+ borderRadius: "rounded-lg",
9301
+ border: {
9302
+ widthClass: "border",
9303
+ colorToken: "border"
9304
+ },
9305
+ shadow: {
9306
+ elevation: "none",
9307
+ softness: null,
9308
+ position: "bottom"
9309
+ },
9310
+ effects: { hover: [{
9311
+ effectId: "lift",
9312
+ options: { translateY: "-2px" }
9313
+ }, {
9314
+ effectId: "background-on-hover",
9315
+ options: { colorToken: "surfaceAlt" }
9316
+ }] }
9317
+ },
9318
+ {
9319
+ id: "accent",
9320
+ name: "Accent",
9321
+ enabled: true,
9322
+ priority: 3,
9323
+ background: {
9324
+ type: "solid",
9325
+ colorToken: "accent"
9326
+ },
9327
+ textColorToken: "accentForeground",
9328
+ borderRadius: "rounded-lg",
9329
+ shadow: {
9330
+ elevation: "none",
9331
+ softness: null,
9332
+ position: "bottom"
9333
+ },
9334
+ affordance: {
9335
+ iconId: "ArrowRight",
9336
+ colorToken: "accentForeground"
9337
+ },
9338
+ effects: { hover: [{
9339
+ effectId: "lift",
9340
+ options: { translateY: "-2px" }
9341
+ }] }
9342
+ }
9343
+ ]
9344
+ }
9345
+ });
9346
+ //#endregion
9140
9347
  //#region ../theme-core/src/buttons/personalities/ink-stamp.ts
9141
9348
  /**
9142
9349
  * Ink Stamp — solid earthy fills with a bordered, hand-stamped block-print
@@ -9907,6 +10114,7 @@ const buttonPersonalities = [
9907
10114
  editorialLink,
9908
10115
  pebble,
9909
10116
  inkStamp,
10117
+ flatInk,
9910
10118
  brushedWash,
9911
10119
  showtimePill,
9912
10120
  joyStamp,
@@ -10837,6 +11045,13 @@ const legacyHeaderLookAliases = defineExactCatalog()([
10837
11045
  styleId: asHeaderStyleId("minimal")
10838
11046
  }
10839
11047
  },
11048
+ {
11049
+ lookId: asHeaderLookId("classic-centered-nav"),
11050
+ selection: {
11051
+ layoutId: asHeaderLayoutId("classic"),
11052
+ styleId: asHeaderStyleId("fine-line")
11053
+ }
11054
+ },
10840
11055
  {
10841
11056
  lookId: asHeaderLookId("brand-solid"),
10842
11057
  selection: {
@@ -10907,6 +11122,50 @@ function getCompatibleHeaderStyleIds(layoutId) {
10907
11122
  return isHeaderStyleCompatibleWithLayout(getHeaderStyleOrThrow(styleId), layoutId);
10908
11123
  });
10909
11124
  }
11125
+ function applyNavTypography(style, typography) {
11126
+ switch (typography) {
11127
+ case "plain": return style;
11128
+ case "heading": return {
11129
+ ...style,
11130
+ typography: "heading",
11131
+ fontWeight: "medium",
11132
+ letterSpacing: "wide"
11133
+ };
11134
+ case "quiet-caps": return {
11135
+ ...style,
11136
+ textTransform: "uppercase",
11137
+ textSize: "sm",
11138
+ letterSpacing: "wide"
11139
+ };
11140
+ case "brand-caps": return {
11141
+ ...style,
11142
+ textTransform: "uppercase",
11143
+ textSize: "sm",
11144
+ letterSpacing: "wider"
11145
+ };
11146
+ default: return assertNever$19(typography, "Unhandled header nav typography");
11147
+ }
11148
+ }
11149
+ function underlineNavLinkStyle(colorToken, underlineColorToken, typography = "plain") {
11150
+ return applyNavTypography({
11151
+ name: "Underline grow",
11152
+ typography: "body",
11153
+ fontWeight: "medium",
11154
+ textTransform: "none",
11155
+ italic: false,
11156
+ colorToken,
11157
+ padding: "compact",
11158
+ paddingX: "compact",
11159
+ borderRadius: "none",
11160
+ effects: { hover: [{
11161
+ effectId: "nav-underline",
11162
+ options: {
11163
+ style: "grow",
11164
+ ...underlineColorToken ? { colorToken: underlineColorToken } : {}
11165
+ }
11166
+ }] }
11167
+ }, typography);
11168
+ }
10910
11169
  function themedHeaderShadow(theme) {
10911
11170
  switch (theme.shadow.elevation) {
10912
11171
  case "none": return;
@@ -11006,6 +11265,38 @@ function cleanBaseHeader(theme) {
11006
11265
  ctaGap: "default"
11007
11266
  };
11008
11267
  }
11268
+ function classicCenteredNavHeader(theme) {
11269
+ return {
11270
+ ...baseHeader(theme),
11271
+ variant: "classic",
11272
+ navPlacement: "center",
11273
+ positioning: "sticky",
11274
+ shrinkOnScroll: false,
11275
+ background: {
11276
+ type: "color",
11277
+ color: "surface"
11278
+ },
11279
+ textColor: "text",
11280
+ navColor: "text",
11281
+ navStyle: "underline-grow",
11282
+ navWeight: "medium",
11283
+ navLinkStyle: underlineNavLinkStyle("text", "accent"),
11284
+ border: {
11285
+ style: "solid",
11286
+ width: "thin",
11287
+ position: "bottom",
11288
+ colorToken: "border"
11289
+ },
11290
+ ctaTreatment: "primary",
11291
+ ctaVariant: resolveHeaderCtaVariant(theme, "primary"),
11292
+ ctaGap: "default",
11293
+ logoText: {
11294
+ mobileMaxWidth: "sm",
11295
+ mobileWrapLines: 1,
11296
+ hideOnShrink: false
11297
+ }
11298
+ };
11299
+ }
11009
11300
  function serifLineHeader(theme) {
11010
11301
  return {
11011
11302
  ...baseHeader(theme),
@@ -11170,6 +11461,21 @@ const headerLookCatalog = defineExactCatalog()([
11170
11461
  logoAccommodations: inlineClassicLogoAccommodations,
11171
11462
  compile: cleanBaseHeader
11172
11463
  },
11464
+ {
11465
+ id: asHeaderLookId("classic-centered-nav"),
11466
+ label: "Centered nav",
11467
+ visualTreatment: "solid-neutral",
11468
+ layoutBehavior: "sticky",
11469
+ configuration: {
11470
+ structure: "inline-classic",
11471
+ treatment: "solid-neutral",
11472
+ behavior: "sticky"
11473
+ },
11474
+ openingRequirement: "none",
11475
+ maxRecommendedNavItems: 6,
11476
+ logoAccommodations: inlineClassicLogoAccommodations,
11477
+ compile: classicCenteredNavHeader
11478
+ },
11173
11479
  {
11174
11480
  id: asHeaderLookId("brand-solid"),
11175
11481
  label: "Brand solid",
@@ -12332,6 +12638,74 @@ const typographyPresets = [
12332
12638
  }
12333
12639
  }
12334
12640
  }
12641
+ }),
12642
+ defineTypographyPreset({
12643
+ id: "typography-preset:fulcrum",
12644
+ name: "Fulcrum",
12645
+ description: "Newsreader serif headings over an IBM Plex Sans body, with an IBM Plex Mono eyebrow role for uppercase kickers and mono captions.",
12646
+ defaultFonts: {
12647
+ body: "IBM Plex Sans, system-ui, -apple-system, sans-serif",
12648
+ heading: "Newsreader, Georgia, \"Times New Roman\", serif"
12649
+ },
12650
+ recommendedFonts: {
12651
+ body: [
12652
+ "IBM Plex Sans",
12653
+ "Source Sans 3",
12654
+ "Inter"
12655
+ ],
12656
+ heading: [
12657
+ "Newsreader",
12658
+ "Source Serif 4",
12659
+ "Lora"
12660
+ ]
12661
+ },
12662
+ slotPolicies: {
12663
+ body: bodyReadablePolicy,
12664
+ heading: displayHeadingPolicy
12665
+ },
12666
+ typography: {
12667
+ body: {
12668
+ family: "IBM Plex Sans, system-ui, -apple-system, sans-serif",
12669
+ size: "lg",
12670
+ weight: "regular",
12671
+ lineHeight: "relaxed",
12672
+ letterSpacing: "normal"
12673
+ },
12674
+ headings: {
12675
+ default: {
12676
+ family: "Newsreader, Georgia, \"Times New Roman\", serif",
12677
+ weight: "medium",
12678
+ lineHeight: "normal",
12679
+ letterSpacing: "tight",
12680
+ colorToken: "primary",
12681
+ italic: null,
12682
+ case: null
12683
+ },
12684
+ h1: {
12685
+ size: "2xl",
12686
+ weight: "medium",
12687
+ lineHeight: "normal",
12688
+ letterSpacing: "tight",
12689
+ italic: null
12690
+ },
12691
+ h2: {
12692
+ size: "xl",
12693
+ weight: "medium",
12694
+ lineHeight: "normal",
12695
+ letterSpacing: "tight",
12696
+ italic: null
12697
+ },
12698
+ h3: {
12699
+ size: "md",
12700
+ weight: "semibold",
12701
+ lineHeight: "normal",
12702
+ letterSpacing: "normal",
12703
+ italic: null
12704
+ }
12705
+ },
12706
+ scale: "spacious",
12707
+ eyebrow: { family: "IBM Plex Mono, ui-monospace, \"SF Mono\", monospace" }
12708
+ }
12335
12709
  })
12336
12710
  ];
12337
12711
  new Map(typographyPresets.map((preset) => [preset.id, preset]));
@@ -14075,7 +14449,53 @@ function generateDefaultButtonSystem(theme) {
14075
14449
  };
14076
14450
  }
14077
14451
  //#endregion
14452
+ //#region ../theme-core/src/buttons/affordanceIcons.ts
14453
+ /**
14454
+ * Affordance icon path data for CSS-emitted button affordances.
14455
+ *
14456
+ * A button variant may declare a trailing `affordance` icon (see
14457
+ * `ButtonVariantAffordance`). The generated button CSS renders that icon as a
14458
+ * masked `::after` pseudo-element, so the personality supplies it to every
14459
+ * button of the variant without per-node authoring.
14460
+ *
14461
+ * Icons live in the `@riverbankcms/blocks` registry, but the button CSS is
14462
+ * generated here in theme-core, so this module carries a small, curated copy of
14463
+ * the *affordance-eligible* icon path data (arrows/chevrons — the action-oriented
14464
+ * subset). The `d` values below are duplicated verbatim from the matching entries
14465
+ * in `blocks/src/system/icons/svg.tsx`; ids match the registry ids exactly so a
14466
+ * node-level `trailingIcon` and a personality `affordance` share one id space.
14467
+ *
14468
+ * Keep this set intentionally tiny. It is not the full CTA icon set — only the
14469
+ * shapes that read as a trailing affordance.
14470
+ */
14471
+ /** Registry icon id → the SVG path `d` fragments composing that icon. */
14472
+ const AFFORDANCE_ICON_PATHS = {
14473
+ ArrowRight: ["M5 12h14", "m13 6 6 6-6 6"],
14474
+ ArrowLeft: ["M19 12H5", "m11 6-6 6 6 6"],
14475
+ ChevronRight: ["m9 18 6-6-6-6"],
14476
+ ChevronLeft: ["m15 18-6-6 6-6"],
14477
+ ChevronDown: ["m6 9 6 6 6-6"],
14478
+ ArrowUpRight: ["M7 17 17 7", "M7 7h10v10"]
14479
+ };
14480
+ /**
14481
+ * Build a `data:image/svg+xml,...` URL for an affordance icon, suitable for use
14482
+ * as a CSS `mask-image`. The stroke is painted solid black so the mask's alpha
14483
+ * traces the icon outline; the visible colour is supplied separately by the
14484
+ * `::after` rule's `background-color`. Returns `null` for unknown ids.
14485
+ */
14486
+ function affordanceIconMaskUrl(iconId) {
14487
+ const paths = AFFORDANCE_ICON_PATHS[iconId];
14488
+ if (!paths || paths.length === 0) return null;
14489
+ 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, " ")}`;
14490
+ }
14491
+ //#endregion
14078
14492
  //#region ../theme-core/src/buttons/generateButtonCss.ts
14493
+ /**
14494
+ * Class the `button` runtime node adds when it renders its own trailing icon,
14495
+ * so a node-level icon suppresses the personality's CSS `::after` affordance
14496
+ * (node overrides personality). Shared so generator and renderer can't drift.
14497
+ */
14498
+ const AFFORDANCE_SUPPRESS_CLASS = "rb-affordance-off";
14079
14499
  const BUTTON_SIZE_NAMES = buttonSizeNameSchema.options;
14080
14500
  /**
14081
14501
  * Letter spacing token → CSS value.
@@ -14170,10 +14590,40 @@ function variantChunks(variant, global, themeSizes, themeId, tokens, theme) {
14170
14590
  out.push(variantFocusVisibleRule(variantId, themeId));
14171
14591
  const effects = variantEffectsCss(variant, themeId, tokens, theme);
14172
14592
  if (effects) out.push({ raw: effects });
14593
+ const affordance = variantAffordanceRule(variant, selector, tokens);
14594
+ if (affordance) out.push(affordance);
14173
14595
  out.push(...variantDisabledRules(variantId, themeId));
14174
14596
  out.push(...variantSizeRules(variant, variantId, themeSizes, themeId));
14175
14597
  return out;
14176
14598
  }
14599
+ /**
14600
+ * Emit the optional trailing affordance icon as a masked `::after`.
14601
+ *
14602
+ * Only variants that declare `affordance` with a known icon id produce a rule —
14603
+ * every other variant is byte-identical to before. The `:not(.rb-affordance-off)`
14604
+ * guard lets a `button` node that renders its own trailing icon suppress this
14605
+ * one (node overrides personality). Colour comes from the affordance token, or
14606
+ * `currentColor` (the button text colour) when no token is given.
14607
+ */
14608
+ function variantAffordanceRule(variant, selector, tokens) {
14609
+ const affordance = variant.affordance;
14610
+ if (!affordance) return null;
14611
+ const maskUrl = affordanceIconMaskUrl(affordance.iconId);
14612
+ if (!maskUrl) return null;
14613
+ const color = affordance.colorToken ? tokens.getColor(affordance.colorToken) : "currentColor";
14614
+ const mask = `url("${maskUrl}") no-repeat center / contain`;
14615
+ return rule(`${selector}:not(.${AFFORDANCE_SUPPRESS_CLASS})::after`, [
14616
+ ["content", "\"\""],
14617
+ ["display", "inline-block"],
14618
+ ["width", "1em"],
14619
+ ["height", "1em"],
14620
+ ["margin-inline-start", "0.5em"],
14621
+ ["flex", "0 0 auto"],
14622
+ ["background-color", color],
14623
+ ["-webkit-mask", mask],
14624
+ ["mask", mask]
14625
+ ]);
14626
+ }
14177
14627
  function parseButtonVariantIdForCss(value) {
14178
14628
  const variantId = parseThemeButtonVariantId(value);
14179
14629
  if (!variantId) throw new Error("Button variant id must be a non-empty string");
@@ -14555,18 +15005,65 @@ function generateDefaultCardSystem(theme) {
14555
15005
  return { variants: [defaultVariant, alternativeVariant] };
14556
15006
  }
14557
15007
  //#endregion
14558
- //#region ../theme-core/src/status/contextualColors.ts
14559
- const STATUS_MUTED_COLOR_VAR = "--rb-status-muted-color";
14560
- const DEFAULT_STATUS_MUTED_COLOR = "rgb(var(--tb-mutedText))";
14561
- const SURFACE_MUTED_STATUS_COLOR = "color-mix(in oklab, currentColor, transparent 18%)";
14562
- function statusMutedColorValue() {
14563
- return `var(${STATUS_MUTED_COLOR_VAR}, ${DEFAULT_STATUS_MUTED_COLOR})`;
15008
+ //#region ../theme-core/src/surfaces/foregroundRoles.ts
15009
+ const SURFACE_FOREGROUND_ROLES = [
15010
+ "text",
15011
+ "heading",
15012
+ "link",
15013
+ "supporting"
15014
+ ];
15015
+ //#endregion
15016
+ //#region ../theme-core/src/surfaces/foregroundContract.ts
15017
+ const SURFACE_FOREGROUND_CSS_VARS = {
15018
+ text: "--rb-surface-text-color",
15019
+ heading: "--rb-surface-heading-color",
15020
+ link: "--rb-surface-link-color",
15021
+ supporting: "--rb-surface-supporting-color"
15022
+ };
15023
+ const DEFAULT_SURFACE_TEXT_COLOR = "rgb(var(--tb-text, 17 24 39))";
15024
+ const DEFAULT_ROLE_FALLBACKS = {
15025
+ text: DEFAULT_SURFACE_TEXT_COLOR,
15026
+ heading: DEFAULT_SURFACE_TEXT_COLOR,
15027
+ link: "rgb(var(--tb-primary, var(--tb-text, 17 24 39)))",
15028
+ supporting: "rgb(var(--tb-mutedText, 100 116 139))"
15029
+ };
15030
+ function requireSurfaceColorValue(role, value) {
15031
+ const normalized = value.trim();
15032
+ if (normalized.length === 0) throw new RangeError(`Surface foreground ${role} must be a non-empty CSS color.`);
15033
+ return normalized;
14564
15034
  }
14565
- function statusMutedColorDeclaration() {
14566
- return `color: ${statusMutedColorValue()};`;
15035
+ function completeSurfaceForegrounds(input) {
15036
+ const text = requireSurfaceColorValue("text", input.text);
15037
+ return {
15038
+ text,
15039
+ heading: input.heading === void 0 ? text : requireSurfaceColorValue("heading", input.heading),
15040
+ link: input.link === void 0 ? text : requireSurfaceColorValue("link", input.link),
15041
+ supporting: input.supporting === void 0 ? text : requireSurfaceColorValue("supporting", input.supporting)
15042
+ };
15043
+ }
15044
+ function surfaceForegroundValue(role, outsideProviderFallback = DEFAULT_ROLE_FALLBACKS[role]) {
15045
+ const roleVariable = SURFACE_FOREGROUND_CSS_VARS[role];
15046
+ const fallback = requireSurfaceColorValue("fallback", outsideProviderFallback);
15047
+ if (role === "text") return `var(${roleVariable}, ${fallback})`;
15048
+ return `var(${roleVariable}, var(${SURFACE_FOREGROUND_CSS_VARS.text}, ${fallback}))`;
15049
+ }
15050
+ function surfaceForegroundValueWithOpacity(role, opacityPercent) {
15051
+ if (!Number.isFinite(opacityPercent) || opacityPercent < 0 || opacityPercent > 100) throw new RangeError("Surface foreground opacity must be between 0 and 100.");
15052
+ return `color-mix(in srgb, ${surfaceForegroundValue(role)} ${opacityPercent}%, transparent)`;
14567
15053
  }
14568
- function contextualMutedStatusColorDeclaration(value) {
14569
- return `${STATUS_MUTED_COLOR_VAR}: ${value}`;
15054
+ function surfaceForegroundStyle(input) {
15055
+ const foregrounds = completeSurfaceForegrounds(input);
15056
+ return {
15057
+ color: foregrounds.text,
15058
+ [SURFACE_FOREGROUND_CSS_VARS.text]: foregrounds.text,
15059
+ [SURFACE_FOREGROUND_CSS_VARS.heading]: foregrounds.heading,
15060
+ [SURFACE_FOREGROUND_CSS_VARS.link]: foregrounds.link,
15061
+ [SURFACE_FOREGROUND_CSS_VARS.supporting]: foregrounds.supporting
15062
+ };
15063
+ }
15064
+ function surfaceForegroundDeclarations(input) {
15065
+ const foregrounds = completeSurfaceForegrounds(input);
15066
+ return [`color: ${foregrounds.text}`, ...SURFACE_FOREGROUND_ROLES.map((role) => `${SURFACE_FOREGROUND_CSS_VARS[role]}: ${foregrounds[role]}`)];
14570
15067
  }
14571
15068
  //#endregion
14572
15069
  //#region ../theme-core/src/cards/generateCardCss.ts
@@ -14653,8 +15150,10 @@ function generateVariantCSS(variant, themeId, tokens, theme) {
14653
15150
  const direction = GRADIENT_DIRECTION_MAP$1[variant.background.direction] || "to right";
14654
15151
  rules.push(`background: linear-gradient(${direction}, ${fromColor}, ${toColor})`);
14655
15152
  } else rules.push(`background: transparent`);
14656
- if (variant.foregroundToken) rules.push(`color: ${tokens.getColor(variant.foregroundToken)}`);
14657
- rules.push(contextualMutedStatusColorDeclaration(SURFACE_MUTED_STATUS_COLOR));
15153
+ if (variant.background.type !== "transparent") {
15154
+ const foreground = tokens.getColor(variant.foregroundToken ?? "text");
15155
+ rules.push(...surfaceForegroundDeclarations({ text: foreground }));
15156
+ }
14658
15157
  if (variant.accentEdge) {
14659
15158
  const side = ACCENT_EDGE_SIDE_MAP$1[variant.accentEdge.edge];
14660
15159
  const width = ACCENT_EDGE_WIDTH_MAP$1[variant.accentEdge.width];
@@ -15015,11 +15514,31 @@ const gradientStopSchema = z$1.object({
15015
15514
  position: z$1.number().min(0).max(100)
15016
15515
  });
15017
15516
  /**
15018
- * Gradient configuration
15517
+ * Radial gradient shape. Optional; defaults to `circle` (the historical shape)
15518
+ * so existing radial gradients are byte-identical.
15519
+ *
15520
+ * Named `RadialGradientShape` to avoid clashing with `styles/gradients.ts`'s
15521
+ * `GradientShape` (which names the gradient *kind*: linear/radial/conic).
15522
+ */
15523
+ const radialGradientShapeSchema = z$1.enum(["circle", "ellipse"]);
15524
+ /**
15525
+ * Gradient configuration.
15526
+ *
15527
+ * `type` is optional and defaults to `'linear'` so gradients persisted without a
15528
+ * type (or with an explicit `'linear'`) render exactly as before. `'radial'` and
15529
+ * `'conic'` are additive kinds:
15530
+ * - linear: `angle` (degrees)
15531
+ * - radial: optional `shape` (circle/ellipse) + `position`
15532
+ * - conic: optional `angle` (from-angle, degrees) + `position`
15019
15533
  */
15020
15534
  const gradientConfigSchema = z$1.object({
15021
- type: z$1.enum(["linear", "radial"]),
15535
+ type: z$1.enum([
15536
+ "linear",
15537
+ "radial",
15538
+ "conic"
15539
+ ]).default("linear"),
15022
15540
  angle: z$1.number().min(0).max(360).optional(),
15541
+ shape: radialGradientShapeSchema.optional(),
15023
15542
  position: z$1.string().optional(),
15024
15543
  stops: z$1.array(gradientStopSchema).min(2)
15025
15544
  });
@@ -15558,9 +16077,17 @@ function parseToken(source) {
15558
16077
  if (parts.kind === "invalid") return { token: source };
15559
16078
  return {
15560
16079
  token: parts.token,
15561
- alpha: parts.opacityPercent === null ? void 0 : parts.opacityPercent / 100
16080
+ opacityPercent: parts.opacityPercent === null ? void 0 : parts.opacityPercent
15562
16081
  };
15563
16082
  }
16083
+ const SURFACE_TEXT_ROLE_BY_TOKEN = {
16084
+ text: "text",
16085
+ heading: "heading",
16086
+ mutedText: "supporting"
16087
+ };
16088
+ function surfaceRoleForTextToken(token) {
16089
+ return Object.prototype.hasOwnProperty.call(SURFACE_TEXT_ROLE_BY_TOKEN, token) ? SURFACE_TEXT_ROLE_BY_TOKEN[token] : null;
16090
+ }
15564
16091
  /**
15565
16092
  * Converts a theme color token to a CSS rgb value.
15566
16093
  * This is the primary function for applying theme colors to CSS properties.
@@ -15574,11 +16101,26 @@ function parseToken(source) {
15574
16101
  * rgbColorValue('raw:transparent') // => 'transparent'
15575
16102
  */
15576
16103
  function rgbColorValue(token) {
15577
- const { token: baseToken, alpha, raw } = parseToken(token);
16104
+ const { token: baseToken, opacityPercent, raw } = parseToken(token);
15578
16105
  if (raw) return raw;
15579
16106
  const cssVar = `${COLOR_VAR_PREFIX}${baseToken}`;
15580
- if (alpha === void 0) return `rgb(var(${cssVar}))`;
15581
- return `rgb(var(${cssVar}) / ${alpha})`;
16107
+ if (opacityPercent === void 0) return `rgb(var(${cssVar}))`;
16108
+ return `rgb(var(${cssVar}) / ${opacityPercent / 100})`;
16109
+ }
16110
+ /**
16111
+ * Converts a theme color token to a CSS variable reference.
16112
+ * Note: This returns the base variable without rgb() wrapper, ignoring opacity.
16113
+ *
16114
+ * @param token - Theme color token
16115
+ * @returns CSS variable reference
16116
+ *
16117
+ * @example
16118
+ * cssVarValue('primary') // => 'var(--tb-primary)'
16119
+ * cssVarValue('surface/95') // => 'var(--tb-surface)' (opacity ignored)
16120
+ */
16121
+ function cssVarValue(token) {
16122
+ const { token: baseToken } = parseToken(token);
16123
+ return `var(${COLOR_VAR_PREFIX}${baseToken})`;
15582
16124
  }
15583
16125
  /**
15584
16126
  * Creates a backgroundColor style object from a theme token.
@@ -15596,6 +16138,9 @@ function backgroundColorStyle(token) {
15596
16138
  * @returns CSSProperties with color
15597
16139
  */
15598
16140
  function textColorStyle(token) {
16141
+ const parsed = parseToken(token);
16142
+ const surfaceRole = parsed.raw ? null : surfaceRoleForTextToken(parsed.token);
16143
+ if (surfaceRole) return { color: parsed.opacityPercent === void 0 ? surfaceForegroundValue(surfaceRole) : surfaceForegroundValueWithOpacity(surfaceRole, parsed.opacityPercent) };
15599
16144
  return { color: rgbColorValue(token) };
15600
16145
  }
15601
16146
  /**
@@ -16002,6 +16547,7 @@ const typographyScaleSchema = z$1.enum([
16002
16547
  "majorThird",
16003
16548
  "perfectFourth"
16004
16549
  ]);
16550
+ const eyebrowTypographyStyleSchema = z$1.object({ family: z$1.string().min(1) });
16005
16551
  const typographySchema = z$1.object({
16006
16552
  body: bodyTypographyStyleSchema,
16007
16553
  headings: z$1.object({
@@ -16014,7 +16560,8 @@ const typographySchema = z$1.object({
16014
16560
  h6: headingTypographyOverridesSchema.optional()
16015
16561
  }),
16016
16562
  scale: typographyScaleSchema,
16017
- proseLinkStyle: proseLinkStyleSchema.optional()
16563
+ proseLinkStyle: proseLinkStyleSchema.optional(),
16564
+ eyebrow: eyebrowTypographyStyleSchema.optional()
16018
16565
  });
16019
16566
  const spaceSchema = z$1.enum([
16020
16567
  "comfortable",
@@ -16039,7 +16586,8 @@ const shadow = z$1.object({
16039
16586
  "soft",
16040
16587
  "hard"
16041
16588
  ]).nullable(),
16042
- position: z$1.enum(["bottom", "bottom-right"]).default("bottom")
16589
+ position: z$1.enum(["bottom", "bottom-right"]).default("bottom"),
16590
+ colorToken: themeColorTokenSchema.optional()
16043
16591
  });
16044
16592
  const border = z$1.object({
16045
16593
  width: z$1.enum([
@@ -16284,6 +16832,7 @@ const headerSchema = z$1.object({
16284
16832
  navWeight: navFontWeight.default("medium"),
16285
16833
  navTypography: z$1.enum(["body", "heading"]).optional(),
16286
16834
  navTextSize: navLinkStyleSchema.shape.textSize,
16835
+ navPlacement: z$1.enum(["end", "center"]).optional(),
16287
16836
  container: headerContainerSchema,
16288
16837
  border: headerBorderSchema,
16289
16838
  shadow: componentShadowSchema.optional(),
@@ -16775,7 +17324,8 @@ const curatedSiteStyleIdValues = [
16775
17324
  "site-style:bold-launch",
16776
17325
  "site-style:south-west-twerk-school",
16777
17326
  "site-style:kirtan-ornate",
16778
- "site-style:original-joy"
17327
+ "site-style:original-joy",
17328
+ "site-style:fulcrum"
16779
17329
  ];
16780
17330
  const quietBudget = {
16781
17331
  accentDensity: "low",
@@ -18229,6 +18779,104 @@ const curatedSiteStyles = [
18229
18779
  buttonChoice("showtime-pill")
18230
18780
  ],
18231
18781
  paletteRecommendations: [paletteRecommendation("warm-dark-ember")]
18782
+ }),
18783
+ defineCuratedSiteStyle({
18784
+ id: "site-style:fulcrum",
18785
+ name: "Fulcrum",
18786
+ headingDecoration: {
18787
+ family: "short-center-rule",
18788
+ colorToken: "accent"
18789
+ },
18790
+ 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.",
18791
+ 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.",
18792
+ siteCreatorSelection: {
18793
+ kind: "site_creator_keyword_heuristic",
18794
+ priority: 28
18795
+ },
18796
+ selectionKeywordRules: [
18797
+ {
18798
+ kind: "substring",
18799
+ value: "physiotherap"
18800
+ },
18801
+ {
18802
+ kind: "word",
18803
+ value: "physio"
18804
+ },
18805
+ {
18806
+ kind: "phrase",
18807
+ value: "sports injury"
18808
+ },
18809
+ {
18810
+ kind: "phrase",
18811
+ value: "rehab clinic"
18812
+ },
18813
+ {
18814
+ kind: "substring",
18815
+ value: "musculoskeletal"
18816
+ },
18817
+ {
18818
+ kind: "phrase",
18819
+ value: "sports therapy"
18820
+ }
18821
+ ],
18822
+ tokenRecipes: {
18823
+ palette: "brand-led",
18824
+ contrast: "standard",
18825
+ radius: "rounded",
18826
+ shadow: "subtle",
18827
+ typography: "serif-editorial",
18828
+ spacing: "airy",
18829
+ motion: "subtle"
18830
+ },
18831
+ compositionBudget: quietBudget,
18832
+ typographyPresetChoices: [
18833
+ typographyChoice("typography-preset:fulcrum"),
18834
+ typographyChoice("typography-preset:editorial-serif"),
18835
+ typographyChoice("typography-preset:modern-sans")
18836
+ ],
18837
+ defaultTypographyPresetId: "typography-preset:fulcrum",
18838
+ themeOverrides: {
18839
+ shadow: { colorToken: "primary" },
18840
+ accordions: {
18841
+ background: null,
18842
+ spacing: "comfortable",
18843
+ corners: "square",
18844
+ border: null,
18845
+ shadow: null,
18846
+ icon: {
18847
+ style: "plus-minus",
18848
+ position: "right",
18849
+ size: "md",
18850
+ colorToken: "accent"
18851
+ },
18852
+ trigger: {
18853
+ textColorToken: "primary",
18854
+ fontWeight: "medium",
18855
+ fontFamily: "heading",
18856
+ fontScale: "md"
18857
+ },
18858
+ hover: { textColorToken: "secondary" },
18859
+ content: { textColorToken: "muted" }
18860
+ },
18861
+ testimonialQuote: serifDisplayQuote
18862
+ },
18863
+ recommendedHeaderLookIds: [
18864
+ "classic-centered-nav",
18865
+ "serif-line",
18866
+ "centered-calm",
18867
+ "clean-base"
18868
+ ],
18869
+ recommendedFooterLookIds: ["footer-editorial-columns", "footer-muted-grounding"],
18870
+ buttonPersonalityChoices: [
18871
+ buttonChoice("flat-ink"),
18872
+ buttonChoice("confident-chip"),
18873
+ buttonChoice("editorial-link")
18874
+ ],
18875
+ paletteRecommendations: [
18876
+ paletteRecommendation("brand-led-spruce"),
18877
+ paletteRecommendation("warm-neutral-forest"),
18878
+ paletteRecommendation("soft-natural-sage")
18879
+ ]
18232
18880
  })
18233
18881
  ];
18234
18882
  const curatedSiteStylesById = new Map(curatedSiteStyles.map((style) => [style.id, style]));
@@ -20364,7 +21012,7 @@ function resolveBackgroundColorValue(value, context) {
20364
21012
  return resolveThemeColorReferenceValue(value, { palette: context.theme?.palette });
20365
21013
  }
20366
21014
  //#endregion
20367
- //#region ../blocks/src/system/transforms/registry/_utils/sectionForegroundStyles.ts
21015
+ //#region ../blocks/src/system/transforms/registry/_utils/surfaceForegroundStyles.ts
20368
21016
  function readColorToken(input, key) {
20369
21017
  const value = input[key];
20370
21018
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
@@ -20373,18 +21021,17 @@ function resolveOptionalForegroundColor(value, context) {
20373
21021
  if (!value) return null;
20374
21022
  return resolveBackgroundColorValue(value, context);
20375
21023
  }
20376
- function resolveSectionForegroundStyles(value, context) {
21024
+ function resolveSurfaceForegroundStyles(value, context) {
20377
21025
  if (!value || typeof value !== "object") return {};
20378
21026
  const input = value;
20379
21027
  const resolvedText = resolveOptionalForegroundColor(readColorToken(input, "textColor"), context);
20380
21028
  if (!resolvedText) return {};
20381
- return {
20382
- color: resolvedText,
20383
- "--section-text-color": resolvedText,
20384
- "--section-heading-color": resolveOptionalForegroundColor(readColorToken(input, "headingColor"), context) ?? resolvedText,
20385
- "--section-link-color": resolveOptionalForegroundColor(readColorToken(input, "linkColor"), context) ?? resolvedText,
20386
- "--section-supporting-color": resolveOptionalForegroundColor(readColorToken(input, "supportingColor"), context) ?? resolvedText
20387
- };
21029
+ return { ...surfaceForegroundStyle({
21030
+ text: resolvedText,
21031
+ heading: resolveOptionalForegroundColor(readColorToken(input, "headingColor"), context) ?? void 0,
21032
+ link: resolveOptionalForegroundColor(readColorToken(input, "linkColor"), context) ?? void 0,
21033
+ supporting: resolveOptionalForegroundColor(readColorToken(input, "supportingColor"), context) ?? void 0
21034
+ }) };
20388
21035
  }
20389
21036
  //#endregion
20390
21037
  //#region ../blocks/src/system/transforms/registry/boxStyles.ts
@@ -20722,67 +21369,12 @@ const containerStylesResolveBoxClassName = {
20722
21369
  }
20723
21370
  };
20724
21371
  /**
20725
- * Resolves section text color style object.
20726
- *
20727
- * The value should be the `_sectionStyles.background.textColor` token.
20728
- * Returns an empty object when unset so the section inherits text color.
20729
- *
20730
- * Outputs both `color` and `--section-text-color` CSS variable so that
20731
- * headings (which have explicit color rules in typography CSS) can inherit
20732
- * the section's text color via the CSS variable cascade.
20733
- */
20734
- const sectionTextColorStyleTransform = {
20735
- id: "sectionStyles.resolveTextColorStyle",
20736
- kind: "formatter",
20737
- summary: "Resolve section text color token into a style object",
20738
- run: (value, options, context) => {
20739
- const fallbackToken = options?.fallbackToken;
20740
- if (typeof value === "string" && value.trim()) {
20741
- const colorValue = resolveBackgroundColorValue(value.trim(), context);
20742
- return {
20743
- color: colorValue,
20744
- "--section-text-color": colorValue
20745
- };
20746
- }
20747
- if (typeof fallbackToken === "string" && fallbackToken.trim()) {
20748
- const colorValue = resolveBackgroundColorValue(fallbackToken.trim(), context);
20749
- return {
20750
- color: colorValue,
20751
- "--section-text-color": colorValue
20752
- };
20753
- }
20754
- return {};
20755
- }
20756
- };
20757
- /**
20758
- * Resolves section heading color style object.
20759
- *
20760
- * The value should be the `_sectionStyles.background.headingColor` token.
20761
- * Returns an empty object when unset so headings inherit from textColor or default.
20762
- *
20763
- * Outputs `--section-heading-color` CSS variable which typography CSS can use
20764
- * to style headings differently from body text within a section.
20765
- */
20766
- const sectionHeadingColorStyleTransform = {
20767
- id: "sectionStyles.resolveHeadingColorStyle",
20768
- kind: "formatter",
20769
- summary: "Resolve section heading color token into a style object",
20770
- run: (value, options, context) => {
20771
- const fallbackToken = options?.fallbackToken;
20772
- if (typeof value === "string" && value.trim()) return { "--section-heading-color": resolveBackgroundColorValue(value.trim(), context) };
20773
- if (typeof fallbackToken === "string" && fallbackToken.trim()) return { "--section-heading-color": resolveBackgroundColorValue(fallbackToken.trim(), context) };
20774
- return {};
20775
- }
20776
- };
20777
- /**
20778
- * Resolves section foreground colors from the background object.
21372
+ * Resolves surface foreground colors from the background object.
20779
21373
  *
20780
21374
  * The value should be the full `_sectionStyles.background` object.
20781
21375
  * Outputs:
20782
- * - `color` and `--section-text-color` from background.textColor
20783
- * - `--section-heading-color` from background.headingColor (falls back to textColor if not set)
20784
- * - `--section-link-color` from background.linkColor (falls back to textColor if not set)
20785
- * - `--section-supporting-color` from background.supportingColor (falls back to textColor if not set)
21376
+ * - `color` and the complete `--rb-surface-*-color` contract
21377
+ * - optional roles fall back atomically to background.textColor
20786
21378
  *
20787
21379
  * This combined transform replaces separate foreground color transforms
20788
21380
  * when both need to be applied to the same style prop.
@@ -20790,8 +21382,8 @@ const sectionHeadingColorStyleTransform = {
20790
21382
  const sectionColorsStyleTransform = {
20791
21383
  id: "sectionStyles.resolveColorsStyle",
20792
21384
  kind: "formatter",
20793
- summary: "Resolve section foreground colors into a style object",
20794
- run: (value, _options, context) => resolveSectionForegroundStyles(value, context)
21385
+ summary: "Resolve surface foreground colors into a style object",
21386
+ run: (value, _options, context) => resolveSurfaceForegroundStyles(value, context)
20795
21387
  };
20796
21388
  /**
20797
21389
  * Maps semantic gap values to theme-aware gap classes.
@@ -20830,8 +21422,6 @@ const boxStylesTransforms = [
20830
21422
  sectionClassNameTransform,
20831
21423
  containerStylesResolveClassName,
20832
21424
  containerStylesResolveBoxClassName,
20833
- sectionTextColorStyleTransform,
20834
- sectionHeadingColorStyleTransform,
20835
21425
  sectionColorsStyleTransform,
20836
21426
  {
20837
21427
  id: "boxStyles.semanticGap",
@@ -21599,7 +22189,29 @@ const ctaRowFragment = defineFragment({
21599
22189
  fields: [createCtasRepeater({
21600
22190
  label: "Calls to action",
21601
22191
  itemLabel: "CTA",
21602
- maxItems: 3
22192
+ maxItems: 3,
22193
+ button: { variants: [
22194
+ {
22195
+ value: "primary",
22196
+ label: "Primary"
22197
+ },
22198
+ {
22199
+ value: "secondary",
22200
+ label: "Secondary"
22201
+ },
22202
+ {
22203
+ value: "outline",
22204
+ label: "Outline"
22205
+ },
22206
+ {
22207
+ value: "link",
22208
+ label: "Link"
22209
+ },
22210
+ {
22211
+ value: "accent",
22212
+ label: "Accent"
22213
+ }
22214
+ ] }
21603
22215
  })],
21604
22216
  layout: [ctaRow({
21605
22217
  collectionPath: "content.ctas",
@@ -21880,6 +22492,55 @@ function buildGridCardsLayout(className) {
21880
22492
  return el("div", { className }, buildResponsiveGridCards());
21881
22493
  }
21882
22494
  /**
22495
+ * Build a single "featured editorial" testimonial card.
22496
+ *
22497
+ * This card type deliberately renders NO avatar/headshot: an amber quote mark,
22498
+ * the quote body, then the author name and a context line. Colour + surface
22499
+ * treatment is driven entirely by `variantClass` (see testimonials.css):
22500
+ * - `rb-testimonials-fe-card--feature` → spruce-ink card (dark primary bg)
22501
+ * - `rb-testimonials-fe-card--plain` → white surface card
22502
+ *
22503
+ * Extra modifiers (`repeat`, `when`) are forwarded to the card node so the
22504
+ * caller can drive first-vs-rest branching.
22505
+ */
22506
+ function buildFeaturedEditorialCard(variantClass, ...mods) {
22507
+ const quoteMark = text({
22508
+ as: "span",
22509
+ className: "rb-testimonials-fe-qmark",
22510
+ "aria-hidden": "true"
22511
+ }, props({ value: "“" }));
22512
+ const quote = richText({ className: "rb-testimonials-fe-quote rb-max-w-none" }, bind("testimonial.content.body"), when("testimonial.content.body"));
22513
+ const meta = el("div", { className: "rb-testimonials-fe-meta" }, [text({
22514
+ as: "strong",
22515
+ className: "rb-testimonials-fe-name"
22516
+ }, bind("testimonial.content.name", { fallback: "Anonymous" })), text({
22517
+ as: "span",
22518
+ className: "rb-testimonials-fe-context"
22519
+ }, when("testimonial.content.jobTitle"), bind("testimonial.content.jobTitle"))]);
22520
+ return el("div", { className: `rb-testimonials-fe-card ${variantClass}` }, [
22521
+ quoteMark,
22522
+ quote,
22523
+ meta
22524
+ ], ...mods);
22525
+ }
22526
+ /**
22527
+ * Build the "featured editorial" layout: an asymmetric two-column arrangement
22528
+ * where the FIRST testimonial is a large featured (spruce-ink) card and the
22529
+ * remaining testimonials stack as white cards beside it.
22530
+ *
22531
+ * First-vs-rest branching is done without an offset primitive: the featured
22532
+ * card uses `repeat(..., { limit: 1 })` (entry 0 only), and the rest column
22533
+ * repeats over every entry but skips index 0 via `when("tIndex", { equals: 0,
22534
+ * not: true })`. The `$when` guard is re-evaluated per repeated item with the
22535
+ * index scope in context, so entry 0 produces no node in the rest column.
22536
+ */
22537
+ function buildFeaturedEditorialLayout() {
22538
+ 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", {
22539
+ equals: 0,
22540
+ not: true
22541
+ }))])]);
22542
+ }
22543
+ /**
21883
22544
  * Fragment definition for testimonials carousel.
21884
22545
  * Provides field definitions and data loader configuration.
21885
22546
  *
@@ -25371,7 +26032,7 @@ const defaultLayout = styledSection({
25371
26032
  children: styledContainer({
25372
26033
  baseClassName: "rb-relative rb-isolate",
25373
26034
  children: stack({
25374
- gap: "xl",
26035
+ gap: "lg",
25375
26036
  className: "rb-w-full rb-max-w-4xl rb-mx-auto"
25376
26037
  }, [headingLayout, accordionLayout])
25377
26038
  }),
@@ -25388,7 +26049,7 @@ const questionColumnsLayout = styledSection({
25388
26049
  children: styledContainer({
25389
26050
  baseClassName: "rb-relative rb-isolate",
25390
26051
  children: stack({
25391
- gap: "xl",
26052
+ gap: "lg",
25392
26053
  className: "rb-w-full"
25393
26054
  }, [headingLayout, questionColumnsAccordionLayout])
25394
26055
  }),
@@ -25553,7 +26214,26 @@ const formattingTransforms = [
25553
26214
  arrayLengthTransform,
25554
26215
  jsonArrayTransform,
25555
26216
  csvTransform,
25556
- marqueeLoopTransform
26217
+ marqueeLoopTransform,
26218
+ {
26219
+ id: "value.mapToken",
26220
+ kind: "formatter",
26221
+ summary: "Map a state/enum value to a theme token colour (rgb(var(--tb-<role>)))",
26222
+ schema: z$1.object({
26223
+ /**
26224
+ * Maps an input state/enum value (stringified) to a theme token ROLE name
26225
+ * (e.g. `secondary`, `accent`, `muted`, `border`). The role is wrapped to
26226
+ * `rgb(var(--tb-<role>))` so the result drops straight into a `style` binding.
26227
+ */
26228
+ cases: z$1.record(z$1.string(), z$1.string()),
26229
+ /** Role used when the value matches no case. Omit to yield `undefined`. */
26230
+ fallback: z$1.string().optional()
26231
+ }),
26232
+ run: (value, options) => {
26233
+ const role = options.cases[String(value)] ?? options.fallback;
26234
+ return role ? `rgb(var(--tb-${role}))` : void 0;
26235
+ }
26236
+ }
25557
26237
  ];
25558
26238
  //#endregion
25559
26239
  //#region ../blocks/src/system/transforms/registry/ui.ts
@@ -26197,6 +26877,26 @@ const headerLogoTextVisibleTransform = {
26197
26877
  return resolveHeaderBrandDisplay(asPartialObject(value)) !== "logo-only";
26198
26878
  }
26199
26879
  };
26880
+ /**
26881
+ * Selects which classic-layout nav row should render.
26882
+ *
26883
+ * - `'inline'` → classic variant with the historical logo-left / nav-right row
26884
+ * - `'centered'`→ classic variant with a centred (flex-1) nav and a right CTA
26885
+ * - `'other'` → not a classic variant (centered/floating/transparent handle it)
26886
+ *
26887
+ * Consumed by the site-header block's `$when` gates so the two classic rows are
26888
+ * mutually exclusive without introducing a new top-level header variant.
26889
+ */
26890
+ const classicNavModeTransform = {
26891
+ id: "layout.classicNavMode",
26892
+ kind: "formatter",
26893
+ summary: "Resolve which classic-layout nav row to render",
26894
+ run: (value) => {
26895
+ const header = asPartialObject(value);
26896
+ if (header?.variant !== "classic") return "other";
26897
+ return header?.navPlacement === "center" ? "centered" : "inline";
26898
+ }
26899
+ };
26200
26900
  const maxWidthClassTransform = {
26201
26901
  id: "layout.maxWidthClass",
26202
26902
  kind: "string",
@@ -26379,8 +27079,9 @@ const footerRootStyleTransform = {
26379
27079
  styleParts.push(backgroundColorStyle(token));
26380
27080
  }
26381
27081
  }
26382
- if (visuals.textColorOverride) styleParts.push({ color: visuals.textColorOverride });
26383
- else if (visuals.textColorToken) styleParts.push(textColorStyle(visuals.textColorToken));
27082
+ const foreground = visuals.textColorOverride ?? (visuals.textColorToken ? rgbColorValue(visuals.textColorToken) : null);
27083
+ if (!visuals.isTransparent && foreground) styleParts.push(surfaceForegroundStyle({ text: foreground }));
27084
+ else if (foreground) styleParts.push({ color: foreground });
26384
27085
  styleParts.push(borderColorStyle("border"));
26385
27086
  return mergeStyles(...styleParts);
26386
27087
  }
@@ -26466,18 +27167,16 @@ const footerBottomBarStyleTransform = {
26466
27167
  const styleParts = [];
26467
27168
  const background = bottomBar?.background;
26468
27169
  if (background?.type === "color") styleParts.push(backgroundColorStyle(background.color ?? "surface"));
26469
- if (bottomBar?.textColor) styleParts.push(textColorStyle(bottomBar.textColor));
26470
27170
  const borderColorToken = bottomBar?.borderTop?.colorToken;
26471
27171
  if (borderColorToken) styleParts.push(borderColorStyle(borderColorToken));
26472
27172
  else styleParts.push(borderColorStyle("border"));
26473
27173
  const borderWidth = bottomBar?.borderTop?.width;
26474
27174
  if (borderWidth && borderWidth !== "none") styleParts.push({ borderTopWidth: borderWidth === "thin" ? "1px" : borderWidth === "medium" ? "2px" : "3px" });
26475
27175
  else if (borderWidth === "none") styleParts.push({ borderTopWidth: "0px" });
26476
- if (!bottomBar?.textColor) {
26477
- const visuals = resolveBackgroundVisualStyles(extractBackgroundColorToken(background, "surface"), context.theme);
26478
- if (visuals.textColorOverride) styleParts.push({ color: visuals.textColorOverride });
26479
- else if (visuals.textColorToken) styleParts.push(textColorStyle(visuals.textColorToken));
26480
- }
27176
+ const visuals = resolveBackgroundVisualStyles(extractBackgroundColorToken(background, "surface"), context.theme);
27177
+ const foreground = bottomBar?.textColor ? rgbColorValue(bottomBar.textColor) : visuals.textColorOverride ?? (visuals.textColorToken ? rgbColorValue(visuals.textColorToken) : null);
27178
+ if (background?.type === "color" && foreground) styleParts.push(surfaceForegroundStyle({ text: foreground }));
27179
+ else if (foreground && bottomBar?.textColor) styleParts.push({ color: foreground });
26481
27180
  return mergeStyles(...styleParts);
26482
27181
  }
26483
27182
  };
@@ -26612,6 +27311,7 @@ const layoutTransforms = [
26612
27311
  headerLogoClassTransform,
26613
27312
  headerLogoMediaClassTransform,
26614
27313
  headerLogoTextVisibleTransform,
27314
+ classicNavModeTransform,
26615
27315
  footerRootClassTransform,
26616
27316
  headerRootStyleTransform,
26617
27317
  headerSectionBackgroundTransform,
@@ -27039,7 +27739,18 @@ const classicLayout = inline({
27039
27739
  logoRow,
27040
27740
  createNavRow(`rb-ml-auto ${desktopOnlyDisplay("flex")} rb-gap-6`),
27041
27741
  headerCta
27042
- ], when("$root.theme.header.variant", { equals: "classic" }));
27742
+ ], whenTransformed$1("$root.theme.header", "layout.classicNavMode", "inline"));
27743
+ const classicCenteredNavLayout = inline({
27744
+ className: bindProp("$root.theme.header.maxWidth", {
27745
+ transforms: pipe(tx("layout.maxWidthClass", { base: `rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6` })),
27746
+ fallback: `rb-container rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6`
27747
+ }),
27748
+ align: "center"
27749
+ }, [
27750
+ logoRow,
27751
+ createNavRow(`rb-flex-1 rb-justify-center ${desktopOnlyDisplay("flex")} rb-gap-6`, "center"),
27752
+ headerCta
27753
+ ], whenTransformed$1("$root.theme.header", "layout.classicNavMode", "centered"));
27043
27754
  const centeredLayout = stack({
27044
27755
  gap: "md",
27045
27756
  align: "center",
@@ -27114,6 +27825,7 @@ const siteHeaderManifest = createBlockManifest({
27114
27825
  }, [
27115
27826
  mobileHeaderBar,
27116
27827
  classicLayout,
27828
+ classicCenteredNavLayout,
27117
27829
  centeredLayout,
27118
27830
  transparentLayout,
27119
27831
  floatingLayout,
@@ -27323,7 +28035,7 @@ const footerLayout = section$1({
27323
28035
  style: { $bind: {
27324
28036
  from: "$root.theme.footer",
27325
28037
  transforms: [{ id: "layout.footerRootStyle" }],
27326
- fallback: mergeStyles(backgroundColorStyle("surface"), textColorStyle("text"), borderColorStyle("border"))
28038
+ fallback: mergeStyles(backgroundColorStyle("surface"), surfaceForegroundStyle({ text: "rgb(var(--tb-text))" }), borderColorStyle("border"))
27327
28039
  } }
27328
28040
  }, [
27329
28041
  simpleFooterLayout,
@@ -27378,7 +28090,7 @@ const backgroundImageClassName = "rb-absolute rb-inset-0 rb-z-neg-10 rb-h-full r
27378
28090
  */
27379
28091
  const wrapVariant$2 = (variantLayout) => styledSection({
27380
28092
  children: styledContainer({ children: stack({
27381
- gap: "xl",
28093
+ gap: "lg",
27382
28094
  className: "rb-relative"
27383
28095
  }, [...testimonialsHeadingFragment.layout, variantLayout]) }),
27384
28096
  baseClass: "",
@@ -27404,6 +28116,26 @@ const splitGridLayout = styledSection({
27404
28116
  spacing: "comfortable",
27405
28117
  imageClassName: backgroundImageClassName
27406
28118
  });
28119
+ const featuredEditorialLayout = styledSection({
28120
+ children: styledContainer({
28121
+ baseClassName: "rb-relative rb-isolate",
28122
+ children: stack({
28123
+ gap: "lg",
28124
+ className: "rb-relative"
28125
+ }, [el("div", { className: "rb-testimonials-fe-head" }, [text({
28126
+ as: "h2",
28127
+ className: "rb-text-left",
28128
+ headingDecoration: "off"
28129
+ }, when("content.heading"), bind("content.heading")), el("div", { className: "rb-testimonials-fe-leadin" }, [text({
28130
+ as: "p",
28131
+ className: "rb-prose",
28132
+ style: textColorStyle("mutedText")
28133
+ }, when("content.subheading"), bind("content.subheading"))])]), buildFeaturedEditorialLayout()])
28134
+ }),
28135
+ baseClass: "",
28136
+ spacing: "comfortable",
28137
+ imageClassName: backgroundImageClassName
28138
+ });
27407
28139
  const additionalFields = parseFieldDefinitions([
27408
28140
  ...testimonialsHeadingFragment.fields,
27409
28141
  {
@@ -27491,6 +28223,7 @@ const testimonialsBlockDefinition = {
27491
28223
  carousel: carouselLayout$1,
27492
28224
  grid: gridLayout$1,
27493
28225
  "split-grid": splitGridLayout,
28226
+ "featured-editorial": featuredEditorialLayout,
27494
28227
  highlight: highlightLayout
27495
28228
  },
27496
28229
  defaultVariant: "carousel",
@@ -27510,6 +28243,11 @@ const testimonialsBlockDefinition = {
27510
28243
  description: "Heading on the left with testimonial cards on the right",
27511
28244
  icon: "PanelLeft"
27512
28245
  },
28246
+ "featured-editorial": {
28247
+ label: "Featured editorial",
28248
+ description: "A large featured ink quote beside a column of stacked cards (no avatars)",
28249
+ icon: "Quote"
28250
+ },
27513
28251
  highlight: {
27514
28252
  label: "Highlight",
27515
28253
  description: "A single, centered testimonial with no carousel",
@@ -28033,7 +28771,7 @@ function grid$2(props, children) {
28033
28771
  const cardsLayout = styledSection({
28034
28772
  children: styledContainer({
28035
28773
  rootType: "stack",
28036
- rootProps: { gap: "xl" },
28774
+ rootProps: { gap: "lg" },
28037
28775
  children: [stack({
28038
28776
  gap: "sm",
28039
28777
  className: "rb-feature-cards-heading rb-mx-auto rb-max-w-3xl rb-text-center"
@@ -30016,8 +30754,11 @@ const recoverEmptyDescriptionWhenActive = {
30016
30754
  kind: "recover-empty-when-active",
30017
30755
  placeholder: "Add description"
30018
30756
  };
30019
- function eventSectionHeaderLayoutNodes() {
30020
- 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 }))];
30757
+ function eventSectionHeaderLayoutNodes(options = {}) {
30758
+ const headingNode = text({ as: "h2" }, bind("content.heading"), when("content.heading", { authoringPresence: recoverEmptyHeadingWhenActive }));
30759
+ const descriptionNode = richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description", { authoringPresence: recoverEmptyDescriptionWhenActive }));
30760
+ if (options.splitClassName) return [el("div", { className: options.splitClassName }, [headingNode, descriptionNode])];
30761
+ return [headingNode, descriptionNode];
30021
30762
  }
30022
30763
  //#endregion
30023
30764
  //#region ../blocks/src/system/blocks/shop.shared.ts
@@ -30084,7 +30825,8 @@ const eventCalendarDisplayModeValues = [
30084
30825
  "combined",
30085
30826
  "calendar",
30086
30827
  "list",
30087
- "timetable"
30828
+ "timetable",
30829
+ "week-board"
30088
30830
  ];
30089
30831
  const eventCalendarDefaultViewValues = ["month", "week"];
30090
30832
  const eventCalendarListLayoutValues = [
@@ -32503,6 +33245,40 @@ function getEventAvailability(event) {
32503
33245
  isFull: false
32504
33246
  };
32505
33247
  }
33248
+ /**
33249
+ * Derive the three-state availability status (available / filling / waitlist)
33250
+ * plus a short pill label. Builds on {@link getEventAvailability} so the
33251
+ * capacity math (availableSpots with a `capacity - registeredCount` fallback)
33252
+ * stays consistent with the binary helper used elsewhere.
33253
+ */
33254
+ function getEventAvailabilityStatus(event, options) {
33255
+ const threshold = options?.fillingThreshold ?? 2;
33256
+ const { available, isFull } = getEventAvailability(event);
33257
+ if (available == null) return {
33258
+ status: "available",
33259
+ available: null,
33260
+ isFull: false,
33261
+ label: null
33262
+ };
33263
+ if (isFull) return {
33264
+ status: "waitlist",
33265
+ available: 0,
33266
+ isFull: true,
33267
+ label: event.waitlistEnabled ? "Waitlist" : "Full"
33268
+ };
33269
+ if (available <= threshold) return {
33270
+ status: "filling",
33271
+ available,
33272
+ isFull: false,
33273
+ label: `${available} left`
33274
+ };
33275
+ return {
33276
+ status: "available",
33277
+ available,
33278
+ isFull: false,
33279
+ label: `${available} places`
33280
+ };
33281
+ }
32506
33282
  //#endregion
32507
33283
  //#region ../blocks/src/system/runtime/nodes/events/shared/utils.ts
32508
33284
  /**
@@ -35063,7 +35839,7 @@ const eventCalendarLayout = [styledSection({
35063
35839
  children: styledContainer({
35064
35840
  rootType: "stack",
35065
35841
  rootProps: { gap: "lg" },
35066
- children: [...eventSectionHeaderLayoutNodes(), el("event-calendar", withApiBindings({
35842
+ children: [...eventSectionHeaderLayoutNodes({ splitClassName: "event-section-head event-section-head--split" }), el("event-calendar", withApiBindings({
35067
35843
  events: { $bind: { from: "data.events" } },
35068
35844
  stage: { $bind: {
35069
35845
  from: "$root.previewStage",
@@ -35174,6 +35950,10 @@ const eventCalendarAdditionalFields = [
35174
35950
  value: "timetable",
35175
35951
  label: "Timetable"
35176
35952
  },
35953
+ {
35954
+ value: "week-board",
35955
+ label: "Week board"
35956
+ },
35177
35957
  {
35178
35958
  value: "list",
35179
35959
  label: "List"
@@ -35356,7 +36136,8 @@ const eventCalendarAdditionalFields = [
35356
36136
  oneOf: [
35357
36137
  "combined",
35358
36138
  "calendar",
35359
- "timetable"
36139
+ "timetable",
36140
+ "week-board"
35360
36141
  ]
35361
36142
  }
35362
36143
  }
@@ -35459,7 +36240,8 @@ const eventCalendarAdditionalFields = [
35459
36240
  "combined",
35460
36241
  "calendar",
35461
36242
  "list",
35462
- "timetable"
36243
+ "timetable",
36244
+ "week-board"
35463
36245
  ]
35464
36246
  }
35465
36247
  }
@@ -35494,7 +36276,8 @@ const eventCalendarAdditionalFields = [
35494
36276
  "combined",
35495
36277
  "calendar",
35496
36278
  "list",
35497
- "timetable"
36279
+ "timetable",
36280
+ "week-board"
35498
36281
  ]
35499
36282
  }, {
35500
36283
  field: "showFilters",
@@ -35517,7 +36300,8 @@ const eventCalendarAdditionalFields = [
35517
36300
  "combined",
35518
36301
  "calendar",
35519
36302
  "list",
35520
- "timetable"
36303
+ "timetable",
36304
+ "week-board"
35521
36305
  ]
35522
36306
  }, {
35523
36307
  field: "showFilters",
@@ -35540,7 +36324,8 @@ const eventCalendarAdditionalFields = [
35540
36324
  "combined",
35541
36325
  "calendar",
35542
36326
  "list",
35543
- "timetable"
36327
+ "timetable",
36328
+ "week-board"
35544
36329
  ]
35545
36330
  }, {
35546
36331
  field: "showFilters",
@@ -35562,7 +36347,8 @@ const eventCalendarAdditionalFields = [
35562
36347
  oneOf: [
35563
36348
  "combined",
35564
36349
  "list",
35565
- "timetable"
36350
+ "timetable",
36351
+ "week-board"
35566
36352
  ]
35567
36353
  }, { allOf: [{
35568
36354
  field: "variant",
@@ -35738,6 +36524,7 @@ const eventCalendarVariants = {
35738
36524
  combined: eventCalendarLayout[0],
35739
36525
  calendar: eventCalendarLayout[0],
35740
36526
  timetable: eventCalendarLayout[0],
36527
+ "week-board": eventCalendarLayout[0],
35741
36528
  list: eventCalendarLayout[0]
35742
36529
  };
35743
36530
  const eventCalendarContentShape = {
@@ -35751,6 +36538,7 @@ const eventCalendarContentShape = {
35751
36538
  { value: "combined" },
35752
36539
  { value: "calendar" },
35753
36540
  { value: "timetable" },
36541
+ { value: "week-board" },
35754
36542
  { value: "list" }
35755
36543
  ]
35756
36544
  },
@@ -35817,6 +36605,7 @@ const eventCalendarBlockDefinition = {
35817
36605
  combined: { label: "Combined" },
35818
36606
  calendar: { label: "Calendar" },
35819
36607
  timetable: { label: "Timetable" },
36608
+ "week-board": { label: "Week board" },
35820
36609
  list: { label: "List" }
35821
36610
  }
35822
36611
  }, eventCalendarContentShape),
@@ -41318,6 +42107,12 @@ const systemBlockDefinitions = [
41318
42107
  family: "centered",
41319
42108
  weight: "featured",
41320
42109
  bias: "soft"
42110
+ }),
42111
+ layoutProfile({
42112
+ variantKey: "featured-editorial",
42113
+ family: "split",
42114
+ weight: "featured",
42115
+ bias: "soft"
41321
42116
  })
41322
42117
  ]
41323
42118
  })),
@@ -41450,6 +42245,11 @@ const systemBlockDefinitions = [
41450
42245
  variantKey: "timetable",
41451
42246
  family: "stack",
41452
42247
  weight: "standard"
42248
+ }),
42249
+ layoutProfile({
42250
+ variantKey: "week-board",
42251
+ family: "stack",
42252
+ weight: "standard"
41453
42253
  })
41454
42254
  ]
41455
42255
  })),
@@ -41807,7 +42607,15 @@ function gradientToCss(config, tokens) {
41807
42607
  const stops = config.stops.map((stop) => {
41808
42608
  return `${tokens[stop.color] ?? "#000000"} ${stop.position}%`;
41809
42609
  }).join(", ");
41810
- if (config.type === "radial") return `radial-gradient(circle at ${config.position ?? "center"}, ${stops})`;
42610
+ const type = config.type ?? "linear";
42611
+ if (type === "radial") {
42612
+ const position = config.position ?? "center";
42613
+ return `radial-gradient(${config.shape ?? "circle"} at ${position}, ${stops})`;
42614
+ }
42615
+ if (type === "conic") {
42616
+ const position = config.position ?? "center";
42617
+ return `conic-gradient(from ${config.angle ?? 0}deg at ${position}, ${stops})`;
42618
+ }
41811
42619
  return `linear-gradient(${config.angle ?? 180}deg, ${stops})`;
41812
42620
  }
41813
42621
  //#endregion
@@ -41885,8 +42693,8 @@ const backgroundTransforms = [
41885
42693
  {
41886
42694
  id: "background.resolveColorsStyle",
41887
42695
  kind: "formatter",
41888
- summary: "Resolve section foreground colors from a background object into scoped CSS vars",
41889
- run: (value, _options, context) => resolveSectionForegroundStyles(value, context)
42696
+ summary: "Resolve surface foreground colors from a background object into scoped CSS vars",
42697
+ run: (value, _options, context) => resolveSurfaceForegroundStyles(value, context)
41890
42698
  },
41891
42699
  {
41892
42700
  id: "background.resolveAllowOverflow",
@@ -42369,21 +43177,53 @@ function Inline({ as: Comp = "div", gap = "md", align = "center", justify = "sta
42369
43177
  }
42370
43178
  //#endregion
42371
43179
  //#region ../blocks/src/primitives/layout/Grid.tsx
43180
+ /** Utility class for a numeric column COUNT at a given breakpoint. */
43181
+ const COL_COUNT_CLASS = {
43182
+ base: (n) => `rb-grid-cols-${n}`,
43183
+ sm: (n) => `rb-sm-grid-cols-${n}`,
43184
+ md: (n) => `rb-md-grid-cols-${n}`,
43185
+ lg: (n) => `rb-lg-grid-cols-${n}`,
43186
+ xl: (n) => `rb-xl-grid-cols-${n}`
43187
+ };
43188
+ /** Inline custom property read by the `.rb-grid-tracks` utility per breakpoint. */
43189
+ const TRACK_VAR = {
43190
+ base: "--rb-gtc-base",
43191
+ sm: "--rb-gtc-sm",
43192
+ md: "--rb-gtc-md",
43193
+ lg: "--rb-gtc-lg",
43194
+ xl: "--rb-gtc-xl"
43195
+ };
43196
+ const GRID_BREAKPOINTS = [
43197
+ "base",
43198
+ "sm",
43199
+ "md",
43200
+ "lg",
43201
+ "xl"
43202
+ ];
42372
43203
  function Grid({ as: Comp = "div", cols = {
42373
43204
  base: 1,
42374
43205
  md: 2
42375
- }, gap = "md", className, children, "data-slot": dataSlot, ...rest }) {
42376
- const colsObj = typeof cols === "number" ? { base: cols } : cols;
42377
- const colClasses = [
42378
- colsObj.base && `rb-grid-cols-${colsObj.base}`,
42379
- colsObj.sm && `rb-sm-grid-cols-${colsObj.sm}`,
42380
- colsObj.md && `rb-md-grid-cols-${colsObj.md}`,
42381
- colsObj.lg && `rb-lg-grid-cols-${colsObj.lg}`,
42382
- colsObj.xl && `rb-xl-grid-cols-${colsObj.xl}`
42383
- ];
43206
+ }, gap = "md", className, style, children, "data-slot": dataSlot, ...rest }) {
43207
+ const colsObj = typeof cols === "number" || typeof cols === "string" ? { base: cols } : cols;
43208
+ const colClasses = [];
43209
+ const trackVars = {};
43210
+ let hasTracks = false;
43211
+ for (const bp of GRID_BREAKPOINTS) {
43212
+ const value = colsObj[bp];
43213
+ if (value == null) continue;
43214
+ if (typeof value === "string") {
43215
+ trackVars[TRACK_VAR[bp]] = value;
43216
+ hasTracks = true;
43217
+ } else colClasses.push(COL_COUNT_CLASS[bp](value));
43218
+ }
43219
+ const mergedStyle = hasTracks ? {
43220
+ ...trackVars,
43221
+ ...style
43222
+ } : style;
42384
43223
  return /* @__PURE__ */ jsx(Comp, {
42385
43224
  ...rest,
42386
- className: clsx("rb-grid", colClasses, {
43225
+ style: mergedStyle,
43226
+ className: clsx("rb-grid", hasTracks && "rb-grid-tracks", colClasses, {
42387
43227
  "rb-gap-0": gap === "none",
42388
43228
  "rb-gap-1": gap === "xs",
42389
43229
  "rb-gap-2": gap === "sm",
@@ -42620,119 +43460,6 @@ function Text$1({ as: Comp = "span", children, className = "", path, maxLength:
42620
43460
  });
42621
43461
  }
42622
43462
  //#endregion
42623
- //#region ../blocks/src/system/runtime/nodes/richText-coerce.ts
42624
- function coerceRichTextDoc(value) {
42625
- const raw = unwrapRichTextRoot(value);
42626
- if (typeof raw === "string") return createRichTextDocFromMarkdown(raw);
42627
- if (isRecord$26(raw) && typeof raw.type === "string") return raw;
42628
- return null;
42629
- }
42630
- //#endregion
42631
- //#region ../blocks/src/system/runtime/nodes/renderInlineText.tsx
42632
- function renderInlineText(value) {
42633
- return value.map((run, index) => renderRun(run, index));
42634
- }
42635
- function renderRun(run, key) {
42636
- return (run.marks ?? []).reduceRight((child, mark, index) => renderMark(mark, `${String(key)}-${index}`, child), run.text);
42637
- }
42638
- function renderMark(mark, key, child) {
42639
- switch (mark.kind) {
42640
- case "strong": return /* @__PURE__ */ jsx("strong", { children: child }, key);
42641
- case "emphasis": return /* @__PURE__ */ jsx("em", { children: child }, key);
42642
- case "accent": return /* @__PURE__ */ jsx("span", {
42643
- className: `rb-inline-text-accent rb-inline-text-accent-${mark.tone}`,
42644
- children: child
42645
- }, key);
42646
- case "highlight": return /* @__PURE__ */ jsx("span", {
42647
- className: `rb-inline-text-highlight rb-inline-text-highlight-${mark.tone}`,
42648
- children: child
42649
- }, key);
42650
- }
42651
- }
42652
- //#endregion
42653
- //#region ../blocks/src/system/runtime/nodes/basic.tsx
42654
- const TextNode = ({ value, children, blockId: _blockId, blockKind: _blockKind, blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, inlineTextFallbackPath, ...rest }) => {
42655
- const inlineText = parseInlineTextFromUnknown(value);
42656
- if (inlineText.ok && !isInlineTextEmpty(inlineText.value)) return /* @__PURE__ */ jsx(Text$1, {
42657
- ...rest,
42658
- children: renderInlineText(inlineText.value)
42659
- });
42660
- const fallbackValue = inlineTextFallbackPath ? readStringPath(blockContent, inlineTextFallbackPath) : null;
42661
- return /* @__PURE__ */ jsx(Text$1, {
42662
- ...rest,
42663
- children: fallbackValue ?? textNodeContent(value) ?? children
42664
- });
42665
- };
42666
- function textNodeContent(value) {
42667
- if (value === null || value === void 0) return null;
42668
- if (typeof value === "string" || typeof value === "number") return value;
42669
- return null;
42670
- }
42671
- function readStringPath(source, path) {
42672
- const value = getAtPath(source, path);
42673
- return typeof value === "string" ? value : null;
42674
- }
42675
- const RichTextNode = ({ value, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
42676
- return /* @__PURE__ */ jsx(RichText$2, {
42677
- doc: coerceRichTextDoc(value) ?? void 0,
42678
- ...rest
42679
- });
42680
- };
42681
- 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 }) => {
42682
- const content = value ?? children;
42683
- if (!content) {
42684
- console.warn("[ButtonNode] No content to render - value and children are both empty");
42685
- return null;
42686
- }
42687
- const resolvedHref = typeof href === "string" ? href.trim() : "";
42688
- if (linkRequired && resolvedHref.length === 0) {
42689
- if (process.env.NODE_ENV === "development") console.warn("[ButtonNode] Missing href for link-required button");
42690
- return null;
42691
- }
42692
- const resolvedVariantId = parseThemeButtonVariantId(variantId);
42693
- const combinedClassName = resolvedVariantId ? themeButtonClassName({
42694
- variant: resolvedVariantId,
42695
- size,
42696
- extraClassName: className
42697
- }) : className;
42698
- if (resolvedHref) return /* @__PURE__ */ jsx("a", {
42699
- href: resolvedHref,
42700
- target,
42701
- rel,
42702
- className: combinedClassName,
42703
- ...rest,
42704
- children: content
42705
- });
42706
- return /* @__PURE__ */ jsx("button", {
42707
- type,
42708
- disabled,
42709
- className: combinedClassName,
42710
- ...rest,
42711
- children: content
42712
- });
42713
- };
42714
- const LinkNode = ({ href, children, className, target, rel, linkRequired = false, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
42715
- if (!children) return null;
42716
- const resolvedHref = typeof href === "string" ? href.trim() : "";
42717
- if (resolvedHref.length === 0) {
42718
- if (linkRequired && process.env.NODE_ENV === "development") console.warn("[LinkNode] Missing href for link-required link");
42719
- if (linkRequired) return null;
42720
- return /* @__PURE__ */ jsx("span", {
42721
- className,
42722
- ...rest,
42723
- children
42724
- });
42725
- }
42726
- return /* @__PURE__ */ jsx("a", {
42727
- href: resolvedHref,
42728
- className,
42729
- target,
42730
- rel,
42731
- ...rest,
42732
- children
42733
- });
42734
- };
42735
- //#endregion
42736
43463
  //#region ../blocks/src/system/icons/cta.ts
42737
43464
  /**
42738
43465
  * Curated icon set for CTA/button icons.
@@ -42831,6 +43558,171 @@ function getIconComponent(name) {
42831
43558
  return getCtaIconComponent(name) ?? getSemanticIconComponent(name);
42832
43559
  }
42833
43560
  //#endregion
43561
+ //#region ../blocks/src/system/runtime/nodes/buttonAffordance.ts
43562
+ function normalize(value) {
43563
+ const trimmed = typeof value === "string" ? value.trim() : "";
43564
+ return trimmed.length > 0 ? trimmed : null;
43565
+ }
43566
+ /**
43567
+ * Resolve the trailing affordance a button should render. Node overrides
43568
+ * personality; returns `null` when nothing is configured.
43569
+ */
43570
+ function resolveButtonAffordance(input) {
43571
+ const nodeIcon = normalize(input.trailingIcon);
43572
+ if (nodeIcon) {
43573
+ const colorToken = normalize(input.trailingIconColorToken);
43574
+ return {
43575
+ iconId: nodeIcon,
43576
+ source: "node",
43577
+ ...colorToken ? { colorToken } : {}
43578
+ };
43579
+ }
43580
+ const personality = input.personalityAffordance;
43581
+ const personalityIcon = normalize(personality?.iconId);
43582
+ if (personality && personalityIcon) {
43583
+ const colorToken = normalize(personality.colorToken);
43584
+ return {
43585
+ iconId: personalityIcon,
43586
+ source: "personality",
43587
+ ...colorToken ? { colorToken } : {}
43588
+ };
43589
+ }
43590
+ return null;
43591
+ }
43592
+ //#endregion
43593
+ //#region ../blocks/src/system/runtime/nodes/richText-coerce.ts
43594
+ function coerceRichTextDoc(value) {
43595
+ const raw = unwrapRichTextRoot(value);
43596
+ if (typeof raw === "string") return createRichTextDocFromMarkdown(raw);
43597
+ if (isRecord$26(raw) && typeof raw.type === "string") return raw;
43598
+ return null;
43599
+ }
43600
+ //#endregion
43601
+ //#region ../blocks/src/system/runtime/nodes/renderInlineText.tsx
43602
+ function renderInlineText(value) {
43603
+ return value.map((run, index) => renderRun(run, index));
43604
+ }
43605
+ function renderRun(run, key) {
43606
+ return (run.marks ?? []).reduceRight((child, mark, index) => renderMark(mark, `${String(key)}-${index}`, child), run.text);
43607
+ }
43608
+ function renderMark(mark, key, child) {
43609
+ switch (mark.kind) {
43610
+ case "strong": return /* @__PURE__ */ jsx("strong", { children: child }, key);
43611
+ case "emphasis": return /* @__PURE__ */ jsx("em", { children: child }, key);
43612
+ case "accent": return /* @__PURE__ */ jsx("span", {
43613
+ className: `rb-inline-text-accent rb-inline-text-accent-${mark.tone}`,
43614
+ children: child
43615
+ }, key);
43616
+ case "highlight": return /* @__PURE__ */ jsx("span", {
43617
+ className: `rb-inline-text-highlight rb-inline-text-highlight-${mark.tone}`,
43618
+ children: child
43619
+ }, key);
43620
+ }
43621
+ }
43622
+ //#endregion
43623
+ //#region ../blocks/src/system/runtime/nodes/basic.tsx
43624
+ const TextNode = ({ value, children, blockId: _blockId, blockKind: _blockKind, blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, inlineTextFallbackPath, ...rest }) => {
43625
+ const inlineText = parseInlineTextFromUnknown(value);
43626
+ if (inlineText.ok && !isInlineTextEmpty(inlineText.value)) return /* @__PURE__ */ jsx(Text$1, {
43627
+ ...rest,
43628
+ children: renderInlineText(inlineText.value)
43629
+ });
43630
+ const fallbackValue = inlineTextFallbackPath ? readStringPath(blockContent, inlineTextFallbackPath) : null;
43631
+ return /* @__PURE__ */ jsx(Text$1, {
43632
+ ...rest,
43633
+ children: fallbackValue ?? textNodeContent(value) ?? children
43634
+ });
43635
+ };
43636
+ function textNodeContent(value) {
43637
+ if (value === null || value === void 0) return null;
43638
+ if (typeof value === "string" || typeof value === "number") return value;
43639
+ return null;
43640
+ }
43641
+ function readStringPath(source, path) {
43642
+ const value = getAtPath(source, path);
43643
+ return typeof value === "string" ? value : null;
43644
+ }
43645
+ const RichTextNode = ({ value, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
43646
+ return /* @__PURE__ */ jsx(RichText$2, {
43647
+ doc: coerceRichTextDoc(value) ?? void 0,
43648
+ ...rest
43649
+ });
43650
+ };
43651
+ /**
43652
+ * Render the resolved trailing affordance icon. Colour is applied via a theme
43653
+ * CSS var so it recolours with the palette; the icon stroke uses `currentColor`.
43654
+ */
43655
+ function renderTrailingAffordanceIcon(affordance) {
43656
+ if (!affordance) return null;
43657
+ const Icon = getIconComponent(affordance.iconId);
43658
+ if (!Icon) return null;
43659
+ return /* @__PURE__ */ jsx(Icon, {
43660
+ "aria-hidden": true,
43661
+ className: "rb-ml-2 rb-h-4 rb-w-4 rb-inline-block",
43662
+ style: affordance.colorToken ? { color: `rgb(var(--tb-${affordance.colorToken}))` } : void 0
43663
+ });
43664
+ }
43665
+ 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 }) => {
43666
+ const content = value ?? children;
43667
+ if (!content) {
43668
+ console.warn("[ButtonNode] No content to render - value and children are both empty");
43669
+ return null;
43670
+ }
43671
+ const affordanceIcon = renderTrailingAffordanceIcon(resolveButtonAffordance({
43672
+ trailingIcon,
43673
+ trailingIconColorToken,
43674
+ personalityAffordance
43675
+ }));
43676
+ const resolvedHref = typeof href === "string" ? href.trim() : "";
43677
+ if (linkRequired && resolvedHref.length === 0) {
43678
+ if (process.env.NODE_ENV === "development") console.warn("[ButtonNode] Missing href for link-required button");
43679
+ return null;
43680
+ }
43681
+ const extraClassName = affordanceIcon ? [className, AFFORDANCE_SUPPRESS_CLASS].filter(Boolean).join(" ") : className;
43682
+ const resolvedVariantId = parseThemeButtonVariantId(variantId);
43683
+ const combinedClassName = resolvedVariantId ? themeButtonClassName({
43684
+ variant: resolvedVariantId,
43685
+ size,
43686
+ extraClassName
43687
+ }) : extraClassName;
43688
+ if (resolvedHref) return /* @__PURE__ */ jsxs("a", {
43689
+ href: resolvedHref,
43690
+ target,
43691
+ rel,
43692
+ className: combinedClassName,
43693
+ ...rest,
43694
+ children: [content, affordanceIcon]
43695
+ });
43696
+ return /* @__PURE__ */ jsxs("button", {
43697
+ type,
43698
+ disabled,
43699
+ className: combinedClassName,
43700
+ ...rest,
43701
+ children: [content, affordanceIcon]
43702
+ });
43703
+ };
43704
+ const LinkNode = ({ href, children, className, target, rel, linkRequired = false, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
43705
+ if (!children) return null;
43706
+ const resolvedHref = typeof href === "string" ? href.trim() : "";
43707
+ if (resolvedHref.length === 0) {
43708
+ if (linkRequired && process.env.NODE_ENV === "development") console.warn("[LinkNode] Missing href for link-required link");
43709
+ if (linkRequired) return null;
43710
+ return /* @__PURE__ */ jsx("span", {
43711
+ className,
43712
+ ...rest,
43713
+ children
43714
+ });
43715
+ }
43716
+ return /* @__PURE__ */ jsx("a", {
43717
+ href: resolvedHref,
43718
+ className,
43719
+ target,
43720
+ rel,
43721
+ ...rest,
43722
+ children
43723
+ });
43724
+ };
43725
+ //#endregion
42834
43726
  //#region ../blocks/src/system/runtime/nodes/icon.tsx
42835
43727
  const IconNode = ({ value, title, className, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
42836
43728
  const parsedValue = parseBlockIconValue(value);
@@ -43528,6 +44420,77 @@ function SpinnerNode({ className, size = "md", color, strokeWidth = 3, srLabel,
43528
44420
  }) : null]
43529
44421
  });
43530
44422
  }
44423
+ //#endregion
44424
+ //#region ../blocks/src/system/runtime/nodes/svg.tsx
44425
+ /**
44426
+ * Turn paint props into a CSS style object. Colour values pass through as-is so
44427
+ * they may reference theme tokens via `var()`, which only resolves in a CSS
44428
+ * property context (not in a bare presentation attribute).
44429
+ */
44430
+ function paintStyle(shape) {
44431
+ const style = {};
44432
+ if (shape.fill !== void 0) style.fill = shape.fill;
44433
+ if (shape.stroke !== void 0) style.stroke = shape.stroke;
44434
+ if (shape.strokeWidth !== void 0) style.strokeWidth = shape.strokeWidth;
44435
+ if (shape.fillOpacity !== void 0) style.fillOpacity = shape.fillOpacity;
44436
+ if (shape.strokeOpacity !== void 0) style.strokeOpacity = shape.strokeOpacity;
44437
+ if (shape.strokeLinecap !== void 0) style.strokeLinecap = shape.strokeLinecap;
44438
+ if (shape.strokeLinejoin !== void 0) style.strokeLinejoin = shape.strokeLinejoin;
44439
+ if (shape.strokeDasharray !== void 0) style.strokeDasharray = shape.strokeDasharray;
44440
+ return style;
44441
+ }
44442
+ function renderShape(shape, key) {
44443
+ const style = paintStyle(shape);
44444
+ switch (shape.kind) {
44445
+ case "path": return /* @__PURE__ */ jsx("path", {
44446
+ d: shape.d,
44447
+ style
44448
+ }, key);
44449
+ case "ellipse": return /* @__PURE__ */ jsx("ellipse", {
44450
+ cx: shape.cx,
44451
+ cy: shape.cy,
44452
+ rx: shape.rx,
44453
+ ry: shape.ry,
44454
+ style
44455
+ }, key);
44456
+ case "circle": return /* @__PURE__ */ jsx("circle", {
44457
+ cx: shape.cx,
44458
+ cy: shape.cy,
44459
+ r: shape.r,
44460
+ style
44461
+ }, key);
44462
+ case "rect": return /* @__PURE__ */ jsx("rect", {
44463
+ x: shape.x,
44464
+ y: shape.y,
44465
+ width: shape.width,
44466
+ height: shape.height,
44467
+ rx: shape.rx,
44468
+ ry: shape.ry,
44469
+ style
44470
+ }, key);
44471
+ case "line": return /* @__PURE__ */ jsx("line", {
44472
+ x1: shape.x1,
44473
+ y1: shape.y1,
44474
+ x2: shape.x2,
44475
+ y2: shape.y2,
44476
+ style
44477
+ }, key);
44478
+ default: return null;
44479
+ }
44480
+ }
44481
+ const SvgNode = ({ blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, viewBox, width, height, className, style, ariaHidden = true, shapes }) => {
44482
+ return /* @__PURE__ */ jsx("svg", {
44483
+ viewBox,
44484
+ width,
44485
+ height,
44486
+ className,
44487
+ style,
44488
+ "aria-hidden": ariaHidden,
44489
+ xmlns: "http://www.w3.org/2000/svg",
44490
+ fill: "none",
44491
+ children: (Array.isArray(shapes) ? shapes : []).map((shape, index) => renderShape(shape, index))
44492
+ });
44493
+ };
43531
44494
  function isRecord$13(value) {
43532
44495
  return typeof value === "object" && value !== null && !Array.isArray(value);
43533
44496
  }
@@ -48762,6 +49725,154 @@ const WeekAgendaView = ({ days, events, showCapacity, emptyMessage = "No events
48762
49725
  });
48763
49726
  };
48764
49727
  //#endregion
49728
+ //#region ../blocks/src/system/runtime/nodes/events/shared/WeekBoardView.tsx
49729
+ const SHORT_DAY_NAMES = [
49730
+ "Sun",
49731
+ "Mon",
49732
+ "Tue",
49733
+ "Wed",
49734
+ "Thu",
49735
+ "Fri",
49736
+ "Sat"
49737
+ ];
49738
+ const LEGEND_ITEMS = [
49739
+ {
49740
+ status: "available",
49741
+ label: "Places available"
49742
+ },
49743
+ {
49744
+ status: "filling",
49745
+ label: "Filling up"
49746
+ },
49747
+ {
49748
+ status: "waitlist",
49749
+ label: "Waitlist"
49750
+ }
49751
+ ];
49752
+ /**
49753
+ * Format an event's start–end as a compact range, merging the shared meridiem.
49754
+ * "6:00 PM" + "7:00 PM" -> "6:00 – 7:00 PM"; "9:30 AM" + "10:15 AM" -> "9:30 – 10:15 AM".
49755
+ */
49756
+ function formatCompactTimeRange(event) {
49757
+ const timeZone = event.timeZone ?? null;
49758
+ const start = formatTime(event.startsAt, { timeZone });
49759
+ const end = formatTime(event.endsAt, { timeZone });
49760
+ const startMeridiem = start.slice(-2);
49761
+ const endMeridiem = end.slice(-2);
49762
+ if ((startMeridiem === "AM" || startMeridiem === "PM") && startMeridiem === endMeridiem) return `${start.slice(0, -3).trim()} – ${end}`;
49763
+ return `${start} – ${end}`;
49764
+ }
49765
+ const WeekBoardCard = ({ event, showCapacity }) => {
49766
+ const linkProps = resolveEventLinkProps(event);
49767
+ const { status, label } = getEventAvailabilityStatus(event);
49768
+ const pill = showCapacity ? label : null;
49769
+ return /* @__PURE__ */ jsxs("a", {
49770
+ ...linkProps,
49771
+ className: "event-week-board-card",
49772
+ "data-status": status,
49773
+ children: [
49774
+ /* @__PURE__ */ jsx("span", {
49775
+ className: "event-week-board-card-title",
49776
+ children: event.title
49777
+ }),
49778
+ /* @__PURE__ */ jsx("span", {
49779
+ className: "event-week-board-card-time",
49780
+ children: formatCompactTimeRange(event)
49781
+ }),
49782
+ pill ? /* @__PURE__ */ jsx("span", {
49783
+ className: "event-week-board-card-pill",
49784
+ "data-status": status,
49785
+ children: pill
49786
+ }) : null
49787
+ ]
49788
+ });
49789
+ };
49790
+ /**
49791
+ * WeekBoardView
49792
+ *
49793
+ * Compact "class day-board": day columns side by side (one card per class per
49794
+ * day), a legend of the three availability states, and a footer CTA. Collapses
49795
+ * to a stacked list on mobile. Repurposes the week-grouped agenda data as a
49796
+ * board rather than an hour-positioned timetable grid.
49797
+ */
49798
+ 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" }) => {
49799
+ const eventsByDate = groupEventsByDate(events);
49800
+ const hasAnyEvents = days.some((date) => (eventsByDate.get(formatDateKey(date)) ?? []).length > 0);
49801
+ const ctaClassName = themeButtonClassName({
49802
+ variant: buttonVariant,
49803
+ size: "sm"
49804
+ });
49805
+ return /* @__PURE__ */ jsx("div", {
49806
+ className: "event-calendar event-calendar--week-board",
49807
+ "data-block": "event-calendar",
49808
+ children: /* @__PURE__ */ jsxs("div", {
49809
+ className: "event-week-board",
49810
+ style: { "--event-week-board-cols": String(days.length) },
49811
+ children: [
49812
+ /* @__PURE__ */ jsxs("div", {
49813
+ className: "event-week-board-top",
49814
+ children: [/* @__PURE__ */ jsx("span", {
49815
+ className: "event-week-board-title",
49816
+ children: title
49817
+ }), /* @__PURE__ */ jsx("div", {
49818
+ className: "event-week-board-legend",
49819
+ "aria-hidden": "true",
49820
+ children: LEGEND_ITEMS.map((item) => /* @__PURE__ */ jsxs("span", {
49821
+ className: "event-week-board-legend-item",
49822
+ children: [/* @__PURE__ */ jsx("i", {
49823
+ className: "event-week-board-legend-dot",
49824
+ "data-status": item.status
49825
+ }), item.label]
49826
+ }, item.status))
49827
+ })]
49828
+ }),
49829
+ /* @__PURE__ */ jsx("div", {
49830
+ className: "event-week-board-grid",
49831
+ children: days.map((date) => {
49832
+ const dateKey = formatDateKey(date);
49833
+ const dayEvents = eventsByDate.get(dateKey) ?? [];
49834
+ const today = isToday(date);
49835
+ return /* @__PURE__ */ jsxs("div", {
49836
+ className: clsx("event-week-board-day", dayEvents.length === 0 && "event-week-board-day--empty", today && "event-week-board-day--today"),
49837
+ children: [/* @__PURE__ */ jsxs("div", {
49838
+ className: "event-week-board-day-head",
49839
+ children: [/* @__PURE__ */ jsx("span", {
49840
+ className: "event-week-board-day-abbr",
49841
+ children: SHORT_DAY_NAMES[date.getDay()]
49842
+ }), /* @__PURE__ */ jsx("span", {
49843
+ className: "event-week-board-day-num",
49844
+ children: date.getDate()
49845
+ })]
49846
+ }), /* @__PURE__ */ jsx("div", {
49847
+ className: "event-week-board-day-body",
49848
+ children: dayEvents.map((event) => /* @__PURE__ */ jsx(WeekBoardCard, {
49849
+ event,
49850
+ showCapacity
49851
+ }, event.occurrenceId || event.id))
49852
+ })]
49853
+ }, dateKey);
49854
+ })
49855
+ }),
49856
+ /* @__PURE__ */ jsxs("div", {
49857
+ className: "event-week-board-foot",
49858
+ children: [/* @__PURE__ */ jsx("p", {
49859
+ className: "event-week-board-foot-note",
49860
+ children: hasAnyEvents ? footerNote : emptyMessage
49861
+ }), ctaHref ? /* @__PURE__ */ jsx("a", {
49862
+ className: ctaClassName,
49863
+ href: ctaHref,
49864
+ children: ctaLabel
49865
+ }) : /* @__PURE__ */ jsx("button", {
49866
+ type: "button",
49867
+ className: ctaClassName,
49868
+ children: ctaLabel
49869
+ })]
49870
+ })
49871
+ ]
49872
+ })
49873
+ });
49874
+ };
49875
+ //#endregion
48765
49876
  //#region ../blocks/src/system/runtime/nodes/events/shared/timetableModel.ts
48766
49877
  /**
48767
49878
  * Event-domain adapter over @riverbankcms/calendar.
@@ -49196,6 +50307,16 @@ function renderTimetableSsr(display) {
49196
50307
  })]
49197
50308
  });
49198
50309
  }
50310
+ function renderWeekBoardSsr(display) {
50311
+ return /* @__PURE__ */ jsx(WeekBoardView, {
50312
+ days: getWeekCalendarDayTuple(/* @__PURE__ */ new Date(), display.startOfWeek),
50313
+ events: [...display.events],
50314
+ showCapacity: display.showCapacity,
50315
+ emptyMessage: display.emptyMessage,
50316
+ ctaLabel: display.buttonText,
50317
+ buttonVariant: display.buttonVariant
50318
+ });
50319
+ }
49199
50320
  function formatHourLabel(hour) {
49200
50321
  if (hour === 0) return "12 AM";
49201
50322
  if (hour === 12) return "12 PM";
@@ -49222,6 +50343,9 @@ const EventCalendarSSR = (props) => {
49222
50343
  case "timetable":
49223
50344
  content = renderTimetableSsr(display);
49224
50345
  break;
50346
+ case "week-board":
50347
+ content = renderWeekBoardSsr(display);
50348
+ break;
49225
50349
  case "calendar":
49226
50350
  content = renderCalendarSsr(display);
49227
50351
  break;
@@ -49793,6 +50917,7 @@ typedObjectKeys(runtimeBackedBlockServerEntries);
49793
50917
  const serverComponentRegistry = {
49794
50918
  div: DivNode,
49795
50919
  figure: FigureNode,
50920
+ svg: SvgNode,
49796
50921
  section: SectionNode,
49797
50922
  headerSection: header_section_server_default,
49798
50923
  stack: StackNode,
@@ -51293,6 +52418,8 @@ function formatFontFamily(family, fallback = "sans-serif") {
51293
52418
  function generateShadowCssVars(theme) {
51294
52419
  const level = theme.shadow.elevation;
51295
52420
  const soft = theme.shadow.softness !== "crisp";
52421
+ const tintVar = theme.shadow.colorToken ? cssVarValue(theme.shadow.colorToken) : null;
52422
+ const shadowColor = (a) => tintVar ? `rgb(${tintVar} / ${a})` : `rgba(0, 0, 0, ${a})`;
51296
52423
  const base = {
51297
52424
  sm: [{
51298
52425
  y: 1,
@@ -51352,7 +52479,7 @@ function generateShadowCssVars(theme) {
51352
52479
  const blurMul = soft ? 1.2 : 1;
51353
52480
  const alphaMul = soft ? .9 : 1.05;
51354
52481
  const fmt = (layers) => layers.map((l) => {
51355
- 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)))})`;
52482
+ 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))))}`;
51356
52483
  }).join(", ");
51357
52484
  return {
51358
52485
  "--shadow-sm": fmt(base.sm),
@@ -51418,7 +52545,13 @@ function generateAccordionCss({ themeId, theme, accordionSystem }) {
51418
52545
  const questionColumnsLastItemSelector = `${questionColumnsItemSelector}:last-child`;
51419
52546
  const questionColumnsTriggerSelector = `${questionColumnsItemSelector} .accordion-trigger`;
51420
52547
  const rootRules = [];
51421
- if (accordionSystem.background) rootRules.push(`background-color: ${rgbColorValue(accordionSystem.background.colorToken)}`);
52548
+ if (accordionSystem.background) {
52549
+ rootRules.push(`background-color: ${rgbColorValue(accordionSystem.background.colorToken)}`);
52550
+ rootRules.push(...surfaceForegroundDeclarations({
52551
+ text: rgbColorValue(accordionSystem.content.textColorToken),
52552
+ heading: rgbColorValue(accordionSystem.trigger.textColorToken)
52553
+ }));
52554
+ }
51422
52555
  const borderRadius = CORNER_MAP$2[accordionSystem.corners ?? theme.corners];
51423
52556
  rootRules.push(`border-radius: ${borderRadius}`);
51424
52557
  if (accordionSystem.border) {
@@ -53002,7 +54135,7 @@ ${root} .status-error { color: rgb(var(--tb-destructive)); }
53002
54135
  ${root} .status-success { color: rgb(var(--tb-success)); }
53003
54136
  ${root} .status-warning { color: rgb(var(--tb-warning)); }
53004
54137
  ${root} .status-info { color: rgb(var(--tb-info)); }
53005
- ${root} .status-muted { ${statusMutedColorDeclaration()} }`);
54138
+ ${root} .status-muted { color: ${surfaceForegroundValue("supporting")}; }`);
53006
54139
  cssBlocks.push(`/* Alert containers */
53007
54140
  ${root} .alert {
53008
54141
  padding: 1rem;
@@ -53140,6 +54273,9 @@ ${root} .step-connector {
53140
54273
  }
53141
54274
  //#endregion
53142
54275
  //#region ../theme-core/src/typography/generateTypographyCss.ts
54276
+ function globalHeadingColorFallback(level) {
54277
+ return `var(--hc-h${level}, var(--hc-heading, ${DEFAULT_SURFACE_TEXT_COLOR}))`;
54278
+ }
53143
54279
  /**
53144
54280
  * Generate typography CSS for heading elements.
53145
54281
  *
@@ -53168,7 +54304,7 @@ ${root} :where(h1) {
53168
54304
  font-style: var(--fi-h1, var(--fi-heading, normal));
53169
54305
  letter-spacing: var(--ls-h1, var(--ls-heading));
53170
54306
  line-height: var(--lh-h1, var(--lh-heading));
53171
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h1, var(--hc-heading, inherit))));
54307
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(1))};
53172
54308
  }
53173
54309
 
53174
54310
  ${root} :where(h2) {
@@ -53177,7 +54313,7 @@ ${root} :where(h2) {
53177
54313
  font-style: var(--fi-h2, var(--fi-heading, normal));
53178
54314
  letter-spacing: var(--ls-h2, var(--ls-heading));
53179
54315
  line-height: var(--lh-h2, var(--lh-heading));
53180
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h2, var(--hc-heading, inherit))));
54316
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(2))};
53181
54317
  }
53182
54318
 
53183
54319
  ${root} :where(h3) {
@@ -53186,7 +54322,7 @@ ${root} :where(h3) {
53186
54322
  font-style: var(--fi-h3, var(--fi-heading, normal));
53187
54323
  letter-spacing: var(--ls-h3, var(--ls-heading));
53188
54324
  line-height: var(--lh-h3, var(--lh-heading));
53189
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h3, var(--hc-heading, inherit))));
54325
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(3))};
53190
54326
  }
53191
54327
 
53192
54328
  ${root} :where(h4) {
@@ -53195,7 +54331,7 @@ ${root} :where(h4) {
53195
54331
  font-style: var(--fi-h4, var(--fi-heading, normal));
53196
54332
  letter-spacing: var(--ls-h4, var(--ls-heading));
53197
54333
  line-height: var(--lh-h4, var(--lh-heading));
53198
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h4, var(--hc-heading, inherit))));
54334
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(4))};
53199
54335
  }
53200
54336
 
53201
54337
  ${root} :where(h5) {
@@ -53204,7 +54340,7 @@ ${root} :where(h5) {
53204
54340
  font-style: var(--fi-h5, var(--fi-heading, normal));
53205
54341
  letter-spacing: var(--ls-h5, var(--ls-heading));
53206
54342
  line-height: var(--lh-h5, var(--lh-heading));
53207
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h5, var(--hc-heading, inherit))));
54343
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(5))};
53208
54344
  }
53209
54345
 
53210
54346
  ${root} :where(h6) {
@@ -53213,7 +54349,7 @@ ${root} :where(h6) {
53213
54349
  font-style: var(--fi-h6, var(--fi-heading, normal));
53214
54350
  letter-spacing: var(--ls-h6, var(--ls-heading));
53215
54351
  line-height: var(--lh-h6, var(--lh-heading));
53216
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h6, var(--hc-heading, inherit))));
54352
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(6))};
53217
54353
  }`);
53218
54354
  cssBlocks.push(`/* Display variant for hero/splash headings */
53219
54355
  ${root} :where(.heading-display) {
@@ -53612,6 +54748,7 @@ const THEME_RADIUS_CSS_VARS = {
53612
54748
  block: "--rb-block-radius",
53613
54749
  button: "--rb-button-radius",
53614
54750
  input: "--rb-input-radius",
54751
+ panel: "--rb-panel-radius",
53615
54752
  legacyCard: "--radius-card",
53616
54753
  legacyControl: "--radius-control"
53617
54754
  };
@@ -53693,6 +54830,10 @@ ${`:where([data-theme-scope="${themeId}"]) :where([data-block="customer-portal"]
53693
54830
  /* Modal (shared rb-modal classes) */
53694
54831
  --rb-modal-overlay-bg: rgb(var(--tb-text, 0 0 0) / 0.35);
53695
54832
  --rb-modal-bg: var(--cp-card-bg);
54833
+ --rb-modal-text: var(--cp-text);
54834
+ --rb-modal-heading: var(--cp-text);
54835
+ --rb-modal-link: var(--cp-primary);
54836
+ --rb-modal-supporting: var(--cp-muted);
53696
54837
  --rb-modal-border: var(--cp-card-border);
53697
54838
  --rb-modal-shadow: var(--shadow-lg, var(--shadow-elev));
53698
54839
  --rb-modal-radius: var(--cp-card-radius);
@@ -54407,6 +55548,7 @@ function buildThemeCssVars(theme) {
54407
55548
  const inputSystem = resolveThemeInputSystem(theme);
54408
55549
  const blockRadius = mapBlockRadius(theme.corners);
54409
55550
  const buttonRadius = mapButtonRadius(theme.corners);
55551
+ const panelRadius = mapPanelRadius(theme.corners);
54410
55552
  const inputRadius = mapInputRadius(inputSystem.base.corners, theme.corners);
54411
55553
  return {
54412
55554
  ...generateCssVars(theme.palette),
@@ -54415,6 +55557,7 @@ function buildThemeCssVars(theme) {
54415
55557
  ...generateShadowCssVars(theme),
54416
55558
  ["--font-heading"]: formatFontFamily(typography.headings.default.family),
54417
55559
  ["--font-body"]: formatFontFamily(typography.body.family),
55560
+ ["--font-eyebrow"]: typography.eyebrow?.family ? formatFontFamily(typography.eyebrow.family) : "var(--font-body)",
54418
55561
  ["--font-weight-heading"]: mapWeight(typography.headings.default.weight),
54419
55562
  ["--font-weight-body"]: mapWeight(typography.body.weight),
54420
55563
  ...typography.headings.h1.weight ? { ["--fw-h1"]: mapWeight(typography.headings.h1.weight) } : {},
@@ -54465,12 +55608,13 @@ function buildThemeCssVars(theme) {
54465
55608
  ["--fs-h3-fluid"]: sized.h3Fluid,
54466
55609
  ["--fs-h1-display"]: sized.h1Display,
54467
55610
  ["--fs-h1-display-fluid"]: sized.h1DisplayFluid,
54468
- ["--shadow-elev"]: mapShadow(shadow.elevation, shadow.softness, shadow.position || "bottom"),
55611
+ ["--shadow-elev"]: mapShadow(shadow.elevation, shadow.softness, shadow.position || "bottom", shadow.colorToken),
54469
55612
  ["--space-mult"]: mapSpaceMult(space),
54470
55613
  ["--motion-duration"]: mapMotionDuration(motion.level),
54471
55614
  ["--motion-ease"]: mapMotionEasing(motion.easing ?? "standard"),
54472
55615
  ["--rt-space-y"]: mapRhythm(space),
54473
55616
  [THEME_RADIUS_CSS_VARS.block]: blockRadius,
55617
+ [THEME_RADIUS_CSS_VARS.panel]: panelRadius,
54474
55618
  ["--rb-block-transition"]: mapBlockTransition(motion.level),
54475
55619
  [THEME_RADIUS_CSS_VARS.button]: buttonRadius,
54476
55620
  ["--rb-border-width"]: mapBorderWidth(border.width),
@@ -54524,20 +55668,22 @@ function mapWeight(w) {
54524
55668
  bold: 700
54525
55669
  }[w];
54526
55670
  }
54527
- function mapShadow(level, softness, position = "bottom") {
55671
+ function mapShadow(level, softness, position = "bottom", colorToken) {
54528
55672
  if (level === "none") return "none";
55673
+ const tintVar = colorToken ? cssVarValue(colorToken) : null;
55674
+ const shadowColor = (a) => tintVar ? `rgb(${tintVar} / ${a})` : `rgba(0,0,0,${a})`;
54529
55675
  const offset = {
54530
55676
  low: 2,
54531
55677
  medium: 4,
54532
55678
  high: 6
54533
55679
  }[level];
54534
55680
  const xOffset = position === "bottom-right" ? offset : 0;
54535
- if (softness === "hard") return `${xOffset}px ${offset}px 0 0 rgba(0,0,0,1)`;
55681
+ if (softness === "hard") return `${xOffset}px ${offset}px 0 0 ${shadowColor("1")}`;
54536
55682
  const soft = softness !== "crisp";
54537
55683
  switch (level) {
54538
- case "low": return soft ? `${xOffset}px 1px 2px rgba(0,0,0,.06)` : `${xOffset}px 1px 1px rgba(0,0,0,.12)`;
54539
- case "medium": return soft ? `${xOffset}px 10px 20px rgba(0,0,0,.08)` : `${xOffset}px 6px 12px rgba(0,0,0,.16)`;
54540
- case "high": return soft ? `${xOffset}px 25px 50px rgba(0,0,0,.18)` : `${xOffset}px 20px 30px rgba(0,0,0,.28)`;
55684
+ case "low": return soft ? `${xOffset}px 1px 2px ${shadowColor(".06")}` : `${xOffset}px 1px 1px ${shadowColor(".12")}`;
55685
+ case "medium": return soft ? `${xOffset}px 10px 20px ${shadowColor(".08")}` : `${xOffset}px 6px 12px ${shadowColor(".16")}`;
55686
+ case "high": return soft ? `${xOffset}px 25px 50px ${shadowColor(".18")}` : `${xOffset}px 20px 30px ${shadowColor(".28")}`;
54541
55687
  }
54542
55688
  }
54543
55689
  function mapSpaceMult(space) {
@@ -54584,6 +55730,21 @@ function mapBlockRadius(corners) {
54584
55730
  }[corners];
54585
55731
  }
54586
55732
  /**
55733
+ * Map corner style to large-panel radius CSS value (Tier 2 `--rb-panel-radius`).
55734
+ * Derived from the same `corners` enum, one step more generous than the
55735
+ * block/card radius so large surfaces can carry a softer corner without a new
55736
+ * schema field. `rounded` lands on 20px (1.25rem), matching the physio mockup's
55737
+ * large-panel radius.
55738
+ */
55739
+ function mapPanelRadius(corners) {
55740
+ return {
55741
+ square: "0",
55742
+ soft: "0.75rem",
55743
+ rounded: "1.25rem",
55744
+ pill: "2rem"
55745
+ }[corners];
55746
+ }
55747
+ /**
54587
55748
  * Map motion level to block transition duration.
54588
55749
  * These are Tier 2 vars used consistently across blocks.
54589
55750
  */
@@ -55567,8 +56728,12 @@ const containerResponsiveThemeCss = `/*
55567
56728
  text-transform: uppercase;
55568
56729
  }
55569
56730
 
55570
- /* Eyebrow text - small uppercase labels for section introductions */
56731
+ /* Eyebrow text - small uppercase labels for section introductions.
56732
+ font-family resolves to the optional eyebrow/mono font role, falling back
56733
+ to the body font when the theme sets none (so existing themes are
56734
+ unchanged). */
55571
56735
  .rb-eyebrow {
56736
+ font-family: var(--font-eyebrow, var(--font-body));
55572
56737
  font-size: 0.875rem;
55573
56738
  font-weight: 600;
55574
56739
  letter-spacing: 0.025em;
@@ -55583,11 +56748,11 @@ const containerResponsiveThemeCss = `/*
55583
56748
  width: fit-content;
55584
56749
  align-items: center;
55585
56750
  justify-content: center;
55586
- border: 1px solid color-mix(in srgb, var(--section-text-color, rgb(var(--tb-text))) 46%, transparent);
56751
+ border: 1px solid color-mix(in srgb, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 46%, transparent);
55587
56752
  border-radius: 9999px;
55588
- background: color-mix(in srgb, var(--section-text-color, rgb(var(--tb-text))) 14%, transparent);
56753
+ background: color-mix(in srgb, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 14%, transparent);
55589
56754
  padding: 0.5rem 1rem;
55590
- color: var(--section-text-color, rgb(var(--tb-text)));
56755
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
55591
56756
  line-height: 1;
55592
56757
  }
55593
56758
  .rb-eyebrow-treatment-pill.rb-eyebrow-treatment-accent {
@@ -56089,6 +57254,58 @@ const containerResponsiveThemeCss = `/*
56089
57254
  }
56090
57255
  }
56091
57256
 
57257
+ }
57258
+
57259
+ /*
57260
+ * Responsive fractional grid tracks (rb-grid-tracks).
57261
+ *
57262
+ * Asymmetric AND responsive grid-template-columns, driven by inline custom
57263
+ * properties the Grid primitive sets per breakpoint from string track
57264
+ * templates:
57265
+ * --rb-gtc-base / --rb-gtc-sm / --rb-gtc-md / --rb-gtc-lg / --rb-gtc-xl
57266
+ * Each breakpoint falls back to the next smaller one, so only the breakpoints
57267
+ * an author actually sets take effect (e.g. base '1fr' + lg '1fr 0.9fr'
57268
+ * collapses to a single column below lg). Authored here (not per node) so the
57269
+ * @media -> @container conversion (container responsive mode) applies too.
57270
+ */
57271
+ .rb-grid-tracks {
57272
+ grid-template-columns: var(--rb-gtc-base, 1fr);
57273
+ }
57274
+ @container rb-site (min-width: 640px) {
57275
+
57276
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57277
+ .rb-grid-tracks {
57278
+ grid-template-columns: var(--rb-gtc-sm, var(--rb-gtc-base, 1fr));
57279
+ }
57280
+ }
57281
+
57282
+ }
57283
+ @container rb-site (min-width: 768px) {
57284
+
57285
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57286
+ .rb-grid-tracks {
57287
+ grid-template-columns: var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr)));
57288
+ }
57289
+ }
57290
+
57291
+ }
57292
+ @container rb-site (min-width: 1024px) {
57293
+
57294
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57295
+ .rb-grid-tracks {
57296
+ grid-template-columns: var(--rb-gtc-lg, var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr))));
57297
+ }
57298
+ }
57299
+
57300
+ }
57301
+ @container rb-site (min-width: 1280px) {
57302
+
57303
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57304
+ .rb-grid-tracks {
57305
+ grid-template-columns: var(--rb-gtc-xl, var(--rb-gtc-lg, var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr)))));
57306
+ }
57307
+ }
57308
+
56092
57309
  }
56093
57310
 
56094
57311
  /* Alignment */
@@ -57293,6 +58510,11 @@ const containerResponsiveThemeCss = `/*
57293
58510
  :where(.theme-scope) .rb-modal {
57294
58511
  width: min(var(--rb-modal-max-width, 560px), 100%);
57295
58512
  background: var(--rb-modal-bg, rgb(var(--tb-surface)));
58513
+ color: var(--rb-modal-text, rgb(var(--tb-text, 17 24 39)));
58514
+ --rb-surface-text-color: var(--rb-modal-text, rgb(var(--tb-text, 17 24 39)));
58515
+ --rb-surface-heading-color: var(--rb-modal-heading, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
58516
+ --rb-surface-link-color: var(--rb-modal-link, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
58517
+ --rb-surface-supporting-color: var(--rb-modal-supporting, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
57296
58518
  border: 1px solid var(--rb-modal-border, rgb(var(--tb-border)));
57297
58519
  border-radius: var(--rb-modal-radius, var(--radius-card));
57298
58520
  box-shadow: var(--rb-modal-shadow, var(--shadow-lg, var(--shadow-elev)));
@@ -57312,7 +58534,7 @@ const containerResponsiveThemeCss = `/*
57312
58534
  font-family: var(--font-heading);
57313
58535
  font-size: 1.0625rem;
57314
58536
  font-weight: 600;
57315
- color: rgb(var(--tb-text));
58537
+ color: var(--rb-surface-heading-color, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))));
57316
58538
  }
57317
58539
 
57318
58540
  :where(.theme-scope) .rb-modal__close {
@@ -57322,7 +58544,7 @@ const containerResponsiveThemeCss = `/*
57322
58544
  height: 2.25rem;
57323
58545
  padding: 0;
57324
58546
  line-height: 1;
57325
- color: rgb(var(--tb-mutedText));
58547
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
57326
58548
  cursor: pointer;
57327
58549
  display: grid;
57328
58550
  place-items: center;
@@ -57330,7 +58552,7 @@ const containerResponsiveThemeCss = `/*
57330
58552
  }
57331
58553
 
57332
58554
  :where(.theme-scope) .rb-modal__close:hover {
57333
- color: rgb(var(--tb-text));
58555
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
57334
58556
  background: rgba(var(--tb-border), 0.2);
57335
58557
  }
57336
58558
 
@@ -57341,7 +58563,7 @@ const containerResponsiveThemeCss = `/*
57341
58563
 
57342
58564
  :where(.theme-scope) .rb-modal__body {
57343
58565
  padding: 0.875rem 1rem 1rem;
57344
- color: rgb(var(--tb-text));
58566
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
57345
58567
  font-size: 0.9375rem;
57346
58568
  line-height: 1.45;
57347
58569
  }
@@ -57501,7 +58723,7 @@ const containerResponsiveThemeCss = `/*
57501
58723
  & h6 {
57502
58724
  font-family: var(--font-heading);
57503
58725
  /* Color is set by generateTypographyCss.ts with full cascade:
57504
- --section-heading-color -> --section-text-color -> --hc-hN -> --hc-heading -> inherit */
58726
+ --rb-surface-heading-color -> --rb-surface-text-color -> --hc-hN -> --hc-heading -> global text */
57505
58727
  font-weight: var(--font-weight-heading);
57506
58728
  text-transform: var(--tt-heading);
57507
58729
  font-variant-caps: var(--fv-heading);
@@ -57649,7 +58871,7 @@ const containerResponsiveThemeCss = `/*
57649
58871
 
57650
58872
  /* Neutral prose uses theme text color by default */
57651
58873
  :where(.theme-scope) .rb-prose-neutral {
57652
- color: var(--section-text-color, rgb(var(--tb-text)));
58874
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
57653
58875
  }
57654
58876
 
57655
58877
  :where(.theme-scope) .rb-prose p,
@@ -57683,8 +58905,14 @@ const containerResponsiveThemeCss = `/*
57683
58905
 
57684
58906
  :where(.theme-scope) .rb-prose a {
57685
58907
  --rb-prose-effective-link-color: var(
57686
- --section-link-color,
57687
- var(--rb-prose-link-color, rgb(var(--tb-primary)))
58908
+ --rb-surface-link-color,
58909
+ var(
58910
+ --rb-surface-text-color,
58911
+ var(
58912
+ --rb-prose-link-color,
58913
+ rgb(var(--tb-primary, var(--tb-text, 17 24 39)))
58914
+ )
58915
+ )
57688
58916
  );
57689
58917
  color: var(--rb-prose-effective-link-color);
57690
58918
  text-decoration-line: var(--rb-prose-link-decoration-line, underline);
@@ -57695,14 +58923,11 @@ const containerResponsiveThemeCss = `/*
57695
58923
  );
57696
58924
  text-underline-offset: var(--rb-prose-link-underline-offset, 0.14em);
57697
58925
  text-decoration-color: var(
57698
- --section-link-decoration-color,
57699
- var(
57700
- --rb-prose-link-decoration-color,
57701
- color-mix(
57702
- in srgb,
57703
- var(--rb-prose-effective-link-color) 35%,
57704
- transparent
57705
- )
58926
+ --rb-prose-link-decoration-color,
58927
+ color-mix(
58928
+ in srgb,
58929
+ var(--rb-prose-effective-link-color) 35%,
58930
+ transparent
57706
58931
  )
57707
58932
  );
57708
58933
  transition:
@@ -57726,7 +58951,7 @@ const containerResponsiveThemeCss = `/*
57726
58951
 
57727
58952
  :where(.theme-scope) .rb-prose blockquote {
57728
58953
  /* Blockquotes use muted text color */
57729
- color: var(--section-supporting-color, rgb(var(--tb-mutedText)));
58954
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
57730
58955
  border-left-color: rgb(var(--tb-border));
57731
58956
  }
57732
58957
 
@@ -58760,6 +59985,10 @@ const containerResponsiveThemeCss = `/*
58760
59985
  :where(.theme-scope) .card-surface {
58761
59986
  background: rgb(var(--tb-surface));
58762
59987
  color: rgb(var(--tb-onSurface, var(--tb-text)));
59988
+ --rb-surface-text-color: rgb(var(--tb-onSurface, var(--tb-text)));
59989
+ --rb-surface-heading-color: rgb(var(--tb-onSurface, var(--tb-text)));
59990
+ --rb-surface-link-color: rgb(var(--tb-onSurface, var(--tb-text)));
59991
+ --rb-surface-supporting-color: rgb(var(--tb-onSurface, var(--tb-text)));
58763
59992
  border: 1px solid rgb(var(--tb-border));
58764
59993
  border-radius: var(--radius-card);
58765
59994
  box-shadow: var(--shadow-elev);
@@ -58903,6 +60132,10 @@ const containerResponsiveThemeCss = `/*
58903
60132
  padding: clamp(1rem, 2vw, 1.5rem);
58904
60133
  background: var(--rb-reg-surface-bg, rgb(var(--tb-surface, 248 250 252)));
58905
60134
  color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60135
+ --rb-surface-text-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60136
+ --rb-surface-heading-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60137
+ --rb-surface-link-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60138
+ --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)))));
58906
60139
  transition: border-color var(--rb-block-transition, 150ms) ease;
58907
60140
  width: 100%;
58908
60141
  max-width: 800px;
@@ -61990,6 +63223,350 @@ const containerResponsiveThemeCss = `/*
61990
63223
  }
61991
63224
  }
61992
63225
 
63226
+ /* -----------------------------------------------------------------------------
63227
+ * Week Board View
63228
+ * Compact "class day-board": day columns side by side, one card per class,
63229
+ * three-state availability (available / filling / waitlist), and a footer CTA.
63230
+ * ----------------------------------------------------------------------------- */
63231
+
63232
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board {
63233
+ /* Status accent tokens — map the three availability states onto theme colours.
63234
+ * available -> secondary, filling -> accent, waitlist -> muted text. */
63235
+ --ec-status-available: rgb(var(--tb-secondary));
63236
+ --ec-status-filling: rgb(var(--tb-accent));
63237
+ --ec-status-waitlist: rgba(var(--tb-text), 0.4);
63238
+ --event-week-board-cols: 7;
63239
+
63240
+ border: 1px solid var(--ec-border);
63241
+ border-radius: var(--rb-block-radius);
63242
+ background-color: var(--ec-surface);
63243
+ color: var(--ec-text);
63244
+ overflow: hidden;
63245
+ box-shadow: 0 14px 34px -24px rgba(var(--tb-text), 0.35);
63246
+ }
63247
+
63248
+ /* Top bar: title + legend */
63249
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-top {
63250
+ display: flex;
63251
+ align-items: center;
63252
+ justify-content: space-between;
63253
+ gap: 1rem;
63254
+ flex-wrap: wrap;
63255
+ padding: 1.1rem 1.5rem;
63256
+ background-color: var(--ec-header-bg);
63257
+ border-bottom: 1px solid var(--ec-border-subtle);
63258
+ }
63259
+
63260
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-title {
63261
+ font-size: 0.8125rem;
63262
+ font-weight: 600;
63263
+ letter-spacing: 0.12em;
63264
+ text-transform: uppercase;
63265
+ color: rgb(var(--tb-primary));
63266
+ }
63267
+
63268
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend {
63269
+ display: flex;
63270
+ align-items: center;
63271
+ gap: 1.1rem;
63272
+ flex-wrap: wrap;
63273
+ font-size: 0.6875rem;
63274
+ letter-spacing: 0.03em;
63275
+ opacity: 0.75;
63276
+ }
63277
+
63278
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-item {
63279
+ display: inline-flex;
63280
+ align-items: center;
63281
+ gap: 0.4rem;
63282
+ }
63283
+
63284
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot {
63285
+ width: 0.55rem;
63286
+ height: 0.55rem;
63287
+ border-radius: 50%;
63288
+ display: inline-block;
63289
+ }
63290
+
63291
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="available"] {
63292
+ background-color: var(--ec-status-available);
63293
+ }
63294
+
63295
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="filling"] {
63296
+ background-color: var(--ec-status-filling);
63297
+ }
63298
+
63299
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="waitlist"] {
63300
+ background-color: var(--ec-status-waitlist);
63301
+ }
63302
+
63303
+ /* Day columns grid */
63304
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-grid {
63305
+ display: grid;
63306
+ grid-template-columns: repeat(var(--event-week-board-cols), minmax(0, 1fr));
63307
+ }
63308
+
63309
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day {
63310
+ display: flex;
63311
+ flex-direction: column;
63312
+ min-height: 15rem;
63313
+ /* Tabular column rule. Driven by the theme border tokens so borderless site
63314
+ * styles (border.width: 'none' -> --rb-border-width: 0) render no grid at all,
63315
+ * while hairline/thin/thick themes get a crisp full-strength divider. */
63316
+ border-right: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63317
+ }
63318
+
63319
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day:last-child {
63320
+ border-right: none;
63321
+ }
63322
+
63323
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-head {
63324
+ display: flex;
63325
+ align-items: baseline;
63326
+ gap: 0.4rem;
63327
+ padding: 0.9rem 0.9rem 0.65rem;
63328
+ /* Header-row underline — same border-token gating as the column rules. */
63329
+ border-bottom: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63330
+ }
63331
+
63332
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--today .event-week-board-day-head {
63333
+ background-color: var(--ec-today-bg);
63334
+ }
63335
+
63336
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-abbr {
63337
+ font-size: 0.6875rem;
63338
+ font-weight: 600;
63339
+ letter-spacing: 0.1em;
63340
+ text-transform: uppercase;
63341
+ opacity: 0.65;
63342
+ }
63343
+
63344
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--today .event-week-board-day-abbr {
63345
+ opacity: 1;
63346
+ color: var(--ec-today-text);
63347
+ }
63348
+
63349
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-num {
63350
+ font-size: 0.8125rem;
63351
+ font-weight: 600;
63352
+ opacity: 0.5;
63353
+ }
63354
+
63355
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-body {
63356
+ display: flex;
63357
+ flex-direction: column;
63358
+ gap: 0.65rem;
63359
+ padding: 0.9rem;
63360
+ flex: 1;
63361
+ }
63362
+
63363
+ /* Class card */
63364
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
63365
+ display: flex;
63366
+ flex-direction: column;
63367
+ gap: 0.45rem;
63368
+ padding: 0.8rem 0.85rem;
63369
+ border: 1px solid var(--ec-border-subtle);
63370
+ border-left: 3px solid var(--ec-status-available);
63371
+ border-radius: calc(var(--rb-block-radius) / 1.5);
63372
+ background-color: var(--ec-surface);
63373
+ color: inherit;
63374
+ text-decoration: none;
63375
+ transition:
63376
+ transform var(--rb-block-transition) ease,
63377
+ box-shadow var(--rb-block-transition) ease,
63378
+ border-color var(--rb-block-transition) ease;
63379
+ }
63380
+
63381
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card[data-status="filling"] {
63382
+ border-left-color: var(--ec-status-filling);
63383
+ }
63384
+
63385
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card[data-status="waitlist"] {
63386
+ border-left-color: var(--ec-status-waitlist);
63387
+ }
63388
+
63389
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card:hover {
63390
+ transform: translateY(-2px);
63391
+ box-shadow: 0 6px 16px -8px rgba(var(--tb-text), 0.28);
63392
+ border-color: rgba(var(--tb-primary), 0.4);
63393
+ }
63394
+
63395
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card:focus-visible {
63396
+ outline: 2px solid rgb(var(--tb-primary));
63397
+ outline-offset: 2px;
63398
+ }
63399
+
63400
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-title {
63401
+ font-family: var(--font-heading, inherit);
63402
+ font-size: 0.9375rem;
63403
+ font-weight: 600;
63404
+ line-height: 1.2;
63405
+ }
63406
+
63407
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-time {
63408
+ font-size: 0.75rem;
63409
+ letter-spacing: 0.01em;
63410
+ opacity: 0.7;
63411
+ }
63412
+
63413
+ /* Capacity pill */
63414
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill {
63415
+ align-self: flex-start;
63416
+ display: inline-flex;
63417
+ align-items: center;
63418
+ padding: 0.15rem 0.6rem;
63419
+ border-radius: 999px;
63420
+ font-size: 0.6875rem;
63421
+ font-weight: 600;
63422
+ letter-spacing: 0.02em;
63423
+ /* Available (default): a calm neutral/muted pill — not a coloured outline.
63424
+ * Filling -> amber and waitlist -> muted outline are handled by the
63425
+ * data-status overrides below. */
63426
+ border: 1px solid var(--ec-border);
63427
+ background-color: rgba(var(--tb-text), 0.06);
63428
+ color: rgba(var(--tb-text), 0.62);
63429
+ border-color: var(--ec-border);
63430
+ }
63431
+
63432
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill[data-status="filling"] {
63433
+ background-color: rgba(var(--tb-accent), 0.14);
63434
+ color: var(--ec-status-filling);
63435
+ border-color: rgba(var(--tb-accent), 0.32);
63436
+ }
63437
+
63438
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill[data-status="waitlist"] {
63439
+ background-color: transparent;
63440
+ color: rgba(var(--tb-text), 0.6);
63441
+ border-color: var(--ec-border);
63442
+ }
63443
+
63444
+ /* Footer */
63445
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-foot {
63446
+ display: flex;
63447
+ align-items: center;
63448
+ justify-content: space-between;
63449
+ gap: 1rem;
63450
+ flex-wrap: wrap;
63451
+ padding: 1.1rem 1.5rem;
63452
+ background-color: var(--ec-header-bg);
63453
+ border-top: 1px solid var(--ec-border-subtle);
63454
+ }
63455
+
63456
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-foot-note {
63457
+ margin: 0;
63458
+ font-size: 0.875rem;
63459
+ opacity: 0.75;
63460
+ }
63461
+
63462
+ /* Responsive: collapse day columns to a stacked list on narrow viewports */
63463
+ @container rb-site (max-width: 1023px) {
63464
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
63465
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-grid {
63466
+ grid-template-columns: 1fr;
63467
+ }
63468
+
63469
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day {
63470
+ min-height: 0;
63471
+ border-right: none;
63472
+ border-bottom: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63473
+ }
63474
+
63475
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day:last-child {
63476
+ border-bottom: none;
63477
+ }
63478
+
63479
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--empty {
63480
+ display: none;
63481
+ }
63482
+
63483
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-head {
63484
+ border-bottom: none;
63485
+ padding-bottom: 0.25rem;
63486
+ }
63487
+
63488
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-body {
63489
+ flex-direction: row;
63490
+ flex-wrap: wrap;
63491
+ padding-top: 0.35rem;
63492
+ }
63493
+
63494
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
63495
+ flex: 1 1 14rem;
63496
+ }
63497
+ }
63498
+ }
63499
+
63500
+ @container rb-site (max-width: 639px) {
63501
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
63502
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend {
63503
+ display: none;
63504
+ }
63505
+
63506
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
63507
+ flex: 1 1 100%;
63508
+ }
63509
+ }
63510
+ }
63511
+
63512
+ /* -----------------------------------------------------------------------------
63513
+ * Week-board section band
63514
+ * Give the whole week-board section (head + panel) a subtle surface band so it
63515
+ * reads as a distinct zone from the page — the "testimonials band" pattern.
63516
+ * Painted as a pseudo-element above the section's fallback background layer
63517
+ * (z-index -10) but behind the content, so no block-content wiring is needed.
63518
+ * Surface + hairline rules are token-referenced: on a borderless site style
63519
+ * (--rb-border-width: 0) the band keeps its surface tint but drops the rules.
63520
+ * ----------------------------------------------------------------------------- */
63521
+ :where(.theme-scope) [data-slot="section"]:has(.event-calendar--week-board)::before {
63522
+ content: "";
63523
+ position: absolute;
63524
+ inset: 0;
63525
+ z-index: -9;
63526
+ background-color: rgb(var(--tb-surfaceAlt));
63527
+ border-block: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63528
+ pointer-events: none;
63529
+ }
63530
+
63531
+ /* -----------------------------------------------------------------------------
63532
+ * Event section head — split (heading left, intro right)
63533
+ * Rendered by the block frame (outside [data-block="event-calendar"]) via
63534
+ * eventSectionHeaderLayoutNodes({ splitClassName }). Lays the heading and its
63535
+ * supporting intro out as a two-column head on wide viewports, stacking on
63536
+ * narrow ones. The section band above provides the distinct background.
63537
+ * ----------------------------------------------------------------------------- */
63538
+ :where(.theme-scope) .event-section-head--split {
63539
+ display: flex;
63540
+ align-items: flex-end;
63541
+ justify-content: space-between;
63542
+ gap: clamp(1.5rem, 4vw, 3.5rem);
63543
+ flex-wrap: wrap;
63544
+ }
63545
+
63546
+ :where(.theme-scope) .event-section-head--split > :where(h1, h2, h3) {
63547
+ margin: 0;
63548
+ }
63549
+
63550
+ :where(.theme-scope) .event-section-head--split > .rb-prose {
63551
+ flex: 1 1 22rem;
63552
+ max-width: 34rem;
63553
+ margin: 0;
63554
+ }
63555
+
63556
+ @container rb-site (max-width: 767px) {
63557
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
63558
+ :where(.theme-scope) .event-section-head--split {
63559
+ flex-direction: column;
63560
+ align-items: flex-start;
63561
+ gap: 0.75rem;
63562
+ }
63563
+
63564
+ :where(.theme-scope) .event-section-head--split > .rb-prose {
63565
+ flex-basis: auto;
63566
+ }
63567
+ }
63568
+ }
63569
+
61993
63570
  /* -----------------------------------------------------------------------------
61994
63571
  * Event Calendar Modals
61995
63572
  * Modal overlays for event details and day events list
@@ -62417,7 +63994,7 @@ const containerResponsiveThemeCss = `/*
62417
63994
  }
62418
63995
 
62419
63996
  :where(.theme-scope) .rb-inline-text-accent-contrast {
62420
- color: var(--section-text-color, rgb(var(--tb-text)));
63997
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
62421
63998
  }
62422
63999
 
62423
64000
  :where(.theme-scope) .rb-inline-text-highlight {
@@ -62428,7 +64005,7 @@ const containerResponsiveThemeCss = `/*
62428
64005
  }
62429
64006
 
62430
64007
  :where(.theme-scope) .rb-inline-text-highlight-soft {
62431
- background: color-mix(in oklab, var(--section-text-color, rgb(var(--tb-text))) 14%, transparent);
64008
+ background: color-mix(in oklab, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 14%, transparent);
62432
64009
  }
62433
64010
 
62434
64011
  :where(.theme-scope) .rb-inline-text-highlight-accent {
@@ -62447,7 +64024,7 @@ const containerResponsiveThemeCss = `/*
62447
64024
 
62448
64025
  /* Eyebrow - color only (typography via .rb-eyebrow) */
62449
64026
  :where(.theme-scope) :where([data-block="hero"]) .hero-eyebrow:not(.rb-eyebrow-treatment-accent) {
62450
- color: var(--section-text-color, rgb(var(--tb-text)));
64027
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
62451
64028
  }
62452
64029
 
62453
64030
  /* Subheadline - supporting text below headline */
@@ -62457,7 +64034,7 @@ const containerResponsiveThemeCss = `/*
62457
64034
 
62458
64035
  :where(.theme-scope) :where([data-block="hero"]) .hero-subheadline {
62459
64036
  font-size: 1.125rem;
62460
- color: var(--section-supporting-color, var(--section-text-color, rgb(var(--tb-text))));
64037
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
62461
64038
  }
62462
64039
 
62463
64040
  @container rb-site (min-width: 640px) {
@@ -62548,7 +64125,7 @@ const containerResponsiveThemeCss = `/*
62548
64125
 
62549
64126
  /* CTA content/body text */
62550
64127
  :where(.theme-scope) :where([data-block="cta-full"]) .cta-content {
62551
- color: var(--section-supporting-color, rgb(var(--tb-mutedText)));
64128
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
62552
64129
  }
62553
64130
 
62554
64131
  /* CTA button row container */
@@ -62648,7 +64225,7 @@ const containerResponsiveThemeCss = `/*
62648
64225
  /* Form intro text */
62649
64226
  :where(.theme-scope) :where([data-block="form"]) .form-copy-intro {
62650
64227
  font-size: 1rem;
62651
- color: var(--section-text-color, rgb(var(--tb-text)));
64228
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
62652
64229
  }
62653
64230
 
62654
64231
  /* =============================================================================
@@ -63007,6 +64584,10 @@ const containerResponsiveThemeCss = `/*
63007
64584
  --rb-modal-overlay-bg: rgb(var(--tb-text, 0 0 0) / 0.6);
63008
64585
  --rb-modal-shadow: var(--shadow-lg, var(--shadow-elev));
63009
64586
  --rb-modal-z-index: 20000;
64587
+ --rb-modal-text: rgb(var(--tb-text, 17 24 39));
64588
+ --rb-modal-heading: rgb(var(--tb-text, 17 24 39));
64589
+ --rb-modal-link: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
64590
+ --rb-modal-supporting: rgb(var(--tb-mutedText, 100 116 139));
63010
64591
  --shop-card-radius: var(--rb-block-radius, var(--radius-card, 0.75rem));
63011
64592
  --shop-control-radius: var(--rb-input-radius, var(--radius-control, 0.375rem));
63012
64593
  }
@@ -63064,6 +64645,11 @@ const containerResponsiveThemeCss = `/*
63064
64645
  /* Product card */
63065
64646
  :where(.theme-scope) .shop__card {
63066
64647
  background: rgb(var(--tb-surface));
64648
+ color: rgb(var(--tb-text, 17 24 39));
64649
+ --rb-surface-text-color: rgb(var(--tb-text, 17 24 39));
64650
+ --rb-surface-heading-color: rgb(var(--tb-text, 17 24 39));
64651
+ --rb-surface-link-color: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
64652
+ --rb-surface-supporting-color: rgb(var(--tb-mutedText, 100 116 139));
63067
64653
  border: 1px solid rgb(var(--tb-border));
63068
64654
  border-radius: var(--shop-card-radius);
63069
64655
  padding: 1.5rem;
@@ -63218,6 +64804,11 @@ const containerResponsiveThemeCss = `/*
63218
64804
  border: 1px solid rgb(var(--tb-border));
63219
64805
  border-radius: var(--shop-card-radius);
63220
64806
  background: rgb(var(--tb-surface));
64807
+ color: rgb(var(--tb-text, 17 24 39));
64808
+ --rb-surface-text-color: rgb(var(--tb-text, 17 24 39));
64809
+ --rb-surface-heading-color: rgb(var(--tb-text, 17 24 39));
64810
+ --rb-surface-link-color: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
64811
+ --rb-surface-supporting-color: rgb(var(--tb-mutedText, 100 116 139));
63221
64812
  }
63222
64813
 
63223
64814
  :where(.theme-scope) :where([data-block="shop"]) .rb-state-icon--success {
@@ -63290,6 +64881,11 @@ const containerResponsiveThemeCss = `/*
63290
64881
  margin: 0 auto;
63291
64882
  padding: 2rem;
63292
64883
  background: var(--cp-card-bg);
64884
+ color: var(--cp-text);
64885
+ --rb-surface-text-color: var(--cp-text);
64886
+ --rb-surface-heading-color: var(--cp-text);
64887
+ --rb-surface-link-color: var(--cp-primary);
64888
+ --rb-surface-supporting-color: var(--cp-muted);
63293
64889
  border: 1px solid var(--cp-card-border);
63294
64890
  border-radius: var(--cp-card-radius);
63295
64891
  }
@@ -63418,6 +65014,11 @@ const containerResponsiveThemeCss = `/*
63418
65014
  padding: 3rem 2rem;
63419
65015
  text-align: center;
63420
65016
  background: var(--cp-card-bg);
65017
+ color: var(--cp-text);
65018
+ --rb-surface-text-color: var(--cp-text);
65019
+ --rb-surface-heading-color: var(--cp-text);
65020
+ --rb-surface-link-color: var(--cp-primary);
65021
+ --rb-surface-supporting-color: var(--cp-muted);
63421
65022
  border: 1px dashed var(--cp-card-border);
63422
65023
  border-radius: var(--cp-card-radius);
63423
65024
  }
@@ -63430,6 +65031,11 @@ const containerResponsiveThemeCss = `/*
63430
65031
  /* Card */
63431
65032
  :where(.theme-scope) :where([data-block="customer-portal"]) .cp-card {
63432
65033
  background: var(--cp-card-bg);
65034
+ color: var(--cp-text);
65035
+ --rb-surface-text-color: var(--cp-text);
65036
+ --rb-surface-heading-color: var(--cp-text);
65037
+ --rb-surface-link-color: var(--cp-primary);
65038
+ --rb-surface-supporting-color: var(--cp-muted);
63433
65039
  border: 1px solid var(--cp-card-border);
63434
65040
  border-radius: var(--cp-card-radius);
63435
65041
  padding: 1.25rem;
@@ -63594,6 +65200,11 @@ const containerResponsiveThemeCss = `/*
63594
65200
  /* Table */
63595
65201
  :where(.theme-scope) :where([data-block="customer-portal"]) .cp-table-wrap {
63596
65202
  background: var(--cp-card-bg);
65203
+ color: var(--cp-text);
65204
+ --rb-surface-text-color: var(--cp-text);
65205
+ --rb-surface-heading-color: var(--cp-text);
65206
+ --rb-surface-link-color: var(--cp-primary);
65207
+ --rb-surface-supporting-color: var(--cp-muted);
63597
65208
  border: 1px solid var(--cp-card-border);
63598
65209
  border-radius: var(--cp-card-radius);
63599
65210
  }
@@ -64155,6 +65766,143 @@ const containerResponsiveThemeCss = `/*
64155
65766
  }
64156
65767
 
64157
65768
 
65769
+ /* =============================================================================
65770
+ * Variant: featured-editorial
65771
+ *
65772
+ * An asymmetric layout: a large featured (spruce-ink) quote card beside a
65773
+ * column of stacked white cards. Renders NO avatars. Colours are palette
65774
+ * tokens only. Node structure is built in testimonialsCarousel.ts.
65775
+ * ============================================================================= */
65776
+
65777
+ /* Section head: heading left, lead-in line to the right; stacks on mobile. */
65778
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-head {
65779
+ display: flex;
65780
+ flex-direction: column;
65781
+ align-items: flex-start;
65782
+ gap: 0.875rem;
65783
+ }
65784
+
65785
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-leadin {
65786
+ max-width: 30rem;
65787
+ }
65788
+
65789
+ @container rb-site (min-width: 768px) {
65790
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
65791
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-head {
65792
+ flex-direction: row;
65793
+ align-items: flex-end;
65794
+ justify-content: space-between;
65795
+ gap: 1.5rem;
65796
+ }
65797
+ }
65798
+ }
65799
+
65800
+ /* Card grid: featured (wider) beside the stacked column. Single column on
65801
+ * small screens. */
65802
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-grid {
65803
+ display: grid;
65804
+ grid-template-columns: minmax(0, 1fr);
65805
+ gap: 1.25rem;
65806
+ align-items: stretch;
65807
+ }
65808
+
65809
+ @container rb-site (min-width: 1024px) {
65810
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
65811
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-grid {
65812
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
65813
+ }
65814
+ }
65815
+ }
65816
+
65817
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-col {
65818
+ display: grid;
65819
+ gap: 1.25rem;
65820
+ align-content: start;
65821
+ }
65822
+
65823
+ /* Base card (white surface) */
65824
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card {
65825
+ display: flex;
65826
+ flex-direction: column;
65827
+ height: 100%;
65828
+ border-radius: var(--rb-block-radius);
65829
+ padding: 1.875rem 1.875rem 1.625rem;
65830
+ background: rgb(var(--tb-surface));
65831
+ border: 1px solid rgba(var(--tb-border), 0.9);
65832
+ box-shadow: 0 1px 2px rgba(var(--tb-shadow, 0 0 0), 0.05);
65833
+ }
65834
+
65835
+ /* Featured (spruce-ink) card */
65836
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature {
65837
+ padding: 2.5rem;
65838
+ background: rgb(var(--tb-primary));
65839
+ border-color: transparent;
65840
+ color: rgb(var(--tb-onPrimary));
65841
+ }
65842
+
65843
+ /* Amber quote mark, oversized serif */
65844
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-qmark {
65845
+ display: block;
65846
+ height: 1.6rem;
65847
+ font-family: var(--font-heading, Georgia, serif);
65848
+ font-style: italic;
65849
+ font-size: 3.375rem;
65850
+ line-height: 0.6;
65851
+ color: rgb(var(--tb-secondary));
65852
+ }
65853
+
65854
+ /* Quote body. The inner <p> is targeted directly (not via :where) so the
65855
+ * serif display font and sizing win over the theme's base paragraph rule. */
65856
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-quote {
65857
+ flex: 1 1 auto;
65858
+ margin-top: 1.25rem;
65859
+ }
65860
+
65861
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-quote p {
65862
+ margin: 0;
65863
+ font-family: var(--font-heading, Georgia, serif);
65864
+ font-size: 1.15rem;
65865
+ line-height: 1.5;
65866
+ color: rgb(var(--tb-text));
65867
+ }
65868
+
65869
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-quote p {
65870
+ color: rgb(var(--tb-onPrimary));
65871
+ font-size: clamp(1.375rem, 2.4vw, 1.6875rem);
65872
+ line-height: 1.42;
65873
+ letter-spacing: -0.01em;
65874
+ }
65875
+
65876
+ /* Attribution: name (bold) + context line, no avatar */
65877
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-meta {
65878
+ display: flex;
65879
+ flex-direction: column;
65880
+ gap: 0.25rem;
65881
+ margin-top: 1.625rem;
65882
+ }
65883
+
65884
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-name {
65885
+ font-weight: 600;
65886
+ font-size: 0.97rem;
65887
+ color: rgb(var(--tb-text));
65888
+ }
65889
+
65890
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-context {
65891
+ font-size: 0.78rem;
65892
+ letter-spacing: 0.02em;
65893
+ color: rgb(var(--tb-mutedText));
65894
+ }
65895
+
65896
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-name {
65897
+ color: rgb(var(--tb-onPrimary));
65898
+ }
65899
+
65900
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-context {
65901
+ color: rgb(var(--tb-onPrimary));
65902
+ opacity: 0.7;
65903
+ }
65904
+
65905
+
64158
65906
  /* Testimonials carousel tweaks: arrow styling + gutters + equal-height cards */
64159
65907
  :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-carousel.carousel-root .carousel-nav-btn {
64160
65908
  height: var(--rb-testimonial-carousel-arrow-size);
@@ -65301,7 +67049,7 @@ const containerResponsiveThemeCss = `/*
65301
67049
 
65302
67050
  :where(.theme-scope) :where([data-block="newsletter-signup"]) .newsletter-copy-subtitle {
65303
67051
  font-size: 1rem;
65304
- color: var(--section-text-color, rgb(var(--tb-text)));
67052
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
65305
67053
  opacity: 0.85;
65306
67054
  }
65307
67055
 
@@ -65350,7 +67098,7 @@ const containerResponsiveThemeCss = `/*
65350
67098
  margin-top: 0.75rem;
65351
67099
  font-size: 1rem;
65352
67100
  line-height: 1.6;
65353
- color: var(--section-text-color, rgb(var(--tb-text)));
67101
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
65354
67102
  text-align: center;
65355
67103
  }
65356
67104
 
@@ -65432,7 +67180,7 @@ const containerResponsiveThemeCss = `/*
65432
67180
 
65433
67181
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .form-copy-intro,
65434
67182
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .newsletter-copy-subtitle {
65435
- color: var(--section-text-color, rgb(var(--tb-text)));
67183
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
65436
67184
  }
65437
67185
 
65438
67186
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .contact-newsletter-info {
@@ -65830,7 +67578,7 @@ const containerResponsiveThemeCss = `/*
65830
67578
 
65831
67579
  :where(.theme-scope) .event-compact-row-title {
65832
67580
  margin: 0;
65833
- color: var(--section-heading-color, rgb(var(--tb-primary, 0 0 0)));
67581
+ color: var(--rb-surface-heading-color, var(--rb-surface-text-color, rgb(var(--tb-primary, var(--tb-text, 17 24 39)))));
65834
67582
  font-family: var(--font-heading, var(--font-body, inherit));
65835
67583
  font-size: clamp(1.05rem, 1rem + 0.35vw, 1.3rem);
65836
67584
  font-weight: 400;
@@ -66492,27 +68240,41 @@ const containerResponsiveThemeCss = `/*
66492
68240
  background: rgb(var(--tb-surface));
66493
68241
  }
66494
68242
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 1) {
68243
+ background: rgb(var(--tb-surface));
66495
68244
  color: rgb(var(--tb-onSurface));
68245
+ --rb-surface-text-color: rgb(var(--tb-onSurface));
68246
+ --rb-surface-heading-color: rgb(var(--tb-onSurface));
68247
+ --rb-surface-link-color: rgb(var(--tb-onSurface));
68248
+ --rb-surface-supporting-color: rgb(var(--tb-onSurface));
66496
68249
  }
66497
68250
 
66498
68251
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 2) > .rb-bg-surface {
66499
68252
  background: rgb(var(--tb-secondary));
66500
68253
  }
66501
68254
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 2) {
68255
+ background: rgb(var(--tb-secondary));
66502
68256
  color: rgb(var(--tb-onSecondary));
68257
+ --rb-surface-text-color: rgb(var(--tb-onSecondary));
68258
+ --rb-surface-heading-color: rgb(var(--tb-onSecondary));
68259
+ --rb-surface-link-color: rgb(var(--tb-onSecondary));
68260
+ --rb-surface-supporting-color: rgb(var(--tb-onSecondary));
66503
68261
  }
66504
68262
 
66505
68263
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 3) > .rb-bg-surface {
66506
68264
  background: rgb(var(--tb-primary));
66507
68265
  }
66508
68266
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 3) {
68267
+ background: rgb(var(--tb-primary));
66509
68268
  color: rgb(var(--tb-onPrimary));
68269
+ --rb-surface-text-color: rgb(var(--tb-onPrimary));
68270
+ --rb-surface-heading-color: rgb(var(--tb-onPrimary));
68271
+ --rb-surface-link-color: rgb(var(--tb-onPrimary));
68272
+ --rb-surface-supporting-color: rgb(var(--tb-onPrimary));
66510
68273
  }
66511
68274
 
66512
- /* Body copy carries an inline muted colour from the block; force it to the
66513
- * card's on-colour so it reads on every block (inline style needs !important). */
68275
+ /* Keep the intended visual hierarchy; the body color itself now resolves via
68276
+ * the nearest surface provider instead of requiring an inline override. */
66514
68277
  :where(.theme-scope) .rb-card-treatment-color-blocked .rb-feature-card-body {
66515
- color: inherit !important;
66516
68278
  opacity: 0.9;
66517
68279
  }
66518
68280
 
@@ -68555,6 +70317,8 @@ const VERTICAL_IDS = [
68555
70317
  "yoga_wellness",
68556
70318
  "fitness_pt",
68557
70319
  "dance_school",
70320
+ "physiotherapy_physical_health",
70321
+ "massage_bodywork",
68558
70322
  "therapist_practitioner",
68559
70323
  "beauty_spa",
68560
70324
  "consultant_coach",
@@ -68590,6 +70354,12 @@ const VERTICAL_BOOKING_PRESETS = [
68590
70354
  preset("dance_school.workshops", "dance_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
68591
70355
  preset("dance_school.private_lessons", "dance_school", "appointment", "Private lesson", "Private lessons", "One-to-one lessons or coaching sessions."),
68592
70356
  preset("dance_school.shows", "dance_school", "performance", "Show", "Shows", "Ticketed performances, showcases, and recitals."),
70357
+ preset("physiotherapy_physical_health.appointments", "physiotherapy_physical_health", "appointment", "Appointment", "Appointments", "Assessments, treatments, follow-ups, and rehabilitation sessions."),
70358
+ preset("physiotherapy_physical_health.rehab_classes", "physiotherapy_physical_health", "class", "Rehab class", "Rehab classes", "Regular rehabilitation, mobility, strength, or recovery classes."),
70359
+ preset("physiotherapy_physical_health.workshops", "physiotherapy_physical_health", "workshop", "Workshop", "Workshops", "One-off workshops, talks, or practical group sessions."),
70360
+ preset("massage_bodywork.treatments", "massage_bodywork", "appointment", "Treatment", "Treatments", "Massage treatments, bodywork sessions, and one-to-one appointments."),
70361
+ preset("massage_bodywork.group_sessions", "massage_bodywork", "class", "Group session", "Group sessions", "Regular bodywork, somatic movement, or self-massage sessions."),
70362
+ preset("massage_bodywork.workshops", "massage_bodywork", "workshop", "Workshop", "Workshops", "One-off bodywork, self-massage, or somatic practice workshops."),
68593
70363
  preset("therapist_practitioner.appointments", "therapist_practitioner", "appointment", "Appointment", "Appointments", "Sessions, consultations, treatments, or assessments."),
68594
70364
  preset("therapist_practitioner.workshops", "therapist_practitioner", "workshop", "Workshop", "Workshops", "One-off group workshops, talks, or wellbeing sessions."),
68595
70365
  preset("therapist_practitioner.retreats", "therapist_practitioner", "retreat", "Retreat", "Retreats", "Multi-day retreats or immersive group experiences."),