@eifi1/ui-kit 0.4.0 → 0.5.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 (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +91 -20
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +96 -20
  63. package/dist/components/dropdown.js +77 -33
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +38 -6
  75. package/dist/components/file-dropzone.js +34 -9
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +28 -2
  105. package/dist/components/number-input.js +12 -2
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +32 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +15 -4
  176. package/dist/components/user-avatar.js +5 -4
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +27 -5
  270. package/dist/theme/palette-presets.js +231 -33
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +67 -14
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -0,0 +1,760 @@
1
+ // Appearance presets — the single source of truth for the "palette switcher"
2
+ // (feedback #307). Each preset is a full set of design-token values for BOTH
3
+ // light and dark, covering the page/surface backgrounds, the brand accent, the
4
+ // semantic money trio and the 9-colour categorical chart palette, plus the
5
+ // heatmap stops. A preset is applied at runtime by writing these as CSS custom
6
+ // properties onto <html> (see palette-store.ts), which instantly re-skins every
7
+ // token-driven surface and chart. The two heatmaps and the treemap interpolate
8
+ // concrete hex in JS, so they read the same values from here via the hooks in
9
+ // palette-store.
10
+ //
11
+ // The alternative presets below were designed + accessibility-vetted as a set
12
+ // (each is CVD-safe across protan/deutan/tritan, money is never red/green, and
13
+ // text/background contrast clears WCAG AA on both themes). To promote one to the
14
+ // app-wide default, copy its values into the :root/.dark blocks in tokens.css
15
+ // (the default preset here mirrors those exactly).
16
+
17
+ import type { HeatStops } from "./chart-palette";
18
+
19
+ export interface TokenSet {
20
+ // Backgrounds & chrome
21
+ bgPage: string;
22
+ bgSurface: string;
23
+ bgSurface2: string;
24
+ border: string;
25
+ textPrimary: string;
26
+ // The two quieter text roles. Derived per preset by blending `textPrimary` toward
27
+ // that preset's own surfaces until the contrast against the WORST of bgPage /
28
+ // bgSurface / bgSurface2 hits 7:1 and 4.8:1 — so both clear WCAG AA for normal text
29
+ // (4.5:1) on every surface they can land on, and both inherit the preset's warmth
30
+ // instead of importing a cool grey from Tailwind's palette.
31
+ textSecondary: string;
32
+ textMuted: string;
33
+ // Interactive brand accent
34
+ brand: string;
35
+ brandHover: string;
36
+ brandContrast: string;
37
+ // Semantic money (never red/green — CVD-safe, paired with +/- cues elsewhere)
38
+ moneyIncome: string;
39
+ moneyExpense: string;
40
+ moneyNet: string;
41
+ moneyNeutral: string;
42
+ // Categorical chart series (Paul Tol "Muted" by default — CVD-safe), exactly 9
43
+ chart: string[];
44
+ // Heatmap interpolation stops (variance diverging + spending sequential)
45
+ heat: HeatStops;
46
+ }
47
+
48
+ export interface PalettePreset {
49
+ id: string;
50
+ name: string;
51
+ blurb: string;
52
+ light: TokenSet;
53
+ dark: TokenSet;
54
+ }
55
+
56
+ // The shipped default (feedback #328): SOLAR-DUSK warm light + INDIGO-NOIR deep
57
+ // dark surfaces — the two curated presets the user picked per mode — but with a
58
+ // SINGLE unified brand accent (indigo) across both modes rather than each
59
+ // preset's own accent (the user asked for the brand accent to stay the same).
60
+ // So the light block is Solar Dusk with its teal brand overridden to indigo, and
61
+ // the dark block is Indigo Noir (already indigo). The money trio
62
+ // (income/expense/net) drives the chart bars and stays palette-driven so those
63
+ // semantic colours adapt to each mode's contrast (best practice — feedback #328).
64
+ export const DEFAULT_PRESET: PalettePreset = {
65
+ id: "default",
66
+ name: "Default",
67
+ blurb: "Solar-dusk light · indigo-noir dark",
68
+ light: {
69
+ // Solar Dusk (light) surfaces, with the brand unified to indigo (see above).
70
+ bgPage: "#ede3cf",
71
+ bgSurface: "#f6ecd9",
72
+ bgSurface2: "#f3ead6",
73
+ border: "#e2d6bd",
74
+ textPrimary: "#463f33",
75
+ textSecondary: "#50493c",
76
+ textMuted: "#696153",
77
+ brand: "#4f46e5",
78
+ brandHover: "#4338ca",
79
+ brandContrast: "#ffffff",
80
+ moneyIncome: "#0b6650",
81
+ moneyExpense: "#905902",
82
+ moneyNet: "#7a4fa3",
83
+ moneyNeutral: "#6b6453",
84
+ chart: ["#332288", "#88ccee", "#44aa99", "#117733", "#999933", "#ddcc77", "#cc6677", "#882255", "#aa4499"],
85
+ heat: {
86
+ neutral: "#f1e7d2",
87
+ under: "#0b6650",
88
+ over: "#9c6418",
89
+ seqLow: "#f6ecd0",
90
+ seqHigh: "#9c6418",
91
+ empty: "rgba(148,163,184,0.14)",
92
+ },
93
+ },
94
+ dark: {
95
+ // Indigo Noir (dark)
96
+ bgPage: "#0e0f1c",
97
+ bgSurface: "#181a2c",
98
+ bgSurface2: "#21243a",
99
+ border: "#2e3252",
100
+ textPrimary: "#e6e8f4",
101
+ textSecondary: "#adafbe",
102
+ textMuted: "#8d90a0",
103
+ brand: "#8b93f8",
104
+ brandHover: "#aab1fb",
105
+ brandContrast: "#16182b",
106
+ moneyIncome: "#22c3b6",
107
+ // Amber-400 (#fbbf24) sat at OKLab L 0.84 while every other dark token lives
108
+ // at 0.68–0.74, so outflows outshouted the inflows they are paired with. Same
109
+ // hue and chroma, pulled to L 0.785 (dev#434). The heat stops below keep the
110
+ // brighter amber on purpose: a gradient endpoint is meant to be the loudest
111
+ // thing in its scale, and it is a fill, not a figure.
112
+ moneyExpense: "#e4b035",
113
+ moneyNet: "#b39aef",
114
+ moneyNeutral: "#9aa1bd",
115
+ chart: ["#7e72d6", "#9fd8f2", "#5fc4b0", "#3fa45f", "#bfbf5e", "#e8dda0", "#e08c9a", "#c25a78", "#cc78be"],
116
+ heat: {
117
+ neutral: "#363a5a",
118
+ under: "#22c3b6",
119
+ over: "#fbbf24",
120
+ seqLow: "#3a2c14",
121
+ seqHigh: "#fbbf24",
122
+ empty: "rgba(148,163,184,0.10)",
123
+ },
124
+ },
125
+ };
126
+
127
+ // Vetted alternatives to preview (CVD-safe, WCAG-AA on both themes).
128
+ export const ALTERNATIVE_PRESETS: PalettePreset[] = [
129
+ {
130
+ id: "warm-paper",
131
+ name: "Warm Paper",
132
+ blurb: "Cream paper · warm charcoal",
133
+ light: {
134
+ bgPage: "#ece5d6",
135
+ bgSurface: "#f5efe3",
136
+ bgSurface2: "#efe9dc",
137
+ border: "#e2dcce",
138
+ textPrimary: "#2b2622",
139
+ textSecondary: "#4f4a44",
140
+ textMuted: "#68625b",
141
+ brand: "#9a5b34",
142
+ brandHover: "#824827",
143
+ brandContrast: "#fdf8f2",
144
+ moneyIncome: "#0e7490",
145
+ moneyExpense: "#8f3f0a",
146
+ moneyNet: "#7c4dab",
147
+ moneyNeutral: "#6b6258",
148
+ chart: ["#332288", "#88ccee", "#44aa99", "#117733", "#999933", "#ddcc77", "#cc6677", "#882255", "#aa4499"],
149
+ heat: {
150
+ neutral: "#efe9dc",
151
+ under: "#0e7490",
152
+ over: "#b45309",
153
+ seqLow: "#fbeed2",
154
+ seqHigh: "#b45309",
155
+ empty: "rgba(148,163,184,0.14)",
156
+ },
157
+ },
158
+ dark: {
159
+ bgPage: "#211e1a",
160
+ bgSurface: "#2a2622",
161
+ bgSurface2: "#322d27",
162
+ border: "#3d372f",
163
+ textPrimary: "#e8e2d6",
164
+ textSecondary: "#bfb9af",
165
+ textMuted: "#9e998f",
166
+ brand: "#e0986a",
167
+ brandHover: "#ecae85",
168
+ brandContrast: "#2a1c10",
169
+ moneyIncome: "#4dd4c4",
170
+ moneyExpense: "#fbbf3a",
171
+ moneyNet: "#bfa0e0",
172
+ moneyNeutral: "#a39a8c",
173
+ chart: ["#7e72d6", "#9fd8f2", "#5fc4b0", "#3fa45f", "#bfbf5e", "#e8dda0", "#e08c9a", "#cf6488", "#cc78be"],
174
+ heat: {
175
+ neutral: "#3a352e",
176
+ under: "#4dd4c4",
177
+ over: "#fbbf3a",
178
+ seqLow: "#2c2014",
179
+ seqHigh: "#fbbf3a",
180
+ empty: "rgba(148,163,184,0.10)",
181
+ },
182
+ },
183
+ },
184
+ {
185
+ id: "nord-frost",
186
+ name: "Nord Frost",
187
+ blurb: "Cool snow · polar night",
188
+ light: {
189
+ bgPage: "#e2e7ef",
190
+ bgSurface: "#f0f3f8",
191
+ bgSurface2: "#e1e6ee",
192
+ border: "#d3dae6",
193
+ textPrimary: "#2e3440",
194
+ textSecondary: "#454b56",
195
+ textMuted: "#5e636e",
196
+ brand: "#3b6ea5",
197
+ brandHover: "#305a86",
198
+ brandContrast: "#ffffff",
199
+ moneyIncome: "#0f8060",
200
+ moneyExpense: "#b8431a",
201
+ moneyNet: "#6b4fa0",
202
+ moneyNeutral: "#5b6577",
203
+ chart: ["#3a6ea5", "#56b6c2", "#2f7d4f", "#cdb52f", "#d05f7a", "#9a3f93", "#4d4bbf", "#dd6a2c", "#7a8aa8"],
204
+ heat: {
205
+ neutral: "#e1e6ee",
206
+ under: "#0f8060",
207
+ over: "#b8431a",
208
+ seqLow: "#f0f3f7",
209
+ seqHigh: "#a14708",
210
+ empty: "rgba(148,163,184,0.14)",
211
+ },
212
+ },
213
+ dark: {
214
+ bgPage: "#242933",
215
+ bgSurface: "#2e3440",
216
+ bgSurface2: "#3b4252",
217
+ border: "#434c5e",
218
+ textPrimary: "#eceff4",
219
+ textSecondary: "#d4d7de",
220
+ textMuted: "#afb3bc",
221
+ brand: "#88c0d0",
222
+ brandHover: "#a3d4e0",
223
+ brandContrast: "#1c2027",
224
+ moneyIncome: "#4ecf9c",
225
+ moneyExpense: "#e8a24f",
226
+ moneyNet: "#caa0e0",
227
+ moneyNeutral: "#9aa5b8",
228
+ chart: ["#7fb4e6", "#5ec9d6", "#5cc483", "#dccb6b", "#e87e96", "#bd6fce", "#9389f2", "#ef8f4a", "#8693ad"],
229
+ heat: {
230
+ neutral: "#3b4252",
231
+ under: "#4ecf9c",
232
+ over: "#e8a24f",
233
+ seqLow: "#33373f",
234
+ seqHigh: "#e8a24f",
235
+ empty: "rgba(148,163,184,0.10)",
236
+ },
237
+ },
238
+ },
239
+ {
240
+ id: "indigo-noir",
241
+ name: "Indigo Noir",
242
+ blurb: "Crisp light · deep indigo",
243
+ light: {
244
+ bgPage: "#e8eaf2",
245
+ bgSurface: "#f6f7fb",
246
+ bgSurface2: "#eef1f8",
247
+ border: "#dadfeb",
248
+ textPrimary: "#1b1f2e",
249
+ textSecondary: "#494d5a",
250
+ textMuted: "#626572",
251
+ brand: "#4338ca",
252
+ brandHover: "#3730a3",
253
+ brandContrast: "#ffffff",
254
+ moneyIncome: "#0c6478",
255
+ moneyExpense: "#b45309",
256
+ moneyNet: "#7c3aed",
257
+ moneyNeutral: "#5b6478",
258
+ chart: ["#332288", "#88ccee", "#44aa99", "#117733", "#999933", "#ddcc77", "#cc6677", "#882255", "#aa4499"],
259
+ heat: {
260
+ neutral: "#eef1f7",
261
+ under: "#0c6478",
262
+ over: "#b45309",
263
+ seqLow: "#fdeed4",
264
+ seqHigh: "#b45309",
265
+ empty: "rgba(148,163,184,0.14)",
266
+ },
267
+ },
268
+ dark: {
269
+ bgPage: "#0e0f1c",
270
+ bgSurface: "#181a2c",
271
+ bgSurface2: "#21243a",
272
+ border: "#2e3252",
273
+ textPrimary: "#e6e8f4",
274
+ textSecondary: "#adafbe",
275
+ textMuted: "#8d90a0",
276
+ brand: "#8b93f8",
277
+ brandHover: "#aab1fb",
278
+ brandContrast: "#16182b",
279
+ moneyIncome: "#22c3b6",
280
+ // Kept byte-identical to DEFAULT_PRESET.dark, which IS this preset (dev#434).
281
+ moneyExpense: "#e4b035",
282
+ moneyNet: "#b39aef",
283
+ moneyNeutral: "#9aa1bd",
284
+ chart: ["#7e72d6", "#9fd8f2", "#5fc4b0", "#3fa45f", "#bfbf5e", "#e8dda0", "#e08c9a", "#c25a78", "#cc78be"],
285
+ heat: {
286
+ neutral: "#363a5a",
287
+ under: "#22c3b6",
288
+ over: "#fbbf24",
289
+ seqLow: "#3a2c14",
290
+ seqHigh: "#fbbf24",
291
+ empty: "rgba(148,163,184,0.10)",
292
+ },
293
+ },
294
+ },
295
+ {
296
+ id: "solar-dusk",
297
+ name: "Solar Dusk",
298
+ blurb: "Solarized warmth, easy on the eyes",
299
+ light: {
300
+ bgPage: "#ede3cf",
301
+ bgSurface: "#f6ecd9",
302
+ bgSurface2: "#f3ead6",
303
+ border: "#e2d6bd",
304
+ textPrimary: "#463f33",
305
+ textSecondary: "#50493c",
306
+ textMuted: "#696153",
307
+ brand: "#2c6979",
308
+ brandHover: "#1f515f",
309
+ brandContrast: "#ffffff",
310
+ moneyIncome: "#0b6650",
311
+ moneyExpense: "#905902",
312
+ moneyNet: "#7a4fa3",
313
+ moneyNeutral: "#6b6453",
314
+ chart: ["#332288", "#88ccee", "#44aa99", "#117733", "#999933", "#ddcc77", "#cc6677", "#882255", "#aa4499"],
315
+ heat: {
316
+ neutral: "#f1e7d2",
317
+ under: "#0b6650",
318
+ over: "#9c6418",
319
+ seqLow: "#f6ecd0",
320
+ seqHigh: "#9c6418",
321
+ empty: "rgba(148,163,184,0.14)",
322
+ },
323
+ },
324
+ dark: {
325
+ bgPage: "#0e1a1f",
326
+ bgSurface: "#15272e",
327
+ bgSurface2: "#1d333c",
328
+ border: "#2a4750",
329
+ textPrimary: "#d6cdb8",
330
+ textSecondary: "#c2bdab",
331
+ textMuted: "#9c9d91",
332
+ brand: "#5fb8cc",
333
+ brandHover: "#82cfe0",
334
+ brandContrast: "#0a161a",
335
+ moneyIncome: "#22b884",
336
+ moneyExpense: "#f0b94e",
337
+ moneyNet: "#bfa0e0",
338
+ moneyNeutral: "#9aa3a0",
339
+ chart: ["#7e72d6", "#9fd8f2", "#5fc4b0", "#3fa45f", "#bfbf5e", "#e8dda0", "#e08c9a", "#c25a78", "#c873c0"],
340
+ heat: {
341
+ neutral: "#26424b",
342
+ under: "#22b884",
343
+ over: "#f0b94e",
344
+ seqLow: "#1f3138",
345
+ seqHigh: "#f0b94e",
346
+ empty: "rgba(148,163,184,0.10)",
347
+ },
348
+ },
349
+ },
350
+ {
351
+ id: "zinc-mono",
352
+ name: "Zinc Mono",
353
+ blurb: "True-neutral chrome · data pops",
354
+ light: {
355
+ bgPage: "#ececec",
356
+ bgSurface: "#f7f7f7",
357
+ bgSurface2: "#f4f4f5",
358
+ border: "#e4e4e7",
359
+ textPrimary: "#18181b",
360
+ textSecondary: "#4e4e50",
361
+ textMuted: "#676769",
362
+ brand: "#1d4ed8",
363
+ brandHover: "#1e40af",
364
+ brandContrast: "#ffffff",
365
+ moneyIncome: "#0e7490",
366
+ moneyExpense: "#cf4310",
367
+ moneyNet: "#7c3aed",
368
+ moneyNeutral: "#52525b",
369
+ chart: ["#332288", "#88ccee", "#44aa99", "#117733", "#999933", "#ddcc77", "#cc6677", "#882255", "#aa4499"],
370
+ heat: {
371
+ neutral: "#f4f4f5",
372
+ under: "#0e7490",
373
+ over: "#cf4310",
374
+ seqLow: "#fdeede",
375
+ seqHigh: "#cf4310",
376
+ empty: "rgba(148,163,184,0.14)",
377
+ },
378
+ },
379
+ dark: {
380
+ bgPage: "#09090b",
381
+ bgSurface: "#18181b",
382
+ bgSurface2: "#27272a",
383
+ border: "#3f3f46",
384
+ textPrimary: "#f4f4f5",
385
+ textSecondary: "#b2b2b3",
386
+ textMuted: "#929294",
387
+ brand: "#60a5fa",
388
+ brandHover: "#93c5fd",
389
+ brandContrast: "#0b1220",
390
+ moneyIncome: "#22d3ee",
391
+ moneyExpense: "#f97316",
392
+ moneyNet: "#c4b5fd",
393
+ moneyNeutral: "#a1a1aa",
394
+ chart: ["#7e72d6", "#9fd8f2", "#5fc4b0", "#3fa45f", "#bfbf5e", "#e8dda0", "#e08c9a", "#c25a78", "#cc78be"],
395
+ heat: {
396
+ neutral: "#3f3f46",
397
+ under: "#22d3ee",
398
+ over: "#f97316",
399
+ seqLow: "#3a2410",
400
+ seqHigh: "#f97316",
401
+ empty: "rgba(148,163,184,0.10)",
402
+ },
403
+ },
404
+ },
405
+ ];
406
+
407
+ // ── "Imprint" chart hues (feedback #119) ──────────────────────────────────────
408
+ // anyplot's categorical palette (https://anyplot.ai/palette), by a colleague of
409
+ // Marcel's. 8 hues + an amber semantic anchor, derived from Wong 2011 by a
410
+ // Petroff-style max-min ΔE search in CAM02-UCS with Machado 2009 CVD simulation —
411
+ // the same lineage as our default Paul Tol "Muted", one generation on, and tuned
412
+ // for exactly the warm cream paper our light theme uses.
413
+ //
414
+ // This preset is DEFAULT_PRESET with ONLY the 9 categorical chart hues swapped;
415
+ // surfaces, brand and the semantic money trio stay put, because those were their
416
+ // own deliberate decisions (#307/#328) and are not what the palette is for. The
417
+ // heatmap stops likewise stay teal/amber: they encode under/over, and imprint's
418
+ // own diverging ramp is red↔blue, which would fight the money semantics.
419
+ //
420
+ // Measured on our surfaces (CIEDE2000, Machado severity-100, 8 like-for-like
421
+ // slots on the cream light surface #f6ecd9) — neither palette dominates:
422
+ // Paul Tol Muted min-contrast 1.38 · worst-CVD ΔE00 11.8 · normal ΔE00 16.1
423
+ // imprint min-contrast 1.95 · worst-CVD ΔE00 7.2 · normal ΔE00 22.2
424
+ // i.e. imprint is punchier and has no near-invisible pale slot (Tol's #88ccee
425
+ // sits at 1.5:1 on cream), while Tol separates better under colour-vision
426
+ // deficiency. So this ships as a CHOICE, not as a new default.
427
+ //
428
+ // The dark set keeps imprint's hues verbatim except matte-red and rose, whose
429
+ // lightness is lifted (oklch L 0.50→0.55) because verbatim they land at 2.67:1
430
+ // and 2.76:1 on our deep-indigo dark surface — under WCAG 2.1 SC 1.4.11's 3:1
431
+ // non-text minimum. Lifted they clear it at 3.30:1 / 3.28:1. anyplot documents
432
+ // the same shortfall on their own dark ground and answers it with a stroke halo;
433
+ // lifting keeps the fix in the token layer instead of every chart component.
434
+ const IMPRINT_HUES_LIGHT = [
435
+ "#009E73", // brand green — first series
436
+ "#C475FD", // lavender — creative
437
+ "#4467A3", // blue — cool / info
438
+ "#BD8233", // ochre — earth / commodity
439
+ "#AE3030", // matte red — bad / loss / error
440
+ "#2ABCCD", // cyan — sky / tech-cool
441
+ "#954477", // rose — wellness / health
442
+ "#99B314", // lime — growth / nature
443
+ "#DDCC77", // amber — anyplot's semantic anchor, used here as slot 9
444
+ ];
445
+ const IMPRINT_HUES_DARK = [
446
+ "#009E73",
447
+ "#C475FD",
448
+ "#4467A3",
449
+ "#BD8233",
450
+ "#BF413E", // matte red, L-lifted for our dark surface
451
+ "#2ABCCD",
452
+ "#A25083", // rose, L-lifted for our dark surface
453
+ "#99B314",
454
+ "#DDCC77",
455
+ ];
456
+
457
+ export const IMPRINT_PRESET: PalettePreset = {
458
+ id: "imprint",
459
+ name: "Imprint",
460
+ blurb: "Colourblind-safe chart hues by anyplot.ai",
461
+ light: { ...DEFAULT_PRESET.light, chart: IMPRINT_HUES_LIGHT },
462
+ dark: { ...DEFAULT_PRESET.dark, chart: IMPRINT_HUES_DARK },
463
+ };
464
+
465
+ // The full-theme switcher from #307 stayed retired (feedback #328) — the default
466
+ // is still the single source of truth for surfaces, brand and money, and
467
+ // ALTERNATIVE_PRESETS above remains a reference bank only. What IS selectable is
468
+ // the categorical chart ramp (feedback #119), so these two presets are the live
469
+ // set; presetById falls back to the default for any stale persisted id.
470
+ /**
471
+ * The categorical chart ramp, shared by every derived preset below.
472
+ *
473
+ * Paul Tol's "Muted" set, unchanged, and deliberately NOT re-derived per preset. A
474
+ * categorical ramp solves a different problem from a UI palette — separability among
475
+ * nine unknown siblings, rather than contrast against one known surface — and a set
476
+ * optimised against real dichromat confusion lines beats one spaced evenly by formula.
477
+ * `auditChartRamp` in palette-derive.ts measures it: 0.059 minimum separation in the
478
+ * light variant, which is the number MIN_SEPARATION was calibrated under.
479
+ *
480
+ * It pays for that in contrast: four of the nine light hues sit below the 3:1 WCAG
481
+ * 1.4.11 asks of a filled shape. That is not fixable by darkening them — see
482
+ * SINGLE_LIGHTNESS_MAX_SERIES — so a chart with more than four series must carry a
483
+ * second channel (a direct label, a pattern, a legend adjacent to the mark).
484
+ */
485
+ const PAUL_TOL = {
486
+ light: DEFAULT_PRESET.light.chart,
487
+ dark: DEFAULT_PRESET.dark.chart,
488
+ } as const;
489
+
490
+ /**
491
+ * Presets DERIVED rather than picked, by `derivePalette` from one brand colour each.
492
+ *
493
+ * Every text role here was solved to a contrast target against the worst of the three
494
+ * surfaces it can land on, every semantic colour against both the page and its own badge
495
+ * tint, and the whole set is re-checked by `derived-presets.test.ts` — which re-runs the
496
+ * deriver and fails if these committed values drift from what it now produces.
497
+ *
498
+ * They are committed rather than derived at import because deriving eight token sets
499
+ * costs ~34ms, and charging every consumer that at startup for palettes most of them
500
+ * will never select is the wrong trade. The test is what keeps the two in agreement.
501
+ */
502
+ export const DERIVED_PRESETS: PalettePreset[] = [
503
+ {
504
+ id: "ink",
505
+ name: "Ink",
506
+ blurb: "Cool blue-grey ground · solved for AAA body text",
507
+ light: {
508
+ bgPage: "#dce2ef",
509
+ bgSurface: "#e6edfa",
510
+ bgSurface2: "#e1e8f5",
511
+ border: "#b0bace",
512
+ textPrimary: "#2e3139",
513
+ textSecondary: "#454950",
514
+ textMuted: "#5f646c",
515
+ brand: "#2f5fd0",
516
+ brandHover: "#1f4cbc",
517
+ brandContrast: "#ffffff",
518
+ moneyIncome: "#00735f",
519
+ moneyExpense: "#8e5900",
520
+ moneyNet: "#7356a2",
521
+ moneyNeutral: "#656970",
522
+ chart: PAUL_TOL.light,
523
+ heat: {
524
+ neutral: "#e1e8f5",
525
+ under: "#00735f",
526
+ over: "#8e5900",
527
+ seqLow: "#fee3c5",
528
+ seqHigh: "#8e5900",
529
+ empty: "rgba(120,110,95,0.14)",
530
+ },
531
+ },
532
+ dark: {
533
+ bgPage: "#090d15",
534
+ bgSurface: "#131720",
535
+ bgSurface2: "#1d212b",
536
+ border: "#363e4e",
537
+ textPrimary: "#c7ccd5",
538
+ textSecondary: "#a7abb4",
539
+ textMuted: "#858992",
540
+ brand: "#3364d5",
541
+ brandHover: "#4477ea",
542
+ brandContrast: "#ffffff",
543
+ moneyIncome: "#00997f",
544
+ moneyExpense: "#b77b29",
545
+ moneyNet: "#9679c9",
546
+ moneyNeutral: "#a1a5ac",
547
+ chart: PAUL_TOL.dark,
548
+ heat: {
549
+ neutral: "#1d212b",
550
+ under: "#00997f",
551
+ over: "#b77b29",
552
+ seqLow: "#331f05",
553
+ seqHigh: "#b77b29",
554
+ empty: "rgba(160,170,190,0.10)",
555
+ },
556
+ },
557
+ },
558
+ {
559
+ id: "moss",
560
+ name: "Moss",
561
+ blurb: "Green ground · solved for AAA body text",
562
+ light: {
563
+ bgPage: "#d8e6dd",
564
+ bgSurface: "#e3f1e8",
565
+ bgSurface2: "#deece3",
566
+ border: "#abbfb2",
567
+ textPrimary: "#2c342e",
568
+ textSecondary: "#434b45",
569
+ textMuted: "#5d6660",
570
+ brand: "#2f7a52",
571
+ brandHover: "#196842",
572
+ brandContrast: "#ffffff",
573
+ moneyIncome: "#00735f",
574
+ moneyExpense: "#8f5a00",
575
+ moneyNet: "#7457a3",
576
+ moneyNeutral: "#646b66",
577
+ chart: PAUL_TOL.light,
578
+ heat: {
579
+ neutral: "#deece3",
580
+ under: "#00735f",
581
+ over: "#8f5a00",
582
+ seqLow: "#fee3c4",
583
+ seqHigh: "#8f5a00",
584
+ empty: "rgba(120,110,95,0.14)",
585
+ },
586
+ },
587
+ dark: {
588
+ bgPage: "#060f0a",
589
+ bgSurface: "#101a14",
590
+ bgSurface2: "#1a241e",
591
+ border: "#314237",
592
+ textPrimary: "#c5cfc9",
593
+ textSecondary: "#a4aea8",
594
+ textMuted: "#838c86",
595
+ brand: "#2f7a52",
596
+ brandHover: "#428c63",
597
+ brandContrast: "#ffffff",
598
+ moneyIncome: "#009a80",
599
+ moneyExpense: "#b87b2a",
600
+ moneyNet: "#9779c9",
601
+ moneyNeutral: "#9fa7a2",
602
+ chart: PAUL_TOL.dark,
603
+ heat: {
604
+ neutral: "#1a241e",
605
+ under: "#009a80",
606
+ over: "#b87b2a",
607
+ seqLow: "#331f05",
608
+ seqHigh: "#b87b2a",
609
+ empty: "rgba(160,170,190,0.10)",
610
+ },
611
+ },
612
+ },
613
+ {
614
+ id: "plum",
615
+ name: "Plum",
616
+ blurb: "Violet ground · solved for AAA body text",
617
+ light: {
618
+ bgPage: "#e7dfea",
619
+ bgSurface: "#f2e9f5",
620
+ bgSurface2: "#ece4ef",
621
+ border: "#c2b6c6",
622
+ textPrimary: "#342f36",
623
+ textSecondary: "#4c474e",
624
+ textMuted: "#676169",
625
+ brand: "#7b3f8f",
626
+ brandHover: "#692d7d",
627
+ brandContrast: "#ffffff",
628
+ moneyIncome: "#00725e",
629
+ moneyExpense: "#8e5900",
630
+ moneyNet: "#7356a2",
631
+ moneyNeutral: "#6c676e",
632
+ chart: PAUL_TOL.light,
633
+ heat: {
634
+ neutral: "#ece4ef",
635
+ under: "#00725e",
636
+ over: "#8e5900",
637
+ seqLow: "#fee3c5",
638
+ seqHigh: "#8e5900",
639
+ empty: "rgba(120,110,95,0.14)",
640
+ },
641
+ },
642
+ dark: {
643
+ bgPage: "#100b12",
644
+ bgSurface: "#1b151d",
645
+ bgSurface2: "#251f27",
646
+ border: "#453a48",
647
+ textPrimary: "#d0cad3",
648
+ textSecondary: "#afa9b1",
649
+ textMuted: "#8d878f",
650
+ brand: "#8f52a4",
651
+ brandHover: "#a264b7",
652
+ brandContrast: "#ffffff",
653
+ moneyIncome: "#00997f",
654
+ moneyExpense: "#b77b29",
655
+ moneyNet: "#9679c8",
656
+ moneyNeutral: "#a8a2aa",
657
+ chart: PAUL_TOL.dark,
658
+ heat: {
659
+ neutral: "#251f27",
660
+ under: "#00997f",
661
+ over: "#b77b29",
662
+ seqLow: "#331f05",
663
+ seqHigh: "#b77b29",
664
+ empty: "rgba(160,170,190,0.10)",
665
+ },
666
+ },
667
+ },
668
+ {
669
+ id: "contrast",
670
+ name: "High contrast",
671
+ blurb: "Near-neutral ground · text solved to 13:1 / 8.5:1 / 5.5:1",
672
+ light: {
673
+ bgPage: "#e0e2e5",
674
+ bgSurface: "#ebedef",
675
+ bgSurface2: "#e6e8ea",
676
+ border: "#b7babe",
677
+ textPrimary: "#191d24",
678
+ textSecondary: "#373c44",
679
+ textMuted: "#535860",
680
+ brand: "#1f2937",
681
+ brandHover: "#111b28",
682
+ brandContrast: "#ffffff",
683
+ moneyIncome: "#00735f",
684
+ moneyExpense: "#8e5900",
685
+ moneyNet: "#7456a2",
686
+ moneyNeutral: "#656970",
687
+ chart: PAUL_TOL.light,
688
+ heat: {
689
+ neutral: "#e6e8ea",
690
+ under: "#00735f",
691
+ over: "#8e5900",
692
+ seqLow: "#fee3c5",
693
+ seqHigh: "#8e5900",
694
+ empty: "rgba(120,110,95,0.14)",
695
+ },
696
+ },
697
+ dark: {
698
+ bgPage: "#0c0d0f",
699
+ bgSurface: "#161719",
700
+ bgSurface2: "#202123",
701
+ border: "#3c3e41",
702
+ textPrimary: "#e1e7f0",
703
+ textSecondary: "#b7bdc5",
704
+ textMuted: "#9197a0",
705
+ brand: "#546c8d",
706
+ brandHover: "#657e9f",
707
+ brandContrast: "#ffffff",
708
+ moneyIncome: "#00997f",
709
+ moneyExpense: "#b77b29",
710
+ moneyNet: "#9679c8",
711
+ moneyNeutral: "#a0a5ac",
712
+ chart: PAUL_TOL.dark,
713
+ heat: {
714
+ neutral: "#202123",
715
+ under: "#00997f",
716
+ over: "#b77b29",
717
+ seqLow: "#331f05",
718
+ seqHigh: "#b77b29",
719
+ empty: "rgba(160,170,190,0.10)",
720
+ },
721
+ },
722
+ },
723
+ ];
724
+
725
+ export const PALETTES: PalettePreset[] = [DEFAULT_PRESET, IMPRINT_PRESET, ...DERIVED_PRESETS];
726
+
727
+ export function presetById(id: string): PalettePreset {
728
+ return PALETTES.find((p) => p.id === id) ?? DEFAULT_PRESET;
729
+ }
730
+
731
+ /**
732
+ * Write a token set onto an element's inline style as CSS custom properties.
733
+ *
734
+ * This is the only writer, and there is no un-writer: `presetById` falls back to
735
+ * `DEFAULT_PRESET` and this then writes the full set, so returning to the default
736
+ * APPLIES it rather than clearing anything. A `clearTokenSet` and the hand-kept
737
+ * 24-name `TOKEN_VARS` list it looped over used to live here, both exported, both
738
+ * called by nobody in either consumer — a second copy of these property names with
739
+ * nothing comparing the two, so adding a token here and forgetting it there would
740
+ * have been silent and only a dead function would have noticed. The list is gone;
741
+ * the test beside this asserts instead that every field of a `TokenSet` reaches the
742
+ * element, which is what that list was standing in for.
743
+ */
744
+ export function applyTokenSet(el: HTMLElement, t: TokenSet): void {
745
+ el.style.setProperty("--bg-page", t.bgPage);
746
+ el.style.setProperty("--bg-surface", t.bgSurface);
747
+ el.style.setProperty("--bg-surface-2", t.bgSurface2);
748
+ el.style.setProperty("--border", t.border);
749
+ el.style.setProperty("--text-primary", t.textPrimary);
750
+ el.style.setProperty("--text-secondary", t.textSecondary);
751
+ el.style.setProperty("--text-muted", t.textMuted);
752
+ el.style.setProperty("--brand", t.brand);
753
+ el.style.setProperty("--brand-hover", t.brandHover);
754
+ el.style.setProperty("--brand-contrast", t.brandContrast);
755
+ el.style.setProperty("--money-income", t.moneyIncome);
756
+ el.style.setProperty("--money-expense", t.moneyExpense);
757
+ el.style.setProperty("--money-net", t.moneyNet);
758
+ el.style.setProperty("--money-neutral", t.moneyNeutral);
759
+ t.chart.forEach((c, i) => el.style.setProperty(`--chart-${i + 1}`, c));
760
+ }