@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
package/dist/bodymap.js CHANGED
@@ -18,7 +18,29 @@ function cn(...inputs) {
18
18
 
19
19
  // src/theme/tokens/primitives.ts
20
20
  var primitiveColors = {
21
- white: "#FFFFFF"};
21
+ white: "#FFFFFF",
22
+ black: "#000000"};
23
+ var alertRedVivid = "#FF4757";
24
+ var resultPaletteColors = {
25
+ improve: "#4caf50",
26
+ improveDark: "#248a24",
27
+ degrade: "#ef5350",
28
+ degradeDark: "#b30000",
29
+ inconclusive: "#9E9A97"
30
+ };
31
+ var semanticPins = {
32
+ // Cool navy-black; darker and more blue-shifted than any warm greyRamp step.
33
+ textPrimaryLight: "#121828",
34
+ // Cool blue-grey; outside greyRamp's warm (R>=G>=B) scale.
35
+ textSecondaryLight: "#65748B",
36
+ // Pure neutral grey (R=G=B); no warm greyRamp step lands on it exactly.
37
+ backgroundBaseLight: "#EBEBEB",
38
+ // Near-neutral grey, kept one step off backgroundBaseLight by design.
39
+ dividerLight: "#E8E9EB",
40
+ // Periwinkle/indigo; more purple than any primitiveRamps.blue step. Shared by
41
+ // dark-mode text-link, border-focus and border-input-focus.
42
+ focusIndigoDark: "#828DF8"
43
+ };
22
44
  var greyRamp = {
23
45
  50: "#F9F6F3",
24
46
  // L*97.0 W6
@@ -52,6 +74,7 @@ var greyRamp = {
52
74
  var primitiveRamps = {
53
75
  red: {
54
76
  50: "#FFF4F4",
77
+ 400: "#F77175",
55
78
  500: "#E05254",
56
79
  600: "#D14343",
57
80
  // pin
@@ -141,22 +164,34 @@ var semanticColorsLight = {
141
164
  "brand-primary-light": primitiveRamps.orange[300],
142
165
  "brand-primary-dark": primitiveRamps.orange[500],
143
166
  "brand-primary-subtle": "rgba(255, 121, 0, 0.08)",
167
+ "brand-primary-muted": "rgba(255, 121, 0, 0.30)",
168
+ "brand-primary-strong": "rgba(255, 121, 0, 0.50)",
144
169
  "brand-primary-hover": primitiveRamps.orange[500],
145
170
  "brand-primary-active": primitiveRamps.orange[600],
146
171
  "brand-secondary": primitiveRamps.cyan[600],
147
172
  "brand-secondary-light": primitiveRamps.cyan[500],
148
173
  "brand-secondary-dark": primitiveRamps.cyan[700],
149
174
  "brand-secondary-subtle": "rgba(48, 123, 155, 0.08)",
175
+ "brand-secondary-muted": "rgba(48, 123, 155, 0.30)",
176
+ "brand-secondary-strong": "rgba(48, 123, 155, 0.50)",
150
177
  "brand-secondary-hover": primitiveRamps.cyan[700],
151
178
  "brand-secondary-active": primitiveRamps.cyan[800],
152
179
  // Text on brand backgrounds (on-*)
153
180
  "on-brand-primary": primitiveColors.white,
154
181
  "on-brand-secondary": primitiveColors.white,
182
+ // Text ON a `-subtle` fill. Light mode keeps today's pairing (the base token) so
183
+ // nothing moves here: light has the same disease mirrored — a light rung on a
184
+ // near-white ramp[50] fill — but correcting it belongs with the light-mode pass
185
+ // in AW-121, not this one.
186
+ "on-brand-primary-subtle": primitiveRamps.orange[400],
187
+ "on-brand-secondary-subtle": primitiveRamps.cyan[600],
155
188
  // Status colors (status-*)
156
189
  "status-success": primitiveRamps.green[300],
157
190
  "status-success-light": primitiveRamps.green[200],
158
191
  "status-success-dark": primitiveRamps.green[600],
159
192
  "status-success-subtle": primitiveRamps.green[50],
193
+ "status-success-muted": "rgba(46, 213, 115, 0.30)",
194
+ "status-success-strong": "rgba(46, 213, 115, 0.50)",
160
195
  // Live-session accent — its OWN role, decoupled from success so the two can diverge
161
196
  "status-live": primitiveRamps.green[300],
162
197
  "status-live-muted": primitiveRamps.green[500],
@@ -164,34 +199,47 @@ var semanticColorsLight = {
164
199
  "status-error-light": primitiveRamps.red[500],
165
200
  "status-error-dark": primitiveRamps.red[700],
166
201
  "status-error-subtle": primitiveRamps.red[50],
167
- "status-error-vivid": primitiveRamps.red[600],
168
- // vivid alert red
202
+ "status-error-muted": "rgba(209, 67, 67, 0.30)",
203
+ "status-error-strong": "rgba(209, 67, 67, 0.50)",
204
+ "status-error-vivid": alertRedVivid,
205
+ // NOT red[600], that is status-error
169
206
  "status-error-vivid-light": primitiveRamps.red[500],
170
207
  "status-error-vivid-dark": primitiveRamps.red[700],
171
208
  "status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
209
+ "status-error-vivid-muted": "rgba(255, 71, 87, 0.30)",
210
+ "status-error-vivid-strong": "rgba(255, 71, 87, 0.50)",
172
211
  "status-warning": primitiveRamps.amber[300],
173
212
  "status-warning-light": primitiveRamps.amber[200],
174
213
  "status-warning-dark": primitiveRamps.amber[500],
175
214
  "status-warning-subtle": primitiveRamps.amber[50],
215
+ "status-warning-muted": "rgba(249, 180, 21, 0.30)",
216
+ "status-warning-strong": "rgba(249, 180, 21, 0.50)",
176
217
  "status-info": primitiveRamps.blue[500],
177
218
  "status-info-light": primitiveRamps.blue[300],
178
219
  "status-info-dark": primitiveRamps.blue[600],
179
220
  "status-info-subtle": primitiveRamps.blue[50],
221
+ "status-info-muted": "rgba(33, 150, 243, 0.30)",
222
+ "status-info-strong": "rgba(33, 150, 243, 0.50)",
223
+ // Text ON a `-subtle` fill — see the on-brand-*-subtle note above.
224
+ "on-status-success-subtle": primitiveRamps.green[300],
225
+ "on-status-error-subtle": primitiveRamps.red[600],
226
+ "on-status-warning-subtle": primitiveRamps.amber[300],
227
+ "on-status-info-subtle": primitiveRamps.blue[500],
180
228
  // Text on status backgrounds (on-status-*)
181
229
  "on-status-success": primitiveColors.white,
182
230
  "on-status-error": primitiveColors.white,
183
231
  "on-status-warning": primitiveColors.white,
184
232
  "on-status-info": primitiveColors.white,
185
233
  // Result/outcome indicators (result-*)
186
- "result-improve": "#4caf50",
234
+ "result-improve": resultPaletteColors.improve,
187
235
  // Green - positive outcome
188
236
  "result-improve-light": "rgba(76, 175, 80, 0.12)",
189
- "result-improve-dark": "#248a24",
190
- "result-degrade": "#ef5350",
237
+ "result-improve-dark": resultPaletteColors.improveDark,
238
+ "result-degrade": resultPaletteColors.degrade,
191
239
  // Red - negative outcome
192
240
  "result-degrade-light": "rgba(239, 83, 80, 0.12)",
193
- "result-degrade-dark": "#b30000",
194
- "result-inconclusive": "#9E9A97",
241
+ "result-degrade-dark": resultPaletteColors.degradeDark,
242
+ "result-inconclusive": resultPaletteColors.inconclusive,
195
243
  // Gray - no clear result
196
244
  "result-inconclusive-light": "rgba(158, 154, 151, 0.12)",
197
245
  "result-neutral": greyRamp[600],
@@ -223,8 +271,8 @@ var semanticColorsLight = {
223
271
  "data-10": discreteRainbow[22],
224
272
  // Dark Orange
225
273
  // Text colors (text-*)
226
- "text-primary": "#121828",
227
- "text-secondary": "#65748B",
274
+ "text-primary": semanticPins.textPrimaryLight,
275
+ "text-secondary": semanticPins.textSecondaryLight,
228
276
  "text-tertiary": greyRamp[400],
229
277
  "text-disabled": "rgba(55, 65, 81, 0.48)",
230
278
  "text-inverse": primitiveColors.white,
@@ -240,7 +288,7 @@ var semanticColorsLight = {
240
288
  "surface-input": greyRamp[50],
241
289
  // Input field background (filled variant)
242
290
  // Background colors (background-*)
243
- "background-base": "#EBEBEB",
291
+ "background-base": semanticPins.backgroundBaseLight,
244
292
  "background-default": primitiveColors.white,
245
293
  "background-subtle": greyRamp[50],
246
294
  // Frame/bezel chrome — top bar + side nav shell, one step below
@@ -271,7 +319,7 @@ var semanticColorsLight = {
271
319
  "interactive-disabled": "rgba(55, 65, 81, 0.12)",
272
320
  "interactive-disabled-text": "rgba(55, 65, 81, 0.26)",
273
321
  // Divider
274
- "divider": "#E8E9EB",
322
+ divider: semanticPins.dividerLight,
275
323
  // Avatar default
276
324
  "avatar-background": greyRamp[600],
277
325
  "avatar-text": primitiveColors.white
@@ -282,54 +330,87 @@ var semanticColorsDark = {
282
330
  "brand-primary-light": primitiveRamps.orange[300],
283
331
  "brand-primary-dark": primitiveRamps.orange[500],
284
332
  "brand-primary-subtle": "rgba(255, 121, 0, 0.12)",
333
+ "brand-primary-muted": "rgba(255, 121, 0, 0.30)",
334
+ "brand-primary-strong": "rgba(255, 121, 0, 0.50)",
285
335
  "brand-primary-hover": primitiveRamps.orange[300],
286
336
  "brand-primary-active": primitiveRamps.orange[200],
287
337
  "brand-secondary": primitiveRamps.cyan[600],
288
338
  "brand-secondary-light": primitiveRamps.cyan[500],
289
339
  "brand-secondary-dark": primitiveRamps.cyan[700],
290
- "brand-secondary-subtle": "rgba(48, 123, 155, 0.12)",
340
+ "brand-secondary-subtle": "rgba(34, 211, 238, 0.12)",
341
+ "brand-secondary-muted": "rgba(48, 123, 155, 0.30)",
342
+ "brand-secondary-strong": "rgba(48, 123, 155, 0.50)",
291
343
  "brand-secondary-hover": primitiveRamps.cyan[500],
292
344
  "brand-secondary-active": primitiveRamps.cyan[400],
293
345
  // Text on brand backgrounds
294
346
  "on-brand-primary": primitiveColors.white,
295
347
  "on-brand-secondary": primitiveColors.white,
348
+ // Text ON a `-subtle` fill. Its own role: `brand-primary` and friends are tuned to
349
+ // carry a white label as a solid fill, which makes the two deepest of them
350
+ // (cyan[600], red[600]) too dark to read as text on a dark plane. Levelling the
351
+ // family at rung 300 puts every tone within OKLCH L 0.769-0.813 instead of
352
+ // 0.550-0.813. Brand is deliberately left at its own orange[400] (operator, AW-133)
353
+ // so the Voltras tone never drifts; it is the one tone under AA on a raised card.
354
+ "on-brand-primary-subtle": primitiveRamps.orange[400],
355
+ "on-brand-secondary-subtle": primitiveRamps.cyan[300],
296
356
  // Status colors
297
357
  "status-success": primitiveRamps.green[300],
298
358
  "status-success-light": primitiveRamps.green[200],
299
359
  "status-success-dark": primitiveRamps.green[600],
300
360
  "status-success-subtle": "rgba(46, 213, 115, 0.12)",
361
+ "status-success-muted": "rgba(46, 213, 115, 0.30)",
362
+ "status-success-strong": "rgba(46, 213, 115, 0.50)",
301
363
  // Live-session accent — its OWN role, decoupled from success so the two can diverge
302
364
  "status-live": primitiveRamps.green[300],
303
365
  "status-live-muted": primitiveRamps.green[500],
304
366
  "status-error": primitiveRamps.red[600],
305
367
  "status-error-light": primitiveRamps.red[500],
306
368
  "status-error-dark": primitiveRamps.red[700],
307
- "status-error-subtle": "rgba(209, 67, 67, 0.12)",
308
- "status-error-vivid": primitiveRamps.red[600],
369
+ // Alpha 0.08, not the family's 0.12: error's label is red[400], a rung darker than
370
+ // its siblings so it reads RED rather than pink (rung 300 has only 0.121 chroma).
371
+ // Thinning the fill buys back the contrast that extra darkness costs. See AW-133.
372
+ "status-error-subtle": "rgba(247, 113, 117, 0.08)",
373
+ "status-error-muted": "rgba(209, 67, 67, 0.30)",
374
+ "status-error-strong": "rgba(209, 67, 67, 0.50)",
375
+ "status-error-vivid": alertRedVivid,
309
376
  "status-error-vivid-light": primitiveRamps.red[500],
310
377
  "status-error-vivid-dark": primitiveRamps.red[700],
311
378
  "status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
379
+ "status-error-vivid-muted": "rgba(255, 71, 87, 0.30)",
380
+ "status-error-vivid-strong": "rgba(255, 71, 87, 0.50)",
312
381
  "status-warning": primitiveRamps.amber[300],
313
382
  "status-warning-light": primitiveRamps.amber[200],
314
383
  "status-warning-dark": primitiveRamps.amber[500],
315
384
  "status-warning-subtle": "rgba(249, 180, 21, 0.12)",
385
+ "status-warning-muted": "rgba(249, 180, 21, 0.30)",
386
+ "status-warning-strong": "rgba(249, 180, 21, 0.50)",
316
387
  "status-info": primitiveRamps.blue[500],
317
388
  "status-info-light": primitiveRamps.blue[300],
318
389
  "status-info-dark": primitiveRamps.blue[600],
319
- "status-info-subtle": "rgba(33, 150, 243, 0.12)",
390
+ "status-info-subtle": "rgba(120, 194, 255, 0.12)",
391
+ "status-info-muted": "rgba(33, 150, 243, 0.30)",
392
+ "status-info-strong": "rgba(33, 150, 243, 0.50)",
320
393
  // Text on status backgrounds
321
394
  "on-status-success": primitiveColors.white,
322
395
  "on-status-error": primitiveColors.white,
323
396
  "on-status-warning": primitiveColors.white,
324
397
  "on-status-info": primitiveColors.white,
398
+ // Text ON a `-subtle` fill — see the on-brand-*-subtle note above. Error is the
399
+ // second deliberate exception to the rung-300 rule (operator, AW-133): red[300]
400
+ // levelled perfectly but read PINK, because a red that light can only hold 0.121
401
+ // chroma. red[400] carries 0.165 and reads red; its fill is thinned to compensate.
402
+ "on-status-success-subtle": primitiveRamps.green[300],
403
+ "on-status-error-subtle": primitiveRamps.red[400],
404
+ "on-status-warning-subtle": primitiveRamps.amber[300],
405
+ "on-status-info-subtle": primitiveRamps.blue[300],
325
406
  // Result/outcome indicators (result-*)
326
- "result-improve": "#4caf50",
407
+ "result-improve": resultPaletteColors.improve,
327
408
  "result-improve-light": "rgba(76, 175, 80, 0.16)",
328
- "result-improve-dark": "#248a24",
329
- "result-degrade": "#ef5350",
409
+ "result-improve-dark": resultPaletteColors.improveDark,
410
+ "result-degrade": resultPaletteColors.degrade,
330
411
  "result-degrade-light": "rgba(239, 83, 80, 0.16)",
331
- "result-degrade-dark": "#b30000",
332
- "result-inconclusive": "#9E9A97",
412
+ "result-degrade-dark": resultPaletteColors.degradeDark,
413
+ "result-inconclusive": resultPaletteColors.inconclusive,
333
414
  "result-inconclusive-light": "rgba(158, 154, 151, 0.16)",
334
415
  "result-neutral": greyRamp[400],
335
416
  // Text on result backgrounds
@@ -360,7 +441,7 @@ var semanticColorsDark = {
360
441
  "text-tertiary": greyRamp[500],
361
442
  "text-disabled": "rgba(255, 255, 255, 0.38)",
362
443
  "text-inverse": greyRamp[950],
363
- "text-link": "#828DF8",
444
+ "text-link": semanticPins.focusIndigoDark,
364
445
  "text-link-hover": primitiveRamps.blue[400],
365
446
  // Surface colors - dark backgrounds — warm-tapered DERIVED ramp (TD-surface-tokens,
366
447
  // S-1, re-spaced S-3). Shipped verbatim from `deriveSurfaceRamp()` — see
@@ -410,10 +491,10 @@ var semanticColorsDark = {
410
491
  // plane, so it keeps its job without collision.
411
492
  "border-prominent": greyRamp[800],
412
493
  // high-visibility divider
413
- "border-focus": "#828DF8",
494
+ "border-focus": semanticPins.focusIndigoDark,
414
495
  "border-input": greyRamp[700],
415
496
  "border-input-hover": greyRamp[600],
416
- "border-input-focus": "#828DF8",
497
+ "border-input-focus": semanticPins.focusIndigoDark,
417
498
  "border-input-error": primitiveRamps.red[500],
418
499
  // Alpha hairline separators — the primary separation cue (§4/S-2). Self-
419
500
  // normalizing: composites toward white by ~the same amount on ANY plane, so
@@ -456,7 +537,7 @@ var semanticColorsDark = {
456
537
  "interactive-disabled": "rgba(255, 255, 255, 0.12)",
457
538
  "interactive-disabled-text": "rgba(255, 255, 255, 0.26)",
458
539
  // Divider
459
- "divider": "rgba(255, 255, 255, 0.09)",
540
+ divider: "rgba(255, 255, 255, 0.09)",
460
541
  // Avatar default
461
542
  "avatar-background": greyRamp[700],
462
543
  "avatar-text": primitiveColors.white
@@ -470,6 +551,182 @@ function resolveColor(token, mode = "dark") {
470
551
  return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
471
552
  }
472
553
 
554
+ // src/theme/color-utils.ts
555
+ function hexToRgb(hex) {
556
+ const cleaned = hex.replace("#", "");
557
+ let r, g, b;
558
+ if (cleaned.length === 3) {
559
+ r = parseInt(cleaned[0] + cleaned[0], 16);
560
+ g = parseInt(cleaned[1] + cleaned[1], 16);
561
+ b = parseInt(cleaned[2] + cleaned[2], 16);
562
+ } else if (cleaned.length === 6) {
563
+ r = parseInt(cleaned.slice(0, 2), 16);
564
+ g = parseInt(cleaned.slice(2, 4), 16);
565
+ b = parseInt(cleaned.slice(4, 6), 16);
566
+ } else {
567
+ return null;
568
+ }
569
+ return { r, g, b };
570
+ }
571
+
572
+ // src/utils/colors.ts
573
+ function alpha(color, opacity) {
574
+ const clampedOpacity = Math.max(0, Math.min(1, opacity));
575
+ if (color.startsWith("#")) {
576
+ const hex = color.slice(1);
577
+ let r, g, b;
578
+ if (hex.length === 3) {
579
+ r = parseInt(hex[0] + hex[0], 16);
580
+ g = parseInt(hex[1] + hex[1], 16);
581
+ b = parseInt(hex[2] + hex[2], 16);
582
+ } else if (hex.length === 6) {
583
+ r = parseInt(hex.slice(0, 2), 16);
584
+ g = parseInt(hex.slice(2, 4), 16);
585
+ b = parseInt(hex.slice(4, 6), 16);
586
+ } else {
587
+ return color;
588
+ }
589
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
590
+ }
591
+ const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
592
+ if (rgbMatch) {
593
+ const [, r, g, b] = rgbMatch;
594
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
595
+ }
596
+ const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
597
+ if (rgbaMatch) {
598
+ const [, r, g, b] = rgbaMatch;
599
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
600
+ }
601
+ return color;
602
+ }
603
+
604
+ // src/theme/materials.ts
605
+ var c = getSemanticColors("dark");
606
+ function insetWell(tone = c["surface-input"]) {
607
+ return {
608
+ backgroundColor: tone,
609
+ boxShadow: "inset 0 2px 6px rgba(0,0,0,0.55), inset 0 -1px 0 rgba(255,255,255,0.12)"
610
+ };
611
+ }
612
+ var FLOATING_LIFT_MIN = 4;
613
+ var LIFT_RIM_ALPHA = { dark: 0.12, light: 0.9 };
614
+ var AMBIENT = {
615
+ 1: [
616
+ { y: 1, blur: 2, alpha: 0.35 },
617
+ { y: 2, blur: 6, alpha: 0.3 }
618
+ ],
619
+ 2: [
620
+ { y: 1, blur: 2, alpha: 0.35 },
621
+ { y: 4, blur: 12, alpha: 0.32 }
622
+ ],
623
+ 3: [
624
+ { y: 2, blur: 4, alpha: 0.35 },
625
+ { y: 8, blur: 22, alpha: 0.35 }
626
+ ],
627
+ 4: [
628
+ { y: 1, blur: 2, alpha: 0.35 },
629
+ { y: 10, blur: 24, alpha: 0.3 },
630
+ { y: 24, blur: 48, alpha: 0.2 }
631
+ ],
632
+ 5: [
633
+ { y: 2, blur: 4, alpha: 0.4 },
634
+ { y: 16, blur: 32, alpha: 0.35 },
635
+ { y: 32, blur: 64, alpha: 0.25 }
636
+ ]
637
+ };
638
+ var AMBIENT_ALPHA_SCALE = { dark: 1, light: 0.4 };
639
+ function ambientLayers(step, mode) {
640
+ const scale = AMBIENT_ALPHA_SCALE[mode];
641
+ return AMBIENT[step].map(
642
+ ({ y, blur, alpha: alpha2 }) => `0 ${y}px ${blur}px rgba(0,0,0,${(alpha2 * scale).toFixed(2)})`
643
+ );
644
+ }
645
+ function liftShadow(step, mode = "dark", opts = {}) {
646
+ const rim = opts.rim ?? LIFT_RIM_ALPHA[mode];
647
+ const layers = ambientLayers(step, mode);
648
+ if (rim > 0) layers.unshift(`inset 0 1px 0 rgba(255,255,255,${rim.toFixed(2)})`);
649
+ return layers.join(", ");
650
+ }
651
+ function liftStyle(step, mode = "dark", opts = {}) {
652
+ const deepest = AMBIENT[step][AMBIENT[step].length - 1];
653
+ return reactNative.Platform.select({
654
+ web: { boxShadow: liftShadow(step, mode, opts) },
655
+ default: {
656
+ shadowColor: "#000",
657
+ shadowOffset: { width: 0, height: Math.round(deepest.y / 2) },
658
+ shadowOpacity: deepest.alpha * AMBIENT_ALPHA_SCALE[mode],
659
+ shadowRadius: deepest.blur / 2,
660
+ elevation: step
661
+ }
662
+ });
663
+ }
664
+
665
+ // src/theme/surface-planes.ts
666
+ var SURFACE_LEVEL_TOKEN = {
667
+ frame: "background-frame",
668
+ background: "background-base",
669
+ base: "surface-base",
670
+ elevated: "surface-elevated",
671
+ raised: "surface-raised",
672
+ overlay: "surface-overlay"
673
+ };
674
+ var PLANE_ORDER = [
675
+ "frame",
676
+ "background",
677
+ "base",
678
+ "elevated",
679
+ "raised",
680
+ "overlay"
681
+ ];
682
+ function surfaceBackground(level, mode) {
683
+ return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]];
684
+ }
685
+ function stepPlane(from, delta) {
686
+ const index = PLANE_ORDER.indexOf(from);
687
+ const clamped = Math.min(PLANE_ORDER.length - 1, Math.max(0, index + delta));
688
+ return PLANE_ORDER[clamped];
689
+ }
690
+ function pressedLevel(parent) {
691
+ return stepPlane(parent, -1);
692
+ }
693
+ function raisedLevel(parent, steps) {
694
+ return stepPlane(parent, Math.max(0, steps));
695
+ }
696
+
697
+ // src/theme/elevation.ts
698
+ var FLOATING_ELEVATION_MIN = FLOATING_LIFT_MIN;
699
+ function getPressedRecessShadow(fillColor, _mode = "dark") {
700
+ const { boxShadow } = insetWell(fillColor);
701
+ return reactNative.Platform.select({
702
+ web: { boxShadow },
703
+ default: {}
704
+ });
705
+ }
706
+ var glowConfig = {
707
+ tight: { blur: 4, spread: 0, opacity: 0.4 },
708
+ subtle: { blur: 12, spread: 0, opacity: 0.25 },
709
+ medium: { blur: 20, spread: 2, opacity: 0.4 },
710
+ strong: { blur: 30, spread: 4, opacity: 0.55 }
711
+ };
712
+ function getGlowShadow(color, intensity = "medium") {
713
+ const config = glowConfig[intensity];
714
+ const rgb = hexToRgb(color);
715
+ if (!rgb) return {};
716
+ return reactNative.Platform.select({
717
+ web: {
718
+ boxShadow: `0 0 ${config.blur}px ${config.spread}px rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${config.opacity})`
719
+ },
720
+ default: {
721
+ shadowColor: color,
722
+ shadowOffset: { width: 0, height: 0 },
723
+ shadowOpacity: config.opacity,
724
+ shadowRadius: config.blur / 2,
725
+ elevation: 0
726
+ }
727
+ });
728
+ }
729
+
473
730
  // src/theme/workout-tokens.ts
474
731
  var WORKOUT_TOKENS = {
475
732
  // BodyMap volume heatmap — the canonical `divergingScale` (under → optimal →
@@ -694,8 +951,8 @@ var jsxs = wrapJsx(jsxRuntime.jsxs);
694
951
  // src/components/custom/Workout/BodyMap.tsx
695
952
  var Body = BodyHighlighter__default.default.default ?? BodyHighlighter__default.default;
696
953
  var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
697
- var OUTLINE_FILL = "rgba(255,255,255,0.08)";
698
- var OUTLINE_BORDER = "rgba(255,255,255,0.12)";
954
+ var OUTLINE_FILL = alpha(primitiveColors.white, 0.08);
955
+ var OUTLINE_BORDER = alpha(primitiveColors.white, 0.12);
699
956
  var BODY_SCALE = 0.8;
700
957
  function buildSlugParts(data) {
701
958
  const bySlug = /* @__PURE__ */ new Map();
@@ -797,7 +1054,8 @@ function BodyMap({
797
1054
  alignSelf: "center",
798
1055
  transform: [{ scale }]
799
1056
  },
800
- glowColor ? { boxShadow: `0 0 14px 2px ${glowColor}` } : void 0
1057
+ // The highlight halo is emphasis, not depth.
1058
+ glowColor ? getGlowShadow(glowColor, "medium") : void 0
801
1059
  ],
802
1060
  "aria-hidden": true,
803
1061
  accessibilityElementsHidden: true,
@@ -870,7 +1128,7 @@ function ViewToggle({ view, onViewChange }) {
870
1128
  paddingHorizontal: 12,
871
1129
  paddingVertical: 4,
872
1130
  borderRadius: 9999,
873
- backgroundColor: active ? "rgba(255,121,0,0.16)" : "transparent",
1131
+ backgroundColor: active ? alpha(BRAND_PRIMARY, 0.16) : "transparent",
874
1132
  borderWidth: 1,
875
1133
  borderColor: active ? BRAND_PRIMARY : resolveColor("hairline-strong")
876
1134
  },
@@ -959,6 +1217,17 @@ var colorStyles = {
959
1217
  info: "bg-status-info",
960
1218
  "error-vivid": "bg-status-error-vivid"
961
1219
  };
1220
+ var t = getSemanticColors("dark");
1221
+ var glowColors = {
1222
+ default: greyRamp[500],
1223
+ primary: t["brand-primary"],
1224
+ success: t["status-success"],
1225
+ live: t["status-live"],
1226
+ error: t["status-error"],
1227
+ warning: t["status-warning"],
1228
+ info: t["status-info"],
1229
+ "error-vivid": t["status-error-vivid"]
1230
+ };
962
1231
  function Indicator({
963
1232
  size = "sm",
964
1233
  color = "default",
@@ -996,11 +1265,7 @@ function Indicator({
996
1265
  )
997
1266
  ] });
998
1267
  }
999
- const dynamicStyle = {
1000
- ...typeof style === "object" ? style : {},
1001
- ...colorStyle ?? {},
1002
- ...glow && customColor ? { boxShadow: `0 0 6px ${customColor}` } : {}
1003
- };
1268
+ const glowStyle = glow ? getGlowShadow(customColor ?? glowColors[color], "subtle") : null;
1004
1269
  return /* @__PURE__ */ jsx(
1005
1270
  reactNative.View,
1006
1271
  {
@@ -1010,54 +1275,168 @@ function Indicator({
1010
1275
  colorClass,
1011
1276
  pulseMode === "opacity" && "animate-pulse",
1012
1277
  ring && "border-2 border-background-base",
1013
- glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
1014
- glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
1015
- glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
1016
- glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
1017
- glow && !customColor && color === "error-vivid" && "shadow-[0_0_6px_rgba(255,71,87,0.6)]",
1018
1278
  className
1019
1279
  ),
1020
- style: Object.keys(dynamicStyle).length > 0 ? dynamicStyle : style,
1280
+ style: [style, colorStyle, glowStyle],
1021
1281
  ...props
1022
1282
  }
1023
1283
  );
1024
1284
  }
1025
-
1026
- // src/components/ui/badge/Badge.tsx
1027
- var variantColorStyles = {
1285
+ var colorToTone = {
1286
+ default: "neutral",
1287
+ primary: "brand",
1288
+ secondary: "brand-secondary",
1289
+ success: "success",
1290
+ error: "error",
1291
+ warning: "warning",
1292
+ info: "info"
1293
+ };
1294
+ var toneStyles = {
1028
1295
  solid: {
1029
- default: "bg-hairline-strong text-text-inverse",
1030
- primary: "bg-brand-primary text-text-inverse",
1031
- secondary: "bg-brand-secondary text-text-inverse",
1032
- success: "bg-status-success text-text-inverse",
1033
- error: "bg-status-error text-text-inverse",
1034
- warning: "bg-status-warning text-text-inverse",
1035
- info: "bg-status-info text-text-inverse"
1296
+ neutral: "bg-hairline-strong border-transparent text-text-inverse",
1297
+ brand: "bg-brand-primary border-transparent text-text-inverse",
1298
+ "brand-secondary": "bg-brand-secondary border-transparent text-text-inverse",
1299
+ success: "bg-status-success border-transparent text-text-inverse",
1300
+ warning: "bg-status-warning border-transparent text-text-inverse",
1301
+ error: "bg-status-error border-transparent text-text-inverse",
1302
+ info: "bg-status-info border-transparent text-text-inverse"
1036
1303
  },
1037
1304
  subtle: {
1038
- default: "bg-surface-raised text-text-secondary",
1039
- primary: "bg-brand-primary-subtle text-brand-primary",
1040
- secondary: "bg-brand-secondary-subtle text-brand-secondary",
1041
- success: "bg-status-success-subtle text-status-success",
1042
- error: "bg-status-error-subtle text-status-error",
1043
- warning: "bg-status-warning-subtle text-status-warning",
1044
- info: "bg-status-info-subtle text-status-info"
1305
+ // Alpha-white rather than a ramp step: a pill sits on whatever plane its
1306
+ // host is, and an alpha fill composites by the same amount on all of them.
1307
+ // A `surface-*` fill would vanish on the plane it names. Borderless like the
1308
+ // six coloured tones — the fill carries the capsule, not a ring.
1309
+ // Label comes from `on-*-subtle`, not the base tone token: the base is tuned to
1310
+ // carry a white label as a solid fill, which left error and accent unreadable
1311
+ // here (AW-133). The on-subtle tokens level the family at one weight.
1312
+ neutral: "bg-hairline-subtle border-transparent text-text-secondary",
1313
+ brand: "bg-brand-primary-subtle border-transparent text-on-brand-primary-subtle",
1314
+ "brand-secondary": "bg-brand-secondary-subtle border-transparent text-on-brand-secondary-subtle",
1315
+ success: "bg-status-success-subtle border-transparent text-on-status-success-subtle",
1316
+ warning: "bg-status-warning-subtle border-transparent text-on-status-warning-subtle",
1317
+ error: "bg-status-error-subtle border-transparent text-on-status-error-subtle",
1318
+ info: "bg-status-info-subtle border-transparent text-on-status-info-subtle"
1045
1319
  },
1046
1320
  outline: {
1047
- default: "border border-hairline-strong text-text-secondary",
1048
- primary: "border border-brand-primary text-brand-primary",
1049
- secondary: "border border-brand-secondary text-brand-secondary",
1050
- success: "border border-status-success text-status-success",
1051
- error: "border border-status-error text-status-error",
1052
- warning: "border border-status-warning text-status-warning",
1053
- info: "border border-status-info text-status-info"
1321
+ neutral: "border-hairline text-text-secondary",
1322
+ brand: "border-brand-primary text-brand-primary",
1323
+ "brand-secondary": "border-brand-secondary text-brand-secondary",
1324
+ success: "border-status-success text-status-success",
1325
+ warning: "border-status-warning text-status-warning",
1326
+ error: "border-status-error text-status-error",
1327
+ info: "border-status-info text-status-info"
1054
1328
  }
1055
1329
  };
1330
+ var dotToneStyles = {
1331
+ neutral: "bg-text-tertiary",
1332
+ brand: "bg-brand-primary",
1333
+ "brand-secondary": "bg-brand-secondary",
1334
+ success: "bg-status-success",
1335
+ warning: "bg-status-warning",
1336
+ error: "bg-status-error",
1337
+ info: "bg-status-info"
1338
+ };
1056
1339
  var sizeStyles2 = {
1057
- sm: "px-1.5 py-0.5 text-xs",
1058
- md: "px-2 py-0.5 text-xs",
1059
- lg: "px-2.5 py-1 text-sm"
1340
+ xs: { container: "px-1 py-px", text: "text-3xs" },
1341
+ sm: { container: "px-2 py-0.5", text: "text-2xs" },
1342
+ md: { container: "px-2.5 py-1", text: "text-xs" },
1343
+ lg: { container: "px-3 py-1.5", text: "text-sm" },
1344
+ xl: { container: "px-4 py-2", text: "text-base" }
1345
+ };
1346
+ function PillDot({ tone, testID }) {
1347
+ return /* @__PURE__ */ jsx(
1348
+ reactNative.View,
1349
+ {
1350
+ className: cn("w-1.5 h-1.5 rounded-full shrink-0", dotToneStyles[tone]),
1351
+ accessibilityElementsHidden: true,
1352
+ testID: testID ? `${testID}-dot` : "pill-dot"
1353
+ }
1354
+ );
1355
+ }
1356
+ function containerClasses(p, tone) {
1357
+ return cn(
1358
+ "flex-row items-center gap-1 border self-start shrink-0",
1359
+ p.rounded === false ? "rounded" : "rounded-full",
1360
+ toneStyles[p.variant ?? "subtle"][tone],
1361
+ sizeStyles2[p.size ?? "sm"].container,
1362
+ p.isDisabled && "opacity-50",
1363
+ p.className
1364
+ );
1365
+ }
1366
+ function PillContent({ tone, ...p }) {
1367
+ const textClasses = cn(
1368
+ "font-heading font-semibold text-inherit",
1369
+ sizeStyles2[p.size ?? "sm"].text,
1370
+ p.textClassName
1371
+ );
1372
+ const slot = p.leading ?? p.leftElement;
1373
+ return /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
1374
+ slot === "dot" ? /* @__PURE__ */ jsx(PillDot, { tone: p.dotTone ?? tone, testID: p.testID }) : slot,
1375
+ typeof p.children === "string" ? /* @__PURE__ */ jsx(reactNative.Text, { className: textClasses, children: p.children }) : p.children,
1376
+ p.trailing
1377
+ ] });
1378
+ }
1379
+ function Pill(props) {
1380
+ const {
1381
+ variant,
1382
+ tone,
1383
+ color,
1384
+ size,
1385
+ rounded,
1386
+ leading,
1387
+ dotTone,
1388
+ leftElement,
1389
+ trailing,
1390
+ onPress,
1391
+ isDisabled,
1392
+ className,
1393
+ textClassName,
1394
+ children,
1395
+ ...viewProps
1396
+ } = props;
1397
+ const resolvedTone = tone ?? (color ? colorToTone[color] : "neutral");
1398
+ const classes = containerClasses(props, resolvedTone);
1399
+ const content = /* @__PURE__ */ jsx(PillContent, { ...props, tone: resolvedTone });
1400
+ if (onPress) {
1401
+ return /* @__PURE__ */ jsx(
1402
+ reactNative.Pressable,
1403
+ {
1404
+ onPress,
1405
+ disabled: isDisabled,
1406
+ accessibilityRole: "button",
1407
+ className: classes,
1408
+ ...viewProps,
1409
+ children: content
1410
+ }
1411
+ );
1412
+ }
1413
+ return /* @__PURE__ */ jsx(reactNative.View, { className: classes, ...viewProps, children: content });
1414
+ }
1415
+
1416
+ // src/components/ui/badge/Badge.tsx
1417
+ var colorToTone2 = {
1418
+ default: "neutral",
1419
+ primary: "brand",
1420
+ secondary: "brand-secondary",
1421
+ success: "success",
1422
+ error: "error",
1423
+ warning: "warning",
1424
+ info: "info"
1425
+ };
1426
+ var sizeStyles3 = {
1427
+ sm: "px-1.5 py-0.5",
1428
+ md: "px-2 py-0.5",
1429
+ lg: "px-2.5 py-1"
1060
1430
  };
1431
+ var textSizeStyles = {
1432
+ sm: "text-xs",
1433
+ md: "text-xs",
1434
+ lg: "text-sm"
1435
+ };
1436
+ function dotIndicatorColor(color, dotColor) {
1437
+ if (dotColor) return dotColor;
1438
+ return color === "default" || color === "secondary" ? "default" : color;
1439
+ }
1061
1440
  function Badge({
1062
1441
  variant = "subtle",
1063
1442
  color = "default",
@@ -1068,31 +1447,116 @@ function Badge({
1068
1447
  children,
1069
1448
  ...props
1070
1449
  }) {
1071
- return /* @__PURE__ */ jsxs(
1072
- reactNative.View,
1450
+ return /* @__PURE__ */ jsx(
1451
+ Pill,
1073
1452
  {
1453
+ variant,
1454
+ tone: colorToTone2[color],
1455
+ leading: dot ? /* @__PURE__ */ jsx(Indicator, { size: "xs", color: dotIndicatorColor(color, dotColor), className: "mr-0.5" }) : void 0,
1074
1456
  className: cn(
1075
- "inline-flex items-center justify-center rounded-full font-medium",
1076
- variantColorStyles[variant][color],
1077
- sizeStyles2[size],
1457
+ "justify-center self-auto gap-0",
1458
+ variant === "outline" ? "border" : "border-0",
1459
+ sizeStyles3[size],
1078
1460
  className
1079
1461
  ),
1462
+ textClassName: cn("font-sans font-medium", textSizeStyles[size]),
1080
1463
  ...props,
1081
- children: [
1082
- dot && /* @__PURE__ */ jsx(
1083
- Indicator,
1084
- {
1085
- size: "xs",
1086
- color: dotColor ?? (color === "default" || color === "secondary" ? "default" : color),
1087
- className: "mr-0.5"
1088
- }
1089
- ),
1090
- typeof children === "string" ? /* @__PURE__ */ jsx(reactNative.Text, { className: "text-inherit font-medium", children }) : children
1091
- ]
1464
+ children
1465
+ }
1466
+ );
1467
+ }
1468
+ var variantStyles = {
1469
+ h1: "font-heading text-5xl font-bold leading-tight",
1470
+ h2: "font-heading text-4xl font-bold leading-tight",
1471
+ h3: "font-heading text-3xl font-bold leading-tight",
1472
+ h4: "font-heading text-2xl font-bold leading-tight",
1473
+ h5: "font-heading text-xl font-semibold leading-tight",
1474
+ h6: "font-heading text-lg font-semibold leading-tight",
1475
+ body1: "font-body text-base font-normal leading-normal",
1476
+ body2: "font-body text-sm font-normal leading-normal",
1477
+ subtitle1: "font-body text-base font-medium leading-relaxed",
1478
+ subtitle2: "font-body text-sm font-medium leading-normal",
1479
+ caption: "font-body text-xs font-normal leading-loose",
1480
+ overline: "font-body text-xs font-semibold uppercase tracking-widest leading-relaxed",
1481
+ button: "font-sans text-sm font-semibold leading-relaxed",
1482
+ // Monospace technical readouts (clocks, ids, metrics) and their all-caps label form.
1483
+ mono: "font-mono text-xs font-normal leading-normal",
1484
+ monoLabel: "font-mono text-xs font-bold uppercase tracking-wide leading-normal",
1485
+ // The sans (Inter) label pair: 10px for dense table/column headers, 12px bold for
1486
+ // badge and row labels. `overline` is the 12px font-body form of the same idea.
1487
+ microLabel: "font-sans text-2xs font-semibold uppercase tracking-widest leading-normal",
1488
+ boldLabel: "font-sans text-xs font-bold leading-normal"
1489
+ };
1490
+ var colorStyles2 = {
1491
+ primary: "text-text-primary",
1492
+ secondary: "text-text-secondary",
1493
+ tertiary: "text-text-tertiary",
1494
+ disabled: "text-text-disabled",
1495
+ inverse: "text-text-inverse",
1496
+ success: "text-status-success",
1497
+ error: "text-status-error",
1498
+ warning: "text-status-warning",
1499
+ info: "text-status-info",
1500
+ inherit: ""
1501
+ // No color class, inherit from parent
1502
+ };
1503
+ var alignStyles = {
1504
+ left: "text-left",
1505
+ center: "text-center",
1506
+ right: "text-right",
1507
+ justify: "text-justify"
1508
+ };
1509
+ var headingVariants = ["h1", "h2", "h3", "h4", "h5", "h6"];
1510
+ var marginBottomStyles = {
1511
+ none: "",
1512
+ sm: "mb-1",
1513
+ md: "mb-2",
1514
+ lg: "mb-4"
1515
+ };
1516
+ function Typography({
1517
+ variant = "body1",
1518
+ color = "primary",
1519
+ align,
1520
+ truncate = false,
1521
+ noWrap = false,
1522
+ // deprecated, kept for backward compatibility
1523
+ marginBottom,
1524
+ gutterBottom = false,
1525
+ // deprecated, kept for backward compatibility
1526
+ maxLines,
1527
+ className,
1528
+ children,
1529
+ ...props
1530
+ }) {
1531
+ const isHeading = headingVariants.includes(variant);
1532
+ const shouldTruncate = truncate || noWrap;
1533
+ const mb = marginBottom || (gutterBottom ? "md" : "none");
1534
+ let numLines;
1535
+ if (maxLines) {
1536
+ numLines = maxLines;
1537
+ } else if (shouldTruncate) {
1538
+ numLines = 1;
1539
+ }
1540
+ return /* @__PURE__ */ jsx(
1541
+ reactNative.Text,
1542
+ {
1543
+ accessibilityRole: isHeading ? "header" : "text",
1544
+ numberOfLines: numLines,
1545
+ className: cn(
1546
+ variantStyles[variant],
1547
+ color !== "inherit" && colorStyles2[color],
1548
+ align && alignStyles[align],
1549
+ shouldTruncate && "truncate",
1550
+ marginBottomStyles[mb],
1551
+ className
1552
+ ),
1553
+ ...props,
1554
+ children
1092
1555
  }
1093
1556
  );
1094
1557
  }
1095
- var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
1558
+
1559
+ // src/components/custom/Workout/Sparkline.tsx
1096
1560
  function normalizeData(data, height) {
1097
1561
  if (data.length === 0) return [];
1098
1562
  const min = Math.min(...data);
@@ -1129,7 +1593,7 @@ function Sparkline({
1129
1593
  }
1130
1594
  );
1131
1595
  }
1132
- const resolvedColor = color ?? BRAND_PRIMARY2;
1596
+ const resolvedColor = color ?? resolveColor("brand-primary");
1133
1597
  const normalized = normalizeData(data, height);
1134
1598
  const stepX = data.length > 1 ? width / (data.length - 1) : 0;
1135
1599
  const dotSize = 3;
@@ -1169,14 +1633,19 @@ function Sparkline({
1169
1633
  ],
1170
1634
  accessibilityElementsHidden: true,
1171
1635
  testID: `sparkline-reference-${i}`,
1172
- children: line.label && /* @__PURE__ */ jsx(
1173
- reactNative.Text,
1636
+ children: line.label && // `3xs` (9px) is the scale floor; the label was 7px, which is off it
1637
+ // entirely (TOKENS.md §4). The line box stays unpinned, as the raw
1638
+ // <Text> this replaced was, so the absolute offset still lands.
1639
+ /* @__PURE__ */ jsx(
1640
+ Typography,
1174
1641
  {
1642
+ variant: "caption",
1643
+ color: "inherit",
1644
+ className: "text-3xs leading-[normal]",
1175
1645
  style: {
1176
1646
  position: "absolute",
1177
1647
  right: 0,
1178
1648
  top: -10,
1179
- fontSize: 7,
1180
1649
  color: line.color,
1181
1650
  opacity: 1
1182
1651
  },
@@ -1245,9 +1714,9 @@ function Sparkline({
1245
1714
  }
1246
1715
 
1247
1716
  // src/components/custom/Workout/BodyMapDetailPanel.tsx
1248
- var t = getSemanticColors("dark");
1249
- var BRAND_PRIMARY3 = t["brand-primary"];
1250
- var VOLUME_GRADIENT = `linear-gradient(90deg, ${t["status-info"]} 0%, ${t["status-success"]} 50%, ${t["status-error"]} 100%)`;
1717
+ var t2 = getSemanticColors("dark");
1718
+ var BRAND_PRIMARY2 = t2["brand-primary"];
1719
+ var VOLUME_GRADIENT = `linear-gradient(90deg, ${t2["status-info"]} 0%, ${t2["status-success"]} 50%, ${t2["status-error"]} 100%)`;
1251
1720
  var SLIDE_OFFSET = 400;
1252
1721
  var BACKDROP_OPACITY = 0.4;
1253
1722
  var STATUS_BADGE_COLOR = {
@@ -1312,7 +1781,14 @@ function BodyMapDetailPanel({
1312
1781
  return /* @__PURE__ */ jsxs(
1313
1782
  reactNative.View,
1314
1783
  {
1315
- style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, justifyContent: "flex-end" },
1784
+ style: {
1785
+ position: "absolute",
1786
+ top: 0,
1787
+ left: 0,
1788
+ right: 0,
1789
+ bottom: 0,
1790
+ justifyContent: "flex-end"
1791
+ },
1316
1792
  testID: "body-map-detail-panel-root",
1317
1793
  children: [
1318
1794
  /* @__PURE__ */ jsx(
@@ -1324,7 +1800,7 @@ function BodyMapDetailPanel({
1324
1800
  left: 0,
1325
1801
  right: 0,
1326
1802
  bottom: 0,
1327
- backgroundColor: "#000000",
1803
+ backgroundColor: primitiveColors.black,
1328
1804
  opacity: backdrop
1329
1805
  },
1330
1806
  testID: "body-map-detail-panel-backdrop-anim",
@@ -1447,8 +1923,22 @@ function BodyMapDetailPanel({
1447
1923
  style: { marginBottom: 6 },
1448
1924
  accessibilityElementsHidden: true,
1449
1925
  children: [
1450
- /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-tertiary", style: { fontSize: 10, fontFamily: "Inter, sans-serif", fontWeight: "600" }, children: `MEV ${landmarks.mev}` }),
1451
- /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-tertiary", style: { fontSize: 10, fontFamily: "Inter, sans-serif", fontWeight: "600" }, children: `MRV ${landmarks.mrv}` })
1926
+ /* @__PURE__ */ jsx(
1927
+ reactNative.Text,
1928
+ {
1929
+ className: "text-text-tertiary",
1930
+ style: { fontSize: 10, fontFamily: "Inter, sans-serif", fontWeight: "600" },
1931
+ children: `MEV ${landmarks.mev}`
1932
+ }
1933
+ ),
1934
+ /* @__PURE__ */ jsx(
1935
+ reactNative.Text,
1936
+ {
1937
+ className: "text-text-tertiary",
1938
+ style: { fontSize: 10, fontFamily: "Inter, sans-serif", fontWeight: "600" },
1939
+ children: `MRV ${landmarks.mrv}`
1940
+ }
1941
+ )
1452
1942
  ]
1453
1943
  }
1454
1944
  ),
@@ -1483,7 +1973,8 @@ function BodyMapDetailPanel({
1483
1973
  height: 14,
1484
1974
  borderRadius: 7,
1485
1975
  borderWidth: 2,
1486
- boxShadow: "0 0 4px rgba(0,0,0,0.5)"
1976
+ // A knob resting on the volume bar: lift, with the ring as its edge.
1977
+ ...liftStyle(1, "dark", { rim: 0 })
1487
1978
  },
1488
1979
  accessibilityElementsHidden: true,
1489
1980
  testID: "body-map-detail-panel-volume-marker"
@@ -1566,7 +2057,12 @@ function BodyMapDetailPanel({
1566
2057
  reactNative.Text,
1567
2058
  {
1568
2059
  className: "text-text-primary",
1569
- style: { flex: 1, fontSize: 13, fontFamily: "Inter, sans-serif", fontWeight: "600" },
2060
+ style: {
2061
+ flex: 1,
2062
+ fontSize: 13,
2063
+ fontFamily: "Inter, sans-serif",
2064
+ fontWeight: "600"
2065
+ },
1570
2066
  testID: `body-map-detail-panel-contributing-${index}-name`,
1571
2067
  children: exercise.name
1572
2068
  }
@@ -1610,7 +2106,12 @@ function BodyMapDetailPanel({
1610
2106
  reactNative.Text,
1611
2107
  {
1612
2108
  className: "text-text-primary",
1613
- style: { flex: 1, fontSize: 13, fontFamily: "Inter, sans-serif", fontWeight: "600" },
2109
+ style: {
2110
+ flex: 1,
2111
+ fontSize: 13,
2112
+ fontFamily: "Inter, sans-serif",
2113
+ fontWeight: "600"
2114
+ },
1614
2115
  testID: `body-map-detail-panel-upcoming-${index}-name`,
1615
2116
  children: exercise.name
1616
2117
  }
@@ -1640,12 +2141,23 @@ function BodyMapDetailPanel({
1640
2141
  paddingVertical: 10,
1641
2142
  borderRadius: 8,
1642
2143
  alignItems: "center",
1643
- backgroundColor: "rgba(255,121,0,0.12)",
2144
+ backgroundColor: alpha(BRAND_PRIMARY2, 0.12),
1644
2145
  borderWidth: 1,
1645
- borderColor: "rgba(255,121,0,0.3)"
2146
+ borderColor: alpha(BRAND_PRIMARY2, 0.3)
1646
2147
  },
1647
2148
  testID: "body-map-detail-panel-view-exercises",
1648
- children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 13, fontFamily: "Inter, sans-serif", fontWeight: "700", color: BRAND_PRIMARY3 }, children: "View exercises" })
2149
+ children: /* @__PURE__ */ jsx(
2150
+ reactNative.Text,
2151
+ {
2152
+ style: {
2153
+ fontSize: 13,
2154
+ fontFamily: "Inter, sans-serif",
2155
+ fontWeight: "700",
2156
+ color: BRAND_PRIMARY2
2157
+ },
2158
+ children: "View exercises"
2159
+ }
2160
+ )
1649
2161
  }
1650
2162
  )
1651
2163
  ]
@@ -1658,439 +2170,81 @@ function BodyMapDetailPanel({
1658
2170
  }
1659
2171
  );
1660
2172
  }
1661
-
1662
- // src/theme/color-utils.ts
1663
- function hexToRgb(hex) {
1664
- const cleaned = hex.replace("#", "");
1665
- let r, g, b;
1666
- if (cleaned.length === 3) {
1667
- r = parseInt(cleaned[0] + cleaned[0], 16);
1668
- g = parseInt(cleaned[1] + cleaned[1], 16);
1669
- b = parseInt(cleaned[2] + cleaned[2], 16);
1670
- } else if (cleaned.length === 6) {
1671
- r = parseInt(cleaned.slice(0, 2), 16);
1672
- g = parseInt(cleaned.slice(2, 4), 16);
1673
- b = parseInt(cleaned.slice(4, 6), 16);
1674
- } else {
1675
- return null;
1676
- }
1677
- return { r, g, b };
1678
- }
1679
- function rgbToHsv(r, g, b) {
1680
- const rNorm = r / 255;
1681
- const gNorm = g / 255;
1682
- const bNorm = b / 255;
1683
- const max = Math.max(rNorm, gNorm, bNorm);
1684
- const min = Math.min(rNorm, gNorm, bNorm);
1685
- const delta = max - min;
1686
- const v = max;
1687
- const s = max === 0 ? 0 : delta / max;
1688
- let h = 0;
1689
- if (delta !== 0) {
1690
- if (max === rNorm) {
1691
- h = 60 * ((gNorm - bNorm) / delta % 6);
1692
- } else if (max === gNorm) {
1693
- h = 60 * ((bNorm - rNorm) / delta + 2);
1694
- } else {
1695
- h = 60 * ((rNorm - gNorm) / delta + 4);
1696
- }
1697
- }
1698
- if (h < 0) h += 360;
1699
- return { h, s, v };
1700
- }
1701
- function hsvToRgb(h, s, v) {
1702
- const c = v * s;
1703
- const x = c * (1 - Math.abs(h / 60 % 2 - 1));
1704
- const m = v - c;
1705
- let rPrime, gPrime, bPrime;
1706
- if (h >= 0 && h < 60) {
1707
- rPrime = c;
1708
- gPrime = x;
1709
- bPrime = 0;
1710
- } else if (h >= 60 && h < 120) {
1711
- rPrime = x;
1712
- gPrime = c;
1713
- bPrime = 0;
1714
- } else if (h >= 120 && h < 180) {
1715
- rPrime = 0;
1716
- gPrime = c;
1717
- bPrime = x;
1718
- } else if (h >= 180 && h < 240) {
1719
- rPrime = 0;
1720
- gPrime = x;
1721
- bPrime = c;
1722
- } else if (h >= 240 && h < 300) {
1723
- rPrime = x;
1724
- gPrime = 0;
1725
- bPrime = c;
1726
- } else {
1727
- rPrime = c;
1728
- gPrime = 0;
1729
- bPrime = x;
1730
- }
1731
- return {
1732
- r: Math.round((rPrime + m) * 255),
1733
- g: Math.round((gPrime + m) * 255),
1734
- b: Math.round((bPrime + m) * 255)
1735
- };
1736
- }
1737
- function rgbToHex(r, g, b) {
1738
- const toHex = (n) => {
1739
- const clamped = Math.max(0, Math.min(255, Math.round(n)));
1740
- return clamped.toString(16).padStart(2, "0");
1741
- };
1742
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1743
- }
1744
- function lighten(hex, amount = 0.1) {
1745
- const rgb = hexToRgb(hex);
1746
- if (!rgb) return hex;
1747
- const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
1748
- const newV = Math.min(1, hsv.v + amount);
1749
- const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
1750
- return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
1751
- }
1752
- var elevationSystem = {
1753
- // Deep inset (recessed input fields, deeply pressed buttons)
1754
- [-2]: {
1755
- name: "deep-inset",
1756
- colorAdjustment: 0,
1757
- shadowIntensity: "strong",
1758
- shadowStyle: "pressed",
1759
- description: "Deeply recessed elements (e.g., input fields, deeply pressed buttons)"
1760
- },
1761
- // Shallow inset (pressed buttons, sunken panels)
1762
- [-1]: {
1763
- name: "inset",
1764
- colorAdjustment: 0,
1765
- shadowIntensity: "medium",
1766
- shadowStyle: "pressed",
1767
- description: "Shallow inset elements (e.g., pressed buttons, sunken panels within cards)"
1768
- },
1769
- // Base level (page background, default surface)
1770
- [0]: {
1771
- name: "base",
1772
- colorAdjustment: 0,
1773
- shadowIntensity: "subtle",
1774
- shadowStyle: "raised",
1775
- description: "Base surface level (page background, default containers)"
1776
- },
1777
- // Level 1: Subtle elevation (outline cards, subtle containers)
1778
- [1]: {
1779
- name: "subtle",
1780
- // The whole ladder was re-spaced in TD-07.16. It used to run
1781
- // 0.01/0.02/0.04/0.06/0.08, where the first step was ~2 RGB values — under
1782
- // the perceptual floor, so level 1 existed in the type system and nowhere on
1783
- // screen. With drop-shadow now gated off below FLOATING_ELEVATION_MIN, TONE
1784
- // is the only thing separating 0-3, so every step has to actually land.
1785
- // Even 0.025 spacing, verified in elevation.test.ts as ΔL* > 1 per step.
1786
- colorAdjustment: 0.025,
1787
- shadowIntensity: "subtle",
1788
- shadowStyle: "raised",
1789
- description: "Subtle elevation (outline cards, subtle containers)"
1790
- },
1791
- // Level 2: Standard elevation (default cards, buttons)
1792
- [2]: {
1793
- name: "standard",
1794
- colorAdjustment: 0.05,
1795
- shadowIntensity: "medium",
1796
- shadowStyle: "raised",
1797
- description: "Standard elevation (default cards, raised buttons)"
1798
- },
1799
- // Level 3: Prominent elevation (important cards, modals)
1800
- [3]: {
1801
- name: "prominent",
1802
- colorAdjustment: 0.075,
1803
- shadowIntensity: "strong",
1804
- shadowStyle: "raised",
1805
- description: "Prominent elevation (important cards, modals, dropdowns)"
1806
- },
1807
- // Level 4: High elevation (overlays, popovers)
1808
- [4]: {
1809
- name: "high",
1810
- colorAdjustment: 0.1,
1811
- shadowIntensity: "strong",
1812
- shadowStyle: "raised",
1813
- description: "High elevation (overlays, popovers, tooltips)"
1814
- },
1815
- // Level 5: Floating (toasts, floating action buttons)
1816
- [5]: {
1817
- name: "floating",
1818
- colorAdjustment: 0.13,
1819
- shadowIntensity: "strong",
1820
- shadowStyle: "raised",
1821
- description: "Floating elevation (toasts, floating action buttons, highest z-index)"
1822
- }
1823
- };
1824
- function calculateRimLightColor(baseColor, level) {
1825
- const rgb = hexToRgb(baseColor);
1826
- if (!rgb) {
1827
- return "rgba(255, 255, 255, 0.5)";
1828
- }
1829
- const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
1830
- const isLight = hsv.v > 0.6;
1831
- const isSaturated = hsv.s > 0.3;
1832
- const absLevel = Math.abs(level);
1833
- const opacityBoost = Math.max(0, (absLevel - 1) * 0.05);
1834
- if (isSaturated) {
1835
- const rimLightValue = 0.95;
1836
- const rimLightRgb = hsvToRgb(hsv.h, hsv.s * 0.3, rimLightValue);
1837
- const baseOpacity = isLight ? 0.9 : 0.5;
1838
- const finalOpacity = Math.min(1, baseOpacity + opacityBoost);
1839
- return `rgba(${rimLightRgb.r}, ${rimLightRgb.g}, ${rimLightRgb.b}, ${finalOpacity.toFixed(2)})`;
1840
- } else {
1841
- const baseOpacity = isLight ? 0.9 : 0.12;
1842
- const finalOpacity = Math.min(1, baseOpacity + opacityBoost);
1843
- return `rgba(255, 255, 255, ${finalOpacity.toFixed(2)})`;
1844
- }
1845
- }
1846
- function calculateDarkShadowColors(surfaceColor) {
1847
- const rgb = hexToRgb(surfaceColor);
1848
- if (!rgb) {
1849
- return {
1850
- dark: "rgba(0, 0, 0, 0.3)",
1851
- darker: "rgba(0, 0, 0, 0.4)"
1852
- };
1853
- }
1854
- const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
1855
- const isLight = hsv.v > 0.6;
1856
- const isSaturated = hsv.s > 0.3;
1857
- if (isSaturated) {
1858
- const darkShadowValue = 0.15;
1859
- const darkShadowRgb = hsvToRgb(hsv.h, hsv.s, darkShadowValue);
1860
- if (isLight) {
1861
- return {
1862
- dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.3)`,
1863
- darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.4)`
1864
- };
1865
- } else {
1866
- return {
1867
- dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.5)`,
1868
- darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.6)`
1869
- };
1870
- }
1871
- } else {
1872
- if (isLight) {
1873
- return {
1874
- dark: "rgba(0, 0, 0, 0.2)",
1875
- darker: "rgba(0, 0, 0, 0.3)"
1876
- };
1877
- } else {
1878
- return {
1879
- dark: "rgba(0, 0, 0, 0.5)",
1880
- darker: "rgba(0, 0, 0, 0.6)"
1881
- };
1882
- }
1883
- }
1884
- }
1885
- function getRimLightConfig() {
1886
- return {
1887
- offset: 1,
1888
- // Fixed 1px for crisp rim
1889
- blur: 1
1890
- // Tight blur for crisp edge
1891
- };
1892
- }
1893
- var elevationSurfaceCache = /* @__PURE__ */ new Map();
1894
- var MAX_CACHE_SIZE = 100;
1895
- function getCacheKey(baseColor, level, theme) {
1896
- return `${baseColor}-${level}-${theme}`;
2173
+ var DEFAULT_CONTEXT = { mode: "dark", level: "base" };
2174
+ var SurfaceContext = react.createContext(DEFAULT_CONTEXT);
2175
+ function useSurface() {
2176
+ return react.useContext(SurfaceContext);
1897
2177
  }
1898
- function calculateElevationSurface(baseColor, level, theme) {
1899
- const config = elevationSystem[level];
1900
- if (config.colorAdjustment === 0) {
1901
- return baseColor;
1902
- }
1903
- if (level < 0) {
1904
- return baseColor;
1905
- }
1906
- return lighten(baseColor, config.colorAdjustment);
1907
- }
1908
- function getElevationSurface(baseColor, level, theme) {
1909
- const cacheKey = getCacheKey(baseColor, level, theme);
1910
- if (elevationSurfaceCache.has(cacheKey)) {
1911
- return elevationSurfaceCache.get(cacheKey);
1912
- }
1913
- const result = calculateElevationSurface(baseColor, level);
1914
- if (elevationSurfaceCache.size >= MAX_CACHE_SIZE) {
1915
- const firstKey = elevationSurfaceCache.keys().next().value;
1916
- elevationSurfaceCache.delete(firstKey);
1917
- }
1918
- elevationSurfaceCache.set(cacheKey, result);
1919
- return result;
1920
- }
1921
- var elevationShadowCache = /* @__PURE__ */ new Map();
1922
- var MAX_SHADOW_CACHE_SIZE = 50;
1923
- function getShadowCacheKey(baseColor, level, theme, isHovered) {
1924
- return `${baseColor}-${level}-${theme}-${isHovered}`;
1925
- }
1926
- function calculateElevationShadow(baseColor, level, theme, isHovered = false) {
1927
- const config = elevationSystem[level];
1928
- if (level === 0) {
1929
- return {};
1930
- }
1931
- const elevatedSurfaceColor = calculateElevationSurface(baseColor, level);
1932
- const rimLightColor = calculateRimLightColor(baseColor, level);
1933
- const darkShadowColors = calculateDarkShadowColors(elevatedSurfaceColor);
1934
- const intensityConfig = {
1935
- subtle: { offset: 1, blur: 2 },
1936
- medium: { offset: 2, blur: 4 },
1937
- strong: { offset: 2, blur: 5 }
1938
- };
1939
- const { offset, blur } = intensityConfig[config.shadowIntensity];
1940
- const rimConfig = getRimLightConfig();
1941
- if (config.shadowStyle === "pressed") {
1942
- if (isHovered) {
1943
- const hoverOffset = Math.max(1, Math.round(offset / 2));
1944
- const hoverBlur = Math.max(1, Math.round(blur / 2));
1945
- return reactNative.Platform.select({
1946
- web: {
1947
- boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1948
- },
1949
- default: {
1950
- shadowOpacity: 0,
1951
- elevation: 0
1952
- }
1953
- });
1954
- }
1955
- return reactNative.Platform.select({
1956
- web: {
1957
- boxShadow: `inset ${offset}px ${offset}px ${blur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1958
- },
1959
- default: {
1960
- shadowOpacity: 0,
1961
- elevation: 0
1962
- }
1963
- });
1964
- } else {
1965
- if (isHovered) {
1966
- const hoverOffset = Math.max(1, Math.round(offset / 2));
1967
- const hoverBlur = Math.max(1, Math.round(blur / 2));
1968
- return reactNative.Platform.select({
1969
- web: {
1970
- boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1971
- },
1972
- default: {
1973
- shadowColor: "#000",
1974
- shadowOffset: { width: hoverOffset, height: hoverOffset },
1975
- shadowOpacity: 0.2,
1976
- shadowRadius: hoverBlur / 2,
1977
- elevation: hoverOffset
1978
- }
1979
- });
1980
- }
1981
- return reactNative.Platform.select({
1982
- web: {
1983
- boxShadow: `${offset}px ${offset}px ${blur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1984
- },
1985
- default: {
1986
- shadowColor: "#000",
1987
- shadowOffset: { width: offset, height: offset },
1988
- shadowOpacity: 0.3,
1989
- shadowRadius: blur / 2,
1990
- elevation: offset
1991
- }
1992
- });
1993
- }
2178
+ function recessDepth(inherited, planesDown, mode) {
2179
+ let plane = inherited.level;
2180
+ for (let i = 0; i < planesDown; i++) plane = pressedLevel(plane);
2181
+ const backgroundColor = surfaceBackground(plane, mode);
2182
+ return { plane, backgroundColor, depthStyle: getPressedRecessShadow(backgroundColor, mode) };
1994
2183
  }
1995
- var FLOATING_ELEVATION_MIN = 4;
1996
- function getElevationShadow(baseColor, level, theme, isHovered = false) {
1997
- const cacheKey = getShadowCacheKey(baseColor, level, theme, isHovered);
1998
- if (elevationShadowCache.has(cacheKey)) {
1999
- return elevationShadowCache.get(cacheKey);
2184
+ function resolveSurfaceDepth(inherited, props) {
2185
+ const mode = props.theme ?? inherited.mode;
2186
+ const elevation = props.elevation ?? 0;
2187
+ if (props.pressed || elevation < 0) {
2188
+ const down = props.pressed ? 1 : -elevation;
2189
+ return { mode, step: 0, lifted: false, ...recessDepth(inherited, down, mode) };
2000
2190
  }
2001
- const result = level > 0 && level < FLOATING_ELEVATION_MIN ? {} : calculateElevationShadow(baseColor, level, theme, isHovered);
2002
- if (elevationShadowCache.size >= MAX_SHADOW_CACHE_SIZE) {
2003
- const firstKey = elevationShadowCache.keys().next().value;
2004
- elevationShadowCache.delete(firstKey);
2005
- }
2006
- elevationShadowCache.set(cacheKey, result);
2007
- return result;
2008
- }
2009
- function getBaseSurfaceColor(theme) {
2010
- return theme === "light" ? semanticColorsLight["surface-raised"] : semanticColorsDark["surface-elevated"];
2191
+ const step = props.level != null ? 0 : props.raise ?? elevation;
2192
+ const floating = step >= FLOATING_ELEVATION_MIN;
2193
+ const plane = props.level ?? (floating ? "overlay" : raisedLevel(inherited.level, step));
2194
+ const lifted = props.lift ?? step > 0;
2195
+ const liftStep = Math.max(1, Math.min(5, step));
2196
+ const depthStyle = lifted ? liftStyle(liftStep, mode, props.liftOptions) : {};
2197
+ return { mode, plane, backgroundColor: surfaceBackground(plane, mode), depthStyle, step, lifted };
2011
2198
  }
2012
- var componentElevationRanges = {
2013
- button: {
2014
- allowed: [2],
2015
- default: 2,
2016
- hover: 3
2017
- },
2018
- card: {
2019
- allowed: [1, 2, 3],
2020
- default: 2,
2021
- hover: 3
2022
- },
2023
- input: {
2024
- allowed: [-2, -1, 0],
2025
- default: -1
2026
- },
2027
- overlay: {
2028
- allowed: [4, 5],
2029
- default: 4
2030
- }
2031
- };
2032
- function getValidatedElevation(component, requested) {
2033
- const range = componentElevationRanges[component];
2034
- if (range.allowed.includes(requested)) {
2035
- return requested;
2036
- }
2037
- const sorted = [...range.allowed].sort((a, b) => a - b);
2038
- if (requested < sorted[0]) return sorted[0];
2039
- if (requested > sorted[sorted.length - 1]) return sorted[sorted.length - 1];
2040
- return sorted.reduce(
2041
- (prev, curr) => Math.abs(curr - requested) < Math.abs(prev - requested) ? curr : prev
2199
+ function useResolvedSurface(props) {
2200
+ const inherited = useSurface();
2201
+ const { level, raise, elevation, pressed, lift, liftOptions, theme } = props;
2202
+ return react.useMemo(
2203
+ () => resolveSurfaceDepth(inherited, {
2204
+ level,
2205
+ raise,
2206
+ elevation,
2207
+ pressed,
2208
+ lift,
2209
+ liftOptions,
2210
+ theme
2211
+ }),
2212
+ [inherited, level, raise, elevation, pressed, lift, liftOptions, theme]
2042
2213
  );
2043
2214
  }
2044
- function getTheme() {
2045
- if (typeof document === "undefined") return "dark";
2046
- return document.documentElement.classList.contains("light") ? "light" : "dark";
2047
- }
2048
- var listeners = [];
2049
- function subscribe(listener) {
2050
- listeners = [...listeners, listener];
2051
- if (typeof document !== "undefined" && listeners.length === 1) {
2052
- const observer = new MutationObserver((mutations) => {
2053
- for (const mutation of mutations) {
2054
- if (mutation.type === "attributes" && mutation.attributeName === "class") {
2055
- listeners.forEach((l) => l());
2056
- }
2057
- }
2058
- });
2059
- observer.observe(document.documentElement, {
2060
- attributes: true,
2061
- attributeFilter: ["class"]
2062
- });
2063
- subscribe._observer = observer;
2064
- }
2065
- return () => {
2066
- listeners = listeners.filter((l) => l !== listener);
2067
- if (listeners.length === 0 && subscribe._observer) {
2068
- subscribe._observer.disconnect();
2069
- subscribe._observer = null;
2070
- }
2071
- };
2072
- }
2073
- function getSnapshot() {
2074
- return getTheme();
2075
- }
2076
- function getServerSnapshot() {
2077
- return "dark";
2078
- }
2079
- function useTheme() {
2080
- return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
2081
- }
2082
2215
 
2083
2216
  // src/components/ui/card/Card.tsx
2084
- var variantStyles = {
2217
+ var EDGE_CLASS = {
2085
2218
  elevated: "",
2086
- // Will be set dynamically via elevation system
2087
- outline: "border-2 border-hairline-strong",
2088
- // Thicker border with stronger contrast
2219
+ accent: "",
2089
2220
  filled: "",
2090
- // Will be set dynamically via elevation system
2091
- accent: "border border-hairline",
2221
+ outline: "border border-hairline-strong",
2092
2222
  subtle: "border border-hairline-subtle"
2093
2223
  };
2224
+ var LIFTED = {
2225
+ elevated: true,
2226
+ accent: true,
2227
+ filled: false,
2228
+ outline: false,
2229
+ subtle: false
2230
+ };
2231
+ var STAYS_ON_HOST = {
2232
+ elevated: false,
2233
+ accent: false,
2234
+ filled: false,
2235
+ outline: true,
2236
+ subtle: true
2237
+ };
2238
+ function useCardDepth(variant, elevation, hovered) {
2239
+ const raise = STAYS_ON_HOST[variant] ? void 0 : elevation;
2240
+ const resolved = useResolvedSurface({ raise, lift: LIFTED[variant] });
2241
+ const depthStyle = react.useMemo(() => {
2242
+ if (!hovered || !LIFTED[variant]) return resolved.depthStyle;
2243
+ const hoverStep = Math.min(3, resolved.step + 1);
2244
+ return liftStyle(hoverStep, resolved.mode);
2245
+ }, [hovered, variant, resolved]);
2246
+ return { ...resolved, depthStyle };
2247
+ }
2094
2248
  function Card({
2095
2249
  variant = "elevated",
2096
2250
  elevation = 2,
@@ -2111,89 +2265,54 @@ function Card({
2111
2265
  }) {
2112
2266
  const isClickable = isInteractive || !!onPress;
2113
2267
  const [isHovered, setIsHovered] = react.useState(false);
2114
- const theme = useTheme();
2115
- const elevationLevel = react.useMemo(() => {
2116
- const validated = getValidatedElevation("card", elevation);
2117
- if (variant === "outline" || variant === "accent" || variant === "subtle") {
2118
- return 1;
2119
- }
2120
- return validated;
2121
- }, [variant, elevation]);
2122
- const baseColor = react.useMemo(() => getBaseSurfaceColor(theme), [theme]);
2123
- const surfaceColor = react.useMemo(() => {
2124
- if ((variant === "outline" || variant === "accent") && theme === "light") {
2125
- return "#FAFAFA";
2126
- }
2127
- if (variant === "subtle") {
2128
- return getElevationSurface(baseColor, 0, theme);
2129
- }
2130
- return getElevationSurface(baseColor, elevationLevel, theme);
2131
- }, [baseColor, elevationLevel, theme, variant]);
2132
- const shadowStyle = react.useMemo(() => {
2133
- const shouldHover = isHovered && isClickable;
2134
- return getElevationShadow(baseColor, elevationLevel, theme, shouldHover);
2135
- }, [baseColor, elevationLevel, theme, isHovered, isClickable]);
2136
- const cardContent = isLoading ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
2137
- skeletonHasHeader && /* @__PURE__ */ jsxs(CardHeader, { children: [
2138
- /* @__PURE__ */ jsx(reactNative.View, { className: "h-5 w-1/3 bg-interactive-disabled rounded" }),
2139
- /* @__PURE__ */ jsx(reactNative.View, { className: "mt-2 h-4 w-2/3 bg-interactive-disabled rounded" })
2140
- ] }),
2141
- /* @__PURE__ */ jsx(CardContent, { children: Array.from({ length: skeletonContentLines }).map((_, i) => /* @__PURE__ */ jsx(
2142
- reactNative.View,
2143
- {
2144
- className: cn(
2145
- "h-4 bg-interactive-disabled rounded",
2146
- i < skeletonContentLines - 1 ? "mb-2 w-full" : "w-4/5"
2147
- )
2148
- },
2149
- i
2150
- )) }),
2151
- skeletonHasFooter && /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(reactNative.View, { className: "h-9 w-24 bg-interactive-disabled rounded" }) })
2152
- ] }) : children;
2268
+ const depth = useCardDepth(variant, elevation, isHovered && isClickable);
2153
2269
  const baseClassName = cn(
2154
2270
  "rounded-lg overflow-hidden relative",
2155
- // Apply variant styles, but only use default border color if no custom borderColor
2156
- variant === "outline" ? borderColor ? "border-2" : variantStyles[variant] : variantStyles[variant],
2157
- // Remove Tailwind shadow classes - we're using elevation shadows via style prop
2271
+ borderColor ? "border" : EDGE_CLASS[variant],
2158
2272
  isClickable && "web:cursor-pointer web:transition-all web:duration-150",
2159
2273
  isClickable && "web:hover:-translate-y-0.5 active:scale-[0.99]",
2160
2274
  isLoading && "pointer-events-none animate-pulse",
2161
2275
  className
2162
2276
  );
2163
- const accentStyle = react.useMemo(() => variant === "accent" ? {
2164
- borderLeftWidth: accentWidth ?? 3,
2165
- borderLeftColor: accentColor ?? "var(--color-brand-primary)"
2166
- } : {}, [variant, accentWidth, accentColor]);
2167
2277
  const mergedStyle = react.useMemo(() => {
2168
- const elevationStyle = {
2169
- backgroundColor: bgColor || surfaceColor,
2278
+ const own = {
2279
+ backgroundColor: bgColor || depth.backgroundColor,
2170
2280
  borderRadius: 8,
2171
2281
  overflow: "hidden",
2172
- ...shadowStyle,
2173
- ...accentStyle
2282
+ ...depth.depthStyle
2174
2283
  };
2175
- if (borderColor) {
2176
- elevationStyle.borderColor = borderColor;
2284
+ if (variant === "accent") {
2285
+ own.borderLeftWidth = accentWidth ?? 3;
2286
+ own.borderLeftColor = accentColor ?? "var(--color-brand-primary)";
2177
2287
  }
2178
- if (!style) return elevationStyle;
2179
- return [elevationStyle, style];
2180
- }, [style, surfaceColor, shadowStyle, borderColor, bgColor, accentStyle]);
2181
- if (isClickable) {
2182
- return /* @__PURE__ */ jsx(
2183
- reactNative.Pressable,
2184
- {
2185
- onPress,
2186
- onHoverIn: () => setIsHovered(true),
2187
- onHoverOut: () => setIsHovered(false),
2188
- accessibilityRole: "button",
2189
- className: baseClassName,
2190
- style: mergedStyle,
2191
- ...props,
2192
- children: cardContent
2193
- }
2194
- );
2195
- }
2196
- return /* @__PURE__ */ jsx(reactNative.View, { className: baseClassName, style: mergedStyle, ...props, children: cardContent });
2288
+ if (borderColor) own.borderColor = borderColor;
2289
+ return style ? [own, style] : own;
2290
+ }, [style, depth, bgColor, borderColor, variant, accentWidth, accentColor]);
2291
+ const context = react.useMemo(
2292
+ () => ({ mode: depth.mode, level: depth.plane }),
2293
+ [depth.mode, depth.plane]
2294
+ );
2295
+ const content = isLoading ? /* @__PURE__ */ jsx(
2296
+ SkeletonBody,
2297
+ {
2298
+ hasHeader: skeletonHasHeader,
2299
+ hasFooter: skeletonHasFooter,
2300
+ contentLines: skeletonContentLines
2301
+ }
2302
+ ) : children;
2303
+ return /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: context, children: isClickable ? /* @__PURE__ */ jsx(
2304
+ reactNative.Pressable,
2305
+ {
2306
+ onPress,
2307
+ onHoverIn: () => setIsHovered(true),
2308
+ onHoverOut: () => setIsHovered(false),
2309
+ accessibilityRole: "button",
2310
+ className: baseClassName,
2311
+ style: mergedStyle,
2312
+ ...props,
2313
+ children: content
2314
+ }
2315
+ ) : /* @__PURE__ */ jsx(reactNative.View, { className: baseClassName, style: mergedStyle, ...props, children: content }) });
2197
2316
  }
2198
2317
  function CardHeader({ children, className }) {
2199
2318
  return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-6", className), children });
@@ -2204,60 +2323,88 @@ function CardContent({ children, className }) {
2204
2323
  function CardFooter({ children, className }) {
2205
2324
  return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
2206
2325
  }
2207
- var t2 = getSemanticColors("dark");
2208
- var solidVariantColors = {
2209
- success: t2["status-success"],
2210
- warning: t2["status-warning"],
2211
- error: t2["status-error"],
2212
- neutral: greyRamp[500]
2326
+ function SkeletonBody({ hasHeader, hasFooter, contentLines }) {
2327
+ return /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
2328
+ hasHeader && /* @__PURE__ */ jsxs(CardHeader, { children: [
2329
+ /* @__PURE__ */ jsx(reactNative.View, { className: "h-5 w-1/3 bg-interactive-disabled rounded" }),
2330
+ /* @__PURE__ */ jsx(reactNative.View, { className: "mt-2 h-4 w-2/3 bg-interactive-disabled rounded" })
2331
+ ] }),
2332
+ /* @__PURE__ */ jsx(CardContent, { children: Array.from({ length: contentLines }).map((_, i) => /* @__PURE__ */ jsx(
2333
+ reactNative.View,
2334
+ {
2335
+ className: cn(
2336
+ "h-4 bg-interactive-disabled rounded",
2337
+ i < contentLines - 1 ? "mb-2 w-full" : "w-4/5"
2338
+ )
2339
+ },
2340
+ i
2341
+ )) }),
2342
+ hasFooter && /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(reactNative.View, { className: "h-9 w-24 bg-interactive-disabled rounded" }) })
2343
+ ] });
2344
+ }
2345
+ var solidFillToken = {
2346
+ success: "status-success",
2347
+ warning: "status-warning",
2348
+ error: "status-error",
2349
+ neutral: "text-tertiary"
2213
2350
  };
2214
- var ringVariantStyles = {
2215
- "on-track": {
2216
- backgroundColor: "rgba(46,213,115,0.15)",
2217
- borderWidth: 1,
2218
- borderColor: "rgba(46,213,115,0.3)"
2219
- },
2220
- deviation: {
2221
- backgroundColor: "rgba(245,158,11,0.15)",
2222
- borderWidth: 1,
2223
- borderColor: "rgba(245,158,11,0.25)"
2224
- },
2225
- future: {
2226
- backgroundColor: "rgba(107,114,128,0.1)",
2227
- borderWidth: 1,
2228
- borderStyle: "dashed",
2229
- borderColor: "rgba(107,114,128,0.2)"
2230
- }
2351
+ var ringWashToken = {
2352
+ "on-track": { fill: "status-success-subtle", border: "status-success-muted" },
2353
+ deviation: { fill: "status-warning-subtle", border: "status-warning-muted" }
2354
+ };
2355
+ var FUTURE_FILL = alpha(greyRamp[500], 0.1);
2356
+ var FUTURE_BORDER = alpha(greyRamp[500], 0.2);
2357
+ var glyphToken = {
2358
+ success: "text-inverse",
2359
+ warning: "text-inverse",
2360
+ error: "text-inverse",
2361
+ neutral: "text-inverse",
2362
+ "on-track": "status-success",
2363
+ deviation: "status-warning",
2364
+ future: "text-tertiary"
2231
2365
  };
2232
- var glowStyles = {
2233
- success: { boxShadow: "0 0 4px rgba(46,213,115,0.4)" },
2234
- warning: { boxShadow: "0 0 4px rgba(245,158,11,0.4)" },
2235
- error: { boxShadow: "0 0 4px rgba(239,68,68,0.4)" },
2236
- neutral: { boxShadow: "0 0 4px rgba(107,114,128,0.4)" },
2237
- "on-track": { boxShadow: "0 0 4px rgba(46,213,115,0.4)" },
2238
- deviation: { boxShadow: "0 0 4px rgba(245,158,11,0.4)" },
2239
- future: { boxShadow: "0 0 4px rgba(107,114,128,0.4)" }
2366
+ var glowColors2 = {
2367
+ success: semanticColorsDark["status-success"],
2368
+ warning: semanticColorsDark["status-warning"],
2369
+ error: semanticColorsDark["status-error"],
2370
+ neutral: greyRamp[500],
2371
+ "on-track": semanticColorsDark["status-success"],
2372
+ deviation: semanticColorsDark["status-warning"],
2373
+ future: greyRamp[500]
2240
2374
  };
2375
+ var glowStyles = Object.fromEntries(
2376
+ Object.entries(glowColors2).map(([variant, color]) => [variant, getGlowShadow(color, "subtle")])
2377
+ );
2241
2378
  var iconChars = {
2242
2379
  check: "\u2713",
2243
2380
  exclamation: "!",
2244
2381
  dash: "\u2014"
2245
2382
  };
2246
- var iconColors = {
2247
- success: "#0A5C52",
2248
- warning: "#6B4000",
2249
- error: "#5C1A1A",
2250
- neutral: greyRamp[200],
2251
- "on-track": t2["status-success"],
2252
- deviation: t2["status-warning"],
2253
- future: greyRamp[500]
2254
- };
2255
2383
  var sizeDimensions = {
2256
2384
  sm: { size: 8, showIcon: false },
2257
2385
  md: { size: 18, showIcon: true }
2258
2386
  };
2259
2387
  function isSolidVariant(variant) {
2260
- return variant in solidVariantColors;
2388
+ return variant in solidFillToken;
2389
+ }
2390
+ function ringStyle(variant) {
2391
+ if (variant === "future") {
2392
+ return {
2393
+ backgroundColor: FUTURE_FILL,
2394
+ borderWidth: 1,
2395
+ borderStyle: "dashed",
2396
+ borderColor: FUTURE_BORDER
2397
+ };
2398
+ }
2399
+ if (variant === "on-track" || variant === "deviation") {
2400
+ const tokens = ringWashToken[variant];
2401
+ return {
2402
+ backgroundColor: resolveColor(tokens.fill),
2403
+ borderWidth: 1,
2404
+ borderColor: resolveColor(tokens.border)
2405
+ };
2406
+ }
2407
+ return void 0;
2261
2408
  }
2262
2409
  function StatusDot({
2263
2410
  variant,
@@ -2270,7 +2417,7 @@ function StatusDot({
2270
2417
  }) {
2271
2418
  const config = sizeDimensions[size];
2272
2419
  const solid = isSolidVariant(variant);
2273
- const ring = ringVariantStyles[variant];
2420
+ const ring = ringStyle(variant);
2274
2421
  const glowStyle = glow ? glowStyles[variant] : null;
2275
2422
  const dot = /* @__PURE__ */ jsx(
2276
2423
  reactNative.View,
@@ -2284,22 +2431,22 @@ function StatusDot({
2284
2431
  justifyContent: "center",
2285
2432
  flexShrink: 0
2286
2433
  },
2287
- solid ? { backgroundColor: solidVariantColors[variant] } : void 0,
2288
- ring ? ring : void 0,
2434
+ solid ? { backgroundColor: resolveColor(solidFillToken[variant]) } : void 0,
2435
+ ring,
2289
2436
  glowStyle ? glowStyle : void 0
2290
2437
  ],
2291
2438
  accessibilityRole: "image",
2292
2439
  accessibilityLabel: `${variant} status`,
2293
2440
  testID: "status-dot",
2294
- children: config.showIcon && icon && /* @__PURE__ */ jsx(
2295
- reactNative.Text,
2441
+ children: config.showIcon && icon && // The glyph was 11px/900, off the type scale (TOKENS.md §4); `boldLabel` is
2442
+ // the 12px sans label step and `font-black` keeps the original weight.
2443
+ /* @__PURE__ */ jsx(
2444
+ Typography,
2296
2445
  {
2297
- style: {
2298
- fontSize: 11,
2299
- lineHeight: 11,
2300
- fontWeight: "900",
2301
- color: iconColors[variant]
2302
- },
2446
+ variant: "boldLabel",
2447
+ color: "inherit",
2448
+ className: "font-black leading-none",
2449
+ style: { color: resolveColor(glyphToken[variant]) },
2303
2450
  accessibilityElementsHidden: true,
2304
2451
  children: iconChars[icon]
2305
2452
  }
@@ -2317,9 +2464,11 @@ function StatusDot({
2317
2464
  children: [
2318
2465
  /* @__PURE__ */ jsx(reactNative.View, { accessibilityElementsHidden: true, children: dot }),
2319
2466
  /* @__PURE__ */ jsx(
2320
- reactNative.Text,
2467
+ Typography,
2321
2468
  {
2322
- style: { fontSize: 12, lineHeight: 16, color: greyRamp[400] },
2469
+ variant: "caption",
2470
+ color: "secondary",
2471
+ className: "leading-4",
2323
2472
  accessibilityElementsHidden: true,
2324
2473
  children: label
2325
2474
  }
@@ -2335,19 +2484,19 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
2335
2484
 
2336
2485
  // src/components/custom/Workout/MesoStatusCard.tsx
2337
2486
  var t3 = getSemanticColors("dark");
2338
- var BRAND_PRIMARY4 = t3["brand-primary"];
2487
+ var BRAND_PRIMARY3 = t3["brand-primary"];
2339
2488
  var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
2340
2489
  var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
2341
2490
  var SUCCESS = t3["status-success"];
2342
2491
  var WARNING = t3["status-warning"];
2343
2492
  var ERROR = t3["status-error"];
2344
- var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY4, BRAND_PRIMARY_LIGHT];
2493
+ var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY3, BRAND_PRIMARY_LIGHT];
2345
2494
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
2346
- var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)";
2495
+ var GAUGE_GRADIENT = `linear-gradient(90deg, ${alpha(SUCCESS, 0.25)} 0%, ${alpha(WARNING, 0.25)} 50%, ${alpha(ERROR, 0.25)} 100%)`;
2347
2496
  var STATUS_VARIANTS = {
2348
- success: { bg: "rgba(46,213,115,0.15)", border: "rgba(46,213,115,0.3)", text: SUCCESS },
2349
- warning: { bg: "rgba(249,180,21,0.15)", border: "rgba(249,180,21,0.3)", text: WARNING },
2350
- error: { bg: "rgba(209,67,67,0.15)", border: "rgba(209,67,67,0.25)", text: ERROR }
2497
+ success: { bg: alpha(SUCCESS, 0.15), border: alpha(SUCCESS, 0.3), text: SUCCESS },
2498
+ warning: { bg: alpha(WARNING, 0.15), border: alpha(WARNING, 0.3), text: WARNING },
2499
+ error: { bg: alpha(ERROR, 0.15), border: alpha(ERROR, 0.25), text: ERROR }
2351
2500
  };
2352
2501
  function clamp012(value) {
2353
2502
  if (!Number.isFinite(value)) return 0;
@@ -2366,15 +2515,7 @@ function renderCoachingText(text, highlights) {
2366
2515
  if (escaped.length === 0) return text;
2367
2516
  const regex = new RegExp(`(${escaped.join("|")})`, "g");
2368
2517
  return text.split(regex).map(
2369
- (part, index) => highlights.includes(part) ? /* @__PURE__ */ jsx(
2370
- reactNative.Text,
2371
- {
2372
- className: "text-text-primary",
2373
- style: { fontWeight: "700" },
2374
- children: part
2375
- },
2376
- `${part}-${index}`
2377
- ) : /* @__PURE__ */ jsx(react.Fragment, { children: part }, `t-${index}`)
2518
+ (part, index) => highlights.includes(part) ? /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-primary", style: { fontWeight: "700" }, children: part }, `${part}-${index}`) : /* @__PURE__ */ jsx(react.Fragment, { children: part }, `t-${index}`)
2378
2519
  );
2379
2520
  }
2380
2521
  function StatusPill({ badge }) {
@@ -2459,41 +2600,34 @@ function Gauge({ gauge }) {
2459
2600
  "aria-valuemax": 100,
2460
2601
  testID: "meso-status-card-gauge",
2461
2602
  children: [
2462
- /* @__PURE__ */ jsxs(
2463
- reactNative.View,
2464
- {
2465
- className: "flex-row items-center justify-between",
2466
- accessibilityElementsHidden: true,
2467
- children: [
2468
- /* @__PURE__ */ jsx(
2469
- reactNative.Text,
2470
- {
2471
- className: "text-text-secondary",
2472
- style: {
2473
- fontSize: 10,
2474
- fontFamily: "Inter, sans-serif",
2475
- fontWeight: "500",
2476
- letterSpacing: 0.5,
2477
- textTransform: "uppercase"
2478
- },
2479
- children: gauge.label
2480
- }
2481
- ),
2482
- gauge.sublabel != null && /* @__PURE__ */ jsx(
2483
- reactNative.Text,
2484
- {
2485
- className: "text-text-tertiary",
2486
- style: {
2487
- fontSize: 10,
2488
- fontFamily: "Inter, sans-serif",
2489
- fontWeight: "500"
2490
- },
2491
- children: gauge.sublabel
2492
- }
2493
- )
2494
- ]
2495
- }
2496
- ),
2603
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center justify-between", accessibilityElementsHidden: true, children: [
2604
+ /* @__PURE__ */ jsx(
2605
+ reactNative.Text,
2606
+ {
2607
+ className: "text-text-secondary",
2608
+ style: {
2609
+ fontSize: 10,
2610
+ fontFamily: "Inter, sans-serif",
2611
+ fontWeight: "500",
2612
+ letterSpacing: 0.5,
2613
+ textTransform: "uppercase"
2614
+ },
2615
+ children: gauge.label
2616
+ }
2617
+ ),
2618
+ gauge.sublabel != null && /* @__PURE__ */ jsx(
2619
+ reactNative.Text,
2620
+ {
2621
+ className: "text-text-tertiary",
2622
+ style: {
2623
+ fontSize: 10,
2624
+ fontFamily: "Inter, sans-serif",
2625
+ fontWeight: "500"
2626
+ },
2627
+ children: gauge.sublabel
2628
+ }
2629
+ )
2630
+ ] }),
2497
2631
  /* @__PURE__ */ jsxs(
2498
2632
  reactNative.View,
2499
2633
  {
@@ -2515,7 +2649,7 @@ function Gauge({ gauge }) {
2515
2649
  bottom: 0,
2516
2650
  width: 1,
2517
2651
  marginLeft: -0.5,
2518
- backgroundColor: "rgba(255,255,255,0.3)"
2652
+ backgroundColor: alpha(primitiveColors.white, 0.3)
2519
2653
  },
2520
2654
  testID: "meso-status-card-gauge-center"
2521
2655
  }
@@ -2534,7 +2668,8 @@ function Gauge({ gauge }) {
2534
2668
  borderRadius: 9999,
2535
2669
  borderWidth: 2,
2536
2670
  backgroundColor: markerColor,
2537
- boxShadow: "0 0 4px rgba(0,0,0,0.5)"
2671
+ // A knob resting on the gauge: lift, with the ring as its edge.
2672
+ ...liftStyle(1, "dark", { rim: 0 })
2538
2673
  },
2539
2674
  testID: "meso-status-card-gauge-marker"
2540
2675
  }
@@ -2586,30 +2721,23 @@ function MesoStatusCard({
2586
2721
  ),
2587
2722
  /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 14, gap: 14 }, testID: "meso-status-card-body", children: [
2588
2723
  /* @__PURE__ */ jsxs(reactNative.View, { testID: "meso-status-card-header", children: [
2589
- /* @__PURE__ */ jsxs(
2590
- reactNative.View,
2591
- {
2592
- className: "flex-row items-center justify-between",
2593
- style: { gap: 8 },
2594
- children: [
2595
- /* @__PURE__ */ jsx(
2596
- reactNative.Text,
2597
- {
2598
- className: "text-text-primary",
2599
- style: {
2600
- flexShrink: 1,
2601
- fontSize: 15,
2602
- fontFamily: '"Space Grotesk", sans-serif',
2603
- fontWeight: "700"
2604
- },
2605
- testID: "meso-status-card-name",
2606
- children: mesoName
2607
- }
2608
- ),
2609
- /* @__PURE__ */ jsx(StatusPill, { badge: statusBadge })
2610
- ]
2611
- }
2612
- ),
2724
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center justify-between", style: { gap: 8 }, children: [
2725
+ /* @__PURE__ */ jsx(
2726
+ reactNative.Text,
2727
+ {
2728
+ className: "text-text-primary",
2729
+ style: {
2730
+ flexShrink: 1,
2731
+ fontSize: 15,
2732
+ fontFamily: '"Space Grotesk", sans-serif',
2733
+ fontWeight: "700"
2734
+ },
2735
+ testID: "meso-status-card-name",
2736
+ children: mesoName
2737
+ }
2738
+ ),
2739
+ /* @__PURE__ */ jsx(StatusPill, { badge: statusBadge })
2740
+ ] }),
2613
2741
  /* @__PURE__ */ jsx(
2614
2742
  reactNative.Text,
2615
2743
  {
@@ -2624,23 +2752,15 @@ function MesoStatusCard({
2624
2752
  }
2625
2753
  )
2626
2754
  ] }),
2627
- metrics.length > 0 && /* @__PURE__ */ jsx(
2628
- reactNative.View,
2629
- {
2630
- className: "flex-row flex-wrap",
2631
- style: { gap: 8 },
2632
- testID: "meso-status-card-metrics",
2633
- children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
2634
- }
2635
- ),
2755
+ metrics.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { className: "flex-row flex-wrap", style: { gap: 8 }, testID: "meso-status-card-metrics", children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`)) }),
2636
2756
  gauges.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 12 }, testID: "meso-status-card-gauges", children: gauges.map((gauge, index) => /* @__PURE__ */ jsx(Gauge, { gauge }, `${gauge.label}-${index}`)) }),
2637
2757
  coaching != null && /* @__PURE__ */ jsx(
2638
2758
  reactNative.View,
2639
2759
  {
2640
2760
  style: {
2641
- backgroundColor: "rgba(249,180,21,0.06)",
2761
+ backgroundColor: alpha(WARNING, 0.06),
2642
2762
  borderWidth: 1,
2643
- borderColor: "rgba(249,180,21,0.15)",
2763
+ borderColor: alpha(WARNING, 0.15),
2644
2764
  borderRadius: 8,
2645
2765
  paddingVertical: 10,
2646
2766
  paddingHorizontal: 12
@@ -2666,9 +2786,9 @@ function MesoStatusCard({
2666
2786
  className: "flex-row items-center",
2667
2787
  style: {
2668
2788
  gap: 8,
2669
- backgroundColor: "rgba(46,213,115,0.06)",
2789
+ backgroundColor: alpha(SUCCESS, 0.06),
2670
2790
  borderWidth: 1,
2671
- borderColor: "rgba(46,213,115,0.2)",
2791
+ borderColor: alpha(SUCCESS, 0.2),
2672
2792
  borderRadius: 8,
2673
2793
  paddingVertical: 10,
2674
2794
  paddingHorizontal: 12