@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.mjs CHANGED
@@ -8,21 +8,18 @@ import { twMerge } from 'tailwind-merge';
8
8
 
9
9
  // src/theme/tokens/primitives.ts
10
10
  var primitiveColors = {
11
- // Blue scale (primary brand)
11
+ // Blue scale
12
12
  blue: {
13
13
  400: "#60A5FA",
14
14
  600: "#5048E5",
15
- // Primary main
16
15
  700: "#3832A0"},
17
- // Red scale (error)
16
+ // Red scale
18
17
  red: {
19
- // Error light
20
18
  500: "#EF4444",
21
19
  600: "#D14343"},
22
- // Vivid red scale (alert)
20
+ // Vivid red scale
23
21
  redVivid: {
24
22
  400: "#FF6070",
25
- // Error vivid light
26
23
  500: "#FF4757",
27
24
  700: "#C42539"},
28
25
  // Neutral/Gray scale
@@ -65,11 +62,16 @@ var primitiveRamps = {
65
62
  50: "#FFF7DD",
66
63
  200: "#FFD352",
67
64
  300: "#F9B415",
65
+ // pin
66
+ 400: "#E08C00",
68
67
  500: "#C27400"},
69
68
  green: {
70
69
  50: "#E3FFEE",
71
70
  200: "#58F69E",
72
71
  300: "#2ED573",
72
+ // pin
73
+ 400: "#21C05D",
74
+ 500: "#22A444",
73
75
  600: "#298732"},
74
76
  cyan: {
75
77
  // pin
@@ -77,7 +79,8 @@ var primitiveRamps = {
77
79
  500: "#2697B7",
78
80
  600: "#307B9B",
79
81
  700: "#2A617F",
80
- 800: "#22465F"},
82
+ 800: "#22465F",
83
+ 900: "#0B3149"},
81
84
  blue: {
82
85
  50: "#EFF8FF",
83
86
  300: "#78C2FF",
@@ -132,15 +135,22 @@ var WORKOUT_TOKENS = {
132
135
  // Canonical 4-band performance scale — the single source for BOTH the
133
136
  // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
134
137
  // is intentionally inverted between the two consumers: for velocity, green =
135
- // fastest/best; for RPE, green = easiest. This is the [0,1,2,4] subsample of
136
- // the canonical `sequentialEffort` primitive. The `orange` key is the band
137
- // label. Consumed inline (RN).
138
+ // fastest/best; for RPE, green = easiest. This is the [0,2,3,4] subsample of
139
+ // the canonical `sequentialEffort` primitive: an EVEN hue walk across the ramp
140
+ // (green gold → orange → red). The earlier [0,1,2,4] spent two of its four
141
+ // bands on adjacent yellows (amber-200 + amber-300) and skipped orange, reading
142
+ // yellow-heavy with an abrupt jump to red; sampling amber-300 + orange-400
143
+ // instead gives four cleanly separable bands that hold at 3px strip scale.
144
+ // Consumed inline (RN).
138
145
  scale: {
139
146
  green: sequentialEffort[0],
140
- yellow: sequentialEffort[1],
141
- orange: sequentialEffort[2],
142
- // gold band
147
+ // green-300
148
+ yellow: sequentialEffort[2],
149
+ // amber-300 (gold)
150
+ orange: sequentialEffort[3],
151
+ // orange-400 (true orange)
143
152
  red: sequentialEffort[4]
153
+ // red-600
144
154
  },
145
155
  // Surface and border colors were removed here: those flip with the theme, so
146
156
  // components use the CSS custom properties (var(--color-surface-*/border-*))
@@ -159,12 +169,6 @@ var WORKOUT_TOKENS = {
159
169
  function roundRpe(rpe) {
160
170
  return Math.round(rpe * 2) / 2;
161
171
  }
162
- function rpeColor(rpe) {
163
- if (rpe >= 9.5) return WORKOUT_TOKENS.scale.red;
164
- if (rpe >= 8.5) return WORKOUT_TOKENS.scale.orange;
165
- if (rpe >= 7) return WORKOUT_TOKENS.scale.yellow;
166
- return WORKOUT_TOKENS.scale.green;
167
- }
168
172
  function roundWeight(weight) {
169
173
  return Math.round(weight);
170
174
  }
@@ -192,14 +196,258 @@ function wrapJsx(fn) {
192
196
  var jsx = wrapJsx(jsx$1);
193
197
  var jsxs = wrapJsx(jsxs$1);
194
198
 
199
+ // src/components/custom/Workout/SegmentedBar.tsx
200
+ var PULSE_HALF_MS = 950;
201
+ var PULSE_MIN_OPACITY = 0.45;
202
+ var SEGMENTED_BAR_GAP = 5;
203
+ function usePulse(active) {
204
+ const [value] = useState(() => new Animated.Value(0));
205
+ useEffect(() => {
206
+ if (!active) return;
207
+ const loop = Animated.loop(
208
+ Animated.sequence([
209
+ Animated.timing(value, {
210
+ toValue: 1,
211
+ duration: PULSE_HALF_MS,
212
+ easing: Easing.inOut(Easing.ease),
213
+ useNativeDriver: false
214
+ }),
215
+ Animated.timing(value, {
216
+ toValue: 0,
217
+ duration: PULSE_HALF_MS,
218
+ easing: Easing.inOut(Easing.ease),
219
+ useNativeDriver: false
220
+ })
221
+ ])
222
+ );
223
+ loop.start();
224
+ return () => loop.stop();
225
+ }, [active, value]);
226
+ return value;
227
+ }
228
+ function SegmentedBar({
229
+ segments,
230
+ height = 8,
231
+ gap = SEGMENTED_BAR_GAP,
232
+ radius = 2,
233
+ marker = null,
234
+ segmentTestID,
235
+ style,
236
+ ...props
237
+ }) {
238
+ const pulse = usePulse(segments.some((s) => s.pulse));
239
+ return /* @__PURE__ */ jsxs(View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
240
+ segments.map((seg, i) => {
241
+ const fillStyle = {
242
+ width: `${(seg.fill ?? 1) * 100}%`,
243
+ height: "100%",
244
+ backgroundColor: seg.color,
245
+ opacity: seg.opacity
246
+ };
247
+ const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
248
+ return /* @__PURE__ */ jsx(
249
+ View,
250
+ {
251
+ style: {
252
+ flex: seg.weight ?? 1,
253
+ minWidth: 0,
254
+ height: "100%",
255
+ marginLeft: seg.leadingGap,
256
+ borderRadius: radius,
257
+ overflow: "hidden"
258
+ },
259
+ children: seg.pulse ? /* @__PURE__ */ jsx(
260
+ Animated.View,
261
+ {
262
+ style: {
263
+ ...fillStyle,
264
+ ...seg.pulseColor ? {
265
+ backgroundColor: pulse.interpolate({
266
+ inputRange: [0, 1],
267
+ outputRange: seg.pulseColor
268
+ })
269
+ } : {
270
+ opacity: pulse.interpolate({
271
+ inputRange: [0, 1],
272
+ outputRange: [PULSE_MIN_OPACITY, 1]
273
+ })
274
+ }
275
+ },
276
+ accessibilityElementsHidden: true,
277
+ testID
278
+ }
279
+ ) : /* @__PURE__ */ jsx(View, { style: fillStyle, accessibilityElementsHidden: true, testID })
280
+ },
281
+ i
282
+ );
283
+ }),
284
+ marker ? /* @__PURE__ */ jsx(
285
+ View,
286
+ {
287
+ style: {
288
+ position: "absolute",
289
+ left: `${marker.position * 100}%`,
290
+ top: 0,
291
+ bottom: 0,
292
+ width: 2,
293
+ backgroundColor: marker.color
294
+ },
295
+ accessibilityElementsHidden: true,
296
+ testID: "segmented-bar-marker"
297
+ }
298
+ ) : null
299
+ ] });
300
+ }
301
+
302
+ // src/components/custom/Workout/SetBar.tsx
303
+ var SET_STRIP_ZONES = {
304
+ slow: primitiveRamps.red[600],
305
+ moderate: primitiveRamps.orange[400],
306
+ fast: primitiveRamps.amber[300],
307
+ fastest: primitiveRamps.green[300]
308
+ };
309
+ var TODO_COLOR = primitiveColors.charcoal[300];
310
+ var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
311
+ var NOTCH_GAP = 2;
312
+ var CLUSTER_GAP = 3;
313
+ var MYO_UPCOMING_CLUSTERS = 3;
314
+ var MYO_UPCOMING_CLUSTER_LEN = 5;
315
+ var MYO_UPCOMING_FADE = 0.6;
316
+ var PULSE_RANGE = {
317
+ [SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
318
+ [SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
319
+ [SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
320
+ [SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
321
+ };
322
+ function velocityZoneColor(v) {
323
+ if (v < 0.5) return SET_STRIP_ZONES.slow;
324
+ if (v < 0.75) return SET_STRIP_ZONES.moderate;
325
+ if (v < 1) return SET_STRIP_ZONES.fast;
326
+ return SET_STRIP_ZONES.fastest;
327
+ }
328
+ function chunkedSegments(chunks, gap) {
329
+ return chunks.flatMap(
330
+ (vels, ci) => vels.map((v, ri) => ({
331
+ color: velocityZoneColor(v),
332
+ ...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
333
+ }))
334
+ );
335
+ }
336
+ function rangeSegments(floor, max, doneVels) {
337
+ return Array.from({ length: max }, (_, i) => {
338
+ if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
339
+ return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
340
+ });
341
+ }
342
+ function myoUpcomingSegments(activationLen, clusterCount) {
343
+ const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
344
+ const trail = Array.from(
345
+ { length: clusterCount },
346
+ (_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
347
+ color: SET_STRIP_VARIABLE_COLOR,
348
+ opacity: MYO_UPCOMING_FADE ** ci,
349
+ ...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
350
+ }))
351
+ ).flat();
352
+ return [...activation, ...trail];
353
+ }
354
+ function setSegments(set) {
355
+ if (set.status === "todo") {
356
+ return [{ color: TODO_COLOR }];
357
+ }
358
+ if (set.status === "done") {
359
+ return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
360
+ }
361
+ if (set.status === "range") {
362
+ return rangeSegments(set.floor, set.max, set.doneVels);
363
+ }
364
+ if (set.status === "drop") {
365
+ return chunkedSegments(set.subloads, NOTCH_GAP);
366
+ }
367
+ if (set.status === "myo") {
368
+ return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
369
+ }
370
+ if (set.status === "myo-upcoming") {
371
+ return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
372
+ }
373
+ const performed = set.velocities.map((v) => {
374
+ const color = velocityZoneColor(v);
375
+ return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
376
+ });
377
+ const remaining = Math.max(0, set.planned - set.velocities.length);
378
+ return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
379
+ }
380
+ function setBarSegmentTestID(seg) {
381
+ if (seg.pulse) return "set-strip-pulse";
382
+ if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
383
+ return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
384
+ }
385
+ function SetBar({ set, height = 8 }) {
386
+ return /* @__PURE__ */ jsx(
387
+ SegmentedBar,
388
+ {
389
+ segments: setSegments(set),
390
+ height,
391
+ gap: 0,
392
+ radius: 0,
393
+ segmentTestID: setBarSegmentTestID,
394
+ style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
395
+ testID: "set-strip-set"
396
+ }
397
+ );
398
+ }
399
+
400
+ // src/components/custom/Workout/SetStrip.tsx
401
+ var SET_STRIP_GAP = 5;
402
+ function setCategory(set) {
403
+ switch (set.status) {
404
+ case "done":
405
+ case "drop":
406
+ case "myo":
407
+ return "done";
408
+ case "active":
409
+ return "active";
410
+ case "range":
411
+ return set.doneVels.length > 0 ? "active" : "upcoming";
412
+ case "todo":
413
+ case "myo-upcoming":
414
+ return "upcoming";
415
+ }
416
+ }
417
+ function describeSets(sets) {
418
+ const done = sets.filter((s) => setCategory(s) === "done").length;
419
+ const active = sets.filter((s) => setCategory(s) === "active").length;
420
+ const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
421
+ return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
422
+ }
423
+ function SetStrip({ sets, height = 8, className, ...props }) {
424
+ return /* @__PURE__ */ jsx(
425
+ View,
426
+ {
427
+ className,
428
+ style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
429
+ accessibilityRole: "image",
430
+ accessibilityLabel: describeSets(sets),
431
+ testID: "set-strip",
432
+ ...props,
433
+ children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
434
+ }
435
+ );
436
+ }
437
+
195
438
  // src/components/custom/Workout/VelocityStrip.tsx
196
439
  var VEL_COLORS = WORKOUT_TOKENS.scale;
197
- var zoneIdToScaleToken = {
198
- speed: "green",
199
- power: "yellow",
200
- strengthSpeed: "orange",
201
- maximalStrength: "red",
202
- grinding: "red"
440
+ var bandIdToEffortColor = {
441
+ speed: sequentialEffort[0],
442
+ // green-300
443
+ power: sequentialEffort[2],
444
+ // amber-300 (gold)
445
+ strengthSpeed: sequentialEffort[3],
446
+ // orange-400
447
+ maximalStrength: sequentialEffort[4],
448
+ // red-600
449
+ grinding: sequentialEffort[5]
450
+ // red-700 — the distinct 5th band
203
451
  };
204
452
  function getVelocityZoneColor(velocity) {
205
453
  if (velocity >= 1) return "vel-green";
@@ -238,14 +486,132 @@ function classifyBand(velocity, bands) {
238
486
  return bands[bands.length - 1];
239
487
  }
240
488
  function bandColor(band) {
241
- const token = zoneIdToScaleToken[band.id];
242
- return token ? VEL_COLORS[token] : VEL_COLORS.green;
489
+ return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
243
490
  }
244
491
  function getLossStyle(loss) {
245
492
  if (loss > 25) return { color: VEL_COLORS.red };
246
493
  if (loss > 20) return { color: VEL_COLORS.orange };
247
494
  return null;
248
495
  }
496
+ var REP_GAP = 2;
497
+ var WIDE_GAP = 8;
498
+ var TODO_COLOR2 = primitiveColors.charcoal[300];
499
+ var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
500
+ function deriveDoneVelocities(set) {
501
+ switch (set.type) {
502
+ case "drop":
503
+ return set.subloads.flat();
504
+ case "myo":
505
+ return [...set.activation, ...set.clusters.flat()];
506
+ default:
507
+ return set.velocities;
508
+ }
509
+ }
510
+ function chunkedRepSlots(chunks) {
511
+ const slots = [];
512
+ chunks.forEach((chunk) => {
513
+ chunk.forEach((velocity, ri) => {
514
+ const first = slots.length === 0;
515
+ slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP });
516
+ });
517
+ });
518
+ return slots;
519
+ }
520
+ function buildSlots(set) {
521
+ switch (set.type) {
522
+ case "straight": {
523
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
524
+ return Array.from({ length: total }, (_, i) => {
525
+ const done = i < set.velocities.length;
526
+ return {
527
+ kind: done ? "rep" : "todo",
528
+ velocity: done ? set.velocities[i] : void 0,
529
+ leadingGap: i === 0 ? 0 : REP_GAP
530
+ };
531
+ });
532
+ }
533
+ case "range": {
534
+ const total = Math.max(set.max, set.velocities.length);
535
+ return Array.from({ length: total }, (_, i) => {
536
+ const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
537
+ return {
538
+ kind,
539
+ velocity: kind === "rep" ? set.velocities[i] : void 0,
540
+ leadingGap: i === 0 ? 0 : REP_GAP
541
+ };
542
+ });
543
+ }
544
+ case "amrap": {
545
+ const reps = set.velocities.map((velocity, i) => ({
546
+ kind: "rep",
547
+ velocity,
548
+ leadingGap: i === 0 ? 0 : REP_GAP
549
+ }));
550
+ reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP });
551
+ return reps;
552
+ }
553
+ case "drop":
554
+ return chunkedRepSlots(set.subloads);
555
+ case "myo": {
556
+ const slots = chunkedRepSlots([set.activation, ...set.clusters]);
557
+ if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP });
558
+ return slots;
559
+ }
560
+ case "cluster": {
561
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
562
+ return Array.from({ length: total }, (_, i) => {
563
+ const done = i < set.velocities.length;
564
+ const boundary = i > 0 && i % set.groupSize === 0;
565
+ return {
566
+ kind: done ? "rep" : "todo",
567
+ velocity: done ? set.velocities[i] : void 0,
568
+ leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP
569
+ };
570
+ });
571
+ }
572
+ }
573
+ }
574
+ function setAccessibilityLabel(set, repCount) {
575
+ switch (set.type) {
576
+ case "straight":
577
+ return `Velocity strip, straight set, ${repCount} reps`;
578
+ case "range":
579
+ return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
580
+ case "amrap":
581
+ return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
582
+ case "drop":
583
+ return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
584
+ case "myo":
585
+ return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
586
+ case "cluster":
587
+ return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
588
+ }
589
+ }
590
+ function slotAccessibilityLabel(slot, index) {
591
+ switch (slot.kind) {
592
+ case "rep":
593
+ return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
594
+ case "todo":
595
+ return `Rep ${index + 1}: planned`;
596
+ case "variable":
597
+ return `Rep ${index + 1}: variable`;
598
+ case "continue":
599
+ return "Keep going";
600
+ }
601
+ }
602
+ var EXPANDED_TODO_STUB_PCT = 16;
603
+ var EXPANDED_ENCODED_PCT = 45;
604
+ var EXPANDED_HEIGHT = 60;
605
+ var FIXED_MAX_VELOCITY = 1.15;
606
+ var BARE_STUB_HEIGHT = 3;
607
+ function scaleDenominator(scale, maxVelocity) {
608
+ return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
609
+ }
610
+ function bareSlotHeight(slot, height, denom) {
611
+ if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
612
+ const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
613
+ return Math.max(BARE_STUB_HEIGHT, ratio * height);
614
+ }
249
615
  function getReducedMotionPreference() {
250
616
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
251
617
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
@@ -267,31 +633,48 @@ var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
267
633
  var POP_EASING = Easing.bezier(0.34, 1.56, 0.64, 1);
268
634
  function VelocityStrip({
269
635
  velocities,
636
+ set,
270
637
  zones,
271
638
  liveRepIndex,
272
- expanded = false,
639
+ expanded = true,
273
640
  onToggle,
274
641
  onRepPress,
275
- variant = "full",
642
+ variant = "expanded",
643
+ height = EXPANDED_HEIGHT,
644
+ scale = "peak",
645
+ showNumbers = true,
276
646
  showInfo = true,
277
647
  className,
278
648
  ...props
279
649
  }) {
280
- const maxVelocity = Math.max(...velocities, 0);
281
- const meanVelocity = calculateMeanVelocity(velocities);
282
- const loss = calculateVelocityLoss(velocities);
650
+ const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
651
+ const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
652
+ kind: "rep",
653
+ velocity: v,
654
+ leadingGap: i === 0 ? 0 : REP_GAP
655
+ }));
656
+ const maxVelocity = Math.max(...doneVelocities, 0);
657
+ const meanVelocity = calculateMeanVelocity(doneVelocities);
658
+ const loss = calculateVelocityLoss(doneVelocities);
659
+ const framed = showNumbers || showInfo;
660
+ const scaleDenom = scaleDenominator(scale, maxVelocity);
283
661
  const hasZones = zones != null && zones.length > 0;
284
662
  const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
663
+ const slotColor = (slot) => {
664
+ if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
665
+ if (slot.kind === "todo") return TODO_COLOR2;
666
+ return SET_STRIP_VARIABLE_COLOR;
667
+ };
285
668
  const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
286
669
  const prefersReducedMotion = usePrefersReducedMotion();
287
- const [heightAnim] = useState(() => new Animated.Value(expanded ? 60 : 3));
670
+ const [heightAnim] = useState(() => new Animated.Value(expanded ? height : 3));
288
671
  const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
289
672
  const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
290
673
  const [liveScale] = useState(() => new Animated.Value(1));
291
- const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
674
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
292
675
  const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
293
676
  useEffect(() => {
294
- if (variant === "mini" || liveRepIndex == null || liveVelocity == null) return;
677
+ if (variant !== "expanded" || !framed || liveRepIndex == null || liveVelocity == null) return;
295
678
  if (prefersReducedMotion) {
296
679
  liveScale.setValue(1);
297
680
  return;
@@ -321,12 +704,12 @@ function VelocityStrip({
321
704
  useNativeDriver: true
322
705
  }).start();
323
706
  }
324
- }, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
707
+ }, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
325
708
  useEffect(() => {
326
- if (variant === "mini") return;
709
+ if (variant !== "expanded" || !framed) return;
327
710
  Animated.parallel([
328
711
  Animated.timing(heightAnim, {
329
- toValue: expanded ? 60 : 3,
712
+ toValue: expanded ? height : 3,
330
713
  duration: ANIMATION_DURATION,
331
714
  easing: ANIMATION_EASING,
332
715
  useNativeDriver: false
@@ -344,39 +727,118 @@ function VelocityStrip({
344
727
  useNativeDriver: false
345
728
  })
346
729
  ]).start();
347
- }, [expanded, variant, heightAnim, labelOpacity, infoOpacity]);
730
+ }, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
731
+ if (set == null && velocities == null) return null;
732
+ const repCount = doneVelocities.length;
733
+ const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
348
734
  if (variant === "mini") {
349
735
  const { style: externalStyle, ...restProps } = props;
350
736
  return /* @__PURE__ */ jsx(
351
737
  View,
352
738
  {
353
739
  className,
354
- style: [{ flexDirection: "row", height: 3, gap: 2, borderRadius: 2, overflow: "hidden" }, externalStyle],
740
+ style: [
741
+ { flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
742
+ externalStyle
743
+ ],
355
744
  accessibilityRole: "image",
356
- accessibilityLabel: `Velocity strip, ${velocities.length} reps`,
745
+ accessibilityLabel: miniLabel,
357
746
  testID: "velocity-strip-mini",
358
747
  ...restProps,
359
- children: velocities.map((v, i) => /* @__PURE__ */ jsx(
748
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
360
749
  View,
361
750
  {
362
751
  style: {
363
752
  flex: 1,
364
- backgroundColor: barColorFor(v),
753
+ backgroundColor: slotColor(slot),
365
754
  borderRadius: 1,
366
755
  minWidth: 4,
367
- height: "100%"
756
+ height: "100%",
757
+ // The container's uniform 2px gap covers rep spacing; a wide slot adds
758
+ // only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
759
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
760
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
761
+ },
762
+ accessibilityElementsHidden: true,
763
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
764
+ },
765
+ i
766
+ ))
767
+ }
768
+ );
769
+ }
770
+ if (!framed) {
771
+ const { style: externalStyle, ...restProps } = props;
772
+ return /* @__PURE__ */ jsx(
773
+ View,
774
+ {
775
+ className,
776
+ style: [
777
+ { flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
778
+ externalStyle
779
+ ],
780
+ accessibilityRole: "image",
781
+ accessibilityLabel: miniLabel,
782
+ testID: "velocity-strip-compact",
783
+ ...restProps,
784
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
785
+ View,
786
+ {
787
+ style: {
788
+ flex: 1,
789
+ minWidth: 4,
790
+ height: bareSlotHeight(slot, height, scaleDenom),
791
+ // Top-rounded to match the framed chart's bars (the "rounded tops from
792
+ // the numbers one"); square bottoms since bars sit on the baseline.
793
+ borderTopLeftRadius: 2,
794
+ borderTopRightRadius: 2,
795
+ backgroundColor: slotColor(slot),
796
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
797
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
368
798
  },
369
799
  accessibilityElementsHidden: true,
370
- testID: `velocity-bar-${i}`
800
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
371
801
  },
372
802
  i
373
803
  ))
374
804
  }
375
805
  );
376
806
  }
377
- const stripLabel = `Velocity chart for set, ${velocities.length} reps, tap to ${expanded ? "collapse" : "expand"}`;
807
+ const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
378
808
  const hasInteractiveContainer = onToggle != null;
379
809
  const hasInteractiveReps = onRepPress != null && expanded;
810
+ const setBars = set != null && slots.map((slot, i) => {
811
+ const isStraightRep = set.type === "straight" && slot.kind === "rep";
812
+ const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
813
+ return /* @__PURE__ */ jsx(
814
+ View,
815
+ {
816
+ style: {
817
+ flex: 1,
818
+ height: "100%",
819
+ justifyContent: "flex-end",
820
+ marginLeft: slot.leadingGap
821
+ },
822
+ children: /* @__PURE__ */ jsx(
823
+ View,
824
+ {
825
+ style: {
826
+ height: `${heightPct}%`,
827
+ minHeight: 2,
828
+ borderTopLeftRadius: 2,
829
+ borderTopRightRadius: 2,
830
+ backgroundColor: slotColor(slot),
831
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
832
+ },
833
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
834
+ accessibilityRole: "image",
835
+ accessibilityLabel: slotAccessibilityLabel(slot, i)
836
+ }
837
+ )
838
+ },
839
+ i
840
+ );
841
+ });
380
842
  const stripContent = /* @__PURE__ */ jsxs(
381
843
  Animated.View,
382
844
  {
@@ -389,7 +851,9 @@ function VelocityStrip({
389
851
  borderRadius: expanded ? 6 : 2,
390
852
  paddingTop: expanded ? 16 : 0,
391
853
  paddingBottom: expanded ? 24 : 0,
392
- paddingHorizontal: expanded ? 6 : 0,
854
+ // No horizontal inset the framed chart's bars span full width, matching
855
+ // the bare spotlight and mini. Numbers/info sit in the vertical padding.
856
+ paddingHorizontal: 0,
393
857
  paddingVertical: expanded ? void 0 : 8,
394
858
  overflow: expanded ? "visible" : "hidden"
395
859
  }
@@ -399,46 +863,65 @@ function VelocityStrip({
399
863
  testID: "velocity-strip",
400
864
  ...props,
401
865
  children: [
402
- /* @__PURE__ */ jsx(
403
- View,
404
- {
405
- style: { flexDirection: "row", flex: 1, gap: 2, alignItems: "flex-end" },
406
- children: velocities.map((v, i) => {
407
- const barBackground = barColorFor(v);
408
- const barHeightPct = maxVelocity > 0 ? Math.round(v / (maxVelocity * 1.15) * 100) : 0;
409
- const isLive = liveRepIndex === i;
410
- const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
411
- const barStyle = expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: barBackground } : {
412
- minHeight: "100%",
413
- borderTopLeftRadius: 2,
414
- borderTopRightRadius: 2,
415
- borderBottomLeftRadius: 0,
416
- borderBottomRightRadius: 0,
417
- backgroundColor: barBackground
418
- };
419
- const barInner = isLive ? /* @__PURE__ */ jsx(
420
- Animated.View,
421
- {
422
- style: [barStyle, { transform: [{ scale: liveScale }] }],
423
- testID: `velocity-bar-${i}`,
424
- accessibilityRole: "image",
425
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
426
- }
427
- ) : /* @__PURE__ */ jsx(
428
- View,
429
- {
430
- style: barStyle,
431
- testID: `velocity-bar-${i}`,
432
- accessibilityRole: "image",
433
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
434
- }
435
- );
436
- const bar = /* @__PURE__ */ jsxs(
437
- View,
438
- {
439
- style: { flex: 1, height: "100%", justifyContent: "flex-end", position: "relative" },
440
- children: [
441
- expanded && /* @__PURE__ */ jsx(Animated.View, { style: { opacity: labelOpacity, alignItems: "center", position: "absolute", top: -13, left: 0, right: 0 }, accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(
866
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
867
+ const barBackground = barColorFor(v);
868
+ const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
869
+ const isLive = liveRepIndex === i;
870
+ const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
871
+ const barStyle = expanded ? {
872
+ height: `${barHeightPct}%`,
873
+ minHeight: 2,
874
+ borderTopLeftRadius: 2,
875
+ borderTopRightRadius: 2,
876
+ backgroundColor: barBackground
877
+ } : {
878
+ minHeight: "100%",
879
+ borderTopLeftRadius: 2,
880
+ borderTopRightRadius: 2,
881
+ borderBottomLeftRadius: 0,
882
+ borderBottomRightRadius: 0,
883
+ backgroundColor: barBackground
884
+ };
885
+ const barInner = isLive ? /* @__PURE__ */ jsx(
886
+ Animated.View,
887
+ {
888
+ style: [barStyle, { transform: [{ scale: liveScale }] }],
889
+ testID: `velocity-bar-${i}`,
890
+ accessibilityRole: "image",
891
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
892
+ }
893
+ ) : /* @__PURE__ */ jsx(
894
+ View,
895
+ {
896
+ style: barStyle,
897
+ testID: `velocity-bar-${i}`,
898
+ accessibilityRole: "image",
899
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
900
+ }
901
+ );
902
+ const bar = /* @__PURE__ */ jsxs(
903
+ View,
904
+ {
905
+ style: {
906
+ flex: 1,
907
+ height: "100%",
908
+ justifyContent: "flex-end",
909
+ position: "relative"
910
+ },
911
+ children: [
912
+ expanded && showNumbers && /* @__PURE__ */ jsx(
913
+ Animated.View,
914
+ {
915
+ style: {
916
+ opacity: labelOpacity,
917
+ alignItems: "center",
918
+ position: "absolute",
919
+ top: -13,
920
+ left: 0,
921
+ right: 0
922
+ },
923
+ accessibilityElementsHidden: true,
924
+ children: /* @__PURE__ */ jsx(
442
925
  Text,
443
926
  {
444
927
  className: "text-text-secondary",
@@ -446,30 +929,30 @@ function VelocityStrip({
446
929
  testID: `velocity-label-${i}`,
447
930
  children: formatVelocity(v)
448
931
  }
449
- ) }),
450
- barInner
451
- ]
452
- },
453
- i
454
- );
455
- if (onRepPress && expanded) {
456
- return /* @__PURE__ */ jsx(
457
- Pressable,
458
- {
459
- style: { flex: 1, height: "100%" },
460
- onPress: () => onRepPress(i, v),
461
- accessibilityRole: "button",
462
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
463
- testID: `velocity-bar-pressable-${i}`,
464
- children: bar
465
- },
466
- i
467
- );
468
- }
469
- return bar;
470
- })
932
+ )
933
+ }
934
+ ),
935
+ barInner
936
+ ]
937
+ },
938
+ i
939
+ );
940
+ if (onRepPress && expanded) {
941
+ return /* @__PURE__ */ jsx(
942
+ Pressable,
943
+ {
944
+ style: { flex: 1, height: "100%" },
945
+ onPress: () => onRepPress(i, v),
946
+ accessibilityRole: "button",
947
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
948
+ testID: `velocity-bar-pressable-${i}`,
949
+ children: bar
950
+ },
951
+ i
952
+ );
471
953
  }
472
- ),
954
+ return bar;
955
+ }) }),
473
956
  expanded && showInfo && /* @__PURE__ */ jsxs(
474
957
  Animated.View,
475
958
  {
@@ -645,17 +1128,49 @@ function SvgIcon({
645
1128
  }
646
1129
 
647
1130
  // src/components/icons/icons.tsx
648
- function DumbbellIcon(props) {
1131
+ function StarIcon(props) {
1132
+ 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" }) });
1133
+ }
1134
+ function ScaleIcon(props) {
649
1135
  return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
650
- /* @__PURE__ */ jsx("path", { d: "M14.4 14.4 9.6 9.6" }),
651
- /* @__PURE__ */ jsx("path", { d: "M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z" }),
652
- /* @__PURE__ */ jsx("path", { d: "m21.5 21.5-1.4-1.4" }),
653
- /* @__PURE__ */ jsx("path", { d: "M3.9 3.9 2.5 2.5" }),
654
- /* @__PURE__ */ jsx("path", { d: "M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z" })
1136
+ /* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
1137
+ /* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
1138
+ /* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
1139
+ /* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
1140
+ /* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
655
1141
  ] });
656
1142
  }
657
- function StarIcon(props) {
658
- return /* @__PURE__ */ jsx(SvgIcon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" }) });
1143
+ function AlertTriangleIcon(props) {
1144
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1145
+ /* @__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" }),
1146
+ /* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
1147
+ /* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
1148
+ ] });
1149
+ }
1150
+ function TrendingDownIcon(props) {
1151
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1152
+ /* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
1153
+ /* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
1154
+ ] });
1155
+ }
1156
+ function CircleSlashIcon(props) {
1157
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1158
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1159
+ /* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
1160
+ ] });
1161
+ }
1162
+ function AwardIcon(props) {
1163
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1164
+ /* @__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" }),
1165
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
1166
+ ] });
1167
+ }
1168
+ function InfoIcon(props) {
1169
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
1170
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1171
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
1172
+ /* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
1173
+ ] });
659
1174
  }
660
1175
  var baseBadgeSizeConfig = {
661
1176
  sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
@@ -758,6 +1273,9 @@ var semanticColorsLight = {
758
1273
  "status-success-light": primitiveRamps.green[200],
759
1274
  "status-success-dark": primitiveRamps.green[600],
760
1275
  "status-success-subtle": primitiveRamps.green[50],
1276
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
1277
+ "status-live": primitiveRamps.green[300],
1278
+ "status-live-muted": primitiveRamps.green[500],
761
1279
  "status-error": primitiveRamps.red[600],
762
1280
  "status-error-light": primitiveRamps.red[500],
763
1281
  "status-error-dark": primitiveRamps.red[700],
@@ -891,6 +1409,9 @@ var semanticColorsDark = {
891
1409
  "status-success-light": primitiveRamps.green[200],
892
1410
  "status-success-dark": primitiveRamps.green[600],
893
1411
  "status-success-subtle": "rgba(46, 213, 115, 0.12)",
1412
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
1413
+ "status-live": primitiveRamps.green[300],
1414
+ "status-live-muted": primitiveRamps.green[500],
894
1415
  "status-error": primitiveRamps.red[600],
895
1416
  "status-error-light": primitiveRamps.red[500],
896
1417
  "status-error-dark": primitiveRamps.red[700],
@@ -995,106 +1516,20 @@ function getSemanticColors(mode) {
995
1516
  return mode === "dark" ? semanticColorsDark : semanticColorsLight;
996
1517
  }
997
1518
 
998
- // src/components/custom/Workout/WeightBadge.tsx
999
- function WeightBadge({
1000
- value,
1001
- unit = "lbs",
1002
- reps,
1003
- delta,
1004
- isPr,
1005
- showIcon = true,
1006
- size = "md",
1007
- onPress,
1008
- className,
1009
- ...props
1010
- }) {
1011
- const config = baseBadgeSizeConfig[size];
1012
- const textColor = isPr ? getSemanticColors("dark")["brand-primary"] : "#9CA3AF";
1013
- const iconColor = textColor;
1014
- const repMaxClause = reps != null ? `${reps} rep max` : "Estimated one rep max";
1015
- const deltaLabel = delta != null ? `, ${delta >= 0 ? "+" : ""}${delta}% change` : "";
1016
- const fullLabel = `${repMaxClause}: ${value} ${unit}${deltaLabel}`;
1017
- return /* @__PURE__ */ jsxs(
1018
- BaseBadge,
1019
- {
1020
- variant: isPr ? "pr" : "plain",
1021
- size,
1022
- onPress,
1023
- className,
1024
- icon: showIcon ? /* @__PURE__ */ jsx(DumbbellIcon, { size: config.iconSize, color: iconColor, strokeWidth: 2 }) : void 0,
1025
- accessibilityLabel: fullLabel,
1026
- testID: "weight-badge",
1027
- ...props,
1028
- children: [
1029
- /* @__PURE__ */ jsxs(
1030
- Text,
1031
- {
1032
- style: {
1033
- fontFamily: '"Space Grotesk", sans-serif',
1034
- fontWeight: "600",
1035
- fontSize: config.fontSize,
1036
- color: textColor
1037
- },
1038
- children: [
1039
- isPr ? "\u2733 " : "",
1040
- value,
1041
- " ",
1042
- unit
1043
- ]
1044
- }
1045
- ),
1046
- reps != null && /* @__PURE__ */ jsxs(
1047
- Text,
1048
- {
1049
- style: {
1050
- fontFamily: '"Space Grotesk", sans-serif',
1051
- fontWeight: "600",
1052
- fontSize: config.fontSize,
1053
- marginLeft: 2,
1054
- color: textColor,
1055
- opacity: 0.7
1056
- },
1057
- testID: "weight-badge-repmax",
1058
- children: [
1059
- reps,
1060
- "RM"
1061
- ]
1062
- }
1063
- ),
1064
- delta != null && /* @__PURE__ */ jsxs(
1065
- Text,
1066
- {
1067
- style: {
1068
- fontFamily: '"Space Grotesk", sans-serif',
1069
- fontSize: config.fontSize,
1070
- marginLeft: 4,
1071
- color: delta >= 0 ? getSemanticColors("dark")["result-improve"] : getSemanticColors("dark")["result-degrade"]
1072
- },
1073
- testID: "weight-badge-delta",
1074
- children: [
1075
- delta >= 0 ? "+" : "",
1076
- delta,
1077
- "%"
1078
- ]
1079
- }
1080
- )
1081
- ]
1082
- }
1083
- );
1084
- }
1085
- var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
1086
- var typeLabels = {
1087
- e1rm: "PR e1RM",
1088
- weight: "PR Weight",
1089
- reps: "PR Reps",
1090
- volume: "PR Volume",
1091
- velocity: "PR Velocity"
1092
- };
1093
- function PrBadge({
1094
- type = "e1rm",
1095
- label: labelProp,
1096
- compact = false,
1097
- animate = true,
1519
+ // src/components/custom/Workout/PrBadge.tsx
1520
+ var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
1521
+ var typeLabels = {
1522
+ e1rm: "PR e1RM",
1523
+ weight: "PR Weight",
1524
+ reps: "PR Reps",
1525
+ volume: "PR Volume",
1526
+ velocity: "PR Velocity"
1527
+ };
1528
+ function PrBadge({
1529
+ type = "e1rm",
1530
+ label: labelProp,
1531
+ compact = false,
1532
+ animate = true,
1098
1533
  className,
1099
1534
  ...props
1100
1535
  }) {
@@ -1165,6 +1600,204 @@ function PrBadge({
1165
1600
  }
1166
1601
  return badge;
1167
1602
  }
1603
+ var DARK = getSemanticColors("dark");
1604
+ var TEXT_ACTIVE = DARK["text-primary"];
1605
+ var TEXT_MUTED = DARK["text-secondary"];
1606
+ var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
1607
+ var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
1608
+ var typeBadge = {
1609
+ fontSize: 10,
1610
+ fontWeight: "700",
1611
+ fontFamily: "Inter, sans-serif",
1612
+ color: WORKOUT_TOKENS.scale.orange,
1613
+ backgroundColor: "rgba(255,165,2,0.12)",
1614
+ paddingVertical: 1,
1615
+ paddingHorizontal: 5,
1616
+ borderRadius: 3,
1617
+ overflow: "hidden"
1618
+ };
1619
+ function displayReps(set) {
1620
+ return set.state === "done" ? set.reps : set.target.reps;
1621
+ }
1622
+ function displayWeight(set) {
1623
+ return set.state === "done" ? set.weight : set.target.weight;
1624
+ }
1625
+ function plannedReps(set) {
1626
+ if (set.state === "done") return set.velocities.length;
1627
+ if (set.state === "live") return set.target.reps;
1628
+ return set.planned ?? set.target.reps;
1629
+ }
1630
+ function accessibilityLabel(set) {
1631
+ const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
1632
+ if (set.state === "live") return `${base}, in progress`;
1633
+ if (set.state === "todo") return `${base}, upcoming`;
1634
+ return base;
1635
+ }
1636
+ function Cell({
1637
+ width,
1638
+ children,
1639
+ testID
1640
+ }) {
1641
+ return /* @__PURE__ */ jsx(View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
1642
+ }
1643
+ function RowStrip({ set }) {
1644
+ const zones = set.velocityZones;
1645
+ if (set.state === "live") {
1646
+ return /* @__PURE__ */ jsx(
1647
+ VelocityStrip,
1648
+ {
1649
+ variant: "expanded",
1650
+ showNumbers: false,
1651
+ showInfo: false,
1652
+ height: 24,
1653
+ scale: "fixed",
1654
+ set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
1655
+ zones
1656
+ }
1657
+ );
1658
+ }
1659
+ const velocities = set.state === "done" ? set.velocities : [];
1660
+ return /* @__PURE__ */ jsx(
1661
+ VelocityStrip,
1662
+ {
1663
+ variant: "mini",
1664
+ set: { type: "straight", velocities, planned: plannedReps(set) },
1665
+ zones
1666
+ }
1667
+ );
1668
+ }
1669
+ function SetRow(set) {
1670
+ const live = set.state === "live";
1671
+ const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
1672
+ const rpe = set.state === "todo" ? null : set.rpe;
1673
+ return /* @__PURE__ */ jsxs(
1674
+ View,
1675
+ {
1676
+ style: { paddingVertical: 6, paddingHorizontal: 8 },
1677
+ accessibilityLabel: accessibilityLabel(set),
1678
+ testID: "set-row",
1679
+ children: [
1680
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
1681
+ /* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
1682
+ /* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
1683
+ /* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
1684
+ /* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
1685
+ ] }),
1686
+ /* @__PURE__ */ jsx(View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
1687
+ ]
1688
+ }
1689
+ );
1690
+ }
1691
+ var PREV_COLUMN = { label: "PREV", width: void 0 };
1692
+ function buildColumns(unit, showPrevious) {
1693
+ return [
1694
+ { label: "SET", width: 36 },
1695
+ ...showPrevious ? [PREV_COLUMN] : [],
1696
+ { label: "REPS", width: 44 },
1697
+ { label: unit.toUpperCase(), width: 56 },
1698
+ { label: "RPE", width: 36 }
1699
+ ];
1700
+ }
1701
+ function SetTableHeader({
1702
+ unit = "lbs",
1703
+ showPrevious = true,
1704
+ testID = "table-header"
1705
+ }) {
1706
+ const columns = buildColumns(unit, showPrevious);
1707
+ return /* @__PURE__ */ jsx(
1708
+ View,
1709
+ {
1710
+ className: "flex-row",
1711
+ style: {
1712
+ paddingVertical: 8,
1713
+ paddingHorizontal: 8,
1714
+ paddingBottom: 4,
1715
+ ...showPrevious ? {} : { justifyContent: "space-between" }
1716
+ },
1717
+ testID,
1718
+ children: columns.map(({ label, width }) => {
1719
+ const isFlex = width === void 0;
1720
+ return /* @__PURE__ */ jsx(
1721
+ View,
1722
+ {
1723
+ style: {
1724
+ ...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
1725
+ alignItems: "center",
1726
+ justifyContent: "center",
1727
+ gap: 8
1728
+ },
1729
+ ...isFlex ? { className: "flex-1" } : {},
1730
+ children: /* @__PURE__ */ jsx(
1731
+ Text,
1732
+ {
1733
+ className: "text-text-tertiary",
1734
+ style: {
1735
+ fontSize: 10,
1736
+ fontWeight: "600",
1737
+ fontFamily: "Inter, sans-serif",
1738
+ textTransform: "uppercase",
1739
+ letterSpacing: 0.8
1740
+ },
1741
+ children: label
1742
+ }
1743
+ )
1744
+ },
1745
+ label
1746
+ );
1747
+ })
1748
+ }
1749
+ );
1750
+ }
1751
+ var METRIC_FONT = "Inter, sans-serif";
1752
+ function MetricCell({ children, color, fontSize = 11 }) {
1753
+ return /* @__PURE__ */ jsx(
1754
+ Text,
1755
+ {
1756
+ style: {
1757
+ fontFamily: METRIC_FONT,
1758
+ fontSize,
1759
+ color,
1760
+ fontWeight: "600",
1761
+ letterSpacing: 1
1762
+ },
1763
+ children
1764
+ }
1765
+ );
1766
+ }
1767
+
1768
+ // src/components/custom/Workout/SetsRepsLoad.tsx
1769
+ var t = getSemanticColors("dark");
1770
+ var TIMES = "\xD7";
1771
+ var AT = "@";
1772
+ function SetsRepsLoad({
1773
+ sets,
1774
+ reps,
1775
+ load,
1776
+ unit = "lb",
1777
+ className,
1778
+ ...props
1779
+ }) {
1780
+ const value = t["text-primary"];
1781
+ const sep = t["text-tertiary"];
1782
+ return /* @__PURE__ */ jsxs(
1783
+ View,
1784
+ {
1785
+ className,
1786
+ style: { flexDirection: "row", alignItems: "baseline" },
1787
+ accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
1788
+ testID: "sets-reps-load",
1789
+ ...props,
1790
+ children: [
1791
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
1792
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
1793
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
1794
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
1795
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
1796
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
1797
+ ]
1798
+ }
1799
+ );
1800
+ }
1168
1801
  function hexToRgb(hex) {
1169
1802
  const cleaned = hex.replace("#", "");
1170
1803
  let r, g, b;
@@ -1474,7 +2107,7 @@ function alpha(color, opacity) {
1474
2107
  }
1475
2108
  return color;
1476
2109
  }
1477
- var t = getSemanticColors("dark");
2110
+ var t2 = getSemanticColors("dark");
1478
2111
  var TEMPO_PACING = {
1479
2112
  behindThresholdPct: 0.15,
1480
2113
  minPhaseDurationMs: 500
@@ -1490,20 +2123,20 @@ function getTempoFillPct(elapsedMs, targetMs) {
1490
2123
  return Math.min(100, elapsedMs / targetMs * 100);
1491
2124
  }
1492
2125
  ({
1493
- concentric: { color: t["status-success"]},
1494
- hold: { color: t["brand-primary"]},
1495
- eccentric: { color: t["status-warning"]}
2126
+ concentric: { color: t2["status-success"]},
2127
+ hold: { color: t2["brand-primary"]},
2128
+ eccentric: { color: t2["status-warning"]}
1496
2129
  });
1497
2130
 
1498
2131
  // src/components/custom/Workout/TempoDisplay.tsx
1499
- var t2 = getSemanticColors("dark");
2132
+ var t3 = getSemanticColors("dark");
1500
2133
  var INTER = "Inter, sans-serif";
1501
2134
  var TEXT_TERTIARY = "#6B7280";
1502
- var STATUS_ERROR = t2["status-error"];
2135
+ var STATUS_ERROR = t3["status-error"];
1503
2136
  var phaseColors = {
1504
- eccentric: t2["status-warning"],
2137
+ eccentric: t3["status-warning"],
1505
2138
  pauseBottom: TEXT_TERTIARY,
1506
- concentric: t2["status-success"],
2139
+ concentric: t3["status-success"],
1507
2140
  pauseTop: TEXT_TERTIARY,
1508
2141
  dash: TEXT_TERTIARY
1509
2142
  };
@@ -1512,34 +2145,10 @@ function TempoValue({
1512
2145
  color,
1513
2146
  fontSize
1514
2147
  }) {
1515
- return /* @__PURE__ */ jsx(
1516
- Text,
1517
- {
1518
- style: {
1519
- fontFamily: INTER,
1520
- fontSize,
1521
- color,
1522
- fontWeight: "600",
1523
- letterSpacing: 1
1524
- },
1525
- children: value
1526
- }
1527
- );
2148
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: value });
1528
2149
  }
1529
2150
  function TempoSeparator({ color, fontSize }) {
1530
- return /* @__PURE__ */ jsx(
1531
- Text,
1532
- {
1533
- style: {
1534
- fontFamily: INTER,
1535
- fontSize,
1536
- color,
1537
- fontWeight: "600",
1538
- letterSpacing: 1
1539
- },
1540
- children: "-"
1541
- }
1542
- );
2151
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: "-" });
1543
2152
  }
1544
2153
  var LIVE_PHASES = [
1545
2154
  { key: "eccentric", color: phaseColors.eccentric },
@@ -1601,6 +2210,7 @@ function TempoDisplay({
1601
2210
  tempo,
1602
2211
  size = "md",
1603
2212
  colored = true,
2213
+ showLabel = true,
1604
2214
  showInfo = true,
1605
2215
  live,
1606
2216
  onPress,
@@ -1634,7 +2244,7 @@ function TempoDisplay({
1634
2244
  },
1635
2245
  ...props,
1636
2246
  children: [
1637
- /* @__PURE__ */ jsx(
2247
+ showLabel && /* @__PURE__ */ jsx(
1638
2248
  Text,
1639
2249
  {
1640
2250
  style: {
@@ -1713,300 +2323,176 @@ function TempoDisplay({
1713
2323
  children: [
1714
2324
  /* @__PURE__ */ jsxs(
1715
2325
  View,
1716
- {
1717
- style: {
1718
- backgroundColor: "#191919",
1719
- borderRadius: 6,
1720
- paddingVertical: 8,
1721
- paddingHorizontal: 12,
1722
- borderWidth: 1,
1723
- borderColor: "#2C2C2C"
1724
- },
1725
- children: [
1726
- /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1727
- "Eccentric: ",
1728
- eccentric,
1729
- "s"
1730
- ] }),
1731
- /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1732
- "Pause (bottom): ",
1733
- pauseBottom,
1734
- "s"
1735
- ] }),
1736
- /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1737
- "Concentric: ",
1738
- concentric,
1739
- "s"
1740
- ] }),
1741
- /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
1742
- "Pause (top): ",
1743
- pauseTop,
1744
- "s"
1745
- ] })
1746
- ]
1747
- }
1748
- ),
1749
- /* @__PURE__ */ jsx(
1750
- View,
1751
- {
1752
- style: {
1753
- width: 0,
1754
- height: 0,
1755
- borderLeftWidth: 5,
1756
- borderRightWidth: 5,
1757
- borderTopWidth: 5,
1758
- borderLeftColor: "transparent",
1759
- borderRightColor: "transparent",
1760
- borderTopColor: "#2C2C2C"
1761
- }
1762
- }
1763
- )
1764
- ]
1765
- }
1766
- )
1767
- ]
1768
- }
1769
- );
1770
- }
1771
- function formatAccessibilityLabel(setNumber, reps, weight, unit) {
1772
- const repsText = reps != null ? `${reps} reps` : "no reps";
1773
- const weightText = weight != null ? `at ${roundWeight(weight)} ${unit}` : "";
1774
- return `Set ${setNumber}: ${repsText}${weightText ? ` ${weightText}` : ""}`;
1775
- }
1776
- function SetRow({
1777
- mode,
1778
- setNumber,
1779
- previous,
1780
- reps,
1781
- weight,
1782
- rpe,
1783
- unit,
1784
- velocities,
1785
- velocityZones,
1786
- velocityLiveRepIndex,
1787
- velocityExpanded,
1788
- onVelocityToggle,
1789
- setType,
1790
- prBadges,
1791
- isNextSet,
1792
- targets
1793
- }) {
1794
- const isActive = mode === "active";
1795
- const isCompleted = mode === "completed";
1796
- const rowStyle = {
1797
- flexDirection: "row",
1798
- alignItems: "center",
1799
- padding: 7,
1800
- paddingHorizontal: 8,
1801
- gap: 8,
1802
- borderRadius: 8
1803
- };
1804
- if (isActive && isNextSet) {
1805
- rowStyle.backgroundColor = "rgba(255,121,0,0.06)";
1806
- rowStyle.borderWidth = 1;
1807
- rowStyle.borderColor = "rgba(255,121,0,0.15)";
1808
- }
1809
- if (isCompleted && !isNextSet) {
1810
- rowStyle.opacity = 0.55;
1811
- }
1812
- return /* @__PURE__ */ jsxs(
1813
- View,
1814
- {
1815
- style: rowStyle,
1816
- accessibilityLabel: formatAccessibilityLabel(
1817
- setNumber,
1818
- reps,
1819
- weight,
1820
- unit
1821
- ),
1822
- testID: "set-row",
1823
- children: [
1824
- /* @__PURE__ */ jsx(
1825
- View,
1826
- {
1827
- style: { width: 36, alignItems: "center", justifyContent: "center" },
1828
- testID: "set-row-set-number",
1829
- children: setType ? /* @__PURE__ */ jsx(
1830
- Text,
1831
- {
1832
- style: {
1833
- fontSize: 10,
1834
- fontWeight: "700",
1835
- fontFamily: "Inter, sans-serif",
1836
- color: WORKOUT_TOKENS.scale.orange,
1837
- backgroundColor: "rgba(255,165,2,0.12)",
1838
- paddingVertical: 1,
1839
- paddingHorizontal: 5,
1840
- borderRadius: 3,
1841
- overflow: "hidden"
1842
- },
1843
- testID: "set-row-type-badge",
1844
- children: setType
1845
- }
1846
- ) : /* @__PURE__ */ jsx(
1847
- Text,
1848
- {
1849
- className: "text-text-secondary",
1850
- style: {
1851
- fontSize: 13,
1852
- fontWeight: "600",
1853
- fontFamily: "Inter, sans-serif"
1854
- },
1855
- children: setNumber
1856
- }
1857
- )
1858
- }
1859
- ),
1860
- /* @__PURE__ */ jsx(
1861
- View,
1862
- {
1863
- className: "flex-1",
1864
- testID: "set-row-previous",
1865
- children: /* @__PURE__ */ jsx(
1866
- Text,
1867
- {
1868
- className: "text-text-secondary",
1869
- style: {
1870
- fontSize: 12,
1871
- fontFamily: "Inter, sans-serif"
1872
- },
1873
- children: previous ? `${previous.reps} x ${roundWeight(previous.weight)}` : "\u2014"
1874
- }
1875
- )
1876
- }
1877
- ),
1878
- /* @__PURE__ */ jsx(
1879
- View,
1880
- {
1881
- style: { width: 44, alignItems: "center", justifyContent: "center" },
1882
- testID: "set-row-reps",
1883
- children: isActive && reps == null && targets ? /* @__PURE__ */ jsx(
1884
- Text,
1885
- {
1886
- className: "text-text-tertiary",
1887
- style: {
1888
- fontSize: 13,
1889
- fontStyle: "italic",
1890
- fontFamily: "Inter, sans-serif"
1891
- },
1892
- testID: "set-row-target-reps",
1893
- children: targets.reps
1894
- }
1895
- ) : /* @__PURE__ */ jsx(
1896
- Text,
1897
- {
1898
- className: reps != null ? "text-text-primary" : "text-text-tertiary",
1899
- style: {
1900
- fontSize: 14,
1901
- fontWeight: "600",
1902
- fontFamily: "Inter, sans-serif"
1903
- },
1904
- children: reps != null ? reps : "\u2014"
1905
- }
1906
- )
1907
- }
1908
- ),
1909
- /* @__PURE__ */ jsx(
1910
- View,
1911
- {
1912
- style: { width: 56, alignItems: "center", justifyContent: "center" },
1913
- testID: "set-row-weight",
1914
- children: isActive && weight == null && targets ? /* @__PURE__ */ jsx(
1915
- Text,
1916
- {
1917
- className: "text-text-tertiary",
1918
- style: {
1919
- fontSize: 13,
1920
- fontStyle: "italic",
1921
- fontFamily: "Inter, sans-serif"
1922
- },
1923
- testID: "set-row-target-weight",
1924
- children: roundWeight(targets.weight)
1925
- }
1926
- ) : /* @__PURE__ */ jsx(
1927
- Text,
1928
- {
1929
- className: weight != null ? "text-text-primary" : "text-text-tertiary",
1930
- style: {
1931
- fontSize: 14,
1932
- fontWeight: "600",
1933
- fontFamily: "Inter, sans-serif"
1934
- },
1935
- children: weight != null ? roundWeight(weight) : "\u2014"
1936
- }
1937
- )
1938
- }
1939
- ),
1940
- /* @__PURE__ */ jsx(
1941
- View,
1942
- {
1943
- style: { width: 36, alignItems: "center", justifyContent: "center" },
1944
- testID: "set-row-rpe",
1945
- children: /* @__PURE__ */ jsx(
1946
- Text,
1947
- {
1948
- className: rpe != null ? void 0 : "text-text-tertiary",
1949
- style: {
1950
- fontSize: 13,
1951
- fontWeight: "600",
1952
- fontFamily: "Inter, sans-serif",
1953
- // rpeColor returns a concrete hex (native-safe); only the tertiary
1954
- // fallback needs the className token.
1955
- color: rpe != null ? rpeColor(rpe) : void 0
1956
- },
1957
- children: rpe != null ? roundRpe(rpe) : "\u2014"
1958
- }
1959
- )
1960
- }
1961
- ),
1962
- prBadges && prBadges.length > 0 && /* @__PURE__ */ jsx(
1963
- View,
1964
- {
1965
- className: "flex-row items-center",
1966
- style: { gap: 4 },
1967
- testID: "set-row-pr-badges",
1968
- children: prBadges.map((badge, i) => /* @__PURE__ */ jsx(
1969
- PrBadge,
1970
- {
1971
- type: badge.type,
1972
- label: badge.label,
1973
- animate: false,
1974
- compact: true
1975
- },
1976
- i
1977
- ))
1978
- }
1979
- ),
1980
- velocities && velocities.length > 0 && /* @__PURE__ */ jsx(
1981
- View,
1982
- {
1983
- style: { position: "absolute", bottom: 0, left: 8, right: 8 },
1984
- testID: "set-row-velocity-strip",
1985
- children: /* @__PURE__ */ jsx(
1986
- VelocityStrip,
1987
- {
1988
- velocities,
1989
- zones: velocityZones,
1990
- liveRepIndex: velocityLiveRepIndex,
1991
- expanded: velocityExpanded,
1992
- onToggle: onVelocityToggle,
1993
- variant: onVelocityToggle ? "full" : "mini"
1994
- }
1995
- )
2326
+ {
2327
+ style: {
2328
+ backgroundColor: "#191919",
2329
+ borderRadius: 6,
2330
+ paddingVertical: 8,
2331
+ paddingHorizontal: 12,
2332
+ borderWidth: 1,
2333
+ borderColor: "#2C2C2C"
2334
+ },
2335
+ children: [
2336
+ /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2337
+ "Eccentric: ",
2338
+ eccentric,
2339
+ "s"
2340
+ ] }),
2341
+ /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2342
+ "Pause (bottom): ",
2343
+ pauseBottom,
2344
+ "s"
2345
+ ] }),
2346
+ /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2347
+ "Concentric: ",
2348
+ concentric,
2349
+ "s"
2350
+ ] }),
2351
+ /* @__PURE__ */ jsxs(Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
2352
+ "Pause (top): ",
2353
+ pauseTop,
2354
+ "s"
2355
+ ] })
2356
+ ]
2357
+ }
2358
+ ),
2359
+ /* @__PURE__ */ jsx(
2360
+ View,
2361
+ {
2362
+ style: {
2363
+ width: 0,
2364
+ height: 0,
2365
+ borderLeftWidth: 5,
2366
+ borderRightWidth: 5,
2367
+ borderTopWidth: 5,
2368
+ borderLeftColor: "transparent",
2369
+ borderRightColor: "transparent",
2370
+ borderTopColor: "#2C2C2C"
2371
+ }
2372
+ }
2373
+ )
2374
+ ]
1996
2375
  }
1997
2376
  )
1998
2377
  ]
1999
2378
  }
2000
2379
  );
2001
2380
  }
2002
- function resolveColor(token, mode = "dark") {
2003
- return Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
2381
+ var t4 = getSemanticColors("dark");
2382
+ var INDICATOR_CONFIG = {
2383
+ imbalance: { Icon: ScaleIcon, color: t4["status-error"], label: "Left/right imbalance" },
2384
+ overshoot: { Icon: AlertTriangleIcon, color: t4["status-error"], label: "Load overshoot" },
2385
+ "velocity-loss": { Icon: TrendingDownIcon, color: t4["status-warning"], label: "Velocity loss" },
2386
+ "missed-reps": { Icon: CircleSlashIcon, color: t4["status-warning"], label: "Missed reps" },
2387
+ pr: { Icon: AwardIcon, color: t4["status-success"], label: "Personal record" },
2388
+ info: { Icon: InfoIcon, color: t4["status-info"], label: "More info" }
2389
+ };
2390
+ function ExerciseIndicator({ kind, onPress, className, ...props }) {
2391
+ const { Icon, color, label } = INDICATOR_CONFIG[kind];
2392
+ const chip = /* @__PURE__ */ jsx(
2393
+ View,
2394
+ {
2395
+ className,
2396
+ style: {
2397
+ width: 16,
2398
+ height: 16,
2399
+ borderRadius: 8,
2400
+ borderWidth: 1,
2401
+ borderColor: color,
2402
+ alignItems: "center",
2403
+ justifyContent: "center"
2404
+ },
2405
+ accessibilityRole: "image",
2406
+ accessibilityLabel: label,
2407
+ testID: "exercise-indicator",
2408
+ ...props,
2409
+ children: /* @__PURE__ */ jsx(Icon, { size: 11, color })
2410
+ }
2411
+ );
2412
+ if (onPress) {
2413
+ return /* @__PURE__ */ jsx(Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, children: chip });
2414
+ }
2415
+ return chip;
2004
2416
  }
2005
2417
 
2006
- // src/components/custom/Workout/ExerciseCard.tsx
2007
- function buildColumnHeaders(unit) {
2008
- return ["SET", "PREV", "REPS", unit.toUpperCase(), "RPE"];
2418
+ // src/components/custom/Workout/ExerciseHeading.tsx
2419
+ function headingLabel(name, sets, reps, load, unit) {
2420
+ return `${name}, ${sets}\xD7${reps} @ ${roundWeight(load)} ${unit}`;
2421
+ }
2422
+ function ExerciseHeading({
2423
+ name,
2424
+ sets,
2425
+ reps,
2426
+ load,
2427
+ unit = "lbs",
2428
+ tempo,
2429
+ indicator,
2430
+ dimmed,
2431
+ onPress
2432
+ }) {
2433
+ return /* @__PURE__ */ jsxs(View, { style: { opacity: dimmed ? 0.55 : 1 }, testID: "exercise-heading", children: [
2434
+ /* @__PURE__ */ jsxs(
2435
+ Pressable,
2436
+ {
2437
+ onPress,
2438
+ accessibilityRole: "button",
2439
+ accessibilityLabel: headingLabel(name, sets, reps, load, unit),
2440
+ className: "flex-row items-center",
2441
+ style: { gap: 8 },
2442
+ testID: "exercise-card-header",
2443
+ children: [
2444
+ /* @__PURE__ */ jsx(
2445
+ Text,
2446
+ {
2447
+ className: "text-text-primary",
2448
+ style: { fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: "700" },
2449
+ testID: "exercise-card-name",
2450
+ children: name
2451
+ }
2452
+ ),
2453
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2454
+ indicator && /* @__PURE__ */ jsx(ExerciseIndicator, { kind: indicator })
2455
+ ]
2456
+ }
2457
+ ),
2458
+ /* @__PURE__ */ jsxs(
2459
+ View,
2460
+ {
2461
+ className: "flex-row items-center",
2462
+ style: { gap: 8, marginTop: 1 },
2463
+ testID: "exercise-card-summary",
2464
+ children: [
2465
+ /* @__PURE__ */ jsx(SetsRepsLoad, { sets, reps, load, unit }),
2466
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2467
+ tempo && /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm", showLabel: false, showInfo: false })
2468
+ ]
2469
+ }
2470
+ )
2471
+ ] });
2472
+ }
2473
+
2474
+ // src/components/custom/Workout/ExerciseCardHeading.tsx
2475
+ function ExerciseCardHeading({
2476
+ setStates,
2477
+ stripHeight = 8,
2478
+ dimmed,
2479
+ testID = "exercise-card",
2480
+ ...heading
2481
+ }) {
2482
+ return /* @__PURE__ */ jsxs(
2483
+ View,
2484
+ {
2485
+ style: { paddingVertical: 9, paddingHorizontal: 12, opacity: dimmed ? 0.55 : 1 },
2486
+ testID,
2487
+ children: [
2488
+ /* @__PURE__ */ jsx(ExerciseHeading, { ...heading }),
2489
+ setStates.length > 0 && /* @__PURE__ */ jsx(View, { style: { marginTop: 7 }, testID: "exercise-card-strip", children: /* @__PURE__ */ jsx(SetStrip, { sets: setStates, height: stripHeight }) })
2490
+ ]
2491
+ }
2492
+ );
2009
2493
  }
2494
+
2495
+ // src/components/custom/Workout/ExerciseCard.tsx
2010
2496
  function getSupersetBorderRadius(position) {
2011
2497
  switch (position) {
2012
2498
  case "first":
@@ -2049,7 +2535,7 @@ function formatSummary(summary) {
2049
2535
  if (!summary) return "";
2050
2536
  return `${summary.sets}\xD7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`;
2051
2537
  }
2052
- function formatAccessibilityLabel2(name, summary, prescription) {
2538
+ function formatAccessibilityLabel(name, summary, prescription) {
2053
2539
  if (summary) return `${name}, ${formatSummary(summary)}`;
2054
2540
  if (prescription) return `${name}, ${prescription}`;
2055
2541
  return name;
@@ -2058,7 +2544,6 @@ function CollapsedCard({
2058
2544
  name,
2059
2545
  onToggle,
2060
2546
  summary,
2061
- e1rm,
2062
2547
  isPR,
2063
2548
  setVelocities,
2064
2549
  velocityZones,
@@ -2077,7 +2562,7 @@ function CollapsedCard({
2077
2562
  onPressIn: () => setPressed(true),
2078
2563
  onPressOut: () => setPressed(false),
2079
2564
  accessibilityRole: "button",
2080
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
2565
+ accessibilityLabel: formatAccessibilityLabel(name, summary, void 0),
2081
2566
  testID: "exercise-card",
2082
2567
  children: /* @__PURE__ */ jsxs(
2083
2568
  View,
@@ -2091,100 +2576,93 @@ function CollapsedCard({
2091
2576
  ...supersetMargin
2092
2577
  },
2093
2578
  children: [
2094
- /* @__PURE__ */ jsxs(
2095
- View,
2096
- {
2097
- className: "flex-row items-center",
2098
- testID: "exercise-card-header",
2099
- children: [
2100
- /* @__PURE__ */ jsx(
2101
- Text,
2102
- {
2103
- className: "text-text-primary",
2104
- style: {
2105
- fontSize: 14,
2106
- fontFamily: '"Space Grotesk", sans-serif',
2107
- fontWeight: "700"
2108
- },
2109
- testID: "exercise-card-name",
2110
- children: name
2111
- }
2112
- ),
2113
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2114
- summary && /* @__PURE__ */ jsx(
2115
- Text,
2116
- {
2117
- className: "text-text-secondary",
2118
- style: {
2119
- fontSize: 12,
2120
- fontFamily: "Inter, sans-serif",
2121
- marginRight: 8
2122
- },
2123
- testID: "exercise-card-summary",
2124
- children: formatSummary(summary)
2125
- }
2126
- ),
2127
- e1rm && /* @__PURE__ */ jsx(
2128
- WeightBadge,
2129
- {
2130
- value: roundWeight(e1rm.value),
2131
- unit: e1rm.unit,
2132
- size: "sm",
2133
- showIcon: false,
2134
- testID: "exercise-card-e1rm"
2135
- }
2136
- ),
2137
- isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2138
- ]
2139
- }
2140
- ),
2141
- (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(
2142
- View,
2143
- {
2144
- className: "flex-row",
2145
- style: { marginTop: 6, gap: 4 },
2146
- testID: "exercise-card-strips",
2147
- children: [
2148
- setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
2149
- VelocityStrip,
2150
- {
2151
- velocities,
2152
- zones: velocityZones,
2153
- variant: "mini",
2154
- testID: `exercise-card-velocity-strip-${i}`
2155
- },
2156
- i
2157
- )),
2158
- Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
2159
- PlaceholderStrip,
2160
- {
2161
- testID: `exercise-card-placeholder-${i}`
2162
- },
2163
- `placeholder-${i}`
2164
- ))
2165
- ]
2166
- }
2167
- )
2579
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", testID: "exercise-card-header", children: [
2580
+ /* @__PURE__ */ jsx(
2581
+ Text,
2582
+ {
2583
+ className: "text-text-primary",
2584
+ style: {
2585
+ fontSize: 14,
2586
+ fontFamily: '"Space Grotesk", sans-serif',
2587
+ fontWeight: "700"
2588
+ },
2589
+ testID: "exercise-card-name",
2590
+ children: name
2591
+ }
2592
+ ),
2593
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2594
+ summary && /* @__PURE__ */ jsx(
2595
+ Text,
2596
+ {
2597
+ className: "text-text-secondary",
2598
+ style: {
2599
+ fontSize: 12,
2600
+ fontFamily: "Inter, sans-serif",
2601
+ marginRight: 8
2602
+ },
2603
+ testID: "exercise-card-summary",
2604
+ children: formatSummary(summary)
2605
+ }
2606
+ ),
2607
+ isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2608
+ ] }),
2609
+ (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(View, { className: "flex-row", style: { marginTop: 6, gap: 4 }, testID: "exercise-card-strips", children: [
2610
+ setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
2611
+ VelocityStrip,
2612
+ {
2613
+ velocities,
2614
+ zones: velocityZones,
2615
+ variant: "mini",
2616
+ testID: `exercise-card-velocity-strip-${i}`
2617
+ },
2618
+ i
2619
+ )),
2620
+ Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
2621
+ PlaceholderStrip,
2622
+ {
2623
+ testID: `exercise-card-placeholder-${i}`
2624
+ },
2625
+ `placeholder-${i}`
2626
+ ))
2627
+ ] })
2168
2628
  ]
2169
2629
  }
2170
2630
  )
2171
2631
  }
2172
2632
  );
2173
2633
  }
2634
+ var DARK2 = getSemanticColors("dark");
2635
+ var BODY_DIVIDER = DARK2["border-subtle"];
2636
+ function deriveHeaderSetStates(sets) {
2637
+ return sets.map((set) => {
2638
+ switch (set.state) {
2639
+ case "live":
2640
+ return { status: "active", velocities: set.velocities, planned: set.target.reps };
2641
+ case "done":
2642
+ return { status: "done", velocities: set.velocities };
2643
+ case "todo":
2644
+ return { status: "todo", planned: set.planned ?? set.target.reps };
2645
+ }
2646
+ });
2647
+ }
2174
2648
  function ExpandedCard({
2175
2649
  name,
2176
2650
  onToggle,
2177
2651
  summary,
2178
- e1rm,
2179
2652
  isPR,
2180
2653
  sets,
2181
2654
  tempo,
2655
+ indicator,
2656
+ setStates,
2657
+ stripHeight = 8,
2658
+ velocityZones,
2182
2659
  supersetPosition
2183
2660
  }) {
2184
2661
  const borderRadius = getSupersetBorderRadius(supersetPosition);
2185
2662
  const supersetMargin = getSupersetMargin(supersetPosition);
2186
2663
  const unit = summary?.unit ?? sets?.[0]?.unit ?? "lbs";
2187
- const columnHeaders = buildColumnHeaders(unit);
2664
+ const headerStates = setStates ?? (sets ? deriveHeaderSetStates(sets) : []);
2665
+ const headerIndicator = indicator ?? (isPR ? "pr" : void 0);
2188
2666
  return /* @__PURE__ */ jsxs(
2189
2667
  View,
2190
2668
  {
@@ -2197,114 +2675,32 @@ function ExpandedCard({
2197
2675
  testID: "exercise-card",
2198
2676
  children: [
2199
2677
  /* @__PURE__ */ jsx(
2200
- Pressable,
2678
+ ExerciseCardHeading,
2201
2679
  {
2680
+ name,
2681
+ sets: summary?.sets ?? sets?.length ?? 0,
2682
+ reps: summary?.reps ?? 0,
2683
+ load: summary?.weight ?? 0,
2684
+ unit,
2685
+ tempo,
2686
+ indicator: headerIndicator,
2687
+ setStates: headerStates,
2688
+ stripHeight,
2202
2689
  onPress: onToggle,
2203
- accessibilityRole: "button",
2204
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
2205
- "aria-expanded": true,
2206
- testID: "exercise-card-header",
2207
- children: /* @__PURE__ */ jsxs(
2208
- View,
2209
- {
2210
- className: "flex-row items-center",
2211
- style: {
2212
- padding: 12,
2213
- paddingHorizontal: 14,
2214
- paddingBottom: 10,
2215
- borderBottomWidth: 1,
2216
- borderBottomColor: resolveColor("border-default")
2217
- },
2218
- children: [
2219
- /* @__PURE__ */ jsx(
2220
- Text,
2221
- {
2222
- className: "text-text-primary",
2223
- style: {
2224
- fontSize: 14,
2225
- fontFamily: '"Space Grotesk", sans-serif',
2226
- fontWeight: "700"
2227
- },
2228
- testID: "exercise-card-name",
2229
- children: name
2230
- }
2231
- ),
2232
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2233
- summary && /* @__PURE__ */ jsx(
2234
- Text,
2235
- {
2236
- className: "text-text-secondary",
2237
- style: {
2238
- fontSize: 12,
2239
- fontFamily: "Inter, sans-serif",
2240
- marginRight: 8
2241
- },
2242
- testID: "exercise-card-summary",
2243
- children: formatSummary(summary)
2244
- }
2245
- ),
2246
- e1rm && /* @__PURE__ */ jsx(
2247
- WeightBadge,
2248
- {
2249
- value: roundWeight(e1rm.value),
2250
- unit: e1rm.unit,
2251
- size: "sm",
2252
- showIcon: false,
2253
- testID: "exercise-card-e1rm"
2254
- }
2255
- ),
2256
- isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
2257
- ]
2258
- }
2259
- )
2690
+ testID: "exercise-card-heading"
2260
2691
  }
2261
2692
  ),
2262
- tempo && /* @__PURE__ */ jsx(View, { style: { marginTop: 8, paddingHorizontal: 14 }, testID: "exercise-card-tempo", children: /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm" }) }),
2263
- /* @__PURE__ */ jsx(
2693
+ /* @__PURE__ */ jsxs(
2264
2694
  View,
2265
2695
  {
2266
- className: "flex-row",
2267
- style: {
2268
- paddingVertical: 8,
2269
- paddingHorizontal: 8,
2270
- paddingBottom: 4
2271
- },
2272
- testID: "exercise-card-column-headers",
2273
- children: columnHeaders.map((header, i) => {
2274
- const widths = [36, void 0, 44, 56, 36];
2275
- const width = widths[i];
2276
- const isFlex = width === void 0;
2277
- return /* @__PURE__ */ jsx(
2278
- View,
2279
- {
2280
- style: {
2281
- ...isFlex ? {} : { width },
2282
- alignItems: "center",
2283
- justifyContent: "center",
2284
- gap: 8
2285
- },
2286
- ...isFlex ? { className: "flex-1" } : {},
2287
- children: /* @__PURE__ */ jsx(
2288
- Text,
2289
- {
2290
- className: "text-text-tertiary",
2291
- style: {
2292
- fontSize: 10,
2293
- fontWeight: "600",
2294
- fontFamily: "Inter, sans-serif",
2295
- textTransform: "uppercase",
2296
- letterSpacing: 0.8
2297
- },
2298
- children: header
2299
- }
2300
- )
2301
- },
2302
- header
2303
- );
2304
- })
2696
+ style: { borderTopWidth: 1, borderTopColor: BODY_DIVIDER, paddingBottom: 6 },
2697
+ testID: "exercise-card-body",
2698
+ children: [
2699
+ /* @__PURE__ */ jsx(SetTableHeader, { unit, showPrevious: false, testID: "exercise-card-column-headers" }),
2700
+ sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((set, i) => /* @__PURE__ */ jsx(SetRow, { ...set, velocityZones: set.velocityZones ?? velocityZones }, i)) })
2701
+ ]
2305
2702
  }
2306
- ),
2307
- sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((setProps, i) => /* @__PURE__ */ jsx(SetRow, { ...setProps }, i)) })
2703
+ )
2308
2704
  ]
2309
2705
  }
2310
2706
  );
@@ -2313,11 +2709,12 @@ function UpcomingCard({
2313
2709
  name,
2314
2710
  prescription,
2315
2711
  previousBest,
2316
- supersetPosition
2712
+ supersetPosition,
2713
+ onToggle
2317
2714
  }) {
2318
2715
  const borderRadius = getSupersetBorderRadius(supersetPosition);
2319
2716
  const supersetMargin = getSupersetMargin(supersetPosition);
2320
- return /* @__PURE__ */ jsx(
2717
+ return /* @__PURE__ */ jsx(Pressable, { onPress: onToggle, accessibilityRole: "button", children: /* @__PURE__ */ jsx(
2321
2718
  View,
2322
2719
  {
2323
2720
  style: {
@@ -2327,7 +2724,7 @@ function UpcomingCard({
2327
2724
  ...borderRadius,
2328
2725
  ...supersetMargin
2329
2726
  },
2330
- accessibilityLabel: formatAccessibilityLabel2(name, void 0, prescription),
2727
+ accessibilityLabel: formatAccessibilityLabel(name, void 0, prescription),
2331
2728
  testID: "exercise-card",
2332
2729
  children: /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", children: [
2333
2730
  /* @__PURE__ */ jsx(
@@ -2335,6 +2732,7 @@ function UpcomingCard({
2335
2732
  {
2336
2733
  className: "text-text-primary",
2337
2734
  style: {
2735
+ flexShrink: 0,
2338
2736
  fontSize: 14,
2339
2737
  fontFamily: '"Space Grotesk", sans-serif',
2340
2738
  fontWeight: "700"
@@ -2347,7 +2745,9 @@ function UpcomingCard({
2347
2745
  Text,
2348
2746
  {
2349
2747
  className: "text-text-secondary",
2748
+ numberOfLines: 1,
2350
2749
  style: {
2750
+ flexShrink: 1,
2351
2751
  fontSize: 12,
2352
2752
  fontFamily: "Inter, sans-serif",
2353
2753
  marginLeft: 8
@@ -2356,12 +2756,14 @@ function UpcomingCard({
2356
2756
  children: prescription
2357
2757
  }
2358
2758
  ),
2359
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
2759
+ /* @__PURE__ */ jsx(View, { className: "flex-1", style: { minWidth: 8 } }),
2360
2760
  previousBest && /* @__PURE__ */ jsx(
2361
2761
  Text,
2362
2762
  {
2363
2763
  className: "text-text-tertiary",
2764
+ numberOfLines: 1,
2364
2765
  style: {
2766
+ flexShrink: 1,
2365
2767
  fontSize: 11,
2366
2768
  fontFamily: "Inter, sans-serif"
2367
2769
  },
@@ -2371,18 +2773,31 @@ function UpcomingCard({
2371
2773
  )
2372
2774
  ] })
2373
2775
  }
2374
- );
2776
+ ) });
2375
2777
  }
2376
- function ExerciseCard(props) {
2377
- switch (props.state) {
2378
- case "collapsed":
2379
- return /* @__PURE__ */ jsx(CollapsedCard, { ...props });
2380
- case "expanded":
2381
- return /* @__PURE__ */ jsx(ExpandedCard, { ...props });
2382
- case "upcoming":
2383
- return /* @__PURE__ */ jsx(UpcomingCard, { ...props });
2384
- }
2778
+ function ExerciseCard({
2779
+ upcoming,
2780
+ expanded,
2781
+ defaultExpanded,
2782
+ onExpandedChange,
2783
+ ...rest
2784
+ }) {
2785
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded ?? false);
2786
+ const isControlled = expanded !== void 0;
2787
+ const isExpanded = isControlled ? expanded : internalExpanded;
2788
+ const onToggle = () => {
2789
+ const next = !isExpanded;
2790
+ onExpandedChange?.(next);
2791
+ if (!isControlled) setInternalExpanded(next);
2792
+ };
2793
+ if (upcoming) return /* @__PURE__ */ jsx(UpcomingCard, { ...rest, onToggle });
2794
+ return isExpanded ? /* @__PURE__ */ jsx(ExpandedCard, { ...rest, onToggle }) : /* @__PURE__ */ jsx(CollapsedCard, { ...rest, onToggle });
2385
2795
  }
2796
+ function resolveColor(token, mode = "dark") {
2797
+ return Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
2798
+ }
2799
+
2800
+ // src/components/custom/Workout/InputBar.tsx
2386
2801
  var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
2387
2802
  var INPUT_CLASSNAME = "bg-surface-raised border-border-strong text-text-primary";
2388
2803
  var inputStyle = {
@@ -2539,6 +2954,45 @@ function InputBar({
2539
2954
  }
2540
2955
  );
2541
2956
  }
2957
+ function formatDuration(ms) {
2958
+ const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
2959
+ const minutes = Math.floor(totalSeconds / 60);
2960
+ const seconds = totalSeconds % 60;
2961
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
2962
+ }
2963
+ function useTimer(options = {}) {
2964
+ const {
2965
+ mode = "up",
2966
+ durationMs,
2967
+ elapsedMs: controlledElapsedMs,
2968
+ running = false,
2969
+ autoTick = false,
2970
+ tickMs = 1e3
2971
+ } = options;
2972
+ const isControlled = controlledElapsedMs != null;
2973
+ const [internalElapsedMs, setInternalElapsedMs] = useState(0);
2974
+ useEffect(() => {
2975
+ if (isControlled || !autoTick || !running) return;
2976
+ const id = setInterval(() => {
2977
+ setInternalElapsedMs((prev) => prev + tickMs);
2978
+ }, tickMs);
2979
+ return () => clearInterval(id);
2980
+ }, [isControlled, autoTick, running, tickMs]);
2981
+ const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs;
2982
+ const hasDuration = durationMs != null && durationMs > 0;
2983
+ const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0;
2984
+ let progress;
2985
+ if (hasDuration) {
2986
+ progress = Math.min(1, Math.max(0, elapsedMs / durationMs));
2987
+ } else {
2988
+ progress = mode === "down" ? 1 : 0;
2989
+ }
2990
+ const label = formatDuration(mode === "down" ? remainingMs : elapsedMs);
2991
+ const done = durationMs != null && elapsedMs >= durationMs;
2992
+ return { elapsedMs, remainingMs, progress, label, done };
2993
+ }
2994
+
2995
+ // src/components/custom/Workout/RestTimer.tsx
2542
2996
  var BRAND_PRIMARY3 = getSemanticColors("dark")["brand-primary"];
2543
2997
  function RestTimer({
2544
2998
  totalSeconds,
@@ -2549,14 +3003,18 @@ function RestTimer({
2549
3003
  visible,
2550
3004
  displayOnly = false
2551
3005
  }) {
2552
- if (!visible) return null;
2553
- const remainingMs = Math.max(0, totalSeconds * 1e3 - elapsedMs);
3006
+ const {
3007
+ remainingMs,
3008
+ progress,
3009
+ label: timeDisplay
3010
+ } = useTimer({
3011
+ mode: "down",
3012
+ durationMs: totalSeconds * 1e3,
3013
+ elapsedMs
3014
+ });
2554
3015
  const remainingSec = Math.ceil(remainingMs / 1e3);
2555
- const minutes = Math.floor(remainingSec / 60);
2556
- const seconds = remainingSec % 60;
2557
- const timeDisplay = `${minutes}:${String(seconds).padStart(2, "0")}`;
2558
- const totalMs = totalSeconds * 1e3;
2559
- const progressPct = totalMs > 0 ? Math.min(100, elapsedMs / totalMs * 100) : 100;
3016
+ const progressPct = progress * 100;
3017
+ if (!visible) return null;
2560
3018
  return /* @__PURE__ */ jsxs(
2561
3019
  View,
2562
3020
  {
@@ -2795,23 +3253,22 @@ function deriveWorkoutProgress(exercises) {
2795
3253
  function findActiveExercise(exercises) {
2796
3254
  return exercises.find((exercise) => exercise.status === "active") ?? null;
2797
3255
  }
2798
- function statusToCardState(status, focused) {
2799
- if (status === "upcoming") return "upcoming";
2800
- return focused ? "expanded" : "collapsed";
3256
+ function isUpcomingExercise(status) {
3257
+ return status === "upcoming";
2801
3258
  }
2802
3259
  function toActiveCardProps(exercise, focused, onToggle, supersetPosition = null, supersetColor) {
2803
- const state = statusToCardState(exercise.status, focused);
3260
+ const upcoming = isUpcomingExercise(exercise.status);
2804
3261
  return {
2805
3262
  name: exercise.name,
2806
- state,
2807
- onToggle,
3263
+ upcoming,
3264
+ expanded: focused,
3265
+ onExpandedChange: onToggle,
2808
3266
  summary: exercise.summary,
2809
- e1rm: exercise.e1rm,
2810
3267
  isPR: exercise.isPR,
2811
3268
  tempo: exercise.tempo,
2812
3269
  setVelocities: exercise.setVelocities,
2813
3270
  totalPlannedSets: exercise.totalPlannedSets,
2814
- sets: state === "expanded" ? exercise.sets : void 0,
3271
+ sets: focused ? exercise.sets : void 0,
2815
3272
  prescription: exercise.prescription,
2816
3273
  previousBest: exercise.previousBest,
2817
3274
  supersetPosition,
@@ -3500,11 +3957,11 @@ function CardContent({ children, className }) {
3500
3957
  function CardFooter({ children, className }) {
3501
3958
  return /* @__PURE__ */ jsx(View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
3502
3959
  }
3503
- var t3 = getSemanticColors("dark");
3960
+ var t5 = getSemanticColors("dark");
3504
3961
  var solidVariantColors = {
3505
- success: t3["status-success"],
3506
- warning: t3["status-warning"],
3507
- error: t3["status-error"],
3962
+ success: t5["status-success"],
3963
+ warning: t5["status-warning"],
3964
+ error: t5["status-error"],
3508
3965
  neutral: "#6B7280"
3509
3966
  };
3510
3967
  var ringVariantStyles = {
@@ -3544,8 +4001,8 @@ var iconColors = {
3544
4001
  warning: "#6B4000",
3545
4002
  error: "#5C1A1A",
3546
4003
  neutral: "#D1D5DB",
3547
- "on-track": t3["status-success"],
3548
- deviation: t3["status-warning"],
4004
+ "on-track": t5["status-success"],
4005
+ deviation: t5["status-warning"],
3549
4006
  future: "#6B7280"
3550
4007
  };
3551
4008
  var sizeDimensions = {
@@ -3631,13 +4088,13 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
3631
4088
  var WORKOUT_PILL_DELOAD = primitiveRamps.magenta[600];
3632
4089
 
3633
4090
  // src/components/custom/Workout/MesoStatusCard.tsx
3634
- var t4 = getSemanticColors("dark");
3635
- var BRAND_PRIMARY5 = t4["brand-primary"];
4091
+ var t6 = getSemanticColors("dark");
4092
+ var BRAND_PRIMARY5 = t6["brand-primary"];
3636
4093
  var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
3637
4094
  var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
3638
- var SUCCESS = t4["status-success"];
3639
- var WARNING = t4["status-warning"];
3640
- var ERROR = t4["status-error"];
4095
+ var SUCCESS = t6["status-success"];
4096
+ var WARNING = t6["status-warning"];
4097
+ var ERROR = t6["status-error"];
3641
4098
  var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY5, BRAND_PRIMARY_LIGHT];
3642
4099
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
3643
4100
  var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)";
@@ -3709,7 +4166,7 @@ function StatusPill({ badge }) {
3709
4166
  }
3710
4167
  );
3711
4168
  }
3712
- function MetricCell({ metric }) {
4169
+ function MetricCell2({ metric }) {
3713
4170
  return /* @__PURE__ */ jsxs(View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
3714
4171
  /* @__PURE__ */ jsx(
3715
4172
  Text,
@@ -3927,7 +4384,7 @@ function MesoStatusCard({
3927
4384
  className: "flex-row flex-wrap",
3928
4385
  style: { gap: 8 },
3929
4386
  testID: "meso-status-card-metrics",
3930
- children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
4387
+ children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell2, { metric }, `${metric.label}-${index}`))
3931
4388
  }
3932
4389
  ),
3933
4390
  gauges.length > 0 && /* @__PURE__ */ jsx(View, { style: { gap: 12 }, testID: "meso-status-card-gauges", children: gauges.map((gauge, index) => /* @__PURE__ */ jsx(Gauge, { gauge }, `${gauge.label}-${index}`)) }),
@@ -4008,8 +4465,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
4008
4465
  var PLOT_LEFT = 26;
4009
4466
  var TOOLTIP_WIDTH = 124;
4010
4467
  function timeOf(dateStr) {
4011
- const t10 = Date.parse(dateStr);
4012
- return Number.isNaN(t10) ? 0 : t10;
4468
+ const t12 = Date.parse(dateStr);
4469
+ return Number.isNaN(t12) ? 0 : t12;
4013
4470
  }
4014
4471
  function formatValue(value) {
4015
4472
  return `${Math.round(value)}`;
@@ -4498,10 +4955,10 @@ function StrengthTrendChart({
4498
4955
  }
4499
4956
  );
4500
4957
  }
4501
- var t5 = getSemanticColors("dark");
4502
- var STATUS_SUCCESS2 = t5["status-success"];
4503
- var STATUS_WARNING2 = t5["status-warning"];
4504
- var STATUS_INFO = t5["status-info"];
4958
+ var t7 = getSemanticColors("dark");
4959
+ var STATUS_SUCCESS2 = t7["status-success"];
4960
+ var STATUS_WARNING2 = t7["status-warning"];
4961
+ var STATUS_INFO = t7["status-info"];
4505
4962
  var DOT_BORDER = "#F3F4F6";
4506
4963
  var BAND_FILL = "rgba(46,213,115,0.1)";
4507
4964
  var BAND_EDGE = "rgba(46,213,115,0.45)";
@@ -4544,13 +5001,13 @@ function interpEdge(pixels, x, key) {
4544
5001
  const a = pixels[i];
4545
5002
  const b = pixels[i + 1];
4546
5003
  if (x >= a.x && x <= b.x) {
4547
- const t10 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
4548
- return a[key] + t10 * (b[key] - a[key]);
5004
+ const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
5005
+ return a[key] + t12 * (b[key] - a[key]);
4549
5006
  }
4550
5007
  }
4551
5008
  return pixels[pixels.length - 1][key];
4552
5009
  }
4553
- function buildColumns(pixels, step) {
5010
+ function buildColumns2(pixels, step) {
4554
5011
  if (pixels.length < 2) return [];
4555
5012
  const start = pixels[0].x;
4556
5013
  const end = pixels[pixels.length - 1].x;
@@ -4662,7 +5119,7 @@ function CapacityBandChart({
4662
5119
  }
4663
5120
  const { toX, toY } = scale;
4664
5121
  const bandPixels = toPixels(band, toX, toY);
4665
- const columns = buildColumns(bandPixels, COLUMN_STEP);
5122
+ const columns = buildColumns2(bandPixels, COLUMN_STEP);
4666
5123
  const topEdge = buildEdges(bandPixels, "yHigh");
4667
5124
  const bottomEdge = buildEdges(bandPixels, "yLow");
4668
5125
  const lastBandPoint = band[band.length - 1];
@@ -4737,8 +5194,8 @@ function CapacityBandChart({
4737
5194
  testID: "capacity-band-chart-reveal",
4738
5195
  children: /* @__PURE__ */ jsxs(View, { style: { width, height, position: "relative" }, children: [
4739
5196
  projection && /* @__PURE__ */ jsxs(View, { testID: "capacity-band-chart-projection", children: [
4740
- renderColumns(buildColumns(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
4741
- renderColumns(buildColumns(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5197
+ renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5198
+ renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
4742
5199
  renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
4743
5200
  renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
4744
5201
  renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
@@ -4899,10 +5356,9 @@ function deriveExerciseStats(entries) {
4899
5356
  function toExerciseCardProps(entry, expanded, onToggle) {
4900
5357
  return {
4901
5358
  name: entry.title,
4902
- state: expanded ? "expanded" : "collapsed",
4903
- onToggle,
5359
+ expanded,
5360
+ onExpandedChange: onToggle,
4904
5361
  summary: entry.summary,
4905
- e1rm: entry.e1rm,
4906
5362
  isPR: entry.isPR,
4907
5363
  tempo: entry.tempo,
4908
5364
  sets: expanded ? entry.sets : void 0
@@ -5446,6 +5902,7 @@ var colorStyles = {
5446
5902
  default: "bg-text-tertiary",
5447
5903
  primary: "bg-brand-primary",
5448
5904
  success: "bg-status-success",
5905
+ live: "bg-status-live",
5449
5906
  error: "bg-status-error",
5450
5907
  warning: "bg-status-warning",
5451
5908
  info: "bg-status-info",
@@ -5502,7 +5959,7 @@ function Indicator({
5502
5959
  colorClass,
5503
5960
  pulseMode === "opacity" && "animate-pulse",
5504
5961
  ring && "border-2 border-background-base",
5505
- glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
5962
+ glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
5506
5963
  glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
5507
5964
  glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
5508
5965
  glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
@@ -5584,7 +6041,7 @@ function Badge({
5584
6041
  }
5585
6042
  );
5586
6043
  }
5587
- var t6 = getSemanticColors("dark");
6044
+ var t8 = getSemanticColors("dark");
5588
6045
  var statusBgColors = {
5589
6046
  completed: "rgba(46,213,115,0.15)",
5590
6047
  current: "rgba(255,121,0,0.15)",
@@ -5602,14 +6059,14 @@ var statusBorderStyles = {
5602
6059
  deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
5603
6060
  };
5604
6061
  var textColors = {
5605
- completed: t6["status-success"],
5606
- current: t6["brand-primary"],
5607
- next: t6["brand-primary"],
6062
+ completed: t8["status-success"],
6063
+ current: t8["brand-primary"],
6064
+ next: t8["brand-primary"],
5608
6065
  upcoming: "#6B7280",
5609
6066
  missed: "rgba(209,67,67,0.7)",
5610
6067
  deload: WORKOUT_PILL_DELOAD
5611
6068
  };
5612
- function usePulse(enabled) {
6069
+ function usePulse2(enabled) {
5613
6070
  const [opacity] = useState(() => new Animated.Value(1));
5614
6071
  useEffect(() => {
5615
6072
  if (!enabled) {
@@ -5647,7 +6104,7 @@ function WorkoutPill({
5647
6104
  ...props
5648
6105
  }) {
5649
6106
  const shouldPulse = pulse ?? status === "current";
5650
- const pulseOpacity = usePulse(shouldPulse);
6107
+ const pulseOpacity = usePulse2(shouldPulse);
5651
6108
  const isCompleted = status === "completed";
5652
6109
  const isMissed = status === "missed";
5653
6110
  const pill = /* @__PURE__ */ jsx(
@@ -5737,19 +6194,19 @@ function WorkoutPill({
5737
6194
  }
5738
6195
  return pill;
5739
6196
  }
5740
- var t7 = getSemanticColors("dark");
6197
+ var t9 = getSemanticColors("dark");
5741
6198
  var TRACK_BG = "#333333";
5742
6199
  var LABEL_COLOR = "#6B7280";
5743
6200
  var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
5744
6201
  var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
5745
6202
  var ZONE = {
5746
- building: t7["status-success"],
6203
+ building: t9["status-success"],
5747
6204
  // below target ramp-up
5748
- approaching: t7["status-warning"],
6205
+ approaching: t9["status-warning"],
5749
6206
  // nearing target
5750
- target: t7["brand-primary"],
6207
+ target: t9["brand-primary"],
5751
6208
  // exactly at target
5752
- over1: t7["status-error"],
6209
+ over1: t9["status-error"],
5753
6210
  // mild over-target
5754
6211
  over2: WORKOUT_TOKENS.intensity.over2,
5755
6212
  // moderate over-target
@@ -6172,13 +6629,13 @@ function MesoCard({
6172
6629
  }
6173
6630
  ) });
6174
6631
  }
6175
- var t8 = getSemanticColors("dark");
6632
+ var t10 = getSemanticColors("dark");
6176
6633
  var dotColorMap = {
6177
6634
  untrained: "#2C2C2C",
6178
- behind: t8["brand-secondary"],
6179
- ontrack: t8["status-success"],
6180
- target: t8["brand-primary"],
6181
- over: t8["status-error"]
6635
+ behind: t10["brand-secondary"],
6636
+ ontrack: t10["status-success"],
6637
+ target: t10["brand-primary"],
6638
+ over: t10["status-error"]
6182
6639
  };
6183
6640
  function MuscleGroupChip({
6184
6641
  name,
@@ -6252,10 +6709,10 @@ function MuscleGroupChip({
6252
6709
  }
6253
6710
 
6254
6711
  // src/components/custom/Workout/WorkoutCard.tsx
6255
- var t9 = getSemanticColors("dark");
6712
+ var t11 = getSemanticColors("dark");
6256
6713
  var COLORS = {
6257
- statusSuccess: t9["status-success"],
6258
- brandPrimary: t9["brand-primary"],
6714
+ statusSuccess: t11["status-success"],
6715
+ brandPrimary: t11["brand-primary"],
6259
6716
  borderDefault: "#1F1F1F",
6260
6717
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
6261
6718
  };
@@ -6539,8 +6996,8 @@ function WeekLevel({ week, onSelectWorkout }) {
6539
6996
  function WorkoutLevel({ workout, expandedExercise, onToggleExercise }) {
6540
6997
  const exercises = workout.exercises.map((exercise, index) => ({
6541
6998
  ...exercise,
6542
- state: exercise.state === "upcoming" ? exercise.state : index === expandedExercise ? "expanded" : "collapsed",
6543
- onToggle: () => onToggleExercise(index)
6999
+ expanded: index === expandedExercise,
7000
+ onExpandedChange: () => onToggleExercise(index)
6544
7001
  }));
6545
7002
  return /* @__PURE__ */ jsx(View, { testID: "program-planning-page-workout-level", children: /* @__PURE__ */ jsx(
6546
7003
  WorkoutCard,
@@ -6669,6 +7126,6 @@ function ProgramPlanningPage({
6669
7126
  );
6670
7127
  }
6671
7128
 
6672
- export { ActiveWorkoutPage, EXERCISE_DETAIL_TABS, ExerciseDetailPage, ProgramPlanningPage, buildBreadcrumbs, countCompletedSets, deriveExerciseStats, deriveNavLevel, deriveWorkoutProgress, findActiveExercise, findMeso, findWeek, findWorkout, groupExercises, statusToCardState, summarizeVbt, toActiveCardProps, toExerciseCardProps, toProgressBarMesos };
7129
+ export { ActiveWorkoutPage, EXERCISE_DETAIL_TABS, ExerciseDetailPage, ProgramPlanningPage, buildBreadcrumbs, countCompletedSets, deriveExerciseStats, deriveNavLevel, deriveWorkoutProgress, findActiveExercise, findMeso, findWeek, findWorkout, groupExercises, isUpcomingExercise, summarizeVbt, toActiveCardProps, toExerciseCardProps, toProgressBarMesos };
6673
7130
  //# sourceMappingURL=pages.mjs.map
6674
7131
  //# sourceMappingURL=pages.mjs.map