@riverbankcms/sdk 0.104.1 → 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 (120) hide show
  1. package/dist/{PreviewEditorSidebar-CmaPXdKu.mjs → PreviewEditorSidebar-CBMhU9p_.mjs} +3 -3
  2. package/dist/{PreviewEditorUI-C5xRTh2b.mjs → PreviewEditorUI-C5KEw4M8.mjs} +2 -2
  3. package/dist/{SdkPreviewModeRuntime-Eks51NcN.mjs → SdkPreviewModeRuntime-Iq12HjL7.mjs} +1142 -177
  4. package/dist/{SiteChromeCustomizeContext-Bt8zb3qu.mjs → SiteChromeCustomizeContext-Cx0NMdyv.mjs} +1640 -877
  5. package/dist/_dts/ai/src/contracts/proposals.d.ts +10 -0
  6. package/dist/_dts/blocks/src/primitives/layout/Grid.d.ts +12 -8
  7. package/dist/_dts/blocks/src/system/blocks/events/event-calendar.d.ts +3 -0
  8. package/dist/_dts/blocks/src/system/blocks/events/shared/layout.d.ts +11 -1
  9. package/dist/_dts/blocks/src/system/blocks/testimonials.d.ts +7 -1
  10. package/dist/_dts/blocks/src/system/constants/events.d.ts +1 -1
  11. package/dist/_dts/blocks/src/system/fragments/library/testimonialsCarousel.d.ts +12 -0
  12. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +2 -2
  13. package/dist/_dts/blocks/src/system/runtime/nodes/basic.d.ts +14 -0
  14. package/dist/_dts/blocks/src/system/runtime/nodes/buttonAffordance.d.ts +33 -0
  15. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +28 -0
  16. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCapacity.d.ts +32 -0
  17. package/dist/_dts/blocks/src/system/runtime/nodes/shared/contract-schemas.d.ts +1 -0
  18. package/dist/_dts/blocks/src/system/runtime/nodes/svg.d.ts +77 -0
  19. package/dist/_dts/blocks/src/system/transforms/registry/_utils/surfaceForegroundStyles.d.ts +2 -0
  20. package/dist/_dts/blocks/src/system/transforms/registry/boxStyles.d.ts +3 -40
  21. package/dist/_dts/blocks/src/system/transforms/registry/formatting.d.ts +14 -0
  22. package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +11 -0
  23. package/dist/_dts/core/src/branded.d.ts +5 -0
  24. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +18 -0
  25. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +2 -2
  26. package/dist/_dts/sdk/src/version.d.ts +1 -1
  27. package/dist/_dts/theme-core/src/ThemeScope.d.ts +1 -1
  28. package/dist/_dts/theme-core/src/buttons/affordanceIcons.d.ts +29 -0
  29. package/dist/_dts/theme-core/src/buttons/generateButtonCss.d.ts +6 -0
  30. package/dist/_dts/theme-core/src/buttons/index.d.ts +1 -0
  31. package/dist/_dts/theme-core/src/buttons/personalities/flat-ink.d.ts +20 -0
  32. package/dist/_dts/theme-core/src/buttons/personalities/index.d.ts +11 -0
  33. package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +4 -0
  34. package/dist/_dts/theme-core/src/buttons/types.d.ts +34 -0
  35. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  36. package/dist/_dts/theme-core/src/gradients/types.d.ts +27 -3
  37. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +51 -15
  38. package/dist/_dts/theme-core/src/palette/curated/brand-led.d.ts +13 -0
  39. package/dist/_dts/theme-core/src/palette/curated/index.d.ts +12 -0
  40. package/dist/_dts/theme-core/src/palette/historicalThemePalette.d.ts +44 -0
  41. package/dist/_dts/theme-core/src/palette/index.d.ts +1 -0
  42. package/dist/_dts/theme-core/src/palette/legacySelection.d.ts +13 -2
  43. package/dist/_dts/theme-core/src/palette/persistenceReadSource.d.ts +17 -2
  44. package/dist/_dts/theme-core/src/palette/themePaletteCompatibility.d.ts +3 -4
  45. package/dist/_dts/theme-core/src/runtime/themeCssVarNames.d.ts +1 -0
  46. package/dist/_dts/theme-core/src/schema.d.ts +108 -26
  47. package/dist/_dts/theme-core/src/site-styles/accessibleSurfaces.d.ts +2 -1
  48. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +50 -1
  49. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +2 -2
  50. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -2
  51. package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +56 -1
  52. package/dist/_dts/theme-core/src/site-styles/typographyPresets.d.ts +16 -1
  53. package/dist/_dts/theme-core/src/status/index.d.ts +0 -1
  54. package/dist/_dts/theme-core/src/surfaces/foregroundContract.d.ts +19 -0
  55. package/dist/_dts/theme-core/src/surfaces/foregroundRoles.d.ts +9 -0
  56. package/dist/_dts/theme-core/src/surfaces/index.d.ts +2 -0
  57. package/dist/_dts/ui/src/button.d.ts +1 -1
  58. package/dist/cli/index.mjs +1558 -468
  59. package/dist/client/{DiscountCodeDisclosure-Dit2aItR.mjs → DiscountCodeDisclosure-BXM73_E5.mjs} +1 -1
  60. package/dist/client/{EventCombined.client-BnREyCjf.mjs → EventCombined.client-DT5elP7j.mjs} +4 -4
  61. package/dist/client/{EventListing.client-0uHld51x.mjs → EventListing.client-pTYGfV9C.mjs} +2 -2
  62. package/dist/client/{LeafletStaticMap-DJcvSfmj.mjs → LeafletStaticMap-dQrhlUe3.mjs} +1 -1
  63. package/dist/client/{MagicLinkForm-EAsIktfw.mjs → MagicLinkForm-u6Dul4j5.mjs} +2 -2
  64. package/dist/client/{StepIndicator-CRR17tYI.mjs → StepIndicator-nWqineJ2.mjs} +2 -2
  65. package/dist/client/{accordion-B47HIuuH.mjs → accordion-CSJcTUyH.mjs} +1 -1
  66. package/dist/client/{bookingForm-I1E-8F9P.mjs → bookingForm-C73wOuVa.mjs} +6 -6
  67. package/dist/client/bookings.mjs +137 -1
  68. package/dist/client/{buttons-BbPn8OKA.mjs → buttons-DfYyqt8C.mjs} +138 -2
  69. package/dist/client/{carousel-OlM7SyBs.mjs → carousel-fMBb0JSc.mjs} +1 -1
  70. package/dist/client/{cart-BKpG5jz6.mjs → cart-BtaCwT3S.mjs} +3 -3
  71. package/dist/client/{checkout-Dmmr6qoU.mjs → checkout-DkTwZSnh.mjs} +3 -3
  72. package/dist/client/client.mjs +2327 -542
  73. package/dist/client/{colorStyles-DuW4Fjqd.mjs → colorStyles-Wyl6JujD.mjs} +45 -4
  74. package/dist/client/{courseRegistration-CflZiqGk.mjs → courseRegistration-DnLmapJu.mjs} +7 -7
  75. package/dist/client/{eventCalendar-BvoLWcp-.mjs → eventCalendar-BvLOc9KY.mjs} +165 -5
  76. package/dist/client/{eventCombined-DsrO8t_t.mjs → eventCombined-kShgJPQr.mjs} +2 -2
  77. package/dist/client/{eventDetails-C3hxgcJm.mjs → eventDetails-C5NTM0Vo.mjs} +3 -3
  78. package/dist/client/{eventListing-C-hb237X.mjs → eventListing-B82ZdKP6.mjs} +2 -2
  79. package/dist/client/{eventRegistration-B0gyzDGi.mjs → eventRegistration-qJVFqBWo.mjs} +7 -7
  80. package/dist/client/{form-C_RUi6oJ.mjs → form-BvAcKiuu.mjs} +2 -2
  81. package/dist/client/{headerSection-IKQiD2U0.mjs → headerSection-NvAiEHAg.mjs} +3 -3
  82. package/dist/client/hooks.mjs +1034 -356
  83. package/dist/client/{locationMap-8ZBrMzRJ.mjs → locationMap-D0j52xfI.mjs} +3 -3
  84. package/dist/client/{media-CZJKL7fi.mjs → media-BCW_g2cQ.mjs} +1 -1
  85. package/dist/client/{newsletterForm-CYk3ucwj.mjs → newsletterForm-B8_i93qH.mjs} +2 -2
  86. package/dist/client/{pricing-B5GmvfIH.mjs → pricing-DIYnyHOk.mjs} +1 -1
  87. package/dist/client/{productDetail-BjS2Dqzo.mjs → productDetail-w0eAHkuQ.mjs} +3 -3
  88. package/dist/client/{productList-BOHvC8My.mjs → productList-Vsguq5Xt.mjs} +3 -3
  89. package/dist/client/{propsDecoder-WS6UEJwZ.mjs → propsDecoder-CD2pJ1WB.mjs} +37 -2
  90. package/dist/client/rendering/client.mjs +1722 -760
  91. package/dist/client/rendering/islands.mjs +19 -19
  92. package/dist/client/rendering.mjs +2303 -518
  93. package/dist/client/{shop-CON03-wN.mjs → shop-Dgxs-Q8F.mjs} +6 -6
  94. package/dist/client/{shop-commerce-DlqrFgH8.mjs → shop-commerce-DJAhsTaT.mjs} +6 -6
  95. package/dist/client/{shop-commerce-store-l74-Kne6.mjs → shop-commerce-store-CfCXltXW.mjs} +1 -1
  96. package/dist/client/{shopShared-CQheEXSg.mjs → shopShared-CmDcPXUt.mjs} +2 -2
  97. package/dist/client/{useDiscountCodeUi-v6OluyWl.mjs → useDiscountCodeUi-CirrT2bM.mjs} +1 -1
  98. package/dist/client/{useEventFilters-BqSTcNOf.mjs → useEventFilters-CNZxlCcO.mjs} +5 -5
  99. package/dist/client/{usePolling-DMfADjIF.mjs → usePolling-Baks_iBI.mjs} +2 -2
  100. package/dist/preview-next/client/runtime.mjs +3 -3
  101. package/dist/{sdk-runtime-BUmJ0DaT.mjs → sdk-runtime-BfNiFeNJ.mjs} +1 -1
  102. package/dist/{separator-Dd-JYok7.mjs → separator-BhAR_Xmf.mjs} +1 -1
  103. package/dist/server/components.mjs +2293 -531
  104. package/dist/server/config-validation.mjs +1040 -357
  105. package/dist/server/config.mjs +1040 -357
  106. package/dist/server/data.mjs +1034 -356
  107. package/dist/server/index.mjs +1 -1
  108. package/dist/server/next.mjs +2296 -534
  109. package/dist/server/page-converter.mjs +1018 -324
  110. package/dist/server/prebuild.mjs +1 -1
  111. package/dist/server/rendering/server.mjs +2278 -521
  112. package/dist/server/rendering.mjs +2293 -531
  113. package/dist/server/routing.mjs +1092 -338
  114. package/dist/server/server.mjs +1016 -338
  115. package/dist/server/theme-bridge.mjs +1240 -77
  116. package/dist/server/theme.mjs +165 -5
  117. package/dist/styles/index.css +617 -34
  118. package/package.json +1 -1
  119. package/dist/_dts/blocks/src/system/transforms/registry/_utils/sectionForegroundStyles.d.ts +0 -2
  120. 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),
@@ -41338,6 +42127,12 @@ const systemBlockDefinitions = [
41338
42127
  family: "centered",
41339
42128
  weight: "featured",
41340
42129
  bias: "soft"
42130
+ }),
42131
+ layoutProfile({
42132
+ variantKey: "featured-editorial",
42133
+ family: "split",
42134
+ weight: "featured",
42135
+ bias: "soft"
41341
42136
  })
41342
42137
  ]
41343
42138
  })),
@@ -41470,6 +42265,11 @@ const systemBlockDefinitions = [
41470
42265
  variantKey: "timetable",
41471
42266
  family: "stack",
41472
42267
  weight: "standard"
42268
+ }),
42269
+ layoutProfile({
42270
+ variantKey: "week-board",
42271
+ family: "stack",
42272
+ weight: "standard"
41473
42273
  })
41474
42274
  ]
41475
42275
  })),
@@ -41827,7 +42627,15 @@ function gradientToCss(config, tokens) {
41827
42627
  const stops = config.stops.map((stop) => {
41828
42628
  return `${tokens[stop.color] ?? "#000000"} ${stop.position}%`;
41829
42629
  }).join(", ");
41830
- if (config.type === "radial") return `radial-gradient(circle at ${config.position ?? "center"}, ${stops})`;
42630
+ const type = config.type ?? "linear";
42631
+ if (type === "radial") {
42632
+ const position = config.position ?? "center";
42633
+ return `radial-gradient(${config.shape ?? "circle"} at ${position}, ${stops})`;
42634
+ }
42635
+ if (type === "conic") {
42636
+ const position = config.position ?? "center";
42637
+ return `conic-gradient(from ${config.angle ?? 0}deg at ${position}, ${stops})`;
42638
+ }
41831
42639
  return `linear-gradient(${config.angle ?? 180}deg, ${stops})`;
41832
42640
  }
41833
42641
  //#endregion
@@ -41905,8 +42713,8 @@ const backgroundTransforms = [
41905
42713
  {
41906
42714
  id: "background.resolveColorsStyle",
41907
42715
  kind: "formatter",
41908
- summary: "Resolve section foreground colors from a background object into scoped CSS vars",
41909
- run: (value, _options, context) => resolveSectionForegroundStyles(value, context)
42716
+ summary: "Resolve surface foreground colors from a background object into scoped CSS vars",
42717
+ run: (value, _options, context) => resolveSurfaceForegroundStyles(value, context)
41910
42718
  },
41911
42719
  {
41912
42720
  id: "background.resolveAllowOverflow",
@@ -42526,21 +43334,53 @@ function Inline({ as: Comp = "div", gap = "md", align = "center", justify = "sta
42526
43334
  }
42527
43335
  //#endregion
42528
43336
  //#region ../blocks/src/primitives/layout/Grid.tsx
43337
+ /** Utility class for a numeric column COUNT at a given breakpoint. */
43338
+ const COL_COUNT_CLASS = {
43339
+ base: (n) => `rb-grid-cols-${n}`,
43340
+ sm: (n) => `rb-sm-grid-cols-${n}`,
43341
+ md: (n) => `rb-md-grid-cols-${n}`,
43342
+ lg: (n) => `rb-lg-grid-cols-${n}`,
43343
+ xl: (n) => `rb-xl-grid-cols-${n}`
43344
+ };
43345
+ /** Inline custom property read by the `.rb-grid-tracks` utility per breakpoint. */
43346
+ const TRACK_VAR = {
43347
+ base: "--rb-gtc-base",
43348
+ sm: "--rb-gtc-sm",
43349
+ md: "--rb-gtc-md",
43350
+ lg: "--rb-gtc-lg",
43351
+ xl: "--rb-gtc-xl"
43352
+ };
43353
+ const GRID_BREAKPOINTS = [
43354
+ "base",
43355
+ "sm",
43356
+ "md",
43357
+ "lg",
43358
+ "xl"
43359
+ ];
42529
43360
  function Grid({ as: Comp = "div", cols = {
42530
43361
  base: 1,
42531
43362
  md: 2
42532
- }, gap = "md", className, children, "data-slot": dataSlot, ...rest }) {
42533
- const colsObj = typeof cols === "number" ? { base: cols } : cols;
42534
- const colClasses = [
42535
- colsObj.base && `rb-grid-cols-${colsObj.base}`,
42536
- colsObj.sm && `rb-sm-grid-cols-${colsObj.sm}`,
42537
- colsObj.md && `rb-md-grid-cols-${colsObj.md}`,
42538
- colsObj.lg && `rb-lg-grid-cols-${colsObj.lg}`,
42539
- colsObj.xl && `rb-xl-grid-cols-${colsObj.xl}`
42540
- ];
43363
+ }, gap = "md", className, style, children, "data-slot": dataSlot, ...rest }) {
43364
+ const colsObj = typeof cols === "number" || typeof cols === "string" ? { base: cols } : cols;
43365
+ const colClasses = [];
43366
+ const trackVars = {};
43367
+ let hasTracks = false;
43368
+ for (const bp of GRID_BREAKPOINTS) {
43369
+ const value = colsObj[bp];
43370
+ if (value == null) continue;
43371
+ if (typeof value === "string") {
43372
+ trackVars[TRACK_VAR[bp]] = value;
43373
+ hasTracks = true;
43374
+ } else colClasses.push(COL_COUNT_CLASS[bp](value));
43375
+ }
43376
+ const mergedStyle = hasTracks ? {
43377
+ ...trackVars,
43378
+ ...style
43379
+ } : style;
42541
43380
  return /* @__PURE__ */ jsx(Comp, {
42542
43381
  ...rest,
42543
- className: clsx("rb-grid", colClasses, {
43382
+ style: mergedStyle,
43383
+ className: clsx("rb-grid", hasTracks && "rb-grid-tracks", colClasses, {
42544
43384
  "rb-gap-0": gap === "none",
42545
43385
  "rb-gap-1": gap === "xs",
42546
43386
  "rb-gap-2": gap === "sm",
@@ -42777,119 +43617,6 @@ function Text$1({ as: Comp = "span", children, className = "", path, maxLength:
42777
43617
  });
42778
43618
  }
42779
43619
  //#endregion
42780
- //#region ../blocks/src/system/runtime/nodes/richText-coerce.ts
42781
- function coerceRichTextDoc(value) {
42782
- const raw = unwrapRichTextRoot(value);
42783
- if (typeof raw === "string") return createRichTextDocFromMarkdown(raw);
42784
- if (isRecord$26(raw) && typeof raw.type === "string") return raw;
42785
- return null;
42786
- }
42787
- //#endregion
42788
- //#region ../blocks/src/system/runtime/nodes/renderInlineText.tsx
42789
- function renderInlineText(value) {
42790
- return value.map((run, index) => renderRun(run, index));
42791
- }
42792
- function renderRun(run, key) {
42793
- return (run.marks ?? []).reduceRight((child, mark, index) => renderMark(mark, `${String(key)}-${index}`, child), run.text);
42794
- }
42795
- function renderMark(mark, key, child) {
42796
- switch (mark.kind) {
42797
- case "strong": return /* @__PURE__ */ jsx("strong", { children: child }, key);
42798
- case "emphasis": return /* @__PURE__ */ jsx("em", { children: child }, key);
42799
- case "accent": return /* @__PURE__ */ jsx("span", {
42800
- className: `rb-inline-text-accent rb-inline-text-accent-${mark.tone}`,
42801
- children: child
42802
- }, key);
42803
- case "highlight": return /* @__PURE__ */ jsx("span", {
42804
- className: `rb-inline-text-highlight rb-inline-text-highlight-${mark.tone}`,
42805
- children: child
42806
- }, key);
42807
- }
42808
- }
42809
- //#endregion
42810
- //#region ../blocks/src/system/runtime/nodes/basic.tsx
42811
- const TextNode = ({ value, children, blockId: _blockId, blockKind: _blockKind, blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, inlineTextFallbackPath, ...rest }) => {
42812
- const inlineText = parseInlineTextFromUnknown(value);
42813
- if (inlineText.ok && !isInlineTextEmpty(inlineText.value)) return /* @__PURE__ */ jsx(Text$1, {
42814
- ...rest,
42815
- children: renderInlineText(inlineText.value)
42816
- });
42817
- const fallbackValue = inlineTextFallbackPath ? readStringPath(blockContent, inlineTextFallbackPath) : null;
42818
- return /* @__PURE__ */ jsx(Text$1, {
42819
- ...rest,
42820
- children: fallbackValue ?? textNodeContent(value) ?? children
42821
- });
42822
- };
42823
- function textNodeContent(value) {
42824
- if (value === null || value === void 0) return null;
42825
- if (typeof value === "string" || typeof value === "number") return value;
42826
- return null;
42827
- }
42828
- function readStringPath(source, path) {
42829
- const value = getAtPath(source, path);
42830
- return typeof value === "string" ? value : null;
42831
- }
42832
- const RichTextNode = ({ value, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
42833
- return /* @__PURE__ */ jsx(RichText$2, {
42834
- doc: coerceRichTextDoc(value) ?? void 0,
42835
- ...rest
42836
- });
42837
- };
42838
- 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 }) => {
42839
- const content = value ?? children;
42840
- if (!content) {
42841
- console.warn("[ButtonNode] No content to render - value and children are both empty");
42842
- return null;
42843
- }
42844
- const resolvedHref = typeof href === "string" ? href.trim() : "";
42845
- if (linkRequired && resolvedHref.length === 0) {
42846
- if (process.env.NODE_ENV === "development") console.warn("[ButtonNode] Missing href for link-required button");
42847
- return null;
42848
- }
42849
- const resolvedVariantId = parseThemeButtonVariantId(variantId);
42850
- const combinedClassName = resolvedVariantId ? themeButtonClassName({
42851
- variant: resolvedVariantId,
42852
- size,
42853
- extraClassName: className
42854
- }) : className;
42855
- if (resolvedHref) return /* @__PURE__ */ jsx("a", {
42856
- href: resolvedHref,
42857
- target,
42858
- rel,
42859
- className: combinedClassName,
42860
- ...rest,
42861
- children: content
42862
- });
42863
- return /* @__PURE__ */ jsx("button", {
42864
- type,
42865
- disabled,
42866
- className: combinedClassName,
42867
- ...rest,
42868
- children: content
42869
- });
42870
- };
42871
- const LinkNode = ({ href, children, className, target, rel, linkRequired = false, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
42872
- if (!children) return null;
42873
- const resolvedHref = typeof href === "string" ? href.trim() : "";
42874
- if (resolvedHref.length === 0) {
42875
- if (linkRequired && process.env.NODE_ENV === "development") console.warn("[LinkNode] Missing href for link-required link");
42876
- if (linkRequired) return null;
42877
- return /* @__PURE__ */ jsx("span", {
42878
- className,
42879
- ...rest,
42880
- children
42881
- });
42882
- }
42883
- return /* @__PURE__ */ jsx("a", {
42884
- href: resolvedHref,
42885
- className,
42886
- target,
42887
- rel,
42888
- ...rest,
42889
- children
42890
- });
42891
- };
42892
- //#endregion
42893
43620
  //#region ../blocks/src/system/icons/cta.ts
42894
43621
  /**
42895
43622
  * Curated icon set for CTA/button icons.
@@ -42988,6 +43715,171 @@ function getIconComponent(name) {
42988
43715
  return getCtaIconComponent(name) ?? getSemanticIconComponent(name);
42989
43716
  }
42990
43717
  //#endregion
43718
+ //#region ../blocks/src/system/runtime/nodes/buttonAffordance.ts
43719
+ function normalize(value) {
43720
+ const trimmed = typeof value === "string" ? value.trim() : "";
43721
+ return trimmed.length > 0 ? trimmed : null;
43722
+ }
43723
+ /**
43724
+ * Resolve the trailing affordance a button should render. Node overrides
43725
+ * personality; returns `null` when nothing is configured.
43726
+ */
43727
+ function resolveButtonAffordance(input) {
43728
+ const nodeIcon = normalize(input.trailingIcon);
43729
+ if (nodeIcon) {
43730
+ const colorToken = normalize(input.trailingIconColorToken);
43731
+ return {
43732
+ iconId: nodeIcon,
43733
+ source: "node",
43734
+ ...colorToken ? { colorToken } : {}
43735
+ };
43736
+ }
43737
+ const personality = input.personalityAffordance;
43738
+ const personalityIcon = normalize(personality?.iconId);
43739
+ if (personality && personalityIcon) {
43740
+ const colorToken = normalize(personality.colorToken);
43741
+ return {
43742
+ iconId: personalityIcon,
43743
+ source: "personality",
43744
+ ...colorToken ? { colorToken } : {}
43745
+ };
43746
+ }
43747
+ return null;
43748
+ }
43749
+ //#endregion
43750
+ //#region ../blocks/src/system/runtime/nodes/richText-coerce.ts
43751
+ function coerceRichTextDoc(value) {
43752
+ const raw = unwrapRichTextRoot(value);
43753
+ if (typeof raw === "string") return createRichTextDocFromMarkdown(raw);
43754
+ if (isRecord$26(raw) && typeof raw.type === "string") return raw;
43755
+ return null;
43756
+ }
43757
+ //#endregion
43758
+ //#region ../blocks/src/system/runtime/nodes/renderInlineText.tsx
43759
+ function renderInlineText(value) {
43760
+ return value.map((run, index) => renderRun(run, index));
43761
+ }
43762
+ function renderRun(run, key) {
43763
+ return (run.marks ?? []).reduceRight((child, mark, index) => renderMark(mark, `${String(key)}-${index}`, child), run.text);
43764
+ }
43765
+ function renderMark(mark, key, child) {
43766
+ switch (mark.kind) {
43767
+ case "strong": return /* @__PURE__ */ jsx("strong", { children: child }, key);
43768
+ case "emphasis": return /* @__PURE__ */ jsx("em", { children: child }, key);
43769
+ case "accent": return /* @__PURE__ */ jsx("span", {
43770
+ className: `rb-inline-text-accent rb-inline-text-accent-${mark.tone}`,
43771
+ children: child
43772
+ }, key);
43773
+ case "highlight": return /* @__PURE__ */ jsx("span", {
43774
+ className: `rb-inline-text-highlight rb-inline-text-highlight-${mark.tone}`,
43775
+ children: child
43776
+ }, key);
43777
+ }
43778
+ }
43779
+ //#endregion
43780
+ //#region ../blocks/src/system/runtime/nodes/basic.tsx
43781
+ const TextNode = ({ value, children, blockId: _blockId, blockKind: _blockKind, blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, inlineTextFallbackPath, ...rest }) => {
43782
+ const inlineText = parseInlineTextFromUnknown(value);
43783
+ if (inlineText.ok && !isInlineTextEmpty(inlineText.value)) return /* @__PURE__ */ jsx(Text$1, {
43784
+ ...rest,
43785
+ children: renderInlineText(inlineText.value)
43786
+ });
43787
+ const fallbackValue = inlineTextFallbackPath ? readStringPath(blockContent, inlineTextFallbackPath) : null;
43788
+ return /* @__PURE__ */ jsx(Text$1, {
43789
+ ...rest,
43790
+ children: fallbackValue ?? textNodeContent(value) ?? children
43791
+ });
43792
+ };
43793
+ function textNodeContent(value) {
43794
+ if (value === null || value === void 0) return null;
43795
+ if (typeof value === "string" || typeof value === "number") return value;
43796
+ return null;
43797
+ }
43798
+ function readStringPath(source, path) {
43799
+ const value = getAtPath(source, path);
43800
+ return typeof value === "string" ? value : null;
43801
+ }
43802
+ const RichTextNode = ({ value, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
43803
+ return /* @__PURE__ */ jsx(RichText$2, {
43804
+ doc: coerceRichTextDoc(value) ?? void 0,
43805
+ ...rest
43806
+ });
43807
+ };
43808
+ /**
43809
+ * Render the resolved trailing affordance icon. Colour is applied via a theme
43810
+ * CSS var so it recolours with the palette; the icon stroke uses `currentColor`.
43811
+ */
43812
+ function renderTrailingAffordanceIcon(affordance) {
43813
+ if (!affordance) return null;
43814
+ const Icon = getIconComponent(affordance.iconId);
43815
+ if (!Icon) return null;
43816
+ return /* @__PURE__ */ jsx(Icon, {
43817
+ "aria-hidden": true,
43818
+ className: "rb-ml-2 rb-h-4 rb-w-4 rb-inline-block",
43819
+ style: affordance.colorToken ? { color: `rgb(var(--tb-${affordance.colorToken}))` } : void 0
43820
+ });
43821
+ }
43822
+ 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 }) => {
43823
+ const content = value ?? children;
43824
+ if (!content) {
43825
+ console.warn("[ButtonNode] No content to render - value and children are both empty");
43826
+ return null;
43827
+ }
43828
+ const affordanceIcon = renderTrailingAffordanceIcon(resolveButtonAffordance({
43829
+ trailingIcon,
43830
+ trailingIconColorToken,
43831
+ personalityAffordance
43832
+ }));
43833
+ const resolvedHref = typeof href === "string" ? href.trim() : "";
43834
+ if (linkRequired && resolvedHref.length === 0) {
43835
+ if (process.env.NODE_ENV === "development") console.warn("[ButtonNode] Missing href for link-required button");
43836
+ return null;
43837
+ }
43838
+ const extraClassName = affordanceIcon ? [className, AFFORDANCE_SUPPRESS_CLASS].filter(Boolean).join(" ") : className;
43839
+ const resolvedVariantId = parseThemeButtonVariantId(variantId);
43840
+ const combinedClassName = resolvedVariantId ? themeButtonClassName({
43841
+ variant: resolvedVariantId,
43842
+ size,
43843
+ extraClassName
43844
+ }) : extraClassName;
43845
+ if (resolvedHref) return /* @__PURE__ */ jsxs("a", {
43846
+ href: resolvedHref,
43847
+ target,
43848
+ rel,
43849
+ className: combinedClassName,
43850
+ ...rest,
43851
+ children: [content, affordanceIcon]
43852
+ });
43853
+ return /* @__PURE__ */ jsxs("button", {
43854
+ type,
43855
+ disabled,
43856
+ className: combinedClassName,
43857
+ ...rest,
43858
+ children: [content, affordanceIcon]
43859
+ });
43860
+ };
43861
+ const LinkNode = ({ href, children, className, target, rel, linkRequired = false, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
43862
+ if (!children) return null;
43863
+ const resolvedHref = typeof href === "string" ? href.trim() : "";
43864
+ if (resolvedHref.length === 0) {
43865
+ if (linkRequired && process.env.NODE_ENV === "development") console.warn("[LinkNode] Missing href for link-required link");
43866
+ if (linkRequired) return null;
43867
+ return /* @__PURE__ */ jsx("span", {
43868
+ className,
43869
+ ...rest,
43870
+ children
43871
+ });
43872
+ }
43873
+ return /* @__PURE__ */ jsx("a", {
43874
+ href: resolvedHref,
43875
+ className,
43876
+ target,
43877
+ rel,
43878
+ ...rest,
43879
+ children
43880
+ });
43881
+ };
43882
+ //#endregion
42991
43883
  //#region ../blocks/src/system/runtime/nodes/icon.tsx
42992
43884
  const IconNode = ({ value, title, className, blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, ...rest }) => {
42993
43885
  const parsedValue = parseBlockIconValue(value);
@@ -43685,6 +44577,77 @@ function SpinnerNode({ className, size = "md", color, strokeWidth = 3, srLabel,
43685
44577
  }) : null]
43686
44578
  });
43687
44579
  }
44580
+ //#endregion
44581
+ //#region ../blocks/src/system/runtime/nodes/svg.tsx
44582
+ /**
44583
+ * Turn paint props into a CSS style object. Colour values pass through as-is so
44584
+ * they may reference theme tokens via `var()`, which only resolves in a CSS
44585
+ * property context (not in a bare presentation attribute).
44586
+ */
44587
+ function paintStyle(shape) {
44588
+ const style = {};
44589
+ if (shape.fill !== void 0) style.fill = shape.fill;
44590
+ if (shape.stroke !== void 0) style.stroke = shape.stroke;
44591
+ if (shape.strokeWidth !== void 0) style.strokeWidth = shape.strokeWidth;
44592
+ if (shape.fillOpacity !== void 0) style.fillOpacity = shape.fillOpacity;
44593
+ if (shape.strokeOpacity !== void 0) style.strokeOpacity = shape.strokeOpacity;
44594
+ if (shape.strokeLinecap !== void 0) style.strokeLinecap = shape.strokeLinecap;
44595
+ if (shape.strokeLinejoin !== void 0) style.strokeLinejoin = shape.strokeLinejoin;
44596
+ if (shape.strokeDasharray !== void 0) style.strokeDasharray = shape.strokeDasharray;
44597
+ return style;
44598
+ }
44599
+ function renderShape(shape, key) {
44600
+ const style = paintStyle(shape);
44601
+ switch (shape.kind) {
44602
+ case "path": return /* @__PURE__ */ jsx("path", {
44603
+ d: shape.d,
44604
+ style
44605
+ }, key);
44606
+ case "ellipse": return /* @__PURE__ */ jsx("ellipse", {
44607
+ cx: shape.cx,
44608
+ cy: shape.cy,
44609
+ rx: shape.rx,
44610
+ ry: shape.ry,
44611
+ style
44612
+ }, key);
44613
+ case "circle": return /* @__PURE__ */ jsx("circle", {
44614
+ cx: shape.cx,
44615
+ cy: shape.cy,
44616
+ r: shape.r,
44617
+ style
44618
+ }, key);
44619
+ case "rect": return /* @__PURE__ */ jsx("rect", {
44620
+ x: shape.x,
44621
+ y: shape.y,
44622
+ width: shape.width,
44623
+ height: shape.height,
44624
+ rx: shape.rx,
44625
+ ry: shape.ry,
44626
+ style
44627
+ }, key);
44628
+ case "line": return /* @__PURE__ */ jsx("line", {
44629
+ x1: shape.x1,
44630
+ y1: shape.y1,
44631
+ x2: shape.x2,
44632
+ y2: shape.y2,
44633
+ style
44634
+ }, key);
44635
+ default: return null;
44636
+ }
44637
+ }
44638
+ const SvgNode = ({ blockId: _blockId, blockKind: _blockKind, blockContent: _blockContent, fragmentRegistry: _fragmentRegistry, pathBase: _pathBase, viewBox, width, height, className, style, ariaHidden = true, shapes }) => {
44639
+ return /* @__PURE__ */ jsx("svg", {
44640
+ viewBox,
44641
+ width,
44642
+ height,
44643
+ className,
44644
+ style,
44645
+ "aria-hidden": ariaHidden,
44646
+ xmlns: "http://www.w3.org/2000/svg",
44647
+ fill: "none",
44648
+ children: (Array.isArray(shapes) ? shapes : []).map((shape, index) => renderShape(shape, index))
44649
+ });
44650
+ };
43688
44651
  function isRecord$13(value) {
43689
44652
  return typeof value === "object" && value !== null && !Array.isArray(value);
43690
44653
  }
@@ -48919,6 +49882,154 @@ const WeekAgendaView = ({ days, events, showCapacity, emptyMessage = "No events
48919
49882
  });
48920
49883
  };
48921
49884
  //#endregion
49885
+ //#region ../blocks/src/system/runtime/nodes/events/shared/WeekBoardView.tsx
49886
+ const SHORT_DAY_NAMES = [
49887
+ "Sun",
49888
+ "Mon",
49889
+ "Tue",
49890
+ "Wed",
49891
+ "Thu",
49892
+ "Fri",
49893
+ "Sat"
49894
+ ];
49895
+ const LEGEND_ITEMS = [
49896
+ {
49897
+ status: "available",
49898
+ label: "Places available"
49899
+ },
49900
+ {
49901
+ status: "filling",
49902
+ label: "Filling up"
49903
+ },
49904
+ {
49905
+ status: "waitlist",
49906
+ label: "Waitlist"
49907
+ }
49908
+ ];
49909
+ /**
49910
+ * Format an event's start–end as a compact range, merging the shared meridiem.
49911
+ * "6:00 PM" + "7:00 PM" -> "6:00 – 7:00 PM"; "9:30 AM" + "10:15 AM" -> "9:30 – 10:15 AM".
49912
+ */
49913
+ function formatCompactTimeRange(event) {
49914
+ const timeZone = event.timeZone ?? null;
49915
+ const start = formatTime(event.startsAt, { timeZone });
49916
+ const end = formatTime(event.endsAt, { timeZone });
49917
+ const startMeridiem = start.slice(-2);
49918
+ const endMeridiem = end.slice(-2);
49919
+ if ((startMeridiem === "AM" || startMeridiem === "PM") && startMeridiem === endMeridiem) return `${start.slice(0, -3).trim()} – ${end}`;
49920
+ return `${start} – ${end}`;
49921
+ }
49922
+ const WeekBoardCard = ({ event, showCapacity }) => {
49923
+ const linkProps = resolveEventLinkProps(event);
49924
+ const { status, label } = getEventAvailabilityStatus(event);
49925
+ const pill = showCapacity ? label : null;
49926
+ return /* @__PURE__ */ jsxs("a", {
49927
+ ...linkProps,
49928
+ className: "event-week-board-card",
49929
+ "data-status": status,
49930
+ children: [
49931
+ /* @__PURE__ */ jsx("span", {
49932
+ className: "event-week-board-card-title",
49933
+ children: event.title
49934
+ }),
49935
+ /* @__PURE__ */ jsx("span", {
49936
+ className: "event-week-board-card-time",
49937
+ children: formatCompactTimeRange(event)
49938
+ }),
49939
+ pill ? /* @__PURE__ */ jsx("span", {
49940
+ className: "event-week-board-card-pill",
49941
+ "data-status": status,
49942
+ children: pill
49943
+ }) : null
49944
+ ]
49945
+ });
49946
+ };
49947
+ /**
49948
+ * WeekBoardView
49949
+ *
49950
+ * Compact "class day-board": day columns side by side (one card per class per
49951
+ * day), a legend of the three availability states, and a footer CTA. Collapses
49952
+ * to a stacked list on mobile. Repurposes the week-grouped agenda data as a
49953
+ * board rather than an hour-positioned timetable grid.
49954
+ */
49955
+ 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" }) => {
49956
+ const eventsByDate = groupEventsByDate(events);
49957
+ const hasAnyEvents = days.some((date) => (eventsByDate.get(formatDateKey(date)) ?? []).length > 0);
49958
+ const ctaClassName = themeButtonClassName({
49959
+ variant: buttonVariant,
49960
+ size: "sm"
49961
+ });
49962
+ return /* @__PURE__ */ jsx("div", {
49963
+ className: "event-calendar event-calendar--week-board",
49964
+ "data-block": "event-calendar",
49965
+ children: /* @__PURE__ */ jsxs("div", {
49966
+ className: "event-week-board",
49967
+ style: { "--event-week-board-cols": String(days.length) },
49968
+ children: [
49969
+ /* @__PURE__ */ jsxs("div", {
49970
+ className: "event-week-board-top",
49971
+ children: [/* @__PURE__ */ jsx("span", {
49972
+ className: "event-week-board-title",
49973
+ children: title
49974
+ }), /* @__PURE__ */ jsx("div", {
49975
+ className: "event-week-board-legend",
49976
+ "aria-hidden": "true",
49977
+ children: LEGEND_ITEMS.map((item) => /* @__PURE__ */ jsxs("span", {
49978
+ className: "event-week-board-legend-item",
49979
+ children: [/* @__PURE__ */ jsx("i", {
49980
+ className: "event-week-board-legend-dot",
49981
+ "data-status": item.status
49982
+ }), item.label]
49983
+ }, item.status))
49984
+ })]
49985
+ }),
49986
+ /* @__PURE__ */ jsx("div", {
49987
+ className: "event-week-board-grid",
49988
+ children: days.map((date) => {
49989
+ const dateKey = formatDateKey(date);
49990
+ const dayEvents = eventsByDate.get(dateKey) ?? [];
49991
+ const today = isToday(date);
49992
+ return /* @__PURE__ */ jsxs("div", {
49993
+ className: clsx("event-week-board-day", dayEvents.length === 0 && "event-week-board-day--empty", today && "event-week-board-day--today"),
49994
+ children: [/* @__PURE__ */ jsxs("div", {
49995
+ className: "event-week-board-day-head",
49996
+ children: [/* @__PURE__ */ jsx("span", {
49997
+ className: "event-week-board-day-abbr",
49998
+ children: SHORT_DAY_NAMES[date.getDay()]
49999
+ }), /* @__PURE__ */ jsx("span", {
50000
+ className: "event-week-board-day-num",
50001
+ children: date.getDate()
50002
+ })]
50003
+ }), /* @__PURE__ */ jsx("div", {
50004
+ className: "event-week-board-day-body",
50005
+ children: dayEvents.map((event) => /* @__PURE__ */ jsx(WeekBoardCard, {
50006
+ event,
50007
+ showCapacity
50008
+ }, event.occurrenceId || event.id))
50009
+ })]
50010
+ }, dateKey);
50011
+ })
50012
+ }),
50013
+ /* @__PURE__ */ jsxs("div", {
50014
+ className: "event-week-board-foot",
50015
+ children: [/* @__PURE__ */ jsx("p", {
50016
+ className: "event-week-board-foot-note",
50017
+ children: hasAnyEvents ? footerNote : emptyMessage
50018
+ }), ctaHref ? /* @__PURE__ */ jsx("a", {
50019
+ className: ctaClassName,
50020
+ href: ctaHref,
50021
+ children: ctaLabel
50022
+ }) : /* @__PURE__ */ jsx("button", {
50023
+ type: "button",
50024
+ className: ctaClassName,
50025
+ children: ctaLabel
50026
+ })]
50027
+ })
50028
+ ]
50029
+ })
50030
+ });
50031
+ };
50032
+ //#endregion
48922
50033
  //#region ../blocks/src/system/runtime/nodes/events/shared/timetableModel.ts
48923
50034
  /**
48924
50035
  * Event-domain adapter over @riverbankcms/calendar.
@@ -49353,6 +50464,16 @@ function renderTimetableSsr(display) {
49353
50464
  })]
49354
50465
  });
49355
50466
  }
50467
+ function renderWeekBoardSsr(display) {
50468
+ return /* @__PURE__ */ jsx(WeekBoardView, {
50469
+ days: getWeekCalendarDayTuple(/* @__PURE__ */ new Date(), display.startOfWeek),
50470
+ events: [...display.events],
50471
+ showCapacity: display.showCapacity,
50472
+ emptyMessage: display.emptyMessage,
50473
+ ctaLabel: display.buttonText,
50474
+ buttonVariant: display.buttonVariant
50475
+ });
50476
+ }
49356
50477
  function formatHourLabel(hour) {
49357
50478
  if (hour === 0) return "12 AM";
49358
50479
  if (hour === 12) return "12 PM";
@@ -49379,6 +50500,9 @@ const EventCalendarSSR = (props) => {
49379
50500
  case "timetable":
49380
50501
  content = renderTimetableSsr(display);
49381
50502
  break;
50503
+ case "week-board":
50504
+ content = renderWeekBoardSsr(display);
50505
+ break;
49382
50506
  case "calendar":
49383
50507
  content = renderCalendarSsr(display);
49384
50508
  break;
@@ -49950,6 +51074,7 @@ typedObjectKeys(runtimeBackedBlockServerEntries);
49950
51074
  const serverComponentRegistry = {
49951
51075
  div: DivNode,
49952
51076
  figure: FigureNode,
51077
+ svg: SvgNode,
49953
51078
  section: SectionNode,
49954
51079
  headerSection: header_section_server_default,
49955
51080
  stack: StackNode,
@@ -51450,6 +52575,8 @@ function formatFontFamily(family, fallback = "sans-serif") {
51450
52575
  function generateShadowCssVars(theme) {
51451
52576
  const level = theme.shadow.elevation;
51452
52577
  const soft = theme.shadow.softness !== "crisp";
52578
+ const tintVar = theme.shadow.colorToken ? cssVarValue(theme.shadow.colorToken) : null;
52579
+ const shadowColor = (a) => tintVar ? `rgb(${tintVar} / ${a})` : `rgba(0, 0, 0, ${a})`;
51453
52580
  const base = {
51454
52581
  sm: [{
51455
52582
  y: 1,
@@ -51509,7 +52636,7 @@ function generateShadowCssVars(theme) {
51509
52636
  const blurMul = soft ? 1.2 : 1;
51510
52637
  const alphaMul = soft ? .9 : 1.05;
51511
52638
  const fmt = (layers) => layers.map((l) => {
51512
- 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)))})`;
52639
+ 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))))}`;
51513
52640
  }).join(", ");
51514
52641
  return {
51515
52642
  "--shadow-sm": fmt(base.sm),
@@ -51575,7 +52702,13 @@ function generateAccordionCss({ themeId, theme, accordionSystem }) {
51575
52702
  const questionColumnsLastItemSelector = `${questionColumnsItemSelector}:last-child`;
51576
52703
  const questionColumnsTriggerSelector = `${questionColumnsItemSelector} .accordion-trigger`;
51577
52704
  const rootRules = [];
51578
- if (accordionSystem.background) rootRules.push(`background-color: ${rgbColorValue(accordionSystem.background.colorToken)}`);
52705
+ if (accordionSystem.background) {
52706
+ rootRules.push(`background-color: ${rgbColorValue(accordionSystem.background.colorToken)}`);
52707
+ rootRules.push(...surfaceForegroundDeclarations({
52708
+ text: rgbColorValue(accordionSystem.content.textColorToken),
52709
+ heading: rgbColorValue(accordionSystem.trigger.textColorToken)
52710
+ }));
52711
+ }
51579
52712
  const borderRadius = CORNER_MAP$2[accordionSystem.corners ?? theme.corners];
51580
52713
  rootRules.push(`border-radius: ${borderRadius}`);
51581
52714
  if (accordionSystem.border) {
@@ -53159,7 +54292,7 @@ ${root} .status-error { color: rgb(var(--tb-destructive)); }
53159
54292
  ${root} .status-success { color: rgb(var(--tb-success)); }
53160
54293
  ${root} .status-warning { color: rgb(var(--tb-warning)); }
53161
54294
  ${root} .status-info { color: rgb(var(--tb-info)); }
53162
- ${root} .status-muted { ${statusMutedColorDeclaration()} }`);
54295
+ ${root} .status-muted { color: ${surfaceForegroundValue("supporting")}; }`);
53163
54296
  cssBlocks.push(`/* Alert containers */
53164
54297
  ${root} .alert {
53165
54298
  padding: 1rem;
@@ -53297,6 +54430,9 @@ ${root} .step-connector {
53297
54430
  }
53298
54431
  //#endregion
53299
54432
  //#region ../theme-core/src/typography/generateTypographyCss.ts
54433
+ function globalHeadingColorFallback(level) {
54434
+ return `var(--hc-h${level}, var(--hc-heading, ${DEFAULT_SURFACE_TEXT_COLOR}))`;
54435
+ }
53300
54436
  /**
53301
54437
  * Generate typography CSS for heading elements.
53302
54438
  *
@@ -53325,7 +54461,7 @@ ${root} :where(h1) {
53325
54461
  font-style: var(--fi-h1, var(--fi-heading, normal));
53326
54462
  letter-spacing: var(--ls-h1, var(--ls-heading));
53327
54463
  line-height: var(--lh-h1, var(--lh-heading));
53328
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h1, var(--hc-heading, inherit))));
54464
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(1))};
53329
54465
  }
53330
54466
 
53331
54467
  ${root} :where(h2) {
@@ -53334,7 +54470,7 @@ ${root} :where(h2) {
53334
54470
  font-style: var(--fi-h2, var(--fi-heading, normal));
53335
54471
  letter-spacing: var(--ls-h2, var(--ls-heading));
53336
54472
  line-height: var(--lh-h2, var(--lh-heading));
53337
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h2, var(--hc-heading, inherit))));
54473
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(2))};
53338
54474
  }
53339
54475
 
53340
54476
  ${root} :where(h3) {
@@ -53343,7 +54479,7 @@ ${root} :where(h3) {
53343
54479
  font-style: var(--fi-h3, var(--fi-heading, normal));
53344
54480
  letter-spacing: var(--ls-h3, var(--ls-heading));
53345
54481
  line-height: var(--lh-h3, var(--lh-heading));
53346
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h3, var(--hc-heading, inherit))));
54482
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(3))};
53347
54483
  }
53348
54484
 
53349
54485
  ${root} :where(h4) {
@@ -53352,7 +54488,7 @@ ${root} :where(h4) {
53352
54488
  font-style: var(--fi-h4, var(--fi-heading, normal));
53353
54489
  letter-spacing: var(--ls-h4, var(--ls-heading));
53354
54490
  line-height: var(--lh-h4, var(--lh-heading));
53355
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h4, var(--hc-heading, inherit))));
54491
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(4))};
53356
54492
  }
53357
54493
 
53358
54494
  ${root} :where(h5) {
@@ -53361,7 +54497,7 @@ ${root} :where(h5) {
53361
54497
  font-style: var(--fi-h5, var(--fi-heading, normal));
53362
54498
  letter-spacing: var(--ls-h5, var(--ls-heading));
53363
54499
  line-height: var(--lh-h5, var(--lh-heading));
53364
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h5, var(--hc-heading, inherit))));
54500
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(5))};
53365
54501
  }
53366
54502
 
53367
54503
  ${root} :where(h6) {
@@ -53370,7 +54506,7 @@ ${root} :where(h6) {
53370
54506
  font-style: var(--fi-h6, var(--fi-heading, normal));
53371
54507
  letter-spacing: var(--ls-h6, var(--ls-heading));
53372
54508
  line-height: var(--lh-h6, var(--lh-heading));
53373
- color: var(--section-heading-color, var(--section-text-color, var(--hc-h6, var(--hc-heading, inherit))));
54509
+ color: ${surfaceForegroundValue("heading", globalHeadingColorFallback(6))};
53374
54510
  }`);
53375
54511
  cssBlocks.push(`/* Display variant for hero/splash headings */
53376
54512
  ${root} :where(.heading-display) {
@@ -53769,6 +54905,7 @@ const THEME_RADIUS_CSS_VARS = {
53769
54905
  block: "--rb-block-radius",
53770
54906
  button: "--rb-button-radius",
53771
54907
  input: "--rb-input-radius",
54908
+ panel: "--rb-panel-radius",
53772
54909
  legacyCard: "--radius-card",
53773
54910
  legacyControl: "--radius-control"
53774
54911
  };
@@ -53850,6 +54987,10 @@ ${`:where([data-theme-scope="${themeId}"]) :where([data-block="customer-portal"]
53850
54987
  /* Modal (shared rb-modal classes) */
53851
54988
  --rb-modal-overlay-bg: rgb(var(--tb-text, 0 0 0) / 0.35);
53852
54989
  --rb-modal-bg: var(--cp-card-bg);
54990
+ --rb-modal-text: var(--cp-text);
54991
+ --rb-modal-heading: var(--cp-text);
54992
+ --rb-modal-link: var(--cp-primary);
54993
+ --rb-modal-supporting: var(--cp-muted);
53853
54994
  --rb-modal-border: var(--cp-card-border);
53854
54995
  --rb-modal-shadow: var(--shadow-lg, var(--shadow-elev));
53855
54996
  --rb-modal-radius: var(--cp-card-radius);
@@ -54564,6 +55705,7 @@ function buildThemeCssVars(theme) {
54564
55705
  const inputSystem = resolveThemeInputSystem(theme);
54565
55706
  const blockRadius = mapBlockRadius(theme.corners);
54566
55707
  const buttonRadius = mapButtonRadius(theme.corners);
55708
+ const panelRadius = mapPanelRadius(theme.corners);
54567
55709
  const inputRadius = mapInputRadius(inputSystem.base.corners, theme.corners);
54568
55710
  return {
54569
55711
  ...generateCssVars(theme.palette),
@@ -54572,6 +55714,7 @@ function buildThemeCssVars(theme) {
54572
55714
  ...generateShadowCssVars(theme),
54573
55715
  ["--font-heading"]: formatFontFamily(typography.headings.default.family),
54574
55716
  ["--font-body"]: formatFontFamily(typography.body.family),
55717
+ ["--font-eyebrow"]: typography.eyebrow?.family ? formatFontFamily(typography.eyebrow.family) : "var(--font-body)",
54575
55718
  ["--font-weight-heading"]: mapWeight(typography.headings.default.weight),
54576
55719
  ["--font-weight-body"]: mapWeight(typography.body.weight),
54577
55720
  ...typography.headings.h1.weight ? { ["--fw-h1"]: mapWeight(typography.headings.h1.weight) } : {},
@@ -54622,12 +55765,13 @@ function buildThemeCssVars(theme) {
54622
55765
  ["--fs-h3-fluid"]: sized.h3Fluid,
54623
55766
  ["--fs-h1-display"]: sized.h1Display,
54624
55767
  ["--fs-h1-display-fluid"]: sized.h1DisplayFluid,
54625
- ["--shadow-elev"]: mapShadow(shadow.elevation, shadow.softness, shadow.position || "bottom"),
55768
+ ["--shadow-elev"]: mapShadow(shadow.elevation, shadow.softness, shadow.position || "bottom", shadow.colorToken),
54626
55769
  ["--space-mult"]: mapSpaceMult(space),
54627
55770
  ["--motion-duration"]: mapMotionDuration(motion.level),
54628
55771
  ["--motion-ease"]: mapMotionEasing(motion.easing ?? "standard"),
54629
55772
  ["--rt-space-y"]: mapRhythm(space),
54630
55773
  [THEME_RADIUS_CSS_VARS.block]: blockRadius,
55774
+ [THEME_RADIUS_CSS_VARS.panel]: panelRadius,
54631
55775
  ["--rb-block-transition"]: mapBlockTransition(motion.level),
54632
55776
  [THEME_RADIUS_CSS_VARS.button]: buttonRadius,
54633
55777
  ["--rb-border-width"]: mapBorderWidth(border.width),
@@ -54681,20 +55825,22 @@ function mapWeight(w) {
54681
55825
  bold: 700
54682
55826
  }[w];
54683
55827
  }
54684
- function mapShadow(level, softness, position = "bottom") {
55828
+ function mapShadow(level, softness, position = "bottom", colorToken) {
54685
55829
  if (level === "none") return "none";
55830
+ const tintVar = colorToken ? cssVarValue(colorToken) : null;
55831
+ const shadowColor = (a) => tintVar ? `rgb(${tintVar} / ${a})` : `rgba(0,0,0,${a})`;
54686
55832
  const offset = {
54687
55833
  low: 2,
54688
55834
  medium: 4,
54689
55835
  high: 6
54690
55836
  }[level];
54691
55837
  const xOffset = position === "bottom-right" ? offset : 0;
54692
- if (softness === "hard") return `${xOffset}px ${offset}px 0 0 rgba(0,0,0,1)`;
55838
+ if (softness === "hard") return `${xOffset}px ${offset}px 0 0 ${shadowColor("1")}`;
54693
55839
  const soft = softness !== "crisp";
54694
55840
  switch (level) {
54695
- case "low": return soft ? `${xOffset}px 1px 2px rgba(0,0,0,.06)` : `${xOffset}px 1px 1px rgba(0,0,0,.12)`;
54696
- case "medium": return soft ? `${xOffset}px 10px 20px rgba(0,0,0,.08)` : `${xOffset}px 6px 12px rgba(0,0,0,.16)`;
54697
- case "high": return soft ? `${xOffset}px 25px 50px rgba(0,0,0,.18)` : `${xOffset}px 20px 30px rgba(0,0,0,.28)`;
55841
+ case "low": return soft ? `${xOffset}px 1px 2px ${shadowColor(".06")}` : `${xOffset}px 1px 1px ${shadowColor(".12")}`;
55842
+ case "medium": return soft ? `${xOffset}px 10px 20px ${shadowColor(".08")}` : `${xOffset}px 6px 12px ${shadowColor(".16")}`;
55843
+ case "high": return soft ? `${xOffset}px 25px 50px ${shadowColor(".18")}` : `${xOffset}px 20px 30px ${shadowColor(".28")}`;
54698
55844
  }
54699
55845
  }
54700
55846
  function mapSpaceMult(space) {
@@ -54741,6 +55887,21 @@ function mapBlockRadius(corners) {
54741
55887
  }[corners];
54742
55888
  }
54743
55889
  /**
55890
+ * Map corner style to large-panel radius CSS value (Tier 2 `--rb-panel-radius`).
55891
+ * Derived from the same `corners` enum, one step more generous than the
55892
+ * block/card radius so large surfaces can carry a softer corner without a new
55893
+ * schema field. `rounded` lands on 20px (1.25rem), matching the physio mockup's
55894
+ * large-panel radius.
55895
+ */
55896
+ function mapPanelRadius(corners) {
55897
+ return {
55898
+ square: "0",
55899
+ soft: "0.75rem",
55900
+ rounded: "1.25rem",
55901
+ pill: "2rem"
55902
+ }[corners];
55903
+ }
55904
+ /**
54744
55905
  * Map motion level to block transition duration.
54745
55906
  * These are Tier 2 vars used consistently across blocks.
54746
55907
  */
@@ -55724,8 +56885,12 @@ const containerResponsiveThemeCss = `/*
55724
56885
  text-transform: uppercase;
55725
56886
  }
55726
56887
 
55727
- /* Eyebrow text - small uppercase labels for section introductions */
56888
+ /* Eyebrow text - small uppercase labels for section introductions.
56889
+ font-family resolves to the optional eyebrow/mono font role, falling back
56890
+ to the body font when the theme sets none (so existing themes are
56891
+ unchanged). */
55728
56892
  .rb-eyebrow {
56893
+ font-family: var(--font-eyebrow, var(--font-body));
55729
56894
  font-size: 0.875rem;
55730
56895
  font-weight: 600;
55731
56896
  letter-spacing: 0.025em;
@@ -55740,11 +56905,11 @@ const containerResponsiveThemeCss = `/*
55740
56905
  width: fit-content;
55741
56906
  align-items: center;
55742
56907
  justify-content: center;
55743
- border: 1px solid color-mix(in srgb, var(--section-text-color, rgb(var(--tb-text))) 46%, transparent);
56908
+ border: 1px solid color-mix(in srgb, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 46%, transparent);
55744
56909
  border-radius: 9999px;
55745
- background: color-mix(in srgb, var(--section-text-color, rgb(var(--tb-text))) 14%, transparent);
56910
+ background: color-mix(in srgb, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 14%, transparent);
55746
56911
  padding: 0.5rem 1rem;
55747
- color: var(--section-text-color, rgb(var(--tb-text)));
56912
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
55748
56913
  line-height: 1;
55749
56914
  }
55750
56915
  .rb-eyebrow-treatment-pill.rb-eyebrow-treatment-accent {
@@ -56246,6 +57411,58 @@ const containerResponsiveThemeCss = `/*
56246
57411
  }
56247
57412
  }
56248
57413
 
57414
+ }
57415
+
57416
+ /*
57417
+ * Responsive fractional grid tracks (rb-grid-tracks).
57418
+ *
57419
+ * Asymmetric AND responsive grid-template-columns, driven by inline custom
57420
+ * properties the Grid primitive sets per breakpoint from string track
57421
+ * templates:
57422
+ * --rb-gtc-base / --rb-gtc-sm / --rb-gtc-md / --rb-gtc-lg / --rb-gtc-xl
57423
+ * Each breakpoint falls back to the next smaller one, so only the breakpoints
57424
+ * an author actually sets take effect (e.g. base '1fr' + lg '1fr 0.9fr'
57425
+ * collapses to a single column below lg). Authored here (not per node) so the
57426
+ * @media -> @container conversion (container responsive mode) applies too.
57427
+ */
57428
+ .rb-grid-tracks {
57429
+ grid-template-columns: var(--rb-gtc-base, 1fr);
57430
+ }
57431
+ @container rb-site (min-width: 640px) {
57432
+
57433
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57434
+ .rb-grid-tracks {
57435
+ grid-template-columns: var(--rb-gtc-sm, var(--rb-gtc-base, 1fr));
57436
+ }
57437
+ }
57438
+
57439
+ }
57440
+ @container rb-site (min-width: 768px) {
57441
+
57442
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57443
+ .rb-grid-tracks {
57444
+ grid-template-columns: var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr)));
57445
+ }
57446
+ }
57447
+
57448
+ }
57449
+ @container rb-site (min-width: 1024px) {
57450
+
57451
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57452
+ .rb-grid-tracks {
57453
+ grid-template-columns: var(--rb-gtc-lg, var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr))));
57454
+ }
57455
+ }
57456
+
57457
+ }
57458
+ @container rb-site (min-width: 1280px) {
57459
+
57460
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
57461
+ .rb-grid-tracks {
57462
+ grid-template-columns: var(--rb-gtc-xl, var(--rb-gtc-lg, var(--rb-gtc-md, var(--rb-gtc-sm, var(--rb-gtc-base, 1fr)))));
57463
+ }
57464
+ }
57465
+
56249
57466
  }
56250
57467
 
56251
57468
  /* Alignment */
@@ -57450,6 +58667,11 @@ const containerResponsiveThemeCss = `/*
57450
58667
  :where(.theme-scope) .rb-modal {
57451
58668
  width: min(var(--rb-modal-max-width, 560px), 100%);
57452
58669
  background: var(--rb-modal-bg, rgb(var(--tb-surface)));
58670
+ color: var(--rb-modal-text, rgb(var(--tb-text, 17 24 39)));
58671
+ --rb-surface-text-color: var(--rb-modal-text, rgb(var(--tb-text, 17 24 39)));
58672
+ --rb-surface-heading-color: var(--rb-modal-heading, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
58673
+ --rb-surface-link-color: var(--rb-modal-link, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
58674
+ --rb-surface-supporting-color: var(--rb-modal-supporting, var(--rb-modal-text, rgb(var(--tb-text, 17 24 39))));
57453
58675
  border: 1px solid var(--rb-modal-border, rgb(var(--tb-border)));
57454
58676
  border-radius: var(--rb-modal-radius, var(--radius-card));
57455
58677
  box-shadow: var(--rb-modal-shadow, var(--shadow-lg, var(--shadow-elev)));
@@ -57469,7 +58691,7 @@ const containerResponsiveThemeCss = `/*
57469
58691
  font-family: var(--font-heading);
57470
58692
  font-size: 1.0625rem;
57471
58693
  font-weight: 600;
57472
- color: rgb(var(--tb-text));
58694
+ color: var(--rb-surface-heading-color, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))));
57473
58695
  }
57474
58696
 
57475
58697
  :where(.theme-scope) .rb-modal__close {
@@ -57479,7 +58701,7 @@ const containerResponsiveThemeCss = `/*
57479
58701
  height: 2.25rem;
57480
58702
  padding: 0;
57481
58703
  line-height: 1;
57482
- color: rgb(var(--tb-mutedText));
58704
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
57483
58705
  cursor: pointer;
57484
58706
  display: grid;
57485
58707
  place-items: center;
@@ -57487,7 +58709,7 @@ const containerResponsiveThemeCss = `/*
57487
58709
  }
57488
58710
 
57489
58711
  :where(.theme-scope) .rb-modal__close:hover {
57490
- color: rgb(var(--tb-text));
58712
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
57491
58713
  background: rgba(var(--tb-border), 0.2);
57492
58714
  }
57493
58715
 
@@ -57498,7 +58720,7 @@ const containerResponsiveThemeCss = `/*
57498
58720
 
57499
58721
  :where(.theme-scope) .rb-modal__body {
57500
58722
  padding: 0.875rem 1rem 1rem;
57501
- color: rgb(var(--tb-text));
58723
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
57502
58724
  font-size: 0.9375rem;
57503
58725
  line-height: 1.45;
57504
58726
  }
@@ -57658,7 +58880,7 @@ const containerResponsiveThemeCss = `/*
57658
58880
  & h6 {
57659
58881
  font-family: var(--font-heading);
57660
58882
  /* Color is set by generateTypographyCss.ts with full cascade:
57661
- --section-heading-color -> --section-text-color -> --hc-hN -> --hc-heading -> inherit */
58883
+ --rb-surface-heading-color -> --rb-surface-text-color -> --hc-hN -> --hc-heading -> global text */
57662
58884
  font-weight: var(--font-weight-heading);
57663
58885
  text-transform: var(--tt-heading);
57664
58886
  font-variant-caps: var(--fv-heading);
@@ -57806,7 +59028,7 @@ const containerResponsiveThemeCss = `/*
57806
59028
 
57807
59029
  /* Neutral prose uses theme text color by default */
57808
59030
  :where(.theme-scope) .rb-prose-neutral {
57809
- color: var(--section-text-color, rgb(var(--tb-text)));
59031
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
57810
59032
  }
57811
59033
 
57812
59034
  :where(.theme-scope) .rb-prose p,
@@ -57840,8 +59062,14 @@ const containerResponsiveThemeCss = `/*
57840
59062
 
57841
59063
  :where(.theme-scope) .rb-prose a {
57842
59064
  --rb-prose-effective-link-color: var(
57843
- --section-link-color,
57844
- var(--rb-prose-link-color, rgb(var(--tb-primary)))
59065
+ --rb-surface-link-color,
59066
+ var(
59067
+ --rb-surface-text-color,
59068
+ var(
59069
+ --rb-prose-link-color,
59070
+ rgb(var(--tb-primary, var(--tb-text, 17 24 39)))
59071
+ )
59072
+ )
57845
59073
  );
57846
59074
  color: var(--rb-prose-effective-link-color);
57847
59075
  text-decoration-line: var(--rb-prose-link-decoration-line, underline);
@@ -57852,14 +59080,11 @@ const containerResponsiveThemeCss = `/*
57852
59080
  );
57853
59081
  text-underline-offset: var(--rb-prose-link-underline-offset, 0.14em);
57854
59082
  text-decoration-color: var(
57855
- --section-link-decoration-color,
57856
- var(
57857
- --rb-prose-link-decoration-color,
57858
- color-mix(
57859
- in srgb,
57860
- var(--rb-prose-effective-link-color) 35%,
57861
- transparent
57862
- )
59083
+ --rb-prose-link-decoration-color,
59084
+ color-mix(
59085
+ in srgb,
59086
+ var(--rb-prose-effective-link-color) 35%,
59087
+ transparent
57863
59088
  )
57864
59089
  );
57865
59090
  transition:
@@ -57883,7 +59108,7 @@ const containerResponsiveThemeCss = `/*
57883
59108
 
57884
59109
  :where(.theme-scope) .rb-prose blockquote {
57885
59110
  /* Blockquotes use muted text color */
57886
- color: var(--section-supporting-color, rgb(var(--tb-mutedText)));
59111
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
57887
59112
  border-left-color: rgb(var(--tb-border));
57888
59113
  }
57889
59114
 
@@ -58917,6 +60142,10 @@ const containerResponsiveThemeCss = `/*
58917
60142
  :where(.theme-scope) .card-surface {
58918
60143
  background: rgb(var(--tb-surface));
58919
60144
  color: rgb(var(--tb-onSurface, var(--tb-text)));
60145
+ --rb-surface-text-color: rgb(var(--tb-onSurface, var(--tb-text)));
60146
+ --rb-surface-heading-color: rgb(var(--tb-onSurface, var(--tb-text)));
60147
+ --rb-surface-link-color: rgb(var(--tb-onSurface, var(--tb-text)));
60148
+ --rb-surface-supporting-color: rgb(var(--tb-onSurface, var(--tb-text)));
58920
60149
  border: 1px solid rgb(var(--tb-border));
58921
60150
  border-radius: var(--radius-card);
58922
60151
  box-shadow: var(--shadow-elev);
@@ -59060,6 +60289,10 @@ const containerResponsiveThemeCss = `/*
59060
60289
  padding: clamp(1rem, 2vw, 1.5rem);
59061
60290
  background: var(--rb-reg-surface-bg, rgb(var(--tb-surface, 248 250 252)));
59062
60291
  color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60292
+ --rb-surface-text-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60293
+ --rb-surface-heading-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60294
+ --rb-surface-link-color: var(--rb-reg-on-surface, rgb(var(--tb-onSurface, var(--tb-text, 15 23 42))));
60295
+ --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)))));
59063
60296
  transition: border-color var(--rb-block-transition, 150ms) ease;
59064
60297
  width: 100%;
59065
60298
  max-width: 800px;
@@ -62147,6 +63380,350 @@ const containerResponsiveThemeCss = `/*
62147
63380
  }
62148
63381
  }
62149
63382
 
63383
+ /* -----------------------------------------------------------------------------
63384
+ * Week Board View
63385
+ * Compact "class day-board": day columns side by side, one card per class,
63386
+ * three-state availability (available / filling / waitlist), and a footer CTA.
63387
+ * ----------------------------------------------------------------------------- */
63388
+
63389
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board {
63390
+ /* Status accent tokens — map the three availability states onto theme colours.
63391
+ * available -> secondary, filling -> accent, waitlist -> muted text. */
63392
+ --ec-status-available: rgb(var(--tb-secondary));
63393
+ --ec-status-filling: rgb(var(--tb-accent));
63394
+ --ec-status-waitlist: rgba(var(--tb-text), 0.4);
63395
+ --event-week-board-cols: 7;
63396
+
63397
+ border: 1px solid var(--ec-border);
63398
+ border-radius: var(--rb-block-radius);
63399
+ background-color: var(--ec-surface);
63400
+ color: var(--ec-text);
63401
+ overflow: hidden;
63402
+ box-shadow: 0 14px 34px -24px rgba(var(--tb-text), 0.35);
63403
+ }
63404
+
63405
+ /* Top bar: title + legend */
63406
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-top {
63407
+ display: flex;
63408
+ align-items: center;
63409
+ justify-content: space-between;
63410
+ gap: 1rem;
63411
+ flex-wrap: wrap;
63412
+ padding: 1.1rem 1.5rem;
63413
+ background-color: var(--ec-header-bg);
63414
+ border-bottom: 1px solid var(--ec-border-subtle);
63415
+ }
63416
+
63417
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-title {
63418
+ font-size: 0.8125rem;
63419
+ font-weight: 600;
63420
+ letter-spacing: 0.12em;
63421
+ text-transform: uppercase;
63422
+ color: rgb(var(--tb-primary));
63423
+ }
63424
+
63425
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend {
63426
+ display: flex;
63427
+ align-items: center;
63428
+ gap: 1.1rem;
63429
+ flex-wrap: wrap;
63430
+ font-size: 0.6875rem;
63431
+ letter-spacing: 0.03em;
63432
+ opacity: 0.75;
63433
+ }
63434
+
63435
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-item {
63436
+ display: inline-flex;
63437
+ align-items: center;
63438
+ gap: 0.4rem;
63439
+ }
63440
+
63441
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot {
63442
+ width: 0.55rem;
63443
+ height: 0.55rem;
63444
+ border-radius: 50%;
63445
+ display: inline-block;
63446
+ }
63447
+
63448
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="available"] {
63449
+ background-color: var(--ec-status-available);
63450
+ }
63451
+
63452
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="filling"] {
63453
+ background-color: var(--ec-status-filling);
63454
+ }
63455
+
63456
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend-dot[data-status="waitlist"] {
63457
+ background-color: var(--ec-status-waitlist);
63458
+ }
63459
+
63460
+ /* Day columns grid */
63461
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-grid {
63462
+ display: grid;
63463
+ grid-template-columns: repeat(var(--event-week-board-cols), minmax(0, 1fr));
63464
+ }
63465
+
63466
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day {
63467
+ display: flex;
63468
+ flex-direction: column;
63469
+ min-height: 15rem;
63470
+ /* Tabular column rule. Driven by the theme border tokens so borderless site
63471
+ * styles (border.width: 'none' -> --rb-border-width: 0) render no grid at all,
63472
+ * while hairline/thin/thick themes get a crisp full-strength divider. */
63473
+ border-right: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63474
+ }
63475
+
63476
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day:last-child {
63477
+ border-right: none;
63478
+ }
63479
+
63480
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-head {
63481
+ display: flex;
63482
+ align-items: baseline;
63483
+ gap: 0.4rem;
63484
+ padding: 0.9rem 0.9rem 0.65rem;
63485
+ /* Header-row underline — same border-token gating as the column rules. */
63486
+ border-bottom: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63487
+ }
63488
+
63489
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--today .event-week-board-day-head {
63490
+ background-color: var(--ec-today-bg);
63491
+ }
63492
+
63493
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-abbr {
63494
+ font-size: 0.6875rem;
63495
+ font-weight: 600;
63496
+ letter-spacing: 0.1em;
63497
+ text-transform: uppercase;
63498
+ opacity: 0.65;
63499
+ }
63500
+
63501
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--today .event-week-board-day-abbr {
63502
+ opacity: 1;
63503
+ color: var(--ec-today-text);
63504
+ }
63505
+
63506
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-num {
63507
+ font-size: 0.8125rem;
63508
+ font-weight: 600;
63509
+ opacity: 0.5;
63510
+ }
63511
+
63512
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-body {
63513
+ display: flex;
63514
+ flex-direction: column;
63515
+ gap: 0.65rem;
63516
+ padding: 0.9rem;
63517
+ flex: 1;
63518
+ }
63519
+
63520
+ /* Class card */
63521
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
63522
+ display: flex;
63523
+ flex-direction: column;
63524
+ gap: 0.45rem;
63525
+ padding: 0.8rem 0.85rem;
63526
+ border: 1px solid var(--ec-border-subtle);
63527
+ border-left: 3px solid var(--ec-status-available);
63528
+ border-radius: calc(var(--rb-block-radius) / 1.5);
63529
+ background-color: var(--ec-surface);
63530
+ color: inherit;
63531
+ text-decoration: none;
63532
+ transition:
63533
+ transform var(--rb-block-transition) ease,
63534
+ box-shadow var(--rb-block-transition) ease,
63535
+ border-color var(--rb-block-transition) ease;
63536
+ }
63537
+
63538
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card[data-status="filling"] {
63539
+ border-left-color: var(--ec-status-filling);
63540
+ }
63541
+
63542
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card[data-status="waitlist"] {
63543
+ border-left-color: var(--ec-status-waitlist);
63544
+ }
63545
+
63546
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card:hover {
63547
+ transform: translateY(-2px);
63548
+ box-shadow: 0 6px 16px -8px rgba(var(--tb-text), 0.28);
63549
+ border-color: rgba(var(--tb-primary), 0.4);
63550
+ }
63551
+
63552
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card:focus-visible {
63553
+ outline: 2px solid rgb(var(--tb-primary));
63554
+ outline-offset: 2px;
63555
+ }
63556
+
63557
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-title {
63558
+ font-family: var(--font-heading, inherit);
63559
+ font-size: 0.9375rem;
63560
+ font-weight: 600;
63561
+ line-height: 1.2;
63562
+ }
63563
+
63564
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-time {
63565
+ font-size: 0.75rem;
63566
+ letter-spacing: 0.01em;
63567
+ opacity: 0.7;
63568
+ }
63569
+
63570
+ /* Capacity pill */
63571
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill {
63572
+ align-self: flex-start;
63573
+ display: inline-flex;
63574
+ align-items: center;
63575
+ padding: 0.15rem 0.6rem;
63576
+ border-radius: 999px;
63577
+ font-size: 0.6875rem;
63578
+ font-weight: 600;
63579
+ letter-spacing: 0.02em;
63580
+ /* Available (default): a calm neutral/muted pill — not a coloured outline.
63581
+ * Filling -> amber and waitlist -> muted outline are handled by the
63582
+ * data-status overrides below. */
63583
+ border: 1px solid var(--ec-border);
63584
+ background-color: rgba(var(--tb-text), 0.06);
63585
+ color: rgba(var(--tb-text), 0.62);
63586
+ border-color: var(--ec-border);
63587
+ }
63588
+
63589
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill[data-status="filling"] {
63590
+ background-color: rgba(var(--tb-accent), 0.14);
63591
+ color: var(--ec-status-filling);
63592
+ border-color: rgba(var(--tb-accent), 0.32);
63593
+ }
63594
+
63595
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card-pill[data-status="waitlist"] {
63596
+ background-color: transparent;
63597
+ color: rgba(var(--tb-text), 0.6);
63598
+ border-color: var(--ec-border);
63599
+ }
63600
+
63601
+ /* Footer */
63602
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-foot {
63603
+ display: flex;
63604
+ align-items: center;
63605
+ justify-content: space-between;
63606
+ gap: 1rem;
63607
+ flex-wrap: wrap;
63608
+ padding: 1.1rem 1.5rem;
63609
+ background-color: var(--ec-header-bg);
63610
+ border-top: 1px solid var(--ec-border-subtle);
63611
+ }
63612
+
63613
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-foot-note {
63614
+ margin: 0;
63615
+ font-size: 0.875rem;
63616
+ opacity: 0.75;
63617
+ }
63618
+
63619
+ /* Responsive: collapse day columns to a stacked list on narrow viewports */
63620
+ @container rb-site (max-width: 1023px) {
63621
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
63622
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-grid {
63623
+ grid-template-columns: 1fr;
63624
+ }
63625
+
63626
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day {
63627
+ min-height: 0;
63628
+ border-right: none;
63629
+ border-bottom: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63630
+ }
63631
+
63632
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day:last-child {
63633
+ border-bottom: none;
63634
+ }
63635
+
63636
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day--empty {
63637
+ display: none;
63638
+ }
63639
+
63640
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-head {
63641
+ border-bottom: none;
63642
+ padding-bottom: 0.25rem;
63643
+ }
63644
+
63645
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-day-body {
63646
+ flex-direction: row;
63647
+ flex-wrap: wrap;
63648
+ padding-top: 0.35rem;
63649
+ }
63650
+
63651
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
63652
+ flex: 1 1 14rem;
63653
+ }
63654
+ }
63655
+ }
63656
+
63657
+ @container rb-site (max-width: 639px) {
63658
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
63659
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-legend {
63660
+ display: none;
63661
+ }
63662
+
63663
+ :where(.theme-scope) :where([data-block="event-calendar"]) .event-week-board-card {
63664
+ flex: 1 1 100%;
63665
+ }
63666
+ }
63667
+ }
63668
+
63669
+ /* -----------------------------------------------------------------------------
63670
+ * Week-board section band
63671
+ * Give the whole week-board section (head + panel) a subtle surface band so it
63672
+ * reads as a distinct zone from the page — the "testimonials band" pattern.
63673
+ * Painted as a pseudo-element above the section's fallback background layer
63674
+ * (z-index -10) but behind the content, so no block-content wiring is needed.
63675
+ * Surface + hairline rules are token-referenced: on a borderless site style
63676
+ * (--rb-border-width: 0) the band keeps its surface tint but drops the rules.
63677
+ * ----------------------------------------------------------------------------- */
63678
+ :where(.theme-scope) [data-slot="section"]:has(.event-calendar--week-board)::before {
63679
+ content: "";
63680
+ position: absolute;
63681
+ inset: 0;
63682
+ z-index: -9;
63683
+ background-color: rgb(var(--tb-surfaceAlt));
63684
+ border-block: var(--rb-border-width, 1px) solid rgb(var(--tb-border));
63685
+ pointer-events: none;
63686
+ }
63687
+
63688
+ /* -----------------------------------------------------------------------------
63689
+ * Event section head — split (heading left, intro right)
63690
+ * Rendered by the block frame (outside [data-block="event-calendar"]) via
63691
+ * eventSectionHeaderLayoutNodes({ splitClassName }). Lays the heading and its
63692
+ * supporting intro out as a two-column head on wide viewports, stacking on
63693
+ * narrow ones. The section band above provides the distinct background.
63694
+ * ----------------------------------------------------------------------------- */
63695
+ :where(.theme-scope) .event-section-head--split {
63696
+ display: flex;
63697
+ align-items: flex-end;
63698
+ justify-content: space-between;
63699
+ gap: clamp(1.5rem, 4vw, 3.5rem);
63700
+ flex-wrap: wrap;
63701
+ }
63702
+
63703
+ :where(.theme-scope) .event-section-head--split > :where(h1, h2, h3) {
63704
+ margin: 0;
63705
+ }
63706
+
63707
+ :where(.theme-scope) .event-section-head--split > .rb-prose {
63708
+ flex: 1 1 22rem;
63709
+ max-width: 34rem;
63710
+ margin: 0;
63711
+ }
63712
+
63713
+ @container rb-site (max-width: 767px) {
63714
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
63715
+ :where(.theme-scope) .event-section-head--split {
63716
+ flex-direction: column;
63717
+ align-items: flex-start;
63718
+ gap: 0.75rem;
63719
+ }
63720
+
63721
+ :where(.theme-scope) .event-section-head--split > .rb-prose {
63722
+ flex-basis: auto;
63723
+ }
63724
+ }
63725
+ }
63726
+
62150
63727
  /* -----------------------------------------------------------------------------
62151
63728
  * Event Calendar Modals
62152
63729
  * Modal overlays for event details and day events list
@@ -62574,7 +64151,7 @@ const containerResponsiveThemeCss = `/*
62574
64151
  }
62575
64152
 
62576
64153
  :where(.theme-scope) .rb-inline-text-accent-contrast {
62577
- color: var(--section-text-color, rgb(var(--tb-text)));
64154
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
62578
64155
  }
62579
64156
 
62580
64157
  :where(.theme-scope) .rb-inline-text-highlight {
@@ -62585,7 +64162,7 @@ const containerResponsiveThemeCss = `/*
62585
64162
  }
62586
64163
 
62587
64164
  :where(.theme-scope) .rb-inline-text-highlight-soft {
62588
- background: color-mix(in oklab, var(--section-text-color, rgb(var(--tb-text))) 14%, transparent);
64165
+ background: color-mix(in oklab, var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39))) 14%, transparent);
62589
64166
  }
62590
64167
 
62591
64168
  :where(.theme-scope) .rb-inline-text-highlight-accent {
@@ -62604,7 +64181,7 @@ const containerResponsiveThemeCss = `/*
62604
64181
 
62605
64182
  /* Eyebrow - color only (typography via .rb-eyebrow) */
62606
64183
  :where(.theme-scope) :where([data-block="hero"]) .hero-eyebrow:not(.rb-eyebrow-treatment-accent) {
62607
- color: var(--section-text-color, rgb(var(--tb-text)));
64184
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
62608
64185
  }
62609
64186
 
62610
64187
  /* Subheadline - supporting text below headline */
@@ -62614,7 +64191,7 @@ const containerResponsiveThemeCss = `/*
62614
64191
 
62615
64192
  :where(.theme-scope) :where([data-block="hero"]) .hero-subheadline {
62616
64193
  font-size: 1.125rem;
62617
- color: var(--section-supporting-color, var(--section-text-color, rgb(var(--tb-text))));
64194
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
62618
64195
  }
62619
64196
 
62620
64197
  @container rb-site (min-width: 640px) {
@@ -62705,7 +64282,7 @@ const containerResponsiveThemeCss = `/*
62705
64282
 
62706
64283
  /* CTA content/body text */
62707
64284
  :where(.theme-scope) :where([data-block="cta-full"]) .cta-content {
62708
- color: var(--section-supporting-color, rgb(var(--tb-mutedText)));
64285
+ color: var(--rb-surface-supporting-color, var(--rb-surface-text-color, rgb(var(--tb-mutedText, 100 116 139))));
62709
64286
  }
62710
64287
 
62711
64288
  /* CTA button row container */
@@ -62805,7 +64382,7 @@ const containerResponsiveThemeCss = `/*
62805
64382
  /* Form intro text */
62806
64383
  :where(.theme-scope) :where([data-block="form"]) .form-copy-intro {
62807
64384
  font-size: 1rem;
62808
- color: var(--section-text-color, rgb(var(--tb-text)));
64385
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
62809
64386
  }
62810
64387
 
62811
64388
  /* =============================================================================
@@ -63164,6 +64741,10 @@ const containerResponsiveThemeCss = `/*
63164
64741
  --rb-modal-overlay-bg: rgb(var(--tb-text, 0 0 0) / 0.6);
63165
64742
  --rb-modal-shadow: var(--shadow-lg, var(--shadow-elev));
63166
64743
  --rb-modal-z-index: 20000;
64744
+ --rb-modal-text: rgb(var(--tb-text, 17 24 39));
64745
+ --rb-modal-heading: rgb(var(--tb-text, 17 24 39));
64746
+ --rb-modal-link: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
64747
+ --rb-modal-supporting: rgb(var(--tb-mutedText, 100 116 139));
63167
64748
  --shop-card-radius: var(--rb-block-radius, var(--radius-card, 0.75rem));
63168
64749
  --shop-control-radius: var(--rb-input-radius, var(--radius-control, 0.375rem));
63169
64750
  }
@@ -63221,6 +64802,11 @@ const containerResponsiveThemeCss = `/*
63221
64802
  /* Product card */
63222
64803
  :where(.theme-scope) .shop__card {
63223
64804
  background: rgb(var(--tb-surface));
64805
+ color: rgb(var(--tb-text, 17 24 39));
64806
+ --rb-surface-text-color: rgb(var(--tb-text, 17 24 39));
64807
+ --rb-surface-heading-color: rgb(var(--tb-text, 17 24 39));
64808
+ --rb-surface-link-color: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
64809
+ --rb-surface-supporting-color: rgb(var(--tb-mutedText, 100 116 139));
63224
64810
  border: 1px solid rgb(var(--tb-border));
63225
64811
  border-radius: var(--shop-card-radius);
63226
64812
  padding: 1.5rem;
@@ -63375,6 +64961,11 @@ const containerResponsiveThemeCss = `/*
63375
64961
  border: 1px solid rgb(var(--tb-border));
63376
64962
  border-radius: var(--shop-card-radius);
63377
64963
  background: rgb(var(--tb-surface));
64964
+ color: rgb(var(--tb-text, 17 24 39));
64965
+ --rb-surface-text-color: rgb(var(--tb-text, 17 24 39));
64966
+ --rb-surface-heading-color: rgb(var(--tb-text, 17 24 39));
64967
+ --rb-surface-link-color: rgb(var(--tb-primary, var(--tb-text, 17 24 39)));
64968
+ --rb-surface-supporting-color: rgb(var(--tb-mutedText, 100 116 139));
63378
64969
  }
63379
64970
 
63380
64971
  :where(.theme-scope) :where([data-block="shop"]) .rb-state-icon--success {
@@ -63447,6 +65038,11 @@ const containerResponsiveThemeCss = `/*
63447
65038
  margin: 0 auto;
63448
65039
  padding: 2rem;
63449
65040
  background: var(--cp-card-bg);
65041
+ color: var(--cp-text);
65042
+ --rb-surface-text-color: var(--cp-text);
65043
+ --rb-surface-heading-color: var(--cp-text);
65044
+ --rb-surface-link-color: var(--cp-primary);
65045
+ --rb-surface-supporting-color: var(--cp-muted);
63450
65046
  border: 1px solid var(--cp-card-border);
63451
65047
  border-radius: var(--cp-card-radius);
63452
65048
  }
@@ -63575,6 +65171,11 @@ const containerResponsiveThemeCss = `/*
63575
65171
  padding: 3rem 2rem;
63576
65172
  text-align: center;
63577
65173
  background: var(--cp-card-bg);
65174
+ color: var(--cp-text);
65175
+ --rb-surface-text-color: var(--cp-text);
65176
+ --rb-surface-heading-color: var(--cp-text);
65177
+ --rb-surface-link-color: var(--cp-primary);
65178
+ --rb-surface-supporting-color: var(--cp-muted);
63578
65179
  border: 1px dashed var(--cp-card-border);
63579
65180
  border-radius: var(--cp-card-radius);
63580
65181
  }
@@ -63587,6 +65188,11 @@ const containerResponsiveThemeCss = `/*
63587
65188
  /* Card */
63588
65189
  :where(.theme-scope) :where([data-block="customer-portal"]) .cp-card {
63589
65190
  background: var(--cp-card-bg);
65191
+ color: var(--cp-text);
65192
+ --rb-surface-text-color: var(--cp-text);
65193
+ --rb-surface-heading-color: var(--cp-text);
65194
+ --rb-surface-link-color: var(--cp-primary);
65195
+ --rb-surface-supporting-color: var(--cp-muted);
63590
65196
  border: 1px solid var(--cp-card-border);
63591
65197
  border-radius: var(--cp-card-radius);
63592
65198
  padding: 1.25rem;
@@ -63751,6 +65357,11 @@ const containerResponsiveThemeCss = `/*
63751
65357
  /* Table */
63752
65358
  :where(.theme-scope) :where([data-block="customer-portal"]) .cp-table-wrap {
63753
65359
  background: var(--cp-card-bg);
65360
+ color: var(--cp-text);
65361
+ --rb-surface-text-color: var(--cp-text);
65362
+ --rb-surface-heading-color: var(--cp-text);
65363
+ --rb-surface-link-color: var(--cp-primary);
65364
+ --rb-surface-supporting-color: var(--cp-muted);
63754
65365
  border: 1px solid var(--cp-card-border);
63755
65366
  border-radius: var(--cp-card-radius);
63756
65367
  }
@@ -64312,6 +65923,143 @@ const containerResponsiveThemeCss = `/*
64312
65923
  }
64313
65924
 
64314
65925
 
65926
+ /* =============================================================================
65927
+ * Variant: featured-editorial
65928
+ *
65929
+ * An asymmetric layout: a large featured (spruce-ink) quote card beside a
65930
+ * column of stacked white cards. Renders NO avatars. Colours are palette
65931
+ * tokens only. Node structure is built in testimonialsCarousel.ts.
65932
+ * ============================================================================= */
65933
+
65934
+ /* Section head: heading left, lead-in line to the right; stacks on mobile. */
65935
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-head {
65936
+ display: flex;
65937
+ flex-direction: column;
65938
+ align-items: flex-start;
65939
+ gap: 0.875rem;
65940
+ }
65941
+
65942
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-leadin {
65943
+ max-width: 30rem;
65944
+ }
65945
+
65946
+ @container rb-site (min-width: 768px) {
65947
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
65948
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-head {
65949
+ flex-direction: row;
65950
+ align-items: flex-end;
65951
+ justify-content: space-between;
65952
+ gap: 1.5rem;
65953
+ }
65954
+ }
65955
+ }
65956
+
65957
+ /* Card grid: featured (wider) beside the stacked column. Single column on
65958
+ * small screens. */
65959
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-grid {
65960
+ display: grid;
65961
+ grid-template-columns: minmax(0, 1fr);
65962
+ gap: 1.25rem;
65963
+ align-items: stretch;
65964
+ }
65965
+
65966
+ @container rb-site (min-width: 1024px) {
65967
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
65968
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-grid {
65969
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
65970
+ }
65971
+ }
65972
+ }
65973
+
65974
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-col {
65975
+ display: grid;
65976
+ gap: 1.25rem;
65977
+ align-content: start;
65978
+ }
65979
+
65980
+ /* Base card (white surface) */
65981
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card {
65982
+ display: flex;
65983
+ flex-direction: column;
65984
+ height: 100%;
65985
+ border-radius: var(--rb-block-radius);
65986
+ padding: 1.875rem 1.875rem 1.625rem;
65987
+ background: rgb(var(--tb-surface));
65988
+ border: 1px solid rgba(var(--tb-border), 0.9);
65989
+ box-shadow: 0 1px 2px rgba(var(--tb-shadow, 0 0 0), 0.05);
65990
+ }
65991
+
65992
+ /* Featured (spruce-ink) card */
65993
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature {
65994
+ padding: 2.5rem;
65995
+ background: rgb(var(--tb-primary));
65996
+ border-color: transparent;
65997
+ color: rgb(var(--tb-onPrimary));
65998
+ }
65999
+
66000
+ /* Amber quote mark, oversized serif */
66001
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-qmark {
66002
+ display: block;
66003
+ height: 1.6rem;
66004
+ font-family: var(--font-heading, Georgia, serif);
66005
+ font-style: italic;
66006
+ font-size: 3.375rem;
66007
+ line-height: 0.6;
66008
+ color: rgb(var(--tb-secondary));
66009
+ }
66010
+
66011
+ /* Quote body. The inner <p> is targeted directly (not via :where) so the
66012
+ * serif display font and sizing win over the theme's base paragraph rule. */
66013
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-quote {
66014
+ flex: 1 1 auto;
66015
+ margin-top: 1.25rem;
66016
+ }
66017
+
66018
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-quote p {
66019
+ margin: 0;
66020
+ font-family: var(--font-heading, Georgia, serif);
66021
+ font-size: 1.15rem;
66022
+ line-height: 1.5;
66023
+ color: rgb(var(--tb-text));
66024
+ }
66025
+
66026
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-quote p {
66027
+ color: rgb(var(--tb-onPrimary));
66028
+ font-size: clamp(1.375rem, 2.4vw, 1.6875rem);
66029
+ line-height: 1.42;
66030
+ letter-spacing: -0.01em;
66031
+ }
66032
+
66033
+ /* Attribution: name (bold) + context line, no avatar */
66034
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-meta {
66035
+ display: flex;
66036
+ flex-direction: column;
66037
+ gap: 0.25rem;
66038
+ margin-top: 1.625rem;
66039
+ }
66040
+
66041
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-name {
66042
+ font-weight: 600;
66043
+ font-size: 0.97rem;
66044
+ color: rgb(var(--tb-text));
66045
+ }
66046
+
66047
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-context {
66048
+ font-size: 0.78rem;
66049
+ letter-spacing: 0.02em;
66050
+ color: rgb(var(--tb-mutedText));
66051
+ }
66052
+
66053
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-name {
66054
+ color: rgb(var(--tb-onPrimary));
66055
+ }
66056
+
66057
+ :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-fe-card--feature .rb-testimonials-fe-context {
66058
+ color: rgb(var(--tb-onPrimary));
66059
+ opacity: 0.7;
66060
+ }
66061
+
66062
+
64315
66063
  /* Testimonials carousel tweaks: arrow styling + gutters + equal-height cards */
64316
66064
  :where(.theme-scope) :where([data-block="testimonials"]) .rb-testimonials-carousel.carousel-root .carousel-nav-btn {
64317
66065
  height: var(--rb-testimonial-carousel-arrow-size);
@@ -65458,7 +67206,7 @@ const containerResponsiveThemeCss = `/*
65458
67206
 
65459
67207
  :where(.theme-scope) :where([data-block="newsletter-signup"]) .newsletter-copy-subtitle {
65460
67208
  font-size: 1rem;
65461
- color: var(--section-text-color, rgb(var(--tb-text)));
67209
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
65462
67210
  opacity: 0.85;
65463
67211
  }
65464
67212
 
@@ -65507,7 +67255,7 @@ const containerResponsiveThemeCss = `/*
65507
67255
  margin-top: 0.75rem;
65508
67256
  font-size: 1rem;
65509
67257
  line-height: 1.6;
65510
- color: var(--section-text-color, rgb(var(--tb-text)));
67258
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
65511
67259
  text-align: center;
65512
67260
  }
65513
67261
 
@@ -65589,7 +67337,7 @@ const containerResponsiveThemeCss = `/*
65589
67337
 
65590
67338
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .form-copy-intro,
65591
67339
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .newsletter-copy-subtitle {
65592
- color: var(--section-text-color, rgb(var(--tb-text)));
67340
+ color: var(--rb-surface-text-color, rgb(var(--tb-text, 17 24 39)));
65593
67341
  }
65594
67342
 
65595
67343
  :where(.theme-scope) :where([data-block="contact-newsletter"]) .contact-newsletter-info {
@@ -65987,7 +67735,7 @@ const containerResponsiveThemeCss = `/*
65987
67735
 
65988
67736
  :where(.theme-scope) .event-compact-row-title {
65989
67737
  margin: 0;
65990
- color: var(--section-heading-color, rgb(var(--tb-primary, 0 0 0)));
67738
+ color: var(--rb-surface-heading-color, var(--rb-surface-text-color, rgb(var(--tb-primary, var(--tb-text, 17 24 39)))));
65991
67739
  font-family: var(--font-heading, var(--font-body, inherit));
65992
67740
  font-size: clamp(1.05rem, 1rem + 0.35vw, 1.3rem);
65993
67741
  font-weight: 400;
@@ -66649,27 +68397,41 @@ const containerResponsiveThemeCss = `/*
66649
68397
  background: rgb(var(--tb-surface));
66650
68398
  }
66651
68399
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 1) {
68400
+ background: rgb(var(--tb-surface));
66652
68401
  color: rgb(var(--tb-onSurface));
68402
+ --rb-surface-text-color: rgb(var(--tb-onSurface));
68403
+ --rb-surface-heading-color: rgb(var(--tb-onSurface));
68404
+ --rb-surface-link-color: rgb(var(--tb-onSurface));
68405
+ --rb-surface-supporting-color: rgb(var(--tb-onSurface));
66653
68406
  }
66654
68407
 
66655
68408
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 2) > .rb-bg-surface {
66656
68409
  background: rgb(var(--tb-secondary));
66657
68410
  }
66658
68411
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 2) {
68412
+ background: rgb(var(--tb-secondary));
66659
68413
  color: rgb(var(--tb-onSecondary));
68414
+ --rb-surface-text-color: rgb(var(--tb-onSecondary));
68415
+ --rb-surface-heading-color: rgb(var(--tb-onSecondary));
68416
+ --rb-surface-link-color: rgb(var(--tb-onSecondary));
68417
+ --rb-surface-supporting-color: rgb(var(--tb-onSecondary));
66660
68418
  }
66661
68419
 
66662
68420
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 3) > .rb-bg-surface {
66663
68421
  background: rgb(var(--tb-primary));
66664
68422
  }
66665
68423
  :where(.theme-scope) .rb-card-treatment-color-blocked:nth-child(3n + 3) {
68424
+ background: rgb(var(--tb-primary));
66666
68425
  color: rgb(var(--tb-onPrimary));
68426
+ --rb-surface-text-color: rgb(var(--tb-onPrimary));
68427
+ --rb-surface-heading-color: rgb(var(--tb-onPrimary));
68428
+ --rb-surface-link-color: rgb(var(--tb-onPrimary));
68429
+ --rb-surface-supporting-color: rgb(var(--tb-onPrimary));
66667
68430
  }
66668
68431
 
66669
- /* Body copy carries an inline muted colour from the block; force it to the
66670
- * card's on-colour so it reads on every block (inline style needs !important). */
68432
+ /* Keep the intended visual hierarchy; the body color itself now resolves via
68433
+ * the nearest surface provider instead of requiring an inline override. */
66671
68434
  :where(.theme-scope) .rb-card-treatment-color-blocked .rb-feature-card-body {
66672
- color: inherit !important;
66673
68435
  opacity: 0.9;
66674
68436
  }
66675
68437