@eifi1/ui-kit 0.32.1 → 0.33.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 (373) hide show
  1. package/README.md +16 -8
  2. package/dist/account/delete-account-setting.d.ts +10 -10
  3. package/dist/account/delete-account-setting.js +2 -1
  4. package/dist/account/delete-account-setting.js.map +1 -1
  5. package/dist/account/email-change-setting.d.ts +5 -0
  6. package/dist/account/email-change-setting.js +4 -3
  7. package/dist/account/email-change-setting.js.map +1 -1
  8. package/dist/account/sessions-setting.d.ts +4 -0
  9. package/dist/account/sessions-setting.js +3 -2
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.d.ts +10 -10
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/invitations-panel.d.ts +10 -10
  14. package/dist/admin/invitations-panel.js +4 -3
  15. package/dist/admin/invitations-panel.js.map +1 -1
  16. package/dist/admin/plan-change.d.ts +100 -0
  17. package/dist/admin/plan-change.js +208 -0
  18. package/dist/admin/plan-change.js.map +1 -0
  19. package/dist/admin/reviewer-scope-editor.d.ts +10 -10
  20. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  21. package/dist/admin/role-select.d.ts +7 -7
  22. package/dist/admin/role-select.js.map +1 -1
  23. package/dist/admin/transfer-ownership-dialog.d.ts +10 -10
  24. package/dist/admin/user-roster.d.ts +7 -7
  25. package/dist/admin/user-roster.js +2 -2
  26. package/dist/admin/user-roster.js.map +1 -1
  27. package/dist/auth/auth-errors.d.ts +13 -2
  28. package/dist/auth/auth-errors.js +5 -1
  29. package/dist/auth/auth-errors.js.map +1 -1
  30. package/dist/auth/complete-name-dialog.d.ts +4 -1
  31. package/dist/auth/complete-name-dialog.js +2 -1
  32. package/dist/auth/complete-name-dialog.js.map +1 -1
  33. package/dist/auth/register-form.d.ts +10 -10
  34. package/dist/billing/billing-banner.d.ts +13 -11
  35. package/dist/billing/billing-banner.js.map +1 -1
  36. package/dist/billing/billing-labels.d.ts +10 -10
  37. package/dist/billing/billing-labels.js +10 -1
  38. package/dist/billing/billing-labels.js.map +1 -1
  39. package/dist/billing/billing-lock.d.ts +39 -12
  40. package/dist/billing/billing-lock.js +9 -3
  41. package/dist/billing/billing-lock.js.map +1 -1
  42. package/dist/billing/billing-overview.d.ts +154 -0
  43. package/dist/billing/billing-overview.js +2 -0
  44. package/dist/billing/billing-overview.js.map +1 -0
  45. package/dist/billing/billing-standing.d.ts +10 -10
  46. package/dist/billing/checkout-processing.d.ts +217 -0
  47. package/dist/billing/checkout-processing.js +198 -0
  48. package/dist/billing/checkout-processing.js.map +1 -0
  49. package/dist/billing/checkout-return.d.ts +51 -0
  50. package/dist/billing/checkout-return.js +43 -0
  51. package/dist/billing/checkout-return.js.map +1 -0
  52. package/dist/billing/pay-page.d.ts +210 -0
  53. package/dist/billing/pay-page.js +113 -0
  54. package/dist/billing/pay-page.js.map +1 -0
  55. package/dist/billing/plan-card.d.ts +10 -10
  56. package/dist/billing/plan-limit-notice.d.ts +10 -10
  57. package/dist/billing/plan-limit-toast.d.ts +143 -0
  58. package/dist/billing/plan-limit-toast.js +40 -0
  59. package/dist/billing/plan-limit-toast.js.map +1 -0
  60. package/dist/billing/subscription-actions.d.ts +38 -15
  61. package/dist/billing/subscription-actions.js +9 -2
  62. package/dist/billing/subscription-actions.js.map +1 -1
  63. package/dist/billing/subscription-status-chip.d.ts +9 -9
  64. package/dist/bin/eifi1-pay-page.mjs +166 -0
  65. package/dist/chart.d.ts +10 -10
  66. package/dist/components/account-chips.d.ts +6 -1
  67. package/dist/components/account-settings.d.ts +4 -1
  68. package/dist/components/account-settings.js +2 -1
  69. package/dist/components/account-settings.js.map +1 -1
  70. package/dist/components/amount-input.d.ts +10 -10
  71. package/dist/components/autocomplete.d.ts +2 -1
  72. package/dist/components/autocomplete.js +3 -1
  73. package/dist/components/autocomplete.js.map +1 -1
  74. package/dist/components/button-group.d.ts +10 -10
  75. package/dist/components/button-group.js +1 -1
  76. package/dist/components/button-group.js.map +1 -1
  77. package/dist/components/calculator.d.ts +10 -10
  78. package/dist/components/calculator.js +3 -3
  79. package/dist/components/calculator.js.map +1 -1
  80. package/dist/components/checkbox-group.d.ts +2 -1
  81. package/dist/components/checkbox-group.js.map +1 -1
  82. package/dist/components/checkbox.d.ts +2 -1
  83. package/dist/components/checkbox.js.map +1 -1
  84. package/dist/components/chip.d.ts +2 -1
  85. package/dist/components/chip.js +19 -17
  86. package/dist/components/chip.js.map +1 -1
  87. package/dist/components/choice-card.d.ts +3 -1
  88. package/dist/components/choice-card.js +1 -1
  89. package/dist/components/choice-card.js.map +1 -1
  90. package/dist/components/combobox.d.ts +4 -3
  91. package/dist/components/combobox.js.map +1 -1
  92. package/dist/components/confirm-dialog.d.ts +10 -10
  93. package/dist/components/confirm-dialog.js +6 -3
  94. package/dist/components/confirm-dialog.js.map +1 -1
  95. package/dist/components/copy-button.d.ts +11 -11
  96. package/dist/components/country-select.d.ts +2 -1
  97. package/dist/components/country-select.js.map +1 -1
  98. package/dist/components/danger-confirm.d.ts +10 -10
  99. package/dist/components/danger-confirm.js.map +1 -1
  100. package/dist/components/data-table-cells.d.ts +12 -1
  101. package/dist/components/data-table-filter-popover.d.ts +12 -1
  102. package/dist/components/data-table-filters.d.ts +12 -1
  103. package/dist/components/data-table-labels.d.ts +12 -1
  104. package/dist/components/data-table-pagination.d.ts +12 -1
  105. package/dist/components/data-table.d.ts +12 -1
  106. package/dist/components/data-table.js +3 -3
  107. package/dist/components/data-table.js.map +1 -1
  108. package/dist/components/date-picker.js +6 -4
  109. package/dist/components/date-picker.js.map +1 -1
  110. package/dist/components/entity-combobox.d.ts +3 -2
  111. package/dist/components/entity-combobox.js.map +1 -1
  112. package/dist/components/facing-pair.d.ts +10 -10
  113. package/dist/components/field-parts.d.ts +32 -4
  114. package/dist/components/field-parts.js +54 -19
  115. package/dist/components/field-parts.js.map +1 -1
  116. package/dist/components/file-button.d.ts +10 -10
  117. package/dist/components/file-button.js +3 -2
  118. package/dist/components/file-button.js.map +1 -1
  119. package/dist/components/file-dropzone.d.ts +11 -11
  120. package/dist/components/file-dropzone.js.map +1 -1
  121. package/dist/components/floating-panel.js +5 -5
  122. package/dist/components/floating-panel.js.map +1 -1
  123. package/dist/components/form-actions.d.ts +9 -9
  124. package/dist/components/form-actions.js +3 -3
  125. package/dist/components/form-actions.js.map +1 -1
  126. package/dist/components/iban-input.d.ts +10 -10
  127. package/dist/components/inline-edit-field.d.ts +2 -1
  128. package/dist/components/inline-edit-field.js +6 -6
  129. package/dist/components/inline-edit-field.js.map +1 -1
  130. package/dist/components/language-select.d.ts +10 -10
  131. package/dist/components/legal-page.d.ts +10 -10
  132. package/dist/components/legal.d.ts +10 -10
  133. package/dist/components/list.d.ts +2 -1
  134. package/dist/components/list.js +4 -5
  135. package/dist/components/list.js.map +1 -1
  136. package/dist/components/money-field.d.ts +10 -10
  137. package/dist/components/month-picker.js +54 -44
  138. package/dist/components/month-picker.js.map +1 -1
  139. package/dist/components/multi-entity-combobox.d.ts +3 -2
  140. package/dist/components/multi-entity-combobox.js.map +1 -1
  141. package/dist/components/number-field.d.ts +10 -10
  142. package/dist/components/number-input.d.ts +10 -10
  143. package/dist/components/numpad-sheet.d.ts +10 -10
  144. package/dist/components/numpad-sheet.js +2 -2
  145. package/dist/components/numpad-sheet.js.map +1 -1
  146. package/dist/components/page-header.js +6 -1
  147. package/dist/components/page-header.js.map +1 -1
  148. package/dist/components/phone-input.d.ts +10 -10
  149. package/dist/components/popover.d.ts +1 -1
  150. package/dist/components/popover.js +2 -0
  151. package/dist/components/popover.js.map +1 -1
  152. package/dist/components/row-actions.d.ts +5 -5
  153. package/dist/components/row-actions.js +130 -38
  154. package/dist/components/row-actions.js.map +1 -1
  155. package/dist/components/series-chart.d.ts +10 -10
  156. package/dist/components/settings-fields.d.ts +10 -10
  157. package/dist/components/share-card.d.ts +7 -7
  158. package/dist/components/share-card.js +26 -10
  159. package/dist/components/share-card.js.map +1 -1
  160. package/dist/components/status-dot.d.ts +1 -0
  161. package/dist/components/status-dot.js +2 -2
  162. package/dist/components/status-dot.js.map +1 -1
  163. package/dist/components/swatch-picker.d.ts +2 -1
  164. package/dist/components/swatch-picker.js.map +1 -1
  165. package/dist/components/swipeable-row.d.ts +88 -17
  166. package/dist/components/swipeable-row.js +46 -9
  167. package/dist/components/swipeable-row.js.map +1 -1
  168. package/dist/components/switch.d.ts +2 -1
  169. package/dist/components/switch.js.map +1 -1
  170. package/dist/components/table.d.ts +2 -1
  171. package/dist/components/table.js +6 -0
  172. package/dist/components/table.js.map +1 -1
  173. package/dist/components/text-link.d.ts +9 -9
  174. package/dist/components/time-input.d.ts +10 -10
  175. package/dist/components/toggle-group.d.ts +2 -1
  176. package/dist/components/toggle-group.js +1 -1
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/translation-review-editor.d.ts +4 -0
  179. package/dist/components/translation-review-editor.js +4 -3
  180. package/dist/components/translation-review-editor.js.map +1 -1
  181. package/dist/components/translation-review-parts.d.ts +1 -0
  182. package/dist/components/translation-review.d.ts +1 -0
  183. package/dist/components/translation-review.js +15 -18
  184. package/dist/components/translation-review.js.map +1 -1
  185. package/dist/components/ui.d.ts +9 -9
  186. package/dist/components/ui.js +48 -28
  187. package/dist/components/ui.js.map +1 -1
  188. package/dist/components/use-table-state.d.ts +12 -1
  189. package/dist/components/user-avatar.d.ts +1 -0
  190. package/dist/components/write-lock.d.ts +101 -13
  191. package/dist/components/write-lock.js +37 -13
  192. package/dist/components/write-lock.js.map +1 -1
  193. package/dist/{data-table-labels-kpAAVi6g.d.ts → data-table-labels-BJ5JE_X2.d.ts} +17 -0
  194. package/dist/data-table.d.ts +12 -1
  195. package/dist/{delete-account-setting-FLyJf-kZ.d.ts → delete-account-setting-DiPX4Vue.d.ts} +365 -47
  196. package/dist/feedback/feedback-attachment.d.ts +10 -10
  197. package/dist/feedback/feedback-attachment.js +7 -7
  198. package/dist/feedback/feedback-attachment.js.map +1 -1
  199. package/dist/feedback/feedback-context.d.ts +10 -10
  200. package/dist/feedback/feedback-crash.d.ts +10 -10
  201. package/dist/feedback/feedback-dialog.d.ts +10 -10
  202. package/dist/feedback/feedback-inbox.d.ts +9 -9
  203. package/dist/feedback/feedback-inbox.js +7 -3
  204. package/dist/feedback/feedback-inbox.js.map +1 -1
  205. package/dist/feedback/feedback-labels.d.ts +10 -10
  206. package/dist/feedback/feedback-menu.d.ts +3 -3
  207. package/dist/feedback/feedback-record.d.ts +10 -10
  208. package/dist/feedback/feedback-row-detail.d.ts +10 -10
  209. package/dist/feedback/feedback-row-detail.js.map +1 -1
  210. package/dist/feedback/feedback-status-undo.d.ts +10 -10
  211. package/dist/feedback/feedback-submit.d.ts +10 -10
  212. package/dist/feedback/feedback-swipe.d.ts +10 -10
  213. package/dist/feedback/feedback-swipe.js +3 -6
  214. package/dist/feedback/feedback-swipe.js.map +1 -1
  215. package/dist/feedback/feedback-table.d.ts +10 -10
  216. package/dist/feedback/feedback-table.js.map +1 -1
  217. package/dist/feedback/feedback-thread.d.ts +10 -10
  218. package/dist/feedback.d.ts +10 -10
  219. package/dist/hooks/use-file-drop.d.ts +10 -10
  220. package/dist/i18n/defaults.d.ts +10 -10
  221. package/dist/i18n/defaults.js +2 -0
  222. package/dist/i18n/defaults.js.map +1 -1
  223. package/dist/i18n/german.d.ts +10 -10
  224. package/dist/i18n/german.js +34 -1
  225. package/dist/i18n/german.js.map +1 -1
  226. package/dist/i18n/kit-labels.d.ts +4 -4
  227. package/dist/i18n/kit-labels.js.map +1 -1
  228. package/dist/i18n/languages.d.ts +10 -10
  229. package/dist/i18n/locales/de-CH.d.ts +10 -10
  230. package/dist/i18n/locales/en.d.ts +10 -10
  231. package/dist/i18n/locales/es.d.ts +10 -10
  232. package/dist/i18n/locales/es.js +34 -1
  233. package/dist/i18n/locales/es.js.map +1 -1
  234. package/dist/i18n/locales/fr.d.ts +10 -10
  235. package/dist/i18n/locales/fr.js +35 -1
  236. package/dist/i18n/locales/fr.js.map +1 -1
  237. package/dist/i18n/locales/hu.d.ts +10 -10
  238. package/dist/i18n/locales/hu.js +36 -1
  239. package/dist/i18n/locales/hu.js.map +1 -1
  240. package/dist/i18n/locales/it.d.ts +10 -10
  241. package/dist/i18n/locales/it.js +34 -1
  242. package/dist/i18n/locales/it.js.map +1 -1
  243. package/dist/i18n/locales/zh.d.ts +10 -10
  244. package/dist/i18n/locales/zh.js +34 -1
  245. package/dist/i18n/locales/zh.js.map +1 -1
  246. package/dist/i18n/review.d.ts +10 -10
  247. package/dist/i18n/review.js +8 -1
  248. package/dist/i18n/review.js.map +1 -1
  249. package/dist/index.d.ts +10 -5
  250. package/dist/index.js +40 -2
  251. package/dist/index.js.map +1 -1
  252. package/dist/landing/landing-actions.d.ts +10 -10
  253. package/dist/landing/landing-sections.d.ts +10 -10
  254. package/dist/landing/public-header.d.ts +10 -10
  255. package/dist/landing/routing.d.ts +10 -10
  256. package/dist/pay/.well-known/apple-developer-merchantid-domain-association +1 -0
  257. package/dist/pay/index.html +30 -0
  258. package/dist/pay/pay.css +126 -0
  259. package/dist/pay/pay.js +198 -0
  260. package/dist/rhf/fields.d.ts +10 -10
  261. package/dist/rhf/form.d.ts +10 -10
  262. package/dist/rhf.d.ts +10 -10
  263. package/dist/settings/settings-section.d.ts +10 -10
  264. package/dist/shell/app-shell.d.ts +9 -9
  265. package/dist/shell/app-shell.js +2 -1
  266. package/dist/shell/app-shell.js.map +1 -1
  267. package/dist/shell/top-bar-brand.d.ts +10 -10
  268. package/dist/shell/topbar-action-menu.d.ts +1 -0
  269. package/dist/shell.d.ts +10 -10
  270. package/dist/testing/css-variables.d.ts +74 -0
  271. package/dist/testing/css-variables.generated.d.ts +7 -0
  272. package/dist/testing/css-variables.generated.js +8 -0
  273. package/dist/testing/css-variables.generated.js.map +1 -0
  274. package/dist/testing/css-variables.js +93 -0
  275. package/dist/testing/css-variables.js.map +1 -0
  276. package/dist/testing/index.d.ts +1 -0
  277. package/dist/testing/index.js +12 -0
  278. package/dist/testing/index.js.map +1 -0
  279. package/dist/theme/contrast-tokens.d.ts +44 -1
  280. package/dist/theme/contrast-tokens.js +21 -0
  281. package/dist/theme/contrast-tokens.js.map +1 -1
  282. package/dist/theme/contrast.d.ts +2 -2
  283. package/dist/theme/palette-derive.js +60 -11
  284. package/dist/theme/palette-derive.js.map +1 -1
  285. package/dist/theme/palette-presets.js +45 -43
  286. package/dist/theme/palette-presets.js.map +1 -1
  287. package/dist/wizard/stepper-nav.d.ts +11 -11
  288. package/dist/wizard/stepper-nav.js.map +1 -1
  289. package/dist/wizard.d.ts +10 -10
  290. package/package.json +10 -2
  291. package/src/account/delete-account-setting.tsx +6 -1
  292. package/src/account/email-change-setting.tsx +9 -3
  293. package/src/account/sessions-setting.tsx +7 -2
  294. package/src/admin/admin-action-confirm.tsx +2 -1
  295. package/src/admin/invitations-panel.tsx +9 -3
  296. package/src/admin/plan-change.tsx +459 -0
  297. package/src/admin/reviewer-scope-editor.tsx +2 -1
  298. package/src/admin/role-select.tsx +2 -1
  299. package/src/admin/user-roster.tsx +5 -3
  300. package/src/auth/auth-errors.ts +25 -2
  301. package/src/auth/complete-name-dialog.tsx +6 -2
  302. package/src/billing/billing-banner.tsx +3 -1
  303. package/src/billing/billing-labels.ts +45 -0
  304. package/src/billing/billing-lock.ts +36 -3
  305. package/src/billing/billing-overview.ts +58 -0
  306. package/src/billing/checkout-processing.ts +391 -0
  307. package/src/billing/checkout-return.ts +90 -0
  308. package/src/billing/pay-page.ts +242 -0
  309. package/src/billing/plan-limit-toast.tsx +91 -0
  310. package/src/billing/subscription-actions.tsx +41 -8
  311. package/src/components/account-settings.tsx +6 -2
  312. package/src/components/autocomplete.tsx +5 -2
  313. package/src/components/button-group.tsx +4 -1
  314. package/src/components/calculator.tsx +10 -6
  315. package/src/components/checkbox-group.tsx +2 -2
  316. package/src/components/checkbox.tsx +2 -1
  317. package/src/components/chip.tsx +37 -32
  318. package/src/components/choice-card.tsx +4 -2
  319. package/src/components/combobox.tsx +4 -4
  320. package/src/components/confirm-dialog.tsx +13 -6
  321. package/src/components/country-select.tsx +2 -2
  322. package/src/components/danger-confirm.tsx +2 -2
  323. package/src/components/data-table.tsx +20 -9
  324. package/src/components/date-picker.tsx +10 -7
  325. package/src/components/entity-combobox.tsx +3 -3
  326. package/src/components/field-parts.tsx +61 -14
  327. package/src/components/file-button.tsx +5 -4
  328. package/src/components/file-dropzone.tsx +2 -2
  329. package/src/components/floating-panel.tsx +7 -5
  330. package/src/components/form-actions.tsx +12 -5
  331. package/src/components/inline-edit-field.tsx +13 -13
  332. package/src/components/list.tsx +10 -9
  333. package/src/components/month-picker.tsx +8 -2
  334. package/src/components/multi-entity-combobox.tsx +3 -3
  335. package/src/components/numpad-sheet.tsx +8 -4
  336. package/src/components/page-header.tsx +15 -1
  337. package/src/components/popover.tsx +4 -0
  338. package/src/components/row-actions.tsx +201 -50
  339. package/src/components/share-card.tsx +67 -13
  340. package/src/components/status-dot.tsx +7 -7
  341. package/src/components/swatch-picker.tsx +2 -2
  342. package/src/components/swipeable-row.tsx +138 -27
  343. package/src/components/switch.tsx +2 -1
  344. package/src/components/table.tsx +8 -1
  345. package/src/components/toggle-group.tsx +5 -3
  346. package/src/components/translation-review-editor.tsx +8 -3
  347. package/src/components/translation-review.tsx +18 -22
  348. package/src/components/ui.tsx +98 -44
  349. package/src/components/write-lock.tsx +150 -24
  350. package/src/feedback/feedback-attachment.tsx +13 -9
  351. package/src/feedback/feedback-inbox.tsx +10 -6
  352. package/src/feedback/feedback-row-detail.tsx +3 -2
  353. package/src/feedback/feedback-swipe.tsx +6 -11
  354. package/src/feedback/feedback-table.tsx +2 -1
  355. package/src/i18n/defaults.ts +2 -0
  356. package/src/i18n/german.ts +38 -0
  357. package/src/i18n/kit-labels.tsx +4 -0
  358. package/src/i18n/locales/es.ts +34 -0
  359. package/src/i18n/locales/fr.ts +39 -0
  360. package/src/i18n/locales/hu.ts +37 -0
  361. package/src/i18n/locales/it.ts +35 -0
  362. package/src/i18n/locales/zh.ts +33 -0
  363. package/src/i18n/review.ts +7 -0
  364. package/src/index.ts +48 -4
  365. package/src/shell/app-shell.tsx +11 -6
  366. package/src/testing/css-variables.generated.ts +112 -0
  367. package/src/testing/css-variables.ts +191 -0
  368. package/src/testing/index.ts +13 -0
  369. package/src/theme/contrast-tokens.ts +53 -2
  370. package/src/theme/palette-derive.ts +87 -10
  371. package/src/theme/palette-presets.ts +45 -43
  372. package/src/wizard/stepper-nav.tsx +2 -1
  373. package/tokens.css +306 -46
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ const KIT_NAMES = "--bg-active --bg-hover --bg-inverse --bg-page --bg-surface --bg-surface-2 --border --border-strong --brand --brand-bg --brand-bg-hover --brand-contrast --brand-hover --brand-muted --breakpoint-3xl --chart-1 --chart-2 --chart-3 --chart-4 --chart-5 --chart-6 --chart-7 --chart-8 --chart-9 --danger --danger-bg --danger-border --danger-border-strong --danger-contrast --danger-hover --flyout-from --focus-ring-width --hue-blue --hue-blue-bg --hue-blue-border --hue-blue-contrast --hue-indigo --hue-indigo-bg --hue-indigo-border --hue-indigo-contrast --hue-orange --hue-orange-bg --hue-orange-border --hue-orange-contrast --hue-purple --hue-purple-bg --hue-purple-border --hue-purple-contrast --hue-teal --hue-teal-bg --hue-teal-border --hue-teal-contrast --info --info-bg --info-border --info-contrast --media-ink --media-scrim --media-scrim-hover --money-expense --money-expense-contrast --money-income --money-income-contrast --money-net --money-net-contrast --money-neutral --money-neutral-contrast --neutral --neutral-contrast --status-edited --status-error --status-pending --status-synced --success --success-bg --success-border --success-contrast --text-caption --text-inverse --text-micro --text-muted --text-placeholder --text-primary --text-secondary --toast-action-bg --toast-action-fg --toast-danger-bg --toast-danger-border --toast-info-bg --toast-info-border --toast-success-bg --toast-success-border --toast-warning-bg --toast-warning-border --warning --warning-bg --warning-border --warning-border-strong --warning-contrast --z-toast";
3
+ const TAILWIND_THEME_NAMES = "--animate-bounce --animate-ping --animate-pulse --animate-spin --aspect-video --blur-2xl --blur-3xl --blur-lg --blur-md --blur-sm --blur-xl --blur-xs --breakpoint-2xl --breakpoint-lg --breakpoint-md --breakpoint-sm --breakpoint-xl --color-amber-100 --color-amber-200 --color-amber-300 --color-amber-400 --color-amber-50 --color-amber-500 --color-amber-600 --color-amber-700 --color-amber-800 --color-amber-900 --color-amber-950 --color-black --color-blue-100 --color-blue-200 --color-blue-300 --color-blue-400 --color-blue-50 --color-blue-500 --color-blue-600 --color-blue-700 --color-blue-800 --color-blue-900 --color-blue-950 --color-cyan-100 --color-cyan-200 --color-cyan-300 --color-cyan-400 --color-cyan-50 --color-cyan-500 --color-cyan-600 --color-cyan-700 --color-cyan-800 --color-cyan-900 --color-cyan-950 --color-emerald-100 --color-emerald-200 --color-emerald-300 --color-emerald-400 --color-emerald-50 --color-emerald-500 --color-emerald-600 --color-emerald-700 --color-emerald-800 --color-emerald-900 --color-emerald-950 --color-fuchsia-100 --color-fuchsia-200 --color-fuchsia-300 --color-fuchsia-400 --color-fuchsia-50 --color-fuchsia-500 --color-fuchsia-600 --color-fuchsia-700 --color-fuchsia-800 --color-fuchsia-900 --color-fuchsia-950 --color-gray-100 --color-gray-200 --color-gray-300 --color-gray-400 --color-gray-50 --color-gray-500 --color-gray-600 --color-gray-700 --color-gray-800 --color-gray-900 --color-gray-950 --color-green-100 --color-green-200 --color-green-300 --color-green-400 --color-green-50 --color-green-500 --color-green-600 --color-green-700 --color-green-800 --color-green-900 --color-green-950 --color-indigo-100 --color-indigo-200 --color-indigo-300 --color-indigo-400 --color-indigo-50 --color-indigo-500 --color-indigo-600 --color-indigo-700 --color-indigo-800 --color-indigo-900 --color-indigo-950 --color-lime-100 --color-lime-200 --color-lime-300 --color-lime-400 --color-lime-50 --color-lime-500 --color-lime-600 --color-lime-700 --color-lime-800 --color-lime-900 --color-lime-950 --color-mauve-100 --color-mauve-200 --color-mauve-300 --color-mauve-400 --color-mauve-50 --color-mauve-500 --color-mauve-600 --color-mauve-700 --color-mauve-800 --color-mauve-900 --color-mauve-950 --color-mist-100 --color-mist-200 --color-mist-300 --color-mist-400 --color-mist-50 --color-mist-500 --color-mist-600 --color-mist-700 --color-mist-800 --color-mist-900 --color-mist-950 --color-neutral-100 --color-neutral-200 --color-neutral-300 --color-neutral-400 --color-neutral-50 --color-neutral-500 --color-neutral-600 --color-neutral-700 --color-neutral-800 --color-neutral-900 --color-neutral-950 --color-olive-100 --color-olive-200 --color-olive-300 --color-olive-400 --color-olive-50 --color-olive-500 --color-olive-600 --color-olive-700 --color-olive-800 --color-olive-900 --color-olive-950 --color-orange-100 --color-orange-200 --color-orange-300 --color-orange-400 --color-orange-50 --color-orange-500 --color-orange-600 --color-orange-700 --color-orange-800 --color-orange-900 --color-orange-950 --color-pink-100 --color-pink-200 --color-pink-300 --color-pink-400 --color-pink-50 --color-pink-500 --color-pink-600 --color-pink-700 --color-pink-800 --color-pink-900 --color-pink-950 --color-purple-100 --color-purple-200 --color-purple-300 --color-purple-400 --color-purple-50 --color-purple-500 --color-purple-600 --color-purple-700 --color-purple-800 --color-purple-900 --color-purple-950 --color-red-100 --color-red-200 --color-red-300 --color-red-400 --color-red-50 --color-red-500 --color-red-600 --color-red-700 --color-red-800 --color-red-900 --color-red-950 --color-rose-100 --color-rose-200 --color-rose-300 --color-rose-400 --color-rose-50 --color-rose-500 --color-rose-600 --color-rose-700 --color-rose-800 --color-rose-900 --color-rose-950 --color-sky-100 --color-sky-200 --color-sky-300 --color-sky-400 --color-sky-50 --color-sky-500 --color-sky-600 --color-sky-700 --color-sky-800 --color-sky-900 --color-sky-950 --color-slate-100 --color-slate-200 --color-slate-300 --color-slate-400 --color-slate-50 --color-slate-500 --color-slate-600 --color-slate-700 --color-slate-800 --color-slate-900 --color-slate-950 --color-stone-100 --color-stone-200 --color-stone-300 --color-stone-400 --color-stone-50 --color-stone-500 --color-stone-600 --color-stone-700 --color-stone-800 --color-stone-900 --color-stone-950 --color-taupe-100 --color-taupe-200 --color-taupe-300 --color-taupe-400 --color-taupe-50 --color-taupe-500 --color-taupe-600 --color-taupe-700 --color-taupe-800 --color-taupe-900 --color-taupe-950 --color-teal-100 --color-teal-200 --color-teal-300 --color-teal-400 --color-teal-50 --color-teal-500 --color-teal-600 --color-teal-700 --color-teal-800 --color-teal-900 --color-teal-950 --color-violet-100 --color-violet-200 --color-violet-300 --color-violet-400 --color-violet-50 --color-violet-500 --color-violet-600 --color-violet-700 --color-violet-800 --color-violet-900 --color-violet-950 --color-white --color-yellow-100 --color-yellow-200 --color-yellow-300 --color-yellow-400 --color-yellow-50 --color-yellow-500 --color-yellow-600 --color-yellow-700 --color-yellow-800 --color-yellow-900 --color-yellow-950 --color-zinc-100 --color-zinc-200 --color-zinc-300 --color-zinc-400 --color-zinc-50 --color-zinc-500 --color-zinc-600 --color-zinc-700 --color-zinc-800 --color-zinc-900 --color-zinc-950 --container-2xl --container-2xs --container-3xl --container-3xs --container-4xl --container-5xl --container-6xl --container-7xl --container-lg --container-md --container-sm --container-xl --container-xs --default-font-family --default-font-feature-settings --default-font-variation-settings --default-mono-font-family --default-mono-font-feature-settings --default-mono-font-variation-settings --default-transition-duration --default-transition-timing-function --drop-shadow-2xl --drop-shadow-lg --drop-shadow-md --drop-shadow-sm --drop-shadow-xl --drop-shadow-xs --ease-in --ease-in-out --ease-out --font-mono --font-sans --font-serif --font-weight-black --font-weight-bold --font-weight-extrabold --font-weight-extralight --font-weight-light --font-weight-medium --font-weight-normal --font-weight-semibold --font-weight-thin --inset-shadow-2xs --inset-shadow-sm --inset-shadow-xs --leading-loose --leading-normal --leading-relaxed --leading-snug --leading-tight --perspective-distant --perspective-dramatic --perspective-midrange --perspective-near --perspective-normal --radius-2xl --radius-3xl --radius-4xl --radius-lg --radius-md --radius-sm --radius-xl --radius-xs --shadow-2xl --shadow-2xs --shadow-lg --shadow-md --shadow-sm --shadow-xl --shadow-xs --spacing --text-2xl --text-2xl--line-height --text-3xl --text-3xl--line-height --text-4xl --text-4xl--line-height --text-5xl --text-5xl--line-height --text-6xl --text-6xl--line-height --text-7xl --text-7xl--line-height --text-8xl --text-8xl--line-height --text-9xl --text-9xl--line-height --text-base --text-base--line-height --text-lg --text-lg--line-height --text-shadow-2xs --text-shadow-lg --text-shadow-md --text-shadow-sm --text-shadow-xs --text-sm --text-sm--line-height --text-xl --text-xl--line-height --text-xs --text-xs--line-height --tracking-normal --tracking-tight --tracking-tighter --tracking-wide --tracking-wider --tracking-widest";
4
+ export {
5
+ KIT_NAMES,
6
+ TAILWIND_THEME_NAMES
7
+ };
8
+ //# sourceMappingURL=css-variables.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/css-variables.generated.ts"],"sourcesContent":["// GENERATED by scripts/gen-kit-css-variables.mjs — do not edit by hand. Re-run it after\n// changing tokens.css or upgrading Tailwind; src/__tests__/token-vars-declared.test.ts\n// fails while this is stale.\n\n/** Every custom property tokens.css declares, `@theme inline` names and `--app-nav-h`\n * left out (100 names). */\nexport const KIT_NAMES =\n \"--bg-active --bg-hover --bg-inverse --bg-page --bg-surface --bg-surface-2 --border \" +\n \"--border-strong --brand --brand-bg --brand-bg-hover --brand-contrast --brand-hover --brand-muted \" +\n \"--breakpoint-3xl --chart-1 --chart-2 --chart-3 --chart-4 --chart-5 --chart-6 --chart-7 --chart-8 \" +\n \"--chart-9 --danger --danger-bg --danger-border --danger-border-strong --danger-contrast \" +\n \"--danger-hover --flyout-from --focus-ring-width --hue-blue --hue-blue-bg --hue-blue-border \" +\n \"--hue-blue-contrast --hue-indigo --hue-indigo-bg --hue-indigo-border --hue-indigo-contrast \" +\n \"--hue-orange --hue-orange-bg --hue-orange-border --hue-orange-contrast --hue-purple \" +\n \"--hue-purple-bg --hue-purple-border --hue-purple-contrast --hue-teal --hue-teal-bg \" +\n \"--hue-teal-border --hue-teal-contrast --info --info-bg --info-border --info-contrast --media-ink \" +\n \"--media-scrim --media-scrim-hover --money-expense --money-expense-contrast --money-income \" +\n \"--money-income-contrast --money-net --money-net-contrast --money-neutral \" +\n \"--money-neutral-contrast --neutral --neutral-contrast --status-edited --status-error \" +\n \"--status-pending --status-synced --success --success-bg --success-border --success-contrast \" +\n \"--text-caption --text-inverse --text-micro --text-muted --text-placeholder --text-primary \" +\n \"--text-secondary --toast-action-bg --toast-action-fg --toast-danger-bg --toast-danger-border \" +\n \"--toast-info-bg --toast-info-border --toast-success-bg --toast-success-border --toast-warning-bg \" +\n \"--toast-warning-border --warning --warning-bg --warning-border --warning-border-strong \" +\n \"--warning-contrast --z-toast\";\n\n/** The theme variables of Tailwind 4.3.3's `theme.css` (413 names). */\nexport const TAILWIND_THEME_NAMES =\n \"--animate-bounce --animate-ping --animate-pulse --animate-spin --aspect-video --blur-2xl \" +\n \"--blur-3xl --blur-lg --blur-md --blur-sm --blur-xl --blur-xs --breakpoint-2xl --breakpoint-lg \" +\n \"--breakpoint-md --breakpoint-sm --breakpoint-xl --color-amber-100 --color-amber-200 \" +\n \"--color-amber-300 --color-amber-400 --color-amber-50 --color-amber-500 --color-amber-600 \" +\n \"--color-amber-700 --color-amber-800 --color-amber-900 --color-amber-950 --color-black \" +\n \"--color-blue-100 --color-blue-200 --color-blue-300 --color-blue-400 --color-blue-50 \" +\n \"--color-blue-500 --color-blue-600 --color-blue-700 --color-blue-800 --color-blue-900 \" +\n \"--color-blue-950 --color-cyan-100 --color-cyan-200 --color-cyan-300 --color-cyan-400 \" +\n \"--color-cyan-50 --color-cyan-500 --color-cyan-600 --color-cyan-700 --color-cyan-800 \" +\n \"--color-cyan-900 --color-cyan-950 --color-emerald-100 --color-emerald-200 --color-emerald-300 \" +\n \"--color-emerald-400 --color-emerald-50 --color-emerald-500 --color-emerald-600 \" +\n \"--color-emerald-700 --color-emerald-800 --color-emerald-900 --color-emerald-950 \" +\n \"--color-fuchsia-100 --color-fuchsia-200 --color-fuchsia-300 --color-fuchsia-400 \" +\n \"--color-fuchsia-50 --color-fuchsia-500 --color-fuchsia-600 --color-fuchsia-700 \" +\n \"--color-fuchsia-800 --color-fuchsia-900 --color-fuchsia-950 --color-gray-100 --color-gray-200 \" +\n \"--color-gray-300 --color-gray-400 --color-gray-50 --color-gray-500 --color-gray-600 \" +\n \"--color-gray-700 --color-gray-800 --color-gray-900 --color-gray-950 --color-green-100 \" +\n \"--color-green-200 --color-green-300 --color-green-400 --color-green-50 --color-green-500 \" +\n \"--color-green-600 --color-green-700 --color-green-800 --color-green-900 --color-green-950 \" +\n \"--color-indigo-100 --color-indigo-200 --color-indigo-300 --color-indigo-400 --color-indigo-50 \" +\n \"--color-indigo-500 --color-indigo-600 --color-indigo-700 --color-indigo-800 --color-indigo-900 \" +\n \"--color-indigo-950 --color-lime-100 --color-lime-200 --color-lime-300 --color-lime-400 \" +\n \"--color-lime-50 --color-lime-500 --color-lime-600 --color-lime-700 --color-lime-800 \" +\n \"--color-lime-900 --color-lime-950 --color-mauve-100 --color-mauve-200 --color-mauve-300 \" +\n \"--color-mauve-400 --color-mauve-50 --color-mauve-500 --color-mauve-600 --color-mauve-700 \" +\n \"--color-mauve-800 --color-mauve-900 --color-mauve-950 --color-mist-100 --color-mist-200 \" +\n \"--color-mist-300 --color-mist-400 --color-mist-50 --color-mist-500 --color-mist-600 \" +\n \"--color-mist-700 --color-mist-800 --color-mist-900 --color-mist-950 --color-neutral-100 \" +\n \"--color-neutral-200 --color-neutral-300 --color-neutral-400 --color-neutral-50 \" +\n \"--color-neutral-500 --color-neutral-600 --color-neutral-700 --color-neutral-800 \" +\n \"--color-neutral-900 --color-neutral-950 --color-olive-100 --color-olive-200 --color-olive-300 \" +\n \"--color-olive-400 --color-olive-50 --color-olive-500 --color-olive-600 --color-olive-700 \" +\n \"--color-olive-800 --color-olive-900 --color-olive-950 --color-orange-100 --color-orange-200 \" +\n \"--color-orange-300 --color-orange-400 --color-orange-50 --color-orange-500 --color-orange-600 \" +\n \"--color-orange-700 --color-orange-800 --color-orange-900 --color-orange-950 --color-pink-100 \" +\n \"--color-pink-200 --color-pink-300 --color-pink-400 --color-pink-50 --color-pink-500 \" +\n \"--color-pink-600 --color-pink-700 --color-pink-800 --color-pink-900 --color-pink-950 \" +\n \"--color-purple-100 --color-purple-200 --color-purple-300 --color-purple-400 --color-purple-50 \" +\n \"--color-purple-500 --color-purple-600 --color-purple-700 --color-purple-800 --color-purple-900 \" +\n \"--color-purple-950 --color-red-100 --color-red-200 --color-red-300 --color-red-400 \" +\n \"--color-red-50 --color-red-500 --color-red-600 --color-red-700 --color-red-800 --color-red-900 \" +\n \"--color-red-950 --color-rose-100 --color-rose-200 --color-rose-300 --color-rose-400 \" +\n \"--color-rose-50 --color-rose-500 --color-rose-600 --color-rose-700 --color-rose-800 \" +\n \"--color-rose-900 --color-rose-950 --color-sky-100 --color-sky-200 --color-sky-300 \" +\n \"--color-sky-400 --color-sky-50 --color-sky-500 --color-sky-600 --color-sky-700 --color-sky-800 \" +\n \"--color-sky-900 --color-sky-950 --color-slate-100 --color-slate-200 --color-slate-300 \" +\n \"--color-slate-400 --color-slate-50 --color-slate-500 --color-slate-600 --color-slate-700 \" +\n \"--color-slate-800 --color-slate-900 --color-slate-950 --color-stone-100 --color-stone-200 \" +\n \"--color-stone-300 --color-stone-400 --color-stone-50 --color-stone-500 --color-stone-600 \" +\n \"--color-stone-700 --color-stone-800 --color-stone-900 --color-stone-950 --color-taupe-100 \" +\n \"--color-taupe-200 --color-taupe-300 --color-taupe-400 --color-taupe-50 --color-taupe-500 \" +\n \"--color-taupe-600 --color-taupe-700 --color-taupe-800 --color-taupe-900 --color-taupe-950 \" +\n \"--color-teal-100 --color-teal-200 --color-teal-300 --color-teal-400 --color-teal-50 \" +\n \"--color-teal-500 --color-teal-600 --color-teal-700 --color-teal-800 --color-teal-900 \" +\n \"--color-teal-950 --color-violet-100 --color-violet-200 --color-violet-300 --color-violet-400 \" +\n \"--color-violet-50 --color-violet-500 --color-violet-600 --color-violet-700 --color-violet-800 \" +\n \"--color-violet-900 --color-violet-950 --color-white --color-yellow-100 --color-yellow-200 \" +\n \"--color-yellow-300 --color-yellow-400 --color-yellow-50 --color-yellow-500 --color-yellow-600 \" +\n \"--color-yellow-700 --color-yellow-800 --color-yellow-900 --color-yellow-950 --color-zinc-100 \" +\n \"--color-zinc-200 --color-zinc-300 --color-zinc-400 --color-zinc-50 --color-zinc-500 \" +\n \"--color-zinc-600 --color-zinc-700 --color-zinc-800 --color-zinc-900 --color-zinc-950 \" +\n \"--container-2xl --container-2xs --container-3xl --container-3xs --container-4xl --container-5xl \" +\n \"--container-6xl --container-7xl --container-lg --container-md --container-sm --container-xl \" +\n \"--container-xs --default-font-family --default-font-feature-settings \" +\n \"--default-font-variation-settings --default-mono-font-family \" +\n \"--default-mono-font-feature-settings --default-mono-font-variation-settings \" +\n \"--default-transition-duration --default-transition-timing-function --drop-shadow-2xl \" +\n \"--drop-shadow-lg --drop-shadow-md --drop-shadow-sm --drop-shadow-xl --drop-shadow-xs --ease-in \" +\n \"--ease-in-out --ease-out --font-mono --font-sans --font-serif --font-weight-black \" +\n \"--font-weight-bold --font-weight-extrabold --font-weight-extralight --font-weight-light \" +\n \"--font-weight-medium --font-weight-normal --font-weight-semibold --font-weight-thin \" +\n \"--inset-shadow-2xs --inset-shadow-sm --inset-shadow-xs --leading-loose --leading-normal \" +\n \"--leading-relaxed --leading-snug --leading-tight --perspective-distant --perspective-dramatic \" +\n \"--perspective-midrange --perspective-near --perspective-normal --radius-2xl --radius-3xl \" +\n \"--radius-4xl --radius-lg --radius-md --radius-sm --radius-xl --radius-xs --shadow-2xl \" +\n \"--shadow-2xs --shadow-lg --shadow-md --shadow-sm --shadow-xl --shadow-xs --spacing --text-2xl \" +\n \"--text-2xl--line-height --text-3xl --text-3xl--line-height --text-4xl --text-4xl--line-height \" +\n \"--text-5xl --text-5xl--line-height --text-6xl --text-6xl--line-height --text-7xl \" +\n \"--text-7xl--line-height --text-8xl --text-8xl--line-height --text-9xl --text-9xl--line-height \" +\n \"--text-base --text-base--line-height --text-lg --text-lg--line-height --text-shadow-2xs \" +\n \"--text-shadow-lg --text-shadow-md --text-shadow-sm --text-shadow-xs --text-sm \" +\n \"--text-sm--line-height --text-xl --text-xl--line-height --text-xs --text-xs--line-height \" +\n \"--tracking-normal --tracking-tight --tracking-tighter --tracking-wide --tracking-wider \" +\n \"--tracking-widest\";\n"],"mappings":";AAMO,MAAM,YACX;AAoBK,MAAM,uBACX;","names":[]}
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import { KIT_NAMES, TAILWIND_THEME_NAMES } from "./css-variables.generated.js";
3
+ const KIT_CSS_VARIABLES = new Set(KIT_NAMES.split(" "));
4
+ const TAILWIND_THEME = new Set(TAILWIND_THEME_NAMES.split(" "));
5
+ const PALETTE_VARIABLE = /^--color-(?:[a-z]+-\d{2,3}|black|white)$/;
6
+ const APP_NAV_H = "--app-nav-h";
7
+ function stripComments(text, isCss) {
8
+ const blank = (m) => m.replace(/[^\n]/g, " ");
9
+ const noBlocks = text.replace(/\/\*[\s\S]*?\*\//g, blank);
10
+ return isCss ? noBlocks : noBlocks.replace(/(^|[^:])(\/\/[^\n]*)/g, (_, pre, c) => pre + blank(c));
11
+ }
12
+ function withoutInlineTheme(css) {
13
+ let out = css;
14
+ for (const m of css.matchAll(/@theme\b[^{;]*\binline\b[^{;]*\{/g)) {
15
+ let depth = 0;
16
+ let end = m.index + m[0].length - 1;
17
+ for (; end < css.length; end++) {
18
+ if (css[end] === "{") depth++;
19
+ else if (css[end] === "}" && --depth === 0) break;
20
+ }
21
+ out = out.slice(0, m.index) + css.slice(m.index, end + 1).replace(/[^\n]/g, " ") + out.slice(end + 1);
22
+ }
23
+ return out;
24
+ }
25
+ function declaredCustomProperties(css) {
26
+ const code = withoutInlineTheme(stripComments(css, true));
27
+ return new Set([...code.matchAll(/(--[a-zA-Z0-9_-]+)\s*:/g)].map((m) => m[1]));
28
+ }
29
+ function runtimeWrites(code) {
30
+ return [
31
+ ...code.matchAll(/setProperty\(\s*["'`](--[a-zA-Z0-9_-]+)["'`]/g),
32
+ ...code.matchAll(/["'](--[a-zA-Z0-9_-]+)["']\s*:/g),
33
+ ...code.matchAll(/\[(--[a-zA-Z0-9_-]+):/g)
34
+ ].map((m) => m[1]);
35
+ }
36
+ function chartKeys(code) {
37
+ return new Set([...code.matchAll(/(?:^|[\s{,])["']?([A-Za-z_][\w-]*)["']?\s*:\s*\{/g)].map((m) => m[1]));
38
+ }
39
+ const isCssPath = (path) => /\.css(?:\?|$)/.test(path);
40
+ function undeclaredCssVariables(sources, options = {}) {
41
+ const { runtime = [], checkFallbacks = false, refusePalette = false } = options;
42
+ const entries = Object.entries(sources);
43
+ if (entries.length < 10) {
44
+ throw new Error(
45
+ `undeclaredCssVariables: only ${entries.length} source file(s) \u2014 the glob or the file list found (almost) nothing, and a check over nothing passes. Pass the app's .ts/.tsx/.css source.`
46
+ );
47
+ }
48
+ const emptyCss = entries.filter(([path, text]) => isCssPath(path) && text.trim() === "").map(([path]) => path);
49
+ if (emptyCss.length) {
50
+ throw new Error(
51
+ `undeclaredCssVariables: ${emptyCss.join(", ")} read as empty. Vitest stubs stylesheets with "" (\`?raw\` too) unless the file is listed in \`test.css.include\`.`
52
+ );
53
+ }
54
+ const code = entries.map(([path, text]) => [path, stripComments(text, isCssPath(path))]);
55
+ const declared = /* @__PURE__ */ new Set();
56
+ for (const [path, text] of code) {
57
+ if (isCssPath(path)) for (const name of declaredCustomProperties(text)) declared.add(name);
58
+ for (const name of runtimeWrites(text)) declared.add(name);
59
+ }
60
+ const listed = (name) => runtime.some((r) => typeof r === "string" ? r === name : r.test(name));
61
+ const out = [];
62
+ for (const [path, text] of code) {
63
+ let keys = null;
64
+ for (const m of text.matchAll(/var\(\s*(--[a-zA-Z0-9_-]+)\s*([,)])/g)) {
65
+ const [, name, next] = m;
66
+ const fallback = next === ",";
67
+ if (fallback && !checkFallbacks) continue;
68
+ if (KIT_CSS_VARIABLES.has(name) || declared.has(name) || listed(name)) continue;
69
+ if (fallback && name === APP_NAV_H) continue;
70
+ if (TAILWIND_THEME.has(name)) {
71
+ if (refusePalette && PALETTE_VARIABLE.test(name)) {
72
+ out.push(`${path}:${lineOf(text, m.index)} ${name} (a Tailwind palette colour; refusePalette is on)`);
73
+ }
74
+ continue;
75
+ }
76
+ const chart = /^--color-([A-Za-z_][\w-]*)$/.exec(name);
77
+ if (chart && (keys ??= chartKeys(text)).has(chart[1])) continue;
78
+ out.push(`${path}:${lineOf(text, m.index)} ${name}`);
79
+ }
80
+ }
81
+ return out;
82
+ }
83
+ function lineOf(text, index) {
84
+ let line = 1;
85
+ for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) line++;
86
+ return line;
87
+ }
88
+ export {
89
+ KIT_CSS_VARIABLES,
90
+ declaredCustomProperties,
91
+ undeclaredCssVariables
92
+ };
93
+ //# sourceMappingURL=css-variables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/css-variables.ts"],"sourcesContent":["import { KIT_NAMES, TAILWIND_THEME_NAMES } from \"./css-variables.generated\";\n\n/**\n * The guard against an undeclared CSS custom property (0.33,\n * docs/colour-roles-harmonization.md §8, §12.5).\n *\n * A `var(--name)` with no fallback, where nothing declares `--name`, makes the whole\n * declaration invalid at computed-value time: `fill` inherits, `color` inherits, a\n * background goes transparent. Kurvenschmiede's control diagram painted\n * `fill-[var(--surface)]` — a token that never existed (the kit's is `--bg-surface`) —\n * and drew black boxes on a public page. The class compiled, the build passed, the type\n * checker had no opinion, and nothing in any suite noticed. This is the check that\n * notices, run from an app's own test over the source texts the app collects.\n *\n * Browser-safe on purpose, like the rest of the kit: it takes TEXTS, not paths, so an app\n * reads its files however its setup allows (`import.meta.glob` with `?raw`, or `node:fs`)\n * and the kit carries no `node:fs`.\n */\n\n/** Every custom property the installed kit declares or publishes to apps.\n *\n * Generated from `tokens.css` when the kit is built (scripts/gen-kit-css-variables.mjs),\n * so it is always the installed version's, and an app never searches `node_modules`.\n * It leaves out two kinds of name on purpose:\n * - a name declared only in an `@theme inline` block (`--text-color-muted`,\n * `--background-color-surface`): Tailwind emits no variable for it, so\n * `var(--text-color-muted)` would pass a guard that counted it and paint nothing;\n * - AppShell's measured `--app-nav-h`, which exists only while an AppShell is mounted —\n * on a landing, legal, sign-in or pay page it is undefined. Read it with a fallback,\n * `var(--app-nav-h,0px)`, as every kit read does.\n * Everything `applyTokenSet` and More contrast write on `<html>` is in, through the\n * stylesheet's `:root` / `.dark` mirror. Component internals (`--slider-fill`, `--fp-*`,\n * `--icon-button-tone`, `--label-strip-edge`) are not: an app that reads one depends on\n * the kit's internals, and the guard says so. */\nexport const KIT_CSS_VARIABLES: ReadonlySet<string> = new Set(KIT_NAMES.split(\" \"));\n\n/** Tailwind's own theme variables (`--color-rose-500`, `--spacing`, `--text-sm` …), from\n * the `tailwindcss/theme.css` the kit builds with. Tailwind emits one when a scanned file\n * names it, so a `var()` of it resolves. */\nconst TAILWIND_THEME: ReadonlySet<string> = new Set(TAILWIND_THEME_NAMES.split(\" \"));\n\n/** A Tailwind PALETTE colour: a hue with a shade, black or white. `refusePalette` reports\n * these although they resolve — an app that has no palette lint asks this test to be\n * its refusal. */\nconst PALETTE_VARIABLE = /^--color-(?:[a-z]+-\\d{2,3}|black|white)$/;\n\n/** Read only where it is set: the guard accepts it in a fallback use, never bare. */\nconst APP_NAV_H = \"--app-nav-h\";\n\nexport interface UndeclaredCssVariablesOptions {\n /** Variables a library writes at runtime, as names or patterns:\n * `[\"--radix-collapsible-content-height\"]`. An app's own writes are found in its\n * source and need no listing. */\n runtime?: readonly (string | RegExp)[];\n /** Also check `var(--x, …)`. Off by default: a fallback says what happens when the\n * name is missing. `--app-nav-h` counts as declared only here. */\n checkFallbacks?: boolean;\n /** Report Tailwind palette variables (`--color-red-500`, `--color-white`) although\n * they resolve. Default false. */\n refusePalette?: boolean;\n}\n\n/** Comments are not CSS. `/* *\\/` in both; `//` in TS only (not after a `:`, so a URL in a\n * string survives). Replaced by spaces of the same shape, so line numbers hold. */\nfunction stripComments(text: string, isCss: boolean): string {\n const blank = (m: string) => m.replace(/[^\\n]/g, \" \");\n const noBlocks = text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, blank);\n return isCss ? noBlocks : noBlocks.replace(/(^|[^:])(\\/\\/[^\\n]*)/g, (_, pre: string, c: string) => pre + blank(c));\n}\n\n/** The text with every `@theme … inline … { … }` block blanked out, braces and all. */\nfunction withoutInlineTheme(css: string): string {\n let out = css;\n for (const m of css.matchAll(/@theme\\b[^{;]*\\binline\\b[^{;]*\\{/g)) {\n let depth = 0;\n let end = m.index + m[0].length - 1;\n for (; end < css.length; end++) {\n if (css[end] === \"{\") depth++;\n else if (css[end] === \"}\" && --depth === 0) break;\n }\n out = out.slice(0, m.index) + css.slice(m.index, end + 1).replace(/[^\\n]/g, \" \") + out.slice(end + 1);\n }\n return out;\n}\n\n/**\n * Every custom property a stylesheet declares (`--name:`), in any block — except a name\n * declared only inside `@theme inline`, which Tailwind never emits as a variable. Comments\n * are skipped. The same rule builds {@link KIT_CSS_VARIABLES} from `tokens.css`.\n */\nexport function declaredCustomProperties(css: string): Set<string> {\n const code = withoutInlineTheme(stripComments(css, true));\n return new Set([...code.matchAll(/(--[a-zA-Z0-9_-]+)\\s*:/g)].map((m) => m[1]));\n}\n\n/** What an app's source writes at runtime: `setProperty(\"--name\"`, a React style key\n * `\"--name\":`, a Tailwind arbitrary property `[--name:…]`. */\nfunction runtimeWrites(code: string): string[] {\n return [\n ...code.matchAll(/setProperty\\(\\s*[\"'`](--[a-zA-Z0-9_-]+)[\"'`]/g),\n ...code.matchAll(/[\"'](--[a-zA-Z0-9_-]+)[\"']\\s*:/g),\n ...code.matchAll(/\\[(--[a-zA-Z0-9_-]+):/g),\n ].map((m) => m[1]);\n}\n\n/** A chart series' colour (`var(--color-total)`) is declared in a file with a `total: {`\n * config entry: the kit's ChartContainer writes `--color-<key>` for each key. */\nfunction chartKeys(code: string): Set<string> {\n return new Set([...code.matchAll(/(?:^|[\\s{,])[\"']?([A-Za-z_][\\w-]*)[\"']?\\s*:\\s*\\{/g)].map((m) => m[1]));\n}\n\nconst isCssPath = (path: string) => /\\.css(?:\\?|$)/.test(path);\n\n/**\n * Every `var(--name)` without a fallback that nothing declares, as `\"path:line --name\"`.\n *\n * `sources` maps a path to its text: the app's `.ts`, `.tsx` and `.css` source, tests\n * left out. A name counts as declared when it is:\n * 1. the kit's ({@link KIT_CSS_VARIABLES});\n * 2. declared in any `.css` entry of `sources` (outside `@theme inline`);\n * 3. written by the source at runtime (`setProperty`, a style key, `[--name:…]`);\n * 4. one of Tailwind's theme variables — unless `refusePalette` and it is a palette colour;\n * 5. a chart series' `--color-<key>`, in a file with a `<key>: {` entry;\n * 6. listed in `options.runtime`.\n *\n * Not counted: comments; a use with a fallback, unless `checkFallbacks`; a name built at\n * runtime (`` `var(--chart-${n})` `` — a name must be followed by `)` or `,`).\n *\n * It REFUSES A VACUOUS RUN: it throws when `sources` holds fewer than ten files, or when\n * a `.css` entry is empty. Vitest stubs every stylesheet with an empty string by default,\n * `?raw` included, so an app that reads its CSS that way without opting it out of the stub\n * (`test.css.include`) would otherwise lose every declaration and every use in it, and\n * this would pass or fail for the wrong reason.\n */\nexport function undeclaredCssVariables(\n sources: Record<string, string>,\n options: UndeclaredCssVariablesOptions = {},\n): string[] {\n const { runtime = [], checkFallbacks = false, refusePalette = false } = options;\n const entries = Object.entries(sources);\n if (entries.length < 10) {\n throw new Error(\n `undeclaredCssVariables: only ${entries.length} source file(s) — the glob or the file list found ` +\n `(almost) nothing, and a check over nothing passes. Pass the app's .ts/.tsx/.css source.`,\n );\n }\n const emptyCss = entries.filter(([path, text]) => isCssPath(path) && text.trim() === \"\").map(([path]) => path);\n if (emptyCss.length) {\n throw new Error(\n `undeclaredCssVariables: ${emptyCss.join(\", \")} read as empty. Vitest stubs stylesheets with \"\" ` +\n `(\\`?raw\\` too) unless the file is listed in \\`test.css.include\\`.`,\n );\n }\n\n const code = entries.map(([path, text]) => [path, stripComments(text, isCssPath(path))] as const);\n const declared = new Set<string>();\n for (const [path, text] of code) {\n if (isCssPath(path)) for (const name of declaredCustomProperties(text)) declared.add(name);\n for (const name of runtimeWrites(text)) declared.add(name);\n }\n const listed = (name: string) =>\n runtime.some((r) => (typeof r === \"string\" ? r === name : r.test(name)));\n\n const out: string[] = [];\n for (const [path, text] of code) {\n let keys: Set<string> | null = null;\n for (const m of text.matchAll(/var\\(\\s*(--[a-zA-Z0-9_-]+)\\s*([,)])/g)) {\n const [, name, next] = m;\n const fallback = next === \",\";\n if (fallback && !checkFallbacks) continue;\n if (KIT_CSS_VARIABLES.has(name) || declared.has(name) || listed(name)) continue;\n if (fallback && name === APP_NAV_H) continue;\n if (TAILWIND_THEME.has(name)) {\n if (refusePalette && PALETTE_VARIABLE.test(name)) {\n out.push(`${path}:${lineOf(text, m.index)} ${name} (a Tailwind palette colour; refusePalette is on)`);\n }\n continue;\n }\n const chart = /^--color-([A-Za-z_][\\w-]*)$/.exec(name);\n if (chart && (keys ??= chartKeys(text)).has(chart[1])) continue;\n out.push(`${path}:${lineOf(text, m.index)} ${name}`);\n }\n }\n return out;\n}\n\nfunction lineOf(text: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index; i++) if (text.charCodeAt(i) === 10) line++;\n return line;\n}\n"],"mappings":";AAAA,SAAS,WAAW,4BAA4B;AAkCzC,MAAM,oBAAyC,IAAI,IAAI,UAAU,MAAM,GAAG,CAAC;AAKlF,MAAM,iBAAsC,IAAI,IAAI,qBAAqB,MAAM,GAAG,CAAC;AAKnF,MAAM,mBAAmB;AAGzB,MAAM,YAAY;AAiBlB,SAAS,cAAc,MAAc,OAAwB;AAC3D,QAAM,QAAQ,CAAC,MAAc,EAAE,QAAQ,UAAU,GAAG;AACpD,QAAM,WAAW,KAAK,QAAQ,qBAAqB,KAAK;AACxD,SAAO,QAAQ,WAAW,SAAS,QAAQ,yBAAyB,CAAC,GAAG,KAAa,MAAc,MAAM,MAAM,CAAC,CAAC;AACnH;AAGA,SAAS,mBAAmB,KAAqB;AAC/C,MAAI,MAAM;AACV,aAAW,KAAK,IAAI,SAAS,mCAAmC,GAAG;AACjE,QAAI,QAAQ;AACZ,QAAI,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS;AAClC,WAAO,MAAM,IAAI,QAAQ,OAAO;AAC9B,UAAI,IAAI,GAAG,MAAM,IAAK;AAAA,eACb,IAAI,GAAG,MAAM,OAAO,EAAE,UAAU,EAAG;AAAA,IAC9C;AACA,UAAM,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI,IAAI,MAAM,EAAE,OAAO,MAAM,CAAC,EAAE,QAAQ,UAAU,GAAG,IAAI,IAAI,MAAM,MAAM,CAAC;AAAA,EACtG;AACA,SAAO;AACT;AAOO,SAAS,yBAAyB,KAA0B;AACjE,QAAM,OAAO,mBAAmB,cAAc,KAAK,IAAI,CAAC;AACxD,SAAO,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,yBAAyB,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAC/E;AAIA,SAAS,cAAc,MAAwB;AAC7C,SAAO;AAAA,IACL,GAAG,KAAK,SAAS,+CAA+C;AAAA,IAChE,GAAG,KAAK,SAAS,iCAAiC;AAAA,IAClD,GAAG,KAAK,SAAS,wBAAwB;AAAA,EAC3C,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AACnB;AAIA,SAAS,UAAU,MAA2B;AAC5C,SAAO,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,mDAAmD,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACzG;AAEA,MAAM,YAAY,CAAC,SAAiB,gBAAgB,KAAK,IAAI;AAuBtD,SAAS,uBACd,SACA,UAAyC,CAAC,GAChC;AACV,QAAM,EAAE,UAAU,CAAC,GAAG,iBAAiB,OAAO,gBAAgB,MAAM,IAAI;AACxE,QAAM,UAAU,OAAO,QAAQ,OAAO;AACtC,MAAI,QAAQ,SAAS,IAAI;AACvB,UAAM,IAAI;AAAA,MACR,gCAAgC,QAAQ,MAAM;AAAA,IAEhD;AAAA,EACF;AACA,QAAM,WAAW,QAAQ,OAAO,CAAC,CAAC,MAAM,IAAI,MAAM,UAAU,IAAI,KAAK,KAAK,KAAK,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AAC7G,MAAI,SAAS,QAAQ;AACnB,UAAM,IAAI;AAAA,MACR,2BAA2B,SAAS,KAAK,IAAI,CAAC;AAAA,IAEhD;AAAA,EACF;AAEA,QAAM,OAAO,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,cAAc,MAAM,UAAU,IAAI,CAAC,CAAC,CAAU;AAChG,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,CAAC,MAAM,IAAI,KAAK,MAAM;AAC/B,QAAI,UAAU,IAAI,EAAG,YAAW,QAAQ,yBAAyB,IAAI,EAAG,UAAS,IAAI,IAAI;AACzF,eAAW,QAAQ,cAAc,IAAI,EAAG,UAAS,IAAI,IAAI;AAAA,EAC3D;AACA,QAAM,SAAS,CAAC,SACd,QAAQ,KAAK,CAAC,MAAO,OAAO,MAAM,WAAW,MAAM,OAAO,EAAE,KAAK,IAAI,CAAE;AAEzE,QAAM,MAAgB,CAAC;AACvB,aAAW,CAAC,MAAM,IAAI,KAAK,MAAM;AAC/B,QAAI,OAA2B;AAC/B,eAAW,KAAK,KAAK,SAAS,sCAAsC,GAAG;AACrE,YAAM,CAAC,EAAE,MAAM,IAAI,IAAI;AACvB,YAAM,WAAW,SAAS;AAC1B,UAAI,YAAY,CAAC,eAAgB;AACjC,UAAI,kBAAkB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,OAAO,IAAI,EAAG;AACvE,UAAI,YAAY,SAAS,UAAW;AACpC,UAAI,eAAe,IAAI,IAAI,GAAG;AAC5B,YAAI,iBAAiB,iBAAiB,KAAK,IAAI,GAAG;AAChD,cAAI,KAAK,GAAG,IAAI,IAAI,OAAO,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,mDAAmD;AAAA,QACtG;AACA;AAAA,MACF;AACA,YAAM,QAAQ,8BAA8B,KAAK,IAAI;AACrD,UAAI,UAAU,SAAS,UAAU,IAAI,GAAG,IAAI,MAAM,CAAC,CAAC,EAAG;AACvD,UAAI,KAAK,GAAG,IAAI,IAAI,OAAO,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;AAAA,IACrD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,OAAO,MAAc,OAAuB;AACnD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,OAAO,IAAK,KAAI,KAAK,WAAW,CAAC,MAAM,GAAI;AAC/D,SAAO;AACT;","names":[]}
@@ -0,0 +1 @@
1
+ export { KIT_CSS_VARIABLES, UndeclaredCssVariablesOptions, declaredCustomProperties, undeclaredCssVariables } from './css-variables.js';
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import {
3
+ KIT_CSS_VARIABLES,
4
+ declaredCustomProperties,
5
+ undeclaredCssVariables
6
+ } from "./css-variables.js";
7
+ export {
8
+ KIT_CSS_VARIABLES,
9
+ declaredCustomProperties,
10
+ undeclaredCssVariables
11
+ };
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/index.ts"],"sourcesContent":["// `@eifi1/ui-kit/testing` — helpers an app runs from its OWN test suite (0.33).\n//\n// Not part of the main barrel: nothing here renders, and an app's production bundle has\n// no business carrying it. Browser-safe like the rest of the kit (no `node:fs`): each\n// helper takes texts, so an app collects them with `import.meta.glob` or `node:fs`, as\n// its setup allows. The first content is the guard against undeclared CSS custom\n// properties (docs/colour-roles-harmonization.md §8).\nexport {\n KIT_CSS_VARIABLES,\n declaredCustomProperties,\n undeclaredCssVariables,\n type UndeclaredCssVariablesOptions,\n} from \"./css-variables\";\n"],"mappings":";AAOA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;","names":[]}
@@ -30,6 +30,49 @@ interface ContrastStep {
30
30
  textPlaceholder: string;
31
31
  border: string;
32
32
  }
33
+ /** `color-mix(in oklab, a p, b)` as a hex — the same formula tokens.css's derived roles
34
+ * use, so a stepped value sits where the stylesheet would have put it. A colour that is
35
+ * not hex (a consumer's own token set) comes back unmixed. The deriver mixes with it
36
+ * too (`--bg-hover`, `--brand-muted`, `--brand-bg`, `--brand-bg-hover`). */
37
+ declare function mix(a: string, b: string, p: number): string;
38
+ /**
39
+ * `--bg-hover` in tokens.css: this share of `--text-primary` mixed into `--bg-surface`.
40
+ *
41
+ * 5.5 %, down from 7 % in 0.33 (docs/colour-roles-harmonization.md §12.10): a hovered row
42
+ * on a card is the darkest fill (in dark, the lightest) that text sits on, and at 7 % the
43
+ * light status colours, the money colours and Ink/Moss/Plum's muted text fell to
44
+ * 4.25–4.49:1 on it. The status literals are fixed, the same in every preset, so the
45
+ * hover moved instead: text wins over the hover's step. At 5.5 % the step from the card
46
+ * is still 1.09–1.13:1, above the 1.08:1 the kit's hover rule asks (§12.2). The deriver
47
+ * solves the money colours against this fill, and tokens-css-audit.test.ts measures
48
+ * every text role on it in every shipped preset, so the two cannot part.
49
+ */
50
+ declare const HOVER_INK = 0.055;
51
+ /** The card's hover fill (`--bg-hover`) for `t`, as tokens.css mixes it. */
52
+ declare function hoverFill(t: Pick<TokenSet, "textPrimary" | "bgSurface">): string;
53
+ /**
54
+ * `--brand-muted` in tokens.css: this share of `--brand` mixed into `--text-primary`.
55
+ *
56
+ * 62 %, down from 70 % in 0.33 (docs/colour-roles-harmonization.md §12.11): the brand as
57
+ * text sits on its soft chip HOVERED too (`--brand-bg-hover` — ButtonGroupLink's current
58
+ * page, a selected day), and at 70 % it read 4.08–4.42:1 there in Default and Moss light
59
+ * and in Ink, Moss, Plum and Contrast dark. The brand could not move instead: the dark
60
+ * brands would have flipped their white label to the dark ink. Nor could the chip's hover
61
+ * lighten: by the time the text cleared 4.5:1 the hover sat 1.00:1 from the chip it
62
+ * hovers. So the text moves toward the ink, as `--bg-hover` moved for its text.
63
+ */
64
+ declare const BRAND_MUTED_SHARE = 0.62;
65
+ /** `--brand-bg` and `--brand-bg-hover` in tokens.css: these shares of `--brand` mixed
66
+ * into `--bg-surface`. */
67
+ declare const BRAND_BG_SHARE = 0.14;
68
+ declare const BRAND_BG_HOVER_SHARE = 0.24;
69
+ /** `--brand-muted`, `--brand-bg` and `--brand-bg-hover` for `t`, as tokens.css mixes them.
70
+ * tokens-css-audit.test.ts holds the three shares equal to the stylesheet's. */
71
+ declare function brandSoft(t: Pick<TokenSet, "brand" | "textPrimary" | "bgSurface">): {
72
+ brandMuted: string;
73
+ brandBg: string;
74
+ brandBgHover: string;
75
+ };
33
76
  /** The four stepped values for `t`. Pure. */
34
77
  declare function contrastStep(t: TokenSet): ContrastStep;
35
78
  /** Remember the set `applyTokenSet` wrote, so a contrast change can re-apply it. */
@@ -38,4 +81,4 @@ declare function rememberTokenSet(el: HTMLElement, t: TokenSet): void;
38
81
  * without the palette layer, whose base tokens come from tokens.css. */
39
82
  declare function rememberedTokenSet(el: HTMLElement): TokenSet | undefined;
40
83
 
41
- export { CONTRAST_STEPPED_VARS, type ContrastStep, contrastStep, rememberTokenSet, rememberedTokenSet };
84
+ export { BRAND_BG_HOVER_SHARE, BRAND_BG_SHARE, BRAND_MUTED_SHARE, CONTRAST_STEPPED_VARS, type ContrastStep, HOVER_INK, brandSoft, contrastStep, hoverFill, mix, rememberTokenSet, rememberedTokenSet };
@@ -19,6 +19,20 @@ function mix(a, b, p) {
19
19
  const h = c < 1e-6 ? 0 : (Math.atan2(y, x) * 180 / Math.PI + 360) % 360;
20
20
  return toHex(oklchToRgb({ l, c, h }));
21
21
  }
22
+ const HOVER_INK = 0.055;
23
+ function hoverFill(t) {
24
+ return mix(t.textPrimary, t.bgSurface, HOVER_INK);
25
+ }
26
+ const BRAND_MUTED_SHARE = 0.62;
27
+ const BRAND_BG_SHARE = 0.14;
28
+ const BRAND_BG_HOVER_SHARE = 0.24;
29
+ function brandSoft(t) {
30
+ return {
31
+ brandMuted: mix(t.brand, t.textPrimary, BRAND_MUTED_SHARE),
32
+ brandBg: mix(t.brand, t.bgSurface, BRAND_BG_SHARE),
33
+ brandBgHover: mix(t.brand, t.bgSurface, BRAND_BG_HOVER_SHARE)
34
+ };
35
+ }
22
36
  const BORDER_TARGET = 3;
23
37
  const BORDER_STRONG_SHARE = 0.45;
24
38
  function worstContrast(color, t) {
@@ -51,8 +65,15 @@ function rememberedTokenSet(el) {
51
65
  return lastWritten.get(el);
52
66
  }
53
67
  export {
68
+ BRAND_BG_HOVER_SHARE,
69
+ BRAND_BG_SHARE,
70
+ BRAND_MUTED_SHARE,
54
71
  CONTRAST_STEPPED_VARS,
72
+ HOVER_INK,
73
+ brandSoft,
55
74
  contrastStep,
75
+ hoverFill,
76
+ mix,
56
77
  rememberTokenSet,
57
78
  rememberedTokenSet
58
79
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/contrast-tokens.ts"],"sourcesContent":["import { contrast, oklchToRgb, parseHex, rgbToOklch, toHex } from \"./color\";\nimport type { TokenSet } from \"./palette-presets\";\n\n/**\n * The \"More contrast\" step on a token set (docs/text-size-harmonization.md §5, §10.5).\n * INTERNAL — `applyTokenSet` and the contrast layer share it; it is not re-exported.\n *\n * The step happens HERE, in JS, because the tokens it changes are written inline on\n * `<html>` by `applyTokenSet`: an inline value beats any `[data-contrast=more]` rule and\n * any `@media (prefers-contrast: more)` block, so a stylesheet cannot do it (§10.5, the\n * keksdose review). `--text-placeholder` is a CSS-derived token, and it is written inline\n * here too rather than redefined per attribute in tokens.css.\n *\n * What moves (§5):\n * - `--text-muted` becomes the set's `--text-secondary`;\n * - `--text-secondary` moves halfway to `--text-primary`;\n * - `--text-placeholder` becomes the set's `--text-muted` (4.6–5.5:1 on every shipped\n * preset, where the CSS formula gives 2.4–2.8:1) — the order primary > secondary >\n * muted > placeholder survives the step;\n * - `--border` moves toward `--text-primary` until it clears 3:1 against the worst of the\n * three surfaces (WCAG 1.4.11, a field's boundary), and never less far than\n * `--border-strong`'s 45 % — so every border is at least as strong as a \"strong\" one was.\n * `--border-strong` stays derived in tokens.css and grows with it.\n */\n\n/** The custom properties a contrast step writes. */\nexport const CONTRAST_STEPPED_VARS = [\"--text-secondary\", \"--text-muted\", \"--text-placeholder\", \"--border\"] as const;\n\nexport interface ContrastStep {\n textSecondary: string;\n textMuted: string;\n textPlaceholder: string;\n border: string;\n}\n\ninterface Lab {\n l: number;\n a: number;\n b: number;\n}\n\nfunction toLab(hex: string): Lab | null {\n const rgb = parseHex(hex);\n if (!rgb) return null;\n const { l, c, h } = rgbToOklch(rgb);\n const rad = (h * Math.PI) / 180;\n return { l, a: c * Math.cos(rad), b: c * Math.sin(rad) };\n}\n\n/** `color-mix(in oklab, a p, b)` as a hex — the same formula tokens.css's derived roles\n * use, so a stepped value sits where the stylesheet would have put it. A colour that is\n * not hex (a consumer's own token set) comes back unmixed. */\nfunction mix(a: string, b: string, p: number): string {\n const A = toLab(a);\n const B = toLab(b);\n if (!A || !B) return a;\n const l = A.l * p + B.l * (1 - p);\n const x = A.a * p + B.a * (1 - p);\n const y = A.b * p + B.b * (1 - p);\n const c = Math.hypot(x, y);\n const h = c < 1e-6 ? 0 : ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360;\n return toHex(oklchToRgb({ l, c, h }));\n}\n\n/** WCAG 1.4.11 for a component's boundary. */\nconst BORDER_TARGET = 3;\n/** `--border-strong` in tokens.css: 55 % border, 45 % text. */\nconst BORDER_STRONG_SHARE = 0.45;\n\nfunction worstContrast(color: string, t: TokenSet): number {\n return Math.min(contrast(color, t.bgPage), contrast(color, t.bgSurface), contrast(color, t.bgSurface2));\n}\n\nfunction steppedBorder(t: TokenSet): string {\n // The share of `--text-primary` in the border: at least --border-strong's, more until\n // 3:1. Monotonic in the share (the mix walks from the border to the text, away from\n // every surface), so a bisection finds the least that clears it.\n let lo = BORDER_STRONG_SHARE;\n if (worstContrast(mix(t.border, t.textPrimary, 1 - lo), t) >= BORDER_TARGET) return mix(t.border, t.textPrimary, 1 - lo);\n let hi = 1;\n for (let i = 0; i < 16; i++) {\n const mid = (lo + hi) / 2;\n if (worstContrast(mix(t.border, t.textPrimary, 1 - mid), t) >= BORDER_TARGET) hi = mid;\n else lo = mid;\n }\n return mix(t.border, t.textPrimary, 1 - hi);\n}\n\n/** The four stepped values for `t`. Pure. */\nexport function contrastStep(t: TokenSet): ContrastStep {\n return {\n textSecondary: mix(t.textSecondary, t.textPrimary, 0.5),\n textMuted: t.textSecondary,\n textPlaceholder: t.textMuted,\n border: steppedBorder(t),\n };\n}\n\n/* ── The token set last written to an element ───────────────────────────── */\n\nconst lastWritten = new WeakMap<HTMLElement, TokenSet>();\n\n/** Remember the set `applyTokenSet` wrote, so a contrast change can re-apply it. */\nexport function rememberTokenSet(el: HTMLElement, t: TokenSet): void {\n lastWritten.set(el, t);\n}\n\n/** The set `applyTokenSet` last wrote to `el`, or undefined when nothing did — an app\n * without the palette layer, whose base tokens come from tokens.css. */\nexport function rememberedTokenSet(el: HTMLElement): TokenSet | undefined {\n return lastWritten.get(el);\n}\n"],"mappings":";AAAA,SAAS,UAAU,YAAY,UAAU,YAAY,aAAa;AA0B3D,MAAM,wBAAwB,CAAC,oBAAoB,gBAAgB,sBAAsB,UAAU;AAe1G,SAAS,MAAM,KAAyB;AACtC,QAAM,MAAM,SAAS,GAAG;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,WAAW,GAAG;AAClC,QAAM,MAAO,IAAI,KAAK,KAAM;AAC5B,SAAO,EAAE,GAAG,GAAG,IAAI,KAAK,IAAI,GAAG,GAAG,GAAG,IAAI,KAAK,IAAI,GAAG,EAAE;AACzD;AAKA,SAAS,IAAI,GAAW,GAAW,GAAmB;AACpD,QAAM,IAAI,MAAM,CAAC;AACjB,QAAM,IAAI,MAAM,CAAC;AACjB,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAM,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,IAAI;AAC/B,QAAM,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,IAAI;AAC/B,QAAM,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,IAAI;AAC/B,QAAM,IAAI,KAAK,MAAM,GAAG,CAAC;AACzB,QAAM,IAAI,IAAI,OAAO,KAAM,KAAK,MAAM,GAAG,CAAC,IAAI,MAAO,KAAK,KAAK,OAAO;AACtE,SAAO,MAAM,WAAW,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;AACtC;AAGA,MAAM,gBAAgB;AAEtB,MAAM,sBAAsB;AAE5B,SAAS,cAAc,OAAe,GAAqB;AACzD,SAAO,KAAK,IAAI,SAAS,OAAO,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,SAAS,GAAG,SAAS,OAAO,EAAE,UAAU,CAAC;AACxG;AAEA,SAAS,cAAc,GAAqB;AAI1C,MAAI,KAAK;AACT,MAAI,cAAc,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,EAAE,GAAG,CAAC,KAAK,cAAe,QAAO,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,EAAE;AACvH,MAAI,KAAK;AACT,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,OAAO,KAAK,MAAM;AACxB,QAAI,cAAc,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,GAAG,GAAG,CAAC,KAAK,cAAe,MAAK;AAAA,QAC9E,MAAK;AAAA,EACZ;AACA,SAAO,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,EAAE;AAC5C;AAGO,SAAS,aAAa,GAA2B;AACtD,SAAO;AAAA,IACL,eAAe,IAAI,EAAE,eAAe,EAAE,aAAa,GAAG;AAAA,IACtD,WAAW,EAAE;AAAA,IACb,iBAAiB,EAAE;AAAA,IACnB,QAAQ,cAAc,CAAC;AAAA,EACzB;AACF;AAIA,MAAM,cAAc,oBAAI,QAA+B;AAGhD,SAAS,iBAAiB,IAAiB,GAAmB;AACnE,cAAY,IAAI,IAAI,CAAC;AACvB;AAIO,SAAS,mBAAmB,IAAuC;AACxE,SAAO,YAAY,IAAI,EAAE;AAC3B;","names":[]}
1
+ {"version":3,"sources":["../../src/theme/contrast-tokens.ts"],"sourcesContent":["import { contrast, oklchToRgb, parseHex, rgbToOklch, toHex } from \"./color\";\nimport type { TokenSet } from \"./palette-presets\";\n\n/**\n * The \"More contrast\" step on a token set (docs/text-size-harmonization.md §5, §10.5).\n * INTERNAL — `applyTokenSet` and the contrast layer share it; it is not re-exported.\n *\n * The step happens HERE, in JS, because the tokens it changes are written inline on\n * `<html>` by `applyTokenSet`: an inline value beats any `[data-contrast=more]` rule and\n * any `@media (prefers-contrast: more)` block, so a stylesheet cannot do it (§10.5, the\n * keksdose review). `--text-placeholder` is a CSS-derived token, and it is written inline\n * here too rather than redefined per attribute in tokens.css.\n *\n * What moves (§5):\n * - `--text-muted` becomes the set's `--text-secondary`;\n * - `--text-secondary` moves halfway to `--text-primary`;\n * - `--text-placeholder` becomes the set's `--text-muted` (4.6–5.5:1 on every shipped\n * preset, where the CSS formula gives 2.4–2.8:1) — the order primary > secondary >\n * muted > placeholder survives the step;\n * - `--border` moves toward `--text-primary` until it clears 3:1 against the worst of the\n * three surfaces (WCAG 1.4.11, a field's boundary), and never less far than\n * `--border-strong`'s 45 % — so every border is at least as strong as a \"strong\" one was.\n * `--border-strong` stays derived in tokens.css and grows with it.\n */\n\n/** The custom properties a contrast step writes. */\nexport const CONTRAST_STEPPED_VARS = [\"--text-secondary\", \"--text-muted\", \"--text-placeholder\", \"--border\"] as const;\n\nexport interface ContrastStep {\n textSecondary: string;\n textMuted: string;\n textPlaceholder: string;\n border: string;\n}\n\ninterface Lab {\n l: number;\n a: number;\n b: number;\n}\n\nfunction toLab(hex: string): Lab | null {\n const rgb = parseHex(hex);\n if (!rgb) return null;\n const { l, c, h } = rgbToOklch(rgb);\n const rad = (h * Math.PI) / 180;\n return { l, a: c * Math.cos(rad), b: c * Math.sin(rad) };\n}\n\n/** `color-mix(in oklab, a p, b)` as a hex — the same formula tokens.css's derived roles\n * use, so a stepped value sits where the stylesheet would have put it. A colour that is\n * not hex (a consumer's own token set) comes back unmixed. The deriver mixes with it\n * too (`--bg-hover`, `--brand-muted`, `--brand-bg`, `--brand-bg-hover`). */\nexport function mix(a: string, b: string, p: number): string {\n const A = toLab(a);\n const B = toLab(b);\n if (!A || !B) return a;\n const l = A.l * p + B.l * (1 - p);\n const x = A.a * p + B.a * (1 - p);\n const y = A.b * p + B.b * (1 - p);\n const c = Math.hypot(x, y);\n const h = c < 1e-6 ? 0 : ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360;\n return toHex(oklchToRgb({ l, c, h }));\n}\n\n/**\n * `--bg-hover` in tokens.css: this share of `--text-primary` mixed into `--bg-surface`.\n *\n * 5.5 %, down from 7 % in 0.33 (docs/colour-roles-harmonization.md §12.10): a hovered row\n * on a card is the darkest fill (in dark, the lightest) that text sits on, and at 7 % the\n * light status colours, the money colours and Ink/Moss/Plum's muted text fell to\n * 4.25–4.49:1 on it. The status literals are fixed, the same in every preset, so the\n * hover moved instead: text wins over the hover's step. At 5.5 % the step from the card\n * is still 1.09–1.13:1, above the 1.08:1 the kit's hover rule asks (§12.2). The deriver\n * solves the money colours against this fill, and tokens-css-audit.test.ts measures\n * every text role on it in every shipped preset, so the two cannot part.\n */\nexport const HOVER_INK = 0.055;\n\n/** The card's hover fill (`--bg-hover`) for `t`, as tokens.css mixes it. */\nexport function hoverFill(t: Pick<TokenSet, \"textPrimary\" | \"bgSurface\">): string {\n return mix(t.textPrimary, t.bgSurface, HOVER_INK);\n}\n\n/**\n * `--brand-muted` in tokens.css: this share of `--brand` mixed into `--text-primary`.\n *\n * 62 %, down from 70 % in 0.33 (docs/colour-roles-harmonization.md §12.11): the brand as\n * text sits on its soft chip HOVERED too (`--brand-bg-hover` — ButtonGroupLink's current\n * page, a selected day), and at 70 % it read 4.08–4.42:1 there in Default and Moss light\n * and in Ink, Moss, Plum and Contrast dark. The brand could not move instead: the dark\n * brands would have flipped their white label to the dark ink. Nor could the chip's hover\n * lighten: by the time the text cleared 4.5:1 the hover sat 1.00:1 from the chip it\n * hovers. So the text moves toward the ink, as `--bg-hover` moved for its text.\n */\nexport const BRAND_MUTED_SHARE = 0.62;\n/** `--brand-bg` and `--brand-bg-hover` in tokens.css: these shares of `--brand` mixed\n * into `--bg-surface`. */\nexport const BRAND_BG_SHARE = 0.14;\nexport const BRAND_BG_HOVER_SHARE = 0.24;\n\n/** `--brand-muted`, `--brand-bg` and `--brand-bg-hover` for `t`, as tokens.css mixes them.\n * tokens-css-audit.test.ts holds the three shares equal to the stylesheet's. */\nexport function brandSoft(t: Pick<TokenSet, \"brand\" | \"textPrimary\" | \"bgSurface\">): {\n brandMuted: string;\n brandBg: string;\n brandBgHover: string;\n} {\n return {\n brandMuted: mix(t.brand, t.textPrimary, BRAND_MUTED_SHARE),\n brandBg: mix(t.brand, t.bgSurface, BRAND_BG_SHARE),\n brandBgHover: mix(t.brand, t.bgSurface, BRAND_BG_HOVER_SHARE),\n };\n}\n\n/** WCAG 1.4.11 for a component's boundary. */\nconst BORDER_TARGET = 3;\n/** `--border-strong` in tokens.css: 55 % border, 45 % text. */\nconst BORDER_STRONG_SHARE = 0.45;\n\nfunction worstContrast(color: string, t: TokenSet): number {\n return Math.min(contrast(color, t.bgPage), contrast(color, t.bgSurface), contrast(color, t.bgSurface2));\n}\n\nfunction steppedBorder(t: TokenSet): string {\n // The share of `--text-primary` in the border: at least --border-strong's, more until\n // 3:1. Monotonic in the share (the mix walks from the border to the text, away from\n // every surface), so a bisection finds the least that clears it.\n let lo = BORDER_STRONG_SHARE;\n if (worstContrast(mix(t.border, t.textPrimary, 1 - lo), t) >= BORDER_TARGET) return mix(t.border, t.textPrimary, 1 - lo);\n let hi = 1;\n for (let i = 0; i < 16; i++) {\n const mid = (lo + hi) / 2;\n if (worstContrast(mix(t.border, t.textPrimary, 1 - mid), t) >= BORDER_TARGET) hi = mid;\n else lo = mid;\n }\n return mix(t.border, t.textPrimary, 1 - hi);\n}\n\n/** The four stepped values for `t`. Pure. */\nexport function contrastStep(t: TokenSet): ContrastStep {\n return {\n textSecondary: mix(t.textSecondary, t.textPrimary, 0.5),\n textMuted: t.textSecondary,\n textPlaceholder: t.textMuted,\n border: steppedBorder(t),\n };\n}\n\n/* ── The token set last written to an element ───────────────────────────── */\n\nconst lastWritten = new WeakMap<HTMLElement, TokenSet>();\n\n/** Remember the set `applyTokenSet` wrote, so a contrast change can re-apply it. */\nexport function rememberTokenSet(el: HTMLElement, t: TokenSet): void {\n lastWritten.set(el, t);\n}\n\n/** The set `applyTokenSet` last wrote to `el`, or undefined when nothing did — an app\n * without the palette layer, whose base tokens come from tokens.css. */\nexport function rememberedTokenSet(el: HTMLElement): TokenSet | undefined {\n return lastWritten.get(el);\n}\n"],"mappings":";AAAA,SAAS,UAAU,YAAY,UAAU,YAAY,aAAa;AA0B3D,MAAM,wBAAwB,CAAC,oBAAoB,gBAAgB,sBAAsB,UAAU;AAe1G,SAAS,MAAM,KAAyB;AACtC,QAAM,MAAM,SAAS,GAAG;AACxB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,WAAW,GAAG;AAClC,QAAM,MAAO,IAAI,KAAK,KAAM;AAC5B,SAAO,EAAE,GAAG,GAAG,IAAI,KAAK,IAAI,GAAG,GAAG,GAAG,IAAI,KAAK,IAAI,GAAG,EAAE;AACzD;AAMO,SAAS,IAAI,GAAW,GAAW,GAAmB;AAC3D,QAAM,IAAI,MAAM,CAAC;AACjB,QAAM,IAAI,MAAM,CAAC;AACjB,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAM,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,IAAI;AAC/B,QAAM,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,IAAI;AAC/B,QAAM,IAAI,EAAE,IAAI,IAAI,EAAE,KAAK,IAAI;AAC/B,QAAM,IAAI,KAAK,MAAM,GAAG,CAAC;AACzB,QAAM,IAAI,IAAI,OAAO,KAAM,KAAK,MAAM,GAAG,CAAC,IAAI,MAAO,KAAK,KAAK,OAAO;AACtE,SAAO,MAAM,WAAW,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;AACtC;AAcO,MAAM,YAAY;AAGlB,SAAS,UAAU,GAAwD;AAChF,SAAO,IAAI,EAAE,aAAa,EAAE,WAAW,SAAS;AAClD;AAaO,MAAM,oBAAoB;AAG1B,MAAM,iBAAiB;AACvB,MAAM,uBAAuB;AAI7B,SAAS,UAAU,GAIxB;AACA,SAAO;AAAA,IACL,YAAY,IAAI,EAAE,OAAO,EAAE,aAAa,iBAAiB;AAAA,IACzD,SAAS,IAAI,EAAE,OAAO,EAAE,WAAW,cAAc;AAAA,IACjD,cAAc,IAAI,EAAE,OAAO,EAAE,WAAW,oBAAoB;AAAA,EAC9D;AACF;AAGA,MAAM,gBAAgB;AAEtB,MAAM,sBAAsB;AAE5B,SAAS,cAAc,OAAe,GAAqB;AACzD,SAAO,KAAK,IAAI,SAAS,OAAO,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,SAAS,GAAG,SAAS,OAAO,EAAE,UAAU,CAAC;AACxG;AAEA,SAAS,cAAc,GAAqB;AAI1C,MAAI,KAAK;AACT,MAAI,cAAc,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,EAAE,GAAG,CAAC,KAAK,cAAe,QAAO,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,EAAE;AACvH,MAAI,KAAK;AACT,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,OAAO,KAAK,MAAM;AACxB,QAAI,cAAc,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,GAAG,GAAG,CAAC,KAAK,cAAe,MAAK;AAAA,QAC9E,MAAK;AAAA,EACZ;AACA,SAAO,IAAI,EAAE,QAAQ,EAAE,aAAa,IAAI,EAAE;AAC5C;AAGO,SAAS,aAAa,GAA2B;AACtD,SAAO;AAAA,IACL,eAAe,IAAI,EAAE,eAAe,EAAE,aAAa,GAAG;AAAA,IACtD,WAAW,EAAE;AAAA,IACb,iBAAiB,EAAE;AAAA,IACnB,QAAQ,cAAc,CAAC;AAAA,EACzB;AACF;AAIA,MAAM,cAAc,oBAAI,QAA+B;AAGhD,SAAS,iBAAiB,IAAiB,GAAmB;AACnE,cAAY,IAAI,IAAI,CAAC;AACvB;AAIO,SAAS,mBAAmB,IAAuC;AACxE,SAAO,YAAY,IAAI,EAAE;AAC3B;","names":[]}
@@ -91,7 +91,7 @@ declare function createContrastStore(storageKey: string): {
91
91
  setState(state: ContrastState | ((state: ContrastState) => ContrastState), replace: true): unknown;
92
92
  persist: {
93
93
  setOptions: (options: Partial<zustand_middleware.PersistOptions<ContrastState, {
94
- contrast: "more" | "system" | "standard" | null;
94
+ contrast: "standard" | "more" | "system" | null;
95
95
  }, unknown>>) => void;
96
96
  clearStorage: () => void;
97
97
  rehydrate: () => Promise<void> | void;
@@ -99,7 +99,7 @@ declare function createContrastStore(storageKey: string): {
99
99
  onHydrate: (fn: (state: ContrastState) => void) => () => void;
100
100
  onFinishHydration: (fn: (state: ContrastState) => void) => () => void;
101
101
  getOptions: () => Partial<zustand_middleware.PersistOptions<ContrastState, {
102
- contrast: "more" | "system" | "standard" | null;
102
+ contrast: "standard" | "more" | "system" | null;
103
103
  }, unknown>>;
104
104
  };
105
105
  }>;
@@ -10,6 +10,7 @@ import {
10
10
  simulateCvd,
11
11
  solveLightness
12
12
  } from "./color.js";
13
+ import { brandSoft, hoverFill } from "./contrast-tokens.js";
13
14
  const HUE_HOME = {
14
15
  danger: { hue: 27, slack: 14 },
15
16
  warning: { hue: 75, slack: 16 },
@@ -36,7 +37,14 @@ function placeHue(home, slack, taken) {
36
37
  const SURFACE_L = {
37
38
  // Light: the PAGE is slightly darker than the cards on it, so a card reads as raised
38
39
  // without needing a shadow to say so. Matches the shipped presets' relationship.
39
- light: { page: 0.912, surface: 0.945, surface2: 0.929 },
40
+ //
41
+ // The light WELL is the page itself (0.33, docs/colour-roles-harmonization.md §5.3,
42
+ // k26). Between the card and the page it sat 1.02–1.05:1 from the card it is inset
43
+ // in, which is no well at all; at the page's lightness it stands out from the card
44
+ // exactly as far as the card does from the page, and nothing re-solves, because the
45
+ // page is already the surface every text role is solved against. Dark keeps its
46
+ // well a step lighter than the card (1.11–1.13:1).
47
+ light: { page: 0.912, surface: 0.945, surface2: 0.912 },
40
48
  dark: { page: 0.158, surface: 0.206, surface2: 0.249 }
41
49
  };
42
50
  const BORDER_CONTRAST = 1.5;
@@ -102,21 +110,44 @@ function derivePalette({
102
110
  const textPrimary = solveAgainst(brand.h, textChroma, targets.primary, worstSurface, mode);
103
111
  const textSecondary = solveAgainst(brand.h, textChroma, targets.secondary, worstSurface, mode);
104
112
  const textMuted = solveAgainst(brand.h, textChroma, targets.muted, worstSurface, mode);
113
+ const hover = hoverFill({ textPrimary, bgSurface: surfaces.bgSurface });
114
+ const worstText = [worstSurface, hover].reduce(
115
+ (a, b) => contrast(a, mode === "light" ? "#000000" : "#ffffff") < contrast(b, mode === "light" ? "#000000" : "#ffffff") ? a : b
116
+ );
105
117
  const brandChroma = Math.max(brand.c, 0.06);
106
118
  const brandRatio = contrast(anchors.brand, worstSurface);
107
- const brandHex = brandRatio >= 3 ? toHex(parseHex(anchors.brand)) : solveAgainst(brand.h, brandChroma, 3, worstSurface, mode);
119
+ let brandHex = brandRatio >= 3 ? toHex(parseHex(anchors.brand)) : solveAgainst(brand.h, brandChroma, 3, worstSurface, mode);
108
120
  if (brandRatio < 3) {
109
121
  warnings.push(
110
122
  `brand ${anchors.brand} sits at ${brandRatio.toFixed(2)}:1 against the ${mode} surfaces, below the 3:1 WCAG 1.4.11 asks of a control boundary. Lightness was adjusted to ${brandHex}; hue and chroma are unchanged.`
111
123
  );
112
124
  }
125
+ const brandMutedWorst = (hex) => {
126
+ const soft = brandSoft({ brand: hex, textPrimary, bgSurface: surfaces.bgSurface });
127
+ return Math.min(
128
+ contrast(soft.brandMuted, worstText),
129
+ contrast(soft.brandMuted, soft.brandBg),
130
+ contrast(soft.brandMuted, soft.brandBgHover)
131
+ );
132
+ };
133
+ for (let i = 0; i < 40 && brandMutedWorst(brandHex) < 4.5; i++) {
134
+ const lch = hexToOklch(brandHex);
135
+ brandHex = oklchToHex({ ...lch, l: mode === "light" ? lch.l - 5e-3 : lch.l + 5e-3 });
136
+ }
137
+ if (brandMutedWorst(brandHex) < 4.5) {
138
+ warnings.push(
139
+ `the brand as text (--brand-muted) reaches only ${brandMutedWorst(brandHex).toFixed(2)}:1 against the ${mode} surfaces (needs 4.5:1).`
140
+ );
141
+ }
113
142
  const brandLch = hexToOklch(brandHex);
143
+ const brandContrast = contrastOn(brandHex);
114
144
  const brandHover = oklchToHex({
115
145
  ...brandLch,
116
- // Hover moves AWAY from the page in both themes: darker on light, lighter on dark.
117
- l: mode === "light" ? brandLch.l - 0.06 : brandLch.l + 0.06
146
+ // Hover moves AWAY from the colour of the text on it (0.33, §5.5): darker under
147
+ // white, lighter under a dark ink. It used to move away from the page, which in dark
148
+ // lightened a fill that carries WHITE text, toward its own label: 4.07:1.
149
+ l: brandContrast === "#ffffff" ? brandLch.l - 0.06 : brandLch.l + 0.06
118
150
  });
119
- const brandContrast = contrastOn(brandHex);
120
151
  const onBrand = contrast(brandHex, brandContrast);
121
152
  if (onBrand < 4.5) {
122
153
  warnings.push(
@@ -156,7 +187,7 @@ function derivePalette({
156
187
  const bg = tintOn(hue, chroma, surfaces.bgSurface, mode);
157
188
  const onSurface = solveLightness(
158
189
  4.5,
159
- parseHex(worstSurface),
190
+ parseHex(worstText),
160
191
  hue,
161
192
  chroma,
162
193
  mode === "light" ? "darker" : "lighter"
@@ -221,11 +252,16 @@ function derivePalette({
221
252
  semantic.accentContrast = contrastOn(accent);
222
253
  semantic.accentBg = tintOn(accentLch.h, Math.max(accentLch.c, 0.06), surfaces.bgSurface, mode);
223
254
  }
224
- const money = (hue, target) => solveAgainst(hue, 0.12, target, worstSurface, mode);
255
+ const money = (hue, target) => solveAgainst(hue, 0.12, target, worstText, mode);
225
256
  const moneyIncome = money(175, 4.5);
226
257
  const moneyExpense = money(70, 4.5);
227
258
  const moneyNet = money(300, 4.5);
228
- const moneyNeutral = oklchToHex({ l: mode === "light" ? 0.52 : 0.72, c: 0.012, h: brand.h });
259
+ const neutralFloor = solveLightness(4.5, parseHex(worstText), brand.h, 0.012, mode === "light" ? "darker" : "lighter");
260
+ const moneyNeutral = oklchToHex({
261
+ l: mode === "light" ? Math.min(0.52, neutralFloor) : Math.max(0.72, neutralFloor),
262
+ c: 0.012,
263
+ h: brand.h
264
+ });
229
265
  const tokens = {
230
266
  ...surfaces,
231
267
  textPrimary,
@@ -269,25 +305,37 @@ function auditPalette(t, semantic) {
269
305
  ["bgSurface", t.bgSurface],
270
306
  ["bgSurface2", t.bgSurface2]
271
307
  ];
308
+ const hover = hoverFill(t);
309
+ const textGrounds = [...surfaces, ["bgHover", hover]];
310
+ const { brandMuted, brandBg, brandBgHover } = brandSoft(t);
272
311
  const add = (pair, fg, bg, required, rule) => {
273
312
  const ratio = contrast(fg, bg);
274
313
  checks.push({ pair, ratio, required, rule, passes: ratio >= required - 0.05 });
275
314
  };
276
315
  for (const [name, bg] of surfaces) {
316
+ add(`brand on ${name}`, t.brand, bg, 3, "WCAG 1.4.11 graphical object");
317
+ add(`border on ${name}`, t.border, bg, 1.1, "house rule: a hairline must be findable");
318
+ }
319
+ for (const [name, bg] of textGrounds) {
277
320
  add(`textPrimary on ${name}`, t.textPrimary, bg, 7, "WCAG 1.4.6 AAA body text");
278
321
  add(`textSecondary on ${name}`, t.textSecondary, bg, 4.5, "WCAG 1.4.3 AA body text");
279
322
  add(`textMuted on ${name}`, t.textMuted, bg, 4.5, "WCAG 1.4.3 AA body text");
280
- add(`brand on ${name}`, t.brand, bg, 3, "WCAG 1.4.11 graphical object");
281
- add(`border on ${name}`, t.border, bg, 1.1, "house rule: a hairline must be findable");
323
+ add(`brandMuted on ${name}`, brandMuted, bg, 4.5, "WCAG 1.4.3 \u2014 the brand as text");
282
324
  for (const [key, hex] of [
283
325
  ["moneyIncome", t.moneyIncome],
284
326
  ["moneyExpense", t.moneyExpense],
285
- ["moneyNet", t.moneyNet]
327
+ ["moneyNet", t.moneyNet],
328
+ // Not a figure's colour but still printed as one (the numpad's sign, a future
329
+ // row's amount) — measured since 0.33, when it was found at 4.24:1.
330
+ ["moneyNeutral", t.moneyNeutral]
286
331
  ]) {
287
332
  add(`${key} on ${name}`, hex, bg, 4.5, "WCAG 1.4.3 \u2014 these are rendered as TEXT");
288
333
  }
289
334
  }
290
335
  add("brandContrast on brand", t.brandContrast, t.brand, 4.5, "WCAG 1.4.3 AA body text");
336
+ add("brandContrast on brandHover", t.brandContrast, t.brandHover, 4.5, "WCAG 1.4.3 AA body text");
337
+ add("brandMuted on brandBg", brandMuted, brandBg, 4.5, "WCAG 1.4.3 \u2014 a soft brand chip's label");
338
+ add("brandMuted on brandBgHover", brandMuted, brandBgHover, 4.5, "WCAG 1.4.3 \u2014 a hovered soft brand chip's label");
291
339
  if (semantic) {
292
340
  for (const [name, fg] of [
293
341
  ["danger", semantic.danger],
@@ -296,6 +344,7 @@ function auditPalette(t, semantic) {
296
344
  ["success", semantic.success]
297
345
  ]) {
298
346
  add(`${name} on bgSurface`, fg, t.bgSurface, 4.5, "WCAG 1.4.3 \u2014 rendered as text");
347
+ add(`${name} on bgHover`, fg, hover, 4.5, "WCAG 1.4.3 \u2014 rendered as text, on a hovered row");
299
348
  }
300
349
  add("danger on dangerBg", semantic.danger, semantic.dangerBg, 4.5, "WCAG 1.4.3 \u2014 badge text");
301
350
  add("warning on warningBg", semantic.warning, semantic.warningBg, 4.5, "WCAG 1.4.3 \u2014 badge text");