@repobit/dex-system-design 0.25.12 → 1.0.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 (336) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/README.md +131 -8
  3. package/package.json +96 -13
  4. package/schemas/component-manifest.json +3294 -405
  5. package/schemas/manifest-omissions.json +173 -0
  6. package/src/blog/src/breakpoints.js +6 -6
  7. package/src/blog/src/content/blog-atoms-showcase.js +1 -1
  8. package/src/blog/src/content/blog-page-layout.js +2 -2
  9. package/src/blog/src/lib/article-toc.js +7 -7
  10. package/src/blog/src/shell/blog-shell.js +1 -1
  11. package/src/blog/src/tokens/blog.js +2 -2
  12. package/src/blog/src/tokens/core.js +34 -35
  13. package/src/blog/src/tokens/legacy.js +0 -9
  14. package/src/blog/tokens/blog.js +2 -2
  15. package/src/blog/tokens/core.js +34 -35
  16. package/src/blog/tokens/legacy.js +0 -9
  17. package/src/blog/tokens.css +36 -46
  18. package/src/components/accordion/accordion-bg.css.js +83 -59
  19. package/src/components/accordion/accordion-bg.js +29 -10
  20. package/src/components/accordion/accordion-bg.stories.js +67 -67
  21. package/src/components/accordion/accordion-keyboard.js +80 -0
  22. package/src/components/accordion/accordion.css.js +142 -80
  23. package/src/components/accordion/accordion.js +37 -10
  24. package/src/components/accordion/accordion.stories.js +7 -7
  25. package/src/components/agent-endpoints/agent-endpoints.css.js +36 -39
  26. package/src/components/agent-endpoints/agent-endpoints.js +30 -20
  27. package/src/components/agent-endpoints/agent-endpoints.stories.js +1 -1
  28. package/src/components/ai-guardian-demo.stories.js +91 -91
  29. package/src/components/alert/alert.css.js +198 -0
  30. package/src/components/alert/alert.js +103 -0
  31. package/src/components/alert/alert.stories.js +206 -0
  32. package/src/components/anchor/anchor-nav.css.js +213 -101
  33. package/src/components/anchor/anchor-nav.js +26 -40
  34. package/src/components/anchor/anchor.stories.js +22 -12
  35. package/src/components/annotation/annotation.css.js +58 -0
  36. package/src/components/annotation/annotation.js +67 -0
  37. package/src/components/annotation/annotation.stories.js +63 -0
  38. package/src/components/architecture.stories.js +285 -0
  39. package/src/components/atoms.stories.js +342 -0
  40. package/src/components/avatar/avatar.css.js +67 -0
  41. package/src/components/avatar/avatar.js +42 -0
  42. package/src/components/avatar/avatar.stories.js +95 -0
  43. package/src/components/awards/awards-flex.css.js +209 -23
  44. package/src/components/awards/awards-flex.js +66 -7
  45. package/src/components/awards/awards-flex.stories.js +1 -1
  46. package/src/components/awards/{awards-light.stories.js → awards-summary.stories.js} +54 -64
  47. package/src/components/awards/awards.css.js +116 -58
  48. package/src/components/awards/awards.js +102 -28
  49. package/src/components/awards/awards.stories.js +1 -1
  50. package/src/components/back/back.css.js +51 -22
  51. package/src/components/back/back.js +28 -7
  52. package/src/components/back/back.stories.js +9 -3
  53. package/src/components/badge/badge.css.js +158 -46
  54. package/src/components/badge/badge.js +62 -74
  55. package/src/components/badge/badge.stories.js +176 -248
  56. package/src/components/bento-grid/bento-grid.css.js +21 -16
  57. package/src/components/bento-grid/bento-grid.js +16 -4
  58. package/src/components/bento-grid/bento-grid.stories.js +19 -18
  59. package/src/components/bento-grid/bento-item.css.js +52 -28
  60. package/src/components/bento-grid/bento-item.js +2 -2
  61. package/src/components/blockquote/blockquote.css.js +47 -0
  62. package/src/components/blockquote/blockquote.js +29 -0
  63. package/src/components/blockquote/blockquote.stories.js +62 -0
  64. package/src/components/breadcrumb/breadcrumb.css.js +92 -22
  65. package/src/components/breadcrumb/breadcrumb.js +56 -11
  66. package/src/components/button/button.css.js +531 -161
  67. package/src/components/button/button.js +206 -214
  68. package/src/components/button/button.stories.js +322 -174
  69. package/src/components/caption/caption.css.js +21 -0
  70. package/src/components/caption/caption.js +31 -0
  71. package/src/components/caption/caption.stories.js +62 -0
  72. package/src/components/card-features/card-features.css.js +14 -22
  73. package/src/components/card-features/card-features.js +68 -9
  74. package/src/components/card-features/card-features.stories.js +11 -11
  75. package/src/components/cards/card.css.js +60 -81
  76. package/src/components/cards/card.js +56 -25
  77. package/src/components/cards/card.stories.js +39 -39
  78. package/src/components/carousel/carousel.css.js +93 -99
  79. package/src/components/carousel/carousel.js +32 -4
  80. package/src/components/checkbox/checkbox.css.js +111 -93
  81. package/src/components/checkbox/checkbox.js +64 -90
  82. package/src/components/checkbox/checkbox.stories.js +96 -29
  83. package/src/components/code/code.css.js +70 -0
  84. package/src/components/code/code.js +40 -0
  85. package/src/components/code/code.stories.js +73 -0
  86. package/src/components/compare/compare.css.js +179 -43
  87. package/src/components/compare/compare.js +52 -25
  88. package/src/components/compare/compare.stories.js +61 -4
  89. package/src/components/comparison-panel/comparison-panel.css.js +38 -51
  90. package/src/components/comparison-panel/comparison-panel.js +5 -4
  91. package/src/components/comparison-panel/comparison-panel.stories.js +1 -1
  92. package/src/components/compatibility-table/compatibility-table.css.js +180 -80
  93. package/src/components/compatibility-table/compatibility-table.js +37 -9
  94. package/src/components/control-alignment.stories.js +393 -0
  95. package/src/components/cta-banner/cta-banner.css.js +41 -13
  96. package/src/components/cta-banner/cta-banner.js +6 -5
  97. package/src/components/cta-banner/cta-banner.stories.js +43 -98
  98. package/src/components/display/display.css.js +25 -1
  99. package/src/components/display/display.js +46 -13
  100. package/src/components/display/display.stories.js +270 -40
  101. package/src/components/divider/divider-horizontal.js +21 -5
  102. package/src/components/divider/divider-vertical.js +15 -7
  103. package/src/components/divider/divider.css.js +2 -2
  104. package/src/components/divider/divider.stories.js +38 -34
  105. package/src/components/dropdown/dropdown.css.js +93 -126
  106. package/src/components/dropdown/dropdown.js +114 -34
  107. package/src/components/dropdown/dropdown.stories.js +56 -24
  108. package/src/components/features/features.css.js +24 -11
  109. package/src/components/features/features.js +7 -3
  110. package/src/components/features/features.stories.js +37 -37
  111. package/src/components/features-tab/features-tab.css.js +19 -8
  112. package/src/components/features-tab/features-tab.js +6 -5
  113. package/src/components/features-tab/features-tab.stories.js +23 -23
  114. package/src/components/fieldset/fieldset.css.js +30 -0
  115. package/src/components/fieldset/fieldset.js +28 -0
  116. package/src/components/fieldset/fieldset.stories.js +84 -0
  117. package/src/components/footer/footer-links-group.css.js +10 -14
  118. package/src/components/footer/footer-links-group.js +4 -3
  119. package/src/components/footer/footer-lp.css.js +59 -30
  120. package/src/components/footer/footer-lp.js +17 -12
  121. package/src/components/footer/footer-lp.stories.js +63 -75
  122. package/src/components/footer/footer-nav-menu.css.js +26 -4
  123. package/src/components/footer/footer-nav-menu.js +3 -2
  124. package/src/components/footer/footer.css.js +146 -264
  125. package/src/components/footer/footer.js +176 -106
  126. package/src/components/footer/footer.stories.js +9 -9
  127. package/src/components/footer/localeMap.js +1 -1
  128. package/src/components/grid/grid.css.js +1 -1
  129. package/src/components/grid/grid.js +49 -7
  130. package/src/components/grid/grid.stories.js +111 -0
  131. package/src/components/header/header-code.css.js +61 -51
  132. package/src/components/header/header-code.js +28 -14
  133. package/src/components/header/header-code.stories.js +5 -5
  134. package/src/components/header/header.css.js +655 -176
  135. package/src/components/header/header.js +269 -36
  136. package/src/components/header/header.stories.js +841 -18
  137. package/src/components/heading/heading.css.js +14 -17
  138. package/src/components/heading/heading.js +48 -33
  139. package/src/components/heading/heading.stories.js +27 -25
  140. package/src/components/help-text/help-text.css.js +21 -0
  141. package/src/components/help-text/help-text.js +25 -0
  142. package/src/components/help-text/help-text.stories.js +66 -0
  143. package/src/components/highlight/highlight.css.js +86 -64
  144. package/src/components/highlight/highlight.js +18 -27
  145. package/src/components/highlight/highlight.stories.js +14 -3
  146. package/src/components/icons/analysis-icon.js +6 -16
  147. package/src/components/icons/arrow-down-icon.js +6 -19
  148. package/src/components/icons/arrow-icon.js +44 -0
  149. package/src/components/icons/arrow-right-icon.js +30 -23
  150. package/src/components/icons/arrow-up-icon.js +19 -18
  151. package/src/components/icons/bd-ai-icon.js +1 -1
  152. package/src/components/icons/brand-marks.stories.js +279 -0
  153. package/src/components/icons/brand-signature-icon.js +42 -0
  154. package/src/components/icons/check-icon.js +27 -0
  155. package/src/components/icons/circle-info-icon.js +33 -0
  156. package/src/components/icons/close-icon.js +6 -18
  157. package/src/components/icons/family-icon.js +8 -20
  158. package/src/components/icons/globe-icon.js +6 -18
  159. package/src/components/icons/guide-icon.js +6 -18
  160. package/src/components/icons/icon-element.js +99 -0
  161. package/src/components/icons/icons.js +36 -0
  162. package/src/components/icons/icons.stories.js +184 -52
  163. package/src/components/icons/individual-icon.js +6 -18
  164. package/src/components/icons/laurel-icon.js +9 -19
  165. package/src/components/icons/machine-learning-icon.js +6 -17
  166. package/src/components/icons/marks.js +41 -0
  167. package/src/components/icons/minus-icon.js +7 -21
  168. package/src/components/icons/missing-icon.js +52 -0
  169. package/src/components/icons/plus-icon.js +7 -21
  170. package/src/components/icons/quote-icon.js +19 -19
  171. package/src/components/image/image.css.js +72 -25
  172. package/src/components/image/image.js +11 -6
  173. package/src/components/image/image.stories.js +2 -2
  174. package/src/components/input/custom-form.stories.js +214 -62
  175. package/src/components/input/input-clipboard.css.js +51 -144
  176. package/src/components/input/input-clipboard.js +31 -15
  177. package/src/components/input/input.css.js +8 -146
  178. package/src/components/input/input.js +93 -38
  179. package/src/components/install-options/install-options.css.js +84 -77
  180. package/src/components/install-options/install-options.js +33 -30
  181. package/src/components/install-options/install-options.stories.js +3 -3
  182. package/src/components/label/label.css.js +35 -0
  183. package/src/components/label/label.js +38 -0
  184. package/src/components/label/label.stories.js +82 -0
  185. package/src/components/light-carousel/light-carousel.css.js +111 -63
  186. package/src/components/light-carousel/light-carousel.js +85 -11
  187. package/src/components/light-carousel/light-carousel.stories.js +126 -82
  188. package/src/components/link/link.css.js +150 -99
  189. package/src/components/link/link.js +86 -11
  190. package/src/components/link/link.stories.js +124 -30
  191. package/src/components/list/list-item/list-item.css.js +74 -22
  192. package/src/components/list/list-item/list-item.js +32 -10
  193. package/src/components/list/list-item/list-item.stories.js +23 -23
  194. package/src/components/list/list.css.js +8 -2
  195. package/src/components/list/list.js +3 -2
  196. package/src/components/list/list.stories.js +222 -0
  197. package/src/components/modal/modal.css.js +114 -24
  198. package/src/components/modal/modal.js +83 -7
  199. package/src/components/modal/modal.stories.js +9 -9
  200. package/src/components/overview/overview.css.js +19 -14
  201. package/src/components/overview/overview.js +6 -5
  202. package/src/components/overview/overview.stories.js +10 -10
  203. package/src/components/page-switcher/page-switcher.css.js +97 -40
  204. package/src/components/page-switcher/page-switcher.js +28 -5
  205. package/src/components/page-switcher/page-switcher.stories.js +95 -0
  206. package/src/components/paragraph/paragraph.css.js +15 -10
  207. package/src/components/paragraph/paragraph.js +41 -44
  208. package/src/components/paragraph/paragraph.stories.js +56 -21
  209. package/src/components/picture/picture.css.js +103 -44
  210. package/src/components/picture/picture.js +100 -30
  211. package/src/components/picture/picture.stories.js +1 -1
  212. package/src/components/pricing-cards/new-pricing-card.js +3 -3
  213. package/src/components/pricing-cards/new-pricing.css.js +24 -15
  214. package/src/components/pricing-cards/pricing-card-actions.css.js +1 -1
  215. package/src/components/pricing-cards/pricing-card-actions.js +3 -4
  216. package/src/components/pricing-cards/pricing-card-container.css.js +3 -3
  217. package/src/components/pricing-cards/pricing-card-container.js +3 -2
  218. package/src/components/pricing-cards/pricing-card-header.css.js +10 -8
  219. package/src/components/pricing-cards/pricing-card-header.js +6 -7
  220. package/src/components/pricing-cards/pricing-card-pricing.css.js +16 -9
  221. package/src/components/pricing-cards/pricing-card-pricing.js +8 -6
  222. package/src/components/pricing-cards/pricing-card-show-more.css.js +11 -3
  223. package/src/components/pricing-cards/pricing-card-show-more.js +8 -3
  224. package/src/components/pricing-cards/pricing-card.css.js +244 -190
  225. package/src/components/pricing-cards/pricing-card.js +54 -70
  226. package/src/components/pricing-cards/pricing-feature-item.css.js +25 -8
  227. package/src/components/pricing-cards/pricing-feature-item.js +8 -3
  228. package/src/components/process-steps/process-steps.css.js +26 -14
  229. package/src/components/process-steps/process-steps.js +23 -6
  230. package/src/components/process-steps/process-steps.stories.js +1 -1
  231. package/src/components/progress/progress.css.js +80 -0
  232. package/src/components/progress/progress.js +42 -0
  233. package/src/components/progress/progress.stories.js +79 -0
  234. package/src/components/quick-answer/quick-answer.css.js +75 -13
  235. package/src/components/quick-answer/quick-answer.js +8 -3
  236. package/src/components/radio/radio.css.js +92 -128
  237. package/src/components/radio/radio.js +106 -127
  238. package/src/components/radio/radio.stories.js +54 -19
  239. package/src/components/review/review-grid.css.js +5 -6
  240. package/src/components/review/review-grid.js +14 -11
  241. package/src/components/review/review.css.js +11 -6
  242. package/src/components/review/review.js +3 -8
  243. package/src/components/shared/ensemble.css.js +93 -0
  244. package/src/components/shared/form-field.css.js +206 -0
  245. package/src/components/shared/prop-vocabulary.js +48 -0
  246. package/src/components/shared/token-scales.js +306 -0
  247. package/src/components/skeleton/skeleton.css.js +34 -0
  248. package/src/components/skeleton/skeleton.js +32 -0
  249. package/src/components/skeleton/skeleton.stories.js +70 -0
  250. package/src/components/spinner/spinner.css.js +62 -0
  251. package/src/components/spinner/spinner.js +38 -0
  252. package/src/components/spinner/spinner.stories.js +92 -0
  253. package/src/components/spotlight-section/spotlight-section.css.js +14 -13
  254. package/src/components/spotlight-section/spotlight-section.js +42 -39
  255. package/src/components/switch/switch.css.js +103 -0
  256. package/src/components/switch/switch.js +44 -0
  257. package/src/components/switch/switch.stories.js +86 -0
  258. package/src/components/tab-list/content-switcher.stories.js +189 -0
  259. package/src/components/tab-list/tab-list.css.js +250 -0
  260. package/src/components/tab-list/tab-list.js +184 -0
  261. package/src/components/tab-list/tab-list.stories.js +221 -0
  262. package/src/components/tabs/tabs.css.js +14 -123
  263. package/src/components/tabs/tabs.js +16 -19
  264. package/src/components/tabs/tabs.stories.js +95 -95
  265. package/src/components/tag/tag.css.js +54 -0
  266. package/src/components/tag/tag.js +32 -0
  267. package/src/components/tag/tag.stories.js +126 -0
  268. package/src/components/terminal/terminal-message.css.js +164 -60
  269. package/src/components/terminal/terminal-message.js +5 -5
  270. package/src/components/terminal/terminal-message.stories.js +70 -1
  271. package/src/components/termsOfUse/terms.css.js +25 -20
  272. package/src/components/termsOfUse/terms.js +13 -9
  273. package/src/components/termsOfUse/terms.stories.js +1 -1
  274. package/src/components/textarea/textarea.css.js +51 -0
  275. package/src/components/textarea/textarea.js +136 -0
  276. package/src/components/textarea/textarea.stories.js +191 -0
  277. package/src/components/tooltip/tooltip.css.js +75 -0
  278. package/src/components/tooltip/tooltip.js +156 -0
  279. package/src/components/tooltip/tooltip.stories.js +161 -0
  280. package/src/components/trust-panel/trust-panel.css.js +85 -33
  281. package/src/components/trust-panel/trust-panel.js +18 -10
  282. package/src/components/trust-panel/trust-panel.stories.js +1 -1
  283. package/src/components/ultimate-security-demo.stories.js +431 -0
  284. package/src/components/video/video.css.js +17 -0
  285. package/src/components/video/video.js +40 -0
  286. package/src/components/video/video.stories.js +106 -0
  287. package/src/components/video-player/video-player.css.js +67 -11
  288. package/src/components/video-player/video-player.js +35 -4
  289. package/src/components/video-player/video-player.stories.js +1 -1
  290. package/src/components/visual-alignment.stories.js +411 -0
  291. package/src/components/visually-hidden/visually-hidden.css.js +24 -0
  292. package/src/components/visually-hidden/visually-hidden.js +16 -0
  293. package/src/components/visually-hidden/visually-hidden.stories.js +56 -0
  294. package/src/stories/Button.stories.js +17 -17
  295. package/src/stories/Header.stories.js +10 -10
  296. package/src/stories/Page.js +1 -1
  297. package/src/stories/Page.stories.js +6 -6
  298. package/src/tokens/borders.stories.js +339 -0
  299. package/src/tokens/color-responsibilities.stories.js +227 -0
  300. package/src/tokens/dark-derivation.js +309 -0
  301. package/src/tokens/dark-findings.js +197 -0
  302. package/src/tokens/dark-mode.stories.js +207 -0
  303. package/src/tokens/dimension.stories.js +251 -0
  304. package/src/tokens/elevation.stories.js +143 -0
  305. package/src/tokens/focus.stories.js +276 -0
  306. package/src/tokens/fonts.css +20 -20
  307. package/src/tokens/fonts.stories.js +354 -374
  308. package/src/tokens/layout.css +25 -11
  309. package/src/tokens/motion.stories.js +231 -0
  310. package/src/tokens/shape.stories.js +272 -0
  311. package/src/tokens/spacing-rhythm.stories.js +359 -0
  312. package/src/tokens/spacing.stories.js +208 -703
  313. package/src/tokens/story-chrome.js +503 -0
  314. package/src/tokens/surfaces.stories.js +224 -0
  315. package/src/tokens/text-on-surface.stories.js +164 -0
  316. package/src/tokens/token-map.js +220 -0
  317. package/src/tokens/token-map.stories.js +257 -0
  318. package/src/tokens/token-purposes.js +266 -0
  319. package/src/tokens/tokens-grid.stories.js +388 -900
  320. package/src/tokens/tokens.css +1633 -1045
  321. package/src/tokens/tokens.js +1130 -492
  322. package/src/tokens/tokens.stories.js +137 -47
  323. package/src/tokens/type-ceiling-words.js +299 -0
  324. package/src/tokens/typography.stories.js +1003 -965
  325. package/src/tokens/vertical-rhythm.stories.js +224 -0
  326. package/src/components/awards/awards-icon.js +0 -56
  327. package/src/components/awards/awards-light.js +0 -240
  328. package/src/components/banner/banner.js +0 -60
  329. package/src/components/banner/banner.stories.js +0 -51
  330. package/src/components/button/icons.js +0 -24
  331. package/src/components/input/index.js +0 -0
  332. package/src/components/light-carousel/light-carousel-simple.css.js +0 -183
  333. package/src/components/light-carousel/light-carousel-simple.js +0 -74
  334. package/src/tokens/colors.js +0 -121
  335. package/src/tokens/typography.css.js +0 -207
  336. package/src/tokens/typography.js +0 -0
@@ -1,7 +1,28 @@
1
1
  import { css } from 'lit';
2
2
 
3
- export const tokens = css`
4
- :host {
3
+ /**
4
+ * Design tokens.
5
+ *
6
+ * These are declared on :root in document scope, inside the `bd-tokens` cascade
7
+ * layer, rather than on :host inside every component. Both halves of that
8
+ * matter:
9
+ *
10
+ * - A :host declaration beats an inherited value. While every component
11
+ * re-declared the full token set on its own :host, an application could not
12
+ * override a token from the page at all — its `:root` never reached the
13
+ * shadow tree.
14
+ * - Moving to :root is not sufficient on its own. adoptedStyleSheets are
15
+ * ordered after the document's own author stylesheets, so an unlayered
16
+ * :root here would still beat the application's CSS. Anything inside a
17
+ * cascade layer loses to unlayered CSS, which is what makes the override
18
+ * work.
19
+ *
20
+ * The upshot: an application can now do `:root { --color-blue-500: ... }`, or a
21
+ * whole `@media (prefers-color-scheme: dark)` block, and it wins.
22
+ *
23
+ * Importing any component installs this once. Nothing else is required.
24
+ */
25
+ const declarations = `
5
26
  --typography-fontFamily-sans: "IBM Plex Sans", system-ui, -apple-system,
6
27
  BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
7
28
  --typography-fontFamily-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular,
@@ -18,7 +39,7 @@ export const tokens = css`
18
39
  --typography-fontSize-4xl: 2.25rem; /* 36px */
19
40
  --typography-fontSize-5xl: 3rem; /* 48px */
20
41
  --typography-fontSize-6xl: 3.75rem; /* 60px */
21
- --typography-fontSize-7xl: 4.5rem; /* 72px */
42
+ --typography-fontSize-7xl: 4.5rem; /* 72px */
22
43
  --typography-fontSize-8xl: 6rem; /* 96px */
23
44
  --typography-fontSize-9xl: 8rem; /* 128px */
24
45
 
@@ -30,11 +51,44 @@ export const tokens = css`
30
51
 
31
52
  --typography-lineHeight-none: 1;
32
53
  --typography-lineHeight-tight: 1.25;
33
- --typography-lineHeight-snug: 1.375;
54
+ /* There is no snug (1.375). It was Tailwind's step, it carried the
55
+ heading roles until the leading was measured against IBM Plex Sans,
56
+ and once those moved to tight and 130 its only readers were four
57
+ documentation-chrome files setting 11px text. 11px is what
58
+ label-extra-small already answers, at normal, so they were repointed
59
+ there and the step was retired rather than kept alive by its own
60
+ documentation. */
34
61
  --typography-lineHeight-normal: 1.5;
35
62
  --typography-lineHeight-relaxed: 1.625;
36
63
  --typography-lineHeight-loose: 2;
37
64
 
65
+ /* The three steps above named for a density -- none .. loose -- are
66
+ Tailwind's default scale, values and names both, and nothing in them
67
+ was derived from IBM Plex Sans. These three were, and they are named
68
+ numerically because that is what the rest of this system does for a
69
+ scale step: --spacing-14, --dimension-44, --border-width-2.
70
+
71
+ 130 the font's own declared line box. hhea, OS/2 sTypo and OS/2
72
+ usWin in IBMPlexSans-*.woff2 all state 1.30, which is both
73
+ unusual and deliberate -- it is why the same text does not
74
+ change height between macOS and Windows.
75
+ 140 IBM's own published value for Plex at 20px (@carbon/type,
76
+ productive-heading-03 and expressive-heading-03).
77
+ 145 between IBM's 1.5 at 16px and 1.4 at 20px.
78
+
79
+ The floor under all of them is ink, not taste. The deepest glyphs in
80
+ this family are the Romanian comma-below letters -- s-comma and
81
+ t-comma, -0.275em in Bold, a third deeper than g -- and the tallest
82
+ are grave and acute capitals at +0.977em. Measured per weight, ink
83
+ spans 1.174 Light, 1.194 Regular, 1.219 Medium, 1.235 SemiBold,
84
+ 1.252 Bold. Nothing may be set below the floor for its weight, which
85
+ is why the display roles stay at tight and are not tightened further.
86
+ Foundations/Typography/Vertical Rhythm reaches 1.231 for 48px Bold
87
+ from canvas metrics; this is the same number from outline bounds. */
88
+ --typography-lineHeight-130: 1.3;
89
+ --typography-lineHeight-140: 1.4;
90
+ --typography-lineHeight-145: 1.45;
91
+
38
92
  --typography-letterSpacing-tighter: -0.05em;
39
93
  --typography-letterSpacing-tight: -0.025em;
40
94
  --typography-letterSpacing-normal: 0em;
@@ -80,6 +134,7 @@ export const tokens = css`
80
134
  --color-green-800: #0b6a26;
81
135
  --color-green-900: #084a1a;
82
136
  --color-green-950: #063c14;
137
+ --color-green-975: #011705;
83
138
 
84
139
  --color-yellow-50: #fffbf0;
85
140
  --color-yellow-100: #fff5d6;
@@ -92,6 +147,7 @@ export const tokens = css`
92
147
  --color-yellow-800: #855f00;
93
148
  --color-yellow-900: #5c4000;
94
149
  --color-yellow-950: #473100;
150
+ --color-yellow-975: #1a0f00;
95
151
 
96
152
  --color-neutral-0: #ffffff;
97
153
  --color-neutral-25: #fafafa;
@@ -100,11 +156,38 @@ export const tokens = css`
100
156
  --color-neutral-200: #c5c9ce;
101
157
  --color-neutral-300: #a6adb4;
102
158
  --color-neutral-400: #88909b;
103
- --color-neutral-500: #6c7580;
159
+ /* The five in-between steps -- 450, 850, 925 here, and the two 975s
160
+ above -- exist for a dark theme, and nothing in light reads them.
161
+ The dark mode study (Foundations/Theming/Dark mode) found the ramps
162
+ too coarse at the dark end: container and subtle had to share
163
+ neutral-900 (finding 2), no step kept text-subtle both quieter than
164
+ muted and over 4.5:1 (finding 3), and green-950 and yellow-950 sat
165
+ 1.5:1 off the dark page where every other tint sits near 1.1
166
+ (finding 5). Each is interpolated in OKLCH between its two
167
+ neighbours to the ratio the derivation asks for: 450 is 4.75:1 on
168
+ a dark card (4.93 on the dark page), midway between 500 and 400 at
169
+ 1.22:1 from each -- subtle text sits on a page or a card, so it is
170
+ measured on the card (decision 17); 925 and 850 are 1.04 and 1.12
171
+ off it, the light container and subtle steps; the 975s are 1.04
172
+ and 1.03, the light tints. Measured against neutral-950, the dark
173
+ page, in src/tokens/dark-derivation.js. */
174
+ --color-neutral-450: #78818c;
175
+ /* #6a737e, moved one step from #6c7580. neutral-500 is
176
+ --color-text-subtle, and at #6c7580 subtle text on a card
177
+ (--color-surface-container, neutral-25) measured 4.48:1 -- under
178
+ 4.5 on the ground most subtle text sits on. #6a737e gives 4.61 on a
179
+ card and 4.81 on the page, and stays a clear step above
180
+ neutral-600 (1.37:1 apart, the gap was 1.39). It does not clear
181
+ subtle on surface-subtle or the status tints (4.29 to 4.46):
182
+ that needs a step this ramp does not have, and is its own
183
+ decision. */
184
+ --color-neutral-500: #6a737e;
104
185
  --color-neutral-600: #565e66;
105
186
  --color-neutral-700: #41474d;
106
187
  --color-neutral-800: #2c3035;
188
+ --color-neutral-850: #181b1d;
107
189
  --color-neutral-900: #151719;
190
+ --color-neutral-925: #111214;
108
191
  --color-neutral-950: #0c0d0e;
109
192
  --color-neutral-1000: #000000;
110
193
 
@@ -132,20 +215,19 @@ export const tokens = css`
132
215
  ================================ */
133
216
 
134
217
 
135
- --dimension-2px: 2px;
136
- --dimension-4px: 4px;
137
- --dimension-8px: 8px;
138
- --dimension-12px: 12px;
139
- --dimension-16px: 16px;
140
- --dimension-20px: 20px;
141
- --dimension-24px: 24px;
142
- --dimension-32px: 32px;
143
- --dimension-34px: 34px;
144
- --dimension-40px: 40px;
145
- --dimension-45px: 45px;
146
- --dimension-48px: 48px;
147
- --dimension-55px: 55px;
148
- --dimension-64px: 64px;
218
+ --dimension-4: 4px;
219
+ --dimension-8: 8px;
220
+ --dimension-12: 12px;
221
+ --dimension-16: 16px;
222
+ --dimension-20: 20px;
223
+ --dimension-24: 24px;
224
+ --dimension-32: 32px;
225
+ --dimension-36: 36px;
226
+ --dimension-40: 40px;
227
+ --dimension-44: 44px;
228
+ --dimension-48: 48px;
229
+ --dimension-56: 56px;
230
+ --dimension-64: 64px;
149
231
 
150
232
  /* ================================
151
233
  CORE TRANSITION TOKENS
@@ -158,6 +240,13 @@ export const tokens = css`
158
240
 
159
241
  --transition-easing-linear: linear;
160
242
  --transition-easing-smooth: ease-in-out;
243
+ /* The CSS default curve. It was missing, and its absence was the single
244
+ reason eleven component transitions could not be expressed in tokens:
245
+ ease is not ease-in-out — it front-loads, and swapping one for the
246
+ other is a visible change. Named after the keyword, as linear,
247
+ ease-in and ease-out beside it are; smooth is the outlier that
248
+ aliases a keyword rather than naming it. */
249
+ --transition-easing-ease: ease;
161
250
  --transition-easing-ease-in: ease-in;
162
251
  --transition-easing-ease-out: ease-out;
163
252
  --transition-easing-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
@@ -190,55 +279,611 @@ export const tokens = css`
190
279
  --focus-outline-success: 0 0 0 2px var(--color-neutral-0),
191
280
  0 0 0 4px var(--color-green-500);
192
281
 
282
+ /* The four above put the WHITE band against the control, which is what
283
+ keeps them visible on a coloured hero. That fails on a control which
284
+ is itself white: bd-button-link variant="white" measured 1.00:1 for
285
+ the inner band against its own fill and 2.38:1 for the blue band
286
+ against a blue hero, so neither band reached the 3:1 WCAG 2.4.11
287
+ asks of a focus indicator. This one inverts the pair -- dark band
288
+ against the control, white band against the ground -- so the inner
289
+ band carries the contrast whatever the control is sitting on. */
290
+ --focus-outline-inverse: 0 0 0 2px var(--color-neutral-900),
291
+ 0 0 0 4px var(--color-neutral-0);
292
+
193
293
  /* Traditional Outline Values for Fallback */
194
294
  --focus-outline-traditional: 2px solid var(--color-blue-500);
195
295
  --focus-outline-offset: 2px;
196
296
 
297
+ /* The ring is a box-shadow, so it already follows the radius of the
298
+ element it is on. This is the default for elements that have no
299
+ radius of their own — a link, a tab, an accordion header — so the
300
+ ring is not a hard rectangle. An element with its own radius should
301
+ set nothing and let the ring follow its shape. */
302
+ --focus-outline-radius: var(--radius-sm);
303
+
304
+ /* ================================
305
+ SEMANTIC ROLE - SELECTED INDICATOR
306
+ ================================
307
+ What marks the current item in a set. Two components already drew it
308
+ — Components/Tab List and the anchor nav — and they drifted apart on
309
+ easing while both used valid tokens. The role is named here so there
310
+ is one answer, not two spellings.
311
+
312
+ THE COLOUR IS NOT NAMED FOR A SHAPE, and that is the whole point of
313
+ the split below. It was --indicator-underline-color, which described
314
+ the underline three components happen to draw rather than the job the
315
+ value does. awards-flex marks its current slide with a DOT: same job,
316
+ same blue-500, and it could not adopt the token because a dot is not
317
+ an underline, so it reached for the primitive instead. Vocabulary
318
+ question E1 found that — see plans/reports/phase-2-vocab-e-has-role-pass.md.
319
+
320
+ Renamed with no back-compat alias, following the same convention as
321
+ --typography-label-xsmall becoming --typography-body-xsmall: rename
322
+ cleanly and say why here.
323
+
324
+ The geometry stays underline-specific, because it genuinely is — a
325
+ 3px height and a width transition describe a rule sliding under a
326
+ tab, and a dot scales instead. A fourth consumer needing dot
327
+ geometry should name it for the dot, not borrow these. */
328
+ --indicator-color: var(--color-blue-500);
329
+ --indicator-underline-height: var(--border-width-3);
330
+ --indicator-underline-duration: var(--transition-duration-slow);
331
+ --indicator-underline-easing: var(--transition-easing-smooth);
332
+
333
+ /* ================================
334
+ SEMANTIC ROLE - CARD SHAPE
335
+ ================================
336
+ The corner and the inset that make a card a card here. Eleven
337
+ card-shaped objects agree on both -- the Card box, CardFeatures,
338
+ LightCarousel, Bento, the Awards feature card, the TrustPanel card,
339
+ the CompareSection card, the Carousel item, the Comparison Panel
340
+ column and both Pricing cards -- and they only agree because the two
341
+ that did not were brought in one at a time.
342
+
343
+ This role is named now and was deliberately not named before, and
344
+ the difference is evidence rather than appetite. CLAUDE.md said
345
+ there was no semantic radius scale because "the components do not
346
+ agree closely enough for a shared role to be honest", which was true
347
+ when it was written: the compare card sat at --radius-3xl and the
348
+ carousel item at --radius-3xl with a 2em inset. Both have since been
349
+ measured, argued and moved. A role declared then would have imposed
350
+ a consensus; declared now it records one.
351
+
352
+ It is a role, not a scale. There is still no --card-* family and no
353
+ semantic step between radii -- naming the job is the whole point,
354
+ and the same reasoning is why --panel-radius is NOT here: the eight
355
+ --radius-xl consumers are a code block, a bar track, a table
356
+ container, a discount pill, two public image variants, a modal and a
357
+ terminal. That is not a family, and calling it one would be the
358
+ mistake this comment is describing.
359
+
360
+ scripts/card-shapes.json lists the eleven and lint:tokens checks it
361
+ both ways, so a card that stops using the role and a registry entry
362
+ that stops matching are both findings. */
363
+ --card-radius: var(--radius-2xl);
364
+ --card-padding: var(--spacing-32);
365
+
366
+ /* The inset a card takes when the viewport is too narrow to afford the
367
+ full one. Four cards reached for this independently before it had a
368
+ name -- Bento below sm, the Comparison Panel and Trust Panel below
369
+ md, and LightCarousel with an em literal -- and three of the four
370
+ picked exactly 24. That is the agreement that makes it a role rather
371
+ than four local decisions.
372
+
373
+ Two steps, not a scale. A card is either affording its inset or it
374
+ is not; there is no third size, and a component that wants one is
375
+ asking a design question rather than a token question. */
376
+ --card-padding-compact: var(--spacing-24);
377
+
378
+ /* ================================
379
+ SEMANTIC ROLES - COLOUR
380
+ ================================
381
+ The tier the architecture always described and the components never
382
+ had. Measured before this landed: 71.8% of the colour references in
383
+ the atom layer pointed straight at a palette step — 38 distinct
384
+ primitives across 233 references, where the roles underneath them
385
+ number about thirty. That gap is why four text atoms ended up with
386
+ four different inks, and why two components disagreed about which
387
+ green means "success".
388
+
389
+ None of these are new decisions. Every value is the step something
390
+ already uses, and the text roles are the ones COLOR_ROLES in
391
+ src/components/shared/token-scales.js had already chosen as clearing
392
+ 4.5:1 on white — that map should now resolve through these rather
393
+ than reaching past them into the palette.
394
+
395
+ Reach for the role, not the step. The palette stays available for
396
+ what no role covers yet; when that happens, add the role.
397
+
398
+ ---
399
+
400
+ NAME COLLISION WITH src/blog. The blog sub-system already declares
401
+ seven of these names with different values:
402
+
403
+ --color-text-secondary here neutral-700 blog neutral-600
404
+ --color-text-muted here neutral-600 blog neutral-500
405
+ --color-surface-brand here blue-500 blog blue-50 (!)
406
+ --color-surface-inverse here neutral-900 blog neutral-950
407
+ --color-border-default here neutral-400 blog neutral-300
408
+ --color-border-subtle here neutral-200 blog neutral-100
409
+ --color-border-strong here neutral-900 blog neutral-600
410
+
411
+ This is safe today and must not be assumed safe. The blog applies
412
+ its tokens on :host inside each blog component, so inside a blog
413
+ shadow tree the blog value wins, and outside it never applies.
414
+ Nothing in this system renders inside that tree.
415
+
416
+ It stops being safe if a page ever loads src/blog/tokens.css
417
+ globally alongside this file. That file is an unlayered :root block,
418
+ and unlayered CSS beats a cascade layer, so it would win — silently
419
+ turning --color-surface-brand from a saturated fill into a pale
420
+ tint under any component that had adopted it.
421
+
422
+ The two files share 616 names and disagree on 82 of them; all 58
423
+ shared palette primitives are identical, so the fork is entirely at
424
+ this tier and above. Reconciling it is its own piece of work. Until
425
+ it happens, do not load both globally. */
426
+
427
+ /* Text. -primary is the default ink for anything that reads as body
428
+ or heading copy. -brand is blue-600 rather than blue-500 on purpose:
429
+ blue-500 is 4.53:1 on pure white and fails on any tint. */
430
+ --color-text-primary: var(--color-neutral-900);
431
+ --color-text-secondary: var(--color-neutral-700);
432
+ --color-text-muted: var(--color-neutral-600);
433
+ /* Subtle sits on the page or a card, and nowhere else: on a band
434
+ (surface-subtle, surface-muted) or a status tint, step up to
435
+ -muted. No value can be both quieter than muted and 4.5:1 on every
436
+ ground -- on surface-muted it would have to be muted -- so the rule
437
+ is where it goes, not how dark it is. Dark mode decision 17. */
438
+ --color-text-subtle: var(--color-neutral-500);
439
+ --color-text-disabled: var(--color-neutral-400);
440
+ --color-text-inverse: var(--color-neutral-0);
441
+ /* Subordinate text on an inverse surface. --color-text-inverse is the
442
+ high-emphasis step there; this is the one below it, and until now the
443
+ system had no such step at all -- the light-side -secondary, -muted and
444
+ -subtle roles invert to 1.91, 2.73 and 3.85 against
445
+ --color-surface-inverse and cannot be used.
446
+
447
+ Two components had already invented it independently and spelled it
448
+ differently: bd-terminal-message with rgba(255,255,255,0.5) and
449
+ bd-process-steps with --color-neutral-300 twice. neutral-300 is the
450
+ value here because it is the portable one. Measured on the three
451
+ inverse grounds in use -- neutral-900, neutral-800 and the #0b1626 the
452
+ process-steps gradient ends on -- it reads 7.92, 5.85 and 8.00, while
453
+ the white-at-half-alpha spelling reads 5.27, 3.89 and 5.32 and only
454
+ clears 4.5 because the terminal's ground happens to be the darkest of
455
+ the three.
456
+
457
+ There is deliberately no -secondary-inverse: bd-cta-banner has a third
458
+ tier at white 0.85 and it is one consumer, which is not a
459
+ responsibility. An earlier draft of this note also called it a
460
+ contrast failure, from a worst-case calculation against the lightest
461
+ stop in its gradient. Sampling the pixels actually behind the text
462
+ disproved that -- the radial highlight sits at 78% -10% and is mostly
463
+ outside the banner, so the three tiers measure 4.60, 4.57 and 4.46 at
464
+ their worst pixel rather than the 3.35, 2.84 and 2.13 the arithmetic
465
+ predicted. The tier is fine; it is still not a role. */
466
+ --color-text-muted-inverse: var(--color-neutral-300);
467
+ /* Ink on --color-surface-media, fixed with it: a part that does not
468
+ switch needs ink that does not switch either (the white button
469
+ preset, dark mode finding 8). text-inverse and text-muted-inverse
470
+ flip in a dark theme and would put dark ink on the dark frame --
471
+ a video play icon at 3.13:1, a terminal's quieter lines at 1.91. */
472
+ --color-text-on-media: var(--color-neutral-0);
473
+ --color-text-on-media-muted: var(--color-neutral-300);
474
+ --color-text-brand: var(--color-blue-600);
475
+ /* Brand text, hovered: a link or a citation reference under the
476
+ pointer. Before this role existed, the hover read
477
+ --color-action-primary-active -- a pressed button fill used as ink.
478
+ The value is the same blue-700, so nothing moves in this theme; the
479
+ difference is that a fill and a text colour answer different
480
+ questions, and a dark theme has to answer them differently. Measured
481
+ in the Figma Dark mode study: the fill stays put on a dark page and
482
+ the link it borrowed falls to 2.27:1. */
483
+ --color-text-brand-hover: var(--color-blue-700);
484
+ --color-text-success: var(--color-green-800);
485
+ --color-text-warning: var(--color-yellow-800);
486
+ --color-text-error: var(--color-red-700);
487
+
488
+ /* Surfaces. The feedback four are the tints a message sits on, each
489
+ measured against the matching --color-text-* step above: success
490
+ 6.42, warning 5.59, error 6.83, info 5.72.
491
+
492
+ -brand is the one to watch. --color-text-inverse on it measures
493
+ 4.53:1 — it passes, with no headroom at all, and it is the same
494
+ blue-500 the contrast note warns about elsewhere. White on brand is
495
+ fine for a button label; do not build a tinted variant on top of it
496
+ without re-measuring. */
497
+ --color-surface-default: var(--color-neutral-0);
498
+ /* The ground a self-contained content object sits on: one step off the
499
+ page, so a card, tile or panel reads as its own object without
500
+ needing a border or a shadow to say so. Twelve components were
501
+ reaching past the surface family for this, eight of them declaring
502
+ the same local alias, because the role did not exist.
503
+
504
+ Container is not subtle. Subtle groups things that already belong
505
+ together and is meant to recede; container is the default ground of
506
+ an object that stands on its own. They also differ visibly -- the
507
+ step between them is the difference between an object on the page
508
+ and a wash behind a group -- so they are not interchangeable.
509
+
510
+ The name means a visual container for content. It has nothing to do
511
+ with CSS container queries. */
512
+ --color-surface-container: var(--color-neutral-25);
513
+ --color-surface-subtle: var(--color-neutral-50);
514
+ --color-surface-muted: var(--color-neutral-100);
515
+ --color-surface-inverse: var(--color-neutral-900);
516
+ /* The frame around media: a video letterbox, a terminal panel. Dark in
517
+ every theme, because what it frames is dark -- which is why it is
518
+ not --color-surface-inverse, the polarity flip, that both used to
519
+ read. Inverse turns light in a dark theme; a letterbox must not,
520
+ and the terminal's own palette (blue-400, the traffic-light green)
521
+ is only legible on a dark ground. Dark mode decision 15. */
522
+ --color-surface-media: var(--color-neutral-900);
523
+ --color-surface-brand: var(--color-blue-500);
524
+ --color-surface-disabled: var(--color-neutral-100);
525
+ /* The ground of a control while it is held down, one step below
526
+ -muted (its hover). bd-back read neutral-200 for this and said in a
527
+ no-token comment that the surface roles stopped at -muted; reading
528
+ the primitive meant it could not follow a theme, and on a dark page
529
+ it measured 1.48:1 under a label that did (dark mode finding 14). */
530
+ --color-surface-pressed: var(--color-neutral-200);
531
+ --color-surface-success: var(--color-green-50);
532
+ --color-surface-warning: var(--color-yellow-50);
533
+ --color-surface-error: var(--color-red-50);
534
+ --color-surface-info: var(--color-blue-50);
535
+
536
+ /* Borders, measured on --color-surface-default. Only the two that
537
+ bound a live control clear the 3:1 WCAG 1.4.11 asks of a UI
538
+ component: -default at 3.23 and -brand at 4.53.
539
+
540
+ -subtle (1.66) and -disabled (2.27) sit below it on purpose.
541
+ -subtle is a decorative hairline between things that already belong
542
+ together, not the edge of anything actionable, and disabled controls
543
+ are exempt under 1.4.3. Do not reach for -subtle to outline a
544
+ control. */
545
+ --color-border-subtle: var(--color-neutral-200);
546
+ --color-border-default: var(--color-neutral-400);
547
+ --color-border-strong: var(--color-neutral-900);
548
+ --color-border-brand: var(--color-blue-500);
549
+ --color-border-disabled: var(--color-neutral-300);
550
+
551
+ /* The boundary of a self-contained object -- a card, a panel, a
552
+ navigation band -- drawn only so the object is perceptible, never so
553
+ it looks operable. It is the border counterpart of
554
+ --color-surface-container, and the two answer the same question from
555
+ opposite sides: is this its own thing.
556
+
557
+ It exists because eight components needed it and none could ask for
558
+ it. Seven reached past the roles to --color-neutral-100 directly and
559
+ one more declared a private --border-card-grey alias in seven files
560
+ to say --color-neutral-50. Measured on --color-surface-default it is
561
+ 1.29:1, and on --color-surface-container 1.23:1 -- deliberately below
562
+ -subtle's 1.66, because -subtle is already spoken for as the hairline
563
+ between things that belong together, and a long way below the 3:1
564
+ WCAG 1.4.11 asks of anything actionable. That is the point: an object
565
+ boundary is not a control edge, and must not read as one.
566
+
567
+ -inverse is the same responsibility on an inverse surface, the way
568
+ --color-text-inverse and --color-surface-inverse are, and not a
569
+ second role. It is spelled as a proportion of --color-neutral-0
570
+ rather than a raw rgba for the same reason
571
+ --button-outline-white-border-default is: the value stays traceable
572
+ to a token when the palette moves. Over --color-surface-inverse it
573
+ measures 1.42:1, which puts it in the same perceptual band as the
574
+ light value rather than at the 10.8:1 --color-border-subtle would
575
+ give there.
576
+
577
+ This role says what a low-emphasis boundary looks like. It does not
578
+ say whether a component should have one -- that stays a component
579
+ decision, and the evidence says the components genuinely disagree. */
580
+ --color-border-container: var(--color-neutral-100);
581
+ --color-border-container-inverse: color-mix(in srgb, var(--color-neutral-0) 12%, transparent);
582
+
583
+ /* Action. The interactive ramp, one step down the palette per state.
584
+ accent is this system's conversion colour — Buy now and the like —
585
+ and is not a danger signal; see the bd-button story notes. */
586
+ --color-action-primary: var(--color-blue-500);
587
+ --color-action-primary-hover: var(--color-blue-600);
588
+ --color-action-primary-active: var(--color-blue-700);
589
+ --color-action-accent: var(--color-red-600);
590
+ --color-action-accent-hover: var(--color-red-700);
591
+ --color-action-accent-active: var(--color-red-800);
592
+ /* The neutral action fill: the secondary button, and the ghost
593
+ button's pressed state. Both read neutral-950 / 800 / 1000 straight
594
+ from the palette, which no theme can reach -- and the dark page is
595
+ neutral-950 too, so in a dark theme the fill would vanish into it
596
+ (dark mode finding 8). A role gives the fill a value of its own in
597
+ each theme. */
598
+ --color-action-secondary: var(--color-neutral-950);
599
+ --color-action-secondary-hover: var(--color-neutral-800);
600
+ --color-action-secondary-active: var(--color-neutral-1000);
601
+ /* Ink on an action fill: a button label, a checkmark, a radio dot, a
602
+ switch thumb. Before this role existed the buttons read
603
+ --color-text-inverse ("text on an inverse surface") and the controls
604
+ read --color-surface-default ("the page"). Both resolve to white in
605
+ this theme, which is why the borrowing never showed -- and both must
606
+ flip to near-black in a dark one, which would turn every label and
607
+ mark dark on its blue or red fill. White clears every action fill:
608
+ 4.53 to 10.51:1. Measured in the Figma Dark mode study. */
609
+ --color-action-on-fill: var(--color-neutral-0);
610
+
611
+ /* ================================
612
+ SEMANTIC ROLES - STACKING
613
+ ================================
614
+ Thirty z-index declarations across eight values that were never
615
+ chosen relative to one another: -1, 1, 2, 10, 200, 1000, 1001 and
616
+ 9999. Both bd-modal and the carousel sit on 9999, so there is no
617
+ answer to which of the two wins — that is what an unscaled ladder
618
+ costs.
619
+
620
+ The rungs are far apart on purpose, so a component can take a rung
621
+ and still stack its own internals inside it with a calc. A tooltip
622
+ opened from inside a dialog has to clear the dialog, which is why
623
+ it is the top rung rather than the first. */
624
+ --z-below: -1;
625
+ --z-base: 1;
626
+ --z-raised: 2;
627
+ --z-sticky: 200;
628
+ --z-overlay: 1000;
629
+ --z-modal: 1100;
630
+ --z-tooltip: 1200;
631
+
632
+ /* ================================
633
+ SEMANTIC ROLES - OPACITY
634
+ ================================
635
+ One role, because one role is what the evidence supports. The dim
636
+ end of a reduced-motion pulse is spelled three times in the atom
637
+ layer — bd-button and bd-spinner at 0.4, bd-skeleton at 0.45 — and
638
+ is the same intent each time: a mark that cannot spin should still
639
+ read as busy.
640
+
641
+ Disabled is deliberately absent. This system expresses disabled with
642
+ the neutral ramp rather than by dimming, and the one token that said
643
+ otherwise (--button-shared-disabled-opacity: 0.6) is unused. */
644
+ --opacity-pulse: 0.4;
645
+
646
+ /* ================================
647
+ SEMANTIC ROLE - STATUS TONES
648
+ ================================
649
+ bd-badge, bd-tag and bd-alert all say the same six things and each
650
+ spelled them out for itself. The text colours already matched across
651
+ badge and tag — the tag story says so — but by coincidence rather
652
+ than construction, and the one place they had drifted proves the
653
+ point: alert drew its success rule in green-600 where badge's
654
+ success dot used green-700.
655
+
656
+ Seven roles, because the three components need different weights of
657
+ the same tone:
658
+
659
+ -text ink on a tinted chip badge shade, tag
660
+ -surface the light tint tag, alert
661
+ -surface-strong the heavier tint badge shade
662
+ -border the hairline tag
663
+ -accent the saturated step badge dot, alert rule
664
+ -fill / -on-fill a saturated chip and its ink badge fill
665
+
666
+ What is deliberately *not* unified: a tag carries a border and sits
667
+ on -surface, a badge has no border and sits on -surface-strong. That
668
+ difference is documented in the tag story — the lighter fill is what
669
+ lets the tag's border read — so it is expressed as two roles rather
670
+ than flattened into one.
671
+
672
+ Brand has no -surface-strong. It would be blue-100, which is
673
+ info's, so a brand badge on it could not be told from an info badge.
674
+ Brand's badge sits on -surface like its tag, and that one collision
675
+ is the whole exception. */
676
+
677
+ --status-neutral-text: var(--color-neutral-700);
678
+ --status-neutral-surface: var(--color-neutral-50);
679
+ --status-neutral-surface-strong: var(--color-neutral-100);
680
+ --status-neutral-border: var(--color-neutral-200);
681
+ --status-neutral-accent: var(--color-neutral-700);
682
+ --status-neutral-fill: var(--color-neutral-900);
683
+ --status-neutral-on-fill: var(--color-neutral-0);
684
+
685
+ --status-brand-text: var(--color-blue-600);
686
+ --status-brand-surface: var(--color-blue-50);
687
+ --status-brand-border: var(--color-blue-200);
688
+ --status-brand-accent: var(--color-blue-600);
689
+ --status-brand-fill: var(--color-blue-500);
690
+ --status-brand-on-fill: var(--color-neutral-0);
691
+
692
+ --status-info-text: var(--color-blue-600);
693
+ --status-info-surface: var(--color-blue-50);
694
+ --status-info-surface-strong: var(--color-blue-100);
695
+ --status-info-border: var(--color-blue-200);
696
+ --status-info-accent: var(--color-blue-500);
697
+ --status-info-fill: var(--color-blue-500);
698
+ --status-info-on-fill: var(--color-neutral-0);
699
+
700
+ --status-success-text: var(--color-green-800);
701
+ --status-success-surface: var(--color-green-50);
702
+ --status-success-surface-strong: var(--color-green-100);
703
+ --status-success-border: var(--color-green-200);
704
+ --status-success-accent: var(--color-green-700);
705
+ --status-success-fill: var(--color-green-700);
706
+ --status-success-on-fill: var(--color-neutral-0);
707
+
708
+ --status-warning-text: var(--color-yellow-800);
709
+ --status-warning-surface: var(--color-yellow-50);
710
+ --status-warning-surface-strong: var(--color-yellow-100);
711
+ --status-warning-border: var(--color-yellow-200);
712
+ --status-warning-accent: var(--color-yellow-700);
713
+ --status-warning-fill: var(--color-yellow-500);
714
+ --status-warning-on-fill: var(--color-neutral-900);
715
+
716
+ --status-error-text: var(--color-red-700);
717
+ --status-error-surface: var(--color-red-50);
718
+ --status-error-surface-strong: var(--color-red-100);
719
+ --status-error-border: var(--color-red-200);
720
+ --status-error-accent: var(--color-red-600);
721
+ --status-error-fill: var(--color-red-600);
722
+ --status-error-on-fill: var(--color-neutral-0);
723
+
197
724
  /* ================================
198
725
  COMPONENT TOKENS - TYPOGRAPHY
199
726
  ================================ */
200
727
 
728
+
729
+ /* ===============================================================
730
+ FLUID TYPE - the roles that cannot hold their size on a phone.
731
+
732
+ WHY. Measured in the Figma file (page "Type on mobile"), the
733
+ binding constraint on large type is not how many lines a sentence
734
+ takes, it is whether the longest UNBREAKABLE WORD fits the column.
735
+ The column is the viewport minus twice --grid-padding-*, so a 320px
736
+ phone gives type 288px -- 10% of the screen is gutter. Against that
737
+ column the ceiling for the worst realistic compound (Finnish
738
+ "Henkilollisyyssuoja", 9.07px of width per px of size) is 31px.
739
+ heading-h1 is 48. It overflowed at 375 as well as at 320, in German
740
+ and Finnish both.
741
+
742
+ WHY NOT A BREAKPOINT STEP. Tried first and abandoned, measured:
743
+ stepping h1 to 36 below sm still overflows at 320 (needs 327 of
744
+ 288) and changes nothing at 576-767 where the ceiling is already 54.
745
+ The constraint moves continuously with the column, because the
746
+ column is a function of viewport width -- a step function
747
+ approximates a line with a staircase. clamp() states the line.
748
+
749
+ THE WINDOW IS 320 -> 992. The minimum is reached at 320 and the
750
+ maximum at 992, which is the lg breakpoint. Above lg NOTHING
751
+ changes from the sizes this system shipped with, so every desktop
752
+ layout that has been reviewed stays exactly as reviewed, and the
753
+ whole change is confined to the widths where the failures were
754
+ measured.
755
+
756
+ THE MIDDLE TERM CARRIES A rem, AND THAT IS NOT OPTIONAL.
757
+ clamp(2rem, 4vw, 3rem) is the spelling everyone reaches for and it
758
+ breaks text resize: a size depending only on viewport width ignores
759
+ the reader's own font-size setting, which is WCAG 1.4.4. With a rem
760
+ in the middle term the type still grows when the reader enlarges
761
+ it. This package already refuses the equivalent mistake twice --
762
+ line-height is always unitless, and --spacing-* is rem precisely so
763
+ boxes grow with the text.
764
+
765
+ THE TWO LITERALS PER ROLE ARE DERIVED, NOT CHOSEN. For a role
766
+ running from min m to max M across the window V1..V2:
767
+
768
+ slope = (M - m) / (V2 - V1) px of size per px of width
769
+ vw term = slope x 100
770
+ rem term = (m - slope x V1) / 16
771
+
772
+ With V1=320 and V2=992 that is (M-m)/6.72 vw and
773
+ (m - (M-m)/2.1) / 16 rem. Every pair below is that arithmetic and
774
+ nothing else; each line states its own m and M so it can be checked
775
+ without re-deriving. Changing the mobile ladder means changing the
776
+ -fontSizeMin token and re-running the two formulas.
777
+
778
+ THE MOBILE LADDER IS THE DESIGN DECISION HERE, and it is these six
779
+ numbers: 30 27 24 21 18 16 for h1 through h6. Its ratios are 1.11,
780
+ 1.13, 1.14, 1.17, 1.13 -- near-even, against the desktop ladder's
781
+ irregular 1.33, 1.13, 1.33, 1.20, 1.11. A phone has less range to
782
+ spend (16 to the 31px ceiling, a factor of 1.94, where desktop has
783
+ 18 to 48, a factor of 2.67), so the ladder has to compress, and
784
+ compressing it evenly is what keeps six levels distinguishable.
785
+ Measured: h1 at 30 needs 272px of a 288px column, so there is 16px
786
+ of margin for a word longer than any tested.
787
+
788
+ WHAT IS DELIBERATELY NOT FLUID.
789
+ - Every role at 24px and below except body-lead. Nothing that small
790
+ overflows, and the small end's own failure is measure, not
791
+ overflow -- see below.
792
+ - display-lg, display-xl, display-2xl, display-3xl (60 to 128px).
793
+ Above display-md the family has NO mobile identity
794
+ to give it: five roles would all have to squeeze between 30 and
795
+ the 31px ceiling. They are desktop-only, and a component that
796
+ needs one of them on a phone should reach for a heading role
797
+ instead. Recorded rather than papered over.
798
+ - body-regular at 16px. It runs 40 characters per line on a 288px
799
+ column, which the study first called "below the comfortable
800
+ 45-75 band" -- and that band is a print and desktop yardstick.
801
+ Reaching 45 CPL on a 288px column needs 14.2px body text, and 50
802
+ CPL needs 12.8px; applying it to a phone trades the 16px
803
+ accessibility floor for a soft readability target. 40 characters
804
+ is what every phone does. body-lead is the one prose role that
805
+ does move, 20 -> 18, because at 20 it ran 32 characters -- five
806
+ or six words a line -- and an intro paragraph has no claim on
807
+ being the largest thing on a small screen.
808
+ =============================================================== */
809
+
201
810
  /* Heading Tokens */
202
- --typography-heading-h1-fontSize: var(--typography-fontSize-5xl);
811
+ /* fluid: 30px at 320 -> 48px at 992. (48-30)/6.72=2.6786vw, (30-(48-30)/2.1)/16=1.33929rem */
812
+ --typography-heading-h1-fontSizeMin: 1.875rem; /* 30px */
813
+ --typography-heading-h1-fontSize: clamp(
814
+ var(--typography-heading-h1-fontSizeMin),
815
+ calc(1.33929rem + 2.6786vw),
816
+ var(--typography-fontSize-5xl)
817
+ );
203
818
  --typography-heading-h1-fontWeight: var(--typography-fontWeight-bold);
204
- --typography-heading-h1-lineHeight: var(--typography-lineHeight-snug);
819
+ --typography-heading-h1-lineHeight: var(--typography-lineHeight-tight);
205
820
  --typography-heading-h1-letterSpacing: var(--typography-letterSpacing-normal);
206
821
  --typography-heading-h1-fontFamily: var(--typography-fontFamily-sans);
207
822
 
208
- --typography-heading-h2-fontSize: var(--typography-fontSize-4xl);
823
+ /* fluid: 27px at 320 -> 36px at 992. (36-27)/6.72=1.3393vw, (27-(36-27)/2.1)/16=1.41964rem */
824
+ --typography-heading-h2-fontSizeMin: 1.6875rem; /* 27px */
825
+ --typography-heading-h2-fontSize: clamp(
826
+ var(--typography-heading-h2-fontSizeMin),
827
+ calc(1.41964rem + 1.3393vw),
828
+ var(--typography-fontSize-4xl)
829
+ );
209
830
  --typography-heading-h2-fontWeight: var(--typography-fontWeight-bold);
210
- --typography-heading-h2-lineHeight: var(--typography-lineHeight-snug);
831
+ --typography-heading-h2-lineHeight: var(--typography-lineHeight-tight);
211
832
  --typography-heading-h2-letterSpacing: var(--typography-letterSpacing-normal);
212
833
  --typography-heading-h2-fontFamily: var(--typography-fontFamily-sans);
213
834
 
214
- --typography-heading-h3-fontSize: var(--typography-fontSize-3xl);
835
+ /* fluid: 24px at 320 -> 32px at 992. (32-24)/6.72=1.1905vw, (24-(32-24)/2.1)/16=1.26190rem */
836
+ --typography-heading-h3-fontSizeMin: 1.5rem; /* 24px */
837
+ --typography-heading-h3-fontSize: clamp(
838
+ var(--typography-heading-h3-fontSizeMin),
839
+ calc(1.26190rem + 1.1905vw),
840
+ var(--typography-fontSize-3xl)
841
+ );
215
842
  --typography-heading-h3-fontWeight: var(--typography-fontWeight-semibold);
216
- --typography-heading-h3-lineHeight: var(--typography-lineHeight-snug);
843
+ --typography-heading-h3-lineHeight: var(--typography-lineHeight-130);
217
844
  --typography-heading-h3-letterSpacing: var(--typography-letterSpacing-normal);
218
845
  --typography-heading-h3-fontFamily: var(--typography-fontFamily-sans);
219
846
 
220
- --typography-heading-h4-fontSize: var(--typography-fontSize-2xl);
847
+ /* fluid: 21px at 320 -> 24px at 992. (24-21)/6.72=0.4464vw, (21-(24-21)/2.1)/16=1.22321rem */
848
+ --typography-heading-h4-fontSizeMin: 1.3125rem; /* 21px */
849
+ --typography-heading-h4-fontSize: clamp(
850
+ var(--typography-heading-h4-fontSizeMin),
851
+ calc(1.22321rem + 0.4464vw),
852
+ var(--typography-fontSize-2xl)
853
+ );
221
854
  --typography-heading-h4-fontWeight: var(--typography-fontWeight-semibold);
222
- --typography-heading-h4-lineHeight: var(--typography-lineHeight-snug);
855
+ --typography-heading-h4-lineHeight: var(--typography-lineHeight-130);
223
856
  --typography-heading-h4-letterSpacing: var(--typography-letterSpacing-normal);
224
857
  --typography-heading-h4-fontFamily: var(--typography-fontFamily-sans);
225
858
 
226
- --typography-heading-h5-fontSize: var(--typography-fontSize-xl);
859
+ /* fluid: 18px at 320 -> 20px at 992. (20-18)/6.72=0.2976vw, (18-(20-18)/2.1)/16=1.06548rem */
860
+ --typography-heading-h5-fontSizeMin: 1.125rem; /* 18px */
861
+ --typography-heading-h5-fontSize: clamp(
862
+ var(--typography-heading-h5-fontSizeMin),
863
+ calc(1.06548rem + 0.2976vw),
864
+ var(--typography-fontSize-xl)
865
+ );
227
866
  --typography-heading-h5-fontWeight: var(--typography-fontWeight-medium);
228
- --typography-heading-h5-lineHeight: var(--typography-lineHeight-normal);
867
+ --typography-heading-h5-lineHeight: var(--typography-lineHeight-140);
229
868
  --typography-heading-h5-letterSpacing: var(--typography-letterSpacing-normal);
230
869
  --typography-heading-h5-fontFamily: var(--typography-fontFamily-sans);
231
870
 
232
- --typography-heading-h6-fontSize: var(--typography-fontSize-lg);
871
+ /* fluid: 16px at 320 -> 18px at 992. (18-16)/6.72=0.2976vw, (16-(18-16)/2.1)/16=0.94048rem */
872
+ --typography-heading-h6-fontSizeMin: 1rem; /* 16px */
873
+ --typography-heading-h6-fontSize: clamp(
874
+ var(--typography-heading-h6-fontSizeMin),
875
+ calc(0.94048rem + 0.2976vw),
876
+ var(--typography-fontSize-lg)
877
+ );
233
878
  --typography-heading-h6-fontWeight: var(--typography-fontWeight-medium);
234
- --typography-heading-h6-lineHeight: var(--typography-lineHeight-normal);
879
+ --typography-heading-h6-lineHeight: var(--typography-lineHeight-145);
235
880
  --typography-heading-h6-letterSpacing: var(--typography-letterSpacing-normal);
236
881
  --typography-heading-h6-fontFamily: var(--typography-fontFamily-sans);
237
882
 
238
883
  /* Body Text Tokens */
239
884
  --typography-body-large-fontSize: var(--typography-fontSize-lg);
240
885
  --typography-body-large-fontWeight: var(--typography-fontWeight-normal);
241
- --typography-body-large-lineHeight: var(--typography-lineHeight-relaxed);
886
+ --typography-body-large-lineHeight: var(--typography-lineHeight-145);
242
887
  --typography-body-large-letterSpacing: var(--typography-letterSpacing-normal);
243
888
  --typography-body-large-fontFamily: var(--typography-fontFamily-sans);
244
889
 
@@ -252,13 +897,19 @@ export const tokens = css`
252
897
 
253
898
  --typography-body-small-fontSize: var(--typography-fontSize-sm);
254
899
  --typography-body-small-fontWeight: var(--typography-fontWeight-normal);
255
- --typography-body-small-lineHeight: var(--typography-lineHeight-normal);
900
+ --typography-body-small-lineHeight: var(--typography-lineHeight-145);
256
901
  --typography-body-small-letterSpacing: var(--typography-letterSpacing-normal);
257
902
  --typography-body-small-fontFamily: var(--typography-fontFamily-sans);
258
903
 
259
- --typography-body-lead-fontSize: var(--typography-fontSize-xl);
904
+ /* fluid: 18px at 320 -> 20px at 992. (20-18)/6.72=0.2976vw, (18-(20-18)/2.1)/16=1.06548rem */
905
+ --typography-body-lead-fontSizeMin: 1.125rem; /* 18px */
906
+ --typography-body-lead-fontSize: clamp(
907
+ var(--typography-body-lead-fontSizeMin),
908
+ calc(1.06548rem + 0.2976vw),
909
+ var(--typography-fontSize-xl)
910
+ );
260
911
  --typography-body-lead-fontWeight: var(--typography-fontWeight-normal);
261
- --typography-body-lead-lineHeight: var(--typography-lineHeight-relaxed);
912
+ --typography-body-lead-lineHeight: var(--typography-lineHeight-140);
262
913
  --typography-body-lead-letterSpacing: var(--typography-letterSpacing-normal);
263
914
  --typography-body-lead-fontFamily: var(--typography-fontFamily-sans);
264
915
 
@@ -277,8 +928,32 @@ export const tokens = css`
277
928
  --typography-label-small-letterSpacing: var(--typography-letterSpacing-wide);
278
929
  --typography-label-small-fontFamily: var(--typography-fontFamily-sans);
279
930
 
931
+ /* The only role for the 11px step, and it carried a size and nothing
932
+ else -- so anything using it inherited its leading from whatever it
933
+ happened to sit inside, which is not a typography contract. The four
934
+ missing axes match label-small, one step up. It has no consumers
935
+ today; the five components that render 11px reach past it for the
936
+ primitive, and moving them is a visual change this phase does not
937
+ make. */
280
938
  --typography-label-extra-small-fontSize: var(--typography-fontSize-xxs);
281
- --typography-label-xsmall-fontSize: var(--typography-fontSize-xs);
939
+ --typography-label-extra-small-fontWeight: var(--typography-fontWeight-medium);
940
+ --typography-label-extra-small-lineHeight: var(--typography-lineHeight-normal);
941
+ --typography-label-extra-small-letterSpacing: var(--typography-letterSpacing-wide);
942
+ --typography-label-extra-small-fontFamily: var(--typography-fontFamily-sans);
943
+
944
+ /* Deliberately size-only: bd-p variant=xsmall composes it with
945
+ body-small's weight, leading and tracking, so giving it axes of its
946
+ own would create a second answer to those questions.
947
+
948
+ It lives in the body family, not the label one. It was
949
+ --typography-label-xsmall-fontSize, one character away from
950
+ label-extra-small, which is a complete 11px role with a weight, a
951
+ leading and a family. Two names that close, describing things that
952
+ different, is a trap: one is a role you can ask for and the other is
953
+ a bare size that matches any one-axis 12px declaration on size
954
+ alone. Naming it for the component that owns it says which is
955
+ which. */
956
+ --typography-body-xsmall-fontSize: var(--typography-fontSize-xs);
282
957
 
283
958
  --typography-label-large-fontSize: var(--typography-fontSize-base);
284
959
  --typography-label-large-fontWeight: var(--typography-fontWeight-medium);
@@ -288,11 +963,56 @@ export const tokens = css`
288
963
  );
289
964
  --typography-label-large-fontFamily: var(--typography-fontFamily-sans);
290
965
 
291
- --typography-label-xl-fontSize: var(--typography-fontSize-lg);
292
- --typography-label-xl-fontWeight: var(--typography-fontWeight-medium);
293
- --typography-label-xl-lineHeight: var(--typography-lineHeight-normal);
294
- --typography-label-xl-letterSpacing: var(--typography-letterSpacing-normal);
295
- --typography-label-xl-fontFamily: var(--typography-fontFamily-sans);
966
+ /* Control Label Tokens
967
+
968
+ label-* at control leading. Every label-* rung is lineHeight-normal
969
+ (1.5); a control -- a badge, a button -- needs 1.25 so its box can be
970
+ padding-driven without the line box forcing it taller.
971
+ scripts/typography-pairings.json already records 16px at 1.25 as
972
+ physically valid "because control text needs it", and rule 8 says
973
+ that file is a technical boundary rather than authorization, so the
974
+ authorization is here: this is the role that means "a label on a
975
+ control".
976
+
977
+ Motivated by badge.css.js and button.css.js's .bd-contact-btn, which
978
+ each arrived independently at medium + tight and had no role to ask
979
+ for. The three rungs mirror label-small / label-default /
980
+ label-large step for step. The one place they do not mirror is
981
+ tracking on the small rung: label-small is letterSpacing-wide, but
982
+ badge -- the atom this rung exists for -- renders normal, so the rung
983
+ describes its consumer rather than its twin. Everywhere else the only
984
+ axis that differs from the label-* rung of the same name is the
985
+ leading. */
986
+ --typography-control-label-small-fontSize: var(--typography-fontSize-xs);
987
+ --typography-control-label-small-fontWeight: var(--typography-fontWeight-medium);
988
+ --typography-control-label-small-lineHeight: var(--typography-lineHeight-tight);
989
+ --typography-control-label-small-letterSpacing: var(--typography-letterSpacing-normal);
990
+ --typography-control-label-small-fontFamily: var(--typography-fontFamily-sans);
991
+
992
+ --typography-control-label-default-fontSize: var(--typography-fontSize-sm);
993
+ --typography-control-label-default-fontWeight: var(--typography-fontWeight-medium);
994
+ --typography-control-label-default-lineHeight: var(--typography-lineHeight-tight);
995
+ --typography-control-label-default-letterSpacing: var(--typography-letterSpacing-normal);
996
+ --typography-control-label-default-fontFamily: var(--typography-fontFamily-sans);
997
+
998
+ --typography-control-label-large-fontSize: var(--typography-fontSize-base);
999
+ --typography-control-label-large-fontWeight: var(--typography-fontWeight-medium);
1000
+ --typography-control-label-large-lineHeight: var(--typography-lineHeight-tight);
1001
+ --typography-control-label-large-letterSpacing: var(--typography-letterSpacing-normal);
1002
+ --typography-control-label-large-fontFamily: var(--typography-fontFamily-sans);
1003
+
1004
+ /* The rung label-xl used to occupy. label-xl was lg/medium/normal and
1005
+ documented in vertical-rhythm.stories.js as "the name of a large
1006
+ control" -- the same job this family names, but at label leading
1007
+ rather than control leading, and with no consumer in the package.
1008
+ Retired in favour of this. Note it was byte-identical to heading-h6,
1009
+ which stays: that pair was two questions with coincidentally equal
1010
+ answers, and heading-h6 is the one bd-h actually renders. */
1011
+ --typography-control-label-xl-fontSize: var(--typography-fontSize-lg);
1012
+ --typography-control-label-xl-fontWeight: var(--typography-fontWeight-medium);
1013
+ --typography-control-label-xl-lineHeight: var(--typography-lineHeight-tight);
1014
+ --typography-control-label-xl-letterSpacing: var(--typography-letterSpacing-normal);
1015
+ --typography-control-label-xl-fontFamily: var(--typography-fontFamily-sans);
296
1016
 
297
1017
  /* Caption Tokens */
298
1018
  --typography-caption-default-fontSize: var(--typography-fontSize-xs);
@@ -302,6 +1022,7 @@ export const tokens = css`
302
1022
  --typography-letterSpacing-normal
303
1023
  );
304
1024
  --typography-caption-default-fontFamily: var(--typography-fontFamily-sans);
1025
+ --typography-caption-default-color: var(--color-text-muted);
305
1026
 
306
1027
  --typography-caption-small-fontSize: var(--typography-fontSize-xs);
307
1028
  --typography-caption-small-fontWeight: var(--typography-fontWeight-normal);
@@ -319,6 +1040,12 @@ export const tokens = css`
319
1040
  --typography-link-default-fontFamily: inherit;
320
1041
  --typography-link-default-textDecoration-default: underline;
321
1042
  --typography-link-default-textDecoration-hover: none;
1043
+ /* The gap between the text and its underline. It was spelled as a 2px
1044
+ literal in seven places in bd-link, on a property the length rule
1045
+ does not check, so nothing could see it and nothing could change it
1046
+ in one move. */
1047
+ --typography-link-underlineOffset: var(--spacing-2);
1048
+ --typography-link-underlineThickness: var(--border-width-2);
322
1049
 
323
1050
  --typography-link-standalone-fontSize: var(--typography-fontSize-base);
324
1051
  --typography-link-standalone-fontWeight: var(--typography-fontWeight-medium);
@@ -370,13 +1097,25 @@ export const tokens = css`
370
1097
  --typography-display-lg-letterSpacing: var(--typography-letterSpacing-tight);
371
1098
  --typography-display-lg-fontFamily: var(--typography-fontFamily-sans);
372
1099
 
373
- --typography-display-md-fontSize: var(--typography-fontSize-5xl);
1100
+ /* fluid: 30px at 320 -> 48px at 992. (48-30)/6.72=2.6786vw, (30-(48-30)/2.1)/16=1.33929rem */
1101
+ --typography-display-md-fontSizeMin: 1.875rem; /* 30px */
1102
+ --typography-display-md-fontSize: clamp(
1103
+ var(--typography-display-md-fontSizeMin),
1104
+ calc(1.33929rem + 2.6786vw),
1105
+ var(--typography-fontSize-5xl)
1106
+ );
374
1107
  --typography-display-md-fontWeight: var(--typography-fontWeight-bold);
375
1108
  --typography-display-md-lineHeight: var(--typography-lineHeight-tight);
376
1109
  --typography-display-md-letterSpacing: var(--typography-letterSpacing-tight);
377
1110
  --typography-display-md-fontFamily: var(--typography-fontFamily-sans);
378
1111
 
379
- --typography-display-sm-fontSize: var(--typography-fontSize-4xl);
1112
+ /* fluid: 30px at 320 -> 36px at 992. (36-30)/6.72=0.8929vw, (30-(36-30)/2.1)/16=1.69643rem */
1113
+ --typography-display-sm-fontSizeMin: 1.875rem; /* 30px */
1114
+ --typography-display-sm-fontSize: clamp(
1115
+ var(--typography-display-sm-fontSizeMin),
1116
+ calc(1.69643rem + 0.8929vw),
1117
+ var(--typography-fontSize-4xl)
1118
+ );
380
1119
  --typography-display-sm-fontWeight: var(--typography-fontWeight-bold);
381
1120
  --typography-display-sm-lineHeight: var(--typography-lineHeight-tight);
382
1121
  --typography-display-sm-letterSpacing: var(--typography-letterSpacing-tight);
@@ -392,65 +1131,104 @@ export const tokens = css`
392
1131
  --form-label-default-lineHeight: var(--typography-lineHeight-normal);
393
1132
  --form-label-default-letterSpacing: var(--typography-letterSpacing-normal);
394
1133
  --form-label-default-fontFamily: var(--typography-fontFamily-sans);
395
- --form-label-default-color: var(--color-neutral-900);
396
- --form-label-default-marginBottom: var(--spacing-1);
1134
+ --form-label-default-color: var(--color-text-primary);
1135
+ --form-label-default-marginBottom: var(--spacing-4);
397
1136
 
398
1137
  --form-label-large-fontSize: var(--typography-fontSize-lg);
399
1138
  --form-label-large-fontWeight: var(--typography-fontWeight-medium);
400
1139
  --form-label-large-lineHeight: var(--typography-lineHeight-normal);
401
1140
  --form-label-large-letterSpacing: var(--typography-letterSpacing-normal);
402
1141
  --form-label-large-fontFamily: var(--typography-fontFamily-sans);
403
- --form-label-large-color: var(--color-neutral-900);
404
- --form-label-large-marginBottom: var(--spacing-2);
1142
+ --form-label-large-color: var(--color-text-primary);
1143
+ --form-label-large-marginBottom: var(--spacing-8);
405
1144
 
406
1145
  --form-label-small-fontSize: var(--typography-fontSize-xs);
407
1146
  --form-label-small-fontWeight: var(--typography-fontWeight-medium);
408
1147
  --form-label-small-lineHeight: var(--typography-lineHeight-normal);
409
1148
  --form-label-small-letterSpacing: var(--typography-letterSpacing-wide);
410
1149
  --form-label-small-fontFamily: var(--typography-fontFamily-sans);
411
- --form-label-small-color: var(--color-neutral-900);
412
- --form-label-small-marginBottom: var(--spacing-1);
1150
+ --form-label-small-color: var(--color-text-primary);
1151
+ --form-label-small-marginBottom: var(--spacing-4);
413
1152
 
414
- --form-label-required-color: var(--color-red-500);
415
- --form-label-required-marginLeft: var(--spacing-1);
1153
+ --form-label-required-color: var(--status-error-accent);
1154
+ --form-label-required-marginLeft: var(--spacing-4);
416
1155
 
417
1156
  /* Form Input Tokens */
418
- --form-input-small-height: var(--dimension-34px);
1157
+
1158
+ /* The height of a control is derived, not chosen: padding, type step and
1159
+ line-height, the same three bd-button derives its own height from.
1160
+ That derivation is what keeps a field and a button of the same size
1161
+ the same height on one row of a form.
1162
+
1163
+ These three said --dimension-36/44/56 and stated a height the system
1164
+ does not draw. A small field renders 33.5px and a large one 54.5px;
1165
+ only medium agreed, and it agreed by coincidence. Nothing read them,
1166
+ so nothing surfaced the disagreement -- which is the argument for
1167
+ spelling the derivation here rather than a rounder number, and for
1168
+ shared/form-field.css.js reading these instead of repeating the calc
1169
+ three times. Changing them changes no pixel; it changes what the
1170
+ package tells a consumer the height is.
1171
+
1172
+ The line box these leave is 2px taller than the content box, because
1173
+ the derivation counts padding but not the 1px border on each edge.
1174
+ Measured at every size: no glyph clips, because an input centres its
1175
+ line and paints into the padding. Correcting it would move a field
1176
+ off its button, so it stays a recorded fact rather than a fix. */
1177
+ --form-input-small-height: calc(
1178
+ var(--spacing-8) * 2 + var(--typography-fontSize-sm) * var(--typography-lineHeight-tight)
1179
+ );
419
1180
  --form-input-small-fontSize: var(--typography-fontSize-sm);
420
- --form-input-small-padding: var(--spacing-2) var(--spacing-3);
1181
+ --form-input-small-padding: var(--spacing-8) var(--spacing-12);
421
1182
  --form-input-small-borderRadius: var(--radius-sm);
422
1183
 
423
- --form-input-medium-height: var(--dimension-45px);
1184
+ --form-input-medium-height: calc(
1185
+ var(--spacing-12) * 2 + var(--typography-fontSize-base) * var(--typography-lineHeight-tight)
1186
+ );
424
1187
  --form-input-medium-fontSize: var(--typography-fontSize-base);
425
- --form-input-medium-padding: var(--spacing-3) var(--spacing-4);
1188
+ --form-input-medium-padding: var(--spacing-12) var(--spacing-16);
426
1189
  --form-input-medium-borderRadius: var(--radius-md);
427
1190
 
428
- --form-input-large-height: var(--dimension-55px);
1191
+ --form-input-large-height: calc(
1192
+ var(--spacing-16) * 2 + var(--typography-fontSize-lg) * var(--typography-lineHeight-tight)
1193
+ );
429
1194
  --form-input-large-fontSize: var(--typography-fontSize-lg);
430
- --form-input-large-padding: var(--spacing-4) var(--spacing-5);
1195
+ --form-input-large-padding: var(--spacing-16) var(--spacing-20);
431
1196
  --form-input-large-borderRadius: var(--radius-lg);
432
1197
 
433
- --form-input-default-backgroundColor: var(--color-neutral-0);
434
- --form-input-default-borderColor: var(--color-neutral-300);
1198
+ --form-input-default-backgroundColor: var(--color-surface-default);
1199
+ --form-input-default-borderColor: var(--color-border-default);
435
1200
  --form-input-default-borderWidth: var(--border-width-1);
436
- --form-input-default-color: var(--color-neutral-900);
1201
+ --form-input-default-color: var(--color-text-primary);
437
1202
  --form-input-default-fontFamily: var(--typography-fontFamily-sans);
438
- --form-input-default-lineHeight: var(--typography-lineHeight-normal);
439
-
440
- --form-input-focus-borderColor: var(--color-blue-500);
1203
+ /* There is deliberately no --form-input-default-lineHeight. There was
1204
+ one, it said normal (1.5), it had no consumer, and it described the
1205
+ opposite of what the control does: form-field.css.js sets tight and
1206
+ records why -- at normal the line box stood taller than the content
1207
+ box the derived height leaves, and the select clipped the descenders
1208
+ of its own option text.
1209
+
1210
+ It is not restored as tight either. The control's height is
1211
+ DERIVED from padding, type step and this leading, so exposing the
1212
+ leading as a component token would let a consuming page change the
1213
+ height by setting something that reads like a type preference. The
1214
+ primitive is used at the one site that needs it, with its reason on
1215
+ the line, which is what keeps the box and the text unable to
1216
+ disagree. */
1217
+
1218
+ --form-input-focus-borderColor: var(--color-border-brand);
441
1219
  --form-input-focus-boxShadow: var(--shadow-focus-outline-blue);
442
1220
 
443
- --form-input-error-backgroundColor: var(--color-red-50);
444
- --form-input-error-borderColor: var(--color-red-500);
445
- --form-input-error-placeholderColor: var(--color-red-400);
1221
+ --form-input-error-backgroundColor: var(--status-error-surface);
1222
+ --form-input-error-borderColor: var(--status-error-accent);
1223
+ --form-input-error-placeholderColor: var(--color-red-600);
446
1224
 
447
- --form-input-success-backgroundColor: var(--color-green-50);
448
- --form-input-success-borderColor: var(--color-green-500);
449
- --form-input-success-placeholderColor: var(--color-green-400);
1225
+ --form-input-success-backgroundColor: var(--status-success-surface);
1226
+ --form-input-success-borderColor: var(--color-green-600);
1227
+ --form-input-success-placeholderColor: var(--color-green-700);
450
1228
 
451
- --form-input-disabled-backgroundColor: var(--color-neutral-100);
452
- --form-input-disabled-borderColor: var(--color-neutral-300);
453
- --form-input-disabled-color: var(--color-neutral-500);
1229
+ --form-input-disabled-backgroundColor: var(--color-surface-disabled);
1230
+ --form-input-disabled-borderColor: var(--color-border-disabled);
1231
+ --form-input-disabled-color: var(--color-text-subtle);
454
1232
  --form-input-disabled-placeholderColor: var(--color-neutral-400);
455
1233
 
456
1234
  /* Form Help Text Tokens */
@@ -461,41 +1239,69 @@ export const tokens = css`
461
1239
  --typography-letterSpacing-normal
462
1240
  );
463
1241
  --form-help-text-default-fontFamily: var(--typography-fontFamily-sans);
464
- --form-help-text-default-color: var(--color-neutral-600);
465
- --form-help-text-default-marginTop: var(--spacing-1);
466
-
467
- --form-help-text-error-color: var(--color-red-600);
468
- --form-help-text-success-color: var(--color-green-600);
469
-
470
- /* Form Layout Tokens */
471
- --form-fieldSpacing: var(--spacing-6);
472
- --form-sectionSpacing: var(--spacing-8);
473
- --form-labelSpacing: var(--spacing-1);
474
- --form-helpTextSpacing: var(--spacing-1);
1242
+ --form-help-text-default-color: var(--color-text-muted);
1243
+ --form-help-text-default-marginTop: var(--spacing-4);
1244
+
1245
+ /* Measured against the tint the field itself takes in each state, not
1246
+ against white — that is the background this text actually sits
1247
+ beside. red-600 on red-50 is 4.92 and needs nothing.
1248
+
1249
+ success was green-700, which is 4.75 on white but only 4.51 on
1250
+ green-50: passing by a hundredth, and the kind of margin that stops
1251
+ passing the moment a tint moves. green-800 is 6.42 there, and is
1252
+ already the step --color-text-success names for this role. */
1253
+ --form-help-text-error-color: var(--status-error-accent);
1254
+ --form-help-text-success-color: var(--color-text-success);
1255
+
1256
+ /* Form Layout Tokens
1257
+
1258
+ These were written against a step-indexed spacing scale, where the
1259
+ number is the position on the scale, and never converted when the
1260
+ scale became pixel-named, where the number is the value. So
1261
+ --spacing-6 stopped meaning "the sixth step, 24px" and started
1262
+ meaning 6px, silently — every token still resolved, and the lint has
1263
+ no way to see it.
1264
+
1265
+ bd-fieldset consumes fieldSpacing, so a form built from it has been
1266
+ stacking its fields 6px apart. Decoded as indices these read 24 and
1267
+ 32, which is what a form actually wants and what the value is set to
1268
+ here. */
1269
+ --form-fieldSpacing: var(--spacing-24);
1270
+ --form-sectionSpacing: var(--spacing-32);
1271
+ --form-labelSpacing: var(--spacing-4);
1272
+ --form-helpTextSpacing: var(--spacing-4);
475
1273
 
476
1274
  /* ================================
477
1275
  COMPONENT TOKENS - BUTTON
478
1276
  ================================ */
479
1277
 
480
1278
  /* Button Size Tokens */
481
- --button-small-height: var(--dimension-34px);
1279
+ /* One size family. There were two — this one and --button-size-*, with
1280
+ different padding in each and neither matching what the component
1281
+ rendered. bd-button reads these.
1282
+
1283
+ No -height. The component derives its height from padding, type step
1284
+ and line-height rather than pinning it, which is what keeps it equal
1285
+ to a field of the same size; a height token here would be a second
1286
+ answer that could disagree. */
482
1287
  --button-small-fontSize: var(--typography-fontSize-sm);
483
- --button-small-padding: var(--spacing-2) var(--spacing-3);
1288
+ --button-small-padding: var(--spacing-8) var(--spacing-16);
1289
+ --button-small-gap: var(--spacing-4);
484
1290
  --button-small-borderRadius: var(--radius-sm);
485
1291
 
486
- --button-medium-height: var(--dimension-45px);
487
1292
  --button-medium-fontSize: var(--typography-fontSize-base);
488
- --button-medium-padding: var(--spacing-3) var(--spacing-4);
1293
+ --button-medium-padding: var(--spacing-12) var(--spacing-24);
1294
+ --button-medium-gap: var(--spacing-8);
489
1295
  --button-medium-borderRadius: var(--radius-md);
490
1296
 
491
- --button-large-height: var(--dimension-55px);
492
1297
  --button-large-fontSize: var(--typography-fontSize-lg);
493
- --button-large-padding: var(--spacing-4) var(--spacing-5);
1298
+ --button-large-padding: var(--spacing-16) var(--spacing-32);
1299
+ --button-large-gap: var(--spacing-8);
494
1300
  --button-large-borderRadius: var(--radius-lg);
495
1301
 
496
1302
  /* Button Default State */
497
1303
  --button-default-backgroundColor: var(--color-neutral-0);
498
- --button-default-borderColor: var(--color-neutral-300);
1304
+ --button-default-borderColor: var(--color-neutral-400);
499
1305
  --button-default-borderWidth: var(--border-width-1);
500
1306
  --button-default-color: var(--color-neutral-900);
501
1307
  --button-default-fontFamily: var(--typography-fontFamily-sans);
@@ -518,113 +1324,142 @@ export const tokens = css`
518
1324
  --button-shared-disabled-opacity: 0.6;
519
1325
  --button-shared-disabled-cursor: not-allowed;
520
1326
 
521
- /* Button Size Tokens */
522
- --button-size-small-padding-vertical: var(--spacing-2);
523
- --button-size-small-padding-horizontal: var(--spacing-4);
524
- --button-size-small-fontSize: var(--typography-fontSize-sm);
525
- --button-size-small-height: var(--dimension-34px);
526
- --button-size-small-borderRadius: var(--radius-sm);
527
- --button-size-small-gap: var(--spacing-1);
528
-
529
- --button-size-medium-padding-vertical: var(--spacing-3);
530
- --button-size-medium-padding-horizontal: var(--spacing-5);
531
- --button-size-medium-fontSize: var(--typography-fontSize-base);
532
- --button-size-medium-height: var(--dimension-45px);
533
- --button-size-medium-borderRadius: var(--radius-md);
534
- --button-size-medium-gap: var(--spacing-2);
535
-
536
- --button-size-large-padding-vertical: var(--spacing-4);
537
- --button-size-large-padding-horizontal: var(--spacing-6);
538
- --button-size-large-fontSize: var(--typography-fontSize-lg);
539
- --button-size-large-height: var(--dimension-55px);
540
- --button-size-large-borderRadius: var(--radius-lg);
541
- --button-size-large-gap: var(--spacing-2);
542
-
543
- /* Button Primary State */
544
- --button-primary-background-default: var(--color-blue-500);
545
- --button-primary-text-default: var(--color-neutral-0);
546
- --button-primary-border-default: var(--color-blue-500);
547
- --button-primary-background-hover: var(--color-blue-600);
548
- --button-primary-border-hover: var(--color-blue-600);
549
- --button-primary-background-active: var(--color-blue-700);
550
- --button-primary-border-active: var(--color-blue-700);
551
- --button-primary-border-focus: var(--color-blue-500);
552
- --button-primary-background-disabled: var(--color-neutral-300);
553
- --button-primary-text-disabled: var(--color-neutral-500);
554
- --button-primary-border-disabled: var(--color-neutral-300);
555
-
556
- /* Button Secondary State */
557
- --button-secondary-background-default: var(--color-neutral-950);
558
- --button-secondary-text-default: var(--color-neutral-0);
559
- --button-secondary-border-default: var(--color-neutral-950);
560
- --button-secondary-background-hover: var(--color-neutral-900);
561
- --button-secondary-text-hover: var(--color-neutral-0);
562
- --button-secondary-border-hover: var(--color-neutral-900);
563
- --button-secondary-background-active: var(--color-neutral-800);
564
- --button-secondary-text-active: var(--color-neutral-0);
565
- --button-secondary-border-active: var(--color-neutral-800);
566
- --button-secondary-border-focus: var(--color-neutral-950);
567
- --button-secondary-background-disabled: var(--color-neutral-300);
568
- --button-secondary-text-disabled: var(--color-neutral-500);
569
- --button-secondary-border-disabled: var(--color-neutral-300);
570
-
571
- /* Button Tertiary Primary State */
572
- --button-tertiaryPrimary-background-default: transparent;
573
- --button-tertiaryPrimary-text-default: var(--color-blue-500);
574
- --button-tertiaryPrimary-border-default: var(--color-blue-500);
575
- --button-tertiaryPrimary-borderWidth: var(--border-width-2);
576
- --button-tertiaryPrimary-background-hover: var(--color-blue-50);
577
- --button-tertiaryPrimary-text-hover: var(--color-blue-500);
578
- --button-tertiaryPrimary-border-hover: var(--color-blue-500);
579
- --button-tertiaryPrimary-background-active: var(--color-blue-500);
580
- --button-tertiaryPrimary-text-active: var(--color-neutral-0);
581
- --button-tertiaryPrimary-border-active: var(--color-blue-500);
582
- --button-tertiaryPrimary-border-focus: var(--color-blue-500);
583
- --button-tertiaryPrimary-background-disabled: transparent;
584
- --button-tertiaryPrimary-text-disabled: var(--color-neutral-400);
585
- --button-tertiaryPrimary-border-disabled: var(--color-neutral-300);
586
-
587
- /* Button Tertiary Secondary State */
588
- --button-tertiarySecondary-background-default: transparent;
589
- --button-tertiarySecondary-text-default: var(--color-neutral-900);
590
- --button-tertiarySecondary-border-default: var(--color-neutral-900);
591
- --button-tertiarySecondary-borderWidth: var(--border-width-2);
592
- --button-tertiarySecondary-background-hover: var(--color-neutral-100);
593
- --button-tertiarySecondary-text-hover: var(--color-neutral-900);
594
- --button-tertiarySecondary-border-hover: var(--color-neutral-900);
595
- --button-tertiarySecondary-background-active: var(--color-neutral-900);
596
- --button-tertiarySecondary-text-active: var(--color-neutral-0);
597
- --button-tertiarySecondary-border-active: var(--color-neutral-900);
598
- --button-tertiarySecondary-border-focus: var(--color-neutral-900);
599
- --button-tertiarySecondary-background-disabled: transparent;
600
- --button-tertiarySecondary-text-disabled: var(--color-neutral-400);
601
- --button-tertiarySecondary-border-disabled: var(--color-neutral-300);
602
-
603
- /* Button CTA State */
604
- --button-cta-background-default: var(--color-red-500);
605
- --button-cta-text-default: var(--color-neutral-0);
606
- --button-cta-border-default: var(--color-red-500);
607
- --button-cta-background-hover: var(--color-red-600);
608
- --button-cta-border-hover: var(--color-red-600);
609
- --button-cta-background-active: var(--color-red-700);
610
- --button-cta-border-active: var(--color-red-700);
611
- --button-cta-border-focus: var(--color-red-500);
612
- --button-cta-background-disabled: var(--color-neutral-300);
613
- --button-cta-text-disabled: var(--color-neutral-500);
614
- --button-cta-border-disabled: var(--color-neutral-300);
615
-
616
- /* Button Empty State */
617
- --button-empty-background-default: var(--color-neutral-200);
618
- --button-empty-text-default: transparent;
619
- --button-empty-border-default: var(--color-neutral-200);
620
-
621
- /* Button Disabled State */
622
- --button-disabled-backgroundColor: var(--color-neutral-100);
623
- --button-disabled-borderColor: var(--color-neutral-300);
624
- --button-disabled-color: var(--color-neutral-500);
625
-
626
- /* Button Focus State */
627
- --button-focus-boxShadow: var(--shadow-focus-outline-blue);
1327
+ /* ---- variant colours -------------------------------------------
1328
+ Named for the kind attribute they serve, because that is the public
1329
+ vocabulary: primary, secondary, tertiary, ghost, accent, white and
1330
+ outline-white. They used to be named tertiaryPrimary,
1331
+ tertiarySecondary and cta, which no consumer could map onto the
1332
+ component without reading its source.
1333
+
1334
+ bd-button aliases each of these onto a --button-*-bg property on its
1335
+ own :host, so there are two override points and they do different
1336
+ jobs: set the token on :root to move every button in the document,
1337
+ or the alias on the bd-button element to move one. The alias is also what
1338
+ surface="dark" switches, which is why the component keeps it.
1339
+
1340
+ Every value here is what the component already renders. The old
1341
+ values had drifted apart from it in four places — secondary's hover
1342
+ and active, tertiary's hover border and ghost's active fill — so
1343
+ wiring them up unchanged would have moved pixels.
1344
+
1345
+ The per-variant -focus and -disabled sets are gone. The component
1346
+ draws one focus ring for every variant, from --focus-outline-primary,
1347
+ and one shared disabled treatment; thirty tokens described per-variant
1348
+ versions of both that nothing rendered. --button-empty-* went with
1349
+ them: there is no empty kind. */
1350
+
1351
+ /* primary: blue fill */
1352
+ --button-primary-background-default: var(--color-action-primary);
1353
+ --button-primary-background-hover: var(--color-action-primary-hover);
1354
+ --button-primary-background-active: var(--color-action-primary-active);
1355
+ --button-primary-text-default: var(--color-action-on-fill);
1356
+
1357
+ /* secondary: near-black fill */
1358
+ --button-secondary-background-default: var(--color-action-secondary);
1359
+ --button-secondary-background-hover: var(--color-action-secondary-hover);
1360
+ --button-secondary-background-active: var(--color-action-secondary-active);
1361
+ --button-secondary-text-default: var(--color-action-on-fill);
1362
+
1363
+ /* tertiary: blue outline. text-hover is blue-600, not blue-500: on the
1364
+ blue-50 hover fill the lighter step measures 4.20:1 and fails 1.4.3,
1365
+ where blue-600 gives 5.72.
1366
+ .
1367
+ text-default is blue-600 for the same reason, applied to the state a
1368
+ reader actually sits in. The line above fixed hover, where blue-500
1369
+ failed outright, and left the resting state on blue-500 at 4.53:1 --
1370
+ passing 4.5 by 0.03 on pure white and failing on any tint, which is
1371
+ the margin CLAUDE.md says is not real. Both states now name the same
1372
+ ink role, and the role is the category-correct one: --color-action-primary
1373
+ is an interaction role for fills, not ink, and it was being read by a
1374
+ color: property. Measured 4.53:1 -> 6.18:1 on all six consumers.
1375
+ The ring is deliberately NOT changed here and stays blue-500: it is
1376
+ non-text UI at a 3:1 bar, it passes, and moving it would mean naming a
1377
+ blue-600 border role that does not exist. See the plan's backlog. */
1378
+ --button-tertiary-text-default: var(--color-text-brand);
1379
+ --button-tertiary-border-default: var(--color-border-brand);
1380
+ --button-tertiary-background-hover: var(--color-surface-info);
1381
+ --button-tertiary-text-hover: var(--color-text-brand);
1382
+ --button-tertiary-border-hover: var(--color-action-primary-hover);
1383
+ --button-tertiary-background-active: var(--color-action-primary);
1384
+ --button-tertiary-text-active: var(--color-action-on-fill);
1385
+
1386
+ /* ghost: dark outline */
1387
+ --button-ghost-text-default: var(--color-text-primary);
1388
+ --button-ghost-border-default: var(--color-border-strong);
1389
+ --button-ghost-background-hover: var(--color-surface-muted);
1390
+ --button-ghost-text-hover: var(--color-text-primary);
1391
+ --button-ghost-background-active: var(--color-action-secondary);
1392
+ --button-ghost-text-active: var(--color-action-on-fill);
1393
+
1394
+ /* accent: red fill. This system's conversion colour — Buy now and the
1395
+ like — not a danger signal; there is deliberately no danger variant.
1396
+ red-600, not red-500: white on red-500 measures 4.00:1 and fails. */
1397
+ --button-accent-background-default: var(--color-action-accent);
1398
+ --button-accent-background-hover: var(--color-action-accent-hover);
1399
+ --button-accent-background-active: var(--color-action-accent-active);
1400
+ --button-accent-text-default: var(--color-action-on-fill);
1401
+ --button-accent-ring-default: transparent;
1402
+
1403
+ /* white and outline-white: fixed presets for a solid-colour banner,
1404
+ where even a surface="dark" primary would sit too close in tone.
1405
+ They ignore surface on purpose.
1406
+
1407
+ text-default is blue-600 rather than blue-500: white is the fill
1408
+ here, and blue-500 on white is 4.53:1 — the marginal step the
1409
+ contrast note warns against for text.
1410
+
1411
+ The translucent whites were rgba(255,255,255,…) and #ffffff8c
1412
+ literals, which no theme could reach. color-mix keeps the alpha and
1413
+ puts the colour back on the palette. */
1414
+ --button-white-background-default: var(--color-neutral-0);
1415
+ --button-white-background-hover: var(--color-blue-50);
1416
+ --button-white-background-active: var(--color-blue-100);
1417
+ /* blue-600 by name, not --color-text-brand. The preset is fixed --
1418
+ a white fill on a brand band, in any theme -- so its ink must be
1419
+ fixed too. Reading the role mixed a fill that never switches with a
1420
+ label that does: text-brand goes light in a dark theme and measured
1421
+ 2.36:1 on the white fill (dark mode finding 8). */
1422
+ --button-white-text-default: var(--color-blue-600);
1423
+
1424
+ --button-outline-white-text-default: var(--color-action-on-fill);
1425
+ /* 71% rather than 55%. This edge is the only thing bounding an
1426
+ outline-white button, so it carries the 3:1 of WCAG 1.4.11 on its
1427
+ own. Measured over the grounds the preset is actually used on, 55%
1428
+ gave 2.34 on blue-500 and 2.90 on blue-600 and only cleared on
1429
+ blue-700 at 3.65. 71% is the lowest value that clears all three
1430
+ with one number, so the token stops depending on which blue is
1431
+ behind it. */
1432
+ --button-outline-white-border-default: color-mix(in srgb, var(--color-neutral-0) 71%, transparent);
1433
+ /* Hover and press DARKEN the band, they do not wash it white. A white
1434
+ wash lightened the blue under a white label: 3.91:1 on hover and
1435
+ 3.38 pressed over blue-500, and 4.22 pressed over blue-600 -- under
1436
+ the 4.5 the label needs. 10% and 20% black take blue-500 to 5.40
1437
+ and 6.49 and give the 71% edge more ground (3.46 and 4.03); every
1438
+ ground the preset sits on now clears, the CTA gradient included.
1439
+ Darker on hover is also what every filled button already does.
1440
+ .
1441
+ The trade-off, chosen knowingly: on the navy end of the CTA
1442
+ gradient (#061a5c) darkening barely shows, a 1.05 step where the
1443
+ white wash gave 1.31. The label is what must pass; the hover there
1444
+ is carried by the cursor. A white wash cannot work on blue-500 at
1445
+ all, where the label clears 4.5 at rest by 0.03. */
1446
+ --button-outline-white-background-hover: color-mix(in srgb, var(--color-neutral-1000) 10%, transparent);
1447
+ --button-outline-white-background-active: color-mix(in srgb, var(--color-neutral-1000) 20%, transparent);
1448
+
1449
+ /* Shared across every variant. Filled kinds go grey; outlined ones keep
1450
+ a transparent fill and take the subtle border. Contrast is exempt
1451
+ here under WCAG 1.4.3.
1452
+
1453
+ borderColor is --color-border-disabled: the role named for the state
1454
+ it expresses, and the one disabled-edge value the whole control
1455
+ family now shares. It read neutral-300 once, then moved to
1456
+ neutral-200 to match what the component drew; bd-checkbox and
1457
+ bd-radio have since moved to neutral-300, so token and component
1458
+ agree again -- a step darker, 2.27:1 against the page where
1459
+ neutral-200 gave 1.66, and still lighter than any enabled edge. */
1460
+ --button-disabled-backgroundColor: var(--color-surface-disabled);
1461
+ --button-disabled-borderColor: var(--color-border-disabled);
1462
+ --button-disabled-color: var(--color-text-subtle);
628
1463
 
629
1464
  /* ================================
630
1465
  COMPONENT TOKENS - ANNOTATION
@@ -643,9 +1478,9 @@ export const tokens = css`
643
1478
  --citation-reference-fontSize: inherit;
644
1479
  --citation-reference-fontWeight: var(--typography-fontWeight-normal);
645
1480
  --citation-reference-lineHeight: inherit;
646
- --citation-reference-color: var(--color-blue-600);
1481
+ --citation-reference-color: var(--color-text-brand);
647
1482
  --citation-reference-textDecoration: underline;
648
- --citation-reference-hover-color: var(--color-blue-700);
1483
+ --citation-reference-hover-color: var(--color-text-brand-hover);
649
1484
  --citation-reference-hover-textDecoration: none;
650
1485
 
651
1486
  /* Mathematical Notation Tokens */
@@ -666,36 +1501,21 @@ export const tokens = css`
666
1501
  ================================ */
667
1502
 
668
1503
  /* Icon Size Tokens */
669
- --icon-xs-size: var(--dimension-12px);
670
- --icon-sm-size: var(--dimension-16px);
671
- --icon-md-size: var(--dimension-20px);
672
- --icon-lg-size: var(--dimension-24px);
673
- --icon-xl-size: var(--dimension-32px);
674
- --icon-2xl-size: var(--dimension-48px);
675
- --icon-3xl-size: var(--dimension-64px);
676
-
677
- /* Icon Context Tokens */
678
- --icon-button-small-size: var(--dimension-16px);
679
- --icon-button-medium-size: var(--dimension-20px);
680
- --icon-button-large-size: var(--dimension-24px);
681
-
682
- --icon-form-small-size: var(--dimension-16px);
683
- --icon-form-medium-size: var(--dimension-20px);
684
- --icon-form-large-size: var(--dimension-24px);
685
-
686
- /* Icon State Tokens */
687
- --icon-default-color: var(--color-neutral-600);
688
- --icon-hover-color: var(--color-neutral-700);
689
- --icon-active-color: var(--color-neutral-800);
690
- --icon-disabled-color: var(--color-neutral-400);
691
-
692
- --icon-primary-color: var(--color-blue-500);
693
- --icon-primary-hover-color: var(--color-blue-600);
694
- --icon-primary-active-color: var(--color-blue-700);
695
-
696
- --icon-success-color: var(--color-green-500);
697
- --icon-warning-color: var(--color-yellow-500);
698
- --icon-error-color: var(--color-red-500);
1504
+ --icon-xs-size: var(--dimension-12);
1505
+ --icon-sm-size: var(--dimension-16);
1506
+ --icon-md-size: var(--dimension-20);
1507
+ --icon-lg-size: var(--dimension-24);
1508
+ --icon-xl-size: var(--dimension-32);
1509
+ --icon-2xl-size: var(--dimension-48);
1510
+ --icon-3xl-size: var(--dimension-64);
1511
+
1512
+ /* The context aliases (--icon-button-*, --icon-form-*) and the ten
1513
+ --icon-*-color tokens were removed when the icon elements moved onto
1514
+ the size scale and currentColor. The aliases said the same thing as a
1515
+ step — --icon-button-small-size and --icon-sm-size were both 16px —
1516
+ and the colours described states an icon no longer has: it takes the
1517
+ colour of the text it sits in, so there is nothing per-state to keep
1518
+ in step. */
699
1519
 
700
1520
  /* ================================
701
1521
  CORE SPACING TOKENS
@@ -729,114 +1549,14 @@ export const tokens = css`
729
1549
  --spacing-112: 7rem; /* 112px */
730
1550
  --spacing-128: 8rem; /* 128px */
731
1551
 
732
- /* =====================
733
- OLD VALUES -------------------------------------------------------------------------------
734
- ====================== */
735
-
736
- /* Display default */
737
- --display-xxsmall: 44px; /* 58.652px = 3.666rem */
738
- --display-xsmall: 48px; /* 63.984px = 3.999rem */
739
- --display-small: 52px; /* 69.316px = 4.332rem */
740
- --display-medium: 56px; /* 74.648px = 4.666rem */
741
- --display-large: 60px; /* 79.98px = 5rem */
742
- --display-xlarge: 64px; /* 85.312px = 5.333rem */
743
-
744
- /* Display mobile */
745
- --display-xxsmall-mobile: 34px; /* 45.322px = 2.833rem */
746
- --display-xsmall-mobile: 36px; /* 47.988px = 2.999rem */
747
- --display-small-mobile: 38px; /* 50.654px = 3.167rem */
748
- --display-medium-mobile: 40px; /* 53.32px = 3.333rem */
749
- --display-large-mobile: 44px; /* 58.652px = 3.666rem */
750
- --display-xlarge-mobile: 48px; /* 63.984px = 3.999rem */
751
-
752
- /* Heading default */
753
- --heading-xxsmall: 18px; /* 23.994px = 1.5rem */
754
- --heading-xsmall: 20px; /* 26.66px = 1.667rem */
755
- --typography-heading-h4-fontSize: 24px; /* 31.992px = 2rem */
756
- --heading-medium: 28px; /* 37.324px = 2.333rem */
757
- --typography-fontSize-3xl: 32px; /* 42.656px = 2.667rem */
758
- --heading-xlarge: 36px; /* 47.988px = 2.999rem */
759
- --heading-xxlarge: 40px; /* 53.32px = 3.333rem */
760
-
761
- /* Body default */
762
- --body-small: 13px; /* 17.329px = 1.083rem */
763
- --typography-body-small-fontSize: 14px; /* 18.662px = 1.167rem */
764
- --typography-body-regular-fontSize: 16px; /* 21.328px = 1.333rem */
765
- --body-xlarge: 18px; /* 23.994px = 1.5rem */
766
-
767
- /* Caption default */
768
- --typography-label-extra-small-fontSize: 11px; /* 14.663px = 0.917rem */
769
- --typography-label-small-fontSize: 12px; /* 15.996px = 1rem */
770
-
771
- /* Display Default */
772
- --display-xlarge-line-height: 1.1; /* For 60px */
773
- --display-medium-line-height: 1.15; /* For 52px */
774
-
775
- /* Display Mobile */
776
- --display-xlarge-mobile-line-height: 1.15; /* For 44px */
777
- --display-medium-mobile-line-height: 1.2; /* For 38px */
778
-
779
- /* Heading */
780
- --heading-xlarge-line-height: 1.2; /* For 36px */
781
- --typography-heading-h4-fontSize-line-height: 1.3; /* For 24px */
782
-
783
- /* Body */
784
- --typography-body-regular-fontSize-line-height: 1.5; /* For 16px */
785
-
786
- /* Caption */
787
- --typography-label-small-fontSize-line-height: 1.35; /* For 12px */
788
-
789
- /* Spacing Tokens
790
- -----------------------------------------------*/
791
- /* Base spacing tokens - Based on 4px grid */
792
- --space-none: 0;
793
- --space-4xs: 0.125rem; /* 2px */
794
- --space-3xs: 0.25rem; /* 4px */
795
- --space-2xs: 0.5rem; /* 8px */
796
- --space-xs: 0.75rem; /* 12px */
797
- --space-sm: 1rem; /* 16px */
798
- --space-md: 1.5rem; /* 24px */
799
- --space-lg: 2rem; /* 32px */
800
- --space-xl: 2.5rem; /* 40px */
801
- --space-2xl: 3rem; /* 48px */
802
- --space-3xl: 4rem; /* 64px */
803
- --space-4xl: 6rem; /* 96px */
804
- --space-5xl: 8rem; /* 128px */
805
-
806
- /* Semantic Padding Tokens */
807
- --padding-button: var(--space-xs) var(--space-md);
808
- --padding-input: var(--space-xs);
809
- --padding-card: var(--space-md);
810
- --padding-card-sm: var(--space-sm);
811
- --padding-card-lg: var(--space-lg);
812
-
813
- --padding-section: var(--space-3xl) 0;
814
- --padding-section-sm: var(--space-2xl) 0;
815
- --padding-section-lg: var(--space-4xl) 0;
816
-
817
- /* Container and Layout Tokens */
818
- --container-padding: var(--space-md);
819
- --container-padding-sm: var(--space-sm);
820
- --container-padding-lg: var(--space-lg);
821
-
822
- /* Inset (Padding All Sides) Tokens */
823
- --inset-xs: var(--space-xs);
824
- --inset-sm: var(--space-sm);
825
- --inset-md: var(--space-md);
826
- --inset-lg: var(--space-lg);
827
-
828
- /* Stack (Vertical Spacing) Tokens */
829
- --stack-xs: var(--space-xs);
830
- --stack-sm: var(--space-sm);
831
- --stack-md: var(--space-md);
832
- --stack-lg: var(--space-lg);
833
- --stack-xl: var(--space-xl);
834
-
835
- /* Inline (Horizontal Spacing) Tokens */
836
- --inline-xs: var(--space-xs);
837
- --inline-sm: var(--space-sm);
838
- --inline-md: var(--space-md);
839
- --inline-lg: var(--space-lg);
1552
+ /* The OLD VALUES block stood here: --display-*, --heading-*, --body-* and
1553
+ --caption-* as pixel font sizes, and a second spacing vocabulary in
1554
+ --space-*, --padding-*, --container-*, --inset-*, --stack-* and
1555
+ --inline-*. Forty-nine tokens, every one a duplicate of something on
1556
+ a live scale under a different name, and not one of them referenced
1557
+ by any component. They are gone. Type comes from
1558
+ --typography-fontSize-* and the component type tokens above it;
1559
+ spacing comes from --spacing-*. */
840
1560
 
841
1561
  /* Accessibility Spacing Tokens
842
1562
  -----------------------------------------------*/
@@ -848,7 +1568,20 @@ export const tokens = css`
848
1568
  --content-width-readable: 70ch; /* Approximately 70 characters per line */
849
1569
  --content-width-max: 1200px; /* Maximum overall content width */
850
1570
 
851
- /** GRID TOKENS
1571
+ /** GRID AND BREAKPOINT TOKENS
1572
+
1573
+ These have no consumer in this package and are kept deliberately.
1574
+ A media condition cannot read a custom property — @media
1575
+ (max-width: var(--breakpoint-sm)) is not valid CSS — so a breakpoint
1576
+ token can never be referenced the way a colour is. It exists so the
1577
+ six values have one place to be read from, by the lint that checks
1578
+ media queries against them and by anything generating CSS. The grid
1579
+ tokens are the same case: a layout that wants them reads them, and
1580
+ nothing in the atom layer does.
1581
+
1582
+ They are the exception. Everything else here that nothing references
1583
+ is dead weight, and the OLD VALUES and FONT TOKENS blocks that used
1584
+ to sit below were removed for exactly that reason.
852
1585
  */
853
1586
  /* Grid container max widths */
854
1587
  --grid-container-max-xs: 100%; /* mobile full width */
@@ -882,162 +1615,19 @@ export const tokens = css`
882
1615
  --breakpoint-xxl: 1400px;
883
1616
  /* =====================
884
1617
  FONT-FACE DEFINITIONS
1618
+ Intentionally NOT here. @font-face nested inside a :host rule is
1619
+ discarded at parse time, and even at the top level of a shadow-root
1620
+ stylesheet it never reaches document.fonts. Font faces must be
1621
+ loaded in document scope: see src/tokens/fonts.css.
885
1622
  ====================== */
886
1623
 
887
- @font-face {
888
- font-family: "IBM Plex Mono";
889
- font-style: normal;
890
- font-weight: 700;
891
- src: url("../assets/fonts/IBMPlexMono-Bold.woff2") format("woff2");
892
- }
893
- @font-face {
894
- font-family: "IBM Plex Mono";
895
- font-style: normal;
896
- font-weight: 700;
897
- src: url("../assets/fonts/IBMPlexMono-BoldItalic.woff2") format("woff2");
898
- }
899
-
900
- @font-face {
901
- font-family: "IBM Plex Mono";
902
- font-style: italic;
903
- font-weight: 300;
904
- src: url("../assets/fonts/IBMPlexMono-Italic.woff2") format("woff2");
905
- }
906
- @font-face {
907
- font-family: "IBM Plex Mono";
908
- font-style: normal;
909
- font-weight: 300;
910
- src: url("../assets/fonts/IBMPlexMono-Light.woff2") format("woff2");
911
- }
912
- @font-face {
913
- font-family: "IBM Plex Mono";
914
- font-style: italic;
915
- font-weight: 300;
916
- src: url("../assets/fonts/IBMPlexMono-LightItalic.woff2") format("woff2");
917
- }
918
-
919
- @font-face {
920
- font-family: "IBM Plex Mono";
921
- font-style: italic;
922
- font-weight: 500;
923
- src: url("../assets/fonts/IBMPlexMono-Medium.woff2") format("woff2");
924
- }
925
- @font-face {
926
- font-family: "IBM Plex Mono";
927
- font-style: italic;
928
- font-weight: 500;
929
- src: url("../assets/fonts/IBMPlexMono-MediumItalic.woff2") format("woff2");
930
- }
931
- @font-face {
932
- font-family: "IBM Plex Mono";
933
- font-style: italic;
934
- font-weight: 400;
935
- src: url("../assets/fonts/IBMPlexMono-Regular.woff2") format("woff2");
936
- }
937
- @font-face {
938
- font-family: "IBM Plex Mono";
939
- font-style: italic;
940
- font-weight: 600;
941
- src: url("../assets/fonts/IBMPlexMono-SemiBold.woff2") format("woff2");
942
- }
943
- @font-face {
944
- font-family: "IBM Plex Mono";
945
- font-style: italic;
946
- font-weight: 600;
947
- src: url("../assets/fonts/IBMPlexMono-SemiBoldItalic.woff2") format("woff2");
948
- }
949
-
950
- /*
951
- IBM PLEX SANS
952
- */
953
-
954
- @font-face {
955
- font-family: "IBMPlexSans";
956
- font-style: normal;
957
- font-weight: 700;
958
- src: url("../assets/fonts/IBMPlexSans-Bold.woff2") format("woff2");
959
- }
960
- @font-face {
961
- font-family: "IBMPlexSans";
962
- font-style: normal;
963
- font-weight: 700;
964
- src: url("../assets/fonts/IBMPlexSans-BoldItalic.woff2") format("woff2");
965
- }
966
-
967
- @font-face {
968
- font-family: "IBMPlexSans";
969
- font-style: italic;
970
- font-weight: 300;
971
- src: url("../assets/fonts/IBMPlexSans-Italic.woff2") format("woff2");
972
- }
973
- @font-face {
974
- font-family: "IBMPlexSans";
975
- font-style: normal;
976
- font-weight: 300;
977
- src: url("../assets/fonts/IBMPlexSans-Light.woff2") format("woff2");
978
- }
979
- @font-face {
980
- font-family: "IBMPlexSans";
981
- font-style: italic;
982
- font-weight: 300;
983
- src: url("../assets/fonts/IBMPlexSans-LightItalic.woff2") format("woff2");
984
- }
985
-
986
- @font-face {
987
- font-family: "IBMPlexSans";
988
- font-style: italic;
989
- font-weight: 500;
990
- src: url("../assets/fonts/IBMPlexSans-Medium.woff2") format("woff2");
991
- }
992
- @font-face {
993
- font-family: "IBMPlexSans";
994
- font-style: italic;
995
- font-weight: 500;
996
- src: url("../assets/fonts/IBMPlexSans-MediumItalic.woff2") format("woff2");
997
- }
998
- @font-face {
999
- font-family: "IBMPlexSans";
1000
- font-style: italic;
1001
- font-weight: 400;
1002
- src: url("../assets/fonts/IBMPlexSans-Regular.woff2") format("woff2");
1003
- }
1004
- @font-face {
1005
- font-family: "IBMPlexSans";
1006
- font-style: italic;
1007
- font-weight: 600;
1008
- src: url("../assets/fonts/IBMPlexSans-SemiBold.woff2") format("woff2");
1009
- }
1010
- @font-face {
1011
- font-family: "IBMPlexSans";
1012
- font-style: italic;
1013
- font-weight: 600;
1014
- src: url("../assets/fonts/IBMPlexSans-SemiBoldItalic.woff2") format("woff2");
1015
- }
1016
-
1017
- /* =====================
1018
- FONT TOKENS
1019
- ====================== */
1020
- /* IBM Plex Mono */
1021
- --font-family-mono: "IBM Plex Mono", monospace;
1022
- --font-weight-mono-light: 300;
1023
- --font-weight-mono-regular: 400;
1024
- --font-weight-mono-medium: 500;
1025
- --font-weight-mono-semibold: 600;
1026
- --font-weight-mono-bold: 700;
1027
-
1028
- --font-style-mono-normal: normal;
1029
- --font-style-mono-italic: italic;
1030
-
1031
- /* IBM Plex Sans */
1032
- --typography-fontFamily-sans: 'IBM Plex Sans', sans-serif;
1033
- --font-weight-sans-light: 300;
1034
- --typography-body-large-fontWeight: 400;
1035
- --font-weight-sans-medium: 500;
1036
- --typography-fontWeight-semibold: 600;
1037
- --typography-fontWeight-bold: 700;
1624
+ /* The FONT TOKENS block stood here: --font-weight-mono-*,
1625
+ --font-weight-sans-*, --font-style-mono-* and --font-style-sans-*.
1626
+ Thirteen tokens duplicating --typography-fontWeight-* under a second
1627
+ name, with regular where the live scale says normal, and nothing
1628
+ consuming any of them. --font-family-mono went earlier, with bd-li.
1629
+ A weight is a weight whichever face it is set in. */
1038
1630
 
1039
- --font-style-sans-normal: normal;
1040
- --font-style-sans-italic: italic;
1041
1631
  /* Awards section — Orbit Frame 14 (node 1882:9342) */
1042
1632
  --layout-awards-section-background: var(--color-blue-50);
1043
1633
  --layout-awards-banner-max-width: 37.5rem; /* legacy narrow cap; prefer cluster */
@@ -1045,7 +1635,55 @@ export const tokens = css`
1045
1635
  --layout-awards-intro-max-width: 39.4375rem; /* 631px — headline stack */
1046
1636
  /* Legacy: narrow lower block from Orbit; prefer --layout-ensemble-content-max for compare-aligned grids */
1047
1637
  --layout-awards-content-max-width: 53.0625rem; /* 849px */
1638
+ /* There is no page-width token. The page column is an implementation --
1639
+ src/components/shared/ensemble.css.js -- on the grid container scale
1640
+ above (--grid-container-max-* with --grid-padding-*), which is what
1641
+ Foundations/Grid System documented and nothing had built. A width token
1642
+ here is how six ensembles came to hold one literal each and seven other
1643
+ modules a different one. This padding step stays only because the blog
1644
+ still reads it. */
1048
1645
  --layout-ensemble-inline-padding: var(--spacing-24);
1049
1646
 
1647
+ `;
1648
+
1649
+ const LAYER = 'bd-tokens';
1650
+ const INSTALLED = '__bdDesignTokens';
1651
+
1652
+ function install() {
1653
+ if (typeof document === 'undefined') return;
1654
+
1655
+ const rule = `@layer ${LAYER} { :root {${declarations}} }`;
1656
+
1657
+ if ('adoptedStyleSheets' in document && typeof CSSStyleSheet === 'function') {
1658
+ if (document.adoptedStyleSheets.some(sheet => sheet[INSTALLED])) return;
1659
+ try {
1660
+ const sheet = new CSSStyleSheet();
1661
+ sheet.replaceSync(rule);
1662
+ sheet[INSTALLED] = true;
1663
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
1664
+ return;
1665
+ } catch {
1666
+ // fall through to the <style> path below
1050
1667
  }
1051
- `;
1668
+ }
1669
+
1670
+ // Fallback for anything without constructable stylesheets. Without this the
1671
+ // whole token set would simply be absent rather than degrading.
1672
+ if (document.querySelector(`style[data-${LAYER}]`)) return;
1673
+ const style = document.createElement('style');
1674
+ style.setAttribute(`data-${LAYER}`, '');
1675
+ style.textContent = rule;
1676
+ document.head.appendChild(style);
1677
+ }
1678
+
1679
+ install();
1680
+
1681
+ /** The raw declaration text, for tooling that needs to emit tokens elsewhere. */
1682
+ export { declarations as tokenDeclarations };
1683
+
1684
+ /**
1685
+ * @deprecated Components no longer need to adopt this — the tokens live in
1686
+ * document scope. Kept, and deliberately empty, so that any existing
1687
+ * `static styles = [tokens, componentCSS]` keeps working unchanged.
1688
+ */
1689
+ export const tokens = css``;