@riverbankcms/sdk 0.111.0 → 0.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/{PreviewEditorSidebar-CTR6LUhN.mjs → PreviewEditorSidebar-C7TXFitV.mjs} +4 -4
  2. package/dist/{PreviewEditorUI-DZg15Zip.mjs → PreviewEditorUI-_1JIHTXS.mjs} +3 -3
  3. package/dist/{PreviewQueryProvider-kkmjklLG.mjs → PreviewQueryProvider-nKUT4TL-.mjs} +1 -1
  4. package/dist/{SdkPreviewModeRuntime-DeOTAFvR.mjs → SdkPreviewModeRuntime-CvJTVgWa.mjs} +2579 -34325
  5. package/dist/{SiteChromeCustomizeContext-DOSDLCiJ.mjs → SiteChromeCustomizeContext-66QfCbAS.mjs} +64625 -32791
  6. package/dist/_dts/ai/src/contracts/proposals.d.ts +5211 -639
  7. package/dist/_dts/ai/src/designer/themePatch.d.ts +1 -1
  8. package/dist/_dts/api/src/bookingAdjustments.d.ts +30 -36
  9. package/dist/_dts/api/src/bookingConfig.d.ts +26 -1
  10. package/dist/_dts/api/src/bookingRecords.d.ts +6 -74
  11. package/dist/_dts/api/src/bookingRefundOutcomes.d.ts +3 -3
  12. package/dist/_dts/api/src/contentRuntime.d.ts +5 -1
  13. package/dist/_dts/api/src/customerMembershipOutcomes.d.ts +4 -4
  14. package/dist/_dts/api/src/endpoints.d.ts +35 -0
  15. package/dist/_dts/api/src/event-presentation.d.ts +4 -0
  16. package/dist/_dts/api/src/index.d.ts +4 -4
  17. package/dist/_dts/api/src/navigation/dashboardBookingsNav.d.ts +4 -1
  18. package/dist/_dts/api/src/navigation/dashboardSettingsNav.d.ts +2 -2
  19. package/dist/_dts/api/src/sdk-event-upsert.d.ts +2 -2
  20. package/dist/_dts/api/src/sdkContracts.d.ts +85 -7
  21. package/dist/_dts/api/src/siteBanner.d.ts +8 -8
  22. package/dist/_dts/api/src/siteOperations.d.ts +1 -1
  23. package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +32 -4
  24. package/dist/_dts/block-form/src/widgets/RichTextField/richTextFieldDefaults.d.ts +3 -12
  25. package/dist/_dts/block-form/src/widgets/RichTextField/richTextFieldSchema.d.ts +1 -5
  26. package/dist/_dts/blocks/src/bindings/index.d.ts +1 -10
  27. package/dist/_dts/blocks/src/index.d.ts +3 -6
  28. package/dist/_dts/blocks/src/primitives/content/primitives.d.ts +2 -2
  29. package/dist/_dts/blocks/src/system/blocks/body-text.d.ts +2 -2
  30. package/dist/_dts/blocks/src/system/blocks/events/recurring-schedule.d.ts +2 -2
  31. package/dist/_dts/blocks/src/system/blocks/events/shared/schemas.d.ts +2 -8
  32. package/dist/_dts/blocks/src/system/blocks/feature-card-icons.d.ts +2 -2
  33. package/dist/_dts/blocks/src/system/blocks/feature-cards.d.ts +3 -3
  34. package/dist/_dts/blocks/src/system/blocks/form.d.ts +2 -15
  35. package/dist/_dts/blocks/src/system/blocks/intro.d.ts +2 -2
  36. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +66 -66
  37. package/dist/_dts/blocks/src/system/blocks/newsletter-signup.d.ts +2 -16
  38. package/dist/_dts/blocks/src/system/blocks/site-footer.d.ts +0 -3
  39. package/dist/_dts/blocks/src/system/fragments/index.d.ts +0 -1
  40. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +19 -2
  41. package/dist/_dts/blocks/src/system/manifest/richTextSchema.d.ts +8 -70
  42. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +1 -0
  43. package/dist/_dts/blocks/src/system/manifest/validation.d.ts +6 -0
  44. package/dist/_dts/blocks/src/system/node/schema.d.ts +4 -4
  45. package/dist/_dts/blocks/src/system/runtime/nodes/basic.d.ts +1 -10
  46. package/dist/_dts/blocks/src/system/runtime/nodes/buttonAffordance.d.ts +5 -21
  47. package/dist/_dts/blocks/src/system/runtime/nodes/richText-coerce.d.ts +14 -2
  48. package/dist/_dts/blocks/src/system/transforms/typed.d.ts +1 -1
  49. package/dist/_dts/content-editor/src/controller/seedDraftBaselinesFromBlocks.d.ts +1 -1
  50. package/dist/_dts/content-editor/src/hooks/useBlockAutosave.d.ts +17 -1
  51. package/dist/_dts/content-editor/src/hooks/useDebouncedLatestSave.d.ts +1 -1
  52. package/dist/_dts/content-editor/src/hooks/useSimpleAutosave.d.ts +1 -1
  53. package/dist/_dts/content-editor/src/ui/editorSnapshot.d.ts +3 -2
  54. package/dist/_dts/content-editor/src/ui/editorUiStore.d.ts +2 -2
  55. package/dist/_dts/content-editor/src/ui/stores/autosaveStore.d.ts +1 -1
  56. package/dist/_dts/content-editor/src/ui/stores/types.d.ts +1 -1
  57. package/dist/_dts/content-model/src/content-types/builtins.d.ts +3 -15
  58. package/dist/_dts/content-model/src/content-types/entryValidation.d.ts +2 -0
  59. package/dist/_dts/content-model/src/content-types/schema.d.ts +1 -1
  60. package/dist/_dts/content-model/src/templates/defaults.d.ts +1 -21
  61. package/dist/_dts/core/src/editor-state.d.ts +14 -1
  62. package/dist/_dts/core/src/footer-composition.d.ts +2 -1
  63. package/dist/_dts/core/src/index.d.ts +1 -0
  64. package/dist/_dts/core/src/rich-text.d.ts +16 -0
  65. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +189 -86
  66. package/dist/_dts/editor/src/index.d.ts +1 -1
  67. package/dist/_dts/editor/src/primitives/rich-text/utils.d.ts +3 -5
  68. package/dist/_dts/editor-blocks/src/BlockEditor.d.ts +1 -1
  69. package/dist/_dts/editor-blocks/src/block-editor/BlockCard.d.ts +2 -1
  70. package/dist/_dts/editor-blocks/src/block-editor/BlockCardHeader.d.ts +2 -1
  71. package/dist/_dts/editor-blocks/src/block-editor/blockDraftBaselineSeed.d.ts +15 -0
  72. package/dist/_dts/editor-blocks/src/block-editor/hooks/useBlockBaselineSync.d.ts +3 -7
  73. package/dist/_dts/editor-blocks/src/block-editor/hooks/useBlockDuplication.d.ts +12 -0
  74. package/dist/_dts/editor-blocks/src/block-editor/types.d.ts +1 -0
  75. package/dist/_dts/editor-blocks/src/block-editor/utils.d.ts +1 -1
  76. package/dist/_dts/editor-blocks/src/index.d.ts +6 -5
  77. package/dist/_dts/editor-blocks/src/widgets/rich-text/RichTextField.d.ts +2 -2
  78. package/dist/_dts/media-core/src/contentMediaAudit.d.ts +104 -0
  79. package/dist/_dts/media-core/src/contentMediaAuditFieldAdapter.d.ts +8 -0
  80. package/dist/_dts/media-core/src/contentMediaSnapshotAudit.d.ts +22 -0
  81. package/dist/_dts/media-core/src/index.d.ts +3 -1
  82. package/dist/_dts/sdk/src/cli/commands/event.d.ts +18 -1
  83. package/dist/_dts/sdk/src/cli/commands/push/execute/theme.d.ts +2 -16
  84. package/dist/_dts/sdk/src/cli/content/reader.d.ts +8 -5
  85. package/dist/_dts/sdk/src/client/management/events.d.ts +2 -1
  86. package/dist/_dts/sdk/src/client/management/index.d.ts +1 -1
  87. package/dist/_dts/sdk/src/client/management/types.d.ts +36 -9
  88. package/dist/_dts/sdk/src/config/typed-entries.d.ts +7 -7
  89. package/dist/_dts/sdk/src/config/validation.d.ts +7 -7
  90. package/dist/_dts/sdk/src/contracts/content.d.ts +5 -15
  91. package/dist/_dts/sdk/src/contracts/index.d.ts +1 -1
  92. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +12 -25
  93. package/dist/_dts/sdk/src/contracts/theme.d.ts +2 -18
  94. package/dist/_dts/sdk/src/manifest/passes/pass1-fields.d.ts +1 -1
  95. package/dist/_dts/sdk/src/manifest/three-pass-converter.d.ts +4 -4
  96. package/dist/_dts/sdk/src/rendering/helpers/bindings.d.ts +1 -2
  97. package/dist/_dts/sdk/src/rendering/helpers/index.d.ts +1 -1
  98. package/dist/_dts/sdk/src/rendering/islands/types.d.ts +2 -13
  99. package/dist/_dts/sdk/src/theme/index.d.ts +0 -29
  100. package/dist/_dts/sdk/src/version.d.ts +1 -1
  101. package/dist/_dts/site-commands/src/commands.d.ts +31 -18
  102. package/dist/_dts/theme-core/src/buttons/affordanceIcons.d.ts +2 -2
  103. package/dist/_dts/theme-core/src/buttons/core.d.ts +0 -1
  104. package/dist/_dts/theme-core/src/buttons/effectClassification.d.ts +22 -9
  105. package/dist/_dts/theme-core/src/buttons/effects/presets/background.d.ts +1 -2
  106. package/dist/_dts/theme-core/src/buttons/effects/presets/border.d.ts +1 -2
  107. package/dist/_dts/theme-core/src/buttons/effects/presets/overlay.d.ts +3 -1
  108. package/dist/_dts/theme-core/src/buttons/effects/presets/shadow.d.ts +2 -2
  109. package/dist/_dts/theme-core/src/buttons/effects/types.d.ts +8 -18
  110. package/dist/_dts/theme-core/src/buttons/generateButtonCss.d.ts +2 -2
  111. package/dist/_dts/theme-core/src/buttons/generateDefaultButtonSystem.d.ts +5 -5
  112. package/dist/_dts/theme-core/src/buttons/index.d.ts +1 -4
  113. package/dist/_dts/theme-core/src/buttons/parseThemeForPersistence.d.ts +36 -0
  114. package/dist/_dts/theme-core/src/buttons/types.d.ts +1638 -93
  115. package/dist/_dts/theme-core/src/cards/types.d.ts +13 -13
  116. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +969 -69
  117. package/dist/_dts/theme-core/src/navigation/types.d.ts +2 -2
  118. package/dist/_dts/theme-core/src/recommend/generateThemes.d.ts +0 -1
  119. package/dist/_dts/theme-core/src/schema.d.ts +1100 -116
  120. package/dist/_dts/theme-core/src/site-styles/backgroundAppearance.d.ts +2 -2
  121. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +14 -14
  122. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +2 -2
  123. package/dist/_dts/theme-core/src/site-styles/pageDesignIntent.d.ts +4 -4
  124. package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +6 -0
  125. package/dist/cli/index.mjs +5706 -4852
  126. package/dist/cli/init-docs/content/cli-reference.md +40 -1
  127. package/dist/cli/init-docs/content/content-management.md +6 -0
  128. package/dist/client/{DiscountCodeDisclosure-C4xvTBux.mjs → DiscountCodeDisclosure-DIWj-P9v.mjs} +1 -1
  129. package/dist/client/{EventCombined.client-Cr7G131j.mjs → EventCombined.client-De8YWBSr.mjs} +2 -2
  130. package/dist/client/{EventListing.client-CAjbseNy.mjs → EventListing.client-seHTjimY.mjs} +2 -2
  131. package/dist/client/{LeafletStaticMap-BMlWfwz3.mjs → LeafletStaticMap-BmQxFcJW.mjs} +1 -1
  132. package/dist/client/{MagicLinkForm-ChdFz96y.mjs → MagicLinkForm-OoJAIUIj.mjs} +1 -1
  133. package/dist/client/{StepIndicator-7D3GaBeM.mjs → StepIndicator-CiZ8kdPk.mjs} +2 -2
  134. package/dist/client/{accordion-D8JTs2Si.mjs → accordion-6lnN__XJ.mjs} +1 -1
  135. package/dist/client/{bookingForm-H3DCRBHs.mjs → bookingForm-5uyqhONU.mjs} +6 -6
  136. package/dist/client/bookings.mjs +2219 -527
  137. package/dist/client/{carousel-DMr_QWlz.mjs → carousel-Dtnifil_.mjs} +1 -1
  138. package/dist/client/{cart-DWMbfBdR.mjs → cart-5Tc5S7NV.mjs} +3 -3
  139. package/dist/client/{checkout-zmfdkXLx.mjs → checkout-CYKKN5Hr.mjs} +3 -3
  140. package/dist/client/client.mjs +2498 -2384
  141. package/dist/client/{colorTokenParts-FSS3al6l.mjs → colorStyles-BWpj_h4C.mjs} +88 -1
  142. package/dist/client/{courseRegistration-Z_mn18jy.mjs → courseRegistration-CxCBybB4.mjs} +6 -6
  143. package/dist/client/{eventCalendar-CYp0YJGF.mjs → eventCalendar-Xj2jZS9X.mjs} +4 -4
  144. package/dist/client/{eventCombined-rKyYZUa9.mjs → eventCombined-CgcMoOgM.mjs} +2 -2
  145. package/dist/client/{eventDetails-X0ZaUuMj.mjs → eventDetails-CEAH8Edc.mjs} +3 -3
  146. package/dist/client/{eventListing-W1SuL7EP.mjs → eventListing-qyPVQ0wP.mjs} +2 -2
  147. package/dist/client/{eventRegistration-ClWricui.mjs → eventRegistration-DDu79CtL.mjs} +6 -6
  148. package/dist/client/{form--eKPwAAD.mjs → form-Bc7JKI7I.mjs} +1 -1
  149. package/dist/client/{headerSection-BSeGr-FS.mjs → headerSection-vTku_LEl.mjs} +2 -2
  150. package/dist/client/hooks.mjs +373 -555
  151. package/dist/client/{locationMap-BpOrtNYn.mjs → locationMap-Bg7X4Jna.mjs} +3 -3
  152. package/dist/client/{media-DoF0dcVx.mjs → media-CNE1Z-wb.mjs} +1 -2
  153. package/dist/client/{newsletterForm-Bt7it7Dy.mjs → newsletterForm-lpylVdp5.mjs} +1 -1
  154. package/dist/client/{pricing-BTjSDDr4.mjs → pricing-DE8fjJiT.mjs} +1 -1
  155. package/dist/client/{productDetail-ICnVTcCg.mjs → productDetail-D7Bo2RMH.mjs} +3 -3
  156. package/dist/client/{productList-ZHr6D604.mjs → productList-VjnZqiqN.mjs} +3 -3
  157. package/dist/client/{propsDecoder-wWN0tBDF.mjs → propsDecoder-DreFXjNP.mjs} +2212 -621
  158. package/dist/client/rendering/client.mjs +21612 -21646
  159. package/dist/client/rendering/islands.mjs +20 -20
  160. package/dist/client/rendering.mjs +1571 -1547
  161. package/dist/client/{shop-C1u_BUn0.mjs → shop-IRsHHbf7.mjs} +5 -5
  162. package/dist/client/{shop-commerce-CleZdRha.mjs → shop-commerce-gnI8PieZ.mjs} +27 -10
  163. package/dist/client/{shop-commerce-store-ycnoB2Ce.mjs → shop-commerce-store-BTbrhObn.mjs} +1 -1
  164. package/dist/client/{shopShared-CPhByt2a.mjs → shopShared-D6KS2_qk.mjs} +3 -3
  165. package/dist/client/{useDiscountCodeUi-cStSQI6C.mjs → useDiscountCodeUi-DCccGCxV.mjs} +1 -1
  166. package/dist/client/{useEventFilters-DKPN7hSY.mjs → useEventFilters-eGs3rPZQ.mjs} +3 -104
  167. package/dist/client/{usePolling-BKAd6pkZ.mjs → usePolling-CvSU2sur.mjs} +2 -2
  168. package/dist/preview-next/before-render.mjs +57 -0
  169. package/dist/preview-next/client/runtime.mjs +4 -4
  170. package/dist/preview-next/middleware.mjs +57 -0
  171. package/dist/{previewAuthStore-BkT1D06A.mjs → previewAuthStore-DN7vcRsv.mjs} +57 -0
  172. package/dist/{sdk-runtime-hu3wr3ng.mjs → sdk-runtime-DaOiA7ct.mjs} +2 -2
  173. package/dist/{separator-Bln8izQ9.mjs → separator-CqRhcCxD.mjs} +1 -1
  174. package/dist/server/components.mjs +4092 -4108
  175. package/dist/server/config-template-validation.mjs +4 -23
  176. package/dist/server/config-validation.mjs +373 -555
  177. package/dist/server/config.mjs +410 -558
  178. package/dist/server/data.mjs +373 -555
  179. package/dist/server/index.mjs +58 -1
  180. package/dist/server/next.mjs +32958 -32926
  181. package/dist/server/page-converter.mjs +369 -526
  182. package/dist/server/prebuild.mjs +1 -1
  183. package/dist/server/rendering/server.mjs +5371 -3174
  184. package/dist/server/rendering.mjs +4099 -4124
  185. package/dist/server/routing.mjs +758 -966
  186. package/dist/server/server.mjs +448 -632
  187. package/dist/server/theme-bridge.mjs +472 -525
  188. package/dist/server/theme.mjs +1074 -392
  189. package/package.json +1 -1
  190. package/dist/_dts/blocks/src/primitives/content/components/Button.d.ts +0 -40
  191. package/dist/_dts/blocks/src/system/fragments/library/footerBottomText.d.ts +0 -19
  192. package/dist/_dts/media-core/src/legacyMediaValue.d.ts +0 -11
  193. package/dist/_dts/theme-core/src/styles/buttons.d.ts +0 -27
  194. package/dist/_dts/theme-core/src/styles/buttonsLibrary.d.ts +0 -2
  195. package/dist/_dts/theme-core/src/styles/glow.d.ts +0 -3
  196. package/dist/client/colorStyles-gcTh5yZT.mjs +0 -89
@@ -1,6 +1,346 @@
1
1
  import z, { z as z$1 } from "zod";
2
2
  import * as fs from "fs/promises";
3
3
  import * as path from "path";
4
+ //#region ../theme-core/src/palette/utils/colorConversion.ts
5
+ const clamp01 = (x) => Math.max(0, Math.min(1, x));
6
+ const mod = (a, n) => (a % n + n) % n;
7
+ const deg = (x) => mod(x, 360);
8
+ function hexToRgb(hex) {
9
+ const h = hex.replace(/^#/, "");
10
+ const m = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
11
+ if (m.length !== 6) throw new Error("Bad hex");
12
+ const int = parseInt(m, 16);
13
+ return {
14
+ r: int >> 16 & 255,
15
+ g: int >> 8 & 255,
16
+ b: int & 255
17
+ };
18
+ }
19
+ function rgbToHex(r, g, b) {
20
+ const to = (n) => Math.round(n).toString(16).padStart(2, "0");
21
+ return `#${to(r)}${to(g)}${to(b)}`;
22
+ }
23
+ function srgbToLinear(u) {
24
+ const x = u / 255;
25
+ return x <= .04045 ? x / 12.92 : Math.pow((x + .055) / 1.055, 2.4);
26
+ }
27
+ function linearToSrgb(x) {
28
+ return clamp01(x <= .0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - .055) * 255;
29
+ }
30
+ function rgbToHsl(r, g, b) {
31
+ const R = r / 255, G = g / 255, B = b / 255;
32
+ const max = Math.max(R, G, B), min = Math.min(R, G, B);
33
+ const d = max - min;
34
+ let h = 0;
35
+ if (d !== 0) {
36
+ switch (max) {
37
+ case R:
38
+ h = (G - B) / d + (G < B ? 6 : 0);
39
+ break;
40
+ case G:
41
+ h = (B - R) / d + 2;
42
+ break;
43
+ case B:
44
+ h = (R - G) / d + 4;
45
+ break;
46
+ }
47
+ h *= 60;
48
+ }
49
+ const l = (max + min) / 2;
50
+ const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
51
+ return {
52
+ h,
53
+ s,
54
+ l
55
+ };
56
+ }
57
+ function srgbToOklab(r, g, b) {
58
+ const R = srgbToLinear(r), G = srgbToLinear(g), B = srgbToLinear(b);
59
+ const l = .4122214708 * R + .5363325363 * G + .0514459929 * B;
60
+ const m = .2119034982 * R + .6806995451 * G + .1073969566 * B;
61
+ const s = .0883024619 * R + .2817188376 * G + .6299787005 * B;
62
+ const l_ = Math.cbrt(l), m_ = Math.cbrt(m), s_ = Math.cbrt(s);
63
+ return {
64
+ L: .2104542553 * l_ + .793617785 * m_ - .0040720468 * s_,
65
+ a: 1.9779984951 * l_ - 2.428592205 * m_ + .4505937099 * s_,
66
+ b: .0259040371 * l_ + .7827717662 * m_ - .808675766 * s_
67
+ };
68
+ }
69
+ function oklabToSrgb(L, a, b) {
70
+ const l_ = Math.pow(L + .3963377774 * a + .2158037573 * b, 3);
71
+ const m_ = Math.pow(L - .1055613458 * a - .0638541728 * b, 3);
72
+ const s_ = Math.pow(L - .0894841775 * a - 1.291485548 * b, 3);
73
+ const R = 4.0767416621 * l_ - 3.3077115913 * m_ + .2309699292 * s_;
74
+ const G = -1.2684380046 * l_ + 2.6097574011 * m_ - .3413193965 * s_;
75
+ const B = -.0041960863 * l_ - .7034186147 * m_ + 1.707614701 * s_;
76
+ return {
77
+ r: linearToSrgb(R),
78
+ g: linearToSrgb(G),
79
+ b: linearToSrgb(B)
80
+ };
81
+ }
82
+ function oklabToOklch(L, a, b) {
83
+ const C = Math.sqrt(a * a + b * b);
84
+ return {
85
+ L,
86
+ C,
87
+ h: C < 1e-7 ? 0 : deg(Math.atan2(b, a) * 180 / Math.PI)
88
+ };
89
+ }
90
+ function oklchToOklab(L, C, h) {
91
+ const hr = h * Math.PI / 180;
92
+ return {
93
+ L,
94
+ a: C * Math.cos(hr),
95
+ b: C * Math.sin(hr)
96
+ };
97
+ }
98
+ function inSRGB(r, g, b) {
99
+ return r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255;
100
+ }
101
+ function oklchToHexGamut(L, C, h) {
102
+ let lo = 0, hi = C, best = 0;
103
+ for (let i = 0; i < 18; i++) {
104
+ const mid = (lo + hi) / 2;
105
+ const { a, b } = oklchToOklab(L, mid, h);
106
+ const { r, g, b: bb } = oklabToSrgb(L, a, b);
107
+ if (inSRGB(r, g, bb)) {
108
+ best = mid;
109
+ lo = mid;
110
+ } else hi = mid;
111
+ }
112
+ const { a, b } = oklchToOklab(L, best, h);
113
+ const { r, g, b: bb } = oklabToSrgb(L, a, b);
114
+ return rgbToHex(r, g, bb);
115
+ }
116
+ function featuresFromHex(hex) {
117
+ const { r, g, b } = hexToRgb(hex);
118
+ const { h, s, l } = rgbToHsl(r, g, b);
119
+ const { L, a, b: b2 } = srgbToOklab(r, g, b);
120
+ const { L: okL, C: okC, h: okH } = oklabToOklch(L, a, b2);
121
+ return {
122
+ r,
123
+ g,
124
+ b,
125
+ h,
126
+ s,
127
+ l,
128
+ v: Math.max(r, g, b) / 255,
129
+ Y: .2126 * srgbToLinear(r) + .7152 * srgbToLinear(g) + .0722 * srgbToLinear(b),
130
+ chroma: s,
131
+ okL,
132
+ okC,
133
+ okH,
134
+ hex
135
+ };
136
+ }
137
+ //#endregion
138
+ //#region ../theme-core/src/palette/neutralScale.ts
139
+ function generateNeutralScale({ background, steps = 11, warm = false }) {
140
+ const isDark = background === "#000000" || background.startsWith("#0") || background.startsWith("#1");
141
+ const shadeNames = [
142
+ 50,
143
+ 100,
144
+ 200,
145
+ 300,
146
+ 400,
147
+ 500,
148
+ 600,
149
+ 700,
150
+ 800,
151
+ 900,
152
+ 950
153
+ ];
154
+ const count = Math.min(steps, shadeNames.length);
155
+ const scale = [];
156
+ for (let i = 0; i < count; i++) {
157
+ const shadeName = shadeNames[i] ?? shadeNames[shadeNames.length - 1] ?? 950;
158
+ const t = i / (count - 1);
159
+ const hex = oklchToHexGamut(isDark ? mix(.06, .92, t) : mix(.98, .1, t), .02, warm ? 85 : 250);
160
+ const name = `neutral-${shadeName}`;
161
+ scale.push({
162
+ name,
163
+ hex
164
+ });
165
+ }
166
+ return scale;
167
+ }
168
+ function mix(a, b, t) {
169
+ return a + (b - a) * Math.max(0, Math.min(1, t));
170
+ }
171
+ //#endregion
172
+ //#region ../theme-core/src/palette/utils/contrast.ts
173
+ function relativeLuminance(r, g, b) {
174
+ const R = srgbToLinear(r), G = srgbToLinear(g), B = srgbToLinear(b);
175
+ return .2126 * R + .7152 * G + .0722 * B;
176
+ }
177
+ function contrastRatio(hex1, hex2) {
178
+ const { r: r1, g: g1, b: b1 } = hexToRgb(hex1);
179
+ const { r: r2, g: g2, b: b2 } = hexToRgb(hex2);
180
+ const L1 = relativeLuminance(r1, g1, b1);
181
+ const L2 = relativeLuminance(r2, g2, b2);
182
+ const [hi, lo] = L1 >= L2 ? [L1, L2] : [L2, L1];
183
+ return (hi + .05) / (lo + .05);
184
+ }
185
+ //#endregion
186
+ //#region ../theme-core/src/palette/utils/textColor.ts
187
+ /**
188
+ * Pick the better of black/white text for a given background using actual
189
+ * contrast ratios, not a simple luminance cutoff.
190
+ *
191
+ * @param background - Background color in hex or rgb/rgba format (e.g., '#FFFFFF' or 'rgb(255, 255, 255)')
192
+ * @param minContrast - Preferred minimum contrast ratio for body-sized text
193
+ * @returns '#000000' for light backgrounds, '#FFFFFF' for dark backgrounds
194
+ */
195
+ function pickBlackOrWhiteTextColor(background, minContrast = 4.5) {
196
+ const { r, g, b } = parseColorToRgb(background);
197
+ const luminance = relativeLuminance(r, g, b);
198
+ const blackContrast = (luminance + .05) / .05;
199
+ const whiteContrast = 1.05 / (luminance + .05);
200
+ if (blackContrast >= minContrast && whiteContrast < minContrast) return "#000000";
201
+ if (whiteContrast >= minContrast && blackContrast < minContrast) return "#FFFFFF";
202
+ return blackContrast >= whiteContrast ? "#000000" : "#FFFFFF";
203
+ }
204
+ /**
205
+ * Backwards-compatible alias for callers that want the best black/white text
206
+ * color for a background.
207
+ */
208
+ function getTextColorForBackground(background) {
209
+ return pickBlackOrWhiteTextColor(background);
210
+ }
211
+ function parseColorToRgb(input) {
212
+ const trimmed = input.trim();
213
+ if (trimmed.startsWith("#")) return hexToRgb(trimmed);
214
+ const rgbMatch = trimmed.match(/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i);
215
+ if (rgbMatch?.[1] && rgbMatch[2] && rgbMatch[3]) return {
216
+ r: clamp255(Number(rgbMatch[1])),
217
+ g: clamp255(Number(rgbMatch[2])),
218
+ b: clamp255(Number(rgbMatch[3]))
219
+ };
220
+ throw new Error("Unsupported color format");
221
+ }
222
+ function clamp255(value) {
223
+ if (Number.isNaN(value)) return 0;
224
+ return Math.max(0, Math.min(255, value));
225
+ }
226
+ //#endregion
227
+ //#region ../theme-core/src/palette/utils/onColor.ts
228
+ const BACKGROUND_ROLE_NAMES = /* @__PURE__ */ new Set([
229
+ "primary",
230
+ "secondary",
231
+ "accent",
232
+ "success",
233
+ "warning",
234
+ "destructive",
235
+ "info",
236
+ "background",
237
+ "surface",
238
+ "surfaceAlt",
239
+ "muted",
240
+ "neutral"
241
+ ]);
242
+ function onColorTokenName(backgroundToken) {
243
+ return `on${backgroundToken.split("-").filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("")}`;
244
+ }
245
+ function isOnColorBackgroundCandidateToken(token) {
246
+ const role = token.match(/^(.*?)(?:-\d{2,3})?$/)?.[1] ?? token;
247
+ return BACKGROUND_ROLE_NAMES.has(role);
248
+ }
249
+ //#endregion
250
+ //#region ../theme-core/src/palette/generateShades.ts
251
+ function generateShades(hexColor, name) {
252
+ const shadeSteps = [
253
+ 50,
254
+ 100,
255
+ 200,
256
+ 300,
257
+ 400,
258
+ 500,
259
+ 600,
260
+ 700,
261
+ 800,
262
+ 900,
263
+ 950
264
+ ];
265
+ const base = featuresFromHex(hexColor);
266
+ const hue = base.okH;
267
+ const baseChroma = base.okC;
268
+ const toneMap = {
269
+ 50: .975,
270
+ 100: .945,
271
+ 200: .89,
272
+ 300: .82,
273
+ 400: .74,
274
+ 500: .66,
275
+ 600: .58,
276
+ 700: .49,
277
+ 800: .4,
278
+ 900: .28,
279
+ 950: .16
280
+ };
281
+ const shadeDefs = shadeSteps.map((step, idx) => {
282
+ const t = idx / (shadeSteps.length - 1);
283
+ const L = toneMap[step];
284
+ const C = baseChroma * (.06 + .94 * Math.sin(Math.PI * t));
285
+ return {
286
+ name: `${name}-${step}`,
287
+ hex: oklchToHexGamut(L, C, hue)
288
+ };
289
+ });
290
+ shadeDefs.push({
291
+ name,
292
+ hex: hexColor
293
+ });
294
+ return shadeDefs;
295
+ }
296
+ //#endregion
297
+ //#region ../theme-core/src/palette/expandPalette.ts
298
+ function colorsToMap(palette) {
299
+ const map = {};
300
+ for (const c of palette.colors) map[c.name] = c.hex;
301
+ return map;
302
+ }
303
+ function isBackgroundDark(hex) {
304
+ try {
305
+ return featuresFromHex(hex).Y < .5;
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+ const nonShadeablePaletteRoleNames = /* @__PURE__ */ new Set([
311
+ "success",
312
+ "warning",
313
+ "destructive"
314
+ ]);
315
+ function expandPalette(palette) {
316
+ const base = colorsToMap(palette);
317
+ const expanded = { ...base };
318
+ generateNeutralScale({
319
+ background: base.background || base.surface || "#ffffff",
320
+ steps: 11,
321
+ warm: false
322
+ }).forEach((n) => {
323
+ expanded[n.name] = n.hex;
324
+ });
325
+ for (const [name, hex] of Object.entries(base)) {
326
+ if (nonShadeablePaletteRoleNames.has(name)) continue;
327
+ if (hex) generateShades(hex, name).forEach((shade) => {
328
+ expanded[shade.name] = shade.hex;
329
+ });
330
+ }
331
+ if (base.text) expanded.body = base.text;
332
+ if (base.danger && !base.destructive) expanded.destructive = base.danger;
333
+ const isDark = base.background ? isBackgroundDark(base.background) : false;
334
+ expanded.mutedText = palette.meta?.mode === "dark" || isDark ? expanded["neutral-600"] ?? "#666" : expanded["neutral-500"] ?? "#888";
335
+ for (const [token, hex] of Object.entries({ ...expanded })) {
336
+ if (!hex || !isOnColorBackgroundCandidateToken(token)) continue;
337
+ const onToken = onColorTokenName(token);
338
+ if (expanded[onToken]) continue;
339
+ expanded[onToken] = getTextColorForBackground(hex);
340
+ }
341
+ return expanded;
342
+ }
343
+ //#endregion
4
344
  //#region ../theme-core/src/tokens/colorTokenParts.ts
5
345
  function parseOpacityPercent(source) {
6
346
  const trimmed = source.trim();
@@ -195,111 +535,6 @@ const mediaSchema = z.object({
195
535
  transform: transformSchema.optional()
196
536
  });
197
537
  //#endregion
198
- //#region ../theme-core/src/buttons/effectClassification.ts
199
- /**
200
- * Exhaustive live policy for every registered effect id.
201
- *
202
- * The resolver consumes the transition payload directly, so adding an effect
203
- * cannot update classification without also defining its runtime behavior.
204
- */
205
- const BUTTON_EFFECT_POLICIES = {
206
- "darken-background": {
207
- classification: "colour-transition",
208
- transition: {
209
- kind: "adjust-shade",
210
- direction: "darker"
211
- }
212
- },
213
- "lighten-background": {
214
- classification: "colour-transition",
215
- transition: {
216
- kind: "adjust-shade",
217
- direction: "lighter"
218
- }
219
- },
220
- "background-on-hover": {
221
- classification: "colour-transition",
222
- transition: { kind: "replace-background" }
223
- },
224
- "background-on-hover-alpha": {
225
- classification: "unsupported-colour",
226
- reason: "alpha_requires_surface"
227
- },
228
- "fade-background": {
229
- classification: "unsupported-colour",
230
- reason: "changes_element_opacity"
231
- },
232
- "opacity-reduce": {
233
- classification: "unsupported-colour",
234
- reason: "changes_element_opacity"
235
- },
236
- "gradient-text": {
237
- classification: "unsupported-colour",
238
- reason: "changes_foreground"
239
- },
240
- "text-color-change": {
241
- classification: "unsupported-colour",
242
- reason: "changes_foreground"
243
- },
244
- "border-color-change": {
245
- classification: "unsupported-colour",
246
- reason: "changes_border"
247
- },
248
- "aurora-glow": {
249
- classification: "unsupported-background-overlay",
250
- reason: "unresolved_background_overlay"
251
- },
252
- "cosmic-stardust": {
253
- classification: "unsupported-background-overlay",
254
- reason: "unresolved_background_overlay"
255
- },
256
- "gradient-border-fill": {
257
- classification: "unsupported-background-overlay",
258
- reason: "unresolved_background_overlay"
259
- },
260
- "animated-gradient-border-fill": {
261
- classification: "unsupported-background-overlay",
262
- reason: "unresolved_background_overlay"
263
- },
264
- "nav-underline": {
265
- classification: "not-button",
266
- reason: "not_button_effect"
267
- },
268
- "nav-frosted-base": {
269
- classification: "not-button",
270
- reason: "not_button_effect"
271
- },
272
- "nav-frosted-hover": {
273
- classification: "not-button",
274
- reason: "not_button_effect"
275
- },
276
- "nav-frosted-active": {
277
- classification: "not-button",
278
- reason: "not_button_effect"
279
- },
280
- "animated-gradient": { classification: "non-colour" },
281
- "border-glow": { classification: "non-colour" },
282
- "drop-shadow": { classification: "non-colour" },
283
- "inner-shadow": { classification: "non-colour" },
284
- "neumorphic-shadow": { classification: "non-colour" },
285
- "outer-glow": { classification: "non-colour" },
286
- "retro-3d-shadow": { classification: "non-colour" },
287
- "scale-up": { classification: "non-colour" },
288
- "scale-down": { classification: "non-colour" },
289
- lift: { classification: "non-colour" },
290
- "press-down": { classification: "non-colour" },
291
- pop: { classification: "non-colour" },
292
- "border-bottom-grow": { classification: "non-colour" },
293
- "sketchy-border": { classification: "non-colour" },
294
- "pulse-animation": { classification: "non-colour" }
295
- };
296
- function getButtonEffectPolicy(effectId) {
297
- return Object.prototype.hasOwnProperty.call(BUTTON_EFFECT_POLICIES, effectId) ? BUTTON_EFFECT_POLICIES[effectId] : null;
298
- }
299
- function classifyButtonEffectId(effectId) {
300
- return getButtonEffectPolicy(effectId)?.classification ?? "unknown";
301
- }
302
- //#endregion
303
538
  //#region ../theme-core/src/buttons/types.ts
304
539
  /**
305
540
  * Standard semantic variant roles a theme exposes.
@@ -316,6 +551,13 @@ const VARIANT_ROLES = [
316
551
  "tertiary",
317
552
  "accent"
318
553
  ];
554
+ /**
555
+ * Canonical persisted/CSS-safe button variant identifier.
556
+ *
557
+ * IDs become class selectors, so keep the contract deliberately narrower than
558
+ * an arbitrary string and shared with every authoring/runtime boundary.
559
+ */
560
+ const buttonVariantIdSchema = z$1.string().min(1).max(50).regex(/^[a-z0-9-]+$/, "Button variant IDs may contain lowercase letters, numbers, and hyphens only");
319
561
  function parseOpaqueButtonColourToken(value) {
320
562
  const token = parseThemeColorToken(value);
321
563
  if (token === null) return null;
@@ -330,6 +572,109 @@ const opaqueButtonColourTokenSchema = themeColorTokenSchema.refine((value) => pa
330
572
  if (token === null) throw new Error("Invariant: opaque button token passed its schema");
331
573
  return token;
332
574
  });
575
+ const buttonShadeAmountSchema = z$1.number().finite().min(0).max(900).multipleOf(100);
576
+ const buttonShadowIntensitySchema = z$1.enum([
577
+ "subtle",
578
+ "medium",
579
+ "strong"
580
+ ]);
581
+ /**
582
+ * The complete persisted button-effect contract.
583
+ *
584
+ * Navigation has its own generic interactive-effect model. Button variants use
585
+ * this closed union so unsupported effect IDs, unknown option keys, and unsafe
586
+ * CSS values cannot become representable after the schema boundary.
587
+ */
588
+ const buttonEffectApplicationSchema = z$1.discriminatedUnion("effectId", [
589
+ z$1.strictObject({
590
+ effectId: z$1.literal("darken-background"),
591
+ options: z$1.strictObject({ amount: buttonShadeAmountSchema.optional() }).optional()
592
+ }),
593
+ z$1.strictObject({
594
+ effectId: z$1.literal("lighten-background"),
595
+ options: z$1.strictObject({ amount: buttonShadeAmountSchema.optional() }).optional()
596
+ }),
597
+ z$1.strictObject({
598
+ effectId: z$1.literal("background-on-hover"),
599
+ options: z$1.strictObject({ colorToken: opaqueButtonColourTokenSchema.optional() }).optional()
600
+ }),
601
+ z$1.strictObject({
602
+ effectId: z$1.literal("animated-gradient"),
603
+ options: z$1.strictObject({ direction: z$1.enum(["horizontal", "vertical"]).optional() }).optional()
604
+ }),
605
+ z$1.strictObject({
606
+ effectId: z$1.literal("drop-shadow"),
607
+ options: z$1.strictObject({ intensity: buttonShadowIntensitySchema.optional() }).optional()
608
+ }),
609
+ z$1.strictObject({
610
+ effectId: z$1.literal("inner-shadow"),
611
+ options: z$1.strictObject({}).optional()
612
+ }),
613
+ z$1.strictObject({
614
+ effectId: z$1.literal("neumorphic-shadow"),
615
+ options: z$1.strictObject({
616
+ mode: z$1.enum(["raised", "pressed"]).optional(),
617
+ intensity: buttonShadowIntensitySchema.optional()
618
+ }).optional()
619
+ }),
620
+ z$1.strictObject({
621
+ effectId: z$1.literal("retro-3d-shadow"),
622
+ options: z$1.strictObject({}).optional()
623
+ }),
624
+ z$1.strictObject({
625
+ effectId: z$1.literal("scale-up"),
626
+ options: z$1.strictObject({ scale: z$1.enum([
627
+ "1.02",
628
+ "1.03",
629
+ "1.05",
630
+ "1.10"
631
+ ]).optional() }).optional()
632
+ }),
633
+ z$1.strictObject({
634
+ effectId: z$1.literal("scale-down"),
635
+ options: z$1.strictObject({ scale: z$1.enum([
636
+ "0.90",
637
+ "0.95",
638
+ "0.97",
639
+ "0.98"
640
+ ]).optional() }).optional()
641
+ }),
642
+ z$1.strictObject({
643
+ effectId: z$1.literal("lift"),
644
+ options: z$1.strictObject({ amount: z$1.enum([
645
+ "0.25",
646
+ "0.5",
647
+ "1"
648
+ ]).optional() }).optional()
649
+ }),
650
+ z$1.strictObject({
651
+ effectId: z$1.literal("press-down"),
652
+ options: z$1.strictObject({}).optional()
653
+ }),
654
+ z$1.strictObject({
655
+ effectId: z$1.literal("pop"),
656
+ options: z$1.strictObject({ intensity: z$1.enum([
657
+ "subtle",
658
+ "medium",
659
+ "dramatic"
660
+ ]).optional() }).optional()
661
+ }),
662
+ z$1.strictObject({
663
+ effectId: z$1.literal("border-bottom-grow"),
664
+ options: z$1.strictObject({
665
+ height: z$1.union([z$1.literal(1), z$1.literal(2)]).optional(),
666
+ origin: z$1.enum([
667
+ "left",
668
+ "center",
669
+ "right"
670
+ ]).optional()
671
+ }).optional()
672
+ }),
673
+ z$1.strictObject({
674
+ effectId: z$1.literal("sketchy-border"),
675
+ options: z$1.strictObject({ intensity: buttonShadowIntensitySchema.optional() }).optional()
676
+ })
677
+ ]);
333
678
  const cornerStyleSchema = z$1.enum([
334
679
  "square",
335
680
  "rounded",
@@ -364,13 +709,7 @@ const hoverTransformSchema = z$1.enum([
364
709
  "scale",
365
710
  "press"
366
711
  ]);
367
- const hoverColorSchema = z$1.enum([
368
- "none",
369
- "brighten",
370
- "darken",
371
- "saturate",
372
- "token"
373
- ]);
712
+ z$1.enum(["none", "token"]);
374
713
  const buttonPaddingPresetSchema = z$1.enum([
375
714
  "compact",
376
715
  "default",
@@ -431,7 +770,7 @@ const buttonSizesSchema = z$1.object({
431
770
  lg,
432
771
  xl: xl ?? lg
433
772
  }));
434
- const canonicalButtonGlobalSettingsSchema = z$1.object({
773
+ const buttonGlobalSettingsBaseSchema = z$1.strictObject({
435
774
  cornerStyle: cornerStyleSchema,
436
775
  cornerRadius: z$1.number().min(0).max(24).optional(),
437
776
  /**
@@ -444,30 +783,23 @@ const canonicalButtonGlobalSettingsSchema = z$1.object({
444
783
  shadow: shadowSizeSchema.optional(),
445
784
  textTransform: textTransformSchema,
446
785
  fontWeight: fontWeightSchema,
447
- /**
448
- * @deprecated Set per-variant via `variant.border.widthClass` instead.
449
- * Schema field is preserved so existing presets parse; the value is unread
450
- * by the current generator and will be removed once the legacy
451
- * `primitives/content/components/Button.tsx` slot exports are deleted.
452
- */
453
- borderWidth: z$1.union([
454
- z$1.literal(1),
455
- z$1.literal(2),
456
- z$1.literal(3)
457
- ]),
458
786
  typography: buttonTypographySchema.optional(),
459
787
  fontSize: z$1.string().optional(),
460
788
  letterSpacing: letterSpacingSchema.optional(),
461
789
  italic: z$1.boolean().optional(),
462
790
  paddingPreset: buttonPaddingPresetSchema.optional(),
463
791
  hoverTransform: hoverTransformSchema,
464
- hoverColor: hoverColorSchema,
465
- hoverColorToken: opaqueButtonColourTokenSchema.optional(),
466
792
  gradientStyle: gradientStyleSchema.optional(),
467
793
  gradientAngle: z$1.number().min(0).max(360).optional(),
468
794
  gradientSharpness: gradientSharpnessSchema.optional()
469
795
  });
470
- const buttonGlobalSettingsSchema = z$1.preprocess(normalizeLegacyButtonGlobalInput, canonicalButtonGlobalSettingsSchema);
796
+ const buttonGlobalSettingsSchema = z$1.discriminatedUnion("hoverColor", [buttonGlobalSettingsBaseSchema.extend({
797
+ hoverColor: z$1.literal("none"),
798
+ hoverColorToken: z$1.never().optional()
799
+ }), buttonGlobalSettingsBaseSchema.extend({
800
+ hoverColor: z$1.literal("token"),
801
+ hoverColorToken: opaqueButtonColourTokenSchema
802
+ })]);
471
803
  const gradientDirectionSchema$1 = z$1.enum([
472
804
  "to-r",
473
805
  "to-l",
@@ -479,11 +811,11 @@ const gradientDirectionSchema$1 = z$1.enum([
479
811
  "to-bl"
480
812
  ]);
481
813
  const buttonColourTreatmentSchema = z$1.discriminatedUnion("type", [
482
- z$1.object({
814
+ z$1.strictObject({
483
815
  type: z$1.literal("solid"),
484
816
  colorToken: opaqueButtonColourTokenSchema
485
817
  }),
486
- z$1.object({
818
+ z$1.strictObject({
487
819
  type: z$1.literal("gradient"),
488
820
  stops: z$1.array(opaqueButtonColourTokenSchema).min(2).transform((stops) => {
489
821
  const [first, second, ...rest] = stops;
@@ -497,19 +829,15 @@ const buttonColourTreatmentSchema = z$1.discriminatedUnion("type", [
497
829
  direction: gradientDirectionSchema$1,
498
830
  foregroundToken: opaqueButtonColourTokenSchema
499
831
  }),
500
- z$1.object({
832
+ z$1.strictObject({
501
833
  type: z$1.literal("surface"),
502
834
  role: z$1.literal("link")
503
835
  })
504
836
  ]);
505
837
  const buttonBackgroundSchema = buttonColourTreatmentSchema;
506
- const effectApplicationSchema$1 = z$1.object({
507
- effectId: z$1.string(),
508
- options: z$1.record(z$1.string(), z$1.json()).optional()
509
- });
510
- const buttonBorderSchema = z$1.object({ widthClass: z$1.string() });
511
- const buttonVariantAffordanceSchema = z$1.object({ iconId: z$1.string().min(1) });
512
- const variantShadowSchema = z$1.object({
838
+ const buttonBorderSchema = z$1.strictObject({ widthClass: z$1.string() });
839
+ const buttonVariantAffordanceSchema = z$1.strictObject({ iconId: z$1.string().min(1) });
840
+ const variantShadowSchema = z$1.strictObject({
513
841
  elevation: z$1.enum([
514
842
  "none",
515
843
  "low",
@@ -522,13 +850,13 @@ const variantShadowSchema = z$1.object({
522
850
  "hard"
523
851
  ]).nullable(),
524
852
  position: z$1.enum(["bottom", "bottom-right"]).default("bottom"),
525
- colorToken: themeColorTokenSchema.optional()
853
+ colorToken: opaqueButtonColourTokenSchema.optional()
526
854
  });
527
- const variantEffectsSchema = z$1.object({
528
- base: z$1.array(effectApplicationSchema$1).optional(),
529
- hover: z$1.array(effectApplicationSchema$1).optional(),
530
- active: z$1.array(effectApplicationSchema$1).optional(),
531
- focus: z$1.array(effectApplicationSchema$1).optional()
855
+ const variantEffectsSchema = z$1.strictObject({
856
+ base: z$1.array(buttonEffectApplicationSchema).optional(),
857
+ hover: z$1.array(buttonEffectApplicationSchema).optional(),
858
+ active: z$1.array(buttonEffectApplicationSchema).optional(),
859
+ focus: z$1.array(buttonEffectApplicationSchema).optional()
532
860
  });
533
861
  /**
534
862
  * Per-variant size overrides schema — every entry is optional.
@@ -549,8 +877,8 @@ const variantSizeOverridesSchema = z$1.object({
549
877
  * default (e.g. link-style variants declaring zero horizontal padding at
550
878
  * every size).
551
879
  */
552
- const canonicalButtonVariantSchema = z$1.object({
553
- id: z$1.string(),
880
+ const buttonVariantSchema = z$1.strictObject({
881
+ id: buttonVariantIdSchema,
554
882
  name: z$1.string().min(1).max(20),
555
883
  enabled: z$1.boolean(),
556
884
  priority: prioritySchema,
@@ -564,154 +892,585 @@ const canonicalButtonVariantSchema = z$1.object({
564
892
  sizes: variantSizeOverridesSchema.optional(),
565
893
  affordance: buttonVariantAffordanceSchema.optional()
566
894
  });
567
- /**
568
- * Temporary persisted-theme admission boundary for issue #1206.
569
- *
570
- * Production inventory on 2026-07-22 found 22 rows using the old independent
571
- * `textColorToken`/border colour shape. New source and all downstream code use
572
- * only the canonical relationship. Delete this preprocessor after the reviewed
573
- * production rewrite reports zero legacy rows.
574
- */
575
- const buttonVariantSchema = z$1.preprocess(normalizeLegacyButtonVariantInput, canonicalButtonVariantSchema);
576
- const buttonSystemSchema = z$1.object({
895
+ const buttonSystemSchema = z$1.strictObject({
577
896
  global: buttonGlobalSettingsSchema,
578
897
  variants: z$1.array(buttonVariantSchema).min(1).max(6),
579
898
  sizes: buttonSizesSchema.optional()
899
+ }).superRefine((system, ctx) => {
900
+ const seen = /* @__PURE__ */ new Set();
901
+ system.variants.forEach((variant, index) => {
902
+ if (seen.has(variant.id)) ctx.addIssue({
903
+ code: "custom",
904
+ path: [
905
+ "variants",
906
+ index,
907
+ "id"
908
+ ],
909
+ message: `Duplicate button variant ID: ${variant.id}`
910
+ });
911
+ seen.add(variant.id);
912
+ });
580
913
  });
581
- function normalizeLegacyButtonVariantInput(input) {
582
- if (!isUnknownRecord(input)) return input;
583
- const background = input.background;
584
- if (!isUnknownRecord(background)) return input;
585
- if (!(background.type === "transparent" || Object.prototype.hasOwnProperty.call(input, "textColorToken") || isUnknownRecord(input.border) && Object.prototype.hasOwnProperty.call(input.border, "colorToken") || hasLegacyButtonEffects(input.effects) || hasLegacyAlphaToken(input, background))) return input;
586
- const { textColorToken, ...withoutLegacyText } = input;
587
- const normalizedBorder = isUnknownRecord(input.border) && typeof input.border.widthClass === "string" ? { widthClass: input.border.widthClass } : input.border;
588
- const normalizedAffordance = isUnknownRecord(input.affordance) && typeof input.affordance.iconId === "string" ? { iconId: input.affordance.iconId } : input.affordance;
589
- const normalizedEffects = normalizeLegacyButtonEffects(input.effects, background.type);
590
- const hoverBackgroundToken = stripLegacyTokenAlpha(input.hoverBackgroundToken);
591
- if (background.type === "transparent") return {
592
- ...withoutLegacyText,
593
- background: {
594
- type: "surface",
595
- role: "link"
596
- },
597
- border: normalizedBorder,
598
- affordance: normalizedAffordance,
599
- effects: normalizedEffects,
600
- hoverBackgroundToken
601
- };
602
- if (background.type === "gradient") {
603
- const stops = Array.isArray(background.stops) ? background.stops.map(stripLegacyTokenAlpha) : background.stops;
604
- return {
605
- ...withoutLegacyText,
606
- background: {
607
- ...background,
608
- stops,
609
- foregroundToken: stripLegacyTokenAlpha(background.foregroundToken ?? textColorToken)
610
- },
611
- border: normalizedBorder,
612
- affordance: normalizedAffordance,
613
- effects: normalizedEffects,
614
- hoverBackgroundToken
615
- };
914
+ //#endregion
915
+ //#region ../theme-core/src/surfaces/foregroundContract.ts
916
+ const SURFACE_FOREGROUND_CSS_VARS = {
917
+ text: "--rb-surface-text-color",
918
+ heading: "--rb-surface-heading-color",
919
+ link: "--rb-surface-link-color",
920
+ supporting: "--rb-surface-supporting-color"
921
+ };
922
+ const DEFAULT_SURFACE_TEXT_COLOR = "rgb(var(--tb-text, 17 24 39))";
923
+ const DEFAULT_ROLE_FALLBACKS = {
924
+ text: DEFAULT_SURFACE_TEXT_COLOR,
925
+ heading: DEFAULT_SURFACE_TEXT_COLOR,
926
+ link: "rgb(var(--tb-primary, var(--tb-text, 17 24 39)))",
927
+ supporting: "rgb(var(--tb-mutedText, 100 116 139))"
928
+ };
929
+ function requireSurfaceColorValue(role, value) {
930
+ const normalized = value.trim();
931
+ if (normalized.length === 0) throw new RangeError(`Surface foreground ${role} must be a non-empty CSS color.`);
932
+ return normalized;
933
+ }
934
+ function surfaceForegroundValue(role, outsideProviderFallback = DEFAULT_ROLE_FALLBACKS[role]) {
935
+ const roleVariable = SURFACE_FOREGROUND_CSS_VARS[role];
936
+ const fallback = requireSurfaceColorValue("fallback", outsideProviderFallback);
937
+ if (role === "text") return `var(${roleVariable}, ${fallback})`;
938
+ return `var(${roleVariable}, var(${SURFACE_FOREGROUND_CSS_VARS.text}, ${fallback}))`;
939
+ }
940
+ //#endregion
941
+ //#region ../theme-core/src/buttons/constants.ts
942
+ /**
943
+ * Shared constants for button CSS generation
944
+ *
945
+ * These mappings convert Tailwind-style tokens to actual CSS values.
946
+ * Centralized here to eliminate duplication across multiple files.
947
+ */
948
+ /**
949
+ * Map Tailwind-style gradient direction tokens to CSS gradient directions
950
+ */
951
+ const GRADIENT_DIRECTION_MAP = {
952
+ "to-r": "to right",
953
+ "to-l": "to left",
954
+ "to-t": "to top",
955
+ "to-b": "to bottom",
956
+ "to-br": "to bottom right",
957
+ "to-bl": "to bottom left",
958
+ "to-tr": "to top right",
959
+ "to-tl": "to top left"
960
+ };
961
+ //#endregion
962
+ //#region ../theme-core/src/buttons/effectClassification.ts
963
+ /**
964
+ * Exhaustive live policy for every registered effect id.
965
+ *
966
+ * The resolver consumes the transition payload directly, so adding an effect
967
+ * cannot update classification without also defining its runtime behavior.
968
+ */
969
+ const BUTTON_EFFECT_POLICIES = {
970
+ "darken-background": {
971
+ classification: "colour-transition",
972
+ transition: {
973
+ kind: "adjust-shade",
974
+ direction: "darker"
975
+ }
976
+ },
977
+ "lighten-background": {
978
+ classification: "colour-transition",
979
+ transition: {
980
+ kind: "adjust-shade",
981
+ direction: "lighter"
982
+ }
983
+ },
984
+ "background-on-hover": {
985
+ classification: "colour-transition",
986
+ transition: { kind: "replace-background" }
987
+ },
988
+ "background-on-hover-alpha": {
989
+ classification: "unsupported-colour",
990
+ reason: "alpha_requires_surface"
991
+ },
992
+ "fade-background": {
993
+ classification: "unsupported-colour",
994
+ reason: "changes_element_opacity"
995
+ },
996
+ "opacity-reduce": {
997
+ classification: "unsupported-colour",
998
+ reason: "changes_element_opacity"
999
+ },
1000
+ "gradient-text": {
1001
+ classification: "unsupported-colour",
1002
+ reason: "changes_foreground"
1003
+ },
1004
+ "text-color-change": {
1005
+ classification: "unsupported-colour",
1006
+ reason: "changes_foreground"
1007
+ },
1008
+ "border-color-change": {
1009
+ classification: "unsupported-colour",
1010
+ reason: "changes_border"
1011
+ },
1012
+ "aurora-glow": {
1013
+ classification: "unsupported-background-overlay",
1014
+ reason: "unresolved_background_overlay"
1015
+ },
1016
+ "cosmic-stardust": {
1017
+ classification: "unsupported-background-overlay",
1018
+ reason: "unresolved_background_overlay"
1019
+ },
1020
+ "gradient-border-fill": {
1021
+ classification: "unsupported-background-overlay",
1022
+ reason: "unresolved_background_overlay"
1023
+ },
1024
+ "animated-gradient-border-fill": {
1025
+ classification: "unsupported-background-overlay",
1026
+ reason: "unresolved_background_overlay"
1027
+ },
1028
+ "nav-underline": {
1029
+ classification: "not-button",
1030
+ reason: "not_button_effect"
1031
+ },
1032
+ "nav-frosted-base": {
1033
+ classification: "not-button",
1034
+ reason: "not_button_effect"
1035
+ },
1036
+ "nav-frosted-hover": {
1037
+ classification: "not-button",
1038
+ reason: "not_button_effect"
1039
+ },
1040
+ "nav-frosted-active": {
1041
+ classification: "not-button",
1042
+ reason: "not_button_effect"
1043
+ },
1044
+ "animated-gradient": { classification: "non-colour" },
1045
+ "border-glow": {
1046
+ classification: "unsupported-colour",
1047
+ reason: "changes_shadow_colour"
1048
+ },
1049
+ "drop-shadow": { classification: "decorative-colour" },
1050
+ "inner-shadow": { classification: "decorative-colour" },
1051
+ "neumorphic-shadow": { classification: "decorative-colour" },
1052
+ "outer-glow": {
1053
+ classification: "unsupported-colour",
1054
+ reason: "changes_shadow_colour"
1055
+ },
1056
+ "retro-3d-shadow": { classification: "decorative-colour" },
1057
+ "scale-up": { classification: "non-colour" },
1058
+ "scale-down": { classification: "non-colour" },
1059
+ lift: { classification: "non-colour" },
1060
+ "press-down": { classification: "non-colour" },
1061
+ pop: { classification: "non-colour" },
1062
+ "border-bottom-grow": { classification: "non-colour" },
1063
+ "sketchy-border": { classification: "decorative-colour" },
1064
+ "pulse-animation": {
1065
+ classification: "unsupported-colour",
1066
+ reason: "changes_element_opacity"
616
1067
  }
1068
+ };
1069
+ function validateButtonEffectOptions(effect) {
1070
+ const parsed = buttonEffectApplicationSchema.safeParse(effect);
1071
+ if (parsed.success) return { ok: true };
1072
+ const firstIssue = parsed.error.issues[0];
1073
+ const firstPathPart = firstIssue?.path[firstIssue.path.length - 1];
617
1074
  return {
618
- ...withoutLegacyText,
619
- background: background.type === "solid" ? {
620
- ...background,
621
- colorToken: stripLegacyTokenAlpha(background.colorToken)
622
- } : background,
623
- border: normalizedBorder,
624
- affordance: normalizedAffordance,
625
- effects: normalizedEffects,
626
- hoverBackgroundToken
1075
+ ok: false,
1076
+ option: (firstIssue?.code === "unrecognized_keys" ? firstIssue.keys[0] : void 0) ?? (typeof firstPathPart === "string" ? firstPathPart : "options")
627
1077
  };
628
1078
  }
629
- function normalizeLegacyButtonGlobalInput(input) {
630
- if (!isUnknownRecord(input)) return input;
631
- const hoverColorToken = stripLegacyTokenAlpha(input.hoverColorToken);
632
- return hoverColorToken === input.hoverColorToken ? input : {
633
- ...input,
634
- hoverColorToken
635
- };
636
- }
637
- function hasLegacyAlphaToken(input, background) {
638
- const stops = isUnknownArray(background.stops) ? background.stops : [];
639
- return [
640
- input.hoverBackgroundToken,
641
- background.colorToken,
642
- background.foregroundToken,
643
- ...stops
644
- ].some((token) => stripLegacyTokenAlpha(token) !== token);
645
- }
646
- function stripLegacyTokenAlpha(value) {
647
- if (typeof value !== "string") return value;
648
- const slashIndex = value.indexOf("/");
649
- return slashIndex < 0 ? value : value.slice(0, slashIndex);
1079
+ function getButtonEffectPolicy(effectId) {
1080
+ return Object.prototype.hasOwnProperty.call(BUTTON_EFFECT_POLICIES, effectId) ? BUTTON_EFFECT_POLICIES[effectId] : null;
650
1081
  }
651
- function hasLegacyButtonEffects(input) {
652
- if (!isUnknownRecord(input)) return false;
653
- return [
654
- "base",
655
- "hover",
656
- "active",
657
- "focus"
658
- ].some((state) => {
659
- const effects = input[state];
660
- return Array.isArray(effects) && effects.some((effect) => {
661
- if (!isUnknownRecord(effect) || typeof effect.effectId !== "string") return true;
662
- const classification = classifyButtonEffectId(effect.effectId);
663
- if (classification !== "colour-transition" && classification !== "non-colour") return true;
664
- return effect.effectId === "background-on-hover" && isUnknownRecord(effect.options) && stripLegacyTokenAlpha(effect.options.colorToken) !== effect.options.colorToken;
665
- });
1082
+ //#endregion
1083
+ //#region ../theme-core/src/buttons/resolveButtonColourStates.ts
1084
+ const BUTTON_VISUAL_STATES = [
1085
+ "base",
1086
+ "hover",
1087
+ "focus",
1088
+ "active",
1089
+ "disabled"
1090
+ ];
1091
+ const BUTTON_CONTRAST_POLICY = {
1092
+ textMinimumRatio: 4.5,
1093
+ nonTextMinimumRatio: 3,
1094
+ gradientSamplesPerSegment: 20,
1095
+ alphaBackgrounds: "reject-without-known-surface",
1096
+ disabled: "retain-full-colour-contract"
1097
+ };
1098
+ function compileButtonColourStates(input) {
1099
+ const baseTransition = applyColourEffects({
1100
+ variantId: input.variant.id,
1101
+ state: "base",
1102
+ treatment: input.variant.background,
1103
+ effects: input.variant.effects?.base,
1104
+ palette: input.palette
666
1105
  });
667
- }
668
- function normalizeLegacyButtonEffects(input, backgroundType) {
669
- if (!isUnknownRecord(input)) return input;
670
- const normalized = {};
1106
+ if (!baseTransition.ok) return baseTransition;
1107
+ const stateTreatments = {
1108
+ base: baseTransition.treatment,
1109
+ hover: hoverTreatment(input.variant, input.global, baseTransition.treatment),
1110
+ focus: baseTransition.treatment,
1111
+ active: baseTransition.treatment,
1112
+ disabled: baseTransition.treatment
1113
+ };
1114
+ const nonColourEffects = { ...baseTransition.nonColourEffects.length > 0 ? { base: [...baseTransition.nonColourEffects] } : {} };
671
1115
  for (const state of [
672
- "base",
673
1116
  "hover",
674
- "active",
675
- "focus"
1117
+ "focus",
1118
+ "active"
676
1119
  ]) {
677
- const effects = input[state];
678
- if (!Array.isArray(effects)) continue;
679
- const safeEffects = effects.flatMap((effect) => {
680
- if (!isUnknownRecord(effect) || typeof effect.effectId !== "string") return [];
681
- const classification = classifyButtonEffectId(effect.effectId);
682
- if (classification === "non-colour") return [effect];
683
- if (classification !== "colour-transition") return [];
684
- if (effect.effectId === "background-on-hover") {
685
- const options = isUnknownRecord(effect.options) ? effect.options : null;
686
- const colorToken = options === null ? null : parseOpaqueButtonColourToken(stripLegacyTokenAlpha(options.colorToken));
687
- return colorToken === null ? [] : [{
688
- ...effect,
689
- options: {
690
- ...options,
1120
+ const transition = applyColourEffects({
1121
+ variantId: input.variant.id,
1122
+ state,
1123
+ treatment: stateTreatments[state],
1124
+ effects: input.variant.effects?.[state],
1125
+ palette: input.palette
1126
+ });
1127
+ if (!transition.ok) return transition;
1128
+ stateTreatments[state] = transition.treatment;
1129
+ if (transition.nonColourEffects.length > 0) nonColourEffects[state] = [...transition.nonColourEffects];
1130
+ }
1131
+ const resolved = {};
1132
+ for (const state of BUTTON_VISUAL_STATES) {
1133
+ const result = resolveTreatment({
1134
+ variantId: input.variant.id,
1135
+ state,
1136
+ treatment: stateTreatments[state],
1137
+ palette: input.palette
1138
+ });
1139
+ if (!result.ok) return result;
1140
+ resolved[state] = result.colour;
1141
+ }
1142
+ return {
1143
+ ok: true,
1144
+ states: resolved,
1145
+ focusIndicator: resolveFocusIndicator(resolved.focus),
1146
+ nonColourEffects
1147
+ };
1148
+ }
1149
+ function hoverTreatment(variant, global, fallback) {
1150
+ const token = variant.hoverBackgroundToken ?? (global.hoverColor === "token" ? global.hoverColorToken : void 0);
1151
+ return token === void 0 ? fallback : {
1152
+ type: "solid",
1153
+ colorToken: token
1154
+ };
1155
+ }
1156
+ function applyColourEffects(input) {
1157
+ let treatment = input.treatment;
1158
+ const nonColourEffects = [];
1159
+ for (const effect of input.effects ?? []) {
1160
+ const policy = getButtonEffectPolicy(effect.effectId);
1161
+ if (policy === null) return unknownEffect(input, effect);
1162
+ if (policy.classification === "unsupported-colour" || policy.classification === "unsupported-background-overlay" || policy.classification === "not-button") return unsupportedEffect(input, effect, policy.reason);
1163
+ const options = validateButtonEffectOptions(effect);
1164
+ if (!options.ok) return invalidEffectOption(input, effect, options.option);
1165
+ switch (policy.classification) {
1166
+ case "non-colour":
1167
+ case "decorative-colour":
1168
+ nonColourEffects.push(effect);
1169
+ break;
1170
+ case "colour-transition":
1171
+ if (policy.transition.kind === "adjust-shade") {
1172
+ if (effect.effectId !== "darken-background" && effect.effectId !== "lighten-background") return unknownEffect(input, effect);
1173
+ if (treatment.type !== "solid") return inapplicableEffect(input, effect, treatment);
1174
+ const amount = readShadeAmount(effect);
1175
+ if (amount === null) return invalidEffectOption(input, effect, "amount");
1176
+ const signedAmount = policy.transition.direction === "darker" ? amount : -amount;
1177
+ treatment = {
1178
+ type: "solid",
1179
+ colorToken: adjustShade(treatment.colorToken, signedAmount, input.palette)
1180
+ };
1181
+ break;
1182
+ }
1183
+ if (policy.transition.kind === "replace-background") {
1184
+ if (effect.effectId !== "background-on-hover") return unknownEffect(input, effect);
1185
+ const colorToken = readOpaqueToken(effect);
1186
+ if (colorToken === null) return invalidEffectOption(input, effect, "colorToken");
1187
+ treatment = {
1188
+ type: "solid",
691
1189
  colorToken
692
- }
693
- }];
694
- }
695
- if (backgroundType !== "solid") return [];
696
- const rawAmount = isUnknownRecord(effect.options) ? effect.options.amount : void 0;
697
- const amount = typeof rawAmount === "number" && rawAmount > 0 ? Math.max(100, Math.round(rawAmount / 100) * 100) : 100;
698
- return [{
699
- ...effect,
700
- options: {
701
- ...isUnknownRecord(effect.options) ? effect.options : {},
702
- amount
1190
+ };
1191
+ break;
703
1192
  }
704
- }];
705
- });
706
- if (safeEffects.length > 0) normalized[state] = safeEffects;
1193
+ return unknownEffect(input, effect);
1194
+ }
707
1195
  }
708
- return Object.keys(normalized).length === 0 ? void 0 : normalized;
1196
+ return {
1197
+ ok: true,
1198
+ treatment,
1199
+ nonColourEffects
1200
+ };
1201
+ }
1202
+ function resolveTreatment(input) {
1203
+ const treatment = input.treatment;
1204
+ switch (treatment.type) {
1205
+ case "surface": return {
1206
+ ok: true,
1207
+ colour: {
1208
+ kind: "surface",
1209
+ role: treatment.role,
1210
+ backgroundCss: "transparent",
1211
+ foregroundCss: surfaceForegroundValue(treatment.role)
1212
+ }
1213
+ };
1214
+ case "solid": return resolveSolid(input, treatment.colorToken);
1215
+ case "gradient": return resolveGradient(input, treatment);
1216
+ }
1217
+ }
1218
+ function resolveFocusIndicator(colour) {
1219
+ switch (colour.kind) {
1220
+ case "solid": return {
1221
+ kind: "two-tone",
1222
+ innerCss: colour.foregroundCss,
1223
+ outerCss: colour.backgroundCss,
1224
+ minimumPairContrast: BUTTON_CONTRAST_POLICY.textMinimumRatio
1225
+ };
1226
+ case "gradient": return {
1227
+ kind: "two-tone",
1228
+ innerCss: colour.foregroundCss,
1229
+ outerCss: tokenCss(colour.backgroundTokens[0]),
1230
+ minimumPairContrast: BUTTON_CONTRAST_POLICY.textMinimumRatio
1231
+ };
1232
+ case "surface": return {
1233
+ kind: "surface-role",
1234
+ colorCss: colour.foregroundCss,
1235
+ minimumSurfaceContrast: BUTTON_CONTRAST_POLICY.nonTextMinimumRatio
1236
+ };
1237
+ }
1238
+ }
1239
+ function resolveSolid(input, backgroundToken) {
1240
+ const backgroundHex = paletteHex(input, backgroundToken);
1241
+ if (!backgroundHex.ok) return backgroundHex;
1242
+ const foregroundToken = unsafeAsOpaqueButtonColourToken(onColorTokenName(backgroundToken));
1243
+ const foregroundHex = input.palette[foregroundToken];
1244
+ if (typeof foregroundHex !== "string") return {
1245
+ ok: false,
1246
+ error: {
1247
+ tag: "missing_on_colour",
1248
+ variantId: input.variantId,
1249
+ state: input.state,
1250
+ backgroundToken,
1251
+ foregroundToken
1252
+ }
1253
+ };
1254
+ const contrast = validateContrast({
1255
+ ...input,
1256
+ background: backgroundToken,
1257
+ backgroundHex: backgroundHex.hex,
1258
+ foreground: foregroundToken,
1259
+ foregroundHex
1260
+ });
1261
+ if (!contrast.ok) return contrast;
1262
+ return {
1263
+ ok: true,
1264
+ colour: {
1265
+ kind: "solid",
1266
+ backgroundToken,
1267
+ foregroundToken,
1268
+ backgroundCss: tokenCss(backgroundToken),
1269
+ foregroundCss: tokenCss(foregroundToken)
1270
+ }
1271
+ };
1272
+ }
1273
+ function resolveGradient(input, treatment) {
1274
+ const foregroundHex = paletteHex(input, treatment.foregroundToken);
1275
+ if (!foregroundHex.ok) return foregroundHex;
1276
+ const stopHexes = [];
1277
+ for (const token of treatment.stops) {
1278
+ const stop = paletteHex(input, token);
1279
+ if (!stop.ok) return stop;
1280
+ stopHexes.push(stop.hex);
1281
+ }
1282
+ for (let segment = 0; segment < stopHexes.length - 1; segment += 1) {
1283
+ const start = stopHexes[segment];
1284
+ const end = stopHexes[segment + 1];
1285
+ const startToken = treatment.stops[segment];
1286
+ const endToken = treatment.stops[segment + 1];
1287
+ if (start === void 0 || end === void 0 || startToken === void 0 || endToken === void 0) continue;
1288
+ for (let sample = 0; sample <= BUTTON_CONTRAST_POLICY.gradientSamplesPerSegment; sample += 1) {
1289
+ const position = sample / BUTTON_CONTRAST_POLICY.gradientSamplesPerSegment;
1290
+ const sampleHex = interpolateHex(start, end, position);
1291
+ const contrast = validateContrast({
1292
+ ...input,
1293
+ background: `${startToken}→${endToken}@${position.toFixed(2)}`,
1294
+ backgroundHex: sampleHex,
1295
+ foreground: treatment.foregroundToken,
1296
+ foregroundHex: foregroundHex.hex
1297
+ });
1298
+ if (!contrast.ok) return contrast;
1299
+ }
1300
+ }
1301
+ const direction = GRADIENT_DIRECTION_MAP[treatment.direction] ?? "to right";
1302
+ return {
1303
+ ok: true,
1304
+ colour: {
1305
+ kind: "gradient",
1306
+ backgroundTokens: treatment.stops,
1307
+ foregroundToken: treatment.foregroundToken,
1308
+ backgroundCss: `linear-gradient(${direction}, ${treatment.stops.map(tokenCss).join(", ")})`,
1309
+ foregroundCss: tokenCss(treatment.foregroundToken)
1310
+ }
1311
+ };
1312
+ }
1313
+ function paletteHex(input, token) {
1314
+ const hex = input.palette[token];
1315
+ return typeof hex === "string" ? {
1316
+ ok: true,
1317
+ hex
1318
+ } : {
1319
+ ok: false,
1320
+ error: {
1321
+ tag: "unknown_palette_token",
1322
+ variantId: input.variantId,
1323
+ state: input.state,
1324
+ token
1325
+ }
1326
+ };
1327
+ }
1328
+ function validateContrast(input) {
1329
+ const ratio = contrastRatio(input.backgroundHex, input.foregroundHex);
1330
+ return ratio >= BUTTON_CONTRAST_POLICY.textMinimumRatio ? { ok: true } : {
1331
+ ok: false,
1332
+ error: {
1333
+ tag: "insufficient_contrast",
1334
+ variantId: input.variantId,
1335
+ state: input.state,
1336
+ background: input.background,
1337
+ foreground: input.foreground,
1338
+ ratio,
1339
+ requiredRatio: BUTTON_CONTRAST_POLICY.textMinimumRatio
1340
+ }
1341
+ };
1342
+ }
1343
+ function tokenCss(token) {
1344
+ return `rgb(var(--tb-${token}))`;
1345
+ }
1346
+ function adjustShade(token, amount, palette) {
1347
+ const match = token.match(/^(.*)-(\d{2,3})$/);
1348
+ const base = match?.[1] ?? token;
1349
+ const currentShade = match?.[2] === void 0 ? 500 : Number(match[2]);
1350
+ const availableShades = [
1351
+ 50,
1352
+ 100,
1353
+ 200,
1354
+ 300,
1355
+ 400,
1356
+ 500,
1357
+ 600,
1358
+ 700,
1359
+ 800,
1360
+ 900,
1361
+ 950
1362
+ ].filter((shade) => typeof palette[`${base}-${shade}`] === "string");
1363
+ if (availableShades.length === 0) return token;
1364
+ const firstShade = availableShades[0];
1365
+ if (firstShade === void 0) return token;
1366
+ const exactIndex = availableShades.indexOf(currentShade);
1367
+ const startingIndex = exactIndex >= 0 ? exactIndex : availableShades.indexOf(availableShades.reduce((closestShade, shade) => Math.abs(shade - currentShade) < Math.abs(closestShade - currentShade) ? shade : closestShade, firstShade));
1368
+ const stepCount = Math.abs(amount) / 100;
1369
+ const direction = amount < 0 ? -1 : 1;
1370
+ const targetShade = availableShades[Math.max(0, Math.min(availableShades.length - 1, startingIndex + direction * stepCount))];
1371
+ return targetShade === void 0 ? token : unsafeAsOpaqueButtonColourToken(`${base}-${targetShade}`);
1372
+ }
1373
+ function readShadeAmount(effect) {
1374
+ const value = effect.options?.amount ?? 100;
1375
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value % 100 === 0 ? value : null;
1376
+ }
1377
+ function readOpaqueToken(effect) {
1378
+ return parseOpaqueButtonColourToken(effect.options?.colorToken ?? "neutral-800");
1379
+ }
1380
+ function unsupportedEffect(input, effect, reason) {
1381
+ return {
1382
+ ok: false,
1383
+ error: {
1384
+ tag: "unsupported_colour_effect",
1385
+ variantId: input.variantId,
1386
+ state: input.state,
1387
+ effectId: effect.effectId,
1388
+ reason
1389
+ }
1390
+ };
1391
+ }
1392
+ function invalidEffectOption(input, effect, option) {
1393
+ return {
1394
+ ok: false,
1395
+ error: {
1396
+ tag: "invalid_colour_effect_options",
1397
+ variantId: input.variantId,
1398
+ state: input.state,
1399
+ effectId: effect.effectId,
1400
+ option
1401
+ }
1402
+ };
709
1403
  }
710
- function isUnknownRecord(value) {
711
- return typeof value === "object" && value !== null && !Array.isArray(value);
1404
+ function inapplicableEffect(input, effect, treatment) {
1405
+ return {
1406
+ ok: false,
1407
+ error: {
1408
+ tag: "inapplicable_colour_effect",
1409
+ variantId: input.variantId,
1410
+ state: input.state,
1411
+ effectId: effect.effectId,
1412
+ treatment: treatment.type
1413
+ }
1414
+ };
1415
+ }
1416
+ function unknownEffect(input, effect) {
1417
+ return {
1418
+ ok: false,
1419
+ error: {
1420
+ tag: "unknown_button_effect",
1421
+ variantId: input.variantId,
1422
+ state: input.state,
1423
+ effectId: effect.effectId
1424
+ }
1425
+ };
712
1426
  }
713
- function isUnknownArray(value) {
714
- return Array.isArray(value);
1427
+ function interpolateHex(startHex, endHex, position) {
1428
+ const start = hexToRgb(startHex);
1429
+ const end = hexToRgb(endHex);
1430
+ const channel = (from, to) => Math.round(from + (to - from) * position).toString(16).padStart(2, "0");
1431
+ return `#${channel(start.r, end.r)}${channel(start.g, end.g)}${channel(start.b, end.b)}`;
1432
+ }
1433
+ //#endregion
1434
+ //#region ../theme-core/src/buttons/parseThemeForPersistence.ts
1435
+ /**
1436
+ * Proves that every persisted button variant compiles against the theme's
1437
+ * concrete palette. Public rendering may still recover from malformed
1438
+ * external/runtime input, but persistence boundaries must reject it.
1439
+ */
1440
+ function parseThemeForPersistence(value) {
1441
+ const parsed = themeSchema$1.safeParse(value);
1442
+ if (!parsed.success) return {
1443
+ ok: false,
1444
+ error: {
1445
+ kind: "invalid-theme-schema",
1446
+ issues: parsed.error.issues
1447
+ }
1448
+ };
1449
+ const theme = parsed.data;
1450
+ if (!theme.buttons) return {
1451
+ ok: true,
1452
+ value: theme
1453
+ };
1454
+ const palette = expandPalette(theme.palette);
1455
+ for (const variant of theme.buttons.variants) {
1456
+ const result = compileButtonColourStates({
1457
+ variant,
1458
+ global: theme.buttons.global,
1459
+ palette
1460
+ });
1461
+ if (!result.ok) return {
1462
+ ok: false,
1463
+ error: {
1464
+ kind: "invalid-button-colour-contract",
1465
+ variantId: variant.id,
1466
+ cause: result.error
1467
+ }
1468
+ };
1469
+ }
1470
+ return {
1471
+ ok: true,
1472
+ value: theme
1473
+ };
715
1474
  }
716
1475
  buttonSizeNameSchema.options;
717
1476
  //#endregion
@@ -868,7 +1627,6 @@ const quietArrowRecipe = defineButtonStyleRecipe({
868
1627
  cornerStyle: "rounded",
869
1628
  textTransform: "uppercase",
870
1629
  fontWeight: 500,
871
- borderWidth: 1,
872
1630
  typography: "body",
873
1631
  fontSize: "text-sm",
874
1632
  letterSpacing: "loose",
@@ -932,7 +1690,7 @@ const quietArrowRecipe = defineButtonStyleRecipe({
932
1690
  affordance: { iconId: "ArrowRight" },
933
1691
  effects: { hover: [{
934
1692
  effectId: "background-on-hover",
935
- options: { colorToken: "surfaceAlt" }
1693
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surfaceAlt") }
936
1694
  }] }
937
1695
  }
938
1696
  },
@@ -1043,7 +1801,6 @@ const softPillRecipe = defineStaticButtonStyleRecipe({
1043
1801
  shadow: "low",
1044
1802
  textTransform: "none",
1045
1803
  fontWeight: 500,
1046
- borderWidth: 2,
1047
1804
  hoverTransform: "lift",
1048
1805
  hoverColor: "none"
1049
1806
  },
@@ -1062,10 +1819,7 @@ const softPillRecipe = defineStaticButtonStyleRecipe({
1062
1819
  position: "bottom"
1063
1820
  },
1064
1821
  effects: {
1065
- hover: [{
1066
- effectId: "lift",
1067
- options: { translateY: "-1px" }
1068
- }],
1822
+ hover: [{ effectId: "lift" }],
1069
1823
  active: [{
1070
1824
  effectId: "scale-down",
1071
1825
  options: { scale: "0.98" }
@@ -1084,7 +1838,7 @@ const softPillRecipe = defineStaticButtonStyleRecipe({
1084
1838
  border: { widthClass: "border-2" },
1085
1839
  effects: { hover: [{
1086
1840
  effectId: "background-on-hover",
1087
- options: { colorToken: "surface" }
1841
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1088
1842
  }] }
1089
1843
  }
1090
1844
  },
@@ -1099,7 +1853,7 @@ const softPillRecipe = defineStaticButtonStyleRecipe({
1099
1853
  border: { widthClass: "border" },
1100
1854
  effects: { hover: [{
1101
1855
  effectId: "background-on-hover",
1102
- options: { colorToken: "surface" }
1856
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1103
1857
  }] }
1104
1858
  }
1105
1859
  },
@@ -1113,7 +1867,7 @@ const softPillRecipe = defineStaticButtonStyleRecipe({
1113
1867
  borderRadius: "rounded-full",
1114
1868
  effects: { hover: [{
1115
1869
  effectId: "background-on-hover",
1116
- options: { colorToken: "surface" }
1870
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1117
1871
  }] }
1118
1872
  }
1119
1873
  },
@@ -1143,7 +1897,6 @@ const earthPillRecipe = defineStaticButtonStyleRecipe({
1143
1897
  shadow: "low",
1144
1898
  textTransform: "none",
1145
1899
  fontWeight: 500,
1146
- borderWidth: 1,
1147
1900
  hoverTransform: "lift",
1148
1901
  hoverColor: "none",
1149
1902
  typography: "body",
@@ -1179,12 +1932,9 @@ const earthPillRecipe = defineStaticButtonStyleRecipe({
1179
1932
  position: "bottom"
1180
1933
  },
1181
1934
  effects: {
1182
- hover: [{
1183
- effectId: "lift",
1184
- options: { translateY: "-1px" }
1185
- }, {
1935
+ hover: [{ effectId: "lift" }, {
1186
1936
  effectId: "background-on-hover",
1187
- options: { colorToken: "accent" }
1937
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("accent") }
1188
1938
  }],
1189
1939
  active: [{
1190
1940
  effectId: "scale-down",
@@ -1204,7 +1954,7 @@ const earthPillRecipe = defineStaticButtonStyleRecipe({
1204
1954
  border: { widthClass: "border" },
1205
1955
  effects: { hover: [{
1206
1956
  effectId: "background-on-hover",
1207
- options: { colorToken: "muted" }
1957
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("muted") }
1208
1958
  }] }
1209
1959
  }
1210
1960
  },
@@ -1219,7 +1969,7 @@ const earthPillRecipe = defineStaticButtonStyleRecipe({
1219
1969
  border: { widthClass: "border" },
1220
1970
  effects: { hover: [{
1221
1971
  effectId: "background-on-hover",
1222
- options: { colorToken: "surfaceAlt" }
1972
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surfaceAlt") }
1223
1973
  }] }
1224
1974
  }
1225
1975
  },
@@ -1279,7 +2029,6 @@ const confidentChipRecipe = defineStaticButtonStyleRecipe({
1279
2029
  shadow: "medium",
1280
2030
  textTransform: "none",
1281
2031
  fontWeight: 600,
1282
- borderWidth: 1,
1283
2032
  hoverTransform: "none",
1284
2033
  hoverColor: "none"
1285
2034
  },
@@ -1320,7 +2069,7 @@ const confidentChipRecipe = defineStaticButtonStyleRecipe({
1320
2069
  border: { widthClass: "border" },
1321
2070
  effects: { hover: [{
1322
2071
  effectId: "background-on-hover",
1323
- options: { colorToken: "background" }
2072
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("background") }
1324
2073
  }] }
1325
2074
  }
1326
2075
  },
@@ -1335,7 +2084,7 @@ const confidentChipRecipe = defineStaticButtonStyleRecipe({
1335
2084
  border: { widthClass: "border" },
1336
2085
  effects: { hover: [{
1337
2086
  effectId: "background-on-hover",
1338
- options: { colorToken: "surface" }
2087
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1339
2088
  }] }
1340
2089
  }
1341
2090
  },
@@ -1349,7 +2098,7 @@ const confidentChipRecipe = defineStaticButtonStyleRecipe({
1349
2098
  borderRadius: "rounded-md",
1350
2099
  effects: { hover: [{
1351
2100
  effectId: "background-on-hover",
1352
- options: { colorToken: "surface" }
2101
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1353
2102
  }] }
1354
2103
  }
1355
2104
  },
@@ -1384,7 +2133,6 @@ const editorialLinkRecipe = defineStaticButtonStyleRecipe({
1384
2133
  cornerStyle: "square",
1385
2134
  textTransform: "none",
1386
2135
  fontWeight: 500,
1387
- borderWidth: 1,
1388
2136
  hoverTransform: "none",
1389
2137
  hoverColor: "none"
1390
2138
  },
@@ -1402,7 +2150,6 @@ const editorialLinkRecipe = defineStaticButtonStyleRecipe({
1402
2150
  effectId: "border-bottom-grow",
1403
2151
  options: {
1404
2152
  height: 2,
1405
- colorToken: "text",
1406
2153
  origin: "left"
1407
2154
  }
1408
2155
  }] }
@@ -1421,7 +2168,6 @@ const editorialLinkRecipe = defineStaticButtonStyleRecipe({
1421
2168
  effectId: "border-bottom-grow",
1422
2169
  options: {
1423
2170
  height: 1,
1424
- colorToken: "mutedText",
1425
2171
  origin: "left"
1426
2172
  }
1427
2173
  }] }
@@ -1440,7 +2186,6 @@ const editorialLinkRecipe = defineStaticButtonStyleRecipe({
1440
2186
  effectId: "border-bottom-grow",
1441
2187
  options: {
1442
2188
  height: 1,
1443
- colorToken: "accent",
1444
2189
  origin: "left"
1445
2190
  }
1446
2191
  }] }
@@ -1464,7 +2209,6 @@ const editorialLinkRecipe = defineStaticButtonStyleRecipe({
1464
2209
  effectId: "border-bottom-grow",
1465
2210
  options: {
1466
2211
  height: 2,
1467
- colorToken: "accent",
1468
2212
  origin: "left"
1469
2213
  }
1470
2214
  }] } }
@@ -1486,7 +2230,6 @@ const pebbleRecipe = defineStaticButtonStyleRecipe({
1486
2230
  cornerStyle: "pill",
1487
2231
  textTransform: "none",
1488
2232
  fontWeight: 500,
1489
- borderWidth: 2,
1490
2233
  hoverTransform: "none",
1491
2234
  hoverColor: "none"
1492
2235
  },
@@ -1531,7 +2274,7 @@ const pebbleRecipe = defineStaticButtonStyleRecipe({
1531
2274
  border: { widthClass: "border-2" },
1532
2275
  effects: { hover: [{
1533
2276
  effectId: "background-on-hover",
1534
- options: { colorToken: "accent" }
2277
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("accent") }
1535
2278
  }] }
1536
2279
  }
1537
2280
  },
@@ -1546,7 +2289,7 @@ const pebbleRecipe = defineStaticButtonStyleRecipe({
1546
2289
  border: { widthClass: "border" },
1547
2290
  effects: { hover: [{
1548
2291
  effectId: "background-on-hover",
1549
- options: { colorToken: "surface" }
2292
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1550
2293
  }] }
1551
2294
  }
1552
2295
  },
@@ -1601,7 +2344,6 @@ const brushedWashRecipe = defineStaticButtonStyleRecipe({
1601
2344
  shadow: "low",
1602
2345
  textTransform: "none",
1603
2346
  fontWeight: 500,
1604
- borderWidth: 1,
1605
2347
  hoverTransform: "none",
1606
2348
  hoverColor: "none"
1607
2349
  },
@@ -1666,7 +2408,7 @@ const brushedWashRecipe = defineStaticButtonStyleRecipe({
1666
2408
  border: { widthClass: "border" },
1667
2409
  effects: { hover: [{
1668
2410
  effectId: "background-on-hover",
1669
- options: { colorToken: "surface" }
2411
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1670
2412
  }] }
1671
2413
  }
1672
2414
  },
@@ -1680,7 +2422,7 @@ const brushedWashRecipe = defineStaticButtonStyleRecipe({
1680
2422
  borderRadius: "rounded-lg",
1681
2423
  effects: { hover: [{
1682
2424
  effectId: "background-on-hover",
1683
- options: { colorToken: "surface" }
2425
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1684
2426
  }] }
1685
2427
  }
1686
2428
  },
@@ -1709,7 +2451,6 @@ const inkStampRecipe = defineStaticButtonStyleRecipe({
1709
2451
  cornerStyle: "rounded",
1710
2452
  textTransform: "uppercase",
1711
2453
  fontWeight: 600,
1712
- borderWidth: 1,
1713
2454
  hoverTransform: "none",
1714
2455
  hoverColor: "none",
1715
2456
  letterSpacing: "loose"
@@ -1767,7 +2508,7 @@ const inkStampRecipe = defineStaticButtonStyleRecipe({
1767
2508
  border: { widthClass: "border-2" },
1768
2509
  effects: { hover: [{
1769
2510
  effectId: "background-on-hover",
1770
- options: { colorToken: "surface" }
2511
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1771
2512
  }] }
1772
2513
  }
1773
2514
  },
@@ -1782,7 +2523,7 @@ const inkStampRecipe = defineStaticButtonStyleRecipe({
1782
2523
  border: { widthClass: "border-2" },
1783
2524
  effects: { hover: [{
1784
2525
  effectId: "background-on-hover",
1785
- options: { colorToken: "surface" }
2526
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
1786
2527
  }] }
1787
2528
  }
1788
2529
  },
@@ -1816,7 +2557,6 @@ const inkStampRecipe = defineStaticButtonStyleRecipe({
1816
2557
  effectId: "border-bottom-grow",
1817
2558
  options: {
1818
2559
  height: 1,
1819
- colorToken: "accent",
1820
2560
  origin: "left"
1821
2561
  }
1822
2562
  }] }
@@ -1847,7 +2587,6 @@ const flatInkRecipe = defineStaticButtonStyleRecipe({
1847
2587
  cornerStyle: "rounded",
1848
2588
  textTransform: "none",
1849
2589
  fontWeight: 600,
1850
- borderWidth: 1,
1851
2590
  typography: "body",
1852
2591
  letterSpacing: "tight",
1853
2592
  paddingPreset: "default",
@@ -1879,12 +2618,9 @@ const flatInkRecipe = defineStaticButtonStyleRecipe({
1879
2618
  },
1880
2619
  borderRadius: "rounded-lg",
1881
2620
  affordance: { iconId: "ArrowRight" },
1882
- effects: { hover: [{
1883
- effectId: "lift",
1884
- options: { translateY: "-2px" }
1885
- }, {
2621
+ effects: { hover: [{ effectId: "lift" }, {
1886
2622
  effectId: "background-on-hover",
1887
- options: { colorToken: "secondary" }
2623
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("secondary") }
1888
2624
  }] }
1889
2625
  }
1890
2626
  },
@@ -1897,12 +2633,9 @@ const flatInkRecipe = defineStaticButtonStyleRecipe({
1897
2633
  },
1898
2634
  borderRadius: "rounded-lg",
1899
2635
  border: { widthClass: "border" },
1900
- effects: { hover: [{
1901
- effectId: "lift",
1902
- options: { translateY: "-2px" }
1903
- }, {
2636
+ effects: { hover: [{ effectId: "lift" }, {
1904
2637
  effectId: "background-on-hover",
1905
- options: { colorToken: "surfaceAlt" }
2638
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surfaceAlt") }
1906
2639
  }] }
1907
2640
  }
1908
2641
  },
@@ -1915,12 +2648,9 @@ const flatInkRecipe = defineStaticButtonStyleRecipe({
1915
2648
  },
1916
2649
  borderRadius: "rounded-lg",
1917
2650
  border: { widthClass: "border" },
1918
- effects: { hover: [{
1919
- effectId: "lift",
1920
- options: { translateY: "-2px" }
1921
- }, {
2651
+ effects: { hover: [{ effectId: "lift" }, {
1922
2652
  effectId: "background-on-hover",
1923
- options: { colorToken: "surfaceAlt" }
2653
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surfaceAlt") }
1924
2654
  }] }
1925
2655
  }
1926
2656
  },
@@ -1932,10 +2662,7 @@ const flatInkRecipe = defineStaticButtonStyleRecipe({
1932
2662
  role: "link"
1933
2663
  },
1934
2664
  borderRadius: "rounded-lg",
1935
- effects: { hover: [{
1936
- effectId: "lift",
1937
- options: { translateY: "-2px" }
1938
- }] }
2665
+ effects: { hover: [{ effectId: "lift" }] }
1939
2666
  }
1940
2667
  },
1941
2668
  accent: {
@@ -1947,10 +2674,7 @@ const flatInkRecipe = defineStaticButtonStyleRecipe({
1947
2674
  },
1948
2675
  borderRadius: "rounded-lg",
1949
2676
  affordance: { iconId: "ArrowRight" },
1950
- effects: { hover: [{
1951
- effectId: "lift",
1952
- options: { translateY: "-2px" }
1953
- }] }
2677
+ effects: { hover: [{ effectId: "lift" }] }
1954
2678
  }
1955
2679
  }
1956
2680
  }
@@ -1971,9 +2695,8 @@ const showtimePillRecipe = defineStaticButtonStyleRecipe({
1971
2695
  shadow: "low",
1972
2696
  textTransform: "uppercase",
1973
2697
  fontWeight: 700,
1974
- borderWidth: 2,
1975
2698
  hoverTransform: "lift",
1976
- hoverColor: "darken",
2699
+ hoverColor: "none",
1977
2700
  typography: "body",
1978
2701
  paddingPreset: "spacious",
1979
2702
  italic: false
@@ -2031,7 +2754,7 @@ const showtimePillRecipe = defineStaticButtonStyleRecipe({
2031
2754
  border: { widthClass: "border-2" },
2032
2755
  effects: { hover: [{
2033
2756
  effectId: "background-on-hover",
2034
- options: { colorToken: "background" }
2757
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("background") }
2035
2758
  }] }
2036
2759
  }
2037
2760
  },
@@ -2045,7 +2768,7 @@ const showtimePillRecipe = defineStaticButtonStyleRecipe({
2045
2768
  borderRadius: "rounded-full",
2046
2769
  effects: { hover: [{
2047
2770
  effectId: "background-on-hover",
2048
- options: { colorToken: "surface" }
2771
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
2049
2772
  }] }
2050
2773
  }
2051
2774
  },
@@ -2080,7 +2803,6 @@ const joyStampRecipe = defineStaticButtonStyleRecipe({
2080
2803
  typography: "heading",
2081
2804
  fontWeight: 700,
2082
2805
  fontSize: "text-lg",
2083
- borderWidth: 2,
2084
2806
  hoverTransform: "none",
2085
2807
  hoverColor: "none"
2086
2808
  },
@@ -2115,7 +2837,7 @@ const joyStampRecipe = defineStaticButtonStyleRecipe({
2115
2837
  elevation: "high",
2116
2838
  softness: "hard",
2117
2839
  position: "bottom-right",
2118
- colorToken: "text"
2840
+ colorToken: unsafeAsOpaqueButtonColourToken("text")
2119
2841
  },
2120
2842
  effects: { hover: [{ effectId: "press-down" }] }
2121
2843
  }
@@ -2133,7 +2855,7 @@ const joyStampRecipe = defineStaticButtonStyleRecipe({
2133
2855
  elevation: "high",
2134
2856
  softness: "hard",
2135
2857
  position: "bottom-right",
2136
- colorToken: "secondary"
2858
+ colorToken: unsafeAsOpaqueButtonColourToken("secondary")
2137
2859
  },
2138
2860
  effects: { hover: [{ effectId: "press-down" }] }
2139
2861
  }
@@ -2178,7 +2900,6 @@ const ornateRitualRecipe = defineStaticButtonStyleRecipe({
2178
2900
  cornerStyle: "rounded",
2179
2901
  textTransform: "uppercase",
2180
2902
  fontWeight: 600,
2181
- borderWidth: 1,
2182
2903
  hoverTransform: "none",
2183
2904
  hoverColor: "none",
2184
2905
  letterSpacing: "loose"
@@ -2214,7 +2935,7 @@ const ornateRitualRecipe = defineStaticButtonStyleRecipe({
2214
2935
  effects: {
2215
2936
  hover: [{
2216
2937
  effectId: "background-on-hover",
2217
- options: { colorToken: "primary-100" }
2938
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("primary-100") }
2218
2939
  }],
2219
2940
  active: [{
2220
2941
  effectId: "scale-down",
@@ -2234,7 +2955,7 @@ const ornateRitualRecipe = defineStaticButtonStyleRecipe({
2234
2955
  border: { widthClass: "border" },
2235
2956
  effects: { hover: [{
2236
2957
  effectId: "background-on-hover",
2237
- options: { colorToken: "surface" }
2958
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("surface") }
2238
2959
  }] }
2239
2960
  }
2240
2961
  },
@@ -2249,7 +2970,7 @@ const ornateRitualRecipe = defineStaticButtonStyleRecipe({
2249
2970
  border: { widthClass: "border" },
2250
2971
  effects: { hover: [{
2251
2972
  effectId: "background-on-hover",
2252
- options: { colorToken: "muted" }
2973
+ options: { colorToken: unsafeAsOpaqueButtonColourToken("muted") }
2253
2974
  }] }
2254
2975
  }
2255
2976
  },
@@ -2283,7 +3004,6 @@ const ornateRitualRecipe = defineStaticButtonStyleRecipe({
2283
3004
  effectId: "border-bottom-grow",
2284
3005
  options: {
2285
3006
  height: 1,
2286
- colorToken: "accent",
2287
3007
  origin: "left"
2288
3008
  }
2289
3009
  }] }
@@ -4224,13 +4944,13 @@ async function loadLocalTheme(contentDir, themeName = "current") {
4224
4944
  } catch (error) {
4225
4945
  throw new Error(`Invalid JSON in theme file: ${filePath}\n${error instanceof Error ? error.message : String(error)}`);
4226
4946
  }
4227
- const result = themeSchema$1.safeParse(parsedContent);
4228
- if (!result.success) {
4229
- const issues = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
4230
- throw new Error(`Invalid theme at ${filePath}\n${issues}`);
4947
+ const result = parseThemeForPersistence(parsedContent);
4948
+ if (!result.ok) {
4949
+ const detail = result.error.kind === "invalid-theme-schema" ? result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n") : ` - buttons.${result.error.variantId}: ${result.error.cause.tag}`;
4950
+ throw new Error(`Invalid theme at ${filePath}\n${detail}`);
4231
4951
  }
4232
4952
  return {
4233
- theme: result.data,
4953
+ theme: result.value,
4234
4954
  name: themeName,
4235
4955
  filePath: path.resolve(filePath)
4236
4956
  };
@@ -4263,43 +4983,5 @@ async function listLocalThemes(contentDir) {
4263
4983
  * These utilities help you work with Builder themes in your application.
4264
4984
  */
4265
4985
  const themeSchema = themeSchema$1;
4266
- /**
4267
- * Apply a Builder theme to the DOM by injecting CSS variables
4268
- *
4269
- * @param theme - The theme object from Riverbank CMS
4270
- * @param element - The element to apply the theme to (default: document.documentElement)
4271
- *
4272
- * @example
4273
- * ```ts
4274
- * const site = await client.getSite({ slug: 'my-site' });
4275
- * applyTheme(site.theme);
4276
- * ```
4277
- */
4278
- function applyTheme(theme, element) {
4279
- const targetElement = element ?? (typeof document !== "undefined" ? document.documentElement : null);
4280
- if (!targetElement) {
4281
- console.warn("[Builder SDK] Cannot apply theme: no document available");
4282
- return;
4283
- }
4284
- targetElement.setAttribute("data-builder-theme", JSON.stringify(theme));
4285
- }
4286
- /**
4287
- * Create a theme object with type-safe defaults
4288
- *
4289
- * @param partial - Partial theme configuration
4290
- * @returns Complete theme object
4291
- *
4292
- * @example
4293
- * ```ts
4294
- * const customTheme = createTheme({
4295
- * colors: {
4296
- * primary: '#007bff',
4297
- * },
4298
- * });
4299
- * ```
4300
- */
4301
- function createTheme(partial = {}) {
4302
- return { ...partial };
4303
- }
4304
4986
  //#endregion
4305
- export { applyTheme, createTheme, listLocalThemes, loadLocalTheme, themeSchema };
4987
+ export { listLocalThemes, loadLocalTheme, themeSchema };