@titan-design/react-ui 0.6.0 → 0.8.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 (258) hide show
  1. package/dist/bodymap.js +12 -7
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +12 -7
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +956 -128
  6. package/dist/index.d.ts +956 -128
  7. package/dist/index.js +2873 -981
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2826 -982
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
  12. package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
  13. package/dist/pages.d.mts +1 -1
  14. package/dist/pages.d.ts +1 -1
  15. package/dist/pages.js +1659 -799
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1660 -800
  18. package/dist/pages.mjs.map +1 -1
  19. package/dist/theme/index.d.mts +16 -82
  20. package/dist/theme/index.d.ts +16 -82
  21. package/dist/theme/index.js +13 -115
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/index.mjs +13 -115
  24. package/dist/theme/index.mjs.map +1 -1
  25. package/dist/theme/tokens-css.js +14 -6
  26. package/dist/theme/tokens-css.js.map +1 -1
  27. package/dist/theme/tokens-css.mjs +14 -6
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/arch/Architecture.stories.tsx +870 -0
  31. package/src/arch/annotations.json +48 -0
  32. package/src/arch/arch-graph.json +4301 -0
  33. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
  34. package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
  35. package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
  36. package/src/components/custom/CircularTimer/index.ts +1 -0
  37. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  38. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  39. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  40. package/src/components/custom/Metric/Metric.stories.tsx +1 -1
  41. package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
  42. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  43. package/src/components/custom/Table/Table.stories.tsx +1 -1
  44. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  45. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  46. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  47. package/src/components/custom/TimerReadout/index.ts +1 -0
  48. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  49. package/src/components/custom/Typography/Typography.stories.tsx +1 -1
  50. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  51. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  52. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
  53. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  54. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  55. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  56. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  57. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  58. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  59. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  60. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  61. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  62. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  63. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  64. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  65. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  66. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  67. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
  68. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  69. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  70. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  71. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  72. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  73. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  74. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  75. package/src/components/custom/Workout/FatigueMeter.test.tsx +85 -0
  76. package/src/components/custom/Workout/FatigueMeter.tsx +86 -0
  77. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  78. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  79. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  80. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  81. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  82. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  83. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  84. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  85. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  86. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  87. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  88. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  89. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  90. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  91. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  92. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  93. package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
  94. package/src/components/custom/Workout/README.md +193 -4
  95. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  96. package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
  97. package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
  98. package/src/components/custom/Workout/RestTimer.tsx +155 -59
  99. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  100. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  101. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  102. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  103. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  104. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  105. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  106. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  107. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  108. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  109. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  110. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  111. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  112. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  113. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  114. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  115. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  116. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  117. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  118. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  119. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  120. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  121. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  122. package/src/components/custom/Workout/SetBar.tsx +171 -0
  123. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  124. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  125. package/src/components/custom/Workout/SetRow.tsx +169 -232
  126. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  127. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  128. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  129. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  130. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  131. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  132. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  133. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  134. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  135. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  136. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  137. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  138. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  139. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  140. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  141. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  142. package/src/components/custom/Workout/TempoBar.stories.tsx +70 -1
  143. package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
  144. package/src/components/custom/Workout/TempoBar.tsx +107 -22
  145. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  146. package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
  147. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  148. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  149. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  150. package/src/components/custom/Workout/VelocityStrip.stories.tsx +262 -56
  151. package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
  152. package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
  153. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  154. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  155. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  156. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  157. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  158. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  159. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  160. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  161. package/src/components/custom/Workout/ZoneTrack.stories.tsx +139 -0
  162. package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
  163. package/src/components/custom/Workout/ZoneTrack.tsx +373 -0
  164. package/src/components/custom/Workout/index.ts +55 -2
  165. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  166. package/src/components/custom/Workout/metricText.tsx +34 -0
  167. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  168. package/src/components/custom/Workout/paceTone.ts +27 -0
  169. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  170. package/src/components/custom/index.ts +2 -0
  171. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  172. package/src/components/icons/icons.stories.tsx +67 -2
  173. package/src/components/icons/icons.test.tsx +32 -2
  174. package/src/components/icons/icons.tsx +108 -0
  175. package/src/components/shell/BrandLockup.stories.tsx +3 -3
  176. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  177. package/src/components/shell/DashboardShell.test.tsx +42 -0
  178. package/src/components/shell/DashboardShell.tsx +74 -0
  179. package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
  180. package/src/components/shell/DeviceMenu.stories.tsx +5 -5
  181. package/src/components/shell/DeviceRow.stories.tsx +3 -3
  182. package/src/components/shell/NavItem.stories.tsx +52 -0
  183. package/src/components/shell/NavItem.test.tsx +35 -0
  184. package/src/components/shell/NavItem.tsx +75 -0
  185. package/src/components/shell/README.md +30 -0
  186. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  187. package/src/components/shell/SessionStatePill.stories.tsx +3 -3
  188. package/src/components/shell/SessionStatePill.tsx +1 -1
  189. package/src/components/shell/SideNav.stories.tsx +81 -0
  190. package/src/components/shell/SideNav.test.tsx +51 -0
  191. package/src/components/shell/SideNav.tsx +74 -0
  192. package/src/components/shell/TopBar.stories.tsx +5 -5
  193. package/src/components/shell/index.ts +5 -0
  194. package/src/components/ui/alert/Alert.stories.tsx +86 -3
  195. package/src/components/ui/alert/Alert.test.tsx +52 -0
  196. package/src/components/ui/alert/Alert.tsx +54 -20
  197. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  198. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  199. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  200. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  201. package/src/components/ui/button/Button.stories.tsx +1 -1
  202. package/src/components/ui/card/Card.stories.tsx +1 -1
  203. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  204. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  205. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  206. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  207. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  208. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  209. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  210. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  211. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  212. package/src/components/ui/index.ts +1 -0
  213. package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
  214. package/src/components/ui/indicator/Indicator.test.tsx +1 -0
  215. package/src/components/ui/indicator/Indicator.tsx +6 -1
  216. package/src/components/ui/input/Input.stories.tsx +1 -1
  217. package/src/components/ui/link/Link.stories.tsx +1 -1
  218. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  219. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  220. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  221. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  222. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  223. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  224. package/src/components/ui/progress/Progress.tsx +47 -22
  225. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  226. package/src/components/ui/section/Section.stories.tsx +1 -1
  227. package/src/components/ui/select/Select.stories.tsx +1 -1
  228. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  229. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  230. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  231. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  232. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  233. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  234. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  235. package/src/components/ui/tile/Tile.test.tsx +48 -0
  236. package/src/components/ui/tile/Tile.tsx +64 -0
  237. package/src/components/ui/tile/index.ts +2 -0
  238. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  239. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  240. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  241. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  242. package/src/hooks/index.ts +2 -0
  243. package/src/hooks/useTimer.test.ts +150 -0
  244. package/src/hooks/useTimer.ts +77 -0
  245. package/src/index.ts +3 -0
  246. package/src/pages.ts +1 -1
  247. package/src/stories/PresetShowcase.stories.tsx +1 -1
  248. package/src/stories/ThemePresets.stories.tsx +1 -1
  249. package/src/theme/ColorSystem.stories.tsx +1 -1
  250. package/src/theme/Colors.stories.tsx +5 -8
  251. package/src/theme/config.ts +4 -0
  252. package/src/theme/elevation.stories.tsx +1 -1
  253. package/src/theme/global.css +5 -1
  254. package/src/theme/shadows.stories.tsx +1 -1
  255. package/src/theme/tokens/primitives.ts +11 -107
  256. package/src/theme/tokens/semantic.ts +8 -0
  257. package/src/theme/workout-tokens.ts +11 -7
  258. package/tailwind.config.js +5 -0
package/dist/pages.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useState, useMemo, useEffect, Fragment as Fragment$1, useSyncExternalStore, useCallback } from 'react';
2
- import { Easing, Platform, View, Text, Pressable, TextInput, Animated, ScrollView } from 'react-native';
2
+ import { Easing, Platform, View, Text, TextInput, Pressable, Animated, ScrollView } from 'react-native';
3
3
  import { Fragment, jsx as jsx$1, jsxs as jsxs$1 } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
@@ -8,21 +8,18 @@ import { twMerge } from 'tailwind-merge';
8
8
 
9
9
  // src/theme/tokens/primitives.ts
10
10
  var primitiveColors = {
11
- // Blue scale (primary brand)
11
+ // Blue scale
12
12
  blue: {
13
13
  400: "#60A5FA",
14
14
  600: "#5048E5",
15
- // Primary main
16
15
  700: "#3832A0"},
17
- // Red scale (error)
16
+ // Red scale
18
17
  red: {
19
- // Error light
20
18
  500: "#EF4444",
21
19
  600: "#D14343"},
22
- // Vivid red scale (alert)
20
+ // Vivid red scale
23
21
  redVivid: {
24
22
  400: "#FF6070",
25
- // Error vivid light
26
23
  500: "#FF4757",
27
24
  700: "#C42539"},
28
25
  // Neutral/Gray scale
@@ -36,6 +33,8 @@ var primitiveColors = {
36
33
  900: "#111827"},
37
34
  // Charcoal scale (dark backgrounds)
38
35
  charcoal: {
36
+ 0: "#6E6E6E",
37
+ 100: "#4C4C4C",
39
38
  200: "#3C3C3C",
40
39
  300: "#2C2C2C",
41
40
  400: "#1F1F1F",
@@ -65,11 +64,16 @@ var primitiveRamps = {
65
64
  50: "#FFF7DD",
66
65
  200: "#FFD352",
67
66
  300: "#F9B415",
67
+ // pin
68
+ 400: "#E08C00",
68
69
  500: "#C27400"},
69
70
  green: {
70
71
  50: "#E3FFEE",
71
72
  200: "#58F69E",
72
73
  300: "#2ED573",
74
+ // pin
75
+ 400: "#21C05D",
76
+ 500: "#22A444",
73
77
  600: "#298732"},
74
78
  cyan: {
75
79
  // pin
@@ -77,7 +81,8 @@ var primitiveRamps = {
77
81
  500: "#2697B7",
78
82
  600: "#307B9B",
79
83
  700: "#2A617F",
80
- 800: "#22465F"},
84
+ 800: "#22465F",
85
+ 900: "#0B3149"},
81
86
  blue: {
82
87
  50: "#EFF8FF",
83
88
  300: "#78C2FF",
@@ -132,15 +137,22 @@ var WORKOUT_TOKENS = {
132
137
  // Canonical 4-band performance scale — the single source for BOTH the
133
138
  // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
134
139
  // is intentionally inverted between the two consumers: for velocity, green =
135
- // fastest/best; for RPE, green = easiest. This is the [0,1,2,4] subsample of
136
- // the canonical `sequentialEffort` primitive. The `orange` key is the band
137
- // label. Consumed inline (RN).
140
+ // fastest/best; for RPE, green = easiest. This is the [0,2,3,4] subsample of
141
+ // the canonical `sequentialEffort` primitive: an EVEN hue walk across the ramp
142
+ // (green gold → orange → red). The earlier [0,1,2,4] spent two of its four
143
+ // bands on adjacent yellows (amber-200 + amber-300) and skipped orange, reading
144
+ // yellow-heavy with an abrupt jump to red; sampling amber-300 + orange-400
145
+ // instead gives four cleanly separable bands that hold at 3px strip scale.
146
+ // Consumed inline (RN).
138
147
  scale: {
139
148
  green: sequentialEffort[0],
140
- yellow: sequentialEffort[1],
141
- orange: sequentialEffort[2],
142
- // gold band
149
+ // green-300
150
+ yellow: sequentialEffort[2],
151
+ // amber-300 (gold)
152
+ orange: sequentialEffort[3],
153
+ // orange-400 (true orange)
143
154
  red: sequentialEffort[4]
155
+ // red-600
144
156
  },
145
157
  // Surface and border colors were removed here: those flip with the theme, so
146
158
  // components use the CSS custom properties (var(--color-surface-*/border-*))
@@ -159,12 +171,6 @@ var WORKOUT_TOKENS = {
159
171
  function roundRpe(rpe) {
160
172
  return Math.round(rpe * 2) / 2;
161
173
  }
162
- function rpeColor(rpe) {
163
- if (rpe >= 9.5) return WORKOUT_TOKENS.scale.red;
164
- if (rpe >= 8.5) return WORKOUT_TOKENS.scale.orange;
165
- if (rpe >= 7) return WORKOUT_TOKENS.scale.yellow;
166
- return WORKOUT_TOKENS.scale.green;
167
- }
168
174
  function roundWeight(weight) {
169
175
  return Math.round(weight);
170
176
  }
@@ -192,14 +198,258 @@ function wrapJsx(fn) {
192
198
  var jsx = wrapJsx(jsx$1);
193
199
  var jsxs = wrapJsx(jsxs$1);
194
200
 
201
+ // src/components/custom/Workout/SegmentedBar.tsx
202
+ var PULSE_HALF_MS = 950;
203
+ var PULSE_MIN_OPACITY = 0.45;
204
+ var SEGMENTED_BAR_GAP = 5;
205
+ function usePulse(active) {
206
+ const [value] = useState(() => new Animated.Value(0));
207
+ useEffect(() => {
208
+ if (!active) return;
209
+ const loop = Animated.loop(
210
+ Animated.sequence([
211
+ Animated.timing(value, {
212
+ toValue: 1,
213
+ duration: PULSE_HALF_MS,
214
+ easing: Easing.inOut(Easing.ease),
215
+ useNativeDriver: false
216
+ }),
217
+ Animated.timing(value, {
218
+ toValue: 0,
219
+ duration: PULSE_HALF_MS,
220
+ easing: Easing.inOut(Easing.ease),
221
+ useNativeDriver: false
222
+ })
223
+ ])
224
+ );
225
+ loop.start();
226
+ return () => loop.stop();
227
+ }, [active, value]);
228
+ return value;
229
+ }
230
+ function SegmentedBar({
231
+ segments,
232
+ height = 8,
233
+ gap = SEGMENTED_BAR_GAP,
234
+ radius = 2,
235
+ marker = null,
236
+ segmentTestID,
237
+ style,
238
+ ...props
239
+ }) {
240
+ const pulse = usePulse(segments.some((s) => s.pulse));
241
+ return /* @__PURE__ */ jsxs(View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
242
+ segments.map((seg, i) => {
243
+ const fillStyle = {
244
+ width: `${(seg.fill ?? 1) * 100}%`,
245
+ height: "100%",
246
+ backgroundColor: seg.color,
247
+ opacity: seg.opacity
248
+ };
249
+ const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
250
+ return /* @__PURE__ */ jsx(
251
+ View,
252
+ {
253
+ style: {
254
+ flex: seg.weight ?? 1,
255
+ minWidth: 0,
256
+ height: "100%",
257
+ marginLeft: seg.leadingGap,
258
+ borderRadius: radius,
259
+ overflow: "hidden"
260
+ },
261
+ children: seg.pulse ? /* @__PURE__ */ jsx(
262
+ Animated.View,
263
+ {
264
+ style: {
265
+ ...fillStyle,
266
+ ...seg.pulseColor ? {
267
+ backgroundColor: pulse.interpolate({
268
+ inputRange: [0, 1],
269
+ outputRange: seg.pulseColor
270
+ })
271
+ } : {
272
+ opacity: pulse.interpolate({
273
+ inputRange: [0, 1],
274
+ outputRange: [PULSE_MIN_OPACITY, 1]
275
+ })
276
+ }
277
+ },
278
+ accessibilityElementsHidden: true,
279
+ testID
280
+ }
281
+ ) : /* @__PURE__ */ jsx(View, { style: fillStyle, accessibilityElementsHidden: true, testID })
282
+ },
283
+ i
284
+ );
285
+ }),
286
+ marker ? /* @__PURE__ */ jsx(
287
+ View,
288
+ {
289
+ style: {
290
+ position: "absolute",
291
+ left: `${marker.position * 100}%`,
292
+ top: 0,
293
+ bottom: 0,
294
+ width: 2,
295
+ backgroundColor: marker.color
296
+ },
297
+ accessibilityElementsHidden: true,
298
+ testID: "segmented-bar-marker"
299
+ }
300
+ ) : null
301
+ ] });
302
+ }
303
+
304
+ // src/components/custom/Workout/SetBar.tsx
305
+ var SET_STRIP_ZONES = {
306
+ slow: primitiveRamps.red[600],
307
+ moderate: primitiveRamps.orange[400],
308
+ fast: primitiveRamps.amber[300],
309
+ fastest: primitiveRamps.green[300]
310
+ };
311
+ var TODO_COLOR = primitiveColors.charcoal[300];
312
+ var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
313
+ var NOTCH_GAP = 2;
314
+ var CLUSTER_GAP = 3;
315
+ var MYO_UPCOMING_CLUSTERS = 3;
316
+ var MYO_UPCOMING_CLUSTER_LEN = 5;
317
+ var MYO_UPCOMING_FADE = 0.6;
318
+ var PULSE_RANGE = {
319
+ [SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
320
+ [SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
321
+ [SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
322
+ [SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
323
+ };
324
+ function velocityZoneColor(v) {
325
+ if (v < 0.5) return SET_STRIP_ZONES.slow;
326
+ if (v < 0.75) return SET_STRIP_ZONES.moderate;
327
+ if (v < 1) return SET_STRIP_ZONES.fast;
328
+ return SET_STRIP_ZONES.fastest;
329
+ }
330
+ function chunkedSegments(chunks, gap) {
331
+ return chunks.flatMap(
332
+ (vels, ci) => vels.map((v, ri) => ({
333
+ color: velocityZoneColor(v),
334
+ ...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
335
+ }))
336
+ );
337
+ }
338
+ function rangeSegments(floor, max, doneVels) {
339
+ return Array.from({ length: max }, (_, i) => {
340
+ if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
341
+ return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
342
+ });
343
+ }
344
+ function myoUpcomingSegments(activationLen, clusterCount) {
345
+ const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
346
+ const trail = Array.from(
347
+ { length: clusterCount },
348
+ (_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
349
+ color: SET_STRIP_VARIABLE_COLOR,
350
+ opacity: MYO_UPCOMING_FADE ** ci,
351
+ ...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
352
+ }))
353
+ ).flat();
354
+ return [...activation, ...trail];
355
+ }
356
+ function setSegments(set) {
357
+ if (set.status === "todo") {
358
+ return [{ color: TODO_COLOR }];
359
+ }
360
+ if (set.status === "done") {
361
+ return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
362
+ }
363
+ if (set.status === "range") {
364
+ return rangeSegments(set.floor, set.max, set.doneVels);
365
+ }
366
+ if (set.status === "drop") {
367
+ return chunkedSegments(set.subloads, NOTCH_GAP);
368
+ }
369
+ if (set.status === "myo") {
370
+ return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
371
+ }
372
+ if (set.status === "myo-upcoming") {
373
+ return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
374
+ }
375
+ const performed = set.velocities.map((v) => {
376
+ const color = velocityZoneColor(v);
377
+ return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
378
+ });
379
+ const remaining = Math.max(0, set.planned - set.velocities.length);
380
+ return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
381
+ }
382
+ function setBarSegmentTestID(seg) {
383
+ if (seg.pulse) return "set-strip-pulse";
384
+ if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
385
+ return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
386
+ }
387
+ function SetBar({ set, height = 8 }) {
388
+ return /* @__PURE__ */ jsx(
389
+ SegmentedBar,
390
+ {
391
+ segments: setSegments(set),
392
+ height,
393
+ gap: 0,
394
+ radius: 0,
395
+ segmentTestID: setBarSegmentTestID,
396
+ style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
397
+ testID: "set-strip-set"
398
+ }
399
+ );
400
+ }
401
+
402
+ // src/components/custom/Workout/SetStrip.tsx
403
+ var SET_STRIP_GAP = 5;
404
+ function setCategory(set) {
405
+ switch (set.status) {
406
+ case "done":
407
+ case "drop":
408
+ case "myo":
409
+ return "done";
410
+ case "active":
411
+ return "active";
412
+ case "range":
413
+ return set.doneVels.length > 0 ? "active" : "upcoming";
414
+ case "todo":
415
+ case "myo-upcoming":
416
+ return "upcoming";
417
+ }
418
+ }
419
+ function describeSets(sets) {
420
+ const done = sets.filter((s) => setCategory(s) === "done").length;
421
+ const active = sets.filter((s) => setCategory(s) === "active").length;
422
+ const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
423
+ return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
424
+ }
425
+ function SetStrip({ sets, height = 8, className, ...props }) {
426
+ return /* @__PURE__ */ jsx(
427
+ View,
428
+ {
429
+ className,
430
+ style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
431
+ accessibilityRole: "image",
432
+ accessibilityLabel: describeSets(sets),
433
+ testID: "set-strip",
434
+ ...props,
435
+ children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
436
+ }
437
+ );
438
+ }
439
+
195
440
  // src/components/custom/Workout/VelocityStrip.tsx
196
441
  var VEL_COLORS = WORKOUT_TOKENS.scale;
197
- var zoneIdToScaleToken = {
198
- speed: "green",
199
- power: "yellow",
200
- strengthSpeed: "orange",
201
- maximalStrength: "red",
202
- grinding: "red"
442
+ var bandIdToEffortColor = {
443
+ speed: sequentialEffort[0],
444
+ // green-300
445
+ power: sequentialEffort[2],
446
+ // amber-300 (gold)
447
+ strengthSpeed: sequentialEffort[3],
448
+ // orange-400
449
+ maximalStrength: sequentialEffort[4],
450
+ // red-600
451
+ grinding: sequentialEffort[5]
452
+ // red-700 — the distinct 5th band
203
453
  };
204
454
  function getVelocityZoneColor(velocity) {
205
455
  if (velocity >= 1) return "vel-green";
@@ -238,14 +488,140 @@ function classifyBand(velocity, bands) {
238
488
  return bands[bands.length - 1];
239
489
  }
240
490
  function bandColor(band) {
241
- const token = zoneIdToScaleToken[band.id];
242
- return token ? VEL_COLORS[token] : VEL_COLORS.green;
491
+ return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
243
492
  }
244
493
  function getLossStyle(loss) {
245
494
  if (loss > 25) return { color: VEL_COLORS.red };
246
495
  if (loss > 20) return { color: VEL_COLORS.orange };
247
496
  return null;
248
497
  }
498
+ var REP_GAP = 2;
499
+ var WIDE_GAP = 8;
500
+ var TODO_COLOR2 = primitiveColors.charcoal[300];
501
+ var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
502
+ function deriveDoneVelocities(set) {
503
+ switch (set.type) {
504
+ case "drop":
505
+ return set.subloads.flat();
506
+ case "myo":
507
+ return [...set.activation, ...set.clusters.flat()];
508
+ default:
509
+ return set.velocities;
510
+ }
511
+ }
512
+ function chunkedRepSlots(chunks) {
513
+ const slots = [];
514
+ chunks.forEach((chunk) => {
515
+ chunk.forEach((velocity, ri) => {
516
+ const first = slots.length === 0;
517
+ slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP });
518
+ });
519
+ });
520
+ return slots;
521
+ }
522
+ function buildSlots(set) {
523
+ switch (set.type) {
524
+ case "straight": {
525
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
526
+ return Array.from({ length: total }, (_, i) => {
527
+ const done = i < set.velocities.length;
528
+ return {
529
+ kind: done ? "rep" : "todo",
530
+ velocity: done ? set.velocities[i] : void 0,
531
+ leadingGap: i === 0 ? 0 : REP_GAP
532
+ };
533
+ });
534
+ }
535
+ case "range": {
536
+ const total = Math.max(set.max, set.velocities.length);
537
+ return Array.from({ length: total }, (_, i) => {
538
+ const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
539
+ return {
540
+ kind,
541
+ velocity: kind === "rep" ? set.velocities[i] : void 0,
542
+ leadingGap: i === 0 ? 0 : REP_GAP
543
+ };
544
+ });
545
+ }
546
+ case "amrap": {
547
+ const reps = set.velocities.map((velocity, i) => ({
548
+ kind: "rep",
549
+ velocity,
550
+ leadingGap: i === 0 ? 0 : REP_GAP
551
+ }));
552
+ reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP });
553
+ return reps;
554
+ }
555
+ case "drop":
556
+ return chunkedRepSlots(set.subloads);
557
+ case "myo": {
558
+ const slots = chunkedRepSlots([set.activation, ...set.clusters]);
559
+ if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP });
560
+ return slots;
561
+ }
562
+ case "cluster": {
563
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
564
+ return Array.from({ length: total }, (_, i) => {
565
+ const done = i < set.velocities.length;
566
+ const boundary = i > 0 && i % set.groupSize === 0;
567
+ return {
568
+ kind: done ? "rep" : "todo",
569
+ velocity: done ? set.velocities[i] : void 0,
570
+ leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP
571
+ };
572
+ });
573
+ }
574
+ }
575
+ }
576
+ function setAccessibilityLabel(set, repCount) {
577
+ switch (set.type) {
578
+ case "straight":
579
+ return `Velocity strip, straight set, ${repCount} reps`;
580
+ case "range":
581
+ return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
582
+ case "amrap":
583
+ return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
584
+ case "drop":
585
+ return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
586
+ case "myo":
587
+ return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
588
+ case "cluster":
589
+ return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
590
+ }
591
+ }
592
+ function slotAccessibilityLabel(slot, index) {
593
+ switch (slot.kind) {
594
+ case "rep":
595
+ return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
596
+ case "todo":
597
+ return `Rep ${index + 1}: planned`;
598
+ case "variable":
599
+ return `Rep ${index + 1}: variable`;
600
+ case "continue":
601
+ return "Keep going";
602
+ }
603
+ }
604
+ var EXPANDED_TODO_STUB_PCT = 16;
605
+ var EXPANDED_ENCODED_PCT = 45;
606
+ var EXPANDED_HEIGHT = 60;
607
+ var FIXED_MAX_VELOCITY = 1.15;
608
+ var BARE_STUB_HEIGHT = 3;
609
+ var HERO_HEIGHT = 220;
610
+ var HERO_LABEL_HEADROOM = 20;
611
+ var HERO_BAR_GAP = 8;
612
+ var HERO_BAR_MAX_WIDTH = 52;
613
+ var HERO_BAR_RADIUS = 5;
614
+ var HERO_MIN_BAR_HEIGHT = 4;
615
+ var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
616
+ var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
617
+ function scaleDenominator(scale, maxVelocity) {
618
+ return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
619
+ }
620
+ function bareSlotHeight(slot, height, denom) {
621
+ if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
622
+ const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
623
+ return Math.max(BARE_STUB_HEIGHT, ratio * height);
624
+ }
249
625
  function getReducedMotionPreference() {
250
626
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
251
627
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
@@ -265,33 +641,11 @@ function usePrefersReducedMotion() {
265
641
  var ANIMATION_DURATION = 400;
266
642
  var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
267
643
  var POP_EASING = Easing.bezier(0.34, 1.56, 0.64, 1);
268
- function VelocityStrip({
269
- velocities,
270
- zones,
271
- liveRepIndex,
272
- expanded = false,
273
- onToggle,
274
- onRepPress,
275
- variant = "full",
276
- showInfo = true,
277
- className,
278
- ...props
279
- }) {
280
- const maxVelocity = Math.max(...velocities, 0);
281
- const meanVelocity = calculateMeanVelocity(velocities);
282
- const loss = calculateVelocityLoss(velocities);
283
- const hasZones = zones != null && zones.length > 0;
284
- const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
285
- const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
644
+ function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
286
645
  const prefersReducedMotion = usePrefersReducedMotion();
287
- const [heightAnim] = useState(() => new Animated.Value(expanded ? 60 : 3));
288
- const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
289
- const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
290
646
  const [liveScale] = useState(() => new Animated.Value(1));
291
- const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
292
- const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
293
647
  useEffect(() => {
294
- if (variant === "mini" || liveRepIndex == null || liveVelocity == null) return;
648
+ if (!active || liveRepIndex == null || liveVelocity == null) return;
295
649
  if (prefersReducedMotion) {
296
650
  liveScale.setValue(1);
297
651
  return;
@@ -321,12 +675,201 @@ function VelocityStrip({
321
675
  useNativeDriver: true
322
676
  }).start();
323
677
  }
324
- }, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
678
+ }, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
679
+ return liveScale;
680
+ }
681
+ function HeroVelocityChart({
682
+ doneVelocities,
683
+ barColorFor,
684
+ scaleDenom,
685
+ referenceVelocity,
686
+ liveRepIndex,
687
+ isNewPeak,
688
+ targetReps,
689
+ height,
690
+ className,
691
+ viewProps
692
+ }) {
693
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
694
+ const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
695
+ const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
696
+ const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
697
+ const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
698
+ const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
699
+ const repCount = doneVelocities.length;
700
+ const total = Math.max(repCount, targetReps ?? repCount);
701
+ const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
702
+ const { style: externalStyle, ...restProps } = viewProps;
703
+ return /* @__PURE__ */ jsx(
704
+ View,
705
+ {
706
+ className,
707
+ style: [{ height }, externalStyle],
708
+ accessibilityRole: "image",
709
+ accessibilityLabel: label,
710
+ testID: "velocity-strip-hero",
711
+ ...restProps,
712
+ children: /* @__PURE__ */ jsxs(
713
+ View,
714
+ {
715
+ style: {
716
+ flex: 1,
717
+ flexDirection: "row",
718
+ alignItems: "flex-end",
719
+ gap: HERO_BAR_GAP,
720
+ position: "relative",
721
+ borderBottomWidth: 2,
722
+ borderBottomColor: HERO_PENDING_COLOR
723
+ },
724
+ children: [
725
+ referencePx > 0 && /* @__PURE__ */ jsx(
726
+ View,
727
+ {
728
+ accessibilityElementsHidden: true,
729
+ style: {
730
+ position: "absolute",
731
+ left: 0,
732
+ right: 0,
733
+ bottom: referencePx,
734
+ borderTopWidth: 1,
735
+ borderStyle: "dashed",
736
+ borderColor: HERO_REFERENCE_COLOR,
737
+ pointerEvents: "none"
738
+ },
739
+ testID: "velocity-hero-reference"
740
+ }
741
+ ),
742
+ doneVelocities.map((velocity, i) => {
743
+ const isLive = liveRepIndex === i;
744
+ return /* @__PURE__ */ jsxs(
745
+ View,
746
+ {
747
+ accessibilityElementsHidden: true,
748
+ style: {
749
+ flex: 1,
750
+ maxWidth: HERO_BAR_MAX_WIDTH,
751
+ height: "100%",
752
+ alignItems: "center",
753
+ justifyContent: "flex-end"
754
+ },
755
+ children: [
756
+ /* @__PURE__ */ jsx(
757
+ Text,
758
+ {
759
+ className: "text-text-primary",
760
+ style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
761
+ testID: `velocity-label-${i}`,
762
+ children: formatVelocity(velocity)
763
+ }
764
+ ),
765
+ /* @__PURE__ */ jsx(
766
+ Animated.View,
767
+ {
768
+ style: [
769
+ {
770
+ width: "100%",
771
+ height: barHeight(velocity),
772
+ borderTopLeftRadius: HERO_BAR_RADIUS,
773
+ borderTopRightRadius: HERO_BAR_RADIUS,
774
+ backgroundColor: barColorFor(velocity)
775
+ },
776
+ isLive ? { transform: [{ scale: liveScale }] } : null
777
+ ],
778
+ testID: `velocity-bar-${i}`
779
+ }
780
+ )
781
+ ]
782
+ },
783
+ i
784
+ );
785
+ }),
786
+ Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
787
+ View,
788
+ {
789
+ accessibilityElementsHidden: true,
790
+ style: {
791
+ flex: 1,
792
+ maxWidth: HERO_BAR_MAX_WIDTH,
793
+ height: "100%",
794
+ alignItems: "center",
795
+ justifyContent: "flex-end"
796
+ },
797
+ children: /* @__PURE__ */ jsx(
798
+ View,
799
+ {
800
+ style: {
801
+ width: "100%",
802
+ height: HERO_MIN_BAR_HEIGHT * 3,
803
+ borderWidth: 1,
804
+ borderStyle: "dashed",
805
+ borderColor: HERO_PENDING_COLOR,
806
+ borderTopLeftRadius: HERO_BAR_RADIUS,
807
+ borderTopRightRadius: HERO_BAR_RADIUS
808
+ },
809
+ testID: "velocity-slot-todo"
810
+ }
811
+ )
812
+ },
813
+ `pending-${i}`
814
+ ))
815
+ ]
816
+ }
817
+ )
818
+ }
819
+ );
820
+ }
821
+ function VelocityStrip({
822
+ velocities,
823
+ set,
824
+ zones,
825
+ liveRepIndex,
826
+ expanded = true,
827
+ onToggle,
828
+ onRepPress,
829
+ variant = "expanded",
830
+ height = EXPANDED_HEIGHT,
831
+ targetReps,
832
+ scale = "peak",
833
+ showNumbers = true,
834
+ showInfo = true,
835
+ className,
836
+ ...props
837
+ }) {
838
+ const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
839
+ const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
840
+ kind: "rep",
841
+ velocity: v,
842
+ leadingGap: i === 0 ? 0 : REP_GAP
843
+ }));
844
+ const maxVelocity = Math.max(...doneVelocities, 0);
845
+ const meanVelocity = calculateMeanVelocity(doneVelocities);
846
+ const loss = calculateVelocityLoss(doneVelocities);
847
+ const framed = showNumbers || showInfo;
848
+ const scaleDenom = scaleDenominator(scale, maxVelocity);
849
+ const hasZones = zones != null && zones.length > 0;
850
+ const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
851
+ const slotColor = (slot) => {
852
+ if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
853
+ if (slot.kind === "todo") return TODO_COLOR2;
854
+ return SET_STRIP_VARIABLE_COLOR;
855
+ };
856
+ const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
857
+ const [heightAnim] = useState(() => new Animated.Value(expanded ? height : 3));
858
+ const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
859
+ const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
860
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
861
+ const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
862
+ const liveScale = useLiveRepPop(
863
+ variant === "expanded" && framed,
864
+ liveRepIndex,
865
+ liveVelocity,
866
+ isNewPeak
867
+ );
325
868
  useEffect(() => {
326
- if (variant === "mini") return;
869
+ if (variant !== "expanded" || !framed) return;
327
870
  Animated.parallel([
328
871
  Animated.timing(heightAnim, {
329
- toValue: expanded ? 60 : 3,
872
+ toValue: expanded ? height : 3,
330
873
  duration: ANIMATION_DURATION,
331
874
  easing: ANIMATION_EASING,
332
875
  useNativeDriver: false
@@ -344,39 +887,136 @@ function VelocityStrip({
344
887
  useNativeDriver: false
345
888
  })
346
889
  ]).start();
347
- }, [expanded, variant, heightAnim, labelOpacity, infoOpacity]);
890
+ }, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
891
+ if (set == null && velocities == null) return null;
892
+ const repCount = doneVelocities.length;
893
+ const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
894
+ if (variant === "hero") {
895
+ const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
896
+ return /* @__PURE__ */ jsx(
897
+ HeroVelocityChart,
898
+ {
899
+ doneVelocities,
900
+ barColorFor,
901
+ scaleDenom,
902
+ referenceVelocity: maxVelocity,
903
+ liveRepIndex,
904
+ isNewPeak,
905
+ targetReps,
906
+ height: heroHeight,
907
+ className,
908
+ viewProps: props
909
+ }
910
+ );
911
+ }
348
912
  if (variant === "mini") {
349
913
  const { style: externalStyle, ...restProps } = props;
350
914
  return /* @__PURE__ */ jsx(
351
915
  View,
352
916
  {
353
917
  className,
354
- style: [{ flexDirection: "row", height: 3, gap: 2, borderRadius: 2, overflow: "hidden" }, externalStyle],
918
+ style: [
919
+ { flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
920
+ externalStyle
921
+ ],
355
922
  accessibilityRole: "image",
356
- accessibilityLabel: `Velocity strip, ${velocities.length} reps`,
923
+ accessibilityLabel: miniLabel,
357
924
  testID: "velocity-strip-mini",
358
925
  ...restProps,
359
- children: velocities.map((v, i) => /* @__PURE__ */ jsx(
926
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
360
927
  View,
361
928
  {
362
929
  style: {
363
930
  flex: 1,
364
- backgroundColor: barColorFor(v),
931
+ backgroundColor: slotColor(slot),
365
932
  borderRadius: 1,
366
933
  minWidth: 4,
367
- height: "100%"
934
+ height: "100%",
935
+ // The container's uniform 2px gap covers rep spacing; a wide slot adds
936
+ // only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
937
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
938
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
368
939
  },
369
940
  accessibilityElementsHidden: true,
370
- testID: `velocity-bar-${i}`
941
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
371
942
  },
372
943
  i
373
944
  ))
374
945
  }
375
946
  );
376
947
  }
377
- const stripLabel = `Velocity chart for set, ${velocities.length} reps, tap to ${expanded ? "collapse" : "expand"}`;
948
+ if (!framed) {
949
+ const { style: externalStyle, ...restProps } = props;
950
+ return /* @__PURE__ */ jsx(
951
+ View,
952
+ {
953
+ className,
954
+ style: [
955
+ { flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
956
+ externalStyle
957
+ ],
958
+ accessibilityRole: "image",
959
+ accessibilityLabel: miniLabel,
960
+ testID: "velocity-strip-compact",
961
+ ...restProps,
962
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
963
+ View,
964
+ {
965
+ style: {
966
+ flex: 1,
967
+ minWidth: 4,
968
+ height: bareSlotHeight(slot, height, scaleDenom),
969
+ // Top-rounded to match the framed chart's bars (the "rounded tops from
970
+ // the numbers one"); square bottoms since bars sit on the baseline.
971
+ borderTopLeftRadius: 2,
972
+ borderTopRightRadius: 2,
973
+ backgroundColor: slotColor(slot),
974
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
975
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
976
+ },
977
+ accessibilityElementsHidden: true,
978
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
979
+ },
980
+ i
981
+ ))
982
+ }
983
+ );
984
+ }
985
+ const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
378
986
  const hasInteractiveContainer = onToggle != null;
379
987
  const hasInteractiveReps = onRepPress != null && expanded;
988
+ const setBars = set != null && slots.map((slot, i) => {
989
+ const isStraightRep = set.type === "straight" && slot.kind === "rep";
990
+ const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
991
+ return /* @__PURE__ */ jsx(
992
+ View,
993
+ {
994
+ style: {
995
+ flex: 1,
996
+ height: "100%",
997
+ justifyContent: "flex-end",
998
+ marginLeft: slot.leadingGap
999
+ },
1000
+ children: /* @__PURE__ */ jsx(
1001
+ View,
1002
+ {
1003
+ style: {
1004
+ height: `${heightPct}%`,
1005
+ minHeight: 2,
1006
+ borderTopLeftRadius: 2,
1007
+ borderTopRightRadius: 2,
1008
+ backgroundColor: slotColor(slot),
1009
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
1010
+ },
1011
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
1012
+ accessibilityRole: "image",
1013
+ accessibilityLabel: slotAccessibilityLabel(slot, i)
1014
+ }
1015
+ )
1016
+ },
1017
+ i
1018
+ );
1019
+ });
380
1020
  const stripContent = /* @__PURE__ */ jsxs(
381
1021
  Animated.View,
382
1022
  {
@@ -389,7 +1029,9 @@ function VelocityStrip({
389
1029
  borderRadius: expanded ? 6 : 2,
390
1030
  paddingTop: expanded ? 16 : 0,
391
1031
  paddingBottom: expanded ? 24 : 0,
392
- paddingHorizontal: expanded ? 6 : 0,
1032
+ // No horizontal inset the framed chart's bars span full width, matching
1033
+ // the bare spotlight and mini. Numbers/info sit in the vertical padding.
1034
+ paddingHorizontal: 0,
393
1035
  paddingVertical: expanded ? void 0 : 8,
394
1036
  overflow: expanded ? "visible" : "hidden"
395
1037
  }
@@ -399,46 +1041,65 @@ function VelocityStrip({
399
1041
  testID: "velocity-strip",
400
1042
  ...props,
401
1043
  children: [
402
- /* @__PURE__ */ jsx(
403
- View,
404
- {
405
- style: { flexDirection: "row", flex: 1, gap: 2, alignItems: "flex-end" },
406
- children: velocities.map((v, i) => {
407
- const barBackground = barColorFor(v);
408
- const barHeightPct = maxVelocity > 0 ? Math.round(v / (maxVelocity * 1.15) * 100) : 0;
409
- const isLive = liveRepIndex === i;
410
- const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
411
- const barStyle = expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: barBackground } : {
412
- minHeight: "100%",
413
- borderTopLeftRadius: 2,
414
- borderTopRightRadius: 2,
415
- borderBottomLeftRadius: 0,
416
- borderBottomRightRadius: 0,
417
- backgroundColor: barBackground
418
- };
419
- const barInner = isLive ? /* @__PURE__ */ jsx(
420
- Animated.View,
421
- {
422
- style: [barStyle, { transform: [{ scale: liveScale }] }],
423
- testID: `velocity-bar-${i}`,
424
- accessibilityRole: "image",
425
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
426
- }
427
- ) : /* @__PURE__ */ jsx(
428
- View,
429
- {
430
- style: barStyle,
431
- testID: `velocity-bar-${i}`,
432
- accessibilityRole: "image",
433
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
434
- }
435
- );
436
- const bar = /* @__PURE__ */ jsxs(
437
- View,
438
- {
439
- style: { flex: 1, height: "100%", justifyContent: "flex-end", position: "relative" },
440
- children: [
441
- expanded && /* @__PURE__ */ jsx(Animated.View, { style: { opacity: labelOpacity, alignItems: "center", position: "absolute", top: -13, left: 0, right: 0 }, accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(
1044
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
1045
+ const barBackground = barColorFor(v);
1046
+ const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
1047
+ const isLive = liveRepIndex === i;
1048
+ const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
1049
+ const barStyle = expanded ? {
1050
+ height: `${barHeightPct}%`,
1051
+ minHeight: 2,
1052
+ borderTopLeftRadius: 2,
1053
+ borderTopRightRadius: 2,
1054
+ backgroundColor: barBackground
1055
+ } : {
1056
+ minHeight: "100%",
1057
+ borderTopLeftRadius: 2,
1058
+ borderTopRightRadius: 2,
1059
+ borderBottomLeftRadius: 0,
1060
+ borderBottomRightRadius: 0,
1061
+ backgroundColor: barBackground
1062
+ };
1063
+ const barInner = isLive ? /* @__PURE__ */ jsx(
1064
+ Animated.View,
1065
+ {
1066
+ style: [barStyle, { transform: [{ scale: liveScale }] }],
1067
+ testID: `velocity-bar-${i}`,
1068
+ accessibilityRole: "image",
1069
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
1070
+ }
1071
+ ) : /* @__PURE__ */ jsx(
1072
+ View,
1073
+ {
1074
+ style: barStyle,
1075
+ testID: `velocity-bar-${i}`,
1076
+ accessibilityRole: "image",
1077
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
1078
+ }
1079
+ );
1080
+ const bar = /* @__PURE__ */ jsxs(
1081
+ View,
1082
+ {
1083
+ style: {
1084
+ flex: 1,
1085
+ height: "100%",
1086
+ justifyContent: "flex-end",
1087
+ position: "relative"
1088
+ },
1089
+ children: [
1090
+ expanded && showNumbers && /* @__PURE__ */ jsx(
1091
+ Animated.View,
1092
+ {
1093
+ style: {
1094
+ opacity: labelOpacity,
1095
+ alignItems: "center",
1096
+ position: "absolute",
1097
+ top: -13,
1098
+ left: 0,
1099
+ right: 0
1100
+ },
1101
+ accessibilityElementsHidden: true,
1102
+ children: /* @__PURE__ */ jsx(
442
1103
  Text,
443
1104
  {
444
1105
  className: "text-text-secondary",
@@ -446,30 +1107,30 @@ function VelocityStrip({
446
1107
  testID: `velocity-label-${i}`,
447
1108
  children: formatVelocity(v)
448
1109
  }
449
- ) }),
450
- barInner
451
- ]
452
- },
453
- i
454
- );
455
- if (onRepPress && expanded) {
456
- return /* @__PURE__ */ jsx(
457
- Pressable,
458
- {
459
- style: { flex: 1, height: "100%" },
460
- onPress: () => onRepPress(i, v),
461
- accessibilityRole: "button",
462
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
463
- testID: `velocity-bar-pressable-${i}`,
464
- children: bar
465
- },
466
- i
467
- );
468
- }
469
- return bar;
470
- })
1110
+ )
1111
+ }
1112
+ ),
1113
+ barInner
1114
+ ]
1115
+ },
1116
+ i
1117
+ );
1118
+ if (onRepPress && expanded) {
1119
+ return /* @__PURE__ */ jsx(
1120
+ Pressable,
1121
+ {
1122
+ style: { flex: 1, height: "100%" },
1123
+ onPress: () => onRepPress(i, v),
1124
+ accessibilityRole: "button",
1125
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
1126
+ testID: `velocity-bar-pressable-${i}`,
1127
+ children: bar
1128
+ },
1129
+ i
1130
+ );
471
1131
  }
472
- ),
1132
+ return bar;
1133
+ }) }),
473
1134
  expanded && showInfo && /* @__PURE__ */ jsxs(
474
1135
  Animated.View,
475
1136
  {
@@ -645,17 +1306,49 @@ function SvgIcon({
645
1306
  }
646
1307
 
647
1308
  // src/components/icons/icons.tsx
648
- function DumbbellIcon(props) {
1309
+ function StarIcon(props) {
1310
+ return /* @__PURE__ */ jsx(SvgIcon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" }) });
1311
+ }
1312
+ function ScaleIcon(props) {
649
1313
  return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
650
- /* @__PURE__ */ jsx("path", { d: "M14.4 14.4 9.6 9.6" }),
651
- /* @__PURE__ */ jsx("path", { d: "M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z" }),
652
- /* @__PURE__ */ jsx("path", { d: "m21.5 21.5-1.4-1.4" }),
653
- /* @__PURE__ */ jsx("path", { d: "M3.9 3.9 2.5 2.5" }),
654
- /* @__PURE__ */ jsx("path", { d: "M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z" })
1314
+ /* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
1315
+ /* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
1316
+ /* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
1317
+ /* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
1318
+ /* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
655
1319
  ] });
656
1320
  }
657
- function StarIcon(props) {
658
- return /* @__PURE__ */ jsx(SvgIcon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" }) });
1321
+ function AlertTriangleIcon(props) {
1322
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1323
+ /* @__PURE__ */ jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }),
1324
+ /* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
1325
+ /* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
1326
+ ] });
1327
+ }
1328
+ function TrendingDownIcon(props) {
1329
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1330
+ /* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
1331
+ /* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
1332
+ ] });
1333
+ }
1334
+ function CircleSlashIcon(props) {
1335
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1336
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1337
+ /* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
1338
+ ] });
1339
+ }
1340
+ function AwardIcon(props) {
1341
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1342
+ /* @__PURE__ */ jsx("path", { d: "m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526" }),
1343
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
1344
+ ] });
1345
+ }
1346
+ function InfoIcon(props) {
1347
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1348
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1349
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
1350
+ /* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
1351
+ ] });
659
1352
  }
660
1353
  var baseBadgeSizeConfig = {
661
1354
  sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
@@ -758,6 +1451,9 @@ var semanticColorsLight = {
758
1451
  "status-success-light": primitiveRamps.green[200],
759
1452
  "status-success-dark": primitiveRamps.green[600],
760
1453
  "status-success-subtle": primitiveRamps.green[50],
1454
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
1455
+ "status-live": primitiveRamps.green[300],
1456
+ "status-live-muted": primitiveRamps.green[500],
761
1457
  "status-error": primitiveRamps.red[600],
762
1458
  "status-error-light": primitiveRamps.red[500],
763
1459
  "status-error-dark": primitiveRamps.red[700],
@@ -891,6 +1587,9 @@ var semanticColorsDark = {
891
1587
  "status-success-light": primitiveRamps.green[200],
892
1588
  "status-success-dark": primitiveRamps.green[600],
893
1589
  "status-success-subtle": "rgba(46, 213, 115, 0.12)",
1590
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
1591
+ "status-live": primitiveRamps.green[300],
1592
+ "status-live-muted": primitiveRamps.green[500],
894
1593
  "status-error": primitiveRamps.red[600],
895
1594
  "status-error-light": primitiveRamps.red[500],
896
1595
  "status-error-dark": primitiveRamps.red[700],
@@ -995,93 +1694,7 @@ function getSemanticColors(mode) {
995
1694
  return mode === "dark" ? semanticColorsDark : semanticColorsLight;
996
1695
  }
997
1696
 
998
- // src/components/custom/Workout/WeightBadge.tsx
999
- function WeightBadge({
1000
- value,
1001
- unit = "lbs",
1002
- reps,
1003
- delta,
1004
- isPr,
1005
- showIcon = true,
1006
- size = "md",
1007
- onPress,
1008
- className,
1009
- ...props
1010
- }) {
1011
- const config = baseBadgeSizeConfig[size];
1012
- const textColor = isPr ? getSemanticColors("dark")["brand-primary"] : "#9CA3AF";
1013
- const iconColor = textColor;
1014
- const repMaxClause = reps != null ? `${reps} rep max` : "Estimated one rep max";
1015
- const deltaLabel = delta != null ? `, ${delta >= 0 ? "+" : ""}${delta}% change` : "";
1016
- const fullLabel = `${repMaxClause}: ${value} ${unit}${deltaLabel}`;
1017
- return /* @__PURE__ */ jsxs(
1018
- BaseBadge,
1019
- {
1020
- variant: isPr ? "pr" : "plain",
1021
- size,
1022
- onPress,
1023
- className,
1024
- icon: showIcon ? /* @__PURE__ */ jsx(DumbbellIcon, { size: config.iconSize, color: iconColor, strokeWidth: 2 }) : void 0,
1025
- accessibilityLabel: fullLabel,
1026
- testID: "weight-badge",
1027
- ...props,
1028
- children: [
1029
- /* @__PURE__ */ jsxs(
1030
- Text,
1031
- {
1032
- style: {
1033
- fontFamily: '"Space Grotesk", sans-serif',
1034
- fontWeight: "600",
1035
- fontSize: config.fontSize,
1036
- color: textColor
1037
- },
1038
- children: [
1039
- isPr ? "\u2733 " : "",
1040
- value,
1041
- " ",
1042
- unit
1043
- ]
1044
- }
1045
- ),
1046
- reps != null && /* @__PURE__ */ jsxs(
1047
- Text,
1048
- {
1049
- style: {
1050
- fontFamily: '"Space Grotesk", sans-serif',
1051
- fontWeight: "600",
1052
- fontSize: config.fontSize,
1053
- marginLeft: 2,
1054
- color: textColor,
1055
- opacity: 0.7
1056
- },
1057
- testID: "weight-badge-repmax",
1058
- children: [
1059
- reps,
1060
- "RM"
1061
- ]
1062
- }
1063
- ),
1064
- delta != null && /* @__PURE__ */ jsxs(
1065
- Text,
1066
- {
1067
- style: {
1068
- fontFamily: '"Space Grotesk", sans-serif',
1069
- fontSize: config.fontSize,
1070
- marginLeft: 4,
1071
- color: delta >= 0 ? getSemanticColors("dark")["result-improve"] : getSemanticColors("dark")["result-degrade"]
1072
- },
1073
- testID: "weight-badge-delta",
1074
- children: [
1075
- delta >= 0 ? "+" : "",
1076
- delta,
1077
- "%"
1078
- ]
1079
- }
1080
- )
1081
- ]
1082
- }
1083
- );
1084
- }
1697
+ // src/components/custom/Workout/PrBadge.tsx
1085
1698
  var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
1086
1699
  var typeLabels = {
1087
1700
  e1rm: "PR e1RM",
@@ -1165,6 +1778,204 @@ function PrBadge({
1165
1778
  }
1166
1779
  return badge;
1167
1780
  }
1781
+ var DARK = getSemanticColors("dark");
1782
+ var TEXT_ACTIVE = DARK["text-primary"];
1783
+ var TEXT_MUTED = DARK["text-secondary"];
1784
+ var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
1785
+ var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
1786
+ var typeBadge = {
1787
+ fontSize: 10,
1788
+ fontWeight: "700",
1789
+ fontFamily: "Inter, sans-serif",
1790
+ color: WORKOUT_TOKENS.scale.orange,
1791
+ backgroundColor: "rgba(255,165,2,0.12)",
1792
+ paddingVertical: 1,
1793
+ paddingHorizontal: 5,
1794
+ borderRadius: 3,
1795
+ overflow: "hidden"
1796
+ };
1797
+ function displayReps(set) {
1798
+ return set.state === "done" ? set.reps : set.target.reps;
1799
+ }
1800
+ function displayWeight(set) {
1801
+ return set.state === "done" ? set.weight : set.target.weight;
1802
+ }
1803
+ function plannedReps(set) {
1804
+ if (set.state === "done") return set.velocities.length;
1805
+ if (set.state === "live") return set.target.reps;
1806
+ return set.planned ?? set.target.reps;
1807
+ }
1808
+ function accessibilityLabel(set) {
1809
+ const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
1810
+ if (set.state === "live") return `${base}, in progress`;
1811
+ if (set.state === "todo") return `${base}, upcoming`;
1812
+ return base;
1813
+ }
1814
+ function Cell({
1815
+ width,
1816
+ children,
1817
+ testID
1818
+ }) {
1819
+ return /* @__PURE__ */ jsx(View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
1820
+ }
1821
+ function RowStrip({ set }) {
1822
+ const zones = set.velocityZones;
1823
+ if (set.state === "live") {
1824
+ return /* @__PURE__ */ jsx(
1825
+ VelocityStrip,
1826
+ {
1827
+ variant: "expanded",
1828
+ showNumbers: false,
1829
+ showInfo: false,
1830
+ height: 24,
1831
+ scale: "fixed",
1832
+ set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
1833
+ zones
1834
+ }
1835
+ );
1836
+ }
1837
+ const velocities = set.state === "done" ? set.velocities : [];
1838
+ return /* @__PURE__ */ jsx(
1839
+ VelocityStrip,
1840
+ {
1841
+ variant: "mini",
1842
+ set: { type: "straight", velocities, planned: plannedReps(set) },
1843
+ zones
1844
+ }
1845
+ );
1846
+ }
1847
+ function SetRow(set) {
1848
+ const live = set.state === "live";
1849
+ const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
1850
+ const rpe = set.state === "todo" ? null : set.rpe;
1851
+ return /* @__PURE__ */ jsxs(
1852
+ View,
1853
+ {
1854
+ style: { paddingVertical: 6, paddingHorizontal: 8 },
1855
+ accessibilityLabel: accessibilityLabel(set),
1856
+ testID: "set-row",
1857
+ children: [
1858
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
1859
+ /* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
1860
+ /* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
1861
+ /* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
1862
+ /* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
1863
+ ] }),
1864
+ /* @__PURE__ */ jsx(View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
1865
+ ]
1866
+ }
1867
+ );
1868
+ }
1869
+ var PREV_COLUMN = { label: "PREV", width: void 0 };
1870
+ function buildColumns(unit, showPrevious) {
1871
+ return [
1872
+ { label: "SET", width: 36 },
1873
+ ...showPrevious ? [PREV_COLUMN] : [],
1874
+ { label: "REPS", width: 44 },
1875
+ { label: unit.toUpperCase(), width: 56 },
1876
+ { label: "RPE", width: 36 }
1877
+ ];
1878
+ }
1879
+ function SetTableHeader({
1880
+ unit = "lbs",
1881
+ showPrevious = true,
1882
+ testID = "table-header"
1883
+ }) {
1884
+ const columns = buildColumns(unit, showPrevious);
1885
+ return /* @__PURE__ */ jsx(
1886
+ View,
1887
+ {
1888
+ className: "flex-row",
1889
+ style: {
1890
+ paddingVertical: 8,
1891
+ paddingHorizontal: 8,
1892
+ paddingBottom: 4,
1893
+ ...showPrevious ? {} : { justifyContent: "space-between" }
1894
+ },
1895
+ testID,
1896
+ children: columns.map(({ label, width }) => {
1897
+ const isFlex = width === void 0;
1898
+ return /* @__PURE__ */ jsx(
1899
+ View,
1900
+ {
1901
+ style: {
1902
+ ...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
1903
+ alignItems: "center",
1904
+ justifyContent: "center",
1905
+ gap: 8
1906
+ },
1907
+ ...isFlex ? { className: "flex-1" } : {},
1908
+ children: /* @__PURE__ */ jsx(
1909
+ Text,
1910
+ {
1911
+ className: "text-text-tertiary",
1912
+ style: {
1913
+ fontSize: 10,
1914
+ fontWeight: "600",
1915
+ fontFamily: "Inter, sans-serif",
1916
+ textTransform: "uppercase",
1917
+ letterSpacing: 0.8
1918
+ },
1919
+ children: label
1920
+ }
1921
+ )
1922
+ },
1923
+ label
1924
+ );
1925
+ })
1926
+ }
1927
+ );
1928
+ }
1929
+ var METRIC_FONT = "Inter, sans-serif";
1930
+ function MetricCell({ children, color, fontSize = 11 }) {
1931
+ return /* @__PURE__ */ jsx(
1932
+ Text,
1933
+ {
1934
+ style: {
1935
+ fontFamily: METRIC_FONT,
1936
+ fontSize,
1937
+ color,
1938
+ fontWeight: "600",
1939
+ letterSpacing: 1
1940
+ },
1941
+ children
1942
+ }
1943
+ );
1944
+ }
1945
+
1946
+ // src/components/custom/Workout/SetsRepsLoad.tsx
1947
+ var t = getSemanticColors("dark");
1948
+ var TIMES = "\xD7";
1949
+ var AT = "@";
1950
+ function SetsRepsLoad({
1951
+ sets,
1952
+ reps,
1953
+ load,
1954
+ unit = "lb",
1955
+ className,
1956
+ ...props
1957
+ }) {
1958
+ const value = t["text-primary"];
1959
+ const sep = t["text-tertiary"];
1960
+ return /* @__PURE__ */ jsxs(
1961
+ View,
1962
+ {
1963
+ className,
1964
+ style: { flexDirection: "row", alignItems: "baseline" },
1965
+ accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
1966
+ testID: "sets-reps-load",
1967
+ ...props,
1968
+ children: [
1969
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
1970
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
1971
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
1972
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
1973
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
1974
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
1975
+ ]
1976
+ }
1977
+ );
1978
+ }
1168
1979
  function hexToRgb(hex) {
1169
1980
  const cleaned = hex.replace("#", "");
1170
1981
  let r, g, b;
@@ -1474,7 +2285,7 @@ function alpha(color, opacity) {
1474
2285
  }
1475
2286
  return color;
1476
2287
  }
1477
- var t = getSemanticColors("dark");
2288
+ var t2 = getSemanticColors("dark");
1478
2289
  var TEMPO_PACING = {
1479
2290
  behindThresholdPct: 0.15,
1480
2291
  minPhaseDurationMs: 500
@@ -1490,20 +2301,20 @@ function getTempoFillPct(elapsedMs, targetMs) {
1490
2301
  return Math.min(100, elapsedMs / targetMs * 100);
1491
2302
  }
1492
2303
  ({
1493
- concentric: { color: t["status-success"]},
1494
- hold: { color: t["brand-primary"]},
1495
- eccentric: { color: t["status-warning"]}
2304
+ concentric: { color: t2["status-success"]},
2305
+ hold: { color: t2["brand-primary"]},
2306
+ eccentric: { color: t2["status-warning"]}
1496
2307
  });
1497
2308
 
1498
2309
  // src/components/custom/Workout/TempoDisplay.tsx
1499
- var t2 = getSemanticColors("dark");
2310
+ var t3 = getSemanticColors("dark");
1500
2311
  var INTER = "Inter, sans-serif";
1501
2312
  var TEXT_TERTIARY = "#6B7280";
1502
- var STATUS_ERROR = t2["status-error"];
2313
+ var STATUS_ERROR = t3["status-error"];
1503
2314
  var phaseColors = {
1504
- eccentric: t2["status-warning"],
2315
+ eccentric: t3["status-warning"],
1505
2316
  pauseBottom: TEXT_TERTIARY,
1506
- concentric: t2["status-success"],
2317
+ concentric: t3["status-success"],
1507
2318
  pauseTop: TEXT_TERTIARY,
1508
2319
  dash: TEXT_TERTIARY
1509
2320
  };
@@ -1512,34 +2323,10 @@ function TempoValue({
1512
2323
  color,
1513
2324
  fontSize
1514
2325
  }) {
1515
- return /* @__PURE__ */ jsx(
1516
- Text,
1517
- {
1518
- style: {
1519
- fontFamily: INTER,
1520
- fontSize,
1521
- color,
1522
- fontWeight: "600",
1523
- letterSpacing: 1
1524
- },
1525
- children: value
1526
- }
1527
- );
2326
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: value });
1528
2327
  }
1529
2328
  function TempoSeparator({ color, fontSize }) {
1530
- return /* @__PURE__ */ jsx(
1531
- Text,
1532
- {
1533
- style: {
1534
- fontFamily: INTER,
1535
- fontSize,
1536
- color,
1537
- fontWeight: "600",
1538
- letterSpacing: 1
1539
- },
1540
- children: "-"
1541
- }
1542
- );
2329
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: "-" });
1543
2330
  }
1544
2331
  var LIVE_PHASES = [
1545
2332
  { key: "eccentric", color: phaseColors.eccentric },
@@ -1601,6 +2388,7 @@ function TempoDisplay({
1601
2388
  tempo,
1602
2389
  size = "md",
1603
2390
  colored = true,
2391
+ showLabel = true,
1604
2392
  showInfo = true,
1605
2393
  live,
1606
2394
  onPress,
@@ -1634,7 +2422,7 @@ function TempoDisplay({
1634
2422
  },
1635
2423
  ...props,
1636
2424
  children: [
1637
- /* @__PURE__ */ jsx(
2425
+ showLabel && /* @__PURE__ */ jsx(
1638
2426
  Text,
1639
2427
  {
1640
2428
  style: {
@@ -1768,245 +2556,121 @@ function TempoDisplay({
1768
2556
  }
1769
2557
  );
1770
2558
  }
1771
- function formatAccessibilityLabel(setNumber, reps, weight, unit) {
1772
- const repsText = reps != null ? `${reps} reps` : "no reps";
1773
- const weightText = weight != null ? `at ${roundWeight(weight)} ${unit}` : "";
1774
- return `Set ${setNumber}: ${repsText}${weightText ? ` ${weightText}` : ""}`;
2559
+ var t4 = getSemanticColors("dark");
2560
+ var INDICATOR_CONFIG = {
2561
+ imbalance: { Icon: ScaleIcon, color: t4["status-error"], label: "Left/right imbalance" },
2562
+ overshoot: { Icon: AlertTriangleIcon, color: t4["status-error"], label: "Load overshoot" },
2563
+ "velocity-loss": { Icon: TrendingDownIcon, color: t4["status-warning"], label: "Velocity loss" },
2564
+ "missed-reps": { Icon: CircleSlashIcon, color: t4["status-warning"], label: "Missed reps" },
2565
+ pr: { Icon: AwardIcon, color: t4["status-success"], label: "Personal record" },
2566
+ info: { Icon: InfoIcon, color: t4["status-info"], label: "More info" }
2567
+ };
2568
+ function ExerciseIndicator({ kind, onPress, className, ...props }) {
2569
+ const { Icon, color, label } = INDICATOR_CONFIG[kind];
2570
+ const chip = /* @__PURE__ */ jsx(
2571
+ View,
2572
+ {
2573
+ className,
2574
+ style: {
2575
+ width: 16,
2576
+ height: 16,
2577
+ borderRadius: 8,
2578
+ borderWidth: 1,
2579
+ borderColor: color,
2580
+ alignItems: "center",
2581
+ justifyContent: "center"
2582
+ },
2583
+ accessibilityRole: "image",
2584
+ accessibilityLabel: label,
2585
+ testID: "exercise-indicator",
2586
+ ...props,
2587
+ children: /* @__PURE__ */ jsx(Icon, { size: 11, color })
2588
+ }
2589
+ );
2590
+ if (onPress) {
2591
+ return /* @__PURE__ */ jsx(Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, children: chip });
2592
+ }
2593
+ return chip;
1775
2594
  }
1776
- function SetRow({
1777
- mode,
1778
- setNumber,
1779
- previous,
2595
+
2596
+ // src/components/custom/Workout/ExerciseHeading.tsx
2597
+ function headingLabel(name, sets, reps, load, unit) {
2598
+ return `${name}, ${sets}\xD7${reps} @ ${roundWeight(load)} ${unit}`;
2599
+ }
2600
+ function ExerciseHeading({
2601
+ name,
2602
+ sets,
1780
2603
  reps,
1781
- weight,
1782
- rpe,
1783
- unit,
1784
- velocities,
1785
- velocityZones,
1786
- velocityLiveRepIndex,
1787
- velocityExpanded,
1788
- onVelocityToggle,
1789
- setType,
1790
- prBadges,
1791
- isNextSet,
1792
- targets
2604
+ load,
2605
+ unit = "lbs",
2606
+ tempo,
2607
+ indicator,
2608
+ dimmed,
2609
+ onPress
2610
+ }) {
2611
+ return /* @__PURE__ */ jsxs(View, { style: { opacity: dimmed ? 0.55 : 1 }, testID: "exercise-heading", children: [
2612
+ /* @__PURE__ */ jsxs(
2613
+ Pressable,
2614
+ {
2615
+ onPress,
2616
+ accessibilityRole: "button",
2617
+ accessibilityLabel: headingLabel(name, sets, reps, load, unit),
2618
+ className: "flex-row items-center",
2619
+ style: { gap: 8 },
2620
+ testID: "exercise-card-header",
2621
+ children: [
2622
+ /* @__PURE__ */ jsx(
2623
+ Text,
2624
+ {
2625
+ className: "text-text-primary",
2626
+ style: { fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: "700" },
2627
+ testID: "exercise-card-name",
2628
+ children: name
2629
+ }
2630
+ ),
2631
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2632
+ indicator && /* @__PURE__ */ jsx(ExerciseIndicator, { kind: indicator })
2633
+ ]
2634
+ }
2635
+ ),
2636
+ /* @__PURE__ */ jsxs(
2637
+ View,
2638
+ {
2639
+ className: "flex-row items-center",
2640
+ style: { gap: 8, marginTop: 1 },
2641
+ testID: "exercise-card-summary",
2642
+ children: [
2643
+ /* @__PURE__ */ jsx(SetsRepsLoad, { sets, reps, load, unit }),
2644
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2645
+ tempo && /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm", showLabel: false, showInfo: false })
2646
+ ]
2647
+ }
2648
+ )
2649
+ ] });
2650
+ }
2651
+
2652
+ // src/components/custom/Workout/ExerciseCardHeading.tsx
2653
+ function ExerciseCardHeading({
2654
+ setStates,
2655
+ stripHeight = 8,
2656
+ dimmed,
2657
+ testID = "exercise-card",
2658
+ ...heading
1793
2659
  }) {
1794
- const isActive = mode === "active";
1795
- const isCompleted = mode === "completed";
1796
- const rowStyle = {
1797
- flexDirection: "row",
1798
- alignItems: "center",
1799
- padding: 7,
1800
- paddingHorizontal: 8,
1801
- gap: 8,
1802
- borderRadius: 8
1803
- };
1804
- if (isActive && isNextSet) {
1805
- rowStyle.backgroundColor = "rgba(255,121,0,0.06)";
1806
- rowStyle.borderWidth = 1;
1807
- rowStyle.borderColor = "rgba(255,121,0,0.15)";
1808
- }
1809
- if (isCompleted && !isNextSet) {
1810
- rowStyle.opacity = 0.55;
1811
- }
1812
2660
  return /* @__PURE__ */ jsxs(
1813
2661
  View,
1814
2662
  {
1815
- style: rowStyle,
1816
- accessibilityLabel: formatAccessibilityLabel(
1817
- setNumber,
1818
- reps,
1819
- weight,
1820
- unit
1821
- ),
1822
- testID: "set-row",
2663
+ style: { paddingVertical: 9, paddingHorizontal: 12, opacity: dimmed ? 0.55 : 1 },
2664
+ testID,
1823
2665
  children: [
1824
- /* @__PURE__ */ jsx(
1825
- View,
1826
- {
1827
- style: { width: 36, alignItems: "center", justifyContent: "center" },
1828
- testID: "set-row-set-number",
1829
- children: setType ? /* @__PURE__ */ jsx(
1830
- Text,
1831
- {
1832
- style: {
1833
- fontSize: 10,
1834
- fontWeight: "700",
1835
- fontFamily: "Inter, sans-serif",
1836
- color: WORKOUT_TOKENS.scale.orange,
1837
- backgroundColor: "rgba(255,165,2,0.12)",
1838
- paddingVertical: 1,
1839
- paddingHorizontal: 5,
1840
- borderRadius: 3,
1841
- overflow: "hidden"
1842
- },
1843
- testID: "set-row-type-badge",
1844
- children: setType
1845
- }
1846
- ) : /* @__PURE__ */ jsx(
1847
- Text,
1848
- {
1849
- className: "text-text-secondary",
1850
- style: {
1851
- fontSize: 13,
1852
- fontWeight: "600",
1853
- fontFamily: "Inter, sans-serif"
1854
- },
1855
- children: setNumber
1856
- }
1857
- )
1858
- }
1859
- ),
1860
- /* @__PURE__ */ jsx(
1861
- View,
1862
- {
1863
- className: "flex-1",
1864
- testID: "set-row-previous",
1865
- children: /* @__PURE__ */ jsx(
1866
- Text,
1867
- {
1868
- className: "text-text-secondary",
1869
- style: {
1870
- fontSize: 12,
1871
- fontFamily: "Inter, sans-serif"
1872
- },
1873
- children: previous ? `${previous.reps} x ${roundWeight(previous.weight)}` : "\u2014"
1874
- }
1875
- )
1876
- }
1877
- ),
1878
- /* @__PURE__ */ jsx(
1879
- View,
1880
- {
1881
- style: { width: 44, alignItems: "center", justifyContent: "center" },
1882
- testID: "set-row-reps",
1883
- children: isActive && reps == null && targets ? /* @__PURE__ */ jsx(
1884
- Text,
1885
- {
1886
- className: "text-text-tertiary",
1887
- style: {
1888
- fontSize: 13,
1889
- fontStyle: "italic",
1890
- fontFamily: "Inter, sans-serif"
1891
- },
1892
- testID: "set-row-target-reps",
1893
- children: targets.reps
1894
- }
1895
- ) : /* @__PURE__ */ jsx(
1896
- Text,
1897
- {
1898
- className: reps != null ? "text-text-primary" : "text-text-tertiary",
1899
- style: {
1900
- fontSize: 14,
1901
- fontWeight: "600",
1902
- fontFamily: "Inter, sans-serif"
1903
- },
1904
- children: reps != null ? reps : "\u2014"
1905
- }
1906
- )
1907
- }
1908
- ),
1909
- /* @__PURE__ */ jsx(
1910
- View,
1911
- {
1912
- style: { width: 56, alignItems: "center", justifyContent: "center" },
1913
- testID: "set-row-weight",
1914
- children: isActive && weight == null && targets ? /* @__PURE__ */ jsx(
1915
- Text,
1916
- {
1917
- className: "text-text-tertiary",
1918
- style: {
1919
- fontSize: 13,
1920
- fontStyle: "italic",
1921
- fontFamily: "Inter, sans-serif"
1922
- },
1923
- testID: "set-row-target-weight",
1924
- children: roundWeight(targets.weight)
1925
- }
1926
- ) : /* @__PURE__ */ jsx(
1927
- Text,
1928
- {
1929
- className: weight != null ? "text-text-primary" : "text-text-tertiary",
1930
- style: {
1931
- fontSize: 14,
1932
- fontWeight: "600",
1933
- fontFamily: "Inter, sans-serif"
1934
- },
1935
- children: weight != null ? roundWeight(weight) : "\u2014"
1936
- }
1937
- )
1938
- }
1939
- ),
1940
- /* @__PURE__ */ jsx(
1941
- View,
1942
- {
1943
- style: { width: 36, alignItems: "center", justifyContent: "center" },
1944
- testID: "set-row-rpe",
1945
- children: /* @__PURE__ */ jsx(
1946
- Text,
1947
- {
1948
- className: rpe != null ? void 0 : "text-text-tertiary",
1949
- style: {
1950
- fontSize: 13,
1951
- fontWeight: "600",
1952
- fontFamily: "Inter, sans-serif",
1953
- // rpeColor returns a concrete hex (native-safe); only the tertiary
1954
- // fallback needs the className token.
1955
- color: rpe != null ? rpeColor(rpe) : void 0
1956
- },
1957
- children: rpe != null ? roundRpe(rpe) : "\u2014"
1958
- }
1959
- )
1960
- }
1961
- ),
1962
- prBadges && prBadges.length > 0 && /* @__PURE__ */ jsx(
1963
- View,
1964
- {
1965
- className: "flex-row items-center",
1966
- style: { gap: 4 },
1967
- testID: "set-row-pr-badges",
1968
- children: prBadges.map((badge, i) => /* @__PURE__ */ jsx(
1969
- PrBadge,
1970
- {
1971
- type: badge.type,
1972
- label: badge.label,
1973
- animate: false,
1974
- compact: true
1975
- },
1976
- i
1977
- ))
1978
- }
1979
- ),
1980
- velocities && velocities.length > 0 && /* @__PURE__ */ jsx(
1981
- View,
1982
- {
1983
- style: { position: "absolute", bottom: 0, left: 8, right: 8 },
1984
- testID: "set-row-velocity-strip",
1985
- children: /* @__PURE__ */ jsx(
1986
- VelocityStrip,
1987
- {
1988
- velocities,
1989
- zones: velocityZones,
1990
- liveRepIndex: velocityLiveRepIndex,
1991
- expanded: velocityExpanded,
1992
- onToggle: onVelocityToggle,
1993
- variant: onVelocityToggle ? "full" : "mini"
1994
- }
1995
- )
1996
- }
1997
- )
2666
+ /* @__PURE__ */ jsx(ExerciseHeading, { ...heading }),
2667
+ setStates.length > 0 && /* @__PURE__ */ jsx(View, { style: { marginTop: 7 }, testID: "exercise-card-strip", children: /* @__PURE__ */ jsx(SetStrip, { sets: setStates, height: stripHeight }) })
1998
2668
  ]
1999
2669
  }
2000
2670
  );
2001
2671
  }
2002
- function resolveColor(token, mode = "dark") {
2003
- return Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
2004
- }
2005
2672
 
2006
2673
  // src/components/custom/Workout/ExerciseCard.tsx
2007
- function buildColumnHeaders(unit) {
2008
- return ["SET", "PREV", "REPS", unit.toUpperCase(), "RPE"];
2009
- }
2010
2674
  function getSupersetBorderRadius(position) {
2011
2675
  switch (position) {
2012
2676
  case "first":
@@ -2049,7 +2713,7 @@ function formatSummary(summary) {
2049
2713
  if (!summary) return "";
2050
2714
  return `${summary.sets}\xD7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`;
2051
2715
  }
2052
- function formatAccessibilityLabel2(name, summary, prescription) {
2716
+ function formatAccessibilityLabel(name, summary, prescription) {
2053
2717
  if (summary) return `${name}, ${formatSummary(summary)}`;
2054
2718
  if (prescription) return `${name}, ${prescription}`;
2055
2719
  return name;
@@ -2058,7 +2722,6 @@ function CollapsedCard({
2058
2722
  name,
2059
2723
  onToggle,
2060
2724
  summary,
2061
- e1rm,
2062
2725
  isPR,
2063
2726
  setVelocities,
2064
2727
  velocityZones,
@@ -2077,7 +2740,7 @@ function CollapsedCard({
2077
2740
  onPressIn: () => setPressed(true),
2078
2741
  onPressOut: () => setPressed(false),
2079
2742
  accessibilityRole: "button",
2080
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
2743
+ accessibilityLabel: formatAccessibilityLabel(name, summary, void 0),
2081
2744
  testID: "exercise-card",
2082
2745
  children: /* @__PURE__ */ jsxs(
2083
2746
  View,
@@ -2091,100 +2754,93 @@ function CollapsedCard({
2091
2754
  ...supersetMargin
2092
2755
  },
2093
2756
  children: [
2094
- /* @__PURE__ */ jsxs(
2095
- View,
2096
- {
2097
- className: "flex-row items-center",
2098
- testID: "exercise-card-header",
2099
- children: [
2100
- /* @__PURE__ */ jsx(
2101
- Text,
2102
- {
2103
- className: "text-text-primary",
2104
- style: {
2105
- fontSize: 14,
2106
- fontFamily: '"Space Grotesk", sans-serif',
2107
- fontWeight: "700"
2108
- },
2109
- testID: "exercise-card-name",
2110
- children: name
2111
- }
2112
- ),
2113
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2114
- summary && /* @__PURE__ */ jsx(
2115
- Text,
2116
- {
2117
- className: "text-text-secondary",
2118
- style: {
2119
- fontSize: 12,
2120
- fontFamily: "Inter, sans-serif",
2121
- marginRight: 8
2122
- },
2123
- testID: "exercise-card-summary",
2124
- children: formatSummary(summary)
2125
- }
2126
- ),
2127
- e1rm && /* @__PURE__ */ jsx(
2128
- WeightBadge,
2129
- {
2130
- value: roundWeight(e1rm.value),
2131
- unit: e1rm.unit,
2132
- size: "sm",
2133
- showIcon: false,
2134
- testID: "exercise-card-e1rm"
2135
- }
2136
- ),
2137
- isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2138
- ]
2139
- }
2140
- ),
2141
- (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(
2142
- View,
2143
- {
2144
- className: "flex-row",
2145
- style: { marginTop: 6, gap: 4 },
2146
- testID: "exercise-card-strips",
2147
- children: [
2148
- setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
2149
- VelocityStrip,
2150
- {
2151
- velocities,
2152
- zones: velocityZones,
2153
- variant: "mini",
2154
- testID: `exercise-card-velocity-strip-${i}`
2155
- },
2156
- i
2157
- )),
2158
- Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
2159
- PlaceholderStrip,
2160
- {
2161
- testID: `exercise-card-placeholder-${i}`
2162
- },
2163
- `placeholder-${i}`
2164
- ))
2165
- ]
2166
- }
2167
- )
2757
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", testID: "exercise-card-header", children: [
2758
+ /* @__PURE__ */ jsx(
2759
+ Text,
2760
+ {
2761
+ className: "text-text-primary",
2762
+ style: {
2763
+ fontSize: 14,
2764
+ fontFamily: '"Space Grotesk", sans-serif',
2765
+ fontWeight: "700"
2766
+ },
2767
+ testID: "exercise-card-name",
2768
+ children: name
2769
+ }
2770
+ ),
2771
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2772
+ summary && /* @__PURE__ */ jsx(
2773
+ Text,
2774
+ {
2775
+ className: "text-text-secondary",
2776
+ style: {
2777
+ fontSize: 12,
2778
+ fontFamily: "Inter, sans-serif",
2779
+ marginRight: 8
2780
+ },
2781
+ testID: "exercise-card-summary",
2782
+ children: formatSummary(summary)
2783
+ }
2784
+ ),
2785
+ isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2786
+ ] }),
2787
+ (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(View, { className: "flex-row", style: { marginTop: 6, gap: 4 }, testID: "exercise-card-strips", children: [
2788
+ setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
2789
+ VelocityStrip,
2790
+ {
2791
+ velocities,
2792
+ zones: velocityZones,
2793
+ variant: "mini",
2794
+ testID: `exercise-card-velocity-strip-${i}`
2795
+ },
2796
+ i
2797
+ )),
2798
+ Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
2799
+ PlaceholderStrip,
2800
+ {
2801
+ testID: `exercise-card-placeholder-${i}`
2802
+ },
2803
+ `placeholder-${i}`
2804
+ ))
2805
+ ] })
2168
2806
  ]
2169
2807
  }
2170
2808
  )
2171
2809
  }
2172
2810
  );
2173
2811
  }
2812
+ var DARK2 = getSemanticColors("dark");
2813
+ var BODY_DIVIDER = DARK2["border-subtle"];
2814
+ function deriveHeaderSetStates(sets) {
2815
+ return sets.map((set) => {
2816
+ switch (set.state) {
2817
+ case "live":
2818
+ return { status: "active", velocities: set.velocities, planned: set.target.reps };
2819
+ case "done":
2820
+ return { status: "done", velocities: set.velocities };
2821
+ case "todo":
2822
+ return { status: "todo", planned: set.planned ?? set.target.reps };
2823
+ }
2824
+ });
2825
+ }
2174
2826
  function ExpandedCard({
2175
2827
  name,
2176
2828
  onToggle,
2177
2829
  summary,
2178
- e1rm,
2179
2830
  isPR,
2180
2831
  sets,
2181
2832
  tempo,
2833
+ indicator,
2834
+ setStates,
2835
+ stripHeight = 8,
2836
+ velocityZones,
2182
2837
  supersetPosition
2183
2838
  }) {
2184
2839
  const borderRadius = getSupersetBorderRadius(supersetPosition);
2185
2840
  const supersetMargin = getSupersetMargin(supersetPosition);
2186
2841
  const unit = summary?.unit ?? sets?.[0]?.unit ?? "lbs";
2187
- const columnHeaders = buildColumnHeaders(unit);
2842
+ const headerStates = setStates ?? (sets ? deriveHeaderSetStates(sets) : []);
2843
+ const headerIndicator = indicator ?? (isPR ? "pr" : void 0);
2188
2844
  return /* @__PURE__ */ jsxs(
2189
2845
  View,
2190
2846
  {
@@ -2197,114 +2853,32 @@ function ExpandedCard({
2197
2853
  testID: "exercise-card",
2198
2854
  children: [
2199
2855
  /* @__PURE__ */ jsx(
2200
- Pressable,
2856
+ ExerciseCardHeading,
2201
2857
  {
2858
+ name,
2859
+ sets: summary?.sets ?? sets?.length ?? 0,
2860
+ reps: summary?.reps ?? 0,
2861
+ load: summary?.weight ?? 0,
2862
+ unit,
2863
+ tempo,
2864
+ indicator: headerIndicator,
2865
+ setStates: headerStates,
2866
+ stripHeight,
2202
2867
  onPress: onToggle,
2203
- accessibilityRole: "button",
2204
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
2205
- "aria-expanded": true,
2206
- testID: "exercise-card-header",
2207
- children: /* @__PURE__ */ jsxs(
2208
- View,
2209
- {
2210
- className: "flex-row items-center",
2211
- style: {
2212
- padding: 12,
2213
- paddingHorizontal: 14,
2214
- paddingBottom: 10,
2215
- borderBottomWidth: 1,
2216
- borderBottomColor: resolveColor("border-default")
2217
- },
2218
- children: [
2219
- /* @__PURE__ */ jsx(
2220
- Text,
2221
- {
2222
- className: "text-text-primary",
2223
- style: {
2224
- fontSize: 14,
2225
- fontFamily: '"Space Grotesk", sans-serif',
2226
- fontWeight: "700"
2227
- },
2228
- testID: "exercise-card-name",
2229
- children: name
2230
- }
2231
- ),
2232
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2233
- summary && /* @__PURE__ */ jsx(
2234
- Text,
2235
- {
2236
- className: "text-text-secondary",
2237
- style: {
2238
- fontSize: 12,
2239
- fontFamily: "Inter, sans-serif",
2240
- marginRight: 8
2241
- },
2242
- testID: "exercise-card-summary",
2243
- children: formatSummary(summary)
2244
- }
2245
- ),
2246
- e1rm && /* @__PURE__ */ jsx(
2247
- WeightBadge,
2248
- {
2249
- value: roundWeight(e1rm.value),
2250
- unit: e1rm.unit,
2251
- size: "sm",
2252
- showIcon: false,
2253
- testID: "exercise-card-e1rm"
2254
- }
2255
- ),
2256
- isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2257
- ]
2258
- }
2259
- )
2868
+ testID: "exercise-card-heading"
2260
2869
  }
2261
2870
  ),
2262
- tempo && /* @__PURE__ */ jsx(View, { style: { marginTop: 8, paddingHorizontal: 14 }, testID: "exercise-card-tempo", children: /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm" }) }),
2263
- /* @__PURE__ */ jsx(
2871
+ /* @__PURE__ */ jsxs(
2264
2872
  View,
2265
2873
  {
2266
- className: "flex-row",
2267
- style: {
2268
- paddingVertical: 8,
2269
- paddingHorizontal: 8,
2270
- paddingBottom: 4
2271
- },
2272
- testID: "exercise-card-column-headers",
2273
- children: columnHeaders.map((header, i) => {
2274
- const widths = [36, void 0, 44, 56, 36];
2275
- const width = widths[i];
2276
- const isFlex = width === void 0;
2277
- return /* @__PURE__ */ jsx(
2278
- View,
2279
- {
2280
- style: {
2281
- ...isFlex ? {} : { width },
2282
- alignItems: "center",
2283
- justifyContent: "center",
2284
- gap: 8
2285
- },
2286
- ...isFlex ? { className: "flex-1" } : {},
2287
- children: /* @__PURE__ */ jsx(
2288
- Text,
2289
- {
2290
- className: "text-text-tertiary",
2291
- style: {
2292
- fontSize: 10,
2293
- fontWeight: "600",
2294
- fontFamily: "Inter, sans-serif",
2295
- textTransform: "uppercase",
2296
- letterSpacing: 0.8
2297
- },
2298
- children: header
2299
- }
2300
- )
2301
- },
2302
- header
2303
- );
2304
- })
2874
+ style: { borderTopWidth: 1, borderTopColor: BODY_DIVIDER, paddingBottom: 6 },
2875
+ testID: "exercise-card-body",
2876
+ children: [
2877
+ /* @__PURE__ */ jsx(SetTableHeader, { unit, showPrevious: false, testID: "exercise-card-column-headers" }),
2878
+ sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((set, i) => /* @__PURE__ */ jsx(SetRow, { ...set, velocityZones: set.velocityZones ?? velocityZones }, i)) })
2879
+ ]
2305
2880
  }
2306
- ),
2307
- sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((setProps, i) => /* @__PURE__ */ jsx(SetRow, { ...setProps }, i)) })
2881
+ )
2308
2882
  ]
2309
2883
  }
2310
2884
  );
@@ -2313,11 +2887,12 @@ function UpcomingCard({
2313
2887
  name,
2314
2888
  prescription,
2315
2889
  previousBest,
2316
- supersetPosition
2890
+ supersetPosition,
2891
+ onToggle
2317
2892
  }) {
2318
2893
  const borderRadius = getSupersetBorderRadius(supersetPosition);
2319
2894
  const supersetMargin = getSupersetMargin(supersetPosition);
2320
- return /* @__PURE__ */ jsx(
2895
+ return /* @__PURE__ */ jsx(Pressable, { onPress: onToggle, accessibilityRole: "button", children: /* @__PURE__ */ jsx(
2321
2896
  View,
2322
2897
  {
2323
2898
  style: {
@@ -2327,7 +2902,7 @@ function UpcomingCard({
2327
2902
  ...borderRadius,
2328
2903
  ...supersetMargin
2329
2904
  },
2330
- accessibilityLabel: formatAccessibilityLabel2(name, void 0, prescription),
2905
+ accessibilityLabel: formatAccessibilityLabel(name, void 0, prescription),
2331
2906
  testID: "exercise-card",
2332
2907
  children: /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", children: [
2333
2908
  /* @__PURE__ */ jsx(
@@ -2335,6 +2910,7 @@ function UpcomingCard({
2335
2910
  {
2336
2911
  className: "text-text-primary",
2337
2912
  style: {
2913
+ flexShrink: 0,
2338
2914
  fontSize: 14,
2339
2915
  fontFamily: '"Space Grotesk", sans-serif',
2340
2916
  fontWeight: "700"
@@ -2347,7 +2923,9 @@ function UpcomingCard({
2347
2923
  Text,
2348
2924
  {
2349
2925
  className: "text-text-secondary",
2926
+ numberOfLines: 1,
2350
2927
  style: {
2928
+ flexShrink: 1,
2351
2929
  fontSize: 12,
2352
2930
  fontFamily: "Inter, sans-serif",
2353
2931
  marginLeft: 8
@@ -2356,12 +2934,14 @@ function UpcomingCard({
2356
2934
  children: prescription
2357
2935
  }
2358
2936
  ),
2359
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2937
+ /* @__PURE__ */ jsx(View, { className: "flex-1", style: { minWidth: 8 } }),
2360
2938
  previousBest && /* @__PURE__ */ jsx(
2361
2939
  Text,
2362
2940
  {
2363
2941
  className: "text-text-tertiary",
2942
+ numberOfLines: 1,
2364
2943
  style: {
2944
+ flexShrink: 1,
2365
2945
  fontSize: 11,
2366
2946
  fontFamily: "Inter, sans-serif"
2367
2947
  },
@@ -2371,18 +2951,31 @@ function UpcomingCard({
2371
2951
  )
2372
2952
  ] })
2373
2953
  }
2374
- );
2954
+ ) });
2375
2955
  }
2376
- function ExerciseCard(props) {
2377
- switch (props.state) {
2378
- case "collapsed":
2379
- return /* @__PURE__ */ jsx(CollapsedCard, { ...props });
2380
- case "expanded":
2381
- return /* @__PURE__ */ jsx(ExpandedCard, { ...props });
2382
- case "upcoming":
2383
- return /* @__PURE__ */ jsx(UpcomingCard, { ...props });
2384
- }
2956
+ function ExerciseCard({
2957
+ upcoming,
2958
+ expanded,
2959
+ defaultExpanded,
2960
+ onExpandedChange,
2961
+ ...rest
2962
+ }) {
2963
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded ?? false);
2964
+ const isControlled = expanded !== void 0;
2965
+ const isExpanded = isControlled ? expanded : internalExpanded;
2966
+ const onToggle = () => {
2967
+ const next = !isExpanded;
2968
+ onExpandedChange?.(next);
2969
+ if (!isControlled) setInternalExpanded(next);
2970
+ };
2971
+ if (upcoming) return /* @__PURE__ */ jsx(UpcomingCard, { ...rest, onToggle });
2972
+ return isExpanded ? /* @__PURE__ */ jsx(ExpandedCard, { ...rest, onToggle }) : /* @__PURE__ */ jsx(CollapsedCard, { ...rest, onToggle });
2973
+ }
2974
+ function resolveColor(token, mode = "dark") {
2975
+ return Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
2385
2976
  }
2977
+
2978
+ // src/components/custom/Workout/InputBar.tsx
2386
2979
  var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
2387
2980
  var INPUT_CLASSNAME = "bg-surface-raised border-border-strong text-text-primary";
2388
2981
  var inputStyle = {
@@ -2539,7 +3132,307 @@ function InputBar({
2539
3132
  }
2540
3133
  );
2541
3134
  }
3135
+ function formatDuration(ms) {
3136
+ const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
3137
+ const minutes = Math.floor(totalSeconds / 60);
3138
+ const seconds = totalSeconds % 60;
3139
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
3140
+ }
3141
+ function useTimer(options = {}) {
3142
+ const {
3143
+ mode = "up",
3144
+ durationMs,
3145
+ elapsedMs: controlledElapsedMs,
3146
+ running = false,
3147
+ autoTick = false,
3148
+ tickMs = 1e3
3149
+ } = options;
3150
+ const isControlled = controlledElapsedMs != null;
3151
+ const [internalElapsedMs, setInternalElapsedMs] = useState(0);
3152
+ useEffect(() => {
3153
+ if (isControlled || !autoTick || !running) return;
3154
+ const id = setInterval(() => {
3155
+ setInternalElapsedMs((prev) => prev + tickMs);
3156
+ }, tickMs);
3157
+ return () => clearInterval(id);
3158
+ }, [isControlled, autoTick, running, tickMs]);
3159
+ const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs;
3160
+ const hasDuration = durationMs != null && durationMs > 0;
3161
+ const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0;
3162
+ let progress;
3163
+ if (hasDuration) {
3164
+ progress = Math.min(1, Math.max(0, elapsedMs / durationMs));
3165
+ } else {
3166
+ progress = mode === "down" ? 1 : 0;
3167
+ }
3168
+ const label = formatDuration(mode === "down" ? remainingMs : elapsedMs);
3169
+ const done = durationMs != null && elapsedMs >= durationMs;
3170
+ return { elapsedMs, remainingMs, progress, label, done };
3171
+ }
3172
+ var colorVarMap = {
3173
+ primary: "var(--color-brand-primary)",
3174
+ secondary: "var(--color-brand-secondary)",
3175
+ success: "var(--color-status-success)",
3176
+ error: "var(--color-status-error)",
3177
+ warning: "var(--color-status-warning)",
3178
+ info: "var(--color-status-info)"
3179
+ };
3180
+ function CircularProgress({
3181
+ value = 0,
3182
+ max = 100,
3183
+ isIndeterminate = false,
3184
+ size = 48,
3185
+ strokeWidth = 4,
3186
+ color = "primary",
3187
+ showValue = false,
3188
+ formatValue: formatValue2 = (v, m) => `${Math.round(v / m * 100)}%`,
3189
+ children,
3190
+ fill = false,
3191
+ className,
3192
+ ...props
3193
+ }) {
3194
+ const percentage = Math.min(Math.max(value / max * 100, 0), 100);
3195
+ const radius = (size - strokeWidth) / 2;
3196
+ const circumference = 2 * Math.PI * radius;
3197
+ const strokeDashoffset = circumference - percentage / 100 * circumference;
3198
+ const center = size / 2;
3199
+ const boxStyle = fill ? { width: "100%", aspectRatio: 1 } : { width: size, height: size };
3200
+ return /* @__PURE__ */ jsxs(
3201
+ View,
3202
+ {
3203
+ className: cn("items-center justify-center", className),
3204
+ style: boxStyle,
3205
+ accessibilityRole: "progressbar",
3206
+ accessibilityValue: {
3207
+ min: 0,
3208
+ max,
3209
+ now: isIndeterminate ? void 0 : value
3210
+ },
3211
+ ...props,
3212
+ children: [
3213
+ /* @__PURE__ */ jsx(View, { className: cn(isIndeterminate && "animate-spin"), style: boxStyle, children: /* @__PURE__ */ jsxs(
3214
+ "svg",
3215
+ {
3216
+ width: "100%",
3217
+ height: "100%",
3218
+ viewBox: `0 0 ${size} ${size}`,
3219
+ style: { position: "absolute" },
3220
+ children: [
3221
+ /* @__PURE__ */ jsx(
3222
+ "circle",
3223
+ {
3224
+ cx: center,
3225
+ cy: center,
3226
+ r: radius,
3227
+ fill: "none",
3228
+ stroke: "var(--color-border-default)",
3229
+ strokeWidth
3230
+ }
3231
+ ),
3232
+ /* @__PURE__ */ jsx(
3233
+ "circle",
3234
+ {
3235
+ cx: center,
3236
+ cy: center,
3237
+ r: radius,
3238
+ fill: "none",
3239
+ stroke: colorVarMap[color],
3240
+ strokeWidth,
3241
+ strokeLinecap: "round",
3242
+ strokeDasharray: circumference,
3243
+ strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
3244
+ transform: `rotate(-90 ${center} ${center})`,
3245
+ style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
3246
+ }
3247
+ )
3248
+ ]
3249
+ }
3250
+ ) }),
3251
+ children != null ? /* @__PURE__ */ jsx(
3252
+ View,
3253
+ {
3254
+ style: {
3255
+ position: "absolute",
3256
+ alignItems: "center",
3257
+ justifyContent: "center",
3258
+ pointerEvents: "none"
3259
+ },
3260
+ children
3261
+ }
3262
+ ) : showValue && !isIndeterminate && /* @__PURE__ */ jsx(
3263
+ Text,
3264
+ {
3265
+ className: "text-xs font-semibold text-text-primary absolute",
3266
+ style: { fontSize: Math.max(size * 0.22, 10) },
3267
+ children: formatValue2(value, max)
3268
+ }
3269
+ )
3270
+ ]
3271
+ }
3272
+ );
3273
+ }
3274
+
3275
+ // src/components/custom/CircularTimer/CircularTimer.tsx
3276
+ function CircularTimer({
3277
+ durationMs,
3278
+ elapsedMs,
3279
+ mode = "down",
3280
+ size = 160,
3281
+ strokeWidth = 8,
3282
+ fill = false,
3283
+ color = "primary",
3284
+ doneLabel,
3285
+ doneColor = "success",
3286
+ controls,
3287
+ accessibilityLabel: accessibilityLabel2,
3288
+ testID
3289
+ }) {
3290
+ const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs });
3291
+ const isDone = done && mode === "down";
3292
+ const showDone = isDone && doneLabel != null;
3293
+ const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0;
3294
+ const arc = mode === "down" ? showDone ? 1 : remainingFrac : progress;
3295
+ const ringColor = showDone ? doneColor : color;
3296
+ const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1e3);
3297
+ const a11y = accessibilityLabel2 ?? (isDone ? "Timer complete" : `${remainingSec} seconds remaining`);
3298
+ return /* @__PURE__ */ jsxs(
3299
+ View,
3300
+ {
3301
+ style: { alignItems: "center", gap: 16 },
3302
+ accessibilityRole: "timer",
3303
+ accessibilityLabel: a11y,
3304
+ testID: testID ?? "circular-timer",
3305
+ children: [
3306
+ /* @__PURE__ */ jsx(
3307
+ CircularProgress,
3308
+ {
3309
+ value: arc,
3310
+ max: 1,
3311
+ size,
3312
+ strokeWidth,
3313
+ fill,
3314
+ color: ringColor,
3315
+ accessibilityLabel: "Timer ring",
3316
+ testID: "circular-timer-ring",
3317
+ children: /* @__PURE__ */ jsx(
3318
+ Text,
3319
+ {
3320
+ className: showDone ? void 0 : "text-text-primary",
3321
+ style: {
3322
+ fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
3323
+ fontFamily: '"Space Grotesk", sans-serif',
3324
+ fontWeight: "800",
3325
+ fontVariant: ["tabular-nums"],
3326
+ letterSpacing: showDone ? 1 : -1,
3327
+ ...showDone ? { color: colorVarMap[doneColor] } : null
3328
+ },
3329
+ testID: "circular-timer-label",
3330
+ children: showDone ? doneLabel : label
3331
+ }
3332
+ )
3333
+ }
3334
+ ),
3335
+ controls
3336
+ ]
3337
+ }
3338
+ );
3339
+ }
3340
+
3341
+ // src/components/custom/Workout/RestTimer.tsx
2542
3342
  var BRAND_PRIMARY3 = getSemanticColors("dark")["brand-primary"];
3343
+ var RING_DEFAULT_SIZE = 180;
3344
+ function RestActions({ onAddTime, onSkip }) {
3345
+ return /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
3346
+ /* @__PURE__ */ jsx(
3347
+ Pressable,
3348
+ {
3349
+ onPress: onAddTime,
3350
+ style: {
3351
+ backgroundColor: "rgba(255,255,255,0.06)",
3352
+ paddingVertical: 8,
3353
+ paddingHorizontal: 20,
3354
+ borderRadius: 8
3355
+ },
3356
+ accessibilityRole: "button",
3357
+ accessibilityLabel: "Add 30 seconds",
3358
+ testID: "rest-timer-add-time",
3359
+ children: /* @__PURE__ */ jsx(
3360
+ Text,
3361
+ {
3362
+ className: "text-text-secondary",
3363
+ style: { fontSize: 11, fontFamily: "Inter, sans-serif", fontWeight: "600" },
3364
+ children: "+30s"
3365
+ }
3366
+ )
3367
+ }
3368
+ ),
3369
+ /* @__PURE__ */ jsx(
3370
+ Pressable,
3371
+ {
3372
+ onPress: onSkip,
3373
+ style: {
3374
+ backgroundColor: "rgba(255,121,0,0.12)",
3375
+ paddingVertical: 8,
3376
+ paddingHorizontal: 20,
3377
+ borderRadius: 8
3378
+ },
3379
+ accessibilityRole: "button",
3380
+ accessibilityLabel: "Skip rest",
3381
+ testID: "rest-timer-skip",
3382
+ children: /* @__PURE__ */ jsx(
3383
+ Text,
3384
+ {
3385
+ style: {
3386
+ fontSize: 11,
3387
+ fontFamily: "Inter, sans-serif",
3388
+ fontWeight: "600",
3389
+ color: BRAND_PRIMARY3
3390
+ },
3391
+ children: "Skip"
3392
+ }
3393
+ )
3394
+ }
3395
+ )
3396
+ ] });
3397
+ }
3398
+ function RestTimerRing({
3399
+ totalSeconds,
3400
+ elapsedMs,
3401
+ remainingSec,
3402
+ done,
3403
+ size,
3404
+ nextSetInfo,
3405
+ displayOnly,
3406
+ onSkip,
3407
+ onAddTime
3408
+ }) {
3409
+ const a11yLabel = done ? "Rest complete, next set ready" : `Rest timer, ${Math.max(0, remainingSec)} seconds remaining`;
3410
+ return /* @__PURE__ */ jsxs(View, { style: { alignItems: "center", gap: 16 }, testID: "rest-timer", children: [
3411
+ /* @__PURE__ */ jsx(
3412
+ CircularTimer,
3413
+ {
3414
+ durationMs: totalSeconds * 1e3,
3415
+ elapsedMs,
3416
+ mode: "down",
3417
+ size,
3418
+ doneLabel: "GO",
3419
+ doneColor: "success",
3420
+ accessibilityLabel: a11yLabel,
3421
+ controls: !displayOnly ? /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip }) : void 0,
3422
+ testID: "rest-timer-ring"
3423
+ }
3424
+ ),
3425
+ nextSetInfo != null && /* @__PURE__ */ jsx(
3426
+ Text,
3427
+ {
3428
+ className: "text-text-tertiary",
3429
+ style: { fontSize: 13, fontFamily: "Inter, sans-serif" },
3430
+ testID: "rest-timer-next-set",
3431
+ children: nextSetInfo
3432
+ }
3433
+ )
3434
+ ] });
3435
+ }
2543
3436
  function RestTimer({
2544
3437
  totalSeconds,
2545
3438
  elapsedMs,
@@ -2547,16 +3440,39 @@ function RestTimer({
2547
3440
  onAddTime,
2548
3441
  nextSetInfo,
2549
3442
  visible,
2550
- displayOnly = false
3443
+ displayOnly = false,
3444
+ variant = "bar",
3445
+ size = RING_DEFAULT_SIZE
2551
3446
  }) {
2552
- if (!visible) return null;
2553
- const remainingMs = Math.max(0, totalSeconds * 1e3 - elapsedMs);
3447
+ const {
3448
+ remainingMs,
3449
+ progress,
3450
+ label: timeDisplay,
3451
+ done
3452
+ } = useTimer({
3453
+ mode: "down",
3454
+ durationMs: totalSeconds * 1e3,
3455
+ elapsedMs
3456
+ });
2554
3457
  const remainingSec = Math.ceil(remainingMs / 1e3);
2555
- const minutes = Math.floor(remainingSec / 60);
2556
- const seconds = remainingSec % 60;
2557
- const timeDisplay = `${minutes}:${String(seconds).padStart(2, "0")}`;
2558
- const totalMs = totalSeconds * 1e3;
2559
- const progressPct = totalMs > 0 ? Math.min(100, elapsedMs / totalMs * 100) : 100;
3458
+ const progressPct = progress * 100;
3459
+ if (!visible) return null;
3460
+ if (variant === "ring") {
3461
+ return /* @__PURE__ */ jsx(
3462
+ RestTimerRing,
3463
+ {
3464
+ totalSeconds,
3465
+ elapsedMs,
3466
+ remainingSec,
3467
+ done,
3468
+ size,
3469
+ nextSetInfo,
3470
+ displayOnly,
3471
+ onSkip,
3472
+ onAddTime
3473
+ }
3474
+ );
3475
+ }
2560
3476
  return /* @__PURE__ */ jsxs(
2561
3477
  View,
2562
3478
  {
@@ -2654,62 +3570,7 @@ function RestTimer({
2654
3570
  )
2655
3571
  }
2656
3572
  ),
2657
- !displayOnly && /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
2658
- /* @__PURE__ */ jsx(
2659
- Pressable,
2660
- {
2661
- onPress: onAddTime,
2662
- style: {
2663
- backgroundColor: "rgba(255,255,255,0.06)",
2664
- paddingVertical: 8,
2665
- paddingHorizontal: 20,
2666
- borderRadius: 8
2667
- },
2668
- accessibilityRole: "button",
2669
- accessibilityLabel: "Add 30 seconds",
2670
- testID: "rest-timer-add-time",
2671
- children: /* @__PURE__ */ jsx(
2672
- Text,
2673
- {
2674
- className: "text-text-secondary",
2675
- style: {
2676
- fontSize: 11,
2677
- fontFamily: "Inter, sans-serif",
2678
- fontWeight: "600"
2679
- },
2680
- children: "+30s"
2681
- }
2682
- )
2683
- }
2684
- ),
2685
- /* @__PURE__ */ jsx(
2686
- Pressable,
2687
- {
2688
- onPress: onSkip,
2689
- style: {
2690
- backgroundColor: "rgba(255,121,0,0.12)",
2691
- paddingVertical: 8,
2692
- paddingHorizontal: 20,
2693
- borderRadius: 8
2694
- },
2695
- accessibilityRole: "button",
2696
- accessibilityLabel: "Skip rest",
2697
- testID: "rest-timer-skip",
2698
- children: /* @__PURE__ */ jsx(
2699
- Text,
2700
- {
2701
- style: {
2702
- fontSize: 11,
2703
- fontFamily: "Inter, sans-serif",
2704
- fontWeight: "600",
2705
- color: BRAND_PRIMARY3
2706
- },
2707
- children: "Skip"
2708
- }
2709
- )
2710
- }
2711
- )
2712
- ] })
3573
+ !displayOnly && /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip })
2713
3574
  ]
2714
3575
  }
2715
3576
  );
@@ -2795,23 +3656,22 @@ function deriveWorkoutProgress(exercises) {
2795
3656
  function findActiveExercise(exercises) {
2796
3657
  return exercises.find((exercise) => exercise.status === "active") ?? null;
2797
3658
  }
2798
- function statusToCardState(status, focused) {
2799
- if (status === "upcoming") return "upcoming";
2800
- return focused ? "expanded" : "collapsed";
3659
+ function isUpcomingExercise(status) {
3660
+ return status === "upcoming";
2801
3661
  }
2802
3662
  function toActiveCardProps(exercise, focused, onToggle, supersetPosition = null, supersetColor) {
2803
- const state = statusToCardState(exercise.status, focused);
3663
+ const upcoming = isUpcomingExercise(exercise.status);
2804
3664
  return {
2805
3665
  name: exercise.name,
2806
- state,
2807
- onToggle,
3666
+ upcoming,
3667
+ expanded: focused,
3668
+ onExpandedChange: onToggle,
2808
3669
  summary: exercise.summary,
2809
- e1rm: exercise.e1rm,
2810
3670
  isPR: exercise.isPR,
2811
3671
  tempo: exercise.tempo,
2812
3672
  setVelocities: exercise.setVelocities,
2813
3673
  totalPlannedSets: exercise.totalPlannedSets,
2814
- sets: state === "expanded" ? exercise.sets : void 0,
3674
+ sets: focused ? exercise.sets : void 0,
2815
3675
  prescription: exercise.prescription,
2816
3676
  previousBest: exercise.previousBest,
2817
3677
  supersetPosition,
@@ -3500,11 +4360,11 @@ function CardContent({ children, className }) {
3500
4360
  function CardFooter({ children, className }) {
3501
4361
  return /* @__PURE__ */ jsx(View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
3502
4362
  }
3503
- var t3 = getSemanticColors("dark");
4363
+ var t5 = getSemanticColors("dark");
3504
4364
  var solidVariantColors = {
3505
- success: t3["status-success"],
3506
- warning: t3["status-warning"],
3507
- error: t3["status-error"],
4365
+ success: t5["status-success"],
4366
+ warning: t5["status-warning"],
4367
+ error: t5["status-error"],
3508
4368
  neutral: "#6B7280"
3509
4369
  };
3510
4370
  var ringVariantStyles = {
@@ -3544,8 +4404,8 @@ var iconColors = {
3544
4404
  warning: "#6B4000",
3545
4405
  error: "#5C1A1A",
3546
4406
  neutral: "#D1D5DB",
3547
- "on-track": t3["status-success"],
3548
- deviation: t3["status-warning"],
4407
+ "on-track": t5["status-success"],
4408
+ deviation: t5["status-warning"],
3549
4409
  future: "#6B7280"
3550
4410
  };
3551
4411
  var sizeDimensions = {
@@ -3631,13 +4491,13 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
3631
4491
  var WORKOUT_PILL_DELOAD = primitiveRamps.magenta[600];
3632
4492
 
3633
4493
  // src/components/custom/Workout/MesoStatusCard.tsx
3634
- var t4 = getSemanticColors("dark");
3635
- var BRAND_PRIMARY5 = t4["brand-primary"];
4494
+ var t6 = getSemanticColors("dark");
4495
+ var BRAND_PRIMARY5 = t6["brand-primary"];
3636
4496
  var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
3637
4497
  var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
3638
- var SUCCESS = t4["status-success"];
3639
- var WARNING = t4["status-warning"];
3640
- var ERROR = t4["status-error"];
4498
+ var SUCCESS = t6["status-success"];
4499
+ var WARNING = t6["status-warning"];
4500
+ var ERROR = t6["status-error"];
3641
4501
  var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY5, BRAND_PRIMARY_LIGHT];
3642
4502
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
3643
4503
  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%)";
@@ -3709,7 +4569,7 @@ function StatusPill({ badge }) {
3709
4569
  }
3710
4570
  );
3711
4571
  }
3712
- function MetricCell({ metric }) {
4572
+ function MetricCell2({ metric }) {
3713
4573
  return /* @__PURE__ */ jsxs(View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
3714
4574
  /* @__PURE__ */ jsx(
3715
4575
  Text,
@@ -3927,7 +4787,7 @@ function MesoStatusCard({
3927
4787
  className: "flex-row flex-wrap",
3928
4788
  style: { gap: 8 },
3929
4789
  testID: "meso-status-card-metrics",
3930
- children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
4790
+ children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell2, { metric }, `${metric.label}-${index}`))
3931
4791
  }
3932
4792
  ),
3933
4793
  gauges.length > 0 && /* @__PURE__ */ jsx(View, { style: { gap: 12 }, testID: "meso-status-card-gauges", children: gauges.map((gauge, index) => /* @__PURE__ */ jsx(Gauge, { gauge }, `${gauge.label}-${index}`)) }),
@@ -4008,8 +4868,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
4008
4868
  var PLOT_LEFT = 26;
4009
4869
  var TOOLTIP_WIDTH = 124;
4010
4870
  function timeOf(dateStr) {
4011
- const t10 = Date.parse(dateStr);
4012
- return Number.isNaN(t10) ? 0 : t10;
4871
+ const t12 = Date.parse(dateStr);
4872
+ return Number.isNaN(t12) ? 0 : t12;
4013
4873
  }
4014
4874
  function formatValue(value) {
4015
4875
  return `${Math.round(value)}`;
@@ -4498,10 +5358,10 @@ function StrengthTrendChart({
4498
5358
  }
4499
5359
  );
4500
5360
  }
4501
- var t5 = getSemanticColors("dark");
4502
- var STATUS_SUCCESS2 = t5["status-success"];
4503
- var STATUS_WARNING2 = t5["status-warning"];
4504
- var STATUS_INFO = t5["status-info"];
5361
+ var t7 = getSemanticColors("dark");
5362
+ var STATUS_SUCCESS2 = t7["status-success"];
5363
+ var STATUS_WARNING2 = t7["status-warning"];
5364
+ var STATUS_INFO = t7["status-info"];
4505
5365
  var DOT_BORDER = "#F3F4F6";
4506
5366
  var BAND_FILL = "rgba(46,213,115,0.1)";
4507
5367
  var BAND_EDGE = "rgba(46,213,115,0.45)";
@@ -4544,13 +5404,13 @@ function interpEdge(pixels, x, key) {
4544
5404
  const a = pixels[i];
4545
5405
  const b = pixels[i + 1];
4546
5406
  if (x >= a.x && x <= b.x) {
4547
- const t10 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
4548
- return a[key] + t10 * (b[key] - a[key]);
5407
+ const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
5408
+ return a[key] + t12 * (b[key] - a[key]);
4549
5409
  }
4550
5410
  }
4551
5411
  return pixels[pixels.length - 1][key];
4552
5412
  }
4553
- function buildColumns(pixels, step) {
5413
+ function buildColumns2(pixels, step) {
4554
5414
  if (pixels.length < 2) return [];
4555
5415
  const start = pixels[0].x;
4556
5416
  const end = pixels[pixels.length - 1].x;
@@ -4662,7 +5522,7 @@ function CapacityBandChart({
4662
5522
  }
4663
5523
  const { toX, toY } = scale;
4664
5524
  const bandPixels = toPixels(band, toX, toY);
4665
- const columns = buildColumns(bandPixels, COLUMN_STEP);
5525
+ const columns = buildColumns2(bandPixels, COLUMN_STEP);
4666
5526
  const topEdge = buildEdges(bandPixels, "yHigh");
4667
5527
  const bottomEdge = buildEdges(bandPixels, "yLow");
4668
5528
  const lastBandPoint = band[band.length - 1];
@@ -4737,8 +5597,8 @@ function CapacityBandChart({
4737
5597
  testID: "capacity-band-chart-reveal",
4738
5598
  children: /* @__PURE__ */ jsxs(View, { style: { width, height, position: "relative" }, children: [
4739
5599
  projection && /* @__PURE__ */ jsxs(View, { testID: "capacity-band-chart-projection", children: [
4740
- renderColumns(buildColumns(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
4741
- renderColumns(buildColumns(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5600
+ renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5601
+ renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
4742
5602
  renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
4743
5603
  renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
4744
5604
  renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
@@ -4899,10 +5759,9 @@ function deriveExerciseStats(entries) {
4899
5759
  function toExerciseCardProps(entry, expanded, onToggle) {
4900
5760
  return {
4901
5761
  name: entry.title,
4902
- state: expanded ? "expanded" : "collapsed",
4903
- onToggle,
5762
+ expanded,
5763
+ onExpandedChange: onToggle,
4904
5764
  summary: entry.summary,
4905
- e1rm: entry.e1rm,
4906
5765
  isPR: entry.isPR,
4907
5766
  tempo: entry.tempo,
4908
5767
  sets: expanded ? entry.sets : void 0
@@ -5446,6 +6305,7 @@ var colorStyles = {
5446
6305
  default: "bg-text-tertiary",
5447
6306
  primary: "bg-brand-primary",
5448
6307
  success: "bg-status-success",
6308
+ live: "bg-status-live",
5449
6309
  error: "bg-status-error",
5450
6310
  warning: "bg-status-warning",
5451
6311
  info: "bg-status-info",
@@ -5502,7 +6362,7 @@ function Indicator({
5502
6362
  colorClass,
5503
6363
  pulseMode === "opacity" && "animate-pulse",
5504
6364
  ring && "border-2 border-background-base",
5505
- glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
6365
+ glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
5506
6366
  glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
5507
6367
  glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
5508
6368
  glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
@@ -5584,7 +6444,7 @@ function Badge({
5584
6444
  }
5585
6445
  );
5586
6446
  }
5587
- var t6 = getSemanticColors("dark");
6447
+ var t8 = getSemanticColors("dark");
5588
6448
  var statusBgColors = {
5589
6449
  completed: "rgba(46,213,115,0.15)",
5590
6450
  current: "rgba(255,121,0,0.15)",
@@ -5602,14 +6462,14 @@ var statusBorderStyles = {
5602
6462
  deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
5603
6463
  };
5604
6464
  var textColors = {
5605
- completed: t6["status-success"],
5606
- current: t6["brand-primary"],
5607
- next: t6["brand-primary"],
6465
+ completed: t8["status-success"],
6466
+ current: t8["brand-primary"],
6467
+ next: t8["brand-primary"],
5608
6468
  upcoming: "#6B7280",
5609
6469
  missed: "rgba(209,67,67,0.7)",
5610
6470
  deload: WORKOUT_PILL_DELOAD
5611
6471
  };
5612
- function usePulse(enabled) {
6472
+ function usePulse2(enabled) {
5613
6473
  const [opacity] = useState(() => new Animated.Value(1));
5614
6474
  useEffect(() => {
5615
6475
  if (!enabled) {
@@ -5647,7 +6507,7 @@ function WorkoutPill({
5647
6507
  ...props
5648
6508
  }) {
5649
6509
  const shouldPulse = pulse ?? status === "current";
5650
- const pulseOpacity = usePulse(shouldPulse);
6510
+ const pulseOpacity = usePulse2(shouldPulse);
5651
6511
  const isCompleted = status === "completed";
5652
6512
  const isMissed = status === "missed";
5653
6513
  const pill = /* @__PURE__ */ jsx(
@@ -5737,19 +6597,19 @@ function WorkoutPill({
5737
6597
  }
5738
6598
  return pill;
5739
6599
  }
5740
- var t7 = getSemanticColors("dark");
6600
+ var t9 = getSemanticColors("dark");
5741
6601
  var TRACK_BG = "#333333";
5742
6602
  var LABEL_COLOR = "#6B7280";
5743
6603
  var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
5744
6604
  var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
5745
6605
  var ZONE = {
5746
- building: t7["status-success"],
6606
+ building: t9["status-success"],
5747
6607
  // below target ramp-up
5748
- approaching: t7["status-warning"],
6608
+ approaching: t9["status-warning"],
5749
6609
  // nearing target
5750
- target: t7["brand-primary"],
6610
+ target: t9["brand-primary"],
5751
6611
  // exactly at target
5752
- over1: t7["status-error"],
6612
+ over1: t9["status-error"],
5753
6613
  // mild over-target
5754
6614
  over2: WORKOUT_TOKENS.intensity.over2,
5755
6615
  // moderate over-target
@@ -6172,13 +7032,13 @@ function MesoCard({
6172
7032
  }
6173
7033
  ) });
6174
7034
  }
6175
- var t8 = getSemanticColors("dark");
7035
+ var t10 = getSemanticColors("dark");
6176
7036
  var dotColorMap = {
6177
7037
  untrained: "#2C2C2C",
6178
- behind: t8["brand-secondary"],
6179
- ontrack: t8["status-success"],
6180
- target: t8["brand-primary"],
6181
- over: t8["status-error"]
7038
+ behind: t10["brand-secondary"],
7039
+ ontrack: t10["status-success"],
7040
+ target: t10["brand-primary"],
7041
+ over: t10["status-error"]
6182
7042
  };
6183
7043
  function MuscleGroupChip({
6184
7044
  name,
@@ -6252,10 +7112,10 @@ function MuscleGroupChip({
6252
7112
  }
6253
7113
 
6254
7114
  // src/components/custom/Workout/WorkoutCard.tsx
6255
- var t9 = getSemanticColors("dark");
7115
+ var t11 = getSemanticColors("dark");
6256
7116
  var COLORS = {
6257
- statusSuccess: t9["status-success"],
6258
- brandPrimary: t9["brand-primary"],
7117
+ statusSuccess: t11["status-success"],
7118
+ brandPrimary: t11["brand-primary"],
6259
7119
  borderDefault: "#1F1F1F",
6260
7120
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
6261
7121
  };
@@ -6539,8 +7399,8 @@ function WeekLevel({ week, onSelectWorkout }) {
6539
7399
  function WorkoutLevel({ workout, expandedExercise, onToggleExercise }) {
6540
7400
  const exercises = workout.exercises.map((exercise, index) => ({
6541
7401
  ...exercise,
6542
- state: exercise.state === "upcoming" ? exercise.state : index === expandedExercise ? "expanded" : "collapsed",
6543
- onToggle: () => onToggleExercise(index)
7402
+ expanded: index === expandedExercise,
7403
+ onExpandedChange: () => onToggleExercise(index)
6544
7404
  }));
6545
7405
  return /* @__PURE__ */ jsx(View, { testID: "program-planning-page-workout-level", children: /* @__PURE__ */ jsx(
6546
7406
  WorkoutCard,
@@ -6669,6 +7529,6 @@ function ProgramPlanningPage({
6669
7529
  );
6670
7530
  }
6671
7531
 
6672
- export { ActiveWorkoutPage, EXERCISE_DETAIL_TABS, ExerciseDetailPage, ProgramPlanningPage, buildBreadcrumbs, countCompletedSets, deriveExerciseStats, deriveNavLevel, deriveWorkoutProgress, findActiveExercise, findMeso, findWeek, findWorkout, groupExercises, statusToCardState, summarizeVbt, toActiveCardProps, toExerciseCardProps, toProgressBarMesos };
7532
+ export { ActiveWorkoutPage, EXERCISE_DETAIL_TABS, ExerciseDetailPage, ProgramPlanningPage, buildBreadcrumbs, countCompletedSets, deriveExerciseStats, deriveNavLevel, deriveWorkoutProgress, findActiveExercise, findMeso, findWeek, findWorkout, groupExercises, isUpcomingExercise, summarizeVbt, toActiveCardProps, toExerciseCardProps, toProgressBarMesos };
6673
7533
  //# sourceMappingURL=pages.mjs.map
6674
7534
  //# sourceMappingURL=pages.mjs.map