@titan-design/react-ui 0.12.1 → 0.14.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 (531) hide show
  1. package/README.md +60 -47
  2. package/dist/bodymap.js +856 -736
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +858 -738
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +1431 -237
  7. package/dist/index.d.ts +1431 -237
  8. package/dist/index.js +6506 -4781
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +6280 -4642
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-B1hqnGIC.d.ts → pages-CsjsUn0G.d.ts} +51 -16
  13. package/dist/{pages-CvsFqNtx.d.mts → pages-ZbT_pD_U.d.mts} +51 -16
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +1919 -1937
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +1927 -1945
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +225 -140
  21. package/dist/theme/index.d.ts +225 -140
  22. package/dist/theme/index.js +298 -319
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +283 -315
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +145 -24
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +145 -24
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +128 -11
  31. package/dist/theme/tokens.d.ts +128 -11
  32. package/dist/theme/tokens.js +126 -26
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +124 -27
  35. package/dist/theme/tokens.mjs.map +1 -1
  36. package/docs/audits/2026-09-08-critique-addenda.md +51 -0
  37. package/docs/audits/2026-09-08-library-critique.md +212 -0
  38. package/docs/library-roadmap.md +99 -0
  39. package/docs/render-testing-pattern.md +130 -0
  40. package/package.json +1 -1
  41. package/src/arch/Architecture.stories.tsx +3 -1
  42. package/src/arch/arch-graph.freshness.test.ts +52 -0
  43. package/src/arch/arch-graph.json +4989 -1583
  44. package/src/components/custom/ActiveWork/CoChangeChip.stories.tsx +44 -0
  45. package/src/components/custom/ActiveWork/CoChangeChip.test.tsx +29 -0
  46. package/src/components/custom/ActiveWork/CoChangeChip.tsx +48 -0
  47. package/src/components/custom/ActiveWork/Eyebrow.stories.tsx +25 -0
  48. package/src/components/custom/ActiveWork/Eyebrow.test.tsx +16 -0
  49. package/src/components/custom/ActiveWork/Eyebrow.tsx +26 -0
  50. package/src/components/custom/ActiveWork/FileActivityDetail.stories.tsx +50 -0
  51. package/src/components/custom/ActiveWork/FileActivityDetail.test.tsx +66 -0
  52. package/src/components/custom/ActiveWork/FileActivityDetail.tsx +202 -0
  53. package/src/components/custom/ActiveWork/FileActivityRow.stories.tsx +70 -0
  54. package/src/components/custom/ActiveWork/FileActivityRow.test.tsx +67 -0
  55. package/src/components/custom/ActiveWork/FileActivityRow.tsx +130 -0
  56. package/src/components/custom/ActiveWork/FileHistoryExplorer.stories.tsx +84 -0
  57. package/src/components/custom/ActiveWork/FileHistoryExplorer.test.tsx +99 -0
  58. package/src/components/custom/ActiveWork/FileHistoryExplorer.tsx +138 -0
  59. package/src/components/custom/ActiveWork/FilePathLabel.stories.tsx +39 -0
  60. package/src/components/custom/ActiveWork/FilePathLabel.test.tsx +53 -0
  61. package/src/components/custom/ActiveWork/FilePathLabel.tsx +63 -0
  62. package/src/components/custom/ActiveWork/InitiativeBrief.stories.tsx +50 -0
  63. package/src/components/custom/ActiveWork/InitiativeBrief.test.tsx +62 -0
  64. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +182 -0
  65. package/src/components/custom/ActiveWork/InitiativeCard.stories.tsx +73 -0
  66. package/src/components/custom/ActiveWork/InitiativeCard.test.tsx +68 -0
  67. package/src/components/custom/ActiveWork/InitiativeCard.tsx +136 -0
  68. package/src/components/custom/ActiveWork/InitiativeHeader.stories.tsx +59 -0
  69. package/src/components/custom/ActiveWork/InitiativeHeader.test.tsx +40 -0
  70. package/src/components/custom/ActiveWork/InitiativeHeader.tsx +73 -0
  71. package/src/components/custom/ActiveWork/InitiativeReader.stories.tsx +145 -0
  72. package/src/components/custom/ActiveWork/OpenLoops.stories.tsx +58 -0
  73. package/src/components/custom/ActiveWork/OpenLoops.test.tsx +61 -0
  74. package/src/components/custom/ActiveWork/OpenLoops.tsx +107 -0
  75. package/src/components/custom/ActiveWork/PortfolioOverview.stories.tsx +101 -0
  76. package/src/components/custom/ActiveWork/PortfolioOverview.test.tsx +81 -0
  77. package/src/components/custom/ActiveWork/PortfolioOverview.tsx +83 -0
  78. package/src/components/custom/ActiveWork/README.md +305 -0
  79. package/src/components/custom/ActiveWork/SessionDetail.stories.tsx +71 -0
  80. package/src/components/custom/ActiveWork/SessionDetail.test.tsx +84 -0
  81. package/src/components/custom/ActiveWork/SessionDetail.tsx +180 -0
  82. package/src/components/custom/ActiveWork/SessionList.stories.tsx +69 -0
  83. package/src/components/custom/ActiveWork/SessionList.test.tsx +78 -0
  84. package/src/components/custom/ActiveWork/SessionList.tsx +103 -0
  85. package/src/components/custom/ActiveWork/SessionListItem.stories.tsx +64 -0
  86. package/src/components/custom/ActiveWork/SessionListItem.test.tsx +62 -0
  87. package/src/components/custom/ActiveWork/SessionListItem.tsx +152 -0
  88. package/src/components/custom/ActiveWork/SessionReader.stories.tsx +113 -0
  89. package/src/components/custom/ActiveWork/SeverityLabel.stories.tsx +51 -0
  90. package/src/components/custom/ActiveWork/SeverityLabel.test.tsx +68 -0
  91. package/src/components/custom/ActiveWork/SeverityLabel.tsx +97 -0
  92. package/src/components/custom/ActiveWork/TaskRow.stories.tsx +63 -0
  93. package/src/components/custom/ActiveWork/TaskRow.test.tsx +112 -0
  94. package/src/components/custom/ActiveWork/TaskRow.tsx +176 -0
  95. package/src/components/custom/ActiveWork/TaskTable.stories.tsx +123 -0
  96. package/src/components/custom/ActiveWork/TaskTable.test.tsx +233 -0
  97. package/src/components/custom/ActiveWork/TaskTable.tsx +321 -0
  98. package/src/components/custom/ActiveWork/active-work-depth.test.tsx +102 -0
  99. package/src/components/custom/ActiveWork/file-history-fixture.ts +91 -0
  100. package/src/components/custom/ActiveWork/format-time.test.ts +23 -0
  101. package/src/components/custom/ActiveWork/format-time.ts +32 -0
  102. package/src/components/custom/ActiveWork/index.ts +19 -0
  103. package/src/components/custom/ActiveWork/initiative-fixture.ts +104 -0
  104. package/src/components/custom/ActiveWork/session-fixture.ts +267 -0
  105. package/src/components/custom/ActiveWork/session-linkers.ts +56 -0
  106. package/src/components/custom/ActiveWork/task-list-fixture.ts +97 -0
  107. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +12 -6
  108. package/src/components/custom/DateTime/DateTime.stories.tsx +2 -1
  109. package/src/components/custom/DateTime/DateTime.test.tsx +3 -1
  110. package/src/components/custom/EmptyState/EmptyState.stories.tsx +22 -9
  111. package/src/components/custom/EmptyState/EmptyState.test.tsx +7 -13
  112. package/src/components/custom/EmptyState/EmptyState.tsx +4 -18
  113. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +4 -3
  114. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  115. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +4 -3
  116. package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +2 -2
  117. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +8 -4
  118. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +116 -6
  119. package/src/components/custom/Fatigue/LiveFatiguePanel.test.tsx +134 -0
  120. package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +54 -83
  121. package/src/components/custom/Fatigue/README.md +40 -3
  122. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  123. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +2 -1
  124. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +4 -3
  125. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  126. package/src/components/custom/Fatigue/fatigue-mock.test.ts +0 -5
  127. package/src/components/custom/Fatigue/fatigue-mock.ts +2 -10
  128. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -0
  129. package/src/components/custom/Fatigue/index.ts +17 -5
  130. package/src/components/custom/Fatigue/panel-layout.test.ts +149 -0
  131. package/src/components/custom/Fatigue/panel-layout.ts +185 -0
  132. package/src/components/custom/Gauge/Gauge.stories.tsx +6 -3
  133. package/src/components/custom/Gauge/Gauge.test.tsx +1 -1
  134. package/src/components/custom/Gauge/Gauge.tsx +7 -2
  135. package/src/components/custom/Metric/Metric.stories.tsx +10 -1
  136. package/src/components/custom/Metric/Metric.test.tsx +1 -3
  137. package/src/components/custom/Metric/Metric.tsx +5 -20
  138. package/src/components/custom/Prose/MarkdownProse.stories.tsx +88 -0
  139. package/src/components/custom/Prose/MarkdownProse.test.tsx +87 -0
  140. package/src/components/custom/Prose/MarkdownProse.tsx +201 -0
  141. package/src/components/custom/Prose/README.md +58 -0
  142. package/src/components/custom/Prose/index.ts +1 -0
  143. package/src/components/custom/Scatter/Scatter.stories.tsx +55 -14
  144. package/src/components/custom/Scatter/Scatter.test.tsx +2 -2
  145. package/src/components/custom/Scatter/Scatter.tsx +122 -18
  146. package/src/components/custom/Sidebar/Sidebar.stories.tsx +41 -8
  147. package/src/components/custom/Sidebar/Sidebar.test.tsx +5 -1
  148. package/src/components/custom/Sidebar/Sidebar.tsx +16 -33
  149. package/src/components/custom/Table/Table.stories.tsx +99 -16
  150. package/src/components/custom/Table/Table.test.tsx +273 -86
  151. package/src/components/custom/Table/Table.tsx +159 -67
  152. package/src/components/custom/Table/column-fit.test.ts +75 -0
  153. package/src/components/custom/Table/column-fit.ts +116 -0
  154. package/src/components/custom/Table/index.ts +9 -0
  155. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +8 -6
  156. package/src/components/custom/Treemap/Treemap.stories.tsx +15 -12
  157. package/src/components/custom/Treemap/Treemap.test.tsx +1 -1
  158. package/src/components/custom/Treemap/Treemap.tsx +11 -3
  159. package/src/components/custom/Typography/Typography.stories.tsx +28 -1
  160. package/src/components/custom/Typography/Typography.test.tsx +34 -0
  161. package/src/components/custom/Typography/Typography.tsx +6 -0
  162. package/src/components/custom/Typography/index.ts +1 -8
  163. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +11 -2
  164. package/src/components/custom/Workout/BaseBadge.stories.tsx +27 -11
  165. package/src/components/custom/Workout/BaseBadge.test.tsx +31 -11
  166. package/src/components/custom/Workout/BaseBadge.tsx +6 -2
  167. package/src/components/custom/Workout/BodyMap.stories.tsx +4 -1
  168. package/src/components/custom/Workout/BodyMap.tsx +8 -4
  169. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +28 -6
  170. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +11 -15
  171. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +50 -12
  172. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +12 -9
  173. package/src/components/custom/Workout/CapacityBandChart.test.tsx +24 -51
  174. package/src/components/custom/Workout/CapacityBandChart.tsx +144 -111
  175. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  176. package/src/components/custom/Workout/DeviationBar.test.tsx +10 -4
  177. package/src/components/custom/Workout/DeviationBar.tsx +24 -16
  178. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +183 -0
  179. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +32 -18
  180. package/src/components/custom/Workout/ExerciseCard.stories.tsx +12 -11
  181. package/src/components/custom/Workout/ExerciseCard.test.tsx +52 -22
  182. package/src/components/custom/Workout/ExerciseCard.tsx +99 -183
  183. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +95 -12
  184. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
  185. package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
  186. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +9 -1
  187. package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
  188. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +8 -8
  189. package/src/components/custom/Workout/ExerciseHeading.tsx +194 -46
  190. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +12 -9
  191. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +27 -11
  192. package/src/components/custom/Workout/ExerciseIndicator.tsx +14 -15
  193. package/src/components/custom/Workout/FatigueMeter.stories.tsx +5 -5
  194. package/src/components/custom/Workout/InputBar.stories.tsx +4 -5
  195. package/src/components/custom/Workout/InputBar.tsx +4 -2
  196. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  197. package/src/components/custom/Workout/IntensityBar.test.tsx +5 -1
  198. package/src/components/custom/Workout/IntensityBar.tsx +8 -3
  199. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +5 -4
  200. package/src/components/custom/Workout/MesoCard.stories.tsx +4 -1
  201. package/src/components/custom/Workout/MesoCard.test.tsx +7 -18
  202. package/src/components/custom/Workout/MesoCard.tsx +14 -10
  203. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  204. package/src/components/custom/Workout/MesoProgressBar.test.tsx +18 -11
  205. package/src/components/custom/Workout/MesoProgressBar.tsx +15 -13
  206. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +2 -1
  207. package/src/components/custom/Workout/MesoStatusCard.test.tsx +14 -44
  208. package/src/components/custom/Workout/MesoStatusCard.tsx +20 -33
  209. package/src/components/custom/Workout/MetricTiles.stories.tsx +3 -2
  210. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +2 -8
  211. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +23 -31
  212. package/src/components/custom/Workout/MuscleGroupChip.tsx +28 -67
  213. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +9 -4
  214. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +17 -10
  215. package/src/components/custom/Workout/PlaceholderStrip.tsx +9 -8
  216. package/src/components/custom/Workout/PrBadge.stories.tsx +2 -1
  217. package/src/components/custom/Workout/PrBadge.test.tsx +3 -9
  218. package/src/components/custom/Workout/PrBadge.tsx +13 -17
  219. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +16 -3
  220. package/src/components/custom/Workout/PrHistoryModal.test.tsx +13 -36
  221. package/src/components/custom/Workout/PrHistoryModal.tsx +8 -4
  222. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +6 -1
  223. package/src/components/custom/Workout/README.md +68 -17
  224. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +3 -2
  225. package/src/components/custom/Workout/ReadinessCheck.test.tsx +12 -18
  226. package/src/components/custom/Workout/ReadinessCheck.tsx +22 -4
  227. package/src/components/custom/Workout/RestTimer.stories.tsx +16 -4
  228. package/src/components/custom/Workout/RestTimer.tsx +4 -2
  229. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +3 -2
  230. package/src/components/custom/Workout/ScheduleTiles.test.tsx +3 -4
  231. package/src/components/custom/Workout/ScheduleTiles.tsx +4 -7
  232. package/src/components/custom/Workout/SegmentedBar.stories.tsx +21 -17
  233. package/src/components/custom/Workout/SegmentedBar.tsx +10 -5
  234. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +5 -5
  235. package/src/components/custom/Workout/SessionHeader.stories.tsx +6 -6
  236. package/src/components/custom/Workout/SessionHeader.tsx +12 -12
  237. package/src/components/custom/Workout/SessionRail.stories.tsx +12 -11
  238. package/src/components/custom/Workout/SessionRail.tsx +7 -9
  239. package/src/components/custom/Workout/SetBar.stories.tsx +10 -6
  240. package/src/components/custom/Workout/SetBar.test.tsx +49 -4
  241. package/src/components/custom/Workout/SetBar.tsx +38 -12
  242. package/src/components/custom/Workout/SetRow.stories.tsx +3 -2
  243. package/src/components/custom/Workout/SetRow.test.tsx +16 -2
  244. package/src/components/custom/Workout/SetRow.tsx +61 -23
  245. package/src/components/custom/Workout/SetStrip.stories.tsx +63 -7
  246. package/src/components/custom/Workout/SetStrip.test.tsx +25 -0
  247. package/src/components/custom/Workout/SetStrip.tsx +24 -4
  248. package/src/components/custom/Workout/SetTableHeader.stories.tsx +6 -6
  249. package/src/components/custom/Workout/SetTableHeader.tsx +9 -12
  250. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +7 -7
  251. package/src/components/custom/Workout/SetsRepsLoad.tsx +24 -12
  252. package/src/components/custom/Workout/Sparkline.stories.tsx +14 -11
  253. package/src/components/custom/Workout/Sparkline.test.tsx +37 -19
  254. package/src/components/custom/Workout/Sparkline.tsx +14 -18
  255. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  256. package/src/components/custom/Workout/StatusDot.test.tsx +44 -5
  257. package/src/components/custom/Workout/StatusDot.tsx +93 -63
  258. package/src/components/custom/Workout/StatusPill.stories.tsx +1 -1
  259. package/src/components/custom/Workout/StatusPill.test.tsx +6 -15
  260. package/src/components/custom/Workout/StatusPill.tsx +27 -38
  261. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +11 -4
  262. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +15 -37
  263. package/src/components/custom/Workout/StrengthTrendChart.tsx +39 -36
  264. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +27 -64
  265. package/src/components/custom/Workout/SupersetWrapper.test.tsx +16 -14
  266. package/src/components/custom/Workout/SupersetWrapper.tsx +17 -21
  267. package/src/components/custom/Workout/TempoDisplay.stories.tsx +26 -10
  268. package/src/components/custom/Workout/TempoDisplay.test.tsx +18 -4
  269. package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
  270. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +9 -1
  271. package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +4 -1
  272. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +10 -6
  273. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +5 -2
  274. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +44 -1
  275. package/src/components/custom/Workout/VelocityStrip.stories.tsx +12 -6
  276. package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
  277. package/src/components/custom/Workout/VelocityStrip.tsx +26 -14
  278. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +6 -7
  279. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +15 -6
  280. package/src/components/custom/Workout/WeekRow.stories.tsx +4 -1
  281. package/src/components/custom/Workout/WeekRow.test.tsx +18 -15
  282. package/src/components/custom/Workout/WeekRow.tsx +23 -16
  283. package/src/components/custom/Workout/WeightBadge.stories.tsx +2 -1
  284. package/src/components/custom/Workout/WeightBadge.test.tsx +50 -19
  285. package/src/components/custom/Workout/WeightBadge.tsx +28 -27
  286. package/src/components/custom/Workout/WorkoutCard.stories.tsx +6 -1
  287. package/src/components/custom/Workout/WorkoutCard.test.tsx +18 -0
  288. package/src/components/custom/Workout/WorkoutCard.tsx +35 -57
  289. package/src/components/custom/Workout/WorkoutPill.stories.tsx +5 -2
  290. package/src/components/custom/Workout/WorkoutPill.test.tsx +39 -7
  291. package/src/components/custom/Workout/WorkoutPill.tsx +88 -57
  292. package/src/components/custom/Workout/ZoneTrack.stories.tsx +10 -7
  293. package/src/components/custom/Workout/ZoneTrack.tsx +10 -9
  294. package/src/components/custom/Workout/exerciseRowState.ts +29 -0
  295. package/src/components/custom/Workout/index.ts +17 -2
  296. package/src/components/custom/Workout/metricText.stories.tsx +15 -13
  297. package/src/components/custom/Workout/metricText.tsx +8 -2
  298. package/src/components/custom/Workout/setHeadingKit.tsx +219 -36
  299. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  300. package/src/components/custom/charts/README.md +66 -0
  301. package/src/components/custom/charts/SetBarChart.test.tsx +46 -1
  302. package/src/components/custom/charts/SetBarChart.tsx +56 -9
  303. package/src/components/custom/charts/SparkBars.stories.tsx +55 -0
  304. package/src/components/custom/charts/SparkBars.test.tsx +69 -0
  305. package/src/components/custom/charts/SparkBars.tsx +85 -0
  306. package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
  307. package/src/components/custom/charts/flatBarGeometry.ts +32 -0
  308. package/src/components/custom/charts/index.ts +3 -0
  309. package/src/components/custom/index.ts +3 -0
  310. package/src/components/custom/stepper/Stepper.stories.tsx +13 -10
  311. package/src/components/custom/stepper/Stepper.test.tsx +1 -7
  312. package/src/components/custom/stepper/Stepper.tsx +21 -34
  313. package/src/components/icons/icons.stories.tsx +1 -1
  314. package/src/components/icons/icons.tsx +51 -0
  315. package/src/components/shell/AppShell.stories.tsx +150 -0
  316. package/src/components/shell/AppShell.test.tsx +70 -0
  317. package/src/components/shell/AppShell.tsx +90 -0
  318. package/src/components/shell/BrandLockup.stories.tsx +66 -5
  319. package/src/components/shell/BrandLockup.test.tsx +34 -1
  320. package/src/components/shell/BrandLockup.tsx +27 -11
  321. package/src/components/shell/NavItem.stories.tsx +4 -4
  322. package/src/components/shell/NavItem.test.tsx +17 -0
  323. package/src/components/shell/NavItem.tsx +17 -5
  324. package/src/components/shell/README.md +89 -26
  325. package/src/components/shell/SideNav.stories.tsx +38 -7
  326. package/src/components/shell/SideNav.test.tsx +16 -8
  327. package/src/components/shell/SideNav.tsx +17 -17
  328. package/src/components/shell/TopBar.stories.tsx +44 -19
  329. package/src/components/shell/TopBar.test.tsx +34 -12
  330. package/src/components/shell/TopBar.tsx +61 -40
  331. package/src/components/shell/brands.tsx +66 -0
  332. package/src/components/shell/index.ts +9 -8
  333. package/src/components/shell/{DeviceIndicator.stories.tsx → workout/DeviceIndicator.stories.tsx} +2 -2
  334. package/src/components/shell/{DeviceIndicator.tsx → workout/DeviceIndicator.tsx} +2 -2
  335. package/src/components/shell/{DeviceMenu.stories.tsx → workout/DeviceMenu.stories.tsx} +2 -2
  336. package/src/components/shell/{DeviceMenu.tsx → workout/DeviceMenu.tsx} +3 -3
  337. package/src/components/shell/{DeviceRow.stories.tsx → workout/DeviceRow.stories.tsx} +2 -2
  338. package/src/components/shell/{DeviceRow.tsx → workout/DeviceRow.tsx} +3 -3
  339. package/src/components/shell/{SessionStatePill.stories.tsx → workout/SessionStatePill.stories.tsx} +2 -2
  340. package/src/components/shell/{SessionStatePill.tsx → workout/SessionStatePill.tsx} +5 -3
  341. package/src/components/shell/workout/WorkoutShell.stories.tsx +71 -0
  342. package/src/components/shell/{DashboardShell.test.tsx → workout/WorkoutShell.test.tsx} +19 -8
  343. package/src/components/shell/workout/WorkoutShell.tsx +77 -0
  344. package/src/components/shell/workout/WorkoutTopBar.stories.tsx +58 -0
  345. package/src/components/shell/workout/WorkoutTopBar.test.tsx +39 -0
  346. package/src/components/shell/workout/WorkoutTopBar.tsx +52 -0
  347. package/src/components/shell/workout/index.ts +9 -0
  348. package/src/components/shell/workout/workoutNavItems.tsx +16 -0
  349. package/src/components/ui/README.md +110 -0
  350. package/src/components/ui/alert/Alert.stories.tsx +15 -5
  351. package/src/components/ui/alert/Alert.tsx +24 -3
  352. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +11 -13
  353. package/src/components/ui/autocomplete/Autocomplete.test.tsx +7 -25
  354. package/src/components/ui/autocomplete/Autocomplete.tsx +21 -28
  355. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  356. package/src/components/ui/avatar/Avatar.tsx +2 -10
  357. package/src/components/ui/badge/Badge.stories.tsx +83 -28
  358. package/src/components/ui/badge/Badge.test.tsx +19 -3
  359. package/src/components/ui/badge/Badge.tsx +48 -55
  360. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  361. package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +4 -16
  362. package/src/components/ui/button/Button.stories.tsx +46 -12
  363. package/src/components/ui/button/Button.tsx +44 -37
  364. package/src/components/ui/card/Card.stories.tsx +90 -94
  365. package/src/components/ui/card/Card.test.tsx +80 -6
  366. package/src/components/ui/card/Card.tsx +150 -203
  367. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  368. package/src/components/ui/checkbox/Checkbox.tsx +7 -27
  369. package/src/components/ui/chip/Chip.stories.tsx +37 -13
  370. package/src/components/ui/chip/Chip.test.tsx +10 -6
  371. package/src/components/ui/chip/Chip.tsx +76 -91
  372. package/src/components/ui/collapse/Collapse.stories.tsx +23 -23
  373. package/src/components/ui/collapse/Collapse.tsx +6 -11
  374. package/src/components/ui/data-row/DataRow.stories.tsx +14 -3
  375. package/src/components/ui/data-row/DataRow.test.tsx +5 -15
  376. package/src/components/ui/data-row/DataRow.tsx +18 -7
  377. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  378. package/src/components/ui/divider/Divider.tsx +1 -5
  379. package/src/components/ui/drawer/Drawer.stories.tsx +5 -16
  380. package/src/components/ui/drawer/Drawer.tsx +13 -27
  381. package/src/components/ui/floating-overlay-depth.test.tsx +143 -0
  382. package/src/components/ui/form-field/FormField.stories.tsx +6 -20
  383. package/src/components/ui/form-field/FormField.test.tsx +1 -5
  384. package/src/components/ui/form-field/FormField.tsx +10 -46
  385. package/src/components/ui/help-tip/HelpTip.stories.tsx +8 -19
  386. package/src/components/ui/help-tip/HelpTip.test.tsx +1 -3
  387. package/src/components/ui/help-tip/HelpTip.tsx +21 -22
  388. package/src/components/ui/help-tip/index.ts +7 -1
  389. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  390. package/src/components/ui/icon-box/IconBox.test.tsx +1 -7
  391. package/src/components/ui/icon-box/IconBox.tsx +10 -18
  392. package/src/components/ui/indicator/Indicator.stories.tsx +5 -4
  393. package/src/components/ui/indicator/Indicator.tsx +20 -14
  394. package/src/components/ui/input/Input.stories.tsx +3 -11
  395. package/src/components/ui/input/Input.test.tsx +2 -10
  396. package/src/components/ui/input/Input.tsx +10 -25
  397. package/src/components/ui/input/index.ts +7 -1
  398. package/src/components/ui/link/Link.stories.tsx +19 -7
  399. package/src/components/ui/list-item/ListItem.stories.tsx +17 -5
  400. package/src/components/ui/list-item/ListItem.tsx +2 -9
  401. package/src/components/ui/menu/Menu.stories.tsx +21 -15
  402. package/src/components/ui/menu/Menu.test.tsx +2 -11
  403. package/src/components/ui/menu/Menu.tsx +12 -14
  404. package/src/components/ui/menu/index.ts +8 -1
  405. package/src/components/ui/modal/Modal.stories.tsx +2 -2
  406. package/src/components/ui/modal/Modal.tsx +31 -19
  407. package/src/components/ui/pill/Pill.stories.tsx +111 -23
  408. package/src/components/ui/pill/Pill.test.tsx +101 -3
  409. package/src/components/ui/pill/Pill.tsx +168 -49
  410. package/src/components/ui/pill/index.ts +1 -1
  411. package/src/components/ui/popover/Popover.stories.tsx +6 -8
  412. package/src/components/ui/popover/Popover.test.tsx +1 -6
  413. package/src/components/ui/popover/Popover.tsx +20 -15
  414. package/src/components/ui/popover/index.ts +7 -1
  415. package/src/components/ui/progress/Progress.stories.tsx +3 -12
  416. package/src/components/ui/progress/Progress.test.tsx +1 -5
  417. package/src/components/ui/progress/Progress.tsx +9 -6
  418. package/src/components/ui/radio/Radio.stories.tsx +42 -21
  419. package/src/components/ui/radio/Radio.test.tsx +6 -2
  420. package/src/components/ui/radio/Radio.tsx +23 -22
  421. package/src/components/ui/section/Section.stories.tsx +12 -21
  422. package/src/components/ui/section/Section.test.tsx +1 -6
  423. package/src/components/ui/section/Section.tsx +1 -3
  424. package/src/components/ui/section/index.ts +1 -5
  425. package/src/components/ui/select/Select.stories.tsx +3 -13
  426. package/src/components/ui/select/Select.test.tsx +2 -11
  427. package/src/components/ui/select/Select.tsx +16 -15
  428. package/src/components/ui/skeleton/Skeleton.stories.tsx +3 -11
  429. package/src/components/ui/skeleton/Skeleton.tsx +3 -14
  430. package/src/components/ui/skeleton/index.ts +1 -7
  431. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  432. package/src/components/ui/spinner/Spinner.tsx +4 -5
  433. package/src/components/ui/stack/Stack.stories.tsx +8 -8
  434. package/src/components/ui/stack/Stack.tsx +1 -1
  435. package/src/components/ui/surface/Surface.stories.tsx +92 -33
  436. package/src/components/ui/surface/Surface.test.tsx +88 -28
  437. package/src/components/ui/surface/Surface.tsx +37 -87
  438. package/src/components/ui/surface/SurfaceContext.ts +22 -62
  439. package/src/components/ui/surface/index.ts +8 -0
  440. package/src/components/ui/surface/resolveSurface.ts +98 -0
  441. package/src/components/ui/surface/surface.contract.test.ts +6 -1
  442. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  443. package/src/components/ui/switch/Switch.tsx +7 -6
  444. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  445. package/src/components/ui/tabs/Tabs.tsx +8 -21
  446. package/src/components/ui/tabs/index.ts +1 -7
  447. package/src/components/ui/tile/Tile.stories.tsx +1 -1
  448. package/src/components/ui/tile/Tile.tsx +3 -0
  449. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  450. package/src/components/ui/toast/Toast.test.tsx +2 -8
  451. package/src/components/ui/toast/Toast.tsx +49 -57
  452. package/src/components/ui/toast/index.ts +0 -1
  453. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +92 -71
  454. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +3 -18
  455. package/src/components/ui/toolbar-button/ToolbarButton.tsx +38 -49
  456. package/src/components/ui/tooltip/Tooltip.stories.tsx +12 -3
  457. package/src/components/ui/tooltip/Tooltip.test.tsx +21 -3
  458. package/src/components/ui/tooltip/Tooltip.tsx +55 -36
  459. package/src/components/ui/tooltip/index.ts +1 -1
  460. package/src/components/ui/trigger/TriggerSurface.test.tsx +215 -0
  461. package/src/components/ui/trigger/TriggerSurface.tsx +81 -0
  462. package/src/components/ui/trigger/index.ts +2 -0
  463. package/src/stories/PresetShowcase.stories.tsx +101 -48
  464. package/src/test/no-device-internals.test.ts +3 -3
  465. package/src/test/no-raw-color.test.ts +39 -0
  466. package/src/theme/Alpha.stories.tsx +103 -0
  467. package/src/theme/ChoosingTokens.stories.tsx +48 -0
  468. package/src/theme/ColorPalettes.stories.tsx +62 -45
  469. package/src/theme/ColorPrimitives.stories.tsx +35 -35
  470. package/src/theme/Depth.stories.tsx +132 -65
  471. package/src/theme/DepthCalibration.stories.tsx +23 -21
  472. package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
  473. package/src/theme/ThemeProvider.test.tsx +4 -4
  474. package/src/theme/barrel.ts +7 -6
  475. package/src/theme/color-stories.coverage.test.ts +1 -1
  476. package/src/theme/color-utils.ts +41 -29
  477. package/src/theme/config.completeness.test.ts +1 -1
  478. package/src/theme/config.ts +40 -0
  479. package/src/theme/elevation.test.ts +82 -56
  480. package/src/theme/elevation.ts +77 -561
  481. package/src/theme/global.css +45 -5
  482. package/src/theme/grey-ramp.test.ts +24 -8
  483. package/src/theme/lift.test.ts +39 -0
  484. package/src/theme/lift.ts +102 -0
  485. package/src/theme/materials.test.ts +5 -2
  486. package/src/theme/materials.ts +38 -4
  487. package/src/theme/native-theming-guard.test.ts +5 -5
  488. package/src/theme/presets/apply.ts +4 -0
  489. package/src/theme/presets/audiobook.ts +5 -2
  490. package/src/theme/presets/default.ts +4 -0
  491. package/src/theme/presets/index.ts +14 -1
  492. package/src/theme/presets/types.ts +1 -1
  493. package/src/theme/semantic-contrast.test.ts +13 -4
  494. package/src/theme/status-distinctness.test.ts +66 -0
  495. package/src/theme/subtle-tone-weight.test.ts +163 -0
  496. package/src/theme/surface-planes.test.ts +37 -0
  497. package/src/theme/surface-planes.ts +68 -0
  498. package/src/theme/tailwind-var-opacity.test.ts +163 -0
  499. package/src/theme/token-export-guard.test.ts +1 -3
  500. package/src/theme/tokens/primitives.test.ts +15 -7
  501. package/src/theme/tokens/primitives.ts +98 -33
  502. package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
  503. package/src/theme/tokens/semantic.ts +124 -54
  504. package/src/theme/wash-ladder.test.ts +58 -0
  505. package/src/theme/workout-tokens.ts +1 -7
  506. package/src/types/jest-axe.d.ts +2 -6
  507. package/src/types/raw-md.d.ts +5 -0
  508. package/src/utils/colors.alpha-adoption.test.ts +408 -0
  509. package/src/utils/colors.ts +4 -4
  510. package/src/utils/form.ts +1 -3
  511. package/src/utils/index.ts +2 -7
  512. package/src/utils/number-format.ts +17 -0
  513. package/src/utils/workout-format.ts +15 -6
  514. package/src/web-jsx/jsx-runtime.ts +1 -5
  515. package/tailwind.config.js +55 -9
  516. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +0 -151
  517. package/src/components/custom/Workout/RowInventory.stories.tsx +0 -408
  518. package/src/components/custom/Workout/S3FullRail.stories.tsx +0 -46
  519. package/src/components/custom/Workout/S3SessionPace.stories.tsx +0 -95
  520. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +0 -61
  521. package/src/components/custom/Workout/S3SetTypes.stories.tsx +0 -176
  522. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +0 -240
  523. package/src/components/shell/DashboardShell.stories.tsx +0 -65
  524. package/src/components/shell/DashboardShell.tsx +0 -75
  525. package/src/components/shell/SessionRailSpecimen.stories.tsx +0 -753
  526. package/src/theme/elevation.stories.tsx +0 -346
  527. package/src/utils/useTheme.ts +0 -72
  528. /package/src/components/shell/{DeviceIndicator.test.tsx → workout/DeviceIndicator.test.tsx} +0 -0
  529. /package/src/components/shell/{DeviceMenu.test.tsx → workout/DeviceMenu.test.tsx} +0 -0
  530. /package/src/components/shell/{DeviceRow.test.tsx → workout/DeviceRow.test.tsx} +0 -0
  531. /package/src/components/shell/{SessionStatePill.test.tsx → workout/SessionStatePill.test.tsx} +0 -0
@@ -19,10 +19,14 @@
19
19
  --color-brand-primary-light: #FFA063;
20
20
  --color-brand-primary-dark: #DA5F00;
21
21
  --color-brand-primary-subtle: rgba(255, 121, 0, 0.12);
22
+ --color-brand-primary-muted: rgba(255, 121, 0, 0.30);
23
+ --color-brand-primary-strong: rgba(255, 121, 0, 0.50);
22
24
  --color-brand-secondary: #307B9B;
23
25
  --color-brand-secondary-light: #2697B7;
24
26
  --color-brand-secondary-dark: #2A617F;
25
- --color-brand-secondary-subtle: rgba(48, 123, 155, 0.12);
27
+ --color-brand-secondary-subtle: rgba(34, 211, 238, 0.12);
28
+ --color-brand-secondary-muted: rgba(48, 123, 155, 0.30);
29
+ --color-brand-secondary-strong: rgba(48, 123, 155, 0.50);
26
30
  --color-brand-primary-hover: #FFA063;
27
31
  --color-brand-primary-active: #FFC7A2;
28
32
  --color-brand-secondary-hover: #2697B7;
@@ -30,36 +34,52 @@
30
34
 
31
35
  --color-on-brand-primary: #FFFFFF;
32
36
  --color-on-brand-secondary: #FFFFFF;
37
+ --color-on-brand-primary-subtle: #FF7900;
38
+ --color-on-brand-secondary-subtle: #22D3EE;
33
39
 
34
40
  --color-status-success: #2ED573;
35
41
  --color-status-success-light: #58F69E;
36
42
  --color-status-success-dark: #298732;
37
43
  --color-status-success-subtle: rgba(46, 213, 115, 0.12);
44
+ --color-status-success-muted: rgba(46, 213, 115, 0.30);
45
+ --color-status-success-strong: rgba(46, 213, 115, 0.50);
38
46
  --color-status-live: #2ED573;
39
47
  --color-status-live-muted: #22A444;
40
48
  --color-status-error: #D14343;
41
49
  --color-status-error-light: #E05254;
42
50
  --color-status-error-dark: #A4221C;
43
- --color-status-error-subtle: rgba(209, 67, 67, 0.12);
44
- --color-status-error-vivid: #D14343;
51
+ --color-status-error-subtle: rgba(247, 113, 117, 0.08);
52
+ --color-status-error-muted: rgba(209, 67, 67, 0.30);
53
+ --color-status-error-strong: rgba(209, 67, 67, 0.50);
54
+ --color-status-error-vivid: #FF4757;
45
55
  --color-status-error-vivid-light: #E05254;
46
56
  --color-status-error-vivid-dark: #A4221C;
47
57
  --color-status-error-vivid-subtle: rgba(255, 71, 87, 0.12);
58
+ --color-status-error-vivid-muted: rgba(255, 71, 87, 0.30);
59
+ --color-status-error-vivid-strong: rgba(255, 71, 87, 0.50);
48
60
  --color-status-error-vivid-rgb: 255, 71, 87;
49
61
  --color-border-prominent: #424140;
50
62
  --color-status-warning: #F9B415;
51
63
  --color-status-warning-light: #FFD352;
52
64
  --color-status-warning-dark: #C27400;
53
65
  --color-status-warning-subtle: rgba(249, 180, 21, 0.12);
66
+ --color-status-warning-muted: rgba(249, 180, 21, 0.30);
67
+ --color-status-warning-strong: rgba(249, 180, 21, 0.50);
54
68
  --color-status-info: #2196F3;
55
69
  --color-status-info-light: #78C2FF;
56
70
  --color-status-info-dark: #1072CB;
57
- --color-status-info-subtle: rgba(33, 150, 243, 0.12);
71
+ --color-status-info-subtle: rgba(120, 194, 255, 0.12);
72
+ --color-status-info-muted: rgba(33, 150, 243, 0.30);
73
+ --color-status-info-strong: rgba(33, 150, 243, 0.50);
58
74
 
59
75
  --color-on-status-success: #FFFFFF;
60
76
  --color-on-status-error: #FFFFFF;
61
77
  --color-on-status-warning: #FFFFFF;
62
78
  --color-on-status-info: #FFFFFF;
79
+ --color-on-status-success-subtle: #2ED573;
80
+ --color-on-status-error-subtle: #F77175;
81
+ --color-on-status-warning-subtle: #F9B415;
82
+ --color-on-status-info-subtle: #78C2FF;
63
83
 
64
84
  --color-result-improve: #4caf50;
65
85
  --color-result-improve-light: rgba(76, 175, 80, 0.16);
@@ -171,10 +191,14 @@
171
191
  --color-brand-primary-light: #FFA063;
172
192
  --color-brand-primary-dark: #DA5F00;
173
193
  --color-brand-primary-subtle: rgba(255, 121, 0, 0.08);
194
+ --color-brand-primary-muted: rgba(255, 121, 0, 0.30);
195
+ --color-brand-primary-strong: rgba(255, 121, 0, 0.50);
174
196
  --color-brand-secondary: #307B9B;
175
197
  --color-brand-secondary-light: #2697B7;
176
198
  --color-brand-secondary-dark: #2A617F;
177
199
  --color-brand-secondary-subtle: rgba(48, 123, 155, 0.08);
200
+ --color-brand-secondary-muted: rgba(48, 123, 155, 0.30);
201
+ --color-brand-secondary-strong: rgba(48, 123, 155, 0.50);
178
202
  --color-brand-primary-hover: #DA5F00;
179
203
  --color-brand-primary-active: #B94A00;
180
204
  --color-brand-secondary-hover: #2A617F;
@@ -182,36 +206,52 @@
182
206
 
183
207
  --color-on-brand-primary: #FFFFFF;
184
208
  --color-on-brand-secondary: #FFFFFF;
209
+ --color-on-brand-primary-subtle: #FF7900;
210
+ --color-on-brand-secondary-subtle: #307B9B;
185
211
 
186
212
  --color-status-success: #2ED573;
187
213
  --color-status-success-light: #58F69E;
188
214
  --color-status-success-dark: #298732;
189
215
  --color-status-success-subtle: #E3FFEE;
216
+ --color-status-success-muted: rgba(46, 213, 115, 0.30);
217
+ --color-status-success-strong: rgba(46, 213, 115, 0.50);
190
218
  --color-status-live: #2ED573;
191
219
  --color-status-live-muted: #22A444;
192
220
  --color-status-error: #D14343;
193
221
  --color-status-error-light: #E05254;
194
222
  --color-status-error-dark: #A4221C;
195
223
  --color-status-error-subtle: #FFF4F4;
196
- --color-status-error-vivid: #D14343;
224
+ --color-status-error-muted: rgba(209, 67, 67, 0.30);
225
+ --color-status-error-strong: rgba(209, 67, 67, 0.50);
226
+ --color-status-error-vivid: #FF4757;
197
227
  --color-status-error-vivid-light: #E05254;
198
228
  --color-status-error-vivid-dark: #A4221C;
199
229
  --color-status-error-vivid-subtle: rgba(255, 71, 87, 0.12);
230
+ --color-status-error-vivid-muted: rgba(255, 71, 87, 0.30);
231
+ --color-status-error-vivid-strong: rgba(255, 71, 87, 0.50);
200
232
  --color-status-error-vivid-rgb: 255, 71, 87;
201
233
  --color-border-prominent: #A29F9D;
202
234
  --color-status-warning: #F9B415;
203
235
  --color-status-warning-light: #FFD352;
204
236
  --color-status-warning-dark: #C27400;
205
237
  --color-status-warning-subtle: #FFF7DD;
238
+ --color-status-warning-muted: rgba(249, 180, 21, 0.30);
239
+ --color-status-warning-strong: rgba(249, 180, 21, 0.50);
206
240
  --color-status-info: #2196F3;
207
241
  --color-status-info-light: #78C2FF;
208
242
  --color-status-info-dark: #1072CB;
209
243
  --color-status-info-subtle: #EFF8FF;
244
+ --color-status-info-muted: rgba(33, 150, 243, 0.30);
245
+ --color-status-info-strong: rgba(33, 150, 243, 0.50);
210
246
 
211
247
  --color-on-status-success: #FFFFFF;
212
248
  --color-on-status-error: #FFFFFF;
213
249
  --color-on-status-warning: #FFFFFF;
214
250
  --color-on-status-info: #FFFFFF;
251
+ --color-on-status-success-subtle: #2ED573;
252
+ --color-on-status-error-subtle: #D14343;
253
+ --color-on-status-warning-subtle: #F9B415;
254
+ --color-on-status-info-subtle: #2196F3;
215
255
 
216
256
  --color-result-improve: #4caf50;
217
257
  --color-result-improve-light: rgba(76, 175, 80, 0.12);
@@ -11,7 +11,6 @@ import { execFileSync } from 'node:child_process'
11
11
  import { join } from 'node:path'
12
12
  import { greyRamp, SURFACE_PLANE_STEPS, primitiveRamps } from './tokens/primitives'
13
13
 
14
-
15
14
  // ── colour math (CIELAB D65 + WCAG relative luminance) ──────────────────────
16
15
  const hex2rgb = (h: string): [number, number, number] => {
17
16
  const n = parseInt(h.slice(1), 16)
@@ -42,9 +41,15 @@ const warmth = (hex: string) => {
42
41
 
43
42
  /** Machado-2009 dichromacy, severity 1.0. */
44
43
  const CVD = {
45
- deuteranopia: [0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881],
46
- protanopia: [0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998],
47
- tritanopia: [1.255528, -0.076749, -0.178779, -0.078411, 0.930809, 0.147602, 0.004733, 0.691367, 0.3039],
44
+ deuteranopia: [
45
+ 0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881,
46
+ ],
47
+ protanopia: [
48
+ 0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998,
49
+ ],
50
+ tritanopia: [
51
+ 1.255528, -0.076749, -0.178779, -0.078411, 0.930809, 0.147602, 0.004733, 0.691367, 0.3039,
52
+ ],
48
53
  } as const
49
54
  function simulateCvd(m: readonly number[], hex: string): string {
50
55
  const [r, g, b] = hex2rgb(hex)
@@ -56,13 +61,19 @@ function simulateCvd(m: readonly number[], hex: string): string {
56
61
  return (
57
62
  '#' +
58
63
  out
59
- .map((v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0'))
64
+ .map((v) =>
65
+ Math.round(Math.max(0, Math.min(255, v)))
66
+ .toString(16)
67
+ .padStart(2, '0')
68
+ )
60
69
  .join('')
61
70
  .toUpperCase()
62
71
  )
63
72
  }
64
73
 
65
- const STEPS = Object.keys(greyRamp).map(Number).sort((a, b) => a - b)
74
+ const STEPS = Object.keys(greyRamp)
75
+ .map(Number)
76
+ .sort((a, b) => a - b)
66
77
  const hexAt = (step: number) => greyRamp[step as keyof typeof greyRamp]
67
78
 
68
79
  describe('greyRamp — shape', () => {
@@ -82,7 +93,9 @@ describe('greyRamp — shape', () => {
82
93
  it('has no two steps a reader could not tell apart', () => {
83
94
  for (let i = 1; i < STEPS.length; i++) {
84
95
  const d = dE76(hexAt(STEPS[i - 1]), hexAt(STEPS[i]))
85
- expect(d, `steps ${STEPS[i - 1]}/${STEPS[i]} collapse at ΔE ${d.toFixed(2)}`).toBeGreaterThan(1.5)
96
+ expect(d, `steps ${STEPS[i - 1]}/${STEPS[i]} collapse at ΔE ${d.toFixed(2)}`).toBeGreaterThan(
97
+ 1.5
98
+ )
86
99
  }
87
100
  })
88
101
 
@@ -150,7 +163,10 @@ describe('greyRamp — accessibility gate (TD-07.15)', () => {
150
163
  // Adjacent steps must stay separable AFTER simulation, or the depth
151
164
  // ordering of the surface stack stops reading for those viewers.
152
165
  for (let i = 1; i < STEPS.length; i++) {
153
- const d = dE76(simulateCvd(matrix, hexAt(STEPS[i - 1])), simulateCvd(matrix, hexAt(STEPS[i])))
166
+ const d = dE76(
167
+ simulateCvd(matrix, hexAt(STEPS[i - 1])),
168
+ simulateCvd(matrix, hexAt(STEPS[i]))
169
+ )
154
170
  expect(d, `${mode}: grey-${STEPS[i - 1]}/${STEPS[i]} merge`).toBeGreaterThan(1.5)
155
171
  }
156
172
  }
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { liftShadow, liftStyle, LIFT_RIM_ALPHA, FLOATING_LIFT_MIN } from './lift'
3
+
4
+ describe('liftShadow', () => {
5
+ it('leads with the card rim on dark: one grade under the paperSheet hero rim', () => {
6
+ expect(liftShadow(1, 'dark')).toMatch(/^inset 0 1px 0 rgba\(255,255,255,0\.12\)/)
7
+ expect(LIFT_RIM_ALPHA.dark).toBe(0.12)
8
+ })
9
+
10
+ it('omits the rim when asked, keeping the ambient layers', () => {
11
+ const shadow = liftShadow(2, 'dark', { rim: 0 })
12
+ expect(shadow).not.toContain('inset')
13
+ expect(shadow.split(', ').length).toBe(2)
14
+ })
15
+
16
+ it('honours a rim override (a hero surface can ask for the paperSheet rim)', () => {
17
+ expect(liftShadow(2, 'dark', { rim: 0.2 })).toContain('rgba(255,255,255,0.20)')
18
+ })
19
+
20
+ it('casts a lighter shadow on a light surface, same geometry', () => {
21
+ const dark = liftShadow(3, 'dark', { rim: 0 }).split(', ')
22
+ const light = liftShadow(3, 'light', { rim: 0 }).split(', ')
23
+ expect(light.length).toBe(dark.length)
24
+ const alpha = (layer: string) => Number(/rgba\(0,0,0,([\d.]+)\)/.exec(layer)![1])
25
+ for (let i = 0; i < dark.length; i++) expect(alpha(light[i])).toBeLessThan(alpha(dark[i]))
26
+ })
27
+
28
+ it('uses three ambient layers from the floating threshold up', () => {
29
+ expect(liftShadow(FLOATING_LIFT_MIN, 'dark', { rim: 0 }).split(', ').length).toBe(3)
30
+ expect(liftShadow(3, 'dark', { rim: 0 }).split(', ').length).toBe(2)
31
+ })
32
+ })
33
+
34
+ describe('liftStyle', () => {
35
+ it('returns a web boxShadow under the RNW alias', () => {
36
+ const style = liftStyle(2) as { boxShadow?: string }
37
+ expect(style.boxShadow).toContain('inset 0 1px 0')
38
+ })
39
+ })
@@ -0,0 +1,102 @@
1
+ /**
2
+ * LIFT — the treatment a plane wears when it sits above its neighbour.
3
+ *
4
+ * Tone says which plane a container sits on; lift says it is resting ON the one
5
+ * below. Two cues, both lit from above:
6
+ *
7
+ * RIM a crisp 1px top rim-light. The one neumorphism half that survives
8
+ * near black. paperSheet's hero rim is 0.20 (VW-99 run 2); a card is
9
+ * not a hero, so the default lift sits one grade quieter at 0.12,
10
+ * chosen against 0.20 side by side in Lab/Depth (2026-09-08).
11
+ * AMBIENT a soft shadow cast onto the plane below, growing with the number of
12
+ * planes crossed. Content lifts (1–3) are tight; floating lifts (4–5)
13
+ * are large and soft, describing separation from the page rather than
14
+ * rank in a stack.
15
+ *
16
+ * Neither cue is a hairline ring: a ring is an edge, not a lift, and stays the
17
+ * divider's job. Web carries the multi-layer boxShadow; native gets the single
18
+ * shadow* set. The tone underneath is load-bearing on its own either way.
19
+ */
20
+ import { Platform, type ViewStyle } from 'react-native'
21
+ import type { ThemeMode } from './tokens/semantic'
22
+
23
+ export type LiftStep = 1 | 2 | 3 | 4 | 5
24
+
25
+ /** The lowest lift that is FLOATING (menu, popover, toast) rather than content. */
26
+ export const FLOATING_LIFT_MIN: LiftStep = 4
27
+
28
+ /** Rim-light alpha per mode. Light mode is a placeholder pending its own pass. */
29
+ export const LIFT_RIM_ALPHA: Record<ThemeMode, number> = { dark: 0.12, light: 0.9 }
30
+
31
+ interface ShadowLayer {
32
+ y: number
33
+ blur: number
34
+ alpha: number
35
+ }
36
+
37
+ const AMBIENT: Record<LiftStep, ShadowLayer[]> = {
38
+ 1: [
39
+ { y: 1, blur: 2, alpha: 0.35 },
40
+ { y: 2, blur: 6, alpha: 0.3 },
41
+ ],
42
+ 2: [
43
+ { y: 1, blur: 2, alpha: 0.35 },
44
+ { y: 4, blur: 12, alpha: 0.32 },
45
+ ],
46
+ 3: [
47
+ { y: 2, blur: 4, alpha: 0.35 },
48
+ { y: 8, blur: 22, alpha: 0.35 },
49
+ ],
50
+ 4: [
51
+ { y: 1, blur: 2, alpha: 0.35 },
52
+ { y: 10, blur: 24, alpha: 0.3 },
53
+ { y: 24, blur: 48, alpha: 0.2 },
54
+ ],
55
+ 5: [
56
+ { y: 2, blur: 4, alpha: 0.4 },
57
+ { y: 16, blur: 32, alpha: 0.35 },
58
+ { y: 32, blur: 64, alpha: 0.25 },
59
+ ],
60
+ }
61
+
62
+ /** A light surface casts a lighter shadow: same geometry, scaled alpha. */
63
+ const AMBIENT_ALPHA_SCALE: Record<ThemeMode, number> = { dark: 1, light: 0.4 }
64
+
65
+ export interface LiftOptions {
66
+ /** Override the rim alpha. `0` drops the rim and leaves the ambient shadow alone. */
67
+ rim?: number
68
+ }
69
+
70
+ function ambientLayers(step: LiftStep, mode: ThemeMode): string[] {
71
+ const scale = AMBIENT_ALPHA_SCALE[mode]
72
+ return AMBIENT[step].map(
73
+ ({ y, blur, alpha }) => `0 ${y}px ${blur}px rgba(0,0,0,${(alpha * scale).toFixed(2)})`
74
+ )
75
+ }
76
+
77
+ /** The web boxShadow string for a lift: rim first, then the ambient layers. */
78
+ export function liftShadow(step: LiftStep, mode: ThemeMode = 'dark', opts: LiftOptions = {}) {
79
+ const rim = opts.rim ?? LIFT_RIM_ALPHA[mode]
80
+ const layers = ambientLayers(step, mode)
81
+ if (rim > 0) layers.unshift(`inset 0 1px 0 rgba(255,255,255,${rim.toFixed(2)})`)
82
+ return layers.join(', ')
83
+ }
84
+
85
+ /** Platform style for a lift. Compose after `backgroundColor`; never instead of it. */
86
+ export function liftStyle(
87
+ step: LiftStep,
88
+ mode: ThemeMode = 'dark',
89
+ opts: LiftOptions = {}
90
+ ): ViewStyle {
91
+ const deepest = AMBIENT[step][AMBIENT[step].length - 1]
92
+ return Platform.select({
93
+ web: { boxShadow: liftShadow(step, mode, opts) } as unknown as ViewStyle,
94
+ default: {
95
+ shadowColor: '#000',
96
+ shadowOffset: { width: 0, height: Math.round(deepest.y / 2) },
97
+ shadowOpacity: deepest.alpha * AMBIENT_ALPHA_SCALE[mode],
98
+ shadowRadius: deepest.blur / 2,
99
+ elevation: step,
100
+ },
101
+ }) as ViewStyle
102
+ }
@@ -15,7 +15,8 @@ import { describe, it, expect } from 'vitest'
15
15
  import { grainOpacityForTone, grainForTone, paperSheet, insetWell, barPaper } from './materials'
16
16
  import { greyRamp } from './tokens/primitives'
17
17
 
18
- const styleOf = (s: Record<string, unknown>) => s as { backgroundImage?: string; boxShadow?: string; backgroundColor?: string }
18
+ const styleOf = (s: Record<string, unknown>) =>
19
+ s as { backgroundImage?: string; boxShadow?: string; backgroundColor?: string }
19
20
 
20
21
  describe('grain', () => {
21
22
  it('scales opacity with the tone it sits on', () => {
@@ -75,7 +76,9 @@ describe('insetWell', () => {
75
76
  // a darker rectangle. It is the light on the floor that says "below".
76
77
  const shadow = styleOf(insetWell(greyRamp[950])).boxShadow!
77
78
  expect(shadow).toContain('inset 0 2px')
78
- expect(shadow, 'missing the bottom floor rim').toContain('inset 0 -1px 0 rgba(255,255,255,0.12)')
79
+ expect(shadow, 'missing the bottom floor rim').toContain(
80
+ 'inset 0 -1px 0 rgba(255,255,255,0.12)'
81
+ )
79
82
  })
80
83
 
81
84
  it('is entirely inset — a well must not cast outward', () => {
@@ -54,10 +54,12 @@
54
54
  *
55
55
  * Kept: grain, rim-light, contact shadow.
56
56
  *
57
- * See sources/design/surface-system-north-star.md §4 and
58
- * sources/runbooks/VW-99-depth-wall-calibration.md.
57
+ * See `sources/design/surface-system-north-star.md` §4 and
58
+ * `sources/runbooks/VW-99-depth-wall-calibration.md`. Those paths are relative to
59
+ * the `voltras-workspace` active-work initiative (`active-work open voltras-workspace`), not this repo —
60
+ * titan-design has no `sources/` directory, so they do not resolve from here.
59
61
  */
60
- import type { ViewStyle } from 'react-native'
62
+ import type { TextStyle, ViewStyle } from 'react-native'
61
63
  import { getSemanticColors } from './tokens/semantic'
62
64
 
63
65
  const c = getSemanticColors('dark')
@@ -66,7 +68,13 @@ const c = getSemanticColors('dark')
66
68
 
67
69
  function parseHex(hex: string): [number, number, number] | null {
68
70
  const h = hex.replace('#', '')
69
- const full = h.length === 3 ? h.split('').map((ch) => ch + ch).join('') : h
71
+ const full =
72
+ h.length === 3
73
+ ? h
74
+ .split('')
75
+ .map((ch) => ch + ch)
76
+ .join('')
77
+ : h
70
78
  const rgb: [number, number, number] = [
71
79
  parseInt(full.slice(0, 2), 16),
72
80
  parseInt(full.slice(2, 4), 16),
@@ -209,3 +217,29 @@ export function barPaper(color: string, flip = false): ViewStyle {
209
217
  : 'inset 0 1.5px 0 rgba(255,255,255,0.22), 0 6px 16px rgba(0,0,0,0.45)',
210
218
  } as unknown as ViewStyle
211
219
  }
220
+
221
+ /**
222
+ * A POST-IT card: a matte tinted sheet with a crisp rim, a defined contact
223
+ * shadow, and a deliberate CANT (`deg`) so it reads as a stuck note — where a
224
+ * small unit of data wants to feel pinned to the surface (alerts, next-set). A
225
+ * degree or two, not a fraction, so the tilt is intentional rather than a bug.
226
+ */
227
+ export function postIt(tone: string = c['surface-raised'], deg = -1.5): ViewStyle {
228
+ return {
229
+ backgroundColor: tone,
230
+ backgroundImage: grainForTone(tone),
231
+ boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.12), 0 6px 14px rgba(0,0,0,0.45)',
232
+ transform: [{ rotate: `${deg}deg` }],
233
+ } as unknown as ViewStyle
234
+ }
235
+
236
+ /**
237
+ * DEBOSSED (engraved) text: a 1px light highlight dropped just below the glyphs
238
+ * so a label reads as pressed INTO the surface. Deliberate, for section eyebrows
239
+ * on a matte plane — not body copy. Pair with a tertiary/dim text colour.
240
+ */
241
+ export const debossLabel: TextStyle = {
242
+ textShadowColor: 'rgba(255,255,255,0.11)',
243
+ textShadowOffset: { width: 0, height: 1 },
244
+ textShadowRadius: 0,
245
+ }
@@ -37,7 +37,7 @@ const colorTree = tailwindConfig.theme?.extend?.colors ?? {}
37
37
  // `brand-primary` class).
38
38
  function flattenColors(
39
39
  node: Record<string, unknown>,
40
- prefix: string[] = [],
40
+ prefix: string[] = []
41
41
  ): Record<string, string> {
42
42
  const out: Record<string, string> = {}
43
43
  for (const [key, value] of Object.entries(node)) {
@@ -83,11 +83,11 @@ describe('native theming guard (no color token renders black on native)', () =>
83
83
  }
84
84
  expect(
85
85
  undefinedDark,
86
- 'tailwind colors point at vars missing from darkThemeCSSVars (className renders black on native dark)',
86
+ 'tailwind colors point at vars missing from darkThemeCSSVars (className renders black on native dark)'
87
87
  ).toEqual([])
88
88
  expect(
89
89
  undefinedLight,
90
- 'tailwind colors point at vars missing from lightThemeCSSVars (className renders black on native light)',
90
+ 'tailwind colors point at vars missing from lightThemeCSSVars (className renders black on native light)'
91
91
  ).toEqual([])
92
92
  })
93
93
 
@@ -106,11 +106,11 @@ describe('native theming guard (no color token renders black on native)', () =>
106
106
  }
107
107
  expect(
108
108
  invalidValues,
109
- `getSemanticColors('${mode}') has tokens with no valid color (renders black/unset on native)`,
109
+ `getSemanticColors('${mode}') has tokens with no valid color (renders black/unset on native)`
110
110
  ).toEqual([])
111
111
  expect(
112
112
  missingVars,
113
- `resolveColor tokens missing a --color-<token> in ${mode} vars (renders unset on web)`,
113
+ `resolveColor tokens missing a --color-<token> in ${mode} vars (renders unset on web)`
114
114
  ).toEqual([])
115
115
  }
116
116
  })
@@ -36,6 +36,10 @@ function injectFontImport(url: string) {
36
36
  document.head.appendChild(link)
37
37
  }
38
38
 
39
+ /**
40
+ * @deprecated Use `ThemeProvider` with semantic tokens — removed once audiobook
41
+ * migrates off presets (AW-129).
42
+ */
39
43
  export function applyThemePreset(preset: ThemePreset): () => void {
40
44
  if (typeof document === 'undefined') {
41
45
  return () => {}
@@ -5,12 +5,15 @@ import type { ThemePreset } from './types'
5
5
  *
6
6
  * Warm copper/patina aesthetic inspired by vintage audio equipment
7
7
  * and reading lounges. Dark-mode only design with editorial typography.
8
+ *
9
+ * @deprecated Use `ThemeProvider` with semantic tokens — removed once audiobook
10
+ * migrates off presets (AW-129).
8
11
  */
9
12
  export const audiobookPreset: ThemePreset = {
10
13
  name: 'audiobook',
11
14
  description: 'Warm copper/patina theme with editorial typography',
12
15
  fontImport:
13
- "https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,400;0,600;0,700;1,400&family=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap",
16
+ 'https://fonts.googleapis.com/css2?family=Newsreader:ital,wght@0,400;0,600;0,700;1,400&family=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap',
14
17
  fonts: {
15
18
  sans: "'Outfit', 'Avenir', 'Avenir Next', system-ui, sans-serif",
16
19
  body: "'Outfit', 'Avenir', 'Avenir Next', system-ui, sans-serif",
@@ -77,7 +80,7 @@ export const audiobookPreset: ThemePreset = {
77
80
  'interactive-active': 'rgba(255, 255, 255, 0.16)',
78
81
  'interactive-selected': 'rgba(255, 255, 255, 0.08)',
79
82
 
80
- 'divider': 'rgba(255, 255, 255, 0.06)',
83
+ divider: 'rgba(255, 255, 255, 0.06)',
81
84
 
82
85
  // Glow RGB values for shadow utilities
83
86
  'brand-primary-rgb': '212, 120, 42',
@@ -1,5 +1,9 @@
1
1
  import type { ThemePreset } from './types'
2
2
 
3
+ /**
4
+ * @deprecated Use `ThemeProvider` with semantic tokens — removed once audiobook
5
+ * migrates off presets (AW-129).
6
+ */
3
7
  export const defaultPreset: ThemePreset = {
4
8
  name: 'default',
5
9
  description: 'Titan Design System default theme — orange accent, steel secondary',
@@ -1,4 +1,17 @@
1
- export type { ThemePreset, ThemePresetColors, ThemePresetFonts, ThemePresetRadii, ThemePresetShadows } from './types'
1
+ /**
2
+ * @deprecated The whole `theme/presets` barrel. Use `ThemeProvider` with
3
+ * semantic tokens — removed once audiobook migrates off presets (AW-129).
4
+ */
5
+ export type {
6
+ ThemePreset,
7
+ ThemePresetColors,
8
+ ThemePresetFonts,
9
+ ThemePresetRadii,
10
+ ThemePresetShadows,
11
+ } from './types'
12
+ /** @deprecated Use `ThemeProvider` with semantic tokens (AW-129). */
2
13
  export { applyThemePreset } from './apply'
14
+ /** @deprecated Use `ThemeProvider` with semantic tokens (AW-129). */
3
15
  export { defaultPreset } from './default'
16
+ /** @deprecated Use `ThemeProvider` with semantic tokens (AW-129). */
4
17
  export { audiobookPreset } from './audiobook'
@@ -32,7 +32,7 @@ export interface ThemePresetColors {
32
32
  'hairline-subtle'?: string
33
33
  'hairline-strong'?: string
34
34
  'border-focus'?: string
35
- 'divider'?: string
35
+ divider?: string
36
36
  [key: `${string}`]: string | undefined
37
37
  }
38
38
 
@@ -54,7 +54,7 @@ describe('semantic text on the surface planes (dark)', () => {
54
54
  const ratio = contrast(fg, greyRamp[plane])
55
55
  expect(
56
56
  ratio,
57
- `${role} ${fg} on grey-${plane} ${greyRamp[plane]} is ${ratio.toFixed(2)}:1, needs ${min}`,
57
+ `${role} ${fg} on grey-${plane} ${greyRamp[plane]} is ${ratio.toFixed(2)}:1, needs ${min}`
58
58
  ).toBeGreaterThanOrEqual(min)
59
59
  })
60
60
  })
@@ -68,10 +68,19 @@ describe('semantic text on the surface planes (dark)', () => {
68
68
  it('keeps primary > secondary > tertiary in contrast on every plane', () => {
69
69
  for (const plane of PLANES) {
70
70
  const [pri, sec, ter] = TEXT_ROLES.map(({ role }) =>
71
- contrast(semanticColorsDark[role as keyof typeof semanticColorsDark] as string, greyRamp[plane]),
71
+ contrast(
72
+ semanticColorsDark[role as keyof typeof semanticColorsDark] as string,
73
+ greyRamp[plane]
74
+ )
72
75
  )
73
- expect(pri, `on grey-${plane}: primary ${pri.toFixed(2)} vs secondary ${sec.toFixed(2)}`).toBeGreaterThan(sec)
74
- expect(sec, `on grey-${plane}: secondary ${sec.toFixed(2)} vs tertiary ${ter.toFixed(2)}`).toBeGreaterThan(ter)
76
+ expect(
77
+ pri,
78
+ `on grey-${plane}: primary ${pri.toFixed(2)} vs secondary ${sec.toFixed(2)}`
79
+ ).toBeGreaterThan(sec)
80
+ expect(
81
+ sec,
82
+ `on grey-${plane}: secondary ${sec.toFixed(2)} vs tertiary ${ter.toFixed(2)}`
83
+ ).toBeGreaterThan(ter)
75
84
  }
76
85
  })
77
86
 
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Status tokens that mean different things must LOOK different.
3
+ *
4
+ * `status-error-vivid` existed to separate "drop everything" from "serious",
5
+ * and it pointed at `ramp.red[600]` — the exact value of `status-error`. The two
6
+ * had been rendering as one colour for as long as the token had existed, in both
7
+ * themes. Three other places in the system already disagreed with it and said
8
+ * vivid was `#FF4757`: `--color-status-error-vivid-rgb`, the `-subtle` rgba, and
9
+ * the `glow` shadow in `Indicator`. Only the value anyone actually rendered was
10
+ * wrong.
11
+ *
12
+ * Nothing caught it because every existing token test asks whether a value is
13
+ * PRESENT and parity-matched across config.ts and global.css — a collapsed token
14
+ * is present and matches perfectly. Distinctness is a different question, so it
15
+ * gets its own gate.
16
+ *
17
+ * Found 2026-08-31 by measuring a rendered Storybook story, not by reading the
18
+ * tokens: `SeverityLabel`'s Critical and High dots came back the same RGB.
19
+ */
20
+ import { describe, it, expect } from 'vitest'
21
+ import { semanticColorsDark, semanticColorsLight } from './tokens/semantic'
22
+ import { alertRedVivid } from './tokens/primitives'
23
+
24
+ type SemanticMap = Record<string, string>
25
+
26
+ const THEMES: [string, SemanticMap][] = [
27
+ ['dark', semanticColorsDark as SemanticMap],
28
+ ['light', semanticColorsLight as SemanticMap],
29
+ ]
30
+
31
+ /**
32
+ * Token pairs a viewer is expected to tell apart at a glance. Each pair carries
33
+ * the reason, so a future retune can weigh it rather than just delete the row.
34
+ */
35
+ const MUST_DIFFER: [string, string, string][] = [
36
+ [
37
+ 'status-error',
38
+ 'status-error-vivid',
39
+ 'severity Critical vs High, and DeviceRow/DeviceIndicator "lost" vs a plain error',
40
+ ],
41
+ ['status-error', 'status-warning', 'error vs warning is the most common status pairing'],
42
+ ['status-warning', 'status-info', 'warning vs informational'],
43
+ ['status-success', 'status-info', 'succeeded vs merely reporting'],
44
+ ]
45
+
46
+ describe('semantically distinct status tokens render distinct values', () => {
47
+ for (const [theme, colors] of THEMES) {
48
+ for (const [a, b, why] of MUST_DIFFER) {
49
+ it(`${theme}: ${a} !== ${b} — ${why}`, () => {
50
+ // Both must exist, or the assertion passes vacuously on a typo.
51
+ expect(colors[a], `${a} missing from ${theme}`).toBeTruthy()
52
+ expect(colors[b], `${b} missing from ${theme}`).toBeTruthy()
53
+ expect(colors[a]!.toLowerCase()).not.toBe(colors[b]!.toLowerCase())
54
+ })
55
+ }
56
+ }
57
+ })
58
+
59
+ describe('status-error-vivid is the vivid red the rest of the system already assumes', () => {
60
+ for (const [theme, colors] of THEMES) {
61
+ it(`${theme}: is the alertRedVivid primitive that -rgb, -subtle and Indicator's glow encode`, () => {
62
+ expect(colors['status-error-vivid']).toBe(alertRedVivid)
63
+ expect(alertRedVivid.toLowerCase()).toBe('#ff4757')
64
+ })
65
+ }
66
+ })