@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
@@ -0,0 +1,163 @@
1
+ /**
2
+ * AW-133 — the `-subtle` fill and the text on it, as a gate.
3
+ *
4
+ * The status family had one token doing two incompatible jobs. `status-error`
5
+ * and `brand-secondary` sit at ramp rung 600 so a white label reads on them as a
6
+ * solid fill; that same depth made them unreadable as TEXT on a dark plane, and a
7
+ * subtle Pill in those tones measured 2.9-3.1 against its own fill. The four
8
+ * lighter tones had the mirror problem. Splitting the roles fixed it: `on-*-subtle`
9
+ * is text on the subtle fill, levelled at rung 300.
10
+ *
11
+ * The numbers run in CI so a future retune cannot quietly reopen it.
12
+ */
13
+ import { describe, it, expect } from 'vitest'
14
+ import { greyRamp } from './tokens/primitives'
15
+ import { semanticColorsDark } from './tokens/semantic'
16
+
17
+ const hex2rgb = (h: string): [number, number, number] => {
18
+ const n = parseInt(h.slice(1), 16)
19
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
20
+ }
21
+ const srgb2lin = (c: number) => {
22
+ const s = c / 255
23
+ return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
24
+ }
25
+ const relLum = (rgb: number[]) => {
26
+ const [r, g, b] = rgb.map(srgb2lin)
27
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b
28
+ }
29
+ const contrast = (a: number[], b: number[]) => {
30
+ const [hi, lo] = [relLum(a), relLum(b)].sort((x, y) => y - x)
31
+ return (hi + 0.05) / (lo + 0.05)
32
+ }
33
+ /** OKLab lightness — the perceptual axis the ramps were generated on. */
34
+ const oklabL = (rgb: number[]) => {
35
+ const [r, g, b] = rgb.map(srgb2lin)
36
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b)
37
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b)
38
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b)
39
+ return 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s
40
+ }
41
+ /** OKLCH chroma — how saturated, independent of how light. */
42
+ const oklabChroma = (rgb: number[]) => {
43
+ const [r, g, b] = rgb.map(srgb2lin)
44
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b)
45
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b)
46
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b)
47
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s
48
+ const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
49
+ return Math.sqrt(a * a + bb * bb)
50
+ }
51
+
52
+ const parseRgba = (v: string): { rgb: number[]; alpha: number } => {
53
+ const m = v.match(/rgba?\(([^)]+)\)/)
54
+ if (!m) throw new Error(`expected an rgba() subtle fill, got "${v}"`)
55
+ const parts = m[1].split(',').map((n) => parseFloat(n.trim()))
56
+ return { rgb: parts.slice(0, 3), alpha: parts[3] ?? 1 }
57
+ }
58
+ const composite = (fill: string, plane: string) => {
59
+ const { rgb, alpha } = parseRgba(fill)
60
+ const bg = hex2rgb(plane)
61
+ return rgb.map((c, i) => Math.round(c * alpha + bg[i] * (1 - alpha)))
62
+ }
63
+
64
+ /** The two planes a subtle pill actually lands on. */
65
+ const PAGE = greyRamp[925]
66
+ const CARD = greyRamp[875]
67
+
68
+ /**
69
+ * Four tones level at ramp rung 300. Two are deliberate exceptions, both operator
70
+ * calls during the AW-133 review, and both are held here rather than quietly
71
+ * widening the band:
72
+ *
73
+ * - `brand` keeps the exact `orange[400]` brand hue instead of levelling, so the
74
+ * Voltras tone never drifts. It is the darkest label and the lowest contrast.
75
+ * - `error` sits at `red[400]`, a rung darker than its siblings. red[300] levelled
76
+ * perfectly but read PINK: a red that light can only hold ~0.121 OKLCH chroma.
77
+ * red[400] carries ~0.165 and reads red. Its fill is thinned to 0.08 to buy back
78
+ * the contrast that darkness costs, which is why it still beats brand's floor.
79
+ */
80
+ const TONES = [
81
+ {
82
+ name: 'brand',
83
+ fill: 'brand-primary-subtle',
84
+ text: 'on-brand-primary-subtle',
85
+ min: 4.0,
86
+ levelled: false,
87
+ },
88
+ {
89
+ name: 'accent',
90
+ fill: 'brand-secondary-subtle',
91
+ text: 'on-brand-secondary-subtle',
92
+ min: 4.5,
93
+ levelled: true,
94
+ },
95
+ {
96
+ name: 'success',
97
+ fill: 'status-success-subtle',
98
+ text: 'on-status-success-subtle',
99
+ min: 4.5,
100
+ levelled: true,
101
+ },
102
+ {
103
+ name: 'warning',
104
+ fill: 'status-warning-subtle',
105
+ text: 'on-status-warning-subtle',
106
+ min: 4.5,
107
+ levelled: true,
108
+ },
109
+ {
110
+ name: 'error',
111
+ fill: 'status-error-subtle',
112
+ text: 'on-status-error-subtle',
113
+ min: 4.2,
114
+ levelled: false,
115
+ },
116
+ {
117
+ name: 'info',
118
+ fill: 'status-info-subtle',
119
+ text: 'on-status-info-subtle',
120
+ min: 4.5,
121
+ levelled: true,
122
+ },
123
+ ] as const
124
+
125
+ describe('subtle tone weight (dark)', () => {
126
+ it.each(TONES)('$name text clears its floor on both planes', ({ fill, text, min }) => {
127
+ const label = hex2rgb(semanticColorsDark[text])
128
+ for (const plane of [PAGE, CARD]) {
129
+ const composited = composite(semanticColorsDark[fill], plane)
130
+ expect(contrast(label, composited)).toBeGreaterThanOrEqual(min)
131
+ }
132
+ })
133
+
134
+ // The original defect was a WEIGHT mismatch, not only a contrast one: the family
135
+ // spanned OKLCH L 0.550-0.813, so accent and error read as a different rung of
136
+ // the system. Hold the levelled tones in a tight band; the two exceptions are
137
+ // measured by their own floors above.
138
+ it('levels the unexceptional tones to one weight', () => {
139
+ const ls = TONES.filter((t) => t.levelled).map((t) =>
140
+ oklabL(hex2rgb(semanticColorsDark[t.text]))
141
+ )
142
+ expect(Math.max(...ls) - Math.min(...ls)).toBeLessThanOrEqual(0.06)
143
+ })
144
+
145
+ // Guards the operator's actual complaint. Levelling error back onto rung 300 would
146
+ // satisfy every other assertion in this file — tighter band, higher contrast — and
147
+ // reintroduce the pink. Chroma is the thing that made it wrong, so chroma is what
148
+ // gets asserted.
149
+ it('keeps error saturated enough to read red rather than pink', () => {
150
+ const c = oklabChroma(hex2rgb(semanticColorsDark['on-status-error-subtle']))
151
+ expect(c).toBeGreaterThanOrEqual(0.15)
152
+ })
153
+
154
+ // A fill whose hue drifts from its label's re-opens the split this fixed.
155
+ it('draws each fill from the same hue family as its label', () => {
156
+ for (const { fill, text, name } of TONES) {
157
+ if (name === 'brand') continue
158
+ const fillRgb = parseRgba(semanticColorsDark[fill]).rgb
159
+ const labelRgb = hex2rgb(semanticColorsDark[text])
160
+ expect(fillRgb).toEqual(labelRgb)
161
+ }
162
+ })
163
+ })
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { PLANE_ORDER, pressedLevel, raisedLevel, surfaceBackground } from './surface-planes'
3
+ import { getSemanticColors } from './tokens/semantic'
4
+
5
+ describe('surface planes', () => {
6
+ it('orders the six planes darkest first', () => {
7
+ expect(PLANE_ORDER).toEqual(['frame', 'background', 'base', 'elevated', 'raised', 'overlay'])
8
+ })
9
+
10
+ it('resolves each plane to its semantic token, literal hex', () => {
11
+ const dark = getSemanticColors('dark')
12
+ expect(surfaceBackground('frame', 'dark')).toBe(dark['background-frame'])
13
+ expect(surfaceBackground('base', 'dark')).toBe(dark['surface-base'])
14
+ expect(surfaceBackground('overlay', 'dark')).toMatch(/^#[0-9A-F]{6}$/i)
15
+ })
16
+
17
+ it.each([
18
+ ['base', 1, 'elevated'],
19
+ ['base', 2, 'raised'],
20
+ ['base', 3, 'overlay'],
21
+ ['elevated', 2, 'overlay'],
22
+ ['raised', 3, 'overlay'],
23
+ ['overlay', 1, 'overlay'],
24
+ ] as const)('raisedLevel(%s, %i) → %s, clamped at overlay', (from, steps, expected) => {
25
+ expect(raisedLevel(from, steps)).toBe(expected)
26
+ })
27
+
28
+ it('raisedLevel with zero steps stays put', () => {
29
+ expect(raisedLevel('raised', 0)).toBe('raised')
30
+ })
31
+
32
+ it('pressedLevel steps down and clamps at the frame', () => {
33
+ expect(pressedLevel('base')).toBe('background')
34
+ expect(pressedLevel('background')).toBe('frame')
35
+ expect(pressedLevel('frame')).toBe('frame')
36
+ })
37
+ })
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The surface planes: the grey-ramp steps a container can sit on, darkest first.
3
+ *
4
+ * Every background a `<Surface>` or `<Card>` paints resolves through this list,
5
+ * so a container can only ever land ON the ramp. Stepping up (a lifted card) or
6
+ * down (a pressed well) is indexing `PLANE_ORDER`, clamped at both ends.
7
+ *
8
+ * `frame` is the bezel the ramp sits inside and the floor a pressed surface
9
+ * clamps at. It replaced a separate `inset` level in TD-07.14: at ΔE 1.10 from
10
+ * the frame that level was an imperceptible duplicate.
11
+ */
12
+ import { getSemanticColors, type ThemeMode } from './tokens/semantic'
13
+
14
+ type ColorToken = keyof ReturnType<typeof getSemanticColors>
15
+
16
+ export type SurfaceLevel = 'frame' | 'background' | 'base' | 'elevated' | 'raised' | 'overlay'
17
+
18
+ // Which semantic token backs each plane. Values live in `semantic.ts` and stay
19
+ // in sync with the grey ramp; this map never carries literal hexes.
20
+ export const SURFACE_LEVEL_TOKEN = {
21
+ frame: 'background-frame',
22
+ background: 'background-base',
23
+ base: 'surface-base',
24
+ elevated: 'surface-elevated',
25
+ raised: 'surface-raised',
26
+ overlay: 'surface-overlay',
27
+ } as const satisfies Record<SurfaceLevel, ColorToken>
28
+
29
+ /** Darkest → lightest. Kept in lockstep with the grey ramp and surface.contract.test. */
30
+ export const PLANE_ORDER = [
31
+ 'frame',
32
+ 'background',
33
+ 'base',
34
+ 'elevated',
35
+ 'raised',
36
+ 'overlay',
37
+ ] as const satisfies readonly SurfaceLevel[]
38
+
39
+ /** The background hex for a plane under a theme mode (literal hex, never a var()). */
40
+ export function surfaceBackground(level: SurfaceLevel, mode: ThemeMode): string {
41
+ return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]]
42
+ }
43
+
44
+ function stepPlane(from: SurfaceLevel, delta: number): SurfaceLevel {
45
+ const index = PLANE_ORDER.indexOf(from)
46
+ const clamped = Math.min(PLANE_ORDER.length - 1, Math.max(0, index + delta))
47
+ return PLANE_ORDER[clamped]
48
+ }
49
+
50
+ /**
51
+ * The plane a `<Surface pressed>` resolves to: its parent's plane stepped one
52
+ * index DOWN the ramp, clamped at the `frame` floor.
53
+ * overlay→raised, raised→elevated, elevated→base, base→background,
54
+ * background→frame, frame→frame (clamped).
55
+ */
56
+ export function pressedLevel(parent: SurfaceLevel): SurfaceLevel {
57
+ return stepPlane(parent, -1)
58
+ }
59
+
60
+ /**
61
+ * The plane a lifted surface resolves to: its parent's plane stepped `steps`
62
+ * UP the ramp, clamped at `overlay`. The clamp is what makes raised-on-raised
63
+ * unrepresentable: a card inside an overlay stays on the overlay and relies on
64
+ * its lift treatment, never on a lighter grey that is not on the ramp.
65
+ */
66
+ export function raisedLevel(parent: SurfaceLevel, steps: number): SurfaceLevel {
67
+ return stepPlane(parent, Math.max(0, steps))
68
+ }
@@ -0,0 +1,163 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest'
2
+ import { createRequire } from 'node:module'
3
+ import { readdirSync, readFileSync } from 'node:fs'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ /**
8
+ * Tailwind opacity modifiers on var()-backed colours (VW-308).
9
+ *
10
+ * Tailwind v3 cannot apply `/<n>` to a colour whose value is a `var(--…)`
11
+ * reference. It does not warn and does not fall back: it emits NO RULE, and the
12
+ * class is dead CSS on web. Every colour in `tailwind.config.js` is var()-backed
13
+ * (that is what makes light/dark switching work), so `bg-brand-primary/10` never
14
+ * painted anything — while the literal-valued `text-white/70` compiles fine.
15
+ * That asymmetry is why it sat unnoticed in four components.
16
+ *
17
+ * This test compiles the REAL `tailwind.config.js` against a fixture, which is
18
+ * how the finding was reproduced. It pins three things:
19
+ * 1. the defect itself, so nobody "fixes" a call site back to a modifier;
20
+ * 2. a POSITIVE CONTROL — `text-white/70` and `bg-black/50` still compile, so
21
+ * an empty stylesheet from a broken harness can't pass as proof;
22
+ * 3. that no var()-backed opacity modifier survives anywhere in `src/`.
23
+ *
24
+ * The chosen fix is (3), not making the modifier work: the `<alpha-value>`
25
+ * pattern needs channel-triplet CSS vars, a hand-aligned rewrite of the whole
26
+ * token layer, and `color-mix()` — the config-only alternative — is not a value
27
+ * React Native understands. The wash ladder (`-subtle`/`-muted`/`-strong`)
28
+ * already expresses translucency on both platforms.
29
+ */
30
+
31
+ const require = createRequire(import.meta.url)
32
+ const uiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
33
+
34
+ // postcss is a declared dependency of tailwindcss, not of this package; resolve
35
+ // it from there rather than relying on hoisting.
36
+ const tailwindEntry = require.resolve('tailwindcss', { paths: [uiRoot] })
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ const tailwind = require(tailwindEntry) as any
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ const postcss = createRequire(tailwindEntry)('postcss') as any
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ const tailwindConfig = require(path.join(uiRoot, 'tailwind.config.js')) as any
43
+
44
+ /** `brand.primary.DEFAULT` -> the `brand-primary` class suffix. */
45
+ function flattenColors(
46
+ node: Record<string, unknown>,
47
+ prefix: string[] = []
48
+ ): Record<string, string> {
49
+ const out: Record<string, string> = {}
50
+ for (const [key, value] of Object.entries(node)) {
51
+ const nextPrefix = key === 'DEFAULT' ? prefix : [...prefix, key]
52
+ if (value && typeof value === 'object') {
53
+ Object.assign(out, flattenColors(value as Record<string, unknown>, nextPrefix))
54
+ } else if (typeof value === 'string') {
55
+ out[nextPrefix.join('-')] = value
56
+ }
57
+ }
58
+ return out
59
+ }
60
+
61
+ const varBackedTokens = new Set(
62
+ Object.entries(flattenColors(tailwindConfig.theme?.extend?.colors ?? {}))
63
+ .filter(([, value]) => /^var\(--color-/.test(value))
64
+ .map(([name]) => name)
65
+ )
66
+
67
+ const PROBE_CLASSES = [
68
+ // The finding, verbatim.
69
+ 'bg-brand-primary/10',
70
+ 'text-on-status-success/70',
71
+ // The four call sites' original classes.
72
+ 'bg-status-success/10',
73
+ 'bg-brand-primary/20',
74
+ 'text-text-tertiary/40',
75
+ // Positive control: literal-valued colours, which DO support the modifier.
76
+ 'text-white/70',
77
+ 'bg-black/50',
78
+ // The replacements the call sites now use.
79
+ 'bg-brand-primary-subtle',
80
+ 'bg-status-success-subtle',
81
+ 'bg-brand-primary-muted',
82
+ 'bg-status-info-muted',
83
+ 'text-text-disabled',
84
+ ]
85
+
86
+ let emitted: Set<string>
87
+
88
+ beforeAll(async () => {
89
+ const raw = `<div class="${PROBE_CLASSES.join(' ')}"></div>`
90
+ const result = await postcss([
91
+ tailwind({ ...tailwindConfig, content: { files: [{ raw, extension: 'html' }] } }),
92
+ ]).process('@tailwind utilities;', { from: undefined })
93
+
94
+ emitted = new Set(
95
+ [...(result.css as string).matchAll(/^\.([^\s{]+)\s*\{/gm)].map((m) => m[1].replace(/\\/g, ''))
96
+ )
97
+ }, 30_000)
98
+
99
+ describe('tailwind opacity modifiers on var()-backed colours (VW-308)', () => {
100
+ it('compiles the real config against the fixture at all', () => {
101
+ // Guards the harness: without this, every "emits no rule" assertion below
102
+ // would also pass against a stylesheet that failed to build.
103
+ expect(emitted.size).toBeGreaterThan(0)
104
+ })
105
+
106
+ // POSITIVE CONTROL. These are literal colours in tailwind's own palette, so
107
+ // the modifier works. If one of these ever stops compiling, the harness is
108
+ // broken, not the design system.
109
+ it.each(['text-white/70', 'bg-black/50'])('%s compiles (positive control)', (className) => {
110
+ expect(emitted).toContain(className)
111
+ })
112
+
113
+ it.each([
114
+ 'bg-brand-primary/10',
115
+ 'text-on-status-success/70',
116
+ 'bg-status-success/10',
117
+ 'bg-brand-primary/20',
118
+ 'text-text-tertiary/40',
119
+ ])('%s emits NO rule — this is the defect, do not use it', (className) => {
120
+ expect(emitted).not.toContain(className)
121
+ })
122
+
123
+ it.each([
124
+ 'bg-brand-primary-subtle',
125
+ 'bg-status-success-subtle',
126
+ 'bg-brand-primary-muted',
127
+ 'bg-status-info-muted',
128
+ 'text-text-disabled',
129
+ ])('%s compiles — the replacement the call sites use', (className) => {
130
+ expect(emitted).toContain(className)
131
+ })
132
+ })
133
+
134
+ const OPACITY_UTILITY =
135
+ /\b(?:[a-z][a-z0-9-]*:)*(?:bg|text|border|ring|fill|stroke|from|to|via|shadow|outline|decoration|divide|accent|caret|placeholder)-([a-z][a-zA-Z0-9-]*)\/(?:\[[^\]]+\]|\d+)/g
136
+
137
+ function sourceFiles(dir: string, found: string[] = []): string[] {
138
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
139
+ const full = path.join(dir, entry.name)
140
+ if (entry.isDirectory()) sourceFiles(full, found)
141
+ else if (/\.(ts|tsx)$/.test(entry.name)) found.push(full)
142
+ }
143
+ return found
144
+ }
145
+
146
+ describe('no var()-backed opacity modifier survives in src/', () => {
147
+ it('finds no dead colour class', () => {
148
+ const srcRoot = path.join(uiRoot, 'src')
149
+ const violations: string[] = []
150
+
151
+ for (const file of sourceFiles(srcRoot)) {
152
+ // This file quotes the dead classes on purpose, as the probe list above.
153
+ if (file === fileURLToPath(import.meta.url)) continue
154
+ for (const match of readFileSync(file, 'utf8').matchAll(OPACITY_UTILITY)) {
155
+ if (varBackedTokens.has(match[1])) {
156
+ violations.push(`${path.relative(uiRoot, file)}: ${match[0]}`)
157
+ }
158
+ }
159
+ }
160
+
161
+ expect(violations, `dead colour classes (VW-308):\n${violations.join('\n')}`).toEqual([])
162
+ })
163
+ })
@@ -82,9 +82,7 @@ describe('token-only subpath export (TP-16)', () => {
82
82
 
83
83
  it('reaches no react / react-native / nativewind module in its import graph', () => {
84
84
  const { bare } = collectGraph(TOKEN_ENTRY)
85
- const leaked = bare.filter((s) =>
86
- FORBIDDEN.some((f) => s === f || s.startsWith(`${f}/`)),
87
- )
85
+ const leaked = bare.filter((s) => FORBIDDEN.some((f) => s === f || s.startsWith(`${f}/`)))
88
86
  expect(leaked).toEqual([])
89
87
  })
90
88
 
@@ -34,15 +34,17 @@ const mul = (M: number[], v: number[]) => [
34
34
  // Machado-2009 dichromacy simulation matrices, severity 1.0. The palette solver
35
35
  // optimizes worst-case deuteranopia/protanopia (red-green is the binding case) —
36
36
  // match that 2-way metric here, not a stricter 3-way including tritan.
37
- const DEUT = [0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881]
38
- const PROT = [0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998]
37
+ const DEUT = [
38
+ 0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881,
39
+ ]
40
+ const PROT = [
41
+ 0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882, -0.048116, 1.051998,
42
+ ]
39
43
  const dist = (a: number[], b: number[]) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]) * 100
40
44
  // worst-case perceived ΔE across deuteranopia and protanopia
41
45
  const cvdDelta = (x: string, y: string) => {
42
46
  const sim = (M: number[], hex: string) => toOklab(mul(M, hexToRgb(hex).map(lin)))
43
- return Math.min(
44
- ...[DEUT, PROT].map((M) => dist(sim(M, x), sim(M, y))),
45
- )
47
+ return Math.min(...[DEUT, PROT].map((M) => dist(sim(M, x), sim(M, y))))
46
48
  }
47
49
 
48
50
  const HEX6 = /^#[0-9A-F]{6}$/
@@ -115,7 +117,10 @@ describe('categoricalPalette', () => {
115
117
  worst = Math.min(worst, cvdDelta(colors[i], colors[j]))
116
118
  }
117
119
  }
118
- expect(worst, `${variant} min CVD deltaE (first ${CATEGORICAL_CVD_SAFE_MAX})`).toBeGreaterThanOrEqual(8)
120
+ expect(
121
+ worst,
122
+ `${variant} min CVD deltaE (first ${CATEGORICAL_CVD_SAFE_MAX})`
123
+ ).toBeGreaterThanOrEqual(8)
119
124
  }
120
125
  })
121
126
 
@@ -212,7 +217,10 @@ describe('sequentialEffort', () => {
212
217
 
213
218
  it('keeps adjacent stops distinguishable', () => {
214
219
  for (let i = 0; i < sequentialEffort.length - 1; i++) {
215
- expect(cvdDelta(sequentialEffort[i], sequentialEffort[i + 1]), `stop ${i}->${i + 1}`).toBeGreaterThanOrEqual(4.5)
220
+ expect(
221
+ cvdDelta(sequentialEffort[i], sequentialEffort[i + 1]),
222
+ `stop ${i}->${i + 1}`
223
+ ).toBeGreaterThanOrEqual(4.5)
216
224
  }
217
225
  })
218
226
 
@@ -19,6 +19,51 @@ export const primitiveColors = {
19
19
  transparent: 'transparent',
20
20
  } as const
21
21
 
22
+ /**
23
+ * The vivid alert red: the one chromatic anchor outside the OKLCH ramps.
24
+ *
25
+ * It survived the TD-07.14 fold of the old `redVivid` scale only as the
26
+ * `-rgb`, `-subtle` and glow encodings of `status-error-vivid`, while the token
27
+ * itself was pinned to `red[600]` and collapsed onto `status-error`. Owning it
28
+ * here means the semantic layer references a primitive instead of carrying a
29
+ * literal. The red ramp has no step this saturated, and adding one means
30
+ * regenerating the ramp, so it stays a pin.
31
+ */
32
+ export const alertRedVivid = '#FF4757' as const
33
+
34
+ /**
35
+ * Result/outcome palette anchors (VW-83): Material-style green/red/grey chosen
36
+ * for the result-improve/-degrade/-inconclusive tokens. No `primitiveRamps` step
37
+ * matches any of these exactly (the ramps are OKLCH-generated with a different
38
+ * hue/chroma arc), so they are pinned by name here rather than left as raw hex
39
+ * in semantic.ts — same pattern as {@link alertRedVivid}.
40
+ */
41
+ export const resultPaletteColors = {
42
+ improve: '#4caf50',
43
+ improveDark: '#248a24',
44
+ degrade: '#ef5350',
45
+ degradeDark: '#b30000',
46
+ inconclusive: '#9E9A97',
47
+ } as const
48
+
49
+ /**
50
+ * One-off semantic literals (VW-83) with no exact `greyRamp`/`primitiveRamps`
51
+ * match, pinned by name instead of left as raw hex in semantic.ts.
52
+ */
53
+ export const semanticPins = {
54
+ // Cool navy-black; darker and more blue-shifted than any warm greyRamp step.
55
+ textPrimaryLight: '#121828',
56
+ // Cool blue-grey; outside greyRamp's warm (R>=G>=B) scale.
57
+ textSecondaryLight: '#65748B',
58
+ // Pure neutral grey (R=G=B); no warm greyRamp step lands on it exactly.
59
+ backgroundBaseLight: '#EBEBEB',
60
+ // Near-neutral grey, kept one step off backgroundBaseLight by design.
61
+ dividerLight: '#E8E9EB',
62
+ // Periwinkle/indigo; more purple than any primitiveRamps.blue step. Shared by
63
+ // dark-mode text-link, border-focus and border-input-focus.
64
+ focusIndigoDark: '#828DF8',
65
+ } as const
66
+
22
67
  /**
23
68
  * Unified warm-neutral grey ramp (TD-07.14)
24
69
  *
@@ -244,7 +289,10 @@ export type CategoricalVariant = keyof typeof categoricalPalette
244
289
  * @param variant - `default` (black-text / light surfaces) or `dark` (white text)
245
290
  * @returns The hex color string
246
291
  */
247
- export function getCategoricalColor(index: number, variant: CategoricalVariant = 'default'): string {
292
+ export function getCategoricalColor(
293
+ index: number,
294
+ variant: CategoricalVariant = 'default'
295
+ ): string {
248
296
  const palette = categoricalPalette[variant]
249
297
  index = Math.max(0, Math.floor(index))
250
298
  return palette[index % palette.length]
@@ -387,21 +435,38 @@ export function getDiscreteRainbowColor(index: number, size: number): string {
387
435
 
388
436
  export const primitiveTypography = {
389
437
  fontFamilies: {
390
- sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica', 'Arial', 'sans-serif'],
438
+ sans: [
439
+ 'Inter',
440
+ '-apple-system',
441
+ 'BlinkMacSystemFont',
442
+ 'Segoe UI',
443
+ 'Helvetica',
444
+ 'Arial',
445
+ 'sans-serif',
446
+ ],
391
447
  body: ['Nunito Sans', 'sans-serif'],
392
448
  heading: ['Space Grotesk', 'sans-serif'],
393
- mono: ['SF Mono', 'Monaco', 'Inconsolata', 'Fira Mono', 'Droid Sans Mono', 'Source Code Pro', 'monospace'],
449
+ mono: [
450
+ 'SF Mono',
451
+ 'Monaco',
452
+ 'Inconsolata',
453
+ 'Fira Mono',
454
+ 'Droid Sans Mono',
455
+ 'Source Code Pro',
456
+ 'monospace',
457
+ ],
394
458
  },
395
459
  fontSizes: {
396
- xs: '0.75rem', // 12px
397
- sm: '0.875rem', // 14px
398
- base: '1rem', // 16px
399
- lg: '1.125rem', // 18px
400
- xl: '1.5rem', // 24px
401
- '2xl': '2rem', // 32px
460
+ '2xs': '0.625rem', // 10px
461
+ xs: '0.75rem', // 12px
462
+ sm: '0.875rem', // 14px
463
+ base: '1rem', // 16px
464
+ lg: '1.125rem', // 18px
465
+ xl: '1.5rem', // 24px
466
+ '2xl': '2rem', // 32px
402
467
  '3xl': '2.25rem', // 36px
403
- '4xl': '3rem', // 48px
404
- '5xl': '3.5rem', // 56px
468
+ '4xl': '3rem', // 48px
469
+ '5xl': '3.5rem', // 56px
405
470
  },
406
471
  fontWeights: {
407
472
  normal: 400,
@@ -430,28 +495,28 @@ export const primitiveTypography = {
430
495
  export const primitiveSpacing = {
431
496
  0: '0',
432
497
  px: '1px',
433
- 0.5: '0.125rem', // 2px
434
- 1: '0.25rem', // 4px
435
- 1.5: '0.375rem', // 6px
436
- 2: '0.5rem', // 8px
437
- 2.5: '0.625rem', // 10px
438
- 3: '0.75rem', // 12px
439
- 3.5: '0.875rem', // 14px
440
- 4: '1rem', // 16px
441
- 5: '1.25rem', // 20px
442
- 6: '1.5rem', // 24px
443
- 7: '1.75rem', // 28px
444
- 8: '2rem', // 32px
445
- 9: '2.25rem', // 36px
446
- 10: '2.5rem', // 40px
447
- 11: '2.75rem', // 44px
448
- 12: '3rem', // 48px
449
- 14: '3.5rem', // 56px
450
- 16: '4rem', // 64px
451
- 20: '5rem', // 80px
452
- 24: '6rem', // 96px
453
- 28: '7rem', // 112px
454
- 32: '8rem', // 128px
498
+ 0.5: '0.125rem', // 2px
499
+ 1: '0.25rem', // 4px
500
+ 1.5: '0.375rem', // 6px
501
+ 2: '0.5rem', // 8px
502
+ 2.5: '0.625rem', // 10px
503
+ 3: '0.75rem', // 12px
504
+ 3.5: '0.875rem', // 14px
505
+ 4: '1rem', // 16px
506
+ 5: '1.25rem', // 20px
507
+ 6: '1.5rem', // 24px
508
+ 7: '1.75rem', // 28px
509
+ 8: '2rem', // 32px
510
+ 9: '2.25rem', // 36px
511
+ 10: '2.5rem', // 40px
512
+ 11: '2.75rem', // 44px
513
+ 12: '3rem', // 48px
514
+ 14: '3.5rem', // 56px
515
+ 16: '4rem', // 64px
516
+ 20: '5rem', // 80px
517
+ 24: '6rem', // 96px
518
+ 28: '7rem', // 112px
519
+ 32: '8rem', // 128px
455
520
  } as const
456
521
 
457
522
  export const primitiveBorderRadius = {