@repobit/dex-system-design 0.25.12 → 0.26.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 +187 -0
  2. package/README.md +131 -8
  3. package/package.json +95 -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
@@ -0,0 +1,257 @@
1
+ import { html } from 'lit';
2
+ import { copyToken, docsPage, refSection, resolve } from './story-chrome.js';
3
+ import { DOCS, analyse } from './token-map.js';
4
+ import { BORDERS, SURFACES } from './token-purposes.js';
5
+ /* The registry and the generated stylesheet themselves, not copies of them --
6
+ the same rule Foundations/Spacing/Rhythm follows. */
7
+ import registry from '../../scripts/token-tiers.json';
8
+ import tokensCss from './tokens.css?raw';
9
+
10
+ export default {
11
+ title: 'Foundations/Token Map',
12
+ tags : ['autodocs']
13
+ };
14
+
15
+ /* Every file that might read a token, as raw text. Lazy, so the source only
16
+ loads when this page is opened. The blog's own token copies are left out:
17
+ they declare tokens, and a declaration is not a read. */
18
+ const SOURCES = import.meta.glob([
19
+ '../components/**/*.js',
20
+ '../blog/**/*.{js,css}',
21
+ '!../blog/tokens.*',
22
+ '!../blog/tokens/**',
23
+ '!../blog/src/tokens/**',
24
+ './*.stories.js',
25
+ './story-chrome.js'
26
+ ], { query: '?raw', import: 'default' });
27
+
28
+ /* Foundations/Surfaces renders its role tables from token-purposes.js, so the
29
+ tokens in those two tables are shown by the docs. The rest of that module is
30
+ description text for Figma, and naming a token in a description is not
31
+ reading it -- counting the whole module would hide --touch-target-spacing
32
+ and --content-width-max, which nothing reads. */
33
+ const SURFACES_PAGE = [...SURFACES, ...BORDERS].map(r => `'${r.token}'`)
34
+ .join(' ');
35
+
36
+ const ownerOf = path => {
37
+ if (path.endsWith('.stories.js') || path.startsWith('./')) return DOCS;
38
+ if (path.startsWith('../blog/')) return 'blog';
39
+ return path.split('/')[2] ?? path;
40
+ };
41
+
42
+ async function load() {
43
+ const entries = await Promise.all(Object.entries(SOURCES).map(async ([path, read]) => [path, await read()]));
44
+ const sources = { ...Object.fromEntries(entries), './surfaces-role-tables': SURFACES_PAGE };
45
+ return analyse({ cssText: tokensCss, registry, sources, ownerOf });
46
+ }
47
+
48
+ /* ------------------------------------------------------------------ cells - */
49
+
50
+ const isColour = value => /^(#|rgb|hsl|color-mix)/.test(value);
51
+
52
+ const swatch = token => html`<span
53
+ style="display:inline-block;width:var(--dimension-16);height:var(--dimension-16);vertical-align:middle;margin-right:var(--spacing-6);border-radius:var(--radius-sm);border:var(--border-width-1) solid var(--color-neutral-200);background:var(${token})"></span>`;
54
+
55
+ const chainCell = row => html`<span class="mono">${row.chain.hops.slice(1).map(h => html` → ${h}`)}${row.chain.compound
56
+ ? html` <span class="muted">(combines several)</span>`
57
+ : ''}</span>`;
58
+
59
+ const valueCell = row => {
60
+ const value = resolve(row.name);
61
+ return html`<span class="num">${isColour(value) ? swatch(row.name) : ''}${value}</span>`;
62
+ };
63
+
64
+ const readersCell = row => {
65
+ const owners = [...row.sourceReaders].sort();
66
+ const tokens = row.tokenReaders.size;
67
+ const reach = [...row.reachable].sort();
68
+ if (!owners.length && !tokens) {
69
+ return reach.length
70
+ ? html`<span class="muted">reachable via ${reach.join(', ')}</span>`
71
+ : html`<span class="unused">nothing</span>`;
72
+ }
73
+ return html`${owners.join(', ')}${tokens ? html`${owners.length ? ' · ' : ''}<span class="muted">${tokens} token${tokens === 1 ? '' : 's'}</span>` : ''}`;
74
+ };
75
+
76
+ const tokenList = rows => html`
77
+ <div class="table-scroll">
78
+ <table>
79
+ <caption>Tokens</caption>
80
+ <thead><tr><th scope="col">Token</th><th scope="col">Tier</th><th scope="col">Family</th><th scope="col">Value</th></tr></thead>
81
+ <tbody>${rows.map(r => html`<tr>
82
+ <td>${copyToken(r.name)}</td><td>${r.tier}</td>
83
+ <td class="mono">${r.family?.prefix}${r.family?.owner ? html` <span class="muted">(${r.family.owner})</span>` : ''}</td>
84
+ <td>${valueCell(r)}</td></tr>`)}</tbody>
85
+ </table>
86
+ </div>`;
87
+
88
+ const collapsed = (summary, body) => html`<details class="usage"><summary>${summary}</summary>${body}</details>`;
89
+
90
+ /* ------------------------------------------------------------------- page - */
91
+
92
+ const GRAMMAR = [
93
+ ['primitive', '--{category}-{family}-{step}', '--color-blue-600, --spacing-16, --radius-lg'],
94
+ ['semantic', '--{category}-{role}-{variant}', '--color-text-primary, --status-error-fill, --typography-heading-h2-fontSize'],
95
+ ['component', '--{component}-{variant}-{property}-{state}', '--button-primary-background-hover']
96
+ ];
97
+
98
+ export const TokenMap = (_args, { loaded: { map } }) => {
99
+ const { rows, flags } = map;
100
+ const tiers = Object.keys(registry.tiers);
101
+ const count = tier => rows.filter(r => r.tier === tier).length;
102
+ const STATES = ['default', 'hover', 'active', 'disabled'];
103
+ const states = STATES.map(s => [s, rows.filter(r => r.name.endsWith(`-${s}`)).length]);
104
+ /* both claims in the naming paragraph are computed, so neither can go stale */
105
+ /* default is left out: it is also a size and a variant name here
106
+ (--form-label-default-* sits beside -small and -large), so a default
107
+ before the property is not evidence of a state in the wrong place */
108
+ const midState = rows.filter(r => r.name.slice(2).split('-')
109
+ .slice(0, -1)
110
+ .some(seg => seg !== 'default' && STATES.includes(seg)) && r.tier === 'component');
111
+ const otherStates = rows.filter(r => /-(hovered|pressed|focused|selected)(-|$)/.test(r.name));
112
+ const judged = registry.families.filter(f => f.basis === 'judgement');
113
+
114
+ return docsPage({
115
+ title: 'Token map',
116
+ lede : `Every design token: which tier it is, what it resolves through, and who reads it. Generated
117
+ when this page opens, from tokens.css, scripts/token-tiers.json and the source of every
118
+ component, so nothing on it is transcribed and nothing on it can go stale.`,
119
+ note: html`<b>The findings are a report, not a gate.</b> Each kind below has been a decision in
120
+ this package at least once rather than a defect -- spacing tokens with no role above them, two
121
+ surfaces that share a grey on purpose -- and this page cannot tell which. It lists them so they
122
+ are seen. The one gate here is <code>check:token-tiers</code>, which only checks that every
123
+ token has a declared tier.<br />
124
+ <b>"Read" means named in source.</b> <code>var(--x)</code> counts, and so does a name held as
125
+ data (<code>token-scales.js</code> maps <code>color="muted"</code> to
126
+ <code>'--color-text-muted'</code>). A step a scale helper could build at runtime, like
127
+ <code>bd-h paddingBottom="16"</code>, is shown as <i>reachable</i>, not read. Stories count
128
+ separately: a token read only by documentation is not read by the product.`,
129
+ children: html`
130
+ ${refSection({
131
+ name : 'Naming',
132
+ meta : 'the shape each tier takes',
133
+ children: html`
134
+ <div class="table-scroll"><table>
135
+ <caption>Naming shape per tier</caption>
136
+ <thead><tr><th scope="col">Tier</th><th scope="col">Shape</th><th scope="col">Examples</th></tr></thead>
137
+ <tbody>${GRAMMAR.map(([t, shape, ex]) => html`<tr><td>${t}</td><td class="mono">${shape}</td><td class="mono">${ex}</td></tr>`)}</tbody>
138
+ </table></div>
139
+ <p style="max-width:70ch">
140
+ Segments are kebab-case. A segment that is a CSS property keeps the property's camelCase
141
+ (<code>fontSize</code>, <code>letterSpacing</code>), so the name says which property it is
142
+ for. State is the last segment, and the package uses four:
143
+ ${states.map(([s, n], i) => html`${i ? ', ' : ''}<code>${s}</code> (${n})`)}.
144
+ ${otherStates.length
145
+ ? html`<b>${otherStates.length} use another state word:</b> ${otherStates.map(r => r.name).join(', ')}.`
146
+ : html`No token uses <code>hovered</code>, <code>pressed</code>, <code>focused</code> or <code>selected</code>.`}
147
+ ${midState.length
148
+ ? html`<b>${midState.length} component tokens put the state before the property</b>,
149
+ against the shape above: ${midState.map((r, i) => html`${i ? ', ' : ''}<code>${r.name}</code>`)}.
150
+ <code>default</code> is not counted, because it is also a size and a variant name
151
+ here: <code>--form-label-default-*</code> sits beside <code>-small</code> and
152
+ <code>-large</code>.`
153
+ : ''}
154
+ </p>`
155
+ })}
156
+
157
+ ${refSection({
158
+ name : 'Tiers',
159
+ meta : `${rows.length} tokens · ${registry.families.length} families`,
160
+ children: html`
161
+ <p style="max-width:70ch">
162
+ ${tiers.map((t, i) => html`${i ? ' · ' : ''}<b>${count(t)}</b> ${t}`)}.
163
+ ${tiers.map(t => html`<br /><b>${t}</b> — ${registry.tiers[t]}`)}
164
+ </p>
165
+ <p style="max-width:70ch">
166
+ <b>${judged.length} families were placed by judgement</b> rather than by documentation or
167
+ convention, and are the weakest calls in the registry:
168
+ ${judged.map((f, i) => html`${i ? ', ' : ''}<code>${f.prefix}</code> (${f.tier})`)}.
169
+ </p>`
170
+ })}
171
+
172
+ ${refSection({
173
+ name : 'Nothing reads these',
174
+ meta : `${flags.unread.length} semantic or component tokens`,
175
+ children: html`
176
+ <p style="max-width:70ch">Not named by any component, the blog, or another token. This is the
177
+ finding that retired <code>display-hero</code> and <code>--status-brand-surface-strong</code>:
178
+ each was either deleted or found a consumer. A token here may also be kept on purpose -- that
179
+ is for its owner to say.</p>
180
+ ${tokenList(flags.unread)}`
181
+ })}
182
+
183
+ ${refSection({
184
+ name : 'Read only by the documentation',
185
+ meta : `${flags.docsOnly.length} tokens`,
186
+ children: tokenList(flags.docsOnly)
187
+ })}
188
+
189
+ ${refSection({
190
+ name : 'One colour, one slot, several tones',
191
+ meta : `${flags.sameSlot.length} slots`,
192
+ children: html`
193
+ <p style="max-width:70ch">Semantic tokens that differ only in the tone or variant segment and
194
+ resolve to the same primitive. Two tones that share every slot cannot be told apart by
195
+ colour; a slot like <code>-on-fill</code> is expected to repeat, because the fill beside it
196
+ carries the difference.</p>
197
+ <div class="table-scroll"><table>
198
+ <caption>Shared slots</caption>
199
+ <thead><tr><th scope="col">Slot</th><th scope="col">Primitive</th><th scope="col">Shared by</th></tr></thead>
200
+ <tbody>${flags.sameSlot.map(g => html`<tr>
201
+ <td class="mono">${g.pattern}</td>
202
+ <td class="num">${swatch(g.primitive)}${g.primitive}</td>
203
+ <td>${g.members.join(', ')}</td></tr>`)}</tbody>
204
+ </table></div>
205
+ ${collapsed(html`<span class="usage-count">${flags.sameRole.length}</span> pairs that share a value across two roles — expected`, html`
206
+ <ul>${flags.sameRole.map(p => html`<li>${p.a.name} = ${p.b.name} (${p.primitive})</li>`)}</ul>`)}`
207
+ })}
208
+
209
+ ${refSection({
210
+ name : 'Component tokens that skip the semantic tier',
211
+ meta : `${flags.tierSkips.reduce((n, g) => n + g.list.length, 0)} tokens · ${flags.inversions.length} inversions`,
212
+ children: html`
213
+ <p style="max-width:70ch">A component token whose first reference is a primitive. Grouped by the
214
+ primitive family it reaches, beside the semantic tokens that already sit above that family --
215
+ the roles it could have asked for. Where that count is zero there was no role to ask for,
216
+ and the skip is the design working. Where it is not zero, read the roles before calling it
217
+ a defect: <code>--spacing-*</code> has the two card roles above it, and that is not a
218
+ spacing vocabulary.</p>
219
+ <div class="table-scroll"><table>
220
+ <caption>Tier skips by primitive family</caption>
221
+ <thead><tr><th scope="col">Reaches</th><th scope="col">Tokens</th><th scope="col">Semantic tokens above it</th></tr></thead>
222
+ <tbody>${flags.tierSkips.map(g => html`<tr>
223
+ <td class="mono">${g.prefix}*</td>
224
+ <td>${collapsed(html`<span class="usage-count">${g.list.length}</span>`, html`
225
+ <ul>${g.list.map(s => html`<li>${s.row.name} → ${s.target.name}</li>`)}</ul>`)}</td>
226
+ <td class="num">${g.rolesAbove.length}</td></tr>`)}</tbody>
227
+ </table></div>`
228
+ })}
229
+
230
+ ${refSection({
231
+ name : 'Listed for completeness',
232
+ meta : 'expected, and kept visible',
233
+ children: html`
234
+ ${collapsed(html`<span class="usage-count">${flags.literalSemantics.length}</span> semantic tokens declared as a literal, with no primitive beneath`, tokenList(flags.literalSemantics))}
235
+ ${collapsed(html`<span class="usage-count">${flags.unreadAxes.length}</span> unread axes of a typography role whose other axes are read`, tokenList(flags.unreadAxes))}
236
+ ${collapsed(html`<span class="usage-count">${flags.reachableOnly.length}</span> steps a scale helper can build but nothing names`, tokenList(flags.reachableOnly))}
237
+ ${collapsed(html`<span class="usage-count">${flags.unreadSteps.length}</span> primitive steps nothing reads — a scale may be complete`, tokenList(flags.unreadSteps))}`
238
+ })}
239
+
240
+ ${refSection({
241
+ name : 'Every token',
242
+ meta : 'by family',
243
+ children: registry.families.map(f => {
244
+ const members = rows.filter(r => r.family === f);
245
+ return collapsed(html`<span class="mono">${f.prefix}*</span> <span class="muted">${f.tier} · ${members.length}</span>`, html`
246
+ <div class="table-scroll"><table>
247
+ <caption>${f.prefix}</caption>
248
+ <thead><tr><th scope="col">Token</th><th scope="col">Resolves through</th><th scope="col">Value</th><th scope="col">Read by</th></tr></thead>
249
+ <tbody>${members.map(r => html`<tr>
250
+ <td>${copyToken(r.name)}</td><td>${chainCell(r)}</td><td>${valueCell(r)}</td><td>${readersCell(r)}</td></tr>`)}</tbody>
251
+ </table></div>`);
252
+ })
253
+ })}`
254
+ });
255
+ };
256
+ TokenMap.storyName = 'Token map';
257
+ TokenMap.loaders = [async () => ({ map: await load() })];
@@ -0,0 +1,266 @@
1
+ /**
2
+ * What each semantic token is FOR, in one place.
3
+ *
4
+ * Roles are described by the job they do, not by the colour they currently
5
+ * resolve to. The swatch on a docs page is there to recognise one on screen;
6
+ * it is never the reason to choose one.
7
+ *
8
+ * Two readers: Foundations/Surfaces renders SURFACES and BORDERS, and
9
+ * scripts/figma-variable-descriptions.mjs turns every entry here into the
10
+ * description a designer sees in Figma's variable picker. Writing the
11
+ * sentence once is the point -- a second hand-kept copy in Figma is how a
12
+ * library goes on describing a role code has changed.
13
+ *
14
+ * Not a story file: Storybook only globs *.stories.js. Pure data, so it
15
+ * imports under Node.
16
+ */
17
+
18
+ export const SURFACES = [
19
+ {
20
+ token : '--color-surface-default',
21
+ purpose : 'The page, canvas or component ground. The thing other things sit on.',
22
+ use : 'The base of a page, a section, or a component that is not itself a distinct object.',
23
+ avoid : 'A card or tile that needs to read as its own object — that is surface-container.',
24
+ relation: 'The reference point. Every other surface is described by how far it sits from this one.',
25
+ example : 'Page background, modal body, the ground inside a section wrapper.',
26
+ theme : 'Whatever the page ground is in the active theme. On dark it is the dark ground, not white.'
27
+ },
28
+ {
29
+ token : '--color-surface-container',
30
+ purpose : 'The ground a self-contained content object sits on, one step off the page, so it reads as its own object without needing a border or a shadow. The step is real but faint: Foundations/Surfaces, A card\'s ground and its edge.',
31
+ use : 'A card, tile, panel or bounded section that should read as a distinct object on the page.',
32
+ avoid : 'The page itself (surface-default); a low-emphasis grouping wash (surface-subtle); a disabled or pressed state (surface-muted); anything on dark (surface-inverse); brand or status meaning.',
33
+ relation: 'One step from default, and closer to it than subtle is. Container is the ground of an object; subtle is a wash behind a group.',
34
+ example : 'Card and tile surfaces, feature panels, quote cards.',
35
+ theme : 'Adjacent to but distinguishable from surface-default. On dark that means one step lighter than the page, not a fixed grey.'
36
+ },
37
+ {
38
+ token : '--color-surface-subtle',
39
+ purpose : 'A low-emphasis background that groups content without making it an object.',
40
+ use : 'A band or wash behind several things that already belong together.',
41
+ avoid : 'The ground of a single self-contained object — that is container.',
42
+ relation: 'Further from default than container. Recedes rather than stands out.',
43
+ example : 'A grouping band behind a row of items.',
44
+ theme : 'A recessive wash relative to the page ground.'
45
+ },
46
+ {
47
+ token : '--color-surface-muted',
48
+ purpose : 'A state surface: the thing is present but not currently active.',
49
+ use : 'Pressed, inactive or de-emphasised states.',
50
+ avoid : 'Resting surfaces. If nothing is in a state, this is the wrong family.',
51
+ relation: 'Shares its value with surface-disabled today; the meanings differ, so do not treat them as interchangeable.',
52
+ example : 'A pressed toggle track, a de-emphasised row.',
53
+ theme : 'A state step away from the surface it applies to.'
54
+ },
55
+ {
56
+ token : '--color-surface-disabled',
57
+ purpose : 'The surface of a control that cannot currently be used.',
58
+ use : 'Disabled controls only.',
59
+ avoid : 'Anything a user can still operate.',
60
+ relation: 'Same value as muted today, different meaning. Disabled is exempt from contrast minimums; muted is not.',
61
+ example : 'A disabled input or button ground.',
62
+ theme : 'Reads as unavailable in the active theme.'
63
+ },
64
+ {
65
+ token : '--color-surface-pressed',
66
+ purpose : 'The ground of a control while it is held down, when surface-muted is already its hover.',
67
+ use : 'The pressed state of a control whose hover is surface-muted.',
68
+ avoid : 'A resting or hovered ground; a pressed action fill, which is the action ramp.',
69
+ relation: 'One step further from the page than muted, the way a press follows a hover.',
70
+ example : 'bd-back while pressed.',
71
+ theme : 'A state step past muted in the active theme. On dark, lighter than muted, not darker.'
72
+ },
73
+ {
74
+ token : '--color-surface-inverse',
75
+ purpose : 'A ground that flips the text polarity: content on it uses the inverse text roles.',
76
+ use : 'Dark panels inside a light page, tooltips, inverted sections.',
77
+ avoid : 'Ordinary surfaces. Choosing this changes which text roles are legible.',
78
+ relation: 'Not a step on the default-container-subtle ladder; it is the other side of it.',
79
+ example : 'Tooltip body. Not a video or terminal frame: those stay dark in every theme, and read surface-media.',
80
+ theme : 'Inverts with the theme: on a dark theme this is the light ground.'
81
+ },
82
+ {
83
+ token : '--color-surface-media',
84
+ purpose : 'The frame around media: a video letterbox, a terminal panel. Dark in every theme.',
85
+ use : 'A ground that must stay dark because what it frames is dark. Its ink is text-on-media.',
86
+ avoid : 'A dark panel that should flip with the theme, such as a tooltip: that is surface-inverse.',
87
+ relation: 'Same value as surface-inverse in light; the two part ways in a dark theme, where inverse turns light and media does not.',
88
+ example : 'bd-video-player and bd-video letterboxes, the bd-terminal-message panel.',
89
+ theme : 'Fixed: the same near-black in every theme.'
90
+ },
91
+ {
92
+ token : '--color-surface-brand',
93
+ purpose : 'A surface whose job is to carry brand identity.',
94
+ use : 'Large brand areas, such as a footer masthead band.',
95
+ avoid : 'Anything blue that is not making a brand statement. An indicator, a badge, a selected row and a switch track are all blue and none of them are brand surfaces.',
96
+ relation: 'A meaning, not a colour. Matching the brand primitive by eye is not evidence.',
97
+ example : 'The coloured band at the top of the footer.',
98
+ theme : 'Follows the brand, not the neutral ramp.'
99
+ },
100
+ {
101
+ token : '--color-surface-info',
102
+ purpose : 'The tint an informational message sits on.',
103
+ use : 'Informational banners and callouts.',
104
+ avoid : 'Decorative blue backgrounds with no message semantics.',
105
+ relation: 'One of the four status tints, each paired with a matching text role.',
106
+ example : 'An informational banner ground.',
107
+ theme : 'A readable informational tint against its paired text role.'
108
+ },
109
+ {
110
+ token : '--color-surface-success',
111
+ purpose : 'The tint a success message sits on.',
112
+ use : 'Confirmation and success states.',
113
+ avoid : 'Decorative green.',
114
+ relation: 'Paired with the matching success text role; the pair is measured together.',
115
+ example : 'A success banner ground.',
116
+ theme : 'A readable success tint against its paired text role.'
117
+ },
118
+ {
119
+ token : '--color-surface-warning',
120
+ purpose : 'The tint a warning message sits on.',
121
+ use : 'Cautionary states.',
122
+ avoid : 'Decorative yellow.',
123
+ relation: 'Paired with the matching warning text role.',
124
+ example : 'A warning banner ground.',
125
+ theme : 'A readable warning tint against its paired text role.'
126
+ },
127
+ {
128
+ token : '--color-surface-error',
129
+ purpose : 'The tint an error message sits on.',
130
+ use : 'Error and destructive-outcome states.',
131
+ avoid : 'Decorative red.',
132
+ relation: 'Paired with the matching error text role.',
133
+ example : 'An error banner ground.',
134
+ theme : 'A readable error tint against its paired text role.'
135
+ }
136
+ ];
137
+
138
+ export const BORDERS = [
139
+ {
140
+ token : '--color-border-subtle',
141
+ purpose: 'A decorative hairline between things that already belong together.',
142
+ use : 'Separating rows or cells inside one object.',
143
+ avoid : 'The edge of anything actionable. It sits below the 3:1 that WCAG 1.4.11 asks of a UI component, on purpose.'
144
+ },
145
+ {
146
+ token : '--color-border-default',
147
+ purpose: 'The boundary of an object or control.',
148
+ use : 'Inputs, bounded controls, the edge of a component.',
149
+ avoid : 'Content separation inside a component — that is a divider job.'
150
+ },
151
+ {
152
+ token : '--color-border-strong',
153
+ purpose: 'A boundary that has to assert itself.',
154
+ use : 'High-emphasis edges.',
155
+ avoid : 'Ordinary component edges, which would flatten the emphasis.'
156
+ },
157
+ {
158
+ token : '--color-border-brand',
159
+ purpose: 'A boundary carrying brand emphasis.',
160
+ use : 'Brand-emphasised edges.',
161
+ avoid : 'Focus. Focus has its own tokens and must not be expressed as a border role.'
162
+ },
163
+ {
164
+ token : '--color-border-disabled',
165
+ purpose: 'The edge of a control that cannot be used.',
166
+ use : 'Disabled controls.',
167
+ avoid : 'Anything operable.'
168
+ }
169
+ ];
170
+
171
+ /* The two border roles the Surfaces table does not list, worded from
172
+ CLAUDE.md's "A card's ground and its edge". */
173
+ export const CARD_EDGES = [
174
+ {
175
+ token : '--color-border-container',
176
+ purpose: 'The edge of a card that sits on a tinted section.',
177
+ use : 'A card on a tinted band, paired with surface-default as its ground.',
178
+ avoid : 'A card on the page ground: there the surface-container step is the separation and an edge is redundant. On a surface-muted band this edge is the same colour as the band and disappears.'
179
+ },
180
+ {
181
+ token : '--color-border-container-inverse',
182
+ purpose: 'The edge of a container that sits on an inverse surface.',
183
+ use : 'Bounding an object on a dark ground.',
184
+ avoid : 'Light surfaces.'
185
+ }
186
+ ];
187
+
188
+ /* Text. Hierarchy and state, from Foundations/Color/Responsibilities. */
189
+ export const TEXT = [
190
+ { token: '--color-text-primary', purpose: 'Default ink on a light surface: headings and body copy.', avoid: 'Inverse surfaces, where text-inverse takes over.' },
191
+ { token: '--color-text-secondary', purpose: 'Supporting copy under a title.', avoid: 'A disabled control: disabled is a state, not a quieter step.' },
192
+ { token: '--color-text-muted', purpose: 'Metadata, captions and timestamps.', avoid: 'A disabled control: disabled is a state, not a quieter step.' },
193
+ { token: '--color-text-subtle', purpose: 'The quietest readable step of the hierarchy, on the page or a card.', avoid: 'A band (surface-subtle, surface-muted) or a status tint: use text-muted there, because subtle cannot clear 4.5:1 on them. A disabled control: disabled is a state, not a quieter step.' },
194
+ { token: '--color-text-disabled', purpose: 'STATE: text on a control the user cannot operate. Exempt from contrast minimums only because it is disabled.', avoid: 'Quiet content that is still usable. Loading is not disabled.' },
195
+ { token: '--color-text-inverse', purpose: 'High-emphasis text on an inverse surface.', avoid: 'Light surfaces.' },
196
+ { token: '--color-text-muted-inverse', purpose: 'Everything subordinate on an inverse surface. The light-side secondary, muted and subtle roles fail contrast there.', avoid: 'Light surfaces.' },
197
+ { token: '--color-text-on-media', purpose: 'Ink on surface-media: a video control glyph, a terminal\'s command lines. White in every theme, fixed with its ground.', avoid: 'Text on an inverse surface (text-inverse), which flips in a dark theme.' },
198
+ { token: '--color-text-on-media-muted', purpose: 'The quieter ink on surface-media, such as a terminal\'s subdued lines.', avoid: 'Subordinate text on an inverse surface: that is text-muted-inverse, which flips.' },
199
+ { token: '--color-text-brand', purpose: 'Brand-coloured ink, such as a link at rest. One step darker than action-primary, because blue-500 fails on any tint.', avoid: 'The fill of an interactive element at a state: that is the action ramp.' },
200
+ { token: '--color-text-brand-hover', purpose: 'Brand text under the pointer: a hovered link or citation reference.', avoid: 'A button fill. It used to be --color-action-primary-active, and a fill and a text colour part ways in a dark theme.' },
201
+ { token: '--color-text-success', purpose: 'Success text, paired with surface-success. Offered as color="success" on bd-h and bd-p.', avoid: 'Feedback inside a component (a badge, tag or alert): that is status-success-text.' },
202
+ { token: '--color-text-warning', purpose: 'Warning text, paired with surface-warning. Offered as color="warning" on bd-h and bd-p.', avoid: 'Feedback inside a component: that is status-warning-text.' },
203
+ { token: '--color-text-error', purpose: 'Error text, paired with surface-error.', avoid: 'Feedback inside a component: that is status-error-text.' }
204
+ ];
205
+
206
+ /* Actions: a state ramp, not a colour. Let the state pick the token. */
207
+ export const ACTION = [
208
+ { token: '--color-action-primary', purpose: 'The fill of a primary interactive element at rest.', avoid: 'Brand identity. Action and brand share a value at this step and diverge after it.' },
209
+ { token: '--color-action-primary-hover', purpose: 'The same element, hovered.', avoid: 'Any blue that is not a hover state. There is no hover-blue role.' },
210
+ { token: '--color-action-primary-active', purpose: 'The same element, pressed.', avoid: 'A selected or current item: that is an indicator.' },
211
+ { token: '--color-action-accent', purpose: 'The fill of a conversion action at rest.', avoid: 'Errors and destructive outcomes, which are status-error.' },
212
+ { token: '--color-action-accent-hover', purpose: 'The conversion action, hovered.', avoid: 'Anything that is not a hover state.' },
213
+ { token: '--color-action-accent-active', purpose: 'The conversion action, pressed.', avoid: 'Anything that is not a pressed state.' },
214
+ { token: '--color-action-secondary', purpose: 'The neutral action fill: a secondary button at rest, and a ghost button while pressed.', avoid: 'A dark ground for content (surface-inverse), or near-black ink (text-primary). Matching neutral-950 by value is not evidence.' },
215
+ { token: '--color-action-secondary-hover', purpose: 'The neutral action, hovered.', avoid: 'Anything that is not a hover state.' },
216
+ { token: '--color-action-secondary-active', purpose: 'The neutral action, pressed.', avoid: 'Anything that is not a pressed state.' },
217
+ { token: '--color-action-on-fill', purpose: 'Ink on an action fill: a button label, a checkmark, a radio dot, a switch thumb. White in every theme.', avoid: 'Text on an inverse surface (text-inverse) or the page colour (surface-default): both happen to be white here, and both flip in a dark theme.' }
218
+ ];
219
+
220
+ /* Status: feedback state, owned by bd-badge, bd-tag and bd-alert. One slot
221
+ per weight of a tone, from the comment above the block in tokens.js. */
222
+ export const STATUS_SLOTS = {
223
+ 'text' : 'ink on a tinted chip (badge shade, tag)',
224
+ 'surface' : 'the light tint (tag, alert). A tag sits here so its border reads',
225
+ 'surface-strong': 'the heavier tint a borderless badge sits on, so a badge and a tag of one tone read apart',
226
+ 'border' : 'the hairline around a tag',
227
+ 'accent' : 'the saturated step (badge dot, alert rule)',
228
+ 'fill' : 'a saturated chip (badge fill)',
229
+ 'on-fill' : 'the ink on that saturated chip'
230
+ };
231
+ export const STATUS_TONES = {
232
+ neutral: { label: 'Neutral', note: '' },
233
+ brand : { label: 'Brand', note: 'Brand has no surface-strong: it would be info\'s blue-100, and a brand badge would read as info. Brand and info differ only in accent, so never choose between them by colour.' },
234
+ info : { label: 'Info', note: 'Info and brand differ only in accent, so never choose between them by colour.' },
235
+ success: { label: 'Success', note: '' },
236
+ warning: { label: 'Warning', note: '' },
237
+ error : { label: 'Error', note: '' }
238
+ };
239
+
240
+ /* The rest of the semantic set Figma carries. */
241
+ export const OTHER = [
242
+ { prefix: '--icon-', purpose: 'The size of an icon glyph, from the scale every icon is sized from. Size only: an icon takes its colour from the text around it.' },
243
+ { prefix: '--grid-container-max-', purpose: 'The page column\'s maximum width at this breakpoint. A section never declares its own width; it composes the ensemble column.' },
244
+ { prefix: '--grid-padding-', purpose: 'The page column\'s gutter at the viewport edge, at this breakpoint. The column is the viewport minus twice this.' },
245
+ { prefix: '--grid-gutter-', purpose: 'The space between grid columns at this breakpoint.' },
246
+ { prefix: '--grid-columns', purpose: 'The number of columns in the page grid.' },
247
+ { prefix: '--touch-target-size', purpose: 'The minimum comfortable hit area for a control (WCAG 2.5.8).' },
248
+ { prefix: '--touch-target-spacing', purpose: 'The minimum space between two touch targets.' },
249
+ { prefix: '--content-width-readable', purpose: 'A comfortable reading measure for running text.' },
250
+ { prefix: '--content-width-max', purpose: 'The widest a content region may grow.' },
251
+ { prefix: '--transition-duration-', purpose: 'A motion duration step. Anything animated must respect prefers-reduced-motion.' }
252
+ ];
253
+
254
+ /** { purpose, use?, avoid? } for a token, or null when nothing is written. */
255
+ export function purposeOf(token) {
256
+ const row = [...SURFACES, ...BORDERS, ...CARD_EDGES, ...TEXT, ...ACTION].find(r => r.token === token);
257
+ if (row) return { purpose: row.purpose, use: row.use, avoid: row.avoid };
258
+ const status = token.match(/^--status-([a-z]+)-(.+)$/);
259
+ if (status && STATUS_TONES[status[1]] && STATUS_SLOTS[status[2]]) {
260
+ const tone = STATUS_TONES[status[1]];
261
+ const slot = STATUS_SLOTS[status[2]];
262
+ return { purpose: `${tone.label} feedback: ${slot}.${tone.note ? ` ${tone.note}` : ''}`, avoid: 'Decoration. A status role means state; a colour that carries no message belongs to the general --color-* family.' };
263
+ }
264
+ const other = OTHER.find(o => token.startsWith(o.prefix));
265
+ return other ? { purpose: other.purpose } : null;
266
+ }