@riverbankcms/sdk 0.104.0 → 0.104.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/{PreviewEditorSidebar-ywqcYjTB.mjs → PreviewEditorSidebar-CBMhU9p_.mjs} +3 -3
  2. package/dist/{PreviewEditorUI-DApUNF6g.mjs → PreviewEditorUI-C5KEw4M8.mjs} +2 -2
  3. package/dist/{SdkPreviewModeRuntime-DmHCcIMO.mjs → SdkPreviewModeRuntime-Iq12HjL7.mjs} +1147 -180
  4. package/dist/{SiteChromeCustomizeContext-BV9GU-3C.mjs → SiteChromeCustomizeContext-Cx0NMdyv.mjs} +1818 -951
  5. package/dist/_dts/ai/src/contracts/proposals.d.ts +10 -0
  6. package/dist/_dts/api/src/bookingVerticals.d.ts +64 -1
  7. package/dist/_dts/blocks/src/primitives/layout/Grid.d.ts +12 -8
  8. package/dist/_dts/blocks/src/system/blocks/events/event-calendar.d.ts +3 -0
  9. package/dist/_dts/blocks/src/system/blocks/events/shared/layout.d.ts +11 -1
  10. package/dist/_dts/blocks/src/system/blocks/testimonials.d.ts +7 -1
  11. package/dist/_dts/blocks/src/system/constants/events.d.ts +1 -1
  12. package/dist/_dts/blocks/src/system/fragments/library/testimonialsCarousel.d.ts +12 -0
  13. package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +2 -2
  14. package/dist/_dts/blocks/src/system/runtime/nodes/basic.d.ts +14 -0
  15. package/dist/_dts/blocks/src/system/runtime/nodes/buttonAffordance.d.ts +33 -0
  16. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/WeekBoardView.d.ts +28 -0
  17. package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/eventCapacity.d.ts +32 -0
  18. package/dist/_dts/blocks/src/system/runtime/nodes/shared/contract-schemas.d.ts +1 -0
  19. package/dist/_dts/blocks/src/system/runtime/nodes/svg.d.ts +77 -0
  20. package/dist/_dts/blocks/src/system/transforms/registry/_utils/surfaceForegroundStyles.d.ts +2 -0
  21. package/dist/_dts/blocks/src/system/transforms/registry/boxStyles.d.ts +3 -40
  22. package/dist/_dts/blocks/src/system/transforms/registry/formatting.d.ts +14 -0
  23. package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +11 -0
  24. package/dist/_dts/core/src/branded.d.ts +5 -0
  25. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +18 -0
  26. package/dist/_dts/editor/src/index.d.ts +3 -1
  27. package/dist/_dts/editor/src/primitives/rich-text/RichTextEditorSurface.d.ts +14 -6
  28. package/dist/_dts/editor/src/primitives/rich-text/RichTextToolbar.d.ts +23 -4
  29. package/dist/_dts/editor/src/primitives/rich-text/config.d.ts +6 -0
  30. package/dist/_dts/editor/src/primitives/rich-text/extensions/RichTextLink.d.ts +1 -0
  31. package/dist/_dts/editor/src/primitives/rich-text/paste.d.ts +10 -0
  32. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +2 -2
  33. package/dist/_dts/sdk/src/version.d.ts +1 -1
  34. package/dist/_dts/theme-core/src/ThemeScope.d.ts +1 -1
  35. package/dist/_dts/theme-core/src/buttons/affordanceIcons.d.ts +29 -0
  36. package/dist/_dts/theme-core/src/buttons/generateButtonCss.d.ts +6 -0
  37. package/dist/_dts/theme-core/src/buttons/index.d.ts +1 -0
  38. package/dist/_dts/theme-core/src/buttons/personalities/flat-ink.d.ts +20 -0
  39. package/dist/_dts/theme-core/src/buttons/personalities/index.d.ts +11 -0
  40. package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +4 -0
  41. package/dist/_dts/theme-core/src/buttons/types.d.ts +34 -0
  42. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  43. package/dist/_dts/theme-core/src/gradients/types.d.ts +27 -3
  44. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +51 -15
  45. package/dist/_dts/theme-core/src/palette/curated/brand-led.d.ts +13 -0
  46. package/dist/_dts/theme-core/src/palette/curated/index.d.ts +12 -0
  47. package/dist/_dts/theme-core/src/palette/historicalThemePalette.d.ts +44 -0
  48. package/dist/_dts/theme-core/src/palette/index.d.ts +1 -0
  49. package/dist/_dts/theme-core/src/palette/legacyRuntimeProjection.d.ts +23 -6
  50. package/dist/_dts/theme-core/src/palette/legacySelection.d.ts +13 -2
  51. package/dist/_dts/theme-core/src/palette/paletteCompatibilityTransition.d.ts +0 -9
  52. package/dist/_dts/theme-core/src/palette/persistenceReadSource.d.ts +17 -2
  53. package/dist/_dts/theme-core/src/palette/themePaletteCompatibility.d.ts +3 -4
  54. package/dist/_dts/theme-core/src/runtime/themeCssVarNames.d.ts +1 -0
  55. package/dist/_dts/theme-core/src/schema.d.ts +108 -26
  56. package/dist/_dts/theme-core/src/site-styles/accessibleSurfaces.d.ts +2 -1
  57. package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +50 -1
  58. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +2 -2
  59. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -2
  60. package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +56 -1
  61. package/dist/_dts/theme-core/src/site-styles/typographyPresets.d.ts +16 -1
  62. package/dist/_dts/theme-core/src/status/index.d.ts +0 -1
  63. package/dist/_dts/theme-core/src/surfaces/foregroundContract.d.ts +19 -0
  64. package/dist/_dts/theme-core/src/surfaces/foregroundRoles.d.ts +9 -0
  65. package/dist/_dts/theme-core/src/surfaces/index.d.ts +2 -0
  66. package/dist/_dts/ui/src/button.d.ts +1 -1
  67. package/dist/cli/index.mjs +1567 -470
  68. package/dist/client/{DiscountCodeDisclosure-Dit2aItR.mjs → DiscountCodeDisclosure-BXM73_E5.mjs} +1 -1
  69. package/dist/client/{EventCombined.client-BnREyCjf.mjs → EventCombined.client-DT5elP7j.mjs} +4 -4
  70. package/dist/client/{EventListing.client-0uHld51x.mjs → EventListing.client-pTYGfV9C.mjs} +2 -2
  71. package/dist/client/{LeafletStaticMap-DJcvSfmj.mjs → LeafletStaticMap-dQrhlUe3.mjs} +1 -1
  72. package/dist/client/{MagicLinkForm-EAsIktfw.mjs → MagicLinkForm-u6Dul4j5.mjs} +2 -2
  73. package/dist/client/{StepIndicator-CRR17tYI.mjs → StepIndicator-nWqineJ2.mjs} +2 -2
  74. package/dist/client/{accordion-B47HIuuH.mjs → accordion-CSJcTUyH.mjs} +1 -1
  75. package/dist/client/{bookingForm-I1E-8F9P.mjs → bookingForm-C73wOuVa.mjs} +6 -6
  76. package/dist/client/bookings.mjs +137 -1
  77. package/dist/client/{buttons-BbPn8OKA.mjs → buttons-DfYyqt8C.mjs} +138 -2
  78. package/dist/client/{carousel-OlM7SyBs.mjs → carousel-fMBb0JSc.mjs} +1 -1
  79. package/dist/client/{cart-BKpG5jz6.mjs → cart-BtaCwT3S.mjs} +3 -3
  80. package/dist/client/{checkout-Dmmr6qoU.mjs → checkout-DkTwZSnh.mjs} +3 -3
  81. package/dist/client/client.mjs +2336 -544
  82. package/dist/client/{colorStyles-DuW4Fjqd.mjs → colorStyles-Wyl6JujD.mjs} +45 -4
  83. package/dist/client/{courseRegistration-CflZiqGk.mjs → courseRegistration-DnLmapJu.mjs} +7 -7
  84. package/dist/client/{eventCalendar-BvoLWcp-.mjs → eventCalendar-BvLOc9KY.mjs} +165 -5
  85. package/dist/client/{eventCombined-DsrO8t_t.mjs → eventCombined-kShgJPQr.mjs} +2 -2
  86. package/dist/client/{eventDetails-C3hxgcJm.mjs → eventDetails-C5NTM0Vo.mjs} +3 -3
  87. package/dist/client/{eventListing-C-hb237X.mjs → eventListing-B82ZdKP6.mjs} +2 -2
  88. package/dist/client/{eventRegistration-B0gyzDGi.mjs → eventRegistration-qJVFqBWo.mjs} +7 -7
  89. package/dist/client/{form-C_RUi6oJ.mjs → form-BvAcKiuu.mjs} +2 -2
  90. package/dist/client/{headerSection-IKQiD2U0.mjs → headerSection-NvAiEHAg.mjs} +3 -3
  91. package/dist/client/hooks.mjs +1023 -337
  92. package/dist/client/{locationMap-8ZBrMzRJ.mjs → locationMap-D0j52xfI.mjs} +3 -3
  93. package/dist/client/{media-CZJKL7fi.mjs → media-BCW_g2cQ.mjs} +1 -1
  94. package/dist/client/{newsletterForm-CYk3ucwj.mjs → newsletterForm-B8_i93qH.mjs} +2 -2
  95. package/dist/client/{pricing-B5GmvfIH.mjs → pricing-DIYnyHOk.mjs} +1 -1
  96. package/dist/client/{productDetail-BjS2Dqzo.mjs → productDetail-w0eAHkuQ.mjs} +3 -3
  97. package/dist/client/{productList-BOHvC8My.mjs → productList-Vsguq5Xt.mjs} +3 -3
  98. package/dist/client/{propsDecoder-WS6UEJwZ.mjs → propsDecoder-CD2pJ1WB.mjs} +37 -2
  99. package/dist/client/rendering/client.mjs +1723 -762
  100. package/dist/client/rendering/islands.mjs +19 -19
  101. package/dist/client/rendering.mjs +2312 -520
  102. package/dist/client/{shop-CON03-wN.mjs → shop-Dgxs-Q8F.mjs} +6 -6
  103. package/dist/client/{shop-commerce-DlqrFgH8.mjs → shop-commerce-DJAhsTaT.mjs} +6 -6
  104. package/dist/client/{shop-commerce-store-l74-Kne6.mjs → shop-commerce-store-CfCXltXW.mjs} +1 -1
  105. package/dist/client/{shopShared-CQheEXSg.mjs → shopShared-CmDcPXUt.mjs} +2 -2
  106. package/dist/client/{useDiscountCodeUi-v6OluyWl.mjs → useDiscountCodeUi-CirrT2bM.mjs} +1 -1
  107. package/dist/client/{useEventFilters-BqSTcNOf.mjs → useEventFilters-CNZxlCcO.mjs} +5 -5
  108. package/dist/client/{usePolling-DMfADjIF.mjs → usePolling-Baks_iBI.mjs} +2 -2
  109. package/dist/preview-next/client/runtime.mjs +3 -3
  110. package/dist/{sdk-runtime-DWb08Xaz.mjs → sdk-runtime-BfNiFeNJ.mjs} +1 -1
  111. package/dist/{separator--6yjc914.mjs → separator-BhAR_Xmf.mjs} +1 -1
  112. package/dist/server/components.mjs +2301 -531
  113. package/dist/server/config-validation.mjs +1028 -337
  114. package/dist/server/config.mjs +1028 -337
  115. package/dist/server/data.mjs +1023 -337
  116. package/dist/server/index.mjs +1 -1
  117. package/dist/server/next.mjs +2304 -534
  118. package/dist/server/page-converter.mjs +1018 -324
  119. package/dist/server/prebuild.mjs +1 -1
  120. package/dist/server/rendering/server.mjs +2286 -521
  121. package/dist/server/rendering.mjs +2301 -531
  122. package/dist/server/routing.mjs +1100 -338
  123. package/dist/server/server.mjs +1024 -338
  124. package/dist/server/theme-bridge.mjs +1240 -77
  125. package/dist/server/theme.mjs +165 -5
  126. package/dist/styles/index.css +617 -34
  127. package/package.json +1 -1
  128. package/dist/_dts/blocks/src/system/transforms/registry/_utils/sectionForegroundStyles.d.ts +0 -2
  129. package/dist/_dts/theme-core/src/status/contextualColors.d.ts +0 -6
@@ -455,6 +455,234 @@ function featuresFromHex(hex) {
455
455
  };
456
456
  }
457
457
  //#endregion
458
+ //#region ../theme-core/src/tokens/colorTokenParts.ts
459
+ function parseOpacityPercent(source) {
460
+ const trimmed = source.trim();
461
+ if (!trimmed) return null;
462
+ if (!/^\d{1,3}(?:\.\d+)?$/.test(trimmed)) return null;
463
+ const value = Number(trimmed);
464
+ if (!Number.isFinite(value)) return null;
465
+ if (value < 0 || value > 100) return null;
466
+ return value;
467
+ }
468
+ function parseColorTokenParts(source) {
469
+ const trimmed = source.trim();
470
+ if (trimmed.startsWith("raw:")) return {
471
+ kind: "raw",
472
+ value: trimmed.slice(4)
473
+ };
474
+ const [token, opacityRaw, extra] = trimmed.split("/");
475
+ if (!token || extra !== void 0) return {
476
+ kind: "invalid",
477
+ token: trimmed
478
+ };
479
+ if (opacityRaw === void 0) return {
480
+ kind: "token",
481
+ token,
482
+ opacityPercent: null
483
+ };
484
+ const opacityPercent = parseOpacityPercent(opacityRaw);
485
+ if (opacityPercent === null) return {
486
+ kind: "invalid",
487
+ token: trimmed
488
+ };
489
+ return {
490
+ kind: "token",
491
+ token,
492
+ opacityPercent
493
+ };
494
+ }
495
+ //#endregion
496
+ //#region ../theme-core/src/tokens/blockColorTokens.ts
497
+ const blockColorTokenNames = [
498
+ "text",
499
+ "heading",
500
+ "mutedText",
501
+ "surface",
502
+ "background",
503
+ "foreground",
504
+ "border",
505
+ "primary",
506
+ "secondary",
507
+ "accent",
508
+ "success",
509
+ "warning",
510
+ "info",
511
+ "error",
512
+ "destructive",
513
+ "scrim",
514
+ "shadow",
515
+ "onPrimary",
516
+ "onSecondary",
517
+ "onAccent",
518
+ "onSuccess",
519
+ "onWarning",
520
+ "onInfo",
521
+ "onError",
522
+ "onDestructive",
523
+ "onSurface"
524
+ ];
525
+ const blockColorTokenNameSet = new Set(blockColorTokenNames);
526
+ function isBlockColorToken(value) {
527
+ if (typeof value !== "string") return false;
528
+ const parts = parseColorTokenParts(value);
529
+ return parts.kind === "token" && blockColorTokenNameSet.has(parts.token);
530
+ }
531
+ function parseBlockColorToken(value) {
532
+ if (!isBlockColorToken(value)) return null;
533
+ return value.trim();
534
+ }
535
+ //#endregion
536
+ //#region ../theme-core/src/tokens/themeColorTokens.ts
537
+ /**
538
+ * Known palette role token names. This is the low-level source of truth for
539
+ * palette roles used by palette variants, Theme V2 schemas, and runtime color
540
+ * reference parsing.
541
+ */
542
+ const PALETTE_TOKEN_NAMES = [
543
+ "primary",
544
+ "primaryForeground",
545
+ "secondary",
546
+ "accent",
547
+ "success",
548
+ "warning",
549
+ "destructive",
550
+ "background",
551
+ "surface",
552
+ "surfaceAlt",
553
+ "muted",
554
+ "text",
555
+ "border"
556
+ ];
557
+ const paletteTokenNameSchema = z$1.enum(PALETTE_TOKEN_NAMES);
558
+ const themeColorBaseTokenNames = [
559
+ ...PALETTE_TOKEN_NAMES,
560
+ ...blockColorTokenNames,
561
+ "white",
562
+ "neutral",
563
+ "primaryText",
564
+ "secondaryForeground",
565
+ "accentForeground"
566
+ ];
567
+ const themeColorBaseTokenSet = new Set(themeColorBaseTokenNames);
568
+ const themeColorShadeSteps = [
569
+ "50",
570
+ "100",
571
+ "200",
572
+ "300",
573
+ "400",
574
+ "500",
575
+ "600",
576
+ "700",
577
+ "800",
578
+ "900",
579
+ "950"
580
+ ];
581
+ const shadeStepSet = new Set(themeColorShadeSteps);
582
+ function isKnownBaseToken(value) {
583
+ return themeColorBaseTokenSet.has(value);
584
+ }
585
+ function isKnownShadeToken(value) {
586
+ const match = value.match(/^([a-zA-Z][a-zA-Z0-9]*)(?:-([0-9]{2,3}))$/);
587
+ if (!match) return false;
588
+ const [, base, shade] = match;
589
+ return Boolean(base && shade && isKnownBaseToken(base) && shadeStepSet.has(shade));
590
+ }
591
+ function isThemeColorTokenBase(value) {
592
+ return isKnownBaseToken(value) || isKnownShadeToken(value);
593
+ }
594
+ function isThemeColorToken(value) {
595
+ if (typeof value !== "string") return false;
596
+ const parts = parseColorTokenParts(value);
597
+ return parts.kind === "token" && isThemeColorTokenBase(parts.token);
598
+ }
599
+ function parseThemeColorToken(value) {
600
+ if (!isThemeColorToken(value)) return null;
601
+ return value.trim();
602
+ }
603
+ const themeColorTokenSchema = z$1.string().min(1).refine((value) => Boolean(isThemeColorToken(value)), "Expected a known Theme V2 color token");
604
+ //#endregion
605
+ //#region ../theme-core/src/palette/colors.ts
606
+ const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/;
607
+ const NORMALIZABLE_HEX_REGEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
608
+ const hexColorSchema = z$1.string().regex(HEX_COLOR_REGEX, "Hex must be #RRGGBB (lowercase)").transform((value) => value);
609
+ const paletteModeSchema = z$1.enum(["light", "dark"]);
610
+ z$1.string().regex(/^[0-9a-f]{64}$/, "Palette hash must be a lowercase SHA-256 value").transform((value) => value);
611
+ const paletteColorsSchema = z$1.object({
612
+ primary: hexColorSchema,
613
+ primaryForeground: hexColorSchema,
614
+ secondary: hexColorSchema,
615
+ accent: hexColorSchema,
616
+ success: hexColorSchema,
617
+ warning: hexColorSchema,
618
+ destructive: hexColorSchema,
619
+ background: hexColorSchema,
620
+ surface: hexColorSchema,
621
+ surfaceAlt: hexColorSchema,
622
+ muted: hexColorSchema,
623
+ text: hexColorSchema,
624
+ border: hexColorSchema
625
+ }).strict().transform((value) => value);
626
+ function normalizePaletteHex(value) {
627
+ if (!NORMALIZABLE_HEX_REGEX.test(value)) return null;
628
+ const normalized = value.toLowerCase();
629
+ if (normalized.length === 7) return normalized;
630
+ const triplet = normalized.slice(1);
631
+ const red = triplet.charAt(0);
632
+ const green = triplet.charAt(1);
633
+ const blue = triplet.charAt(2);
634
+ return `#${red}${red}${green}${green}${blue}${blue}`;
635
+ }
636
+ function paletteColorsFromEntries(entries) {
637
+ const values = {};
638
+ for (const entry of entries) {
639
+ const parsedRole = paletteTokenNameSchema.safeParse(entry.name);
640
+ if (!parsedRole.success) return {
641
+ ok: false,
642
+ error: {
643
+ kind: "unknown-role",
644
+ role: entry.name
645
+ }
646
+ };
647
+ const role = parsedRole.data;
648
+ if (values[role] !== void 0) return {
649
+ ok: false,
650
+ error: {
651
+ kind: "duplicate-role",
652
+ role
653
+ }
654
+ };
655
+ const hex = normalizePaletteHex(entry.hex);
656
+ if (!hex) return {
657
+ ok: false,
658
+ error: {
659
+ kind: "invalid-color",
660
+ role,
661
+ value: entry.hex
662
+ }
663
+ };
664
+ values[role] = hex;
665
+ }
666
+ const missing = PALETTE_TOKEN_NAMES.filter((role) => values[role] === void 0);
667
+ if (missing.length > 0) return {
668
+ ok: false,
669
+ error: {
670
+ kind: "missing-roles",
671
+ roles: missing
672
+ }
673
+ };
674
+ return {
675
+ ok: true,
676
+ colors: paletteColorsSchema.parse(values)
677
+ };
678
+ }
679
+ function requirePaletteColorsFromEntries(entries) {
680
+ const result = paletteColorsFromEntries(entries);
681
+ if (result.ok) return result.colors;
682
+ throw new Error(`Invalid complete palette: ${result.error.kind}`);
683
+ }
684
+ new Set(PALETTE_TOKEN_NAMES);
685
+ //#endregion
458
686
  //#region ../../node_modules/.pnpm/@vibrant+image@4.0.4/node_modules/@vibrant/image/dist/esm/histogram.js
459
687
  var Histogram = class {
460
688
  constructor(pixels, opts) {
@@ -1489,233 +1717,6 @@ const pipeline = new BasicPipeline().filter.register("default", (r, g, b, a) =>
1489
1717
  //#region ../../node_modules/.pnpm/node-vibrant@4.0.4/node_modules/node-vibrant/dist/esm/browser.js
1490
1718
  Vibrant.use(pipeline);
1491
1719
  //#endregion
1492
- //#region ../theme-core/src/tokens/colorTokenParts.ts
1493
- function parseOpacityPercent(source) {
1494
- const trimmed = source.trim();
1495
- if (!trimmed) return null;
1496
- if (!/^\d{1,3}(?:\.\d+)?$/.test(trimmed)) return null;
1497
- const value = Number(trimmed);
1498
- if (!Number.isFinite(value)) return null;
1499
- if (value < 0 || value > 100) return null;
1500
- return value;
1501
- }
1502
- function parseColorTokenParts(source) {
1503
- const trimmed = source.trim();
1504
- if (trimmed.startsWith("raw:")) return {
1505
- kind: "raw",
1506
- value: trimmed.slice(4)
1507
- };
1508
- const [token, opacityRaw, extra] = trimmed.split("/");
1509
- if (!token || extra !== void 0) return {
1510
- kind: "invalid",
1511
- token: trimmed
1512
- };
1513
- if (opacityRaw === void 0) return {
1514
- kind: "token",
1515
- token,
1516
- opacityPercent: null
1517
- };
1518
- const opacityPercent = parseOpacityPercent(opacityRaw);
1519
- if (opacityPercent === null) return {
1520
- kind: "invalid",
1521
- token: trimmed
1522
- };
1523
- return {
1524
- kind: "token",
1525
- token,
1526
- opacityPercent
1527
- };
1528
- }
1529
- //#endregion
1530
- //#region ../theme-core/src/tokens/blockColorTokens.ts
1531
- const blockColorTokenNames = [
1532
- "text",
1533
- "heading",
1534
- "mutedText",
1535
- "surface",
1536
- "background",
1537
- "foreground",
1538
- "border",
1539
- "primary",
1540
- "secondary",
1541
- "accent",
1542
- "success",
1543
- "warning",
1544
- "info",
1545
- "error",
1546
- "destructive",
1547
- "scrim",
1548
- "shadow",
1549
- "onPrimary",
1550
- "onSecondary",
1551
- "onAccent",
1552
- "onSuccess",
1553
- "onWarning",
1554
- "onInfo",
1555
- "onError",
1556
- "onDestructive",
1557
- "onSurface"
1558
- ];
1559
- const blockColorTokenNameSet = new Set(blockColorTokenNames);
1560
- function isBlockColorToken(value) {
1561
- if (typeof value !== "string") return false;
1562
- const parts = parseColorTokenParts(value);
1563
- return parts.kind === "token" && blockColorTokenNameSet.has(parts.token);
1564
- }
1565
- function parseBlockColorToken(value) {
1566
- if (!isBlockColorToken(value)) return null;
1567
- return value.trim();
1568
- }
1569
- //#endregion
1570
- //#region ../theme-core/src/tokens/themeColorTokens.ts
1571
- /**
1572
- * Known palette role token names. This is the low-level source of truth for
1573
- * palette roles used by palette variants, Theme V2 schemas, and runtime color
1574
- * reference parsing.
1575
- */
1576
- const PALETTE_TOKEN_NAMES = [
1577
- "primary",
1578
- "primaryForeground",
1579
- "secondary",
1580
- "accent",
1581
- "success",
1582
- "warning",
1583
- "destructive",
1584
- "background",
1585
- "surface",
1586
- "surfaceAlt",
1587
- "muted",
1588
- "text",
1589
- "border"
1590
- ];
1591
- const paletteTokenNameSchema = z$1.enum(PALETTE_TOKEN_NAMES);
1592
- const themeColorBaseTokenNames = [
1593
- ...PALETTE_TOKEN_NAMES,
1594
- ...blockColorTokenNames,
1595
- "white",
1596
- "neutral",
1597
- "primaryText",
1598
- "secondaryForeground",
1599
- "accentForeground"
1600
- ];
1601
- const themeColorBaseTokenSet = new Set(themeColorBaseTokenNames);
1602
- const themeColorShadeSteps = [
1603
- "50",
1604
- "100",
1605
- "200",
1606
- "300",
1607
- "400",
1608
- "500",
1609
- "600",
1610
- "700",
1611
- "800",
1612
- "900",
1613
- "950"
1614
- ];
1615
- const shadeStepSet = new Set(themeColorShadeSteps);
1616
- function isKnownBaseToken(value) {
1617
- return themeColorBaseTokenSet.has(value);
1618
- }
1619
- function isKnownShadeToken(value) {
1620
- const match = value.match(/^([a-zA-Z][a-zA-Z0-9]*)(?:-([0-9]{2,3}))$/);
1621
- if (!match) return false;
1622
- const [, base, shade] = match;
1623
- return Boolean(base && shade && isKnownBaseToken(base) && shadeStepSet.has(shade));
1624
- }
1625
- function isThemeColorTokenBase(value) {
1626
- return isKnownBaseToken(value) || isKnownShadeToken(value);
1627
- }
1628
- function isThemeColorToken(value) {
1629
- if (typeof value !== "string") return false;
1630
- const parts = parseColorTokenParts(value);
1631
- return parts.kind === "token" && isThemeColorTokenBase(parts.token);
1632
- }
1633
- function parseThemeColorToken(value) {
1634
- if (!isThemeColorToken(value)) return null;
1635
- return value.trim();
1636
- }
1637
- const themeColorTokenSchema = z$1.string().min(1).refine((value) => Boolean(isThemeColorToken(value)), "Expected a known Theme V2 color token");
1638
- //#endregion
1639
- //#region ../theme-core/src/palette/colors.ts
1640
- const HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/;
1641
- const NORMALIZABLE_HEX_REGEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
1642
- const hexColorSchema = z$1.string().regex(HEX_COLOR_REGEX, "Hex must be #RRGGBB (lowercase)").transform((value) => value);
1643
- const paletteModeSchema = z$1.enum(["light", "dark"]);
1644
- z$1.string().regex(/^[0-9a-f]{64}$/, "Palette hash must be a lowercase SHA-256 value").transform((value) => value);
1645
- const paletteColorsSchema = z$1.object({
1646
- primary: hexColorSchema,
1647
- primaryForeground: hexColorSchema,
1648
- secondary: hexColorSchema,
1649
- accent: hexColorSchema,
1650
- success: hexColorSchema,
1651
- warning: hexColorSchema,
1652
- destructive: hexColorSchema,
1653
- background: hexColorSchema,
1654
- surface: hexColorSchema,
1655
- surfaceAlt: hexColorSchema,
1656
- muted: hexColorSchema,
1657
- text: hexColorSchema,
1658
- border: hexColorSchema
1659
- }).strict().transform((value) => value);
1660
- function normalizePaletteHex(value) {
1661
- if (!NORMALIZABLE_HEX_REGEX.test(value)) return null;
1662
- const normalized = value.toLowerCase();
1663
- if (normalized.length === 7) return normalized;
1664
- const triplet = normalized.slice(1);
1665
- const red = triplet.charAt(0);
1666
- const green = triplet.charAt(1);
1667
- const blue = triplet.charAt(2);
1668
- return `#${red}${red}${green}${green}${blue}${blue}`;
1669
- }
1670
- function paletteColorsFromEntries(entries) {
1671
- const values = {};
1672
- for (const entry of entries) {
1673
- const parsedRole = paletteTokenNameSchema.safeParse(entry.name);
1674
- if (!parsedRole.success) return {
1675
- ok: false,
1676
- error: {
1677
- kind: "unknown-role",
1678
- role: entry.name
1679
- }
1680
- };
1681
- const role = parsedRole.data;
1682
- if (values[role] !== void 0) return {
1683
- ok: false,
1684
- error: {
1685
- kind: "duplicate-role",
1686
- role
1687
- }
1688
- };
1689
- const hex = normalizePaletteHex(entry.hex);
1690
- if (!hex) return {
1691
- ok: false,
1692
- error: {
1693
- kind: "invalid-color",
1694
- role,
1695
- value: entry.hex
1696
- }
1697
- };
1698
- values[role] = hex;
1699
- }
1700
- const missing = PALETTE_TOKEN_NAMES.filter((role) => values[role] === void 0);
1701
- if (missing.length > 0) return {
1702
- ok: false,
1703
- error: {
1704
- kind: "missing-roles",
1705
- roles: missing
1706
- }
1707
- };
1708
- return {
1709
- ok: true,
1710
- colors: paletteColorsSchema.parse(values)
1711
- };
1712
- }
1713
- function requirePaletteColorsFromEntries(entries) {
1714
- const result = paletteColorsFromEntries(entries);
1715
- if (result.ok) return result.colors;
1716
- throw new Error(`Invalid complete palette: ${result.error.kind}`);
1717
- }
1718
- //#endregion
1719
1720
  //#region ../theme-core/src/site-styles/styleTags.ts
1720
1721
  /**
1721
1722
  * Descriptive style-axis metadata for button personalities and palette
@@ -1989,6 +1990,71 @@ const brandLedJewel = defineCuratedPalette({
1989
1990
  }
1990
1991
  ]
1991
1992
  });
1993
+ const brandLedSpruce = defineCuratedPalette({
1994
+ id: "brand-led-spruce",
1995
+ name: "Brand-led — Spruce",
1996
+ description: "Spruce-ink primary and moss secondary with an amber accent over a green-mist ground and hairline sage borders.",
1997
+ tags: {
1998
+ tone: "classic",
1999
+ energy: "calm",
2000
+ era: "editorial"
2001
+ },
2002
+ mode: "light",
2003
+ colors: [
2004
+ {
2005
+ name: "primary",
2006
+ hex: "#14312b"
2007
+ },
2008
+ {
2009
+ name: "secondary",
2010
+ hex: "#356a59"
2011
+ },
2012
+ {
2013
+ name: "accent",
2014
+ hex: "#f08a4b"
2015
+ },
2016
+ {
2017
+ name: "success",
2018
+ hex: "#356a59"
2019
+ },
2020
+ {
2021
+ name: "warning",
2022
+ hex: "#f08a4b"
2023
+ },
2024
+ {
2025
+ name: "destructive",
2026
+ hex: "#b5561f"
2027
+ },
2028
+ {
2029
+ name: "background",
2030
+ hex: "#ecefe9"
2031
+ },
2032
+ {
2033
+ name: "surface",
2034
+ hex: "#ffffff"
2035
+ },
2036
+ {
2037
+ name: "surfaceAlt",
2038
+ hex: "#f4f6f1"
2039
+ },
2040
+ {
2041
+ name: "muted",
2042
+ hex: "#5c6a64"
2043
+ },
2044
+ {
2045
+ name: "text",
2046
+ hex: "#1c2a26"
2047
+ },
2048
+ {
2049
+ name: "primaryForeground",
2050
+ hex: "#f4f7f1"
2051
+ },
2052
+ {
2053
+ name: "border",
2054
+ hex: "#d7ddd2"
2055
+ }
2056
+ ]
2057
+ });
1992
2058
  const brandLedBurgundyGold = defineCuratedPalette({
1993
2059
  id: "brand-led-burgundy-gold",
1994
2060
  name: "Brand-led — Burgundy Gold",
@@ -2528,6 +2594,7 @@ const curatedPalettes = [
2528
2594
  brandLedWarm,
2529
2595
  brandLedJewel,
2530
2596
  brandLedBurgundyGold,
2597
+ brandLedSpruce,
2531
2598
  defineCuratedPalette({
2532
2599
  id: "warm-neutral-clay",
2533
2600
  name: "Warm Neutral — Clay",
@@ -2992,6 +3059,55 @@ z$1.discriminatedUnion("kind", [
2992
3059
  })
2993
3060
  ]);
2994
3061
  //#endregion
3062
+ //#region ../theme-core/src/surfaces/foregroundContract.ts
3063
+ const SURFACE_FOREGROUND_CSS_VARS = {
3064
+ text: "--rb-surface-text-color",
3065
+ heading: "--rb-surface-heading-color",
3066
+ link: "--rb-surface-link-color",
3067
+ supporting: "--rb-surface-supporting-color"
3068
+ };
3069
+ const DEFAULT_SURFACE_TEXT_COLOR = "rgb(var(--tb-text, 17 24 39))";
3070
+ const DEFAULT_ROLE_FALLBACKS = {
3071
+ text: DEFAULT_SURFACE_TEXT_COLOR,
3072
+ heading: DEFAULT_SURFACE_TEXT_COLOR,
3073
+ link: "rgb(var(--tb-primary, var(--tb-text, 17 24 39)))",
3074
+ supporting: "rgb(var(--tb-mutedText, 100 116 139))"
3075
+ };
3076
+ function requireSurfaceColorValue(role, value) {
3077
+ const normalized = value.trim();
3078
+ if (normalized.length === 0) throw new RangeError(`Surface foreground ${role} must be a non-empty CSS color.`);
3079
+ return normalized;
3080
+ }
3081
+ function completeSurfaceForegrounds(input) {
3082
+ const text = requireSurfaceColorValue("text", input.text);
3083
+ return {
3084
+ text,
3085
+ heading: input.heading === void 0 ? text : requireSurfaceColorValue("heading", input.heading),
3086
+ link: input.link === void 0 ? text : requireSurfaceColorValue("link", input.link),
3087
+ supporting: input.supporting === void 0 ? text : requireSurfaceColorValue("supporting", input.supporting)
3088
+ };
3089
+ }
3090
+ function surfaceForegroundValue(role, outsideProviderFallback = DEFAULT_ROLE_FALLBACKS[role]) {
3091
+ const roleVariable = SURFACE_FOREGROUND_CSS_VARS[role];
3092
+ const fallback = requireSurfaceColorValue("fallback", outsideProviderFallback);
3093
+ if (role === "text") return `var(${roleVariable}, ${fallback})`;
3094
+ return `var(${roleVariable}, var(${SURFACE_FOREGROUND_CSS_VARS.text}, ${fallback}))`;
3095
+ }
3096
+ function surfaceForegroundValueWithOpacity(role, opacityPercent) {
3097
+ if (!Number.isFinite(opacityPercent) || opacityPercent < 0 || opacityPercent > 100) throw new RangeError("Surface foreground opacity must be between 0 and 100.");
3098
+ return `color-mix(in srgb, ${surfaceForegroundValue(role)} ${opacityPercent}%, transparent)`;
3099
+ }
3100
+ function surfaceForegroundStyle(input) {
3101
+ const foregrounds = completeSurfaceForegrounds(input);
3102
+ return {
3103
+ color: foregrounds.text,
3104
+ [SURFACE_FOREGROUND_CSS_VARS.text]: foregrounds.text,
3105
+ [SURFACE_FOREGROUND_CSS_VARS.heading]: foregrounds.heading,
3106
+ [SURFACE_FOREGROUND_CSS_VARS.link]: foregrounds.link,
3107
+ [SURFACE_FOREGROUND_CSS_VARS.supporting]: foregrounds.supporting
3108
+ };
3109
+ }
3110
+ //#endregion
2995
3111
  //#region ../theme-core/src/utils/colorStyles.ts
2996
3112
  const COLOR_VAR_PREFIX = "--tb-";
2997
3113
  function parseToken(source) {
@@ -3003,9 +3119,17 @@ function parseToken(source) {
3003
3119
  if (parts.kind === "invalid") return { token: source };
3004
3120
  return {
3005
3121
  token: parts.token,
3006
- alpha: parts.opacityPercent === null ? void 0 : parts.opacityPercent / 100
3122
+ opacityPercent: parts.opacityPercent === null ? void 0 : parts.opacityPercent
3007
3123
  };
3008
3124
  }
3125
+ const SURFACE_TEXT_ROLE_BY_TOKEN = {
3126
+ text: "text",
3127
+ heading: "heading",
3128
+ mutedText: "supporting"
3129
+ };
3130
+ function surfaceRoleForTextToken(token) {
3131
+ return Object.prototype.hasOwnProperty.call(SURFACE_TEXT_ROLE_BY_TOKEN, token) ? SURFACE_TEXT_ROLE_BY_TOKEN[token] : null;
3132
+ }
3009
3133
  /**
3010
3134
  * Converts a theme color token to a CSS rgb value.
3011
3135
  * This is the primary function for applying theme colors to CSS properties.
@@ -3019,11 +3143,11 @@ function parseToken(source) {
3019
3143
  * rgbColorValue('raw:transparent') // => 'transparent'
3020
3144
  */
3021
3145
  function rgbColorValue(token) {
3022
- const { token: baseToken, alpha, raw } = parseToken(token);
3146
+ const { token: baseToken, opacityPercent, raw } = parseToken(token);
3023
3147
  if (raw) return raw;
3024
3148
  const cssVar = `${COLOR_VAR_PREFIX}${baseToken}`;
3025
- if (alpha === void 0) return `rgb(var(${cssVar}))`;
3026
- return `rgb(var(${cssVar}) / ${alpha})`;
3149
+ if (opacityPercent === void 0) return `rgb(var(${cssVar}))`;
3150
+ return `rgb(var(${cssVar}) / ${opacityPercent / 100})`;
3027
3151
  }
3028
3152
  /**
3029
3153
  * Creates a backgroundColor style object from a theme token.
@@ -3041,6 +3165,9 @@ function backgroundColorStyle(token) {
3041
3165
  * @returns CSSProperties with color
3042
3166
  */
3043
3167
  function textColorStyle(token) {
3168
+ const parsed = parseToken(token);
3169
+ const surfaceRole = parsed.raw ? null : surfaceRoleForTextToken(parsed.token);
3170
+ if (surfaceRole) return { color: parsed.opacityPercent === void 0 ? surfaceForegroundValue(surfaceRole) : surfaceForegroundValueWithOpacity(surfaceRole, parsed.opacityPercent) };
3044
3171
  return { color: rgbColorValue(token) };
3045
3172
  }
3046
3173
  /**
@@ -3310,6 +3437,10 @@ const buttonBorderSchema = z$1.object({
3310
3437
  widthClass: z$1.string(),
3311
3438
  colorToken: themeColorTokenSchema
3312
3439
  });
3440
+ const buttonVariantAffordanceSchema = z$1.object({
3441
+ iconId: z$1.string().min(1),
3442
+ colorToken: themeColorTokenSchema.optional()
3443
+ });
3313
3444
  const variantShadowSchema = z$1.object({
3314
3445
  elevation: z$1.enum([
3315
3446
  "none",
@@ -3363,7 +3494,8 @@ const buttonVariantSchema = z$1.object({
3363
3494
  border: buttonBorderSchema.optional(),
3364
3495
  hoverBackgroundToken: z$1.string().optional(),
3365
3496
  effects: variantEffectsSchema.optional(),
3366
- sizes: variantSizeOverridesSchema.optional()
3497
+ sizes: variantSizeOverridesSchema.optional(),
3498
+ affordance: buttonVariantAffordanceSchema.optional()
3367
3499
  });
3368
3500
  const buttonSystemSchema = z$1.object({
3369
3501
  global: buttonGlobalSettingsSchema,
@@ -3908,6 +4040,136 @@ const editorialLink = defineButtonPersonality({
3908
4040
  }
3909
4041
  });
3910
4042
  //#endregion
4043
+ //#region ../theme-core/src/buttons/personalities/flat-ink.ts
4044
+ /**
4045
+ * Flat Ink — a solid ink primary, a hairline ghost secondary, and a single
4046
+ * amber accent climax, all completely flat (no drop shadow) with a modest
4047
+ * radius. Reads as grounded, editorial and clinician-precise: the Fulcrum
4048
+ * physiotherapy voice. The primary shifts to the secondary (moss) on hover and
4049
+ * every variant lifts 2px; the amber accent is meant to be used sparingly as
4050
+ * the page's one conversion climax.
4051
+ */
4052
+ const flatInk = defineButtonPersonality({
4053
+ id: "flat-ink",
4054
+ name: "Flat Ink",
4055
+ description: "Flat solid-ink primary, hairline ghost secondary, and a single amber accent climax — modest radius, no shadow.",
4056
+ tags: {
4057
+ tone: "classic",
4058
+ energy: "calm",
4059
+ motion: "subtle",
4060
+ era: "editorial"
4061
+ },
4062
+ buttonSystem: {
4063
+ global: {
4064
+ cornerStyle: "rounded",
4065
+ shadow: "low",
4066
+ textTransform: "none",
4067
+ fontWeight: 600,
4068
+ borderWidth: 1,
4069
+ typography: "body",
4070
+ letterSpacing: "tight",
4071
+ paddingPreset: "default",
4072
+ hoverTransform: "lift",
4073
+ hoverColor: "none"
4074
+ },
4075
+ sizes: {
4076
+ sm: {
4077
+ padding: "0.625rem 1.125rem",
4078
+ fontSize: "text-sm"
4079
+ },
4080
+ md: { padding: "0.8125rem 1.375rem" },
4081
+ lg: {
4082
+ padding: "0.9375rem 1.625rem",
4083
+ fontSize: "text-lg"
4084
+ },
4085
+ xl: {
4086
+ padding: "1.0625rem 2rem",
4087
+ fontSize: "text-xl"
4088
+ }
4089
+ },
4090
+ variants: [
4091
+ {
4092
+ id: "primary",
4093
+ name: "Primary",
4094
+ enabled: true,
4095
+ priority: 1,
4096
+ background: {
4097
+ type: "solid",
4098
+ colorToken: "primary"
4099
+ },
4100
+ textColorToken: "primaryForeground",
4101
+ borderRadius: "rounded-lg",
4102
+ affordance: {
4103
+ iconId: "ArrowRight",
4104
+ colorToken: "accent"
4105
+ },
4106
+ shadow: {
4107
+ elevation: "none",
4108
+ softness: null,
4109
+ position: "bottom"
4110
+ },
4111
+ effects: { hover: [{
4112
+ effectId: "lift",
4113
+ options: { translateY: "-2px" }
4114
+ }, {
4115
+ effectId: "background-on-hover",
4116
+ options: { colorToken: "secondary" }
4117
+ }] }
4118
+ },
4119
+ {
4120
+ id: "secondary",
4121
+ name: "Secondary",
4122
+ enabled: true,
4123
+ priority: 2,
4124
+ background: { type: "transparent" },
4125
+ textColorToken: "primary",
4126
+ borderRadius: "rounded-lg",
4127
+ border: {
4128
+ widthClass: "border",
4129
+ colorToken: "border"
4130
+ },
4131
+ shadow: {
4132
+ elevation: "none",
4133
+ softness: null,
4134
+ position: "bottom"
4135
+ },
4136
+ effects: { hover: [{
4137
+ effectId: "lift",
4138
+ options: { translateY: "-2px" }
4139
+ }, {
4140
+ effectId: "background-on-hover",
4141
+ options: { colorToken: "surfaceAlt" }
4142
+ }] }
4143
+ },
4144
+ {
4145
+ id: "accent",
4146
+ name: "Accent",
4147
+ enabled: true,
4148
+ priority: 3,
4149
+ background: {
4150
+ type: "solid",
4151
+ colorToken: "accent"
4152
+ },
4153
+ textColorToken: "accentForeground",
4154
+ borderRadius: "rounded-lg",
4155
+ shadow: {
4156
+ elevation: "none",
4157
+ softness: null,
4158
+ position: "bottom"
4159
+ },
4160
+ affordance: {
4161
+ iconId: "ArrowRight",
4162
+ colorToken: "accentForeground"
4163
+ },
4164
+ effects: { hover: [{
4165
+ effectId: "lift",
4166
+ options: { translateY: "-2px" }
4167
+ }] }
4168
+ }
4169
+ ]
4170
+ }
4171
+ });
4172
+ //#endregion
3911
4173
  //#region ../theme-core/src/buttons/personalities/ink-stamp.ts
3912
4174
  /**
3913
4175
  * Ink Stamp — solid earthy fills with a bordered, hand-stamped block-print
@@ -4678,6 +4940,7 @@ const buttonPersonalities = [
4678
4940
  editorialLink,
4679
4941
  pebble,
4680
4942
  inkStamp,
4943
+ flatInk,
4681
4944
  brushedWash,
4682
4945
  showtimePill,
4683
4946
  joyStamp,
@@ -5035,11 +5298,31 @@ const gradientStopSchema = z$1.object({
5035
5298
  position: z$1.number().min(0).max(100)
5036
5299
  });
5037
5300
  /**
5038
- * Gradient configuration
5301
+ * Radial gradient shape. Optional; defaults to `circle` (the historical shape)
5302
+ * so existing radial gradients are byte-identical.
5303
+ *
5304
+ * Named `RadialGradientShape` to avoid clashing with `styles/gradients.ts`'s
5305
+ * `GradientShape` (which names the gradient *kind*: linear/radial/conic).
5306
+ */
5307
+ const radialGradientShapeSchema = z$1.enum(["circle", "ellipse"]);
5308
+ /**
5309
+ * Gradient configuration.
5310
+ *
5311
+ * `type` is optional and defaults to `'linear'` so gradients persisted without a
5312
+ * type (or with an explicit `'linear'`) render exactly as before. `'radial'` and
5313
+ * `'conic'` are additive kinds:
5314
+ * - linear: `angle` (degrees)
5315
+ * - radial: optional `shape` (circle/ellipse) + `position`
5316
+ * - conic: optional `angle` (from-angle, degrees) + `position`
5039
5317
  */
5040
5318
  const gradientConfigSchema = z$1.object({
5041
- type: z$1.enum(["linear", "radial"]),
5319
+ type: z$1.enum([
5320
+ "linear",
5321
+ "radial",
5322
+ "conic"
5323
+ ]).default("linear"),
5042
5324
  angle: z$1.number().min(0).max(360).optional(),
5325
+ shape: radialGradientShapeSchema.optional(),
5043
5326
  position: z$1.string().optional(),
5044
5327
  stops: z$1.array(gradientStopSchema).min(2)
5045
5328
  });
@@ -6021,6 +6304,7 @@ const typographyScaleSchema = z$1.enum([
6021
6304
  "majorThird",
6022
6305
  "perfectFourth"
6023
6306
  ]);
6307
+ const eyebrowTypographyStyleSchema = z$1.object({ family: z$1.string().min(1) });
6024
6308
  const typographySchema = z$1.object({
6025
6309
  body: bodyTypographyStyleSchema,
6026
6310
  headings: z$1.object({
@@ -6033,7 +6317,8 @@ const typographySchema = z$1.object({
6033
6317
  h6: headingTypographyOverridesSchema.optional()
6034
6318
  }),
6035
6319
  scale: typographyScaleSchema,
6036
- proseLinkStyle: proseLinkStyleSchema.optional()
6320
+ proseLinkStyle: proseLinkStyleSchema.optional(),
6321
+ eyebrow: eyebrowTypographyStyleSchema.optional()
6037
6322
  });
6038
6323
  const spaceSchema = z$1.enum([
6039
6324
  "comfortable",
@@ -6058,7 +6343,8 @@ const shadow = z$1.object({
6058
6343
  "soft",
6059
6344
  "hard"
6060
6345
  ]).nullable(),
6061
- position: z$1.enum(["bottom", "bottom-right"]).default("bottom")
6346
+ position: z$1.enum(["bottom", "bottom-right"]).default("bottom"),
6347
+ colorToken: themeColorTokenSchema.optional()
6062
6348
  });
6063
6349
  const border = z$1.object({
6064
6350
  width: z$1.enum([
@@ -6303,6 +6589,7 @@ const headerSchema = z$1.object({
6303
6589
  navWeight: navFontWeight.default("medium"),
6304
6590
  navTypography: z$1.enum(["body", "heading"]).optional(),
6305
6591
  navTextSize: navLinkStyleSchema.shape.textSize,
6592
+ navPlacement: z$1.enum(["end", "center"]).optional(),
6306
6593
  container: headerContainerSchema,
6307
6594
  border: headerBorderSchema,
6308
6595
  shadow: componentShadowSchema.optional(),
@@ -8406,6 +8693,13 @@ const legacyHeaderLookAliases = defineExactCatalog()([
8406
8693
  styleId: asHeaderStyleId("minimal")
8407
8694
  }
8408
8695
  },
8696
+ {
8697
+ lookId: asHeaderLookId("classic-centered-nav"),
8698
+ selection: {
8699
+ layoutId: asHeaderLayoutId("classic"),
8700
+ styleId: asHeaderStyleId("fine-line")
8701
+ }
8702
+ },
8409
8703
  {
8410
8704
  lookId: asHeaderLookId("brand-solid"),
8411
8705
  selection: {
@@ -8476,6 +8770,50 @@ function getCompatibleHeaderStyleIds(layoutId) {
8476
8770
  return isHeaderStyleCompatibleWithLayout(getHeaderStyleOrThrow(styleId), layoutId);
8477
8771
  });
8478
8772
  }
8773
+ function applyNavTypography(style, typography) {
8774
+ switch (typography) {
8775
+ case "plain": return style;
8776
+ case "heading": return {
8777
+ ...style,
8778
+ typography: "heading",
8779
+ fontWeight: "medium",
8780
+ letterSpacing: "wide"
8781
+ };
8782
+ case "quiet-caps": return {
8783
+ ...style,
8784
+ textTransform: "uppercase",
8785
+ textSize: "sm",
8786
+ letterSpacing: "wide"
8787
+ };
8788
+ case "brand-caps": return {
8789
+ ...style,
8790
+ textTransform: "uppercase",
8791
+ textSize: "sm",
8792
+ letterSpacing: "wider"
8793
+ };
8794
+ default: return assertNever$1(typography, "Unhandled header nav typography");
8795
+ }
8796
+ }
8797
+ function underlineNavLinkStyle(colorToken, underlineColorToken, typography = "plain") {
8798
+ return applyNavTypography({
8799
+ name: "Underline grow",
8800
+ typography: "body",
8801
+ fontWeight: "medium",
8802
+ textTransform: "none",
8803
+ italic: false,
8804
+ colorToken,
8805
+ padding: "compact",
8806
+ paddingX: "compact",
8807
+ borderRadius: "none",
8808
+ effects: { hover: [{
8809
+ effectId: "nav-underline",
8810
+ options: {
8811
+ style: "grow",
8812
+ ...underlineColorToken ? { colorToken: underlineColorToken } : {}
8813
+ }
8814
+ }] }
8815
+ }, typography);
8816
+ }
8479
8817
  function themedHeaderShadow(theme) {
8480
8818
  switch (theme.shadow.elevation) {
8481
8819
  case "none": return;
@@ -8575,6 +8913,38 @@ function cleanBaseHeader(theme) {
8575
8913
  ctaGap: "default"
8576
8914
  };
8577
8915
  }
8916
+ function classicCenteredNavHeader(theme) {
8917
+ return {
8918
+ ...baseHeader(theme),
8919
+ variant: "classic",
8920
+ navPlacement: "center",
8921
+ positioning: "sticky",
8922
+ shrinkOnScroll: false,
8923
+ background: {
8924
+ type: "color",
8925
+ color: "surface"
8926
+ },
8927
+ textColor: "text",
8928
+ navColor: "text",
8929
+ navStyle: "underline-grow",
8930
+ navWeight: "medium",
8931
+ navLinkStyle: underlineNavLinkStyle("text", "accent"),
8932
+ border: {
8933
+ style: "solid",
8934
+ width: "thin",
8935
+ position: "bottom",
8936
+ colorToken: "border"
8937
+ },
8938
+ ctaTreatment: "primary",
8939
+ ctaVariant: resolveHeaderCtaVariant(theme, "primary"),
8940
+ ctaGap: "default",
8941
+ logoText: {
8942
+ mobileMaxWidth: "sm",
8943
+ mobileWrapLines: 1,
8944
+ hideOnShrink: false
8945
+ }
8946
+ };
8947
+ }
8578
8948
  function serifLineHeader(theme) {
8579
8949
  return {
8580
8950
  ...baseHeader(theme),
@@ -8739,6 +9109,21 @@ const headerLookCatalog = defineExactCatalog()([
8739
9109
  logoAccommodations: inlineClassicLogoAccommodations,
8740
9110
  compile: cleanBaseHeader
8741
9111
  },
9112
+ {
9113
+ id: asHeaderLookId("classic-centered-nav"),
9114
+ label: "Centered nav",
9115
+ visualTreatment: "solid-neutral",
9116
+ layoutBehavior: "sticky",
9117
+ configuration: {
9118
+ structure: "inline-classic",
9119
+ treatment: "solid-neutral",
9120
+ behavior: "sticky"
9121
+ },
9122
+ openingRequirement: "none",
9123
+ maxRecommendedNavItems: 6,
9124
+ logoAccommodations: inlineClassicLogoAccommodations,
9125
+ compile: classicCenteredNavHeader
9126
+ },
8742
9127
  {
8743
9128
  id: asHeaderLookId("brand-solid"),
8744
9129
  label: "Brand solid",
@@ -9905,6 +10290,74 @@ const typographyPresets = [
9905
10290
  }
9906
10291
  }
9907
10292
  }
10293
+ }),
10294
+ defineTypographyPreset({
10295
+ id: "typography-preset:fulcrum",
10296
+ name: "Fulcrum",
10297
+ description: "Newsreader serif headings over an IBM Plex Sans body, with an IBM Plex Mono eyebrow role for uppercase kickers and mono captions.",
10298
+ defaultFonts: {
10299
+ body: "IBM Plex Sans, system-ui, -apple-system, sans-serif",
10300
+ heading: "Newsreader, Georgia, \"Times New Roman\", serif"
10301
+ },
10302
+ recommendedFonts: {
10303
+ body: [
10304
+ "IBM Plex Sans",
10305
+ "Source Sans 3",
10306
+ "Inter"
10307
+ ],
10308
+ heading: [
10309
+ "Newsreader",
10310
+ "Source Serif 4",
10311
+ "Lora"
10312
+ ]
10313
+ },
10314
+ slotPolicies: {
10315
+ body: bodyReadablePolicy,
10316
+ heading: displayHeadingPolicy
10317
+ },
10318
+ typography: {
10319
+ body: {
10320
+ family: "IBM Plex Sans, system-ui, -apple-system, sans-serif",
10321
+ size: "lg",
10322
+ weight: "regular",
10323
+ lineHeight: "relaxed",
10324
+ letterSpacing: "normal"
10325
+ },
10326
+ headings: {
10327
+ default: {
10328
+ family: "Newsreader, Georgia, \"Times New Roman\", serif",
10329
+ weight: "medium",
10330
+ lineHeight: "normal",
10331
+ letterSpacing: "tight",
10332
+ colorToken: "primary",
10333
+ italic: null,
10334
+ case: null
10335
+ },
10336
+ h1: {
10337
+ size: "2xl",
10338
+ weight: "medium",
10339
+ lineHeight: "normal",
10340
+ letterSpacing: "tight",
10341
+ italic: null
10342
+ },
10343
+ h2: {
10344
+ size: "xl",
10345
+ weight: "medium",
10346
+ lineHeight: "normal",
10347
+ letterSpacing: "tight",
10348
+ italic: null
10349
+ },
10350
+ h3: {
10351
+ size: "md",
10352
+ weight: "semibold",
10353
+ lineHeight: "normal",
10354
+ letterSpacing: "normal",
10355
+ italic: null
10356
+ }
10357
+ },
10358
+ scale: "spacious",
10359
+ eyebrow: { family: "IBM Plex Mono, ui-monospace, \"SF Mono\", monospace" }
10360
+ }
9908
10361
  })
9909
10362
  ];
9910
10363
  new Map(typographyPresets.map((preset) => [preset.id, preset]));
@@ -10125,7 +10578,8 @@ const curatedSiteStyleIdValues = [
10125
10578
  "site-style:bold-launch",
10126
10579
  "site-style:south-west-twerk-school",
10127
10580
  "site-style:kirtan-ornate",
10128
- "site-style:original-joy"
10581
+ "site-style:original-joy",
10582
+ "site-style:fulcrum"
10129
10583
  ];
10130
10584
  const quietBudget = {
10131
10585
  accentDensity: "low",
@@ -11579,6 +12033,104 @@ const curatedSiteStyles = [
11579
12033
  buttonChoice("showtime-pill")
11580
12034
  ],
11581
12035
  paletteRecommendations: [paletteRecommendation("warm-dark-ember")]
12036
+ }),
12037
+ defineCuratedSiteStyle({
12038
+ id: "site-style:fulcrum",
12039
+ name: "Fulcrum",
12040
+ headingDecoration: {
12041
+ family: "short-center-rule",
12042
+ colorToken: "accent"
12043
+ },
12044
+ 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.",
12045
+ 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.",
12046
+ siteCreatorSelection: {
12047
+ kind: "site_creator_keyword_heuristic",
12048
+ priority: 28
12049
+ },
12050
+ selectionKeywordRules: [
12051
+ {
12052
+ kind: "substring",
12053
+ value: "physiotherap"
12054
+ },
12055
+ {
12056
+ kind: "word",
12057
+ value: "physio"
12058
+ },
12059
+ {
12060
+ kind: "phrase",
12061
+ value: "sports injury"
12062
+ },
12063
+ {
12064
+ kind: "phrase",
12065
+ value: "rehab clinic"
12066
+ },
12067
+ {
12068
+ kind: "substring",
12069
+ value: "musculoskeletal"
12070
+ },
12071
+ {
12072
+ kind: "phrase",
12073
+ value: "sports therapy"
12074
+ }
12075
+ ],
12076
+ tokenRecipes: {
12077
+ palette: "brand-led",
12078
+ contrast: "standard",
12079
+ radius: "rounded",
12080
+ shadow: "subtle",
12081
+ typography: "serif-editorial",
12082
+ spacing: "airy",
12083
+ motion: "subtle"
12084
+ },
12085
+ compositionBudget: quietBudget,
12086
+ typographyPresetChoices: [
12087
+ typographyChoice("typography-preset:fulcrum"),
12088
+ typographyChoice("typography-preset:editorial-serif"),
12089
+ typographyChoice("typography-preset:modern-sans")
12090
+ ],
12091
+ defaultTypographyPresetId: "typography-preset:fulcrum",
12092
+ themeOverrides: {
12093
+ shadow: { colorToken: "primary" },
12094
+ accordions: {
12095
+ background: null,
12096
+ spacing: "comfortable",
12097
+ corners: "square",
12098
+ border: null,
12099
+ shadow: null,
12100
+ icon: {
12101
+ style: "plus-minus",
12102
+ position: "right",
12103
+ size: "md",
12104
+ colorToken: "accent"
12105
+ },
12106
+ trigger: {
12107
+ textColorToken: "primary",
12108
+ fontWeight: "medium",
12109
+ fontFamily: "heading",
12110
+ fontScale: "md"
12111
+ },
12112
+ hover: { textColorToken: "secondary" },
12113
+ content: { textColorToken: "muted" }
12114
+ },
12115
+ testimonialQuote: serifDisplayQuote
12116
+ },
12117
+ recommendedHeaderLookIds: [
12118
+ "classic-centered-nav",
12119
+ "serif-line",
12120
+ "centered-calm",
12121
+ "clean-base"
12122
+ ],
12123
+ recommendedFooterLookIds: ["footer-editorial-columns", "footer-muted-grounding"],
12124
+ buttonPersonalityChoices: [
12125
+ buttonChoice("flat-ink"),
12126
+ buttonChoice("confident-chip"),
12127
+ buttonChoice("editorial-link")
12128
+ ],
12129
+ paletteRecommendations: [
12130
+ paletteRecommendation("brand-led-spruce"),
12131
+ paletteRecommendation("warm-neutral-forest"),
12132
+ paletteRecommendation("soft-natural-sage")
12133
+ ]
11582
12134
  })
11583
12135
  ];
11584
12136
  new Map(curatedSiteStyles.map((style) => [style.id, style]));
@@ -12020,7 +12572,15 @@ function gradientToCss(config, tokens) {
12020
12572
  const stops = config.stops.map((stop) => {
12021
12573
  return `${tokens[stop.color] ?? "#000000"} ${stop.position}%`;
12022
12574
  }).join(", ");
12023
- if (config.type === "radial") return `radial-gradient(circle at ${config.position ?? "center"}, ${stops})`;
12575
+ const type = config.type ?? "linear";
12576
+ if (type === "radial") {
12577
+ const position = config.position ?? "center";
12578
+ return `radial-gradient(${config.shape ?? "circle"} at ${position}, ${stops})`;
12579
+ }
12580
+ if (type === "conic") {
12581
+ const position = config.position ?? "center";
12582
+ return `conic-gradient(from ${config.angle ?? 0}deg at ${position}, ${stops})`;
12583
+ }
12024
12584
  return `linear-gradient(${config.angle ?? 180}deg, ${stops})`;
12025
12585
  }
12026
12586
  const BREAKPOINT_LG = 1024;
@@ -12378,6 +12938,9 @@ const ALLOWED_NODE_TYPES = [
12378
12938
  "richText",
12379
12939
  "media",
12380
12940
  "videoEmbed",
12941
+ "icon",
12942
+ "feature-card-icon",
12943
+ "svg",
12381
12944
  "button",
12382
12945
  "link",
12383
12946
  "carousel",
@@ -12414,11 +12977,13 @@ const ALLOWED_TRANSFORM_IDS = [
12414
12977
  "value.jsonArray",
12415
12978
  "value.csv",
12416
12979
  "array.marqueeLoop",
12980
+ "value.mapToken",
12417
12981
  "layout.maxWidthClass",
12418
12982
  "layout.headerRootClass",
12419
12983
  "layout.footerRootClass",
12420
12984
  "layout.headerRootStyle",
12421
12985
  "layout.headerSectionBackground",
12986
+ "layout.classicNavMode",
12422
12987
  "layout.footerRootStyle",
12423
12988
  "ui.headerNavLinkClass",
12424
12989
  "ui.headingClassFromAlignment",
@@ -14920,7 +15485,7 @@ function resolveBackgroundColorValue(value, context) {
14920
15485
  return resolveThemeColorReferenceValue(value, { palette: context.theme?.palette });
14921
15486
  }
14922
15487
  //#endregion
14923
- //#region ../blocks/src/system/transforms/registry/_utils/sectionForegroundStyles.ts
15488
+ //#region ../blocks/src/system/transforms/registry/_utils/surfaceForegroundStyles.ts
14924
15489
  function readColorToken(input, key) {
14925
15490
  const value = input[key];
14926
15491
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
@@ -14929,18 +15494,17 @@ function resolveOptionalForegroundColor(value, context) {
14929
15494
  if (!value) return null;
14930
15495
  return resolveBackgroundColorValue(value, context);
14931
15496
  }
14932
- function resolveSectionForegroundStyles(value, context) {
15497
+ function resolveSurfaceForegroundStyles(value, context) {
14933
15498
  if (!value || typeof value !== "object") return {};
14934
15499
  const input = value;
14935
15500
  const resolvedText = resolveOptionalForegroundColor(readColorToken(input, "textColor"), context);
14936
15501
  if (!resolvedText) return {};
14937
- return {
14938
- color: resolvedText,
14939
- "--section-text-color": resolvedText,
14940
- "--section-heading-color": resolveOptionalForegroundColor(readColorToken(input, "headingColor"), context) ?? resolvedText,
14941
- "--section-link-color": resolveOptionalForegroundColor(readColorToken(input, "linkColor"), context) ?? resolvedText,
14942
- "--section-supporting-color": resolveOptionalForegroundColor(readColorToken(input, "supportingColor"), context) ?? resolvedText
14943
- };
15502
+ return { ...surfaceForegroundStyle({
15503
+ text: resolvedText,
15504
+ heading: resolveOptionalForegroundColor(readColorToken(input, "headingColor"), context) ?? void 0,
15505
+ link: resolveOptionalForegroundColor(readColorToken(input, "linkColor"), context) ?? void 0,
15506
+ supporting: resolveOptionalForegroundColor(readColorToken(input, "supportingColor"), context) ?? void 0
15507
+ }) };
14944
15508
  }
14945
15509
  //#endregion
14946
15510
  //#region ../blocks/src/system/transforms/registry/boxStyles.ts
@@ -15278,67 +15842,12 @@ const containerStylesResolveBoxClassName = {
15278
15842
  }
15279
15843
  };
15280
15844
  /**
15281
- * Resolves section text color style object.
15282
- *
15283
- * The value should be the `_sectionStyles.background.textColor` token.
15284
- * Returns an empty object when unset so the section inherits text color.
15285
- *
15286
- * Outputs both `color` and `--section-text-color` CSS variable so that
15287
- * headings (which have explicit color rules in typography CSS) can inherit
15288
- * the section's text color via the CSS variable cascade.
15289
- */
15290
- const sectionTextColorStyleTransform = {
15291
- id: "sectionStyles.resolveTextColorStyle",
15292
- kind: "formatter",
15293
- summary: "Resolve section text color token into a style object",
15294
- run: (value, options, context) => {
15295
- const fallbackToken = options?.fallbackToken;
15296
- if (typeof value === "string" && value.trim()) {
15297
- const colorValue = resolveBackgroundColorValue(value.trim(), context);
15298
- return {
15299
- color: colorValue,
15300
- "--section-text-color": colorValue
15301
- };
15302
- }
15303
- if (typeof fallbackToken === "string" && fallbackToken.trim()) {
15304
- const colorValue = resolveBackgroundColorValue(fallbackToken.trim(), context);
15305
- return {
15306
- color: colorValue,
15307
- "--section-text-color": colorValue
15308
- };
15309
- }
15310
- return {};
15311
- }
15312
- };
15313
- /**
15314
- * Resolves section heading color style object.
15315
- *
15316
- * The value should be the `_sectionStyles.background.headingColor` token.
15317
- * Returns an empty object when unset so headings inherit from textColor or default.
15318
- *
15319
- * Outputs `--section-heading-color` CSS variable which typography CSS can use
15320
- * to style headings differently from body text within a section.
15321
- */
15322
- const sectionHeadingColorStyleTransform = {
15323
- id: "sectionStyles.resolveHeadingColorStyle",
15324
- kind: "formatter",
15325
- summary: "Resolve section heading color token into a style object",
15326
- run: (value, options, context) => {
15327
- const fallbackToken = options?.fallbackToken;
15328
- if (typeof value === "string" && value.trim()) return { "--section-heading-color": resolveBackgroundColorValue(value.trim(), context) };
15329
- if (typeof fallbackToken === "string" && fallbackToken.trim()) return { "--section-heading-color": resolveBackgroundColorValue(fallbackToken.trim(), context) };
15330
- return {};
15331
- }
15332
- };
15333
- /**
15334
- * Resolves section foreground colors from the background object.
15845
+ * Resolves surface foreground colors from the background object.
15335
15846
  *
15336
15847
  * The value should be the full `_sectionStyles.background` object.
15337
15848
  * Outputs:
15338
- * - `color` and `--section-text-color` from background.textColor
15339
- * - `--section-heading-color` from background.headingColor (falls back to textColor if not set)
15340
- * - `--section-link-color` from background.linkColor (falls back to textColor if not set)
15341
- * - `--section-supporting-color` from background.supportingColor (falls back to textColor if not set)
15849
+ * - `color` and the complete `--rb-surface-*-color` contract
15850
+ * - optional roles fall back atomically to background.textColor
15342
15851
  *
15343
15852
  * This combined transform replaces separate foreground color transforms
15344
15853
  * when both need to be applied to the same style prop.
@@ -15346,8 +15855,8 @@ const sectionHeadingColorStyleTransform = {
15346
15855
  const sectionColorsStyleTransform = {
15347
15856
  id: "sectionStyles.resolveColorsStyle",
15348
15857
  kind: "formatter",
15349
- summary: "Resolve section foreground colors into a style object",
15350
- run: (value, _options, context) => resolveSectionForegroundStyles(value, context)
15858
+ summary: "Resolve surface foreground colors into a style object",
15859
+ run: (value, _options, context) => resolveSurfaceForegroundStyles(value, context)
15351
15860
  };
15352
15861
  /**
15353
15862
  * Maps semantic gap values to theme-aware gap classes.
@@ -15386,8 +15895,6 @@ const boxStylesTransforms = [
15386
15895
  sectionClassNameTransform,
15387
15896
  containerStylesResolveClassName,
15388
15897
  containerStylesResolveBoxClassName,
15389
- sectionTextColorStyleTransform,
15390
- sectionHeadingColorStyleTransform,
15391
15898
  sectionColorsStyleTransform,
15392
15899
  {
15393
15900
  id: "boxStyles.semanticGap",
@@ -16291,7 +16798,29 @@ const ctaRowFragment = defineFragment({
16291
16798
  fields: [createCtasRepeater({
16292
16799
  label: "Calls to action",
16293
16800
  itemLabel: "CTA",
16294
- maxItems: 3
16801
+ maxItems: 3,
16802
+ button: { variants: [
16803
+ {
16804
+ value: "primary",
16805
+ label: "Primary"
16806
+ },
16807
+ {
16808
+ value: "secondary",
16809
+ label: "Secondary"
16810
+ },
16811
+ {
16812
+ value: "outline",
16813
+ label: "Outline"
16814
+ },
16815
+ {
16816
+ value: "link",
16817
+ label: "Link"
16818
+ },
16819
+ {
16820
+ value: "accent",
16821
+ label: "Accent"
16822
+ }
16823
+ ] }
16295
16824
  })],
16296
16825
  layout: [ctaRow({
16297
16826
  collectionPath: "content.ctas",
@@ -16572,6 +17101,55 @@ function buildGridCardsLayout(className) {
16572
17101
  return el("div", { className }, buildResponsiveGridCards());
16573
17102
  }
16574
17103
  /**
17104
+ * Build a single "featured editorial" testimonial card.
17105
+ *
17106
+ * This card type deliberately renders NO avatar/headshot: an amber quote mark,
17107
+ * the quote body, then the author name and a context line. Colour + surface
17108
+ * treatment is driven entirely by `variantClass` (see testimonials.css):
17109
+ * - `rb-testimonials-fe-card--feature` → spruce-ink card (dark primary bg)
17110
+ * - `rb-testimonials-fe-card--plain` → white surface card
17111
+ *
17112
+ * Extra modifiers (`repeat`, `when`) are forwarded to the card node so the
17113
+ * caller can drive first-vs-rest branching.
17114
+ */
17115
+ function buildFeaturedEditorialCard(variantClass, ...mods) {
17116
+ const quoteMark = text({
17117
+ as: "span",
17118
+ className: "rb-testimonials-fe-qmark",
17119
+ "aria-hidden": "true"
17120
+ }, props({ value: "“" }));
17121
+ const quote = richText({ className: "rb-testimonials-fe-quote rb-max-w-none" }, bind("testimonial.content.body"), when("testimonial.content.body"));
17122
+ const meta = el("div", { className: "rb-testimonials-fe-meta" }, [text({
17123
+ as: "strong",
17124
+ className: "rb-testimonials-fe-name"
17125
+ }, bind("testimonial.content.name", { fallback: "Anonymous" })), text({
17126
+ as: "span",
17127
+ className: "rb-testimonials-fe-context"
17128
+ }, when("testimonial.content.jobTitle"), bind("testimonial.content.jobTitle"))]);
17129
+ return el("div", { className: `rb-testimonials-fe-card ${variantClass}` }, [
17130
+ quoteMark,
17131
+ quote,
17132
+ meta
17133
+ ], ...mods);
17134
+ }
17135
+ /**
17136
+ * Build the "featured editorial" layout: an asymmetric two-column arrangement
17137
+ * where the FIRST testimonial is a large featured (spruce-ink) card and the
17138
+ * remaining testimonials stack as white cards beside it.
17139
+ *
17140
+ * First-vs-rest branching is done without an offset primitive: the featured
17141
+ * card uses `repeat(..., { limit: 1 })` (entry 0 only), and the rest column
17142
+ * repeats over every entry but skips index 0 via `when("tIndex", { equals: 0,
17143
+ * not: true })`. The `$when` guard is re-evaluated per repeated item with the
17144
+ * index scope in context, so entry 0 produces no node in the rest column.
17145
+ */
17146
+ function buildFeaturedEditorialLayout() {
17147
+ 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", {
17148
+ equals: 0,
17149
+ not: true
17150
+ }))])]);
17151
+ }
17152
+ /**
16575
17153
  * Fragment definition for testimonials carousel.
16576
17154
  * Provides field definitions and data loader configuration.
16577
17155
  *
@@ -19589,7 +20167,7 @@ const defaultLayout = styledSection({
19589
20167
  children: styledContainer({
19590
20168
  baseClassName: "rb-relative rb-isolate",
19591
20169
  children: stack({
19592
- gap: "xl",
20170
+ gap: "lg",
19593
20171
  className: "rb-w-full rb-max-w-4xl rb-mx-auto"
19594
20172
  }, [headingLayout, accordionLayout])
19595
20173
  }),
@@ -19606,7 +20184,7 @@ const questionColumnsLayout = styledSection({
19606
20184
  children: styledContainer({
19607
20185
  baseClassName: "rb-relative rb-isolate",
19608
20186
  children: stack({
19609
- gap: "xl",
20187
+ gap: "lg",
19610
20188
  className: "rb-w-full"
19611
20189
  }, [headingLayout, questionColumnsAccordionLayout])
19612
20190
  }),
@@ -19771,7 +20349,26 @@ const formattingTransforms = [
19771
20349
  arrayLengthTransform,
19772
20350
  jsonArrayTransform,
19773
20351
  csvTransform,
19774
- marqueeLoopTransform
20352
+ marqueeLoopTransform,
20353
+ {
20354
+ id: "value.mapToken",
20355
+ kind: "formatter",
20356
+ summary: "Map a state/enum value to a theme token colour (rgb(var(--tb-<role>)))",
20357
+ schema: z$1.object({
20358
+ /**
20359
+ * Maps an input state/enum value (stringified) to a theme token ROLE name
20360
+ * (e.g. `secondary`, `accent`, `muted`, `border`). The role is wrapped to
20361
+ * `rgb(var(--tb-<role>))` so the result drops straight into a `style` binding.
20362
+ */
20363
+ cases: z$1.record(z$1.string(), z$1.string()),
20364
+ /** Role used when the value matches no case. Omit to yield `undefined`. */
20365
+ fallback: z$1.string().optional()
20366
+ }),
20367
+ run: (value, options) => {
20368
+ const role = options.cases[String(value)] ?? options.fallback;
20369
+ return role ? `rgb(var(--tb-${role}))` : void 0;
20370
+ }
20371
+ }
19775
20372
  ];
19776
20373
  //#endregion
19777
20374
  //#region ../blocks/src/system/transforms/registry/ui.ts
@@ -20355,6 +20952,26 @@ const headerLogoTextVisibleTransform = {
20355
20952
  return resolveHeaderBrandDisplay(asPartialObject(value)) !== "logo-only";
20356
20953
  }
20357
20954
  };
20955
+ /**
20956
+ * Selects which classic-layout nav row should render.
20957
+ *
20958
+ * - `'inline'` → classic variant with the historical logo-left / nav-right row
20959
+ * - `'centered'`→ classic variant with a centred (flex-1) nav and a right CTA
20960
+ * - `'other'` → not a classic variant (centered/floating/transparent handle it)
20961
+ *
20962
+ * Consumed by the site-header block's `$when` gates so the two classic rows are
20963
+ * mutually exclusive without introducing a new top-level header variant.
20964
+ */
20965
+ const classicNavModeTransform = {
20966
+ id: "layout.classicNavMode",
20967
+ kind: "formatter",
20968
+ summary: "Resolve which classic-layout nav row to render",
20969
+ run: (value) => {
20970
+ const header = asPartialObject(value);
20971
+ if (header?.variant !== "classic") return "other";
20972
+ return header?.navPlacement === "center" ? "centered" : "inline";
20973
+ }
20974
+ };
20358
20975
  const maxWidthClassTransform = {
20359
20976
  id: "layout.maxWidthClass",
20360
20977
  kind: "string",
@@ -20537,8 +21154,9 @@ const footerRootStyleTransform = {
20537
21154
  styleParts.push(backgroundColorStyle(token));
20538
21155
  }
20539
21156
  }
20540
- if (visuals.textColorOverride) styleParts.push({ color: visuals.textColorOverride });
20541
- else if (visuals.textColorToken) styleParts.push(textColorStyle(visuals.textColorToken));
21157
+ const foreground = visuals.textColorOverride ?? (visuals.textColorToken ? rgbColorValue(visuals.textColorToken) : null);
21158
+ if (!visuals.isTransparent && foreground) styleParts.push(surfaceForegroundStyle({ text: foreground }));
21159
+ else if (foreground) styleParts.push({ color: foreground });
20542
21160
  styleParts.push(borderColorStyle("border"));
20543
21161
  return mergeStyles(...styleParts);
20544
21162
  }
@@ -20624,18 +21242,16 @@ const footerBottomBarStyleTransform = {
20624
21242
  const styleParts = [];
20625
21243
  const background = bottomBar?.background;
20626
21244
  if (background?.type === "color") styleParts.push(backgroundColorStyle(background.color ?? "surface"));
20627
- if (bottomBar?.textColor) styleParts.push(textColorStyle(bottomBar.textColor));
20628
21245
  const borderColorToken = bottomBar?.borderTop?.colorToken;
20629
21246
  if (borderColorToken) styleParts.push(borderColorStyle(borderColorToken));
20630
21247
  else styleParts.push(borderColorStyle("border"));
20631
21248
  const borderWidth = bottomBar?.borderTop?.width;
20632
21249
  if (borderWidth && borderWidth !== "none") styleParts.push({ borderTopWidth: borderWidth === "thin" ? "1px" : borderWidth === "medium" ? "2px" : "3px" });
20633
21250
  else if (borderWidth === "none") styleParts.push({ borderTopWidth: "0px" });
20634
- if (!bottomBar?.textColor) {
20635
- const visuals = resolveBackgroundVisualStyles(extractBackgroundColorToken(background, "surface"), context.theme);
20636
- if (visuals.textColorOverride) styleParts.push({ color: visuals.textColorOverride });
20637
- else if (visuals.textColorToken) styleParts.push(textColorStyle(visuals.textColorToken));
20638
- }
21251
+ const visuals = resolveBackgroundVisualStyles(extractBackgroundColorToken(background, "surface"), context.theme);
21252
+ const foreground = bottomBar?.textColor ? rgbColorValue(bottomBar.textColor) : visuals.textColorOverride ?? (visuals.textColorToken ? rgbColorValue(visuals.textColorToken) : null);
21253
+ if (background?.type === "color" && foreground) styleParts.push(surfaceForegroundStyle({ text: foreground }));
21254
+ else if (foreground && bottomBar?.textColor) styleParts.push({ color: foreground });
20639
21255
  return mergeStyles(...styleParts);
20640
21256
  }
20641
21257
  };
@@ -20770,6 +21386,7 @@ const layoutTransforms = [
20770
21386
  headerLogoClassTransform,
20771
21387
  headerLogoMediaClassTransform,
20772
21388
  headerLogoTextVisibleTransform,
21389
+ classicNavModeTransform,
20773
21390
  footerRootClassTransform,
20774
21391
  headerRootStyleTransform,
20775
21392
  headerSectionBackgroundTransform,
@@ -21166,7 +21783,18 @@ const classicLayout = inline({
21166
21783
  logoRow,
21167
21784
  createNavRow(`rb-ml-auto ${desktopOnlyDisplay("flex")} rb-gap-6`),
21168
21785
  headerCta
21169
- ], when("$root.theme.header.variant", { equals: "classic" }));
21786
+ ], whenTransformed$1("$root.theme.header", "layout.classicNavMode", "inline"));
21787
+ const classicCenteredNavLayout = inline({
21788
+ className: bindProp("$root.theme.header.maxWidth", {
21789
+ transforms: pipe(tx("layout.maxWidthClass", { base: `rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6` })),
21790
+ fallback: `rb-container rb-header-layout ${desktopOnlyDisplay("flex")} rb-w-full rb-items-center rb-gap-6`
21791
+ }),
21792
+ align: "center"
21793
+ }, [
21794
+ logoRow,
21795
+ createNavRow(`rb-flex-1 rb-justify-center ${desktopOnlyDisplay("flex")} rb-gap-6`, "center"),
21796
+ headerCta
21797
+ ], whenTransformed$1("$root.theme.header", "layout.classicNavMode", "centered"));
21170
21798
  const centeredLayout = stack({
21171
21799
  gap: "md",
21172
21800
  align: "center",
@@ -21241,6 +21869,7 @@ const siteHeaderBlockDefinition = { manifest: createBlockManifest({
21241
21869
  }, [
21242
21870
  mobileHeaderBar,
21243
21871
  classicLayout,
21872
+ classicCenteredNavLayout,
21244
21873
  centeredLayout,
21245
21874
  transparentLayout,
21246
21875
  floatingLayout,
@@ -21449,7 +22078,7 @@ const footerLayout = section({
21449
22078
  style: { $bind: {
21450
22079
  from: "$root.theme.footer",
21451
22080
  transforms: [{ id: "layout.footerRootStyle" }],
21452
- fallback: mergeStyles(backgroundColorStyle("surface"), textColorStyle("text"), borderColorStyle("border"))
22081
+ fallback: mergeStyles(backgroundColorStyle("surface"), surfaceForegroundStyle({ text: "rgb(var(--tb-text))" }), borderColorStyle("border"))
21453
22082
  } }
21454
22083
  }, [
21455
22084
  simpleFooterLayout,
@@ -21503,7 +22132,7 @@ const backgroundImageClassName = "rb-absolute rb-inset-0 rb-z-neg-10 rb-h-full r
21503
22132
  */
21504
22133
  const wrapVariant$2 = (variantLayout) => styledSection({
21505
22134
  children: styledContainer({ children: stack({
21506
- gap: "xl",
22135
+ gap: "lg",
21507
22136
  className: "rb-relative"
21508
22137
  }, [...testimonialsHeadingFragment.layout, variantLayout]) }),
21509
22138
  baseClass: "",
@@ -21529,6 +22158,26 @@ const splitGridLayout = styledSection({
21529
22158
  spacing: "comfortable",
21530
22159
  imageClassName: backgroundImageClassName
21531
22160
  });
22161
+ const featuredEditorialLayout = styledSection({
22162
+ children: styledContainer({
22163
+ baseClassName: "rb-relative rb-isolate",
22164
+ children: stack({
22165
+ gap: "lg",
22166
+ className: "rb-relative"
22167
+ }, [el("div", { className: "rb-testimonials-fe-head" }, [text({
22168
+ as: "h2",
22169
+ className: "rb-text-left",
22170
+ headingDecoration: "off"
22171
+ }, when("content.heading"), bind("content.heading")), el("div", { className: "rb-testimonials-fe-leadin" }, [text({
22172
+ as: "p",
22173
+ className: "rb-prose",
22174
+ style: textColorStyle("mutedText")
22175
+ }, when("content.subheading"), bind("content.subheading"))])]), buildFeaturedEditorialLayout()])
22176
+ }),
22177
+ baseClass: "",
22178
+ spacing: "comfortable",
22179
+ imageClassName: backgroundImageClassName
22180
+ });
21532
22181
  const additionalFields = parseFieldDefinitions([
21533
22182
  ...testimonialsHeadingFragment.fields,
21534
22183
  {
@@ -21616,6 +22265,7 @@ const testimonialsBlockDefinition = {
21616
22265
  carousel: carouselLayout$1,
21617
22266
  grid: gridLayout$1,
21618
22267
  "split-grid": splitGridLayout,
22268
+ "featured-editorial": featuredEditorialLayout,
21619
22269
  highlight: highlightLayout
21620
22270
  },
21621
22271
  defaultVariant: "carousel",
@@ -21635,6 +22285,11 @@ const testimonialsBlockDefinition = {
21635
22285
  description: "Heading on the left with testimonial cards on the right",
21636
22286
  icon: "PanelLeft"
21637
22287
  },
22288
+ "featured-editorial": {
22289
+ label: "Featured editorial",
22290
+ description: "A large featured ink quote beside a column of stacked cards (no avatars)",
22291
+ icon: "Quote"
22292
+ },
21638
22293
  highlight: {
21639
22294
  label: "Highlight",
21640
22295
  description: "A single, centered testimonial with no carousel",
@@ -22107,7 +22762,7 @@ function grid$2(props, children) {
22107
22762
  const cardsLayout = styledSection({
22108
22763
  children: styledContainer({
22109
22764
  rootType: "stack",
22110
- rootProps: { gap: "xl" },
22765
+ rootProps: { gap: "lg" },
22111
22766
  children: [stack({
22112
22767
  gap: "sm",
22113
22768
  className: "rb-feature-cards-heading rb-mx-auto rb-max-w-3xl rb-text-center"
@@ -23994,8 +24649,11 @@ const recoverEmptyDescriptionWhenActive = {
23994
24649
  kind: "recover-empty-when-active",
23995
24650
  placeholder: "Add description"
23996
24651
  };
23997
- function eventSectionHeaderLayoutNodes() {
23998
- 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 }))];
24652
+ function eventSectionHeaderLayoutNodes(options = {}) {
24653
+ const headingNode = text({ as: "h2" }, bind("content.heading"), when("content.heading", { authoringPresence: recoverEmptyHeadingWhenActive }));
24654
+ const descriptionNode = richText({ className: "rb-prose rb-max-w-none" }, bind("content.description"), when("content.description", { authoringPresence: recoverEmptyDescriptionWhenActive }));
24655
+ if (options.splitClassName) return [el("div", { className: options.splitClassName }, [headingNode, descriptionNode])];
24656
+ return [headingNode, descriptionNode];
23999
24657
  }
24000
24658
  //#endregion
24001
24659
  //#region ../blocks/src/system/blocks/shop.shared.ts
@@ -24062,7 +24720,8 @@ const eventCalendarDisplayModeValues = [
24062
24720
  "combined",
24063
24721
  "calendar",
24064
24722
  "list",
24065
- "timetable"
24723
+ "timetable",
24724
+ "week-board"
24066
24725
  ];
24067
24726
  const eventCalendarDefaultViewValues = ["month", "week"];
24068
24727
  const eventCalendarListLayoutValues = [
@@ -26504,7 +27163,7 @@ const eventCalendarLayout = [styledSection({
26504
27163
  children: styledContainer({
26505
27164
  rootType: "stack",
26506
27165
  rootProps: { gap: "lg" },
26507
- children: [...eventSectionHeaderLayoutNodes(), el("event-calendar", withApiBindings({
27166
+ children: [...eventSectionHeaderLayoutNodes({ splitClassName: "event-section-head event-section-head--split" }), el("event-calendar", withApiBindings({
26508
27167
  events: { $bind: { from: "data.events" } },
26509
27168
  stage: { $bind: {
26510
27169
  from: "$root.previewStage",
@@ -26615,6 +27274,10 @@ const eventCalendarAdditionalFields = [
26615
27274
  value: "timetable",
26616
27275
  label: "Timetable"
26617
27276
  },
27277
+ {
27278
+ value: "week-board",
27279
+ label: "Week board"
27280
+ },
26618
27281
  {
26619
27282
  value: "list",
26620
27283
  label: "List"
@@ -26797,7 +27460,8 @@ const eventCalendarAdditionalFields = [
26797
27460
  oneOf: [
26798
27461
  "combined",
26799
27462
  "calendar",
26800
- "timetable"
27463
+ "timetable",
27464
+ "week-board"
26801
27465
  ]
26802
27466
  }
26803
27467
  }
@@ -26900,7 +27564,8 @@ const eventCalendarAdditionalFields = [
26900
27564
  "combined",
26901
27565
  "calendar",
26902
27566
  "list",
26903
- "timetable"
27567
+ "timetable",
27568
+ "week-board"
26904
27569
  ]
26905
27570
  }
26906
27571
  }
@@ -26935,7 +27600,8 @@ const eventCalendarAdditionalFields = [
26935
27600
  "combined",
26936
27601
  "calendar",
26937
27602
  "list",
26938
- "timetable"
27603
+ "timetable",
27604
+ "week-board"
26939
27605
  ]
26940
27606
  }, {
26941
27607
  field: "showFilters",
@@ -26958,7 +27624,8 @@ const eventCalendarAdditionalFields = [
26958
27624
  "combined",
26959
27625
  "calendar",
26960
27626
  "list",
26961
- "timetable"
27627
+ "timetable",
27628
+ "week-board"
26962
27629
  ]
26963
27630
  }, {
26964
27631
  field: "showFilters",
@@ -26981,7 +27648,8 @@ const eventCalendarAdditionalFields = [
26981
27648
  "combined",
26982
27649
  "calendar",
26983
27650
  "list",
26984
- "timetable"
27651
+ "timetable",
27652
+ "week-board"
26985
27653
  ]
26986
27654
  }, {
26987
27655
  field: "showFilters",
@@ -27003,7 +27671,8 @@ const eventCalendarAdditionalFields = [
27003
27671
  oneOf: [
27004
27672
  "combined",
27005
27673
  "list",
27006
- "timetable"
27674
+ "timetable",
27675
+ "week-board"
27007
27676
  ]
27008
27677
  }, { allOf: [{
27009
27678
  field: "variant",
@@ -27179,6 +27848,7 @@ const eventCalendarVariants = {
27179
27848
  combined: eventCalendarLayout[0],
27180
27849
  calendar: eventCalendarLayout[0],
27181
27850
  timetable: eventCalendarLayout[0],
27851
+ "week-board": eventCalendarLayout[0],
27182
27852
  list: eventCalendarLayout[0]
27183
27853
  };
27184
27854
  const eventCalendarContentShape = {
@@ -27192,6 +27862,7 @@ const eventCalendarContentShape = {
27192
27862
  { value: "combined" },
27193
27863
  { value: "calendar" },
27194
27864
  { value: "timetable" },
27865
+ { value: "week-board" },
27195
27866
  { value: "list" }
27196
27867
  ]
27197
27868
  },
@@ -27258,6 +27929,7 @@ const eventCalendarBlockDefinition = {
27258
27929
  combined: { label: "Combined" },
27259
27930
  calendar: { label: "Calendar" },
27260
27931
  timetable: { label: "Timetable" },
27932
+ "week-board": { label: "Week board" },
27261
27933
  list: { label: "List" }
27262
27934
  }
27263
27935
  }, eventCalendarContentShape),
@@ -32435,6 +33107,12 @@ const systemBlockDefinitions = [
32435
33107
  family: "centered",
32436
33108
  weight: "featured",
32437
33109
  bias: "soft"
33110
+ }),
33111
+ layoutProfile({
33112
+ variantKey: "featured-editorial",
33113
+ family: "split",
33114
+ weight: "featured",
33115
+ bias: "soft"
32438
33116
  })
32439
33117
  ]
32440
33118
  })),
@@ -32567,6 +33245,11 @@ const systemBlockDefinitions = [
32567
33245
  variantKey: "timetable",
32568
33246
  family: "stack",
32569
33247
  weight: "standard"
33248
+ }),
33249
+ layoutProfile({
33250
+ variantKey: "week-board",
33251
+ family: "stack",
33252
+ weight: "standard"
32570
33253
  })
32571
33254
  ]
32572
33255
  })),
@@ -32875,8 +33558,8 @@ const backgroundTransforms = [
32875
33558
  {
32876
33559
  id: "background.resolveColorsStyle",
32877
33560
  kind: "formatter",
32878
- summary: "Resolve section foreground colors from a background object into scoped CSS vars",
32879
- run: (value, _options, context) => resolveSectionForegroundStyles(value, context)
33561
+ summary: "Resolve surface foreground colors from a background object into scoped CSS vars",
33562
+ run: (value, _options, context) => resolveSurfaceForegroundStyles(value, context)
32880
33563
  },
32881
33564
  {
32882
33565
  id: "background.resolveAllowOverflow",
@@ -33549,6 +34232,8 @@ const VERTICAL_IDS = [
33549
34232
  "yoga_wellness",
33550
34233
  "fitness_pt",
33551
34234
  "dance_school",
34235
+ "physiotherapy_physical_health",
34236
+ "massage_bodywork",
33552
34237
  "therapist_practitioner",
33553
34238
  "beauty_spa",
33554
34239
  "consultant_coach",
@@ -33584,6 +34269,12 @@ const VERTICAL_BOOKING_PRESETS = [
33584
34269
  preset("dance_school.workshops", "dance_school", "workshop", "Workshop", "Workshops", "One-off workshops, masterclasses, or intensives."),
33585
34270
  preset("dance_school.private_lessons", "dance_school", "appointment", "Private lesson", "Private lessons", "One-to-one lessons or coaching sessions."),
33586
34271
  preset("dance_school.shows", "dance_school", "performance", "Show", "Shows", "Ticketed performances, showcases, and recitals."),
34272
+ preset("physiotherapy_physical_health.appointments", "physiotherapy_physical_health", "appointment", "Appointment", "Appointments", "Assessments, treatments, follow-ups, and rehabilitation sessions."),
34273
+ preset("physiotherapy_physical_health.rehab_classes", "physiotherapy_physical_health", "class", "Rehab class", "Rehab classes", "Regular rehabilitation, mobility, strength, or recovery classes."),
34274
+ preset("physiotherapy_physical_health.workshops", "physiotherapy_physical_health", "workshop", "Workshop", "Workshops", "One-off workshops, talks, or practical group sessions."),
34275
+ preset("massage_bodywork.treatments", "massage_bodywork", "appointment", "Treatment", "Treatments", "Massage treatments, bodywork sessions, and one-to-one appointments."),
34276
+ preset("massage_bodywork.group_sessions", "massage_bodywork", "class", "Group session", "Group sessions", "Regular bodywork, somatic movement, or self-massage sessions."),
34277
+ preset("massage_bodywork.workshops", "massage_bodywork", "workshop", "Workshop", "Workshops", "One-off bodywork, self-massage, or somatic practice workshops."),
33587
34278
  preset("therapist_practitioner.appointments", "therapist_practitioner", "appointment", "Appointment", "Appointments", "Sessions, consultations, treatments, or assessments."),
33588
34279
  preset("therapist_practitioner.workshops", "therapist_practitioner", "workshop", "Workshop", "Workshops", "One-off group workshops, talks, or wellbeing sessions."),
33589
34280
  preset("therapist_practitioner.retreats", "therapist_practitioner", "retreat", "Retreat", "Retreats", "Multi-day retreats or immersive group experiences."),