@titan-design/react-ui 0.6.0 → 0.7.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 (248) 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 +855 -124
  6. package/dist/index.d.ts +855 -124
  7. package/dist/index.js +2276 -801
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2230 -802
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{pages-MO0JCySL.d.mts → pages-8u_4WbL-.d.mts} +280 -57
  12. package/dist/{pages-D7Jlssvp.d.ts → pages-DaWiBOGa.d.ts} +280 -57
  13. package/dist/pages.d.mts +1 -1
  14. package/dist/pages.d.ts +1 -1
  15. package/dist/pages.js +1255 -798
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1255 -798
  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/DateTime/DateTime.stories.tsx +1 -1
  34. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  35. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  36. package/src/components/custom/Metric/Metric.stories.tsx +1 -1
  37. package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
  38. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  39. package/src/components/custom/Table/Table.stories.tsx +1 -1
  40. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  41. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  42. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  43. package/src/components/custom/TimerReadout/index.ts +1 -0
  44. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  45. package/src/components/custom/Typography/Typography.stories.tsx +1 -1
  46. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  47. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  48. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
  49. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  50. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  51. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  52. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  53. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  54. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  55. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  56. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  57. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  58. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  59. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  60. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  61. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  62. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  63. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
  64. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  65. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  66. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  67. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  68. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  69. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  70. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  71. package/src/components/custom/Workout/FatigueMeter.test.tsx +73 -0
  72. package/src/components/custom/Workout/FatigueMeter.tsx +82 -0
  73. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  74. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  75. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  76. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  77. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  78. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  79. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  80. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  81. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  82. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  83. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  84. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  85. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  86. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  87. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  88. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  89. package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
  90. package/src/components/custom/Workout/README.md +146 -4
  91. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  92. package/src/components/custom/Workout/RestTimer.stories.tsx +1 -1
  93. package/src/components/custom/Workout/RestTimer.tsx +15 -10
  94. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  95. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  96. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  97. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  98. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  99. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  100. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  101. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  102. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  103. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  104. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  105. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  106. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  107. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  108. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  109. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  110. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  111. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  112. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  113. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  114. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  115. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  116. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  117. package/src/components/custom/Workout/SetBar.tsx +171 -0
  118. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  119. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  120. package/src/components/custom/Workout/SetRow.tsx +169 -232
  121. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  122. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  123. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  124. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  125. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  126. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  127. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  128. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  129. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  130. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  131. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  132. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  133. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  134. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  135. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  136. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  137. package/src/components/custom/Workout/TempoBar.stories.tsx +1 -1
  138. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  139. package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
  140. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  141. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  142. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  143. package/src/components/custom/Workout/VelocityStrip.stories.tsx +129 -66
  144. package/src/components/custom/Workout/VelocityStrip.test.tsx +376 -141
  145. package/src/components/custom/Workout/VelocityStrip.tsx +508 -120
  146. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  147. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  148. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  149. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  150. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  151. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  152. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  153. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  154. package/src/components/custom/Workout/ZoneTrack.stories.tsx +140 -0
  155. package/src/components/custom/Workout/ZoneTrack.test.tsx +200 -0
  156. package/src/components/custom/Workout/ZoneTrack.tsx +315 -0
  157. package/src/components/custom/Workout/index.ts +55 -2
  158. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  159. package/src/components/custom/Workout/metricText.tsx +34 -0
  160. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  161. package/src/components/custom/Workout/paceTone.ts +27 -0
  162. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  163. package/src/components/custom/index.ts +1 -0
  164. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  165. package/src/components/icons/icons.stories.tsx +67 -2
  166. package/src/components/icons/icons.test.tsx +32 -2
  167. package/src/components/icons/icons.tsx +108 -0
  168. package/src/components/shell/BrandLockup.stories.tsx +3 -3
  169. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  170. package/src/components/shell/DashboardShell.test.tsx +42 -0
  171. package/src/components/shell/DashboardShell.tsx +74 -0
  172. package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
  173. package/src/components/shell/DeviceMenu.stories.tsx +5 -5
  174. package/src/components/shell/DeviceRow.stories.tsx +3 -3
  175. package/src/components/shell/NavItem.stories.tsx +52 -0
  176. package/src/components/shell/NavItem.test.tsx +35 -0
  177. package/src/components/shell/NavItem.tsx +75 -0
  178. package/src/components/shell/README.md +30 -0
  179. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  180. package/src/components/shell/SessionStatePill.stories.tsx +3 -3
  181. package/src/components/shell/SessionStatePill.tsx +1 -1
  182. package/src/components/shell/SideNav.stories.tsx +81 -0
  183. package/src/components/shell/SideNav.test.tsx +51 -0
  184. package/src/components/shell/SideNav.tsx +74 -0
  185. package/src/components/shell/TopBar.stories.tsx +5 -5
  186. package/src/components/shell/index.ts +5 -0
  187. package/src/components/ui/alert/Alert.stories.tsx +1 -1
  188. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  189. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  190. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  191. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  192. package/src/components/ui/button/Button.stories.tsx +1 -1
  193. package/src/components/ui/card/Card.stories.tsx +1 -1
  194. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  195. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  196. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  197. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  198. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  199. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  200. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  201. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  202. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  203. package/src/components/ui/index.ts +1 -0
  204. package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
  205. package/src/components/ui/indicator/Indicator.test.tsx +1 -0
  206. package/src/components/ui/indicator/Indicator.tsx +6 -1
  207. package/src/components/ui/input/Input.stories.tsx +1 -1
  208. package/src/components/ui/link/Link.stories.tsx +1 -1
  209. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  210. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  211. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  212. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  213. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  214. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  215. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  216. package/src/components/ui/section/Section.stories.tsx +1 -1
  217. package/src/components/ui/select/Select.stories.tsx +1 -1
  218. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  219. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  220. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  221. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  222. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  223. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  224. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  225. package/src/components/ui/tile/Tile.test.tsx +48 -0
  226. package/src/components/ui/tile/Tile.tsx +64 -0
  227. package/src/components/ui/tile/index.ts +2 -0
  228. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  229. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  230. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  231. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  232. package/src/hooks/index.ts +2 -0
  233. package/src/hooks/useTimer.test.ts +150 -0
  234. package/src/hooks/useTimer.ts +77 -0
  235. package/src/index.ts +3 -0
  236. package/src/pages.ts +1 -1
  237. package/src/stories/PresetShowcase.stories.tsx +1 -1
  238. package/src/stories/ThemePresets.stories.tsx +1 -1
  239. package/src/theme/ColorSystem.stories.tsx +1 -1
  240. package/src/theme/Colors.stories.tsx +5 -8
  241. package/src/theme/config.ts +4 -0
  242. package/src/theme/elevation.stories.tsx +1 -1
  243. package/src/theme/global.css +5 -1
  244. package/src/theme/shadows.stories.tsx +1 -1
  245. package/src/theme/tokens/primitives.ts +11 -107
  246. package/src/theme/tokens/semantic.ts +8 -0
  247. package/src/theme/workout-tokens.ts +11 -7
  248. package/tailwind.config.js +5 -0
package/dist/pages.js CHANGED
@@ -10,21 +10,18 @@ var tailwindMerge = require('tailwind-merge');
10
10
 
11
11
  // src/theme/tokens/primitives.ts
12
12
  var primitiveColors = {
13
- // Blue scale (primary brand)
13
+ // Blue scale
14
14
  blue: {
15
15
  400: "#60A5FA",
16
16
  600: "#5048E5",
17
- // Primary main
18
17
  700: "#3832A0"},
19
- // Red scale (error)
18
+ // Red scale
20
19
  red: {
21
- // Error light
22
20
  500: "#EF4444",
23
21
  600: "#D14343"},
24
- // Vivid red scale (alert)
22
+ // Vivid red scale
25
23
  redVivid: {
26
24
  400: "#FF6070",
27
- // Error vivid light
28
25
  500: "#FF4757",
29
26
  700: "#C42539"},
30
27
  // Neutral/Gray scale
@@ -67,11 +64,16 @@ var primitiveRamps = {
67
64
  50: "#FFF7DD",
68
65
  200: "#FFD352",
69
66
  300: "#F9B415",
67
+ // pin
68
+ 400: "#E08C00",
70
69
  500: "#C27400"},
71
70
  green: {
72
71
  50: "#E3FFEE",
73
72
  200: "#58F69E",
74
73
  300: "#2ED573",
74
+ // pin
75
+ 400: "#21C05D",
76
+ 500: "#22A444",
75
77
  600: "#298732"},
76
78
  cyan: {
77
79
  // pin
@@ -79,7 +81,8 @@ var primitiveRamps = {
79
81
  500: "#2697B7",
80
82
  600: "#307B9B",
81
83
  700: "#2A617F",
82
- 800: "#22465F"},
84
+ 800: "#22465F",
85
+ 900: "#0B3149"},
83
86
  blue: {
84
87
  50: "#EFF8FF",
85
88
  300: "#78C2FF",
@@ -134,15 +137,22 @@ var WORKOUT_TOKENS = {
134
137
  // Canonical 4-band performance scale — the single source for BOTH the
135
138
  // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
136
139
  // is intentionally inverted between the two consumers: for velocity, green =
137
- // fastest/best; for RPE, green = easiest. This is the [0,1,2,4] subsample of
138
- // the canonical `sequentialEffort` primitive. The `orange` key is the band
139
- // 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).
140
147
  scale: {
141
148
  green: sequentialEffort[0],
142
- yellow: sequentialEffort[1],
143
- orange: sequentialEffort[2],
144
- // gold band
149
+ // green-300
150
+ yellow: sequentialEffort[2],
151
+ // amber-300 (gold)
152
+ orange: sequentialEffort[3],
153
+ // orange-400 (true orange)
145
154
  red: sequentialEffort[4]
155
+ // red-600
146
156
  },
147
157
  // Surface and border colors were removed here: those flip with the theme, so
148
158
  // components use the CSS custom properties (var(--color-surface-*/border-*))
@@ -161,12 +171,6 @@ var WORKOUT_TOKENS = {
161
171
  function roundRpe(rpe) {
162
172
  return Math.round(rpe * 2) / 2;
163
173
  }
164
- function rpeColor(rpe) {
165
- if (rpe >= 9.5) return WORKOUT_TOKENS.scale.red;
166
- if (rpe >= 8.5) return WORKOUT_TOKENS.scale.orange;
167
- if (rpe >= 7) return WORKOUT_TOKENS.scale.yellow;
168
- return WORKOUT_TOKENS.scale.green;
169
- }
170
174
  function roundWeight(weight) {
171
175
  return Math.round(weight);
172
176
  }
@@ -194,14 +198,258 @@ function wrapJsx(fn) {
194
198
  var jsx = wrapJsx(jsxRuntime.jsx);
195
199
  var jsxs = wrapJsx(jsxRuntime.jsxs);
196
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] = react.useState(() => new reactNative.Animated.Value(0));
207
+ react.useEffect(() => {
208
+ if (!active) return;
209
+ const loop = reactNative.Animated.loop(
210
+ reactNative.Animated.sequence([
211
+ reactNative.Animated.timing(value, {
212
+ toValue: 1,
213
+ duration: PULSE_HALF_MS,
214
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
215
+ useNativeDriver: false
216
+ }),
217
+ reactNative.Animated.timing(value, {
218
+ toValue: 0,
219
+ duration: PULSE_HALF_MS,
220
+ easing: reactNative.Easing.inOut(reactNative.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(reactNative.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
+ reactNative.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
+ reactNative.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(reactNative.View, { style: fillStyle, accessibilityElementsHidden: true, testID })
282
+ },
283
+ i
284
+ );
285
+ }),
286
+ marker ? /* @__PURE__ */ jsx(
287
+ reactNative.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
+ reactNative.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
+
197
440
  // src/components/custom/Workout/VelocityStrip.tsx
198
441
  var VEL_COLORS = WORKOUT_TOKENS.scale;
199
- var zoneIdToScaleToken = {
200
- speed: "green",
201
- power: "yellow",
202
- strengthSpeed: "orange",
203
- maximalStrength: "red",
204
- 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
205
453
  };
206
454
  function getVelocityZoneColor(velocity) {
207
455
  if (velocity >= 1) return "vel-green";
@@ -240,14 +488,132 @@ function classifyBand(velocity, bands) {
240
488
  return bands[bands.length - 1];
241
489
  }
242
490
  function bandColor(band) {
243
- const token = zoneIdToScaleToken[band.id];
244
- return token ? VEL_COLORS[token] : VEL_COLORS.green;
491
+ return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
245
492
  }
246
493
  function getLossStyle(loss) {
247
494
  if (loss > 25) return { color: VEL_COLORS.red };
248
495
  if (loss > 20) return { color: VEL_COLORS.orange };
249
496
  return null;
250
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
+ function scaleDenominator(scale, maxVelocity) {
610
+ return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
611
+ }
612
+ function bareSlotHeight(slot, height, denom) {
613
+ if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
614
+ const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
615
+ return Math.max(BARE_STUB_HEIGHT, ratio * height);
616
+ }
251
617
  function getReducedMotionPreference() {
252
618
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
253
619
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
@@ -269,31 +635,48 @@ var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
269
635
  var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
270
636
  function VelocityStrip({
271
637
  velocities,
638
+ set,
272
639
  zones,
273
640
  liveRepIndex,
274
- expanded = false,
641
+ expanded = true,
275
642
  onToggle,
276
643
  onRepPress,
277
- variant = "full",
644
+ variant = "expanded",
645
+ height = EXPANDED_HEIGHT,
646
+ scale = "peak",
647
+ showNumbers = true,
278
648
  showInfo = true,
279
649
  className,
280
650
  ...props
281
651
  }) {
282
- const maxVelocity = Math.max(...velocities, 0);
283
- const meanVelocity = calculateMeanVelocity(velocities);
284
- const loss = calculateVelocityLoss(velocities);
652
+ const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
653
+ const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
654
+ kind: "rep",
655
+ velocity: v,
656
+ leadingGap: i === 0 ? 0 : REP_GAP
657
+ }));
658
+ const maxVelocity = Math.max(...doneVelocities, 0);
659
+ const meanVelocity = calculateMeanVelocity(doneVelocities);
660
+ const loss = calculateVelocityLoss(doneVelocities);
661
+ const framed = showNumbers || showInfo;
662
+ const scaleDenom = scaleDenominator(scale, maxVelocity);
285
663
  const hasZones = zones != null && zones.length > 0;
286
664
  const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
665
+ const slotColor = (slot) => {
666
+ if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
667
+ if (slot.kind === "todo") return TODO_COLOR2;
668
+ return SET_STRIP_VARIABLE_COLOR;
669
+ };
287
670
  const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
288
671
  const prefersReducedMotion = usePrefersReducedMotion();
289
- const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ? 60 : 3));
672
+ const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ? height : 3));
290
673
  const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
291
674
  const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
292
675
  const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
293
- const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
676
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
294
677
  const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
295
678
  react.useEffect(() => {
296
- if (variant === "mini" || liveRepIndex == null || liveVelocity == null) return;
679
+ if (variant !== "expanded" || !framed || liveRepIndex == null || liveVelocity == null) return;
297
680
  if (prefersReducedMotion) {
298
681
  liveScale.setValue(1);
299
682
  return;
@@ -323,12 +706,12 @@ function VelocityStrip({
323
706
  useNativeDriver: true
324
707
  }).start();
325
708
  }
326
- }, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
709
+ }, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
327
710
  react.useEffect(() => {
328
- if (variant === "mini") return;
711
+ if (variant !== "expanded" || !framed) return;
329
712
  reactNative.Animated.parallel([
330
713
  reactNative.Animated.timing(heightAnim, {
331
- toValue: expanded ? 60 : 3,
714
+ toValue: expanded ? height : 3,
332
715
  duration: ANIMATION_DURATION,
333
716
  easing: ANIMATION_EASING,
334
717
  useNativeDriver: false
@@ -346,39 +729,118 @@ function VelocityStrip({
346
729
  useNativeDriver: false
347
730
  })
348
731
  ]).start();
349
- }, [expanded, variant, heightAnim, labelOpacity, infoOpacity]);
732
+ }, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
733
+ if (set == null && velocities == null) return null;
734
+ const repCount = doneVelocities.length;
735
+ const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
350
736
  if (variant === "mini") {
351
737
  const { style: externalStyle, ...restProps } = props;
352
738
  return /* @__PURE__ */ jsx(
353
739
  reactNative.View,
354
740
  {
355
741
  className,
356
- style: [{ flexDirection: "row", height: 3, gap: 2, borderRadius: 2, overflow: "hidden" }, externalStyle],
742
+ style: [
743
+ { flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
744
+ externalStyle
745
+ ],
357
746
  accessibilityRole: "image",
358
- accessibilityLabel: `Velocity strip, ${velocities.length} reps`,
747
+ accessibilityLabel: miniLabel,
359
748
  testID: "velocity-strip-mini",
360
749
  ...restProps,
361
- children: velocities.map((v, i) => /* @__PURE__ */ jsx(
750
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
362
751
  reactNative.View,
363
752
  {
364
753
  style: {
365
754
  flex: 1,
366
- backgroundColor: barColorFor(v),
755
+ backgroundColor: slotColor(slot),
367
756
  borderRadius: 1,
368
757
  minWidth: 4,
369
- height: "100%"
758
+ height: "100%",
759
+ // The container's uniform 2px gap covers rep spacing; a wide slot adds
760
+ // only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
761
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
762
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
763
+ },
764
+ accessibilityElementsHidden: true,
765
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
766
+ },
767
+ i
768
+ ))
769
+ }
770
+ );
771
+ }
772
+ if (!framed) {
773
+ const { style: externalStyle, ...restProps } = props;
774
+ return /* @__PURE__ */ jsx(
775
+ reactNative.View,
776
+ {
777
+ className,
778
+ style: [
779
+ { flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
780
+ externalStyle
781
+ ],
782
+ accessibilityRole: "image",
783
+ accessibilityLabel: miniLabel,
784
+ testID: "velocity-strip-compact",
785
+ ...restProps,
786
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
787
+ reactNative.View,
788
+ {
789
+ style: {
790
+ flex: 1,
791
+ minWidth: 4,
792
+ height: bareSlotHeight(slot, height, scaleDenom),
793
+ // Top-rounded to match the framed chart's bars (the "rounded tops from
794
+ // the numbers one"); square bottoms since bars sit on the baseline.
795
+ borderTopLeftRadius: 2,
796
+ borderTopRightRadius: 2,
797
+ backgroundColor: slotColor(slot),
798
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
799
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
370
800
  },
371
801
  accessibilityElementsHidden: true,
372
- testID: `velocity-bar-${i}`
802
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
373
803
  },
374
804
  i
375
805
  ))
376
806
  }
377
807
  );
378
808
  }
379
- const stripLabel = `Velocity chart for set, ${velocities.length} reps, tap to ${expanded ? "collapse" : "expand"}`;
809
+ const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
380
810
  const hasInteractiveContainer = onToggle != null;
381
811
  const hasInteractiveReps = onRepPress != null && expanded;
812
+ const setBars = set != null && slots.map((slot, i) => {
813
+ const isStraightRep = set.type === "straight" && slot.kind === "rep";
814
+ const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
815
+ return /* @__PURE__ */ jsx(
816
+ reactNative.View,
817
+ {
818
+ style: {
819
+ flex: 1,
820
+ height: "100%",
821
+ justifyContent: "flex-end",
822
+ marginLeft: slot.leadingGap
823
+ },
824
+ children: /* @__PURE__ */ jsx(
825
+ reactNative.View,
826
+ {
827
+ style: {
828
+ height: `${heightPct}%`,
829
+ minHeight: 2,
830
+ borderTopLeftRadius: 2,
831
+ borderTopRightRadius: 2,
832
+ backgroundColor: slotColor(slot),
833
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
834
+ },
835
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
836
+ accessibilityRole: "image",
837
+ accessibilityLabel: slotAccessibilityLabel(slot, i)
838
+ }
839
+ )
840
+ },
841
+ i
842
+ );
843
+ });
382
844
  const stripContent = /* @__PURE__ */ jsxs(
383
845
  reactNative.Animated.View,
384
846
  {
@@ -391,7 +853,9 @@ function VelocityStrip({
391
853
  borderRadius: expanded ? 6 : 2,
392
854
  paddingTop: expanded ? 16 : 0,
393
855
  paddingBottom: expanded ? 24 : 0,
394
- paddingHorizontal: expanded ? 6 : 0,
856
+ // No horizontal inset the framed chart's bars span full width, matching
857
+ // the bare spotlight and mini. Numbers/info sit in the vertical padding.
858
+ paddingHorizontal: 0,
395
859
  paddingVertical: expanded ? void 0 : 8,
396
860
  overflow: expanded ? "visible" : "hidden"
397
861
  }
@@ -401,46 +865,65 @@ function VelocityStrip({
401
865
  testID: "velocity-strip",
402
866
  ...props,
403
867
  children: [
404
- /* @__PURE__ */ jsx(
405
- reactNative.View,
406
- {
407
- style: { flexDirection: "row", flex: 1, gap: 2, alignItems: "flex-end" },
408
- children: velocities.map((v, i) => {
409
- const barBackground = barColorFor(v);
410
- const barHeightPct = maxVelocity > 0 ? Math.round(v / (maxVelocity * 1.15) * 100) : 0;
411
- const isLive = liveRepIndex === i;
412
- const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
413
- const barStyle = expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: barBackground } : {
414
- minHeight: "100%",
415
- borderTopLeftRadius: 2,
416
- borderTopRightRadius: 2,
417
- borderBottomLeftRadius: 0,
418
- borderBottomRightRadius: 0,
419
- backgroundColor: barBackground
420
- };
421
- const barInner = isLive ? /* @__PURE__ */ jsx(
422
- reactNative.Animated.View,
423
- {
424
- style: [barStyle, { transform: [{ scale: liveScale }] }],
425
- testID: `velocity-bar-${i}`,
426
- accessibilityRole: "image",
427
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
428
- }
429
- ) : /* @__PURE__ */ jsx(
430
- reactNative.View,
431
- {
432
- style: barStyle,
433
- testID: `velocity-bar-${i}`,
434
- accessibilityRole: "image",
435
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
436
- }
437
- );
438
- const bar = /* @__PURE__ */ jsxs(
439
- reactNative.View,
440
- {
441
- style: { flex: 1, height: "100%", justifyContent: "flex-end", position: "relative" },
442
- children: [
443
- expanded && /* @__PURE__ */ jsx(reactNative.Animated.View, { style: { opacity: labelOpacity, alignItems: "center", position: "absolute", top: -13, left: 0, right: 0 }, accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(
868
+ /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
869
+ const barBackground = barColorFor(v);
870
+ const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
871
+ const isLive = liveRepIndex === i;
872
+ const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
873
+ const barStyle = expanded ? {
874
+ height: `${barHeightPct}%`,
875
+ minHeight: 2,
876
+ borderTopLeftRadius: 2,
877
+ borderTopRightRadius: 2,
878
+ backgroundColor: barBackground
879
+ } : {
880
+ minHeight: "100%",
881
+ borderTopLeftRadius: 2,
882
+ borderTopRightRadius: 2,
883
+ borderBottomLeftRadius: 0,
884
+ borderBottomRightRadius: 0,
885
+ backgroundColor: barBackground
886
+ };
887
+ const barInner = isLive ? /* @__PURE__ */ jsx(
888
+ reactNative.Animated.View,
889
+ {
890
+ style: [barStyle, { transform: [{ scale: liveScale }] }],
891
+ testID: `velocity-bar-${i}`,
892
+ accessibilityRole: "image",
893
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
894
+ }
895
+ ) : /* @__PURE__ */ jsx(
896
+ reactNative.View,
897
+ {
898
+ style: barStyle,
899
+ testID: `velocity-bar-${i}`,
900
+ accessibilityRole: "image",
901
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
902
+ }
903
+ );
904
+ const bar = /* @__PURE__ */ jsxs(
905
+ reactNative.View,
906
+ {
907
+ style: {
908
+ flex: 1,
909
+ height: "100%",
910
+ justifyContent: "flex-end",
911
+ position: "relative"
912
+ },
913
+ children: [
914
+ expanded && showNumbers && /* @__PURE__ */ jsx(
915
+ reactNative.Animated.View,
916
+ {
917
+ style: {
918
+ opacity: labelOpacity,
919
+ alignItems: "center",
920
+ position: "absolute",
921
+ top: -13,
922
+ left: 0,
923
+ right: 0
924
+ },
925
+ accessibilityElementsHidden: true,
926
+ children: /* @__PURE__ */ jsx(
444
927
  reactNative.Text,
445
928
  {
446
929
  className: "text-text-secondary",
@@ -448,30 +931,30 @@ function VelocityStrip({
448
931
  testID: `velocity-label-${i}`,
449
932
  children: formatVelocity(v)
450
933
  }
451
- ) }),
452
- barInner
453
- ]
454
- },
455
- i
456
- );
457
- if (onRepPress && expanded) {
458
- return /* @__PURE__ */ jsx(
459
- reactNative.Pressable,
460
- {
461
- style: { flex: 1, height: "100%" },
462
- onPress: () => onRepPress(i, v),
463
- accessibilityRole: "button",
464
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
465
- testID: `velocity-bar-pressable-${i}`,
466
- children: bar
467
- },
468
- i
469
- );
470
- }
471
- return bar;
472
- })
934
+ )
935
+ }
936
+ ),
937
+ barInner
938
+ ]
939
+ },
940
+ i
941
+ );
942
+ if (onRepPress && expanded) {
943
+ return /* @__PURE__ */ jsx(
944
+ reactNative.Pressable,
945
+ {
946
+ style: { flex: 1, height: "100%" },
947
+ onPress: () => onRepPress(i, v),
948
+ accessibilityRole: "button",
949
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
950
+ testID: `velocity-bar-pressable-${i}`,
951
+ children: bar
952
+ },
953
+ i
954
+ );
473
955
  }
474
- ),
956
+ return bar;
957
+ }) }),
475
958
  expanded && showInfo && /* @__PURE__ */ jsxs(
476
959
  reactNative.Animated.View,
477
960
  {
@@ -647,17 +1130,49 @@ function SvgIcon({
647
1130
  }
648
1131
 
649
1132
  // src/components/icons/icons.tsx
650
- function DumbbellIcon(props) {
1133
+ function StarIcon(props) {
1134
+ 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" }) });
1135
+ }
1136
+ function ScaleIcon(props) {
651
1137
  return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
652
- /* @__PURE__ */ jsx("path", { d: "M14.4 14.4 9.6 9.6" }),
653
- /* @__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" }),
654
- /* @__PURE__ */ jsx("path", { d: "m21.5 21.5-1.4-1.4" }),
655
- /* @__PURE__ */ jsx("path", { d: "M3.9 3.9 2.5 2.5" }),
656
- /* @__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" })
1138
+ /* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
1139
+ /* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
1140
+ /* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
1141
+ /* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
1142
+ /* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
657
1143
  ] });
658
1144
  }
659
- function StarIcon(props) {
660
- 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" }) });
1145
+ function AlertTriangleIcon(props) {
1146
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1147
+ /* @__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" }),
1148
+ /* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
1149
+ /* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
1150
+ ] });
1151
+ }
1152
+ function TrendingDownIcon(props) {
1153
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1154
+ /* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
1155
+ /* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
1156
+ ] });
1157
+ }
1158
+ function CircleSlashIcon(props) {
1159
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1160
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1161
+ /* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
1162
+ ] });
1163
+ }
1164
+ function AwardIcon(props) {
1165
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1166
+ /* @__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" }),
1167
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
1168
+ ] });
1169
+ }
1170
+ function InfoIcon(props) {
1171
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1172
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1173
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
1174
+ /* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
1175
+ ] });
661
1176
  }
662
1177
  var baseBadgeSizeConfig = {
663
1178
  sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
@@ -760,6 +1275,9 @@ var semanticColorsLight = {
760
1275
  "status-success-light": primitiveRamps.green[200],
761
1276
  "status-success-dark": primitiveRamps.green[600],
762
1277
  "status-success-subtle": primitiveRamps.green[50],
1278
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
1279
+ "status-live": primitiveRamps.green[300],
1280
+ "status-live-muted": primitiveRamps.green[500],
763
1281
  "status-error": primitiveRamps.red[600],
764
1282
  "status-error-light": primitiveRamps.red[500],
765
1283
  "status-error-dark": primitiveRamps.red[700],
@@ -893,6 +1411,9 @@ var semanticColorsDark = {
893
1411
  "status-success-light": primitiveRamps.green[200],
894
1412
  "status-success-dark": primitiveRamps.green[600],
895
1413
  "status-success-subtle": "rgba(46, 213, 115, 0.12)",
1414
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
1415
+ "status-live": primitiveRamps.green[300],
1416
+ "status-live-muted": primitiveRamps.green[500],
896
1417
  "status-error": primitiveRamps.red[600],
897
1418
  "status-error-light": primitiveRamps.red[500],
898
1419
  "status-error-dark": primitiveRamps.red[700],
@@ -997,106 +1518,20 @@ function getSemanticColors(mode) {
997
1518
  return mode === "dark" ? semanticColorsDark : semanticColorsLight;
998
1519
  }
999
1520
 
1000
- // src/components/custom/Workout/WeightBadge.tsx
1001
- function WeightBadge({
1002
- value,
1003
- unit = "lbs",
1004
- reps,
1005
- delta,
1006
- isPr,
1007
- showIcon = true,
1008
- size = "md",
1009
- onPress,
1010
- className,
1011
- ...props
1012
- }) {
1013
- const config = baseBadgeSizeConfig[size];
1014
- const textColor = isPr ? getSemanticColors("dark")["brand-primary"] : "#9CA3AF";
1015
- const iconColor = textColor;
1016
- const repMaxClause = reps != null ? `${reps} rep max` : "Estimated one rep max";
1017
- const deltaLabel = delta != null ? `, ${delta >= 0 ? "+" : ""}${delta}% change` : "";
1018
- const fullLabel = `${repMaxClause}: ${value} ${unit}${deltaLabel}`;
1019
- return /* @__PURE__ */ jsxs(
1020
- BaseBadge,
1021
- {
1022
- variant: isPr ? "pr" : "plain",
1023
- size,
1024
- onPress,
1025
- className,
1026
- icon: showIcon ? /* @__PURE__ */ jsx(DumbbellIcon, { size: config.iconSize, color: iconColor, strokeWidth: 2 }) : void 0,
1027
- accessibilityLabel: fullLabel,
1028
- testID: "weight-badge",
1029
- ...props,
1030
- children: [
1031
- /* @__PURE__ */ jsxs(
1032
- reactNative.Text,
1033
- {
1034
- style: {
1035
- fontFamily: '"Space Grotesk", sans-serif',
1036
- fontWeight: "600",
1037
- fontSize: config.fontSize,
1038
- color: textColor
1039
- },
1040
- children: [
1041
- isPr ? "\u2733 " : "",
1042
- value,
1043
- " ",
1044
- unit
1045
- ]
1046
- }
1047
- ),
1048
- reps != null && /* @__PURE__ */ jsxs(
1049
- reactNative.Text,
1050
- {
1051
- style: {
1052
- fontFamily: '"Space Grotesk", sans-serif',
1053
- fontWeight: "600",
1054
- fontSize: config.fontSize,
1055
- marginLeft: 2,
1056
- color: textColor,
1057
- opacity: 0.7
1058
- },
1059
- testID: "weight-badge-repmax",
1060
- children: [
1061
- reps,
1062
- "RM"
1063
- ]
1064
- }
1065
- ),
1066
- delta != null && /* @__PURE__ */ jsxs(
1067
- reactNative.Text,
1068
- {
1069
- style: {
1070
- fontFamily: '"Space Grotesk", sans-serif',
1071
- fontSize: config.fontSize,
1072
- marginLeft: 4,
1073
- color: delta >= 0 ? getSemanticColors("dark")["result-improve"] : getSemanticColors("dark")["result-degrade"]
1074
- },
1075
- testID: "weight-badge-delta",
1076
- children: [
1077
- delta >= 0 ? "+" : "",
1078
- delta,
1079
- "%"
1080
- ]
1081
- }
1082
- )
1083
- ]
1084
- }
1085
- );
1086
- }
1087
- var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
1088
- var typeLabels = {
1089
- e1rm: "PR e1RM",
1090
- weight: "PR Weight",
1091
- reps: "PR Reps",
1092
- volume: "PR Volume",
1093
- velocity: "PR Velocity"
1094
- };
1095
- function PrBadge({
1096
- type = "e1rm",
1097
- label: labelProp,
1098
- compact = false,
1099
- animate = true,
1521
+ // src/components/custom/Workout/PrBadge.tsx
1522
+ var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
1523
+ var typeLabels = {
1524
+ e1rm: "PR e1RM",
1525
+ weight: "PR Weight",
1526
+ reps: "PR Reps",
1527
+ volume: "PR Volume",
1528
+ velocity: "PR Velocity"
1529
+ };
1530
+ function PrBadge({
1531
+ type = "e1rm",
1532
+ label: labelProp,
1533
+ compact = false,
1534
+ animate = true,
1100
1535
  className,
1101
1536
  ...props
1102
1537
  }) {
@@ -1167,6 +1602,204 @@ function PrBadge({
1167
1602
  }
1168
1603
  return badge;
1169
1604
  }
1605
+ var DARK = getSemanticColors("dark");
1606
+ var TEXT_ACTIVE = DARK["text-primary"];
1607
+ var TEXT_MUTED = DARK["text-secondary"];
1608
+ var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
1609
+ var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
1610
+ var typeBadge = {
1611
+ fontSize: 10,
1612
+ fontWeight: "700",
1613
+ fontFamily: "Inter, sans-serif",
1614
+ color: WORKOUT_TOKENS.scale.orange,
1615
+ backgroundColor: "rgba(255,165,2,0.12)",
1616
+ paddingVertical: 1,
1617
+ paddingHorizontal: 5,
1618
+ borderRadius: 3,
1619
+ overflow: "hidden"
1620
+ };
1621
+ function displayReps(set) {
1622
+ return set.state === "done" ? set.reps : set.target.reps;
1623
+ }
1624
+ function displayWeight(set) {
1625
+ return set.state === "done" ? set.weight : set.target.weight;
1626
+ }
1627
+ function plannedReps(set) {
1628
+ if (set.state === "done") return set.velocities.length;
1629
+ if (set.state === "live") return set.target.reps;
1630
+ return set.planned ?? set.target.reps;
1631
+ }
1632
+ function accessibilityLabel(set) {
1633
+ const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
1634
+ if (set.state === "live") return `${base}, in progress`;
1635
+ if (set.state === "todo") return `${base}, upcoming`;
1636
+ return base;
1637
+ }
1638
+ function Cell({
1639
+ width,
1640
+ children,
1641
+ testID
1642
+ }) {
1643
+ return /* @__PURE__ */ jsx(reactNative.View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
1644
+ }
1645
+ function RowStrip({ set }) {
1646
+ const zones = set.velocityZones;
1647
+ if (set.state === "live") {
1648
+ return /* @__PURE__ */ jsx(
1649
+ VelocityStrip,
1650
+ {
1651
+ variant: "expanded",
1652
+ showNumbers: false,
1653
+ showInfo: false,
1654
+ height: 24,
1655
+ scale: "fixed",
1656
+ set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
1657
+ zones
1658
+ }
1659
+ );
1660
+ }
1661
+ const velocities = set.state === "done" ? set.velocities : [];
1662
+ return /* @__PURE__ */ jsx(
1663
+ VelocityStrip,
1664
+ {
1665
+ variant: "mini",
1666
+ set: { type: "straight", velocities, planned: plannedReps(set) },
1667
+ zones
1668
+ }
1669
+ );
1670
+ }
1671
+ function SetRow(set) {
1672
+ const live = set.state === "live";
1673
+ const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
1674
+ const rpe = set.state === "todo" ? null : set.rpe;
1675
+ return /* @__PURE__ */ jsxs(
1676
+ reactNative.View,
1677
+ {
1678
+ style: { paddingVertical: 6, paddingHorizontal: 8 },
1679
+ accessibilityLabel: accessibilityLabel(set),
1680
+ testID: "set-row",
1681
+ children: [
1682
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
1683
+ /* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(reactNative.Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
1684
+ /* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
1685
+ /* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
1686
+ /* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
1687
+ ] }),
1688
+ /* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
1689
+ ]
1690
+ }
1691
+ );
1692
+ }
1693
+ var PREV_COLUMN = { label: "PREV", width: void 0 };
1694
+ function buildColumns(unit, showPrevious) {
1695
+ return [
1696
+ { label: "SET", width: 36 },
1697
+ ...showPrevious ? [PREV_COLUMN] : [],
1698
+ { label: "REPS", width: 44 },
1699
+ { label: unit.toUpperCase(), width: 56 },
1700
+ { label: "RPE", width: 36 }
1701
+ ];
1702
+ }
1703
+ function SetTableHeader({
1704
+ unit = "lbs",
1705
+ showPrevious = true,
1706
+ testID = "table-header"
1707
+ }) {
1708
+ const columns = buildColumns(unit, showPrevious);
1709
+ return /* @__PURE__ */ jsx(
1710
+ reactNative.View,
1711
+ {
1712
+ className: "flex-row",
1713
+ style: {
1714
+ paddingVertical: 8,
1715
+ paddingHorizontal: 8,
1716
+ paddingBottom: 4,
1717
+ ...showPrevious ? {} : { justifyContent: "space-between" }
1718
+ },
1719
+ testID,
1720
+ children: columns.map(({ label, width }) => {
1721
+ const isFlex = width === void 0;
1722
+ return /* @__PURE__ */ jsx(
1723
+ reactNative.View,
1724
+ {
1725
+ style: {
1726
+ ...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
1727
+ alignItems: "center",
1728
+ justifyContent: "center",
1729
+ gap: 8
1730
+ },
1731
+ ...isFlex ? { className: "flex-1" } : {},
1732
+ children: /* @__PURE__ */ jsx(
1733
+ reactNative.Text,
1734
+ {
1735
+ className: "text-text-tertiary",
1736
+ style: {
1737
+ fontSize: 10,
1738
+ fontWeight: "600",
1739
+ fontFamily: "Inter, sans-serif",
1740
+ textTransform: "uppercase",
1741
+ letterSpacing: 0.8
1742
+ },
1743
+ children: label
1744
+ }
1745
+ )
1746
+ },
1747
+ label
1748
+ );
1749
+ })
1750
+ }
1751
+ );
1752
+ }
1753
+ var METRIC_FONT = "Inter, sans-serif";
1754
+ function MetricCell({ children, color, fontSize = 11 }) {
1755
+ return /* @__PURE__ */ jsx(
1756
+ reactNative.Text,
1757
+ {
1758
+ style: {
1759
+ fontFamily: METRIC_FONT,
1760
+ fontSize,
1761
+ color,
1762
+ fontWeight: "600",
1763
+ letterSpacing: 1
1764
+ },
1765
+ children
1766
+ }
1767
+ );
1768
+ }
1769
+
1770
+ // src/components/custom/Workout/SetsRepsLoad.tsx
1771
+ var t = getSemanticColors("dark");
1772
+ var TIMES = "\xD7";
1773
+ var AT = "@";
1774
+ function SetsRepsLoad({
1775
+ sets,
1776
+ reps,
1777
+ load,
1778
+ unit = "lb",
1779
+ className,
1780
+ ...props
1781
+ }) {
1782
+ const value = t["text-primary"];
1783
+ const sep = t["text-tertiary"];
1784
+ return /* @__PURE__ */ jsxs(
1785
+ reactNative.View,
1786
+ {
1787
+ className,
1788
+ style: { flexDirection: "row", alignItems: "baseline" },
1789
+ accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
1790
+ testID: "sets-reps-load",
1791
+ ...props,
1792
+ children: [
1793
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
1794
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
1795
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
1796
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
1797
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
1798
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
1799
+ ]
1800
+ }
1801
+ );
1802
+ }
1170
1803
  function hexToRgb(hex) {
1171
1804
  const cleaned = hex.replace("#", "");
1172
1805
  let r, g, b;
@@ -1476,7 +2109,7 @@ function alpha(color, opacity) {
1476
2109
  }
1477
2110
  return color;
1478
2111
  }
1479
- var t = getSemanticColors("dark");
2112
+ var t2 = getSemanticColors("dark");
1480
2113
  var TEMPO_PACING = {
1481
2114
  behindThresholdPct: 0.15,
1482
2115
  minPhaseDurationMs: 500
@@ -1492,20 +2125,20 @@ function getTempoFillPct(elapsedMs, targetMs) {
1492
2125
  return Math.min(100, elapsedMs / targetMs * 100);
1493
2126
  }
1494
2127
  ({
1495
- concentric: { color: t["status-success"]},
1496
- hold: { color: t["brand-primary"]},
1497
- eccentric: { color: t["status-warning"]}
2128
+ concentric: { color: t2["status-success"]},
2129
+ hold: { color: t2["brand-primary"]},
2130
+ eccentric: { color: t2["status-warning"]}
1498
2131
  });
1499
2132
 
1500
2133
  // src/components/custom/Workout/TempoDisplay.tsx
1501
- var t2 = getSemanticColors("dark");
2134
+ var t3 = getSemanticColors("dark");
1502
2135
  var INTER = "Inter, sans-serif";
1503
2136
  var TEXT_TERTIARY = "#6B7280";
1504
- var STATUS_ERROR = t2["status-error"];
2137
+ var STATUS_ERROR = t3["status-error"];
1505
2138
  var phaseColors = {
1506
- eccentric: t2["status-warning"],
2139
+ eccentric: t3["status-warning"],
1507
2140
  pauseBottom: TEXT_TERTIARY,
1508
- concentric: t2["status-success"],
2141
+ concentric: t3["status-success"],
1509
2142
  pauseTop: TEXT_TERTIARY,
1510
2143
  dash: TEXT_TERTIARY
1511
2144
  };
@@ -1514,34 +2147,10 @@ function TempoValue({
1514
2147
  color,
1515
2148
  fontSize
1516
2149
  }) {
1517
- return /* @__PURE__ */ jsx(
1518
- reactNative.Text,
1519
- {
1520
- style: {
1521
- fontFamily: INTER,
1522
- fontSize,
1523
- color,
1524
- fontWeight: "600",
1525
- letterSpacing: 1
1526
- },
1527
- children: value
1528
- }
1529
- );
2150
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: value });
1530
2151
  }
1531
2152
  function TempoSeparator({ color, fontSize }) {
1532
- return /* @__PURE__ */ jsx(
1533
- reactNative.Text,
1534
- {
1535
- style: {
1536
- fontFamily: INTER,
1537
- fontSize,
1538
- color,
1539
- fontWeight: "600",
1540
- letterSpacing: 1
1541
- },
1542
- children: "-"
1543
- }
1544
- );
2153
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: "-" });
1545
2154
  }
1546
2155
  var LIVE_PHASES = [
1547
2156
  { key: "eccentric", color: phaseColors.eccentric },
@@ -1603,6 +2212,7 @@ function TempoDisplay({
1603
2212
  tempo,
1604
2213
  size = "md",
1605
2214
  colored = true,
2215
+ showLabel = true,
1606
2216
  showInfo = true,
1607
2217
  live,
1608
2218
  onPress,
@@ -1636,7 +2246,7 @@ function TempoDisplay({
1636
2246
  },
1637
2247
  ...props,
1638
2248
  children: [
1639
- /* @__PURE__ */ jsx(
2249
+ showLabel && /* @__PURE__ */ jsx(
1640
2250
  reactNative.Text,
1641
2251
  {
1642
2252
  style: {
@@ -1715,300 +2325,176 @@ function TempoDisplay({
1715
2325
  children: [
1716
2326
  /* @__PURE__ */ jsxs(
1717
2327
  reactNative.View,
1718
- {
1719
- style: {
1720
- backgroundColor: "#191919",
1721
- borderRadius: 6,
1722
- paddingVertical: 8,
1723
- paddingHorizontal: 12,
1724
- borderWidth: 1,
1725
- borderColor: "#2C2C2C"
1726
- },
1727
- children: [
1728
- /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1729
- "Eccentric: ",
1730
- eccentric,
1731
- "s"
1732
- ] }),
1733
- /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1734
- "Pause (bottom): ",
1735
- pauseBottom,
1736
- "s"
1737
- ] }),
1738
- /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1739
- "Concentric: ",
1740
- concentric,
1741
- "s"
1742
- ] }),
1743
- /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1744
- "Pause (top): ",
1745
- pauseTop,
1746
- "s"
1747
- ] })
1748
- ]
1749
- }
1750
- ),
1751
- /* @__PURE__ */ jsx(
1752
- reactNative.View,
1753
- {
1754
- style: {
1755
- width: 0,
1756
- height: 0,
1757
- borderLeftWidth: 5,
1758
- borderRightWidth: 5,
1759
- borderTopWidth: 5,
1760
- borderLeftColor: "transparent",
1761
- borderRightColor: "transparent",
1762
- borderTopColor: "#2C2C2C"
1763
- }
1764
- }
1765
- )
1766
- ]
1767
- }
1768
- )
1769
- ]
1770
- }
1771
- );
1772
- }
1773
- function formatAccessibilityLabel(setNumber, reps, weight, unit) {
1774
- const repsText = reps != null ? `${reps} reps` : "no reps";
1775
- const weightText = weight != null ? `at ${roundWeight(weight)} ${unit}` : "";
1776
- return `Set ${setNumber}: ${repsText}${weightText ? ` ${weightText}` : ""}`;
1777
- }
1778
- function SetRow({
1779
- mode,
1780
- setNumber,
1781
- previous,
1782
- reps,
1783
- weight,
1784
- rpe,
1785
- unit,
1786
- velocities,
1787
- velocityZones,
1788
- velocityLiveRepIndex,
1789
- velocityExpanded,
1790
- onVelocityToggle,
1791
- setType,
1792
- prBadges,
1793
- isNextSet,
1794
- targets
1795
- }) {
1796
- const isActive = mode === "active";
1797
- const isCompleted = mode === "completed";
1798
- const rowStyle = {
1799
- flexDirection: "row",
1800
- alignItems: "center",
1801
- padding: 7,
1802
- paddingHorizontal: 8,
1803
- gap: 8,
1804
- borderRadius: 8
1805
- };
1806
- if (isActive && isNextSet) {
1807
- rowStyle.backgroundColor = "rgba(255,121,0,0.06)";
1808
- rowStyle.borderWidth = 1;
1809
- rowStyle.borderColor = "rgba(255,121,0,0.15)";
1810
- }
1811
- if (isCompleted && !isNextSet) {
1812
- rowStyle.opacity = 0.55;
1813
- }
1814
- return /* @__PURE__ */ jsxs(
1815
- reactNative.View,
1816
- {
1817
- style: rowStyle,
1818
- accessibilityLabel: formatAccessibilityLabel(
1819
- setNumber,
1820
- reps,
1821
- weight,
1822
- unit
1823
- ),
1824
- testID: "set-row",
1825
- children: [
1826
- /* @__PURE__ */ jsx(
1827
- reactNative.View,
1828
- {
1829
- style: { width: 36, alignItems: "center", justifyContent: "center" },
1830
- testID: "set-row-set-number",
1831
- children: setType ? /* @__PURE__ */ jsx(
1832
- reactNative.Text,
1833
- {
1834
- style: {
1835
- fontSize: 10,
1836
- fontWeight: "700",
1837
- fontFamily: "Inter, sans-serif",
1838
- color: WORKOUT_TOKENS.scale.orange,
1839
- backgroundColor: "rgba(255,165,2,0.12)",
1840
- paddingVertical: 1,
1841
- paddingHorizontal: 5,
1842
- borderRadius: 3,
1843
- overflow: "hidden"
1844
- },
1845
- testID: "set-row-type-badge",
1846
- children: setType
1847
- }
1848
- ) : /* @__PURE__ */ jsx(
1849
- reactNative.Text,
1850
- {
1851
- className: "text-text-secondary",
1852
- style: {
1853
- fontSize: 13,
1854
- fontWeight: "600",
1855
- fontFamily: "Inter, sans-serif"
1856
- },
1857
- children: setNumber
1858
- }
1859
- )
1860
- }
1861
- ),
1862
- /* @__PURE__ */ jsx(
1863
- reactNative.View,
1864
- {
1865
- className: "flex-1",
1866
- testID: "set-row-previous",
1867
- children: /* @__PURE__ */ jsx(
1868
- reactNative.Text,
1869
- {
1870
- className: "text-text-secondary",
1871
- style: {
1872
- fontSize: 12,
1873
- fontFamily: "Inter, sans-serif"
1874
- },
1875
- children: previous ? `${previous.reps} x ${roundWeight(previous.weight)}` : "\u2014"
1876
- }
1877
- )
1878
- }
1879
- ),
1880
- /* @__PURE__ */ jsx(
1881
- reactNative.View,
1882
- {
1883
- style: { width: 44, alignItems: "center", justifyContent: "center" },
1884
- testID: "set-row-reps",
1885
- children: isActive && reps == null && targets ? /* @__PURE__ */ jsx(
1886
- reactNative.Text,
1887
- {
1888
- className: "text-text-tertiary",
1889
- style: {
1890
- fontSize: 13,
1891
- fontStyle: "italic",
1892
- fontFamily: "Inter, sans-serif"
1893
- },
1894
- testID: "set-row-target-reps",
1895
- children: targets.reps
1896
- }
1897
- ) : /* @__PURE__ */ jsx(
1898
- reactNative.Text,
1899
- {
1900
- className: reps != null ? "text-text-primary" : "text-text-tertiary",
1901
- style: {
1902
- fontSize: 14,
1903
- fontWeight: "600",
1904
- fontFamily: "Inter, sans-serif"
1905
- },
1906
- children: reps != null ? reps : "\u2014"
1907
- }
1908
- )
1909
- }
1910
- ),
1911
- /* @__PURE__ */ jsx(
1912
- reactNative.View,
1913
- {
1914
- style: { width: 56, alignItems: "center", justifyContent: "center" },
1915
- testID: "set-row-weight",
1916
- children: isActive && weight == null && targets ? /* @__PURE__ */ jsx(
1917
- reactNative.Text,
1918
- {
1919
- className: "text-text-tertiary",
1920
- style: {
1921
- fontSize: 13,
1922
- fontStyle: "italic",
1923
- fontFamily: "Inter, sans-serif"
1924
- },
1925
- testID: "set-row-target-weight",
1926
- children: roundWeight(targets.weight)
1927
- }
1928
- ) : /* @__PURE__ */ jsx(
1929
- reactNative.Text,
1930
- {
1931
- className: weight != null ? "text-text-primary" : "text-text-tertiary",
1932
- style: {
1933
- fontSize: 14,
1934
- fontWeight: "600",
1935
- fontFamily: "Inter, sans-serif"
1936
- },
1937
- children: weight != null ? roundWeight(weight) : "\u2014"
1938
- }
1939
- )
1940
- }
1941
- ),
1942
- /* @__PURE__ */ jsx(
1943
- reactNative.View,
1944
- {
1945
- style: { width: 36, alignItems: "center", justifyContent: "center" },
1946
- testID: "set-row-rpe",
1947
- children: /* @__PURE__ */ jsx(
1948
- reactNative.Text,
1949
- {
1950
- className: rpe != null ? void 0 : "text-text-tertiary",
1951
- style: {
1952
- fontSize: 13,
1953
- fontWeight: "600",
1954
- fontFamily: "Inter, sans-serif",
1955
- // rpeColor returns a concrete hex (native-safe); only the tertiary
1956
- // fallback needs the className token.
1957
- color: rpe != null ? rpeColor(rpe) : void 0
1958
- },
1959
- children: rpe != null ? roundRpe(rpe) : "\u2014"
1960
- }
1961
- )
1962
- }
1963
- ),
1964
- prBadges && prBadges.length > 0 && /* @__PURE__ */ jsx(
1965
- reactNative.View,
1966
- {
1967
- className: "flex-row items-center",
1968
- style: { gap: 4 },
1969
- testID: "set-row-pr-badges",
1970
- children: prBadges.map((badge, i) => /* @__PURE__ */ jsx(
1971
- PrBadge,
1972
- {
1973
- type: badge.type,
1974
- label: badge.label,
1975
- animate: false,
1976
- compact: true
1977
- },
1978
- i
1979
- ))
1980
- }
1981
- ),
1982
- velocities && velocities.length > 0 && /* @__PURE__ */ jsx(
1983
- reactNative.View,
1984
- {
1985
- style: { position: "absolute", bottom: 0, left: 8, right: 8 },
1986
- testID: "set-row-velocity-strip",
1987
- children: /* @__PURE__ */ jsx(
1988
- VelocityStrip,
1989
- {
1990
- velocities,
1991
- zones: velocityZones,
1992
- liveRepIndex: velocityLiveRepIndex,
1993
- expanded: velocityExpanded,
1994
- onToggle: onVelocityToggle,
1995
- variant: onVelocityToggle ? "full" : "mini"
1996
- }
1997
- )
2328
+ {
2329
+ style: {
2330
+ backgroundColor: "#191919",
2331
+ borderRadius: 6,
2332
+ paddingVertical: 8,
2333
+ paddingHorizontal: 12,
2334
+ borderWidth: 1,
2335
+ borderColor: "#2C2C2C"
2336
+ },
2337
+ children: [
2338
+ /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2339
+ "Eccentric: ",
2340
+ eccentric,
2341
+ "s"
2342
+ ] }),
2343
+ /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2344
+ "Pause (bottom): ",
2345
+ pauseBottom,
2346
+ "s"
2347
+ ] }),
2348
+ /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2349
+ "Concentric: ",
2350
+ concentric,
2351
+ "s"
2352
+ ] }),
2353
+ /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2354
+ "Pause (top): ",
2355
+ pauseTop,
2356
+ "s"
2357
+ ] })
2358
+ ]
2359
+ }
2360
+ ),
2361
+ /* @__PURE__ */ jsx(
2362
+ reactNative.View,
2363
+ {
2364
+ style: {
2365
+ width: 0,
2366
+ height: 0,
2367
+ borderLeftWidth: 5,
2368
+ borderRightWidth: 5,
2369
+ borderTopWidth: 5,
2370
+ borderLeftColor: "transparent",
2371
+ borderRightColor: "transparent",
2372
+ borderTopColor: "#2C2C2C"
2373
+ }
2374
+ }
2375
+ )
2376
+ ]
1998
2377
  }
1999
2378
  )
2000
2379
  ]
2001
2380
  }
2002
2381
  );
2003
2382
  }
2004
- function resolveColor(token, mode = "dark") {
2005
- return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
2383
+ var t4 = getSemanticColors("dark");
2384
+ var INDICATOR_CONFIG = {
2385
+ imbalance: { Icon: ScaleIcon, color: t4["status-error"], label: "Left/right imbalance" },
2386
+ overshoot: { Icon: AlertTriangleIcon, color: t4["status-error"], label: "Load overshoot" },
2387
+ "velocity-loss": { Icon: TrendingDownIcon, color: t4["status-warning"], label: "Velocity loss" },
2388
+ "missed-reps": { Icon: CircleSlashIcon, color: t4["status-warning"], label: "Missed reps" },
2389
+ pr: { Icon: AwardIcon, color: t4["status-success"], label: "Personal record" },
2390
+ info: { Icon: InfoIcon, color: t4["status-info"], label: "More info" }
2391
+ };
2392
+ function ExerciseIndicator({ kind, onPress, className, ...props }) {
2393
+ const { Icon, color, label } = INDICATOR_CONFIG[kind];
2394
+ const chip = /* @__PURE__ */ jsx(
2395
+ reactNative.View,
2396
+ {
2397
+ className,
2398
+ style: {
2399
+ width: 16,
2400
+ height: 16,
2401
+ borderRadius: 8,
2402
+ borderWidth: 1,
2403
+ borderColor: color,
2404
+ alignItems: "center",
2405
+ justifyContent: "center"
2406
+ },
2407
+ accessibilityRole: "image",
2408
+ accessibilityLabel: label,
2409
+ testID: "exercise-indicator",
2410
+ ...props,
2411
+ children: /* @__PURE__ */ jsx(Icon, { size: 11, color })
2412
+ }
2413
+ );
2414
+ if (onPress) {
2415
+ return /* @__PURE__ */ jsx(reactNative.Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, children: chip });
2416
+ }
2417
+ return chip;
2006
2418
  }
2007
2419
 
2008
- // src/components/custom/Workout/ExerciseCard.tsx
2009
- function buildColumnHeaders(unit) {
2010
- return ["SET", "PREV", "REPS", unit.toUpperCase(), "RPE"];
2420
+ // src/components/custom/Workout/ExerciseHeading.tsx
2421
+ function headingLabel(name, sets, reps, load, unit) {
2422
+ return `${name}, ${sets}\xD7${reps} @ ${roundWeight(load)} ${unit}`;
2423
+ }
2424
+ function ExerciseHeading({
2425
+ name,
2426
+ sets,
2427
+ reps,
2428
+ load,
2429
+ unit = "lbs",
2430
+ tempo,
2431
+ indicator,
2432
+ dimmed,
2433
+ onPress
2434
+ }) {
2435
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { opacity: dimmed ? 0.55 : 1 }, testID: "exercise-heading", children: [
2436
+ /* @__PURE__ */ jsxs(
2437
+ reactNative.Pressable,
2438
+ {
2439
+ onPress,
2440
+ accessibilityRole: "button",
2441
+ accessibilityLabel: headingLabel(name, sets, reps, load, unit),
2442
+ className: "flex-row items-center",
2443
+ style: { gap: 8 },
2444
+ testID: "exercise-card-header",
2445
+ children: [
2446
+ /* @__PURE__ */ jsx(
2447
+ reactNative.Text,
2448
+ {
2449
+ className: "text-text-primary",
2450
+ style: { fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: "700" },
2451
+ testID: "exercise-card-name",
2452
+ children: name
2453
+ }
2454
+ ),
2455
+ /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
2456
+ indicator && /* @__PURE__ */ jsx(ExerciseIndicator, { kind: indicator })
2457
+ ]
2458
+ }
2459
+ ),
2460
+ /* @__PURE__ */ jsxs(
2461
+ reactNative.View,
2462
+ {
2463
+ className: "flex-row items-center",
2464
+ style: { gap: 8, marginTop: 1 },
2465
+ testID: "exercise-card-summary",
2466
+ children: [
2467
+ /* @__PURE__ */ jsx(SetsRepsLoad, { sets, reps, load, unit }),
2468
+ /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
2469
+ tempo && /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm", showLabel: false, showInfo: false })
2470
+ ]
2471
+ }
2472
+ )
2473
+ ] });
2474
+ }
2475
+
2476
+ // src/components/custom/Workout/ExerciseCardHeading.tsx
2477
+ function ExerciseCardHeading({
2478
+ setStates,
2479
+ stripHeight = 8,
2480
+ dimmed,
2481
+ testID = "exercise-card",
2482
+ ...heading
2483
+ }) {
2484
+ return /* @__PURE__ */ jsxs(
2485
+ reactNative.View,
2486
+ {
2487
+ style: { paddingVertical: 9, paddingHorizontal: 12, opacity: dimmed ? 0.55 : 1 },
2488
+ testID,
2489
+ children: [
2490
+ /* @__PURE__ */ jsx(ExerciseHeading, { ...heading }),
2491
+ setStates.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: 7 }, testID: "exercise-card-strip", children: /* @__PURE__ */ jsx(SetStrip, { sets: setStates, height: stripHeight }) })
2492
+ ]
2493
+ }
2494
+ );
2011
2495
  }
2496
+
2497
+ // src/components/custom/Workout/ExerciseCard.tsx
2012
2498
  function getSupersetBorderRadius(position) {
2013
2499
  switch (position) {
2014
2500
  case "first":
@@ -2051,7 +2537,7 @@ function formatSummary(summary) {
2051
2537
  if (!summary) return "";
2052
2538
  return `${summary.sets}\xD7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`;
2053
2539
  }
2054
- function formatAccessibilityLabel2(name, summary, prescription) {
2540
+ function formatAccessibilityLabel(name, summary, prescription) {
2055
2541
  if (summary) return `${name}, ${formatSummary(summary)}`;
2056
2542
  if (prescription) return `${name}, ${prescription}`;
2057
2543
  return name;
@@ -2060,7 +2546,6 @@ function CollapsedCard({
2060
2546
  name,
2061
2547
  onToggle,
2062
2548
  summary,
2063
- e1rm,
2064
2549
  isPR,
2065
2550
  setVelocities,
2066
2551
  velocityZones,
@@ -2079,7 +2564,7 @@ function CollapsedCard({
2079
2564
  onPressIn: () => setPressed(true),
2080
2565
  onPressOut: () => setPressed(false),
2081
2566
  accessibilityRole: "button",
2082
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
2567
+ accessibilityLabel: formatAccessibilityLabel(name, summary, void 0),
2083
2568
  testID: "exercise-card",
2084
2569
  children: /* @__PURE__ */ jsxs(
2085
2570
  reactNative.View,
@@ -2093,100 +2578,93 @@ function CollapsedCard({
2093
2578
  ...supersetMargin
2094
2579
  },
2095
2580
  children: [
2096
- /* @__PURE__ */ jsxs(
2097
- reactNative.View,
2098
- {
2099
- className: "flex-row items-center",
2100
- testID: "exercise-card-header",
2101
- children: [
2102
- /* @__PURE__ */ jsx(
2103
- reactNative.Text,
2104
- {
2105
- className: "text-text-primary",
2106
- style: {
2107
- fontSize: 14,
2108
- fontFamily: '"Space Grotesk", sans-serif',
2109
- fontWeight: "700"
2110
- },
2111
- testID: "exercise-card-name",
2112
- children: name
2113
- }
2114
- ),
2115
- /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
2116
- summary && /* @__PURE__ */ jsx(
2117
- reactNative.Text,
2118
- {
2119
- className: "text-text-secondary",
2120
- style: {
2121
- fontSize: 12,
2122
- fontFamily: "Inter, sans-serif",
2123
- marginRight: 8
2124
- },
2125
- testID: "exercise-card-summary",
2126
- children: formatSummary(summary)
2127
- }
2128
- ),
2129
- e1rm && /* @__PURE__ */ jsx(
2130
- WeightBadge,
2131
- {
2132
- value: roundWeight(e1rm.value),
2133
- unit: e1rm.unit,
2134
- size: "sm",
2135
- showIcon: false,
2136
- testID: "exercise-card-e1rm"
2137
- }
2138
- ),
2139
- isPR && /* @__PURE__ */ jsx(reactNative.View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2140
- ]
2141
- }
2142
- ),
2143
- (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(
2144
- reactNative.View,
2145
- {
2146
- className: "flex-row",
2147
- style: { marginTop: 6, gap: 4 },
2148
- testID: "exercise-card-strips",
2149
- children: [
2150
- setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
2151
- VelocityStrip,
2152
- {
2153
- velocities,
2154
- zones: velocityZones,
2155
- variant: "mini",
2156
- testID: `exercise-card-velocity-strip-${i}`
2157
- },
2158
- i
2159
- )),
2160
- Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
2161
- PlaceholderStrip,
2162
- {
2163
- testID: `exercise-card-placeholder-${i}`
2164
- },
2165
- `placeholder-${i}`
2166
- ))
2167
- ]
2168
- }
2169
- )
2581
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", testID: "exercise-card-header", children: [
2582
+ /* @__PURE__ */ jsx(
2583
+ reactNative.Text,
2584
+ {
2585
+ className: "text-text-primary",
2586
+ style: {
2587
+ fontSize: 14,
2588
+ fontFamily: '"Space Grotesk", sans-serif',
2589
+ fontWeight: "700"
2590
+ },
2591
+ testID: "exercise-card-name",
2592
+ children: name
2593
+ }
2594
+ ),
2595
+ /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
2596
+ summary && /* @__PURE__ */ jsx(
2597
+ reactNative.Text,
2598
+ {
2599
+ className: "text-text-secondary",
2600
+ style: {
2601
+ fontSize: 12,
2602
+ fontFamily: "Inter, sans-serif",
2603
+ marginRight: 8
2604
+ },
2605
+ testID: "exercise-card-summary",
2606
+ children: formatSummary(summary)
2607
+ }
2608
+ ),
2609
+ isPR && /* @__PURE__ */ jsx(reactNative.View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2610
+ ] }),
2611
+ (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row", style: { marginTop: 6, gap: 4 }, testID: "exercise-card-strips", children: [
2612
+ setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
2613
+ VelocityStrip,
2614
+ {
2615
+ velocities,
2616
+ zones: velocityZones,
2617
+ variant: "mini",
2618
+ testID: `exercise-card-velocity-strip-${i}`
2619
+ },
2620
+ i
2621
+ )),
2622
+ Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
2623
+ PlaceholderStrip,
2624
+ {
2625
+ testID: `exercise-card-placeholder-${i}`
2626
+ },
2627
+ `placeholder-${i}`
2628
+ ))
2629
+ ] })
2170
2630
  ]
2171
2631
  }
2172
2632
  )
2173
2633
  }
2174
2634
  );
2175
2635
  }
2636
+ var DARK2 = getSemanticColors("dark");
2637
+ var BODY_DIVIDER = DARK2["border-subtle"];
2638
+ function deriveHeaderSetStates(sets) {
2639
+ return sets.map((set) => {
2640
+ switch (set.state) {
2641
+ case "live":
2642
+ return { status: "active", velocities: set.velocities, planned: set.target.reps };
2643
+ case "done":
2644
+ return { status: "done", velocities: set.velocities };
2645
+ case "todo":
2646
+ return { status: "todo", planned: set.planned ?? set.target.reps };
2647
+ }
2648
+ });
2649
+ }
2176
2650
  function ExpandedCard({
2177
2651
  name,
2178
2652
  onToggle,
2179
2653
  summary,
2180
- e1rm,
2181
2654
  isPR,
2182
2655
  sets,
2183
2656
  tempo,
2657
+ indicator,
2658
+ setStates,
2659
+ stripHeight = 8,
2660
+ velocityZones,
2184
2661
  supersetPosition
2185
2662
  }) {
2186
2663
  const borderRadius = getSupersetBorderRadius(supersetPosition);
2187
2664
  const supersetMargin = getSupersetMargin(supersetPosition);
2188
2665
  const unit = summary?.unit ?? sets?.[0]?.unit ?? "lbs";
2189
- const columnHeaders = buildColumnHeaders(unit);
2666
+ const headerStates = setStates ?? (sets ? deriveHeaderSetStates(sets) : []);
2667
+ const headerIndicator = indicator ?? (isPR ? "pr" : void 0);
2190
2668
  return /* @__PURE__ */ jsxs(
2191
2669
  reactNative.View,
2192
2670
  {
@@ -2199,114 +2677,32 @@ function ExpandedCard({
2199
2677
  testID: "exercise-card",
2200
2678
  children: [
2201
2679
  /* @__PURE__ */ jsx(
2202
- reactNative.Pressable,
2680
+ ExerciseCardHeading,
2203
2681
  {
2682
+ name,
2683
+ sets: summary?.sets ?? sets?.length ?? 0,
2684
+ reps: summary?.reps ?? 0,
2685
+ load: summary?.weight ?? 0,
2686
+ unit,
2687
+ tempo,
2688
+ indicator: headerIndicator,
2689
+ setStates: headerStates,
2690
+ stripHeight,
2204
2691
  onPress: onToggle,
2205
- accessibilityRole: "button",
2206
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
2207
- "aria-expanded": true,
2208
- testID: "exercise-card-header",
2209
- children: /* @__PURE__ */ jsxs(
2210
- reactNative.View,
2211
- {
2212
- className: "flex-row items-center",
2213
- style: {
2214
- padding: 12,
2215
- paddingHorizontal: 14,
2216
- paddingBottom: 10,
2217
- borderBottomWidth: 1,
2218
- borderBottomColor: resolveColor("border-default")
2219
- },
2220
- children: [
2221
- /* @__PURE__ */ jsx(
2222
- reactNative.Text,
2223
- {
2224
- className: "text-text-primary",
2225
- style: {
2226
- fontSize: 14,
2227
- fontFamily: '"Space Grotesk", sans-serif',
2228
- fontWeight: "700"
2229
- },
2230
- testID: "exercise-card-name",
2231
- children: name
2232
- }
2233
- ),
2234
- /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
2235
- summary && /* @__PURE__ */ jsx(
2236
- reactNative.Text,
2237
- {
2238
- className: "text-text-secondary",
2239
- style: {
2240
- fontSize: 12,
2241
- fontFamily: "Inter, sans-serif",
2242
- marginRight: 8
2243
- },
2244
- testID: "exercise-card-summary",
2245
- children: formatSummary(summary)
2246
- }
2247
- ),
2248
- e1rm && /* @__PURE__ */ jsx(
2249
- WeightBadge,
2250
- {
2251
- value: roundWeight(e1rm.value),
2252
- unit: e1rm.unit,
2253
- size: "sm",
2254
- showIcon: false,
2255
- testID: "exercise-card-e1rm"
2256
- }
2257
- ),
2258
- isPR && /* @__PURE__ */ jsx(reactNative.View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2259
- ]
2260
- }
2261
- )
2692
+ testID: "exercise-card-heading"
2262
2693
  }
2263
2694
  ),
2264
- tempo && /* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: 8, paddingHorizontal: 14 }, testID: "exercise-card-tempo", children: /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm" }) }),
2265
- /* @__PURE__ */ jsx(
2695
+ /* @__PURE__ */ jsxs(
2266
2696
  reactNative.View,
2267
2697
  {
2268
- className: "flex-row",
2269
- style: {
2270
- paddingVertical: 8,
2271
- paddingHorizontal: 8,
2272
- paddingBottom: 4
2273
- },
2274
- testID: "exercise-card-column-headers",
2275
- children: columnHeaders.map((header, i) => {
2276
- const widths = [36, void 0, 44, 56, 36];
2277
- const width = widths[i];
2278
- const isFlex = width === void 0;
2279
- return /* @__PURE__ */ jsx(
2280
- reactNative.View,
2281
- {
2282
- style: {
2283
- ...isFlex ? {} : { width },
2284
- alignItems: "center",
2285
- justifyContent: "center",
2286
- gap: 8
2287
- },
2288
- ...isFlex ? { className: "flex-1" } : {},
2289
- children: /* @__PURE__ */ jsx(
2290
- reactNative.Text,
2291
- {
2292
- className: "text-text-tertiary",
2293
- style: {
2294
- fontSize: 10,
2295
- fontWeight: "600",
2296
- fontFamily: "Inter, sans-serif",
2297
- textTransform: "uppercase",
2298
- letterSpacing: 0.8
2299
- },
2300
- children: header
2301
- }
2302
- )
2303
- },
2304
- header
2305
- );
2306
- })
2698
+ style: { borderTopWidth: 1, borderTopColor: BODY_DIVIDER, paddingBottom: 6 },
2699
+ testID: "exercise-card-body",
2700
+ children: [
2701
+ /* @__PURE__ */ jsx(SetTableHeader, { unit, showPrevious: false, testID: "exercise-card-column-headers" }),
2702
+ sets && /* @__PURE__ */ jsx(reactNative.View, { testID: "exercise-card-sets", children: sets.map((set, i) => /* @__PURE__ */ jsx(SetRow, { ...set, velocityZones: set.velocityZones ?? velocityZones }, i)) })
2703
+ ]
2307
2704
  }
2308
- ),
2309
- sets && /* @__PURE__ */ jsx(reactNative.View, { testID: "exercise-card-sets", children: sets.map((setProps, i) => /* @__PURE__ */ jsx(SetRow, { ...setProps }, i)) })
2705
+ )
2310
2706
  ]
2311
2707
  }
2312
2708
  );
@@ -2315,11 +2711,12 @@ function UpcomingCard({
2315
2711
  name,
2316
2712
  prescription,
2317
2713
  previousBest,
2318
- supersetPosition
2714
+ supersetPosition,
2715
+ onToggle
2319
2716
  }) {
2320
2717
  const borderRadius = getSupersetBorderRadius(supersetPosition);
2321
2718
  const supersetMargin = getSupersetMargin(supersetPosition);
2322
- return /* @__PURE__ */ jsx(
2719
+ return /* @__PURE__ */ jsx(reactNative.Pressable, { onPress: onToggle, accessibilityRole: "button", children: /* @__PURE__ */ jsx(
2323
2720
  reactNative.View,
2324
2721
  {
2325
2722
  style: {
@@ -2329,7 +2726,7 @@ function UpcomingCard({
2329
2726
  ...borderRadius,
2330
2727
  ...supersetMargin
2331
2728
  },
2332
- accessibilityLabel: formatAccessibilityLabel2(name, void 0, prescription),
2729
+ accessibilityLabel: formatAccessibilityLabel(name, void 0, prescription),
2333
2730
  testID: "exercise-card",
2334
2731
  children: /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", children: [
2335
2732
  /* @__PURE__ */ jsx(
@@ -2337,6 +2734,7 @@ function UpcomingCard({
2337
2734
  {
2338
2735
  className: "text-text-primary",
2339
2736
  style: {
2737
+ flexShrink: 0,
2340
2738
  fontSize: 14,
2341
2739
  fontFamily: '"Space Grotesk", sans-serif',
2342
2740
  fontWeight: "700"
@@ -2349,7 +2747,9 @@ function UpcomingCard({
2349
2747
  reactNative.Text,
2350
2748
  {
2351
2749
  className: "text-text-secondary",
2750
+ numberOfLines: 1,
2352
2751
  style: {
2752
+ flexShrink: 1,
2353
2753
  fontSize: 12,
2354
2754
  fontFamily: "Inter, sans-serif",
2355
2755
  marginLeft: 8
@@ -2358,12 +2758,14 @@ function UpcomingCard({
2358
2758
  children: prescription
2359
2759
  }
2360
2760
  ),
2361
- /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1" }),
2761
+ /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { minWidth: 8 } }),
2362
2762
  previousBest && /* @__PURE__ */ jsx(
2363
2763
  reactNative.Text,
2364
2764
  {
2365
2765
  className: "text-text-tertiary",
2766
+ numberOfLines: 1,
2366
2767
  style: {
2768
+ flexShrink: 1,
2367
2769
  fontSize: 11,
2368
2770
  fontFamily: "Inter, sans-serif"
2369
2771
  },
@@ -2373,18 +2775,31 @@ function UpcomingCard({
2373
2775
  )
2374
2776
  ] })
2375
2777
  }
2376
- );
2778
+ ) });
2377
2779
  }
2378
- function ExerciseCard(props) {
2379
- switch (props.state) {
2380
- case "collapsed":
2381
- return /* @__PURE__ */ jsx(CollapsedCard, { ...props });
2382
- case "expanded":
2383
- return /* @__PURE__ */ jsx(ExpandedCard, { ...props });
2384
- case "upcoming":
2385
- return /* @__PURE__ */ jsx(UpcomingCard, { ...props });
2386
- }
2780
+ function ExerciseCard({
2781
+ upcoming,
2782
+ expanded,
2783
+ defaultExpanded,
2784
+ onExpandedChange,
2785
+ ...rest
2786
+ }) {
2787
+ const [internalExpanded, setInternalExpanded] = react.useState(defaultExpanded ?? false);
2788
+ const isControlled = expanded !== void 0;
2789
+ const isExpanded = isControlled ? expanded : internalExpanded;
2790
+ const onToggle = () => {
2791
+ const next = !isExpanded;
2792
+ onExpandedChange?.(next);
2793
+ if (!isControlled) setInternalExpanded(next);
2794
+ };
2795
+ if (upcoming) return /* @__PURE__ */ jsx(UpcomingCard, { ...rest, onToggle });
2796
+ return isExpanded ? /* @__PURE__ */ jsx(ExpandedCard, { ...rest, onToggle }) : /* @__PURE__ */ jsx(CollapsedCard, { ...rest, onToggle });
2387
2797
  }
2798
+ function resolveColor(token, mode = "dark") {
2799
+ return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
2800
+ }
2801
+
2802
+ // src/components/custom/Workout/InputBar.tsx
2388
2803
  var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
2389
2804
  var INPUT_CLASSNAME = "bg-surface-raised border-border-strong text-text-primary";
2390
2805
  var inputStyle = {
@@ -2541,6 +2956,45 @@ function InputBar({
2541
2956
  }
2542
2957
  );
2543
2958
  }
2959
+ function formatDuration(ms) {
2960
+ const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
2961
+ const minutes = Math.floor(totalSeconds / 60);
2962
+ const seconds = totalSeconds % 60;
2963
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
2964
+ }
2965
+ function useTimer(options = {}) {
2966
+ const {
2967
+ mode = "up",
2968
+ durationMs,
2969
+ elapsedMs: controlledElapsedMs,
2970
+ running = false,
2971
+ autoTick = false,
2972
+ tickMs = 1e3
2973
+ } = options;
2974
+ const isControlled = controlledElapsedMs != null;
2975
+ const [internalElapsedMs, setInternalElapsedMs] = react.useState(0);
2976
+ react.useEffect(() => {
2977
+ if (isControlled || !autoTick || !running) return;
2978
+ const id = setInterval(() => {
2979
+ setInternalElapsedMs((prev) => prev + tickMs);
2980
+ }, tickMs);
2981
+ return () => clearInterval(id);
2982
+ }, [isControlled, autoTick, running, tickMs]);
2983
+ const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs;
2984
+ const hasDuration = durationMs != null && durationMs > 0;
2985
+ const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0;
2986
+ let progress;
2987
+ if (hasDuration) {
2988
+ progress = Math.min(1, Math.max(0, elapsedMs / durationMs));
2989
+ } else {
2990
+ progress = mode === "down" ? 1 : 0;
2991
+ }
2992
+ const label = formatDuration(mode === "down" ? remainingMs : elapsedMs);
2993
+ const done = durationMs != null && elapsedMs >= durationMs;
2994
+ return { elapsedMs, remainingMs, progress, label, done };
2995
+ }
2996
+
2997
+ // src/components/custom/Workout/RestTimer.tsx
2544
2998
  var BRAND_PRIMARY3 = getSemanticColors("dark")["brand-primary"];
2545
2999
  function RestTimer({
2546
3000
  totalSeconds,
@@ -2551,14 +3005,18 @@ function RestTimer({
2551
3005
  visible,
2552
3006
  displayOnly = false
2553
3007
  }) {
2554
- if (!visible) return null;
2555
- const remainingMs = Math.max(0, totalSeconds * 1e3 - elapsedMs);
3008
+ const {
3009
+ remainingMs,
3010
+ progress,
3011
+ label: timeDisplay
3012
+ } = useTimer({
3013
+ mode: "down",
3014
+ durationMs: totalSeconds * 1e3,
3015
+ elapsedMs
3016
+ });
2556
3017
  const remainingSec = Math.ceil(remainingMs / 1e3);
2557
- const minutes = Math.floor(remainingSec / 60);
2558
- const seconds = remainingSec % 60;
2559
- const timeDisplay = `${minutes}:${String(seconds).padStart(2, "0")}`;
2560
- const totalMs = totalSeconds * 1e3;
2561
- const progressPct = totalMs > 0 ? Math.min(100, elapsedMs / totalMs * 100) : 100;
3018
+ const progressPct = progress * 100;
3019
+ if (!visible) return null;
2562
3020
  return /* @__PURE__ */ jsxs(
2563
3021
  reactNative.View,
2564
3022
  {
@@ -2797,23 +3255,22 @@ function deriveWorkoutProgress(exercises) {
2797
3255
  function findActiveExercise(exercises) {
2798
3256
  return exercises.find((exercise) => exercise.status === "active") ?? null;
2799
3257
  }
2800
- function statusToCardState(status, focused) {
2801
- if (status === "upcoming") return "upcoming";
2802
- return focused ? "expanded" : "collapsed";
3258
+ function isUpcomingExercise(status) {
3259
+ return status === "upcoming";
2803
3260
  }
2804
3261
  function toActiveCardProps(exercise, focused, onToggle, supersetPosition = null, supersetColor) {
2805
- const state = statusToCardState(exercise.status, focused);
3262
+ const upcoming = isUpcomingExercise(exercise.status);
2806
3263
  return {
2807
3264
  name: exercise.name,
2808
- state,
2809
- onToggle,
3265
+ upcoming,
3266
+ expanded: focused,
3267
+ onExpandedChange: onToggle,
2810
3268
  summary: exercise.summary,
2811
- e1rm: exercise.e1rm,
2812
3269
  isPR: exercise.isPR,
2813
3270
  tempo: exercise.tempo,
2814
3271
  setVelocities: exercise.setVelocities,
2815
3272
  totalPlannedSets: exercise.totalPlannedSets,
2816
- sets: state === "expanded" ? exercise.sets : void 0,
3273
+ sets: focused ? exercise.sets : void 0,
2817
3274
  prescription: exercise.prescription,
2818
3275
  previousBest: exercise.previousBest,
2819
3276
  supersetPosition,
@@ -3502,11 +3959,11 @@ function CardContent({ children, className }) {
3502
3959
  function CardFooter({ children, className }) {
3503
3960
  return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
3504
3961
  }
3505
- var t3 = getSemanticColors("dark");
3962
+ var t5 = getSemanticColors("dark");
3506
3963
  var solidVariantColors = {
3507
- success: t3["status-success"],
3508
- warning: t3["status-warning"],
3509
- error: t3["status-error"],
3964
+ success: t5["status-success"],
3965
+ warning: t5["status-warning"],
3966
+ error: t5["status-error"],
3510
3967
  neutral: "#6B7280"
3511
3968
  };
3512
3969
  var ringVariantStyles = {
@@ -3546,8 +4003,8 @@ var iconColors = {
3546
4003
  warning: "#6B4000",
3547
4004
  error: "#5C1A1A",
3548
4005
  neutral: "#D1D5DB",
3549
- "on-track": t3["status-success"],
3550
- deviation: t3["status-warning"],
4006
+ "on-track": t5["status-success"],
4007
+ deviation: t5["status-warning"],
3551
4008
  future: "#6B7280"
3552
4009
  };
3553
4010
  var sizeDimensions = {
@@ -3633,13 +4090,13 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
3633
4090
  var WORKOUT_PILL_DELOAD = primitiveRamps.magenta[600];
3634
4091
 
3635
4092
  // src/components/custom/Workout/MesoStatusCard.tsx
3636
- var t4 = getSemanticColors("dark");
3637
- var BRAND_PRIMARY5 = t4["brand-primary"];
4093
+ var t6 = getSemanticColors("dark");
4094
+ var BRAND_PRIMARY5 = t6["brand-primary"];
3638
4095
  var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
3639
4096
  var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
3640
- var SUCCESS = t4["status-success"];
3641
- var WARNING = t4["status-warning"];
3642
- var ERROR = t4["status-error"];
4097
+ var SUCCESS = t6["status-success"];
4098
+ var WARNING = t6["status-warning"];
4099
+ var ERROR = t6["status-error"];
3643
4100
  var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY5, BRAND_PRIMARY_LIGHT];
3644
4101
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
3645
4102
  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%)";
@@ -3711,7 +4168,7 @@ function StatusPill({ badge }) {
3711
4168
  }
3712
4169
  );
3713
4170
  }
3714
- function MetricCell({ metric }) {
4171
+ function MetricCell2({ metric }) {
3715
4172
  return /* @__PURE__ */ jsxs(reactNative.View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
3716
4173
  /* @__PURE__ */ jsx(
3717
4174
  reactNative.Text,
@@ -3929,7 +4386,7 @@ function MesoStatusCard({
3929
4386
  className: "flex-row flex-wrap",
3930
4387
  style: { gap: 8 },
3931
4388
  testID: "meso-status-card-metrics",
3932
- children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
4389
+ children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell2, { metric }, `${metric.label}-${index}`))
3933
4390
  }
3934
4391
  ),
3935
4392
  gauges.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 12 }, testID: "meso-status-card-gauges", children: gauges.map((gauge, index) => /* @__PURE__ */ jsx(Gauge, { gauge }, `${gauge.label}-${index}`)) }),
@@ -4010,8 +4467,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
4010
4467
  var PLOT_LEFT = 26;
4011
4468
  var TOOLTIP_WIDTH = 124;
4012
4469
  function timeOf(dateStr) {
4013
- const t10 = Date.parse(dateStr);
4014
- return Number.isNaN(t10) ? 0 : t10;
4470
+ const t12 = Date.parse(dateStr);
4471
+ return Number.isNaN(t12) ? 0 : t12;
4015
4472
  }
4016
4473
  function formatValue(value) {
4017
4474
  return `${Math.round(value)}`;
@@ -4500,10 +4957,10 @@ function StrengthTrendChart({
4500
4957
  }
4501
4958
  );
4502
4959
  }
4503
- var t5 = getSemanticColors("dark");
4504
- var STATUS_SUCCESS2 = t5["status-success"];
4505
- var STATUS_WARNING2 = t5["status-warning"];
4506
- var STATUS_INFO = t5["status-info"];
4960
+ var t7 = getSemanticColors("dark");
4961
+ var STATUS_SUCCESS2 = t7["status-success"];
4962
+ var STATUS_WARNING2 = t7["status-warning"];
4963
+ var STATUS_INFO = t7["status-info"];
4507
4964
  var DOT_BORDER = "#F3F4F6";
4508
4965
  var BAND_FILL = "rgba(46,213,115,0.1)";
4509
4966
  var BAND_EDGE = "rgba(46,213,115,0.45)";
@@ -4546,13 +5003,13 @@ function interpEdge(pixels, x, key) {
4546
5003
  const a = pixels[i];
4547
5004
  const b = pixels[i + 1];
4548
5005
  if (x >= a.x && x <= b.x) {
4549
- const t10 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
4550
- return a[key] + t10 * (b[key] - a[key]);
5006
+ const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
5007
+ return a[key] + t12 * (b[key] - a[key]);
4551
5008
  }
4552
5009
  }
4553
5010
  return pixels[pixels.length - 1][key];
4554
5011
  }
4555
- function buildColumns(pixels, step) {
5012
+ function buildColumns2(pixels, step) {
4556
5013
  if (pixels.length < 2) return [];
4557
5014
  const start = pixels[0].x;
4558
5015
  const end = pixels[pixels.length - 1].x;
@@ -4664,7 +5121,7 @@ function CapacityBandChart({
4664
5121
  }
4665
5122
  const { toX, toY } = scale;
4666
5123
  const bandPixels = toPixels(band, toX, toY);
4667
- const columns = buildColumns(bandPixels, COLUMN_STEP);
5124
+ const columns = buildColumns2(bandPixels, COLUMN_STEP);
4668
5125
  const topEdge = buildEdges(bandPixels, "yHigh");
4669
5126
  const bottomEdge = buildEdges(bandPixels, "yLow");
4670
5127
  const lastBandPoint = band[band.length - 1];
@@ -4739,8 +5196,8 @@ function CapacityBandChart({
4739
5196
  testID: "capacity-band-chart-reveal",
4740
5197
  children: /* @__PURE__ */ jsxs(reactNative.View, { style: { width, height, position: "relative" }, children: [
4741
5198
  projection && /* @__PURE__ */ jsxs(reactNative.View, { testID: "capacity-band-chart-projection", children: [
4742
- renderColumns(buildColumns(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
4743
- renderColumns(buildColumns(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5199
+ renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5200
+ renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
4744
5201
  renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
4745
5202
  renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
4746
5203
  renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
@@ -4901,10 +5358,9 @@ function deriveExerciseStats(entries) {
4901
5358
  function toExerciseCardProps(entry, expanded, onToggle) {
4902
5359
  return {
4903
5360
  name: entry.title,
4904
- state: expanded ? "expanded" : "collapsed",
4905
- onToggle,
5361
+ expanded,
5362
+ onExpandedChange: onToggle,
4906
5363
  summary: entry.summary,
4907
- e1rm: entry.e1rm,
4908
5364
  isPR: entry.isPR,
4909
5365
  tempo: entry.tempo,
4910
5366
  sets: expanded ? entry.sets : void 0
@@ -5448,6 +5904,7 @@ var colorStyles = {
5448
5904
  default: "bg-text-tertiary",
5449
5905
  primary: "bg-brand-primary",
5450
5906
  success: "bg-status-success",
5907
+ live: "bg-status-live",
5451
5908
  error: "bg-status-error",
5452
5909
  warning: "bg-status-warning",
5453
5910
  info: "bg-status-info",
@@ -5504,7 +5961,7 @@ function Indicator({
5504
5961
  colorClass,
5505
5962
  pulseMode === "opacity" && "animate-pulse",
5506
5963
  ring && "border-2 border-background-base",
5507
- glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
5964
+ glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
5508
5965
  glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
5509
5966
  glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
5510
5967
  glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
@@ -5586,7 +6043,7 @@ function Badge({
5586
6043
  }
5587
6044
  );
5588
6045
  }
5589
- var t6 = getSemanticColors("dark");
6046
+ var t8 = getSemanticColors("dark");
5590
6047
  var statusBgColors = {
5591
6048
  completed: "rgba(46,213,115,0.15)",
5592
6049
  current: "rgba(255,121,0,0.15)",
@@ -5604,14 +6061,14 @@ var statusBorderStyles = {
5604
6061
  deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
5605
6062
  };
5606
6063
  var textColors = {
5607
- completed: t6["status-success"],
5608
- current: t6["brand-primary"],
5609
- next: t6["brand-primary"],
6064
+ completed: t8["status-success"],
6065
+ current: t8["brand-primary"],
6066
+ next: t8["brand-primary"],
5610
6067
  upcoming: "#6B7280",
5611
6068
  missed: "rgba(209,67,67,0.7)",
5612
6069
  deload: WORKOUT_PILL_DELOAD
5613
6070
  };
5614
- function usePulse(enabled) {
6071
+ function usePulse2(enabled) {
5615
6072
  const [opacity] = react.useState(() => new reactNative.Animated.Value(1));
5616
6073
  react.useEffect(() => {
5617
6074
  if (!enabled) {
@@ -5649,7 +6106,7 @@ function WorkoutPill({
5649
6106
  ...props
5650
6107
  }) {
5651
6108
  const shouldPulse = pulse ?? status === "current";
5652
- const pulseOpacity = usePulse(shouldPulse);
6109
+ const pulseOpacity = usePulse2(shouldPulse);
5653
6110
  const isCompleted = status === "completed";
5654
6111
  const isMissed = status === "missed";
5655
6112
  const pill = /* @__PURE__ */ jsx(
@@ -5739,19 +6196,19 @@ function WorkoutPill({
5739
6196
  }
5740
6197
  return pill;
5741
6198
  }
5742
- var t7 = getSemanticColors("dark");
6199
+ var t9 = getSemanticColors("dark");
5743
6200
  var TRACK_BG = "#333333";
5744
6201
  var LABEL_COLOR = "#6B7280";
5745
6202
  var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
5746
6203
  var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
5747
6204
  var ZONE = {
5748
- building: t7["status-success"],
6205
+ building: t9["status-success"],
5749
6206
  // below target ramp-up
5750
- approaching: t7["status-warning"],
6207
+ approaching: t9["status-warning"],
5751
6208
  // nearing target
5752
- target: t7["brand-primary"],
6209
+ target: t9["brand-primary"],
5753
6210
  // exactly at target
5754
- over1: t7["status-error"],
6211
+ over1: t9["status-error"],
5755
6212
  // mild over-target
5756
6213
  over2: WORKOUT_TOKENS.intensity.over2,
5757
6214
  // moderate over-target
@@ -6174,13 +6631,13 @@ function MesoCard({
6174
6631
  }
6175
6632
  ) });
6176
6633
  }
6177
- var t8 = getSemanticColors("dark");
6634
+ var t10 = getSemanticColors("dark");
6178
6635
  var dotColorMap = {
6179
6636
  untrained: "#2C2C2C",
6180
- behind: t8["brand-secondary"],
6181
- ontrack: t8["status-success"],
6182
- target: t8["brand-primary"],
6183
- over: t8["status-error"]
6637
+ behind: t10["brand-secondary"],
6638
+ ontrack: t10["status-success"],
6639
+ target: t10["brand-primary"],
6640
+ over: t10["status-error"]
6184
6641
  };
6185
6642
  function MuscleGroupChip({
6186
6643
  name,
@@ -6254,10 +6711,10 @@ function MuscleGroupChip({
6254
6711
  }
6255
6712
 
6256
6713
  // src/components/custom/Workout/WorkoutCard.tsx
6257
- var t9 = getSemanticColors("dark");
6714
+ var t11 = getSemanticColors("dark");
6258
6715
  var COLORS = {
6259
- statusSuccess: t9["status-success"],
6260
- brandPrimary: t9["brand-primary"],
6716
+ statusSuccess: t11["status-success"],
6717
+ brandPrimary: t11["brand-primary"],
6261
6718
  borderDefault: "#1F1F1F",
6262
6719
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
6263
6720
  };
@@ -6541,8 +6998,8 @@ function WeekLevel({ week, onSelectWorkout }) {
6541
6998
  function WorkoutLevel({ workout, expandedExercise, onToggleExercise }) {
6542
6999
  const exercises = workout.exercises.map((exercise, index) => ({
6543
7000
  ...exercise,
6544
- state: exercise.state === "upcoming" ? exercise.state : index === expandedExercise ? "expanded" : "collapsed",
6545
- onToggle: () => onToggleExercise(index)
7001
+ expanded: index === expandedExercise,
7002
+ onExpandedChange: () => onToggleExercise(index)
6546
7003
  }));
6547
7004
  return /* @__PURE__ */ jsx(reactNative.View, { testID: "program-planning-page-workout-level", children: /* @__PURE__ */ jsx(
6548
7005
  WorkoutCard,
@@ -6685,7 +7142,7 @@ exports.findMeso = findMeso;
6685
7142
  exports.findWeek = findWeek;
6686
7143
  exports.findWorkout = findWorkout;
6687
7144
  exports.groupExercises = groupExercises;
6688
- exports.statusToCardState = statusToCardState;
7145
+ exports.isUpcomingExercise = isUpcomingExercise;
6689
7146
  exports.summarizeVbt = summarizeVbt;
6690
7147
  exports.toActiveCardProps = toActiveCardProps;
6691
7148
  exports.toExerciseCardProps = toExerciseCardProps;