@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/index.mjs CHANGED
@@ -76,6 +76,72 @@ function DumbbellIcon(props) {
76
76
  function StarIcon(props) {
77
77
  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" }) });
78
78
  }
79
+ function ActivityIcon(props) {
80
+ return /* @__PURE__ */ jsx(SvgIcon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M22 12h-4l-3 9L9 3l-3 9H2" }) });
81
+ }
82
+ function HistoryIcon(props) {
83
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
84
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
85
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" }),
86
+ /* @__PURE__ */ jsx("path", { d: "M12 7v5l4 2" })
87
+ ] });
88
+ }
89
+ function LayersIcon(props) {
90
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
91
+ /* @__PURE__ */ jsx("path", { d: "m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z" }),
92
+ /* @__PURE__ */ jsx("path", { d: "m6.08 9.5-3.49 1.59a1 1 0 0 0 0 1.81l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59" }),
93
+ /* @__PURE__ */ jsx("path", { d: "m6.08 14.5-3.49 1.59a1 1 0 0 0 0 1.81l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59" })
94
+ ] });
95
+ }
96
+ function PersonStandingIcon(props) {
97
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
98
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "1" }),
99
+ /* @__PURE__ */ jsx("path", { d: "m9 20 3-6 3 6" }),
100
+ /* @__PURE__ */ jsx("path", { d: "m6 8 6 2 6-2" }),
101
+ /* @__PURE__ */ jsx("path", { d: "M12 10v4" })
102
+ ] });
103
+ }
104
+ function ScaleIcon(props) {
105
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
106
+ /* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
107
+ /* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
108
+ /* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
109
+ /* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
110
+ /* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
111
+ ] });
112
+ }
113
+ function AlertTriangleIcon(props) {
114
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
115
+ /* @__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" }),
116
+ /* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
117
+ /* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
118
+ ] });
119
+ }
120
+ function TrendingDownIcon(props) {
121
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
122
+ /* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
123
+ /* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
124
+ ] });
125
+ }
126
+ function CircleSlashIcon(props) {
127
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
128
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
129
+ /* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
130
+ ] });
131
+ }
132
+ function AwardIcon(props) {
133
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
134
+ /* @__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" }),
135
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
136
+ ] });
137
+ }
138
+ function InfoIcon(props) {
139
+ return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
140
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
141
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
142
+ /* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
143
+ ] });
144
+ }
79
145
  function cn(...inputs) {
80
146
  return twMerge(clsx(inputs));
81
147
  }
@@ -174,7 +240,7 @@ function AlertDescription({ children, className }) {
174
240
 
175
241
  // src/theme/tokens/primitives.ts
176
242
  var primitiveColors = {
177
- // Blue scale (primary brand)
243
+ // Blue scale
178
244
  blue: {
179
245
  50: "#EFF6FF",
180
246
  100: "#DBEAFE",
@@ -183,124 +249,36 @@ var primitiveColors = {
183
249
  400: "#60A5FA",
184
250
  500: "#3B82F6",
185
251
  600: "#5048E5",
186
- // Primary main
187
252
  700: "#3832A0",
188
- // Primary dark
189
253
  800: "#1E40AF",
190
254
  900: "#1E3A8A"
191
255
  },
192
- // Green scale (secondary brand)
193
- green: {
194
- 50: "#ECFDF5",
195
- 100: "#D1FAE5",
196
- 200: "#A7F3D0",
197
- 300: "#6EE7B7",
198
- 400: "#3FC79A",
199
- // Secondary light
200
- 500: "#10B981",
201
- // Secondary main
202
- 600: "#059669",
203
- 700: "#0B815A",
204
- // Secondary dark
205
- 800: "#065F46",
206
- 900: "#064E3B"
207
- },
208
- // Teal scale (success)
209
- teal: {
210
- 50: "#F0FDFA",
211
- 100: "#CCFBF1",
212
- 200: "#99F6E4",
213
- 300: "#5EEAD4",
214
- 400: "#43C6B7",
215
- // Success light
216
- 500: "#14B8A6",
217
- // Success main
218
- 600: "#0D9488",
219
- 700: "#0E8074",
220
- // Success dark
221
- 800: "#115E59",
222
- 900: "#134E4A"
223
- },
224
- // Red scale (error)
256
+ // Red scale
225
257
  red: {
226
258
  50: "#FEF2F2",
227
259
  100: "#FEE2E2",
228
260
  200: "#FECACA",
229
261
  300: "#FCA5A5",
230
262
  400: "#DA6868",
231
- // Error light
232
263
  500: "#EF4444",
233
264
  600: "#D14343",
234
- // Error main
235
265
  700: "#922E2E",
236
- // Error dark
237
266
  800: "#991B1B",
238
267
  900: "#7F1D1D"
239
268
  },
240
- // Vivid green scale (live/go success)
241
- greenVivid: {
242
- 50: "#E9FBF0",
243
- 100: "#C8F7DA",
244
- 200: "#9FF0BF",
245
- 300: "#6BE79D",
246
- 400: "#47DE84",
247
- // Success vivid light
248
- 500: "#2ED573",
249
- // Success vivid main
250
- 600: "#22B85F",
251
- 700: "#1A9950",
252
- // Success vivid dark
253
- 800: "#157A40",
254
- 900: "#105C30"
255
- },
256
- // Vivid red scale (alert)
269
+ // Vivid red scale
257
270
  redVivid: {
258
271
  50: "#FFECEE",
259
272
  100: "#FFD6DB",
260
273
  200: "#FFB3BC",
261
274
  300: "#FF8593",
262
275
  400: "#FF6070",
263
- // Error vivid light
264
276
  500: "#FF4757",
265
- // Error vivid main
266
277
  600: "#E63548",
267
278
  700: "#C42539",
268
- // Error vivid dark
269
279
  800: "#9E1C2C",
270
280
  900: "#7A1520"
271
281
  },
272
- // Amber scale (warning)
273
- amber: {
274
- 50: "#FFFBEB",
275
- 100: "#FEF3C7",
276
- 200: "#FDE68A",
277
- 300: "#FCD34D",
278
- 400: "#FFBF4C",
279
- // Warning light
280
- 500: "#FFB020",
281
- // Warning main
282
- 600: "#B27B16",
283
- // Warning dark
284
- 700: "#B45309",
285
- 800: "#92400E",
286
- 900: "#78350F"
287
- },
288
- // Sky/Blue scale (info)
289
- sky: {
290
- 50: "#F0F9FF",
291
- 100: "#E0F2FE",
292
- 200: "#BAE6FD",
293
- 300: "#7DD3FC",
294
- 400: "#64B6F7",
295
- // Info light
296
- 500: "#2196F3",
297
- // Info main
298
- 600: "#0284C7",
299
- 700: "#0B79D0",
300
- // Info dark
301
- 800: "#075985",
302
- 900: "#0C4A6E"
303
- },
304
282
  // Neutral/Gray scale
305
283
  neutral: {
306
284
  50: "#FAFAFA",
@@ -315,18 +293,6 @@ var primitiveColors = {
315
293
  900: "#111827",
316
294
  950: "#030712"
317
295
  },
318
- // Orange scale (accent/brand color)
319
- accent: {
320
- 100: "#FFA860",
321
- 200: "#FF9630",
322
- 300: "#FF8500",
323
- 400: "#FF7900",
324
- 500: "#F56C00",
325
- 600: "#E06D10",
326
- 700: "#D0620C",
327
- 800: "#B75500",
328
- 900: "#A34900"
329
- },
330
296
  // Charcoal scale (dark backgrounds)
331
297
  charcoal: {
332
298
  0: "#6E6E6E",
@@ -341,18 +307,6 @@ var primitiveColors = {
341
307
  800: "#131313",
342
308
  900: "#101010"
343
309
  },
344
- // Steel scale (cool accent)
345
- steel: {
346
- 100: "#8AA4B8",
347
- 200: "#7894A8",
348
- 300: "#678498",
349
- 400: "#557488",
350
- 500: "#406D87",
351
- 600: "#39617A",
352
- 700: "#32556D",
353
- 800: "#243D53",
354
- 900: "#1D3146"
355
- },
356
310
  // Pure colors
357
311
  white: "#FFFFFF",
358
312
  black: "#000000",
@@ -743,6 +697,9 @@ var semanticColorsLight = {
743
697
  "status-success-light": primitiveRamps.green[200],
744
698
  "status-success-dark": primitiveRamps.green[600],
745
699
  "status-success-subtle": primitiveRamps.green[50],
700
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
701
+ "status-live": primitiveRamps.green[300],
702
+ "status-live-muted": primitiveRamps.green[500],
746
703
  "status-error": primitiveRamps.red[600],
747
704
  "status-error-light": primitiveRamps.red[500],
748
705
  "status-error-dark": primitiveRamps.red[700],
@@ -876,6 +833,9 @@ var semanticColorsDark = {
876
833
  "status-success-light": primitiveRamps.green[200],
877
834
  "status-success-dark": primitiveRamps.green[600],
878
835
  "status-success-subtle": "rgba(46, 213, 115, 0.12)",
836
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
837
+ "status-live": primitiveRamps.green[300],
838
+ "status-live-muted": primitiveRamps.green[500],
879
839
  "status-error": primitiveRamps.red[600],
880
840
  "status-error-light": primitiveRamps.red[500],
881
841
  "status-error-dark": primitiveRamps.red[700],
@@ -1470,6 +1430,8 @@ var lightThemeCSSVars = {
1470
1430
  "--color-on-brand-secondary": semanticColorsLight["on-brand-secondary"],
1471
1431
  "--color-status-success": semanticColorsLight["status-success"],
1472
1432
  "--color-status-success-subtle": semanticColorsLight["status-success-subtle"],
1433
+ "--color-status-live": semanticColorsLight["status-live"],
1434
+ "--color-status-live-muted": semanticColorsLight["status-live-muted"],
1473
1435
  "--color-status-error": semanticColorsLight["status-error"],
1474
1436
  "--color-status-error-subtle": semanticColorsLight["status-error-subtle"],
1475
1437
  "--color-status-warning": semanticColorsLight["status-warning"],
@@ -1566,6 +1528,8 @@ var darkThemeCSSVars = {
1566
1528
  "--color-on-brand-secondary": semanticColorsDark["on-brand-secondary"],
1567
1529
  "--color-status-success": semanticColorsDark["status-success"],
1568
1530
  "--color-status-success-subtle": semanticColorsDark["status-success-subtle"],
1531
+ "--color-status-live": semanticColorsDark["status-live"],
1532
+ "--color-status-live-muted": semanticColorsDark["status-live-muted"],
1569
1533
  "--color-status-error": semanticColorsDark["status-error"],
1570
1534
  "--color-status-error-subtle": semanticColorsDark["status-error-subtle"],
1571
1535
  "--color-status-warning": semanticColorsDark["status-warning"],
@@ -2783,6 +2747,7 @@ var colorStyles = {
2783
2747
  default: "bg-text-tertiary",
2784
2748
  primary: "bg-brand-primary",
2785
2749
  success: "bg-status-success",
2750
+ live: "bg-status-live",
2786
2751
  error: "bg-status-error",
2787
2752
  warning: "bg-status-warning",
2788
2753
  info: "bg-status-info",
@@ -2839,7 +2804,7 @@ function Indicator({
2839
2804
  colorClass,
2840
2805
  pulseMode === "opacity" && "animate-pulse",
2841
2806
  ring && "border-2 border-background-base",
2842
- glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
2807
+ glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
2843
2808
  glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
2844
2809
  glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
2845
2810
  glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
@@ -5263,7 +5228,7 @@ function ToastProvider({
5263
5228
  return id;
5264
5229
  }, [defaultDuration, maxToasts]);
5265
5230
  const removeToast = useCallback((id) => {
5266
- setToasts((prev) => prev.filter((t12) => t12.id !== id));
5231
+ setToasts((prev) => prev.filter((t18) => t18.id !== id));
5267
5232
  }, []);
5268
5233
  const removeAllToasts = useCallback(() => {
5269
5234
  setToasts([]);
@@ -6132,6 +6097,50 @@ function Pill({
6132
6097
  }
6133
6098
  return /* @__PURE__ */ jsx(View, { className: containerClasses, ...props, children: content });
6134
6099
  }
6100
+ function Tile({
6101
+ label,
6102
+ value,
6103
+ valueColor,
6104
+ align = "center",
6105
+ className,
6106
+ ...props
6107
+ }) {
6108
+ const isCenter = align === "center";
6109
+ return /* @__PURE__ */ jsxs(
6110
+ View,
6111
+ {
6112
+ className: cn(
6113
+ "flex-1 rounded-md bg-surface-raised px-1.5 py-2",
6114
+ isCenter ? "items-center" : "items-start",
6115
+ className
6116
+ ),
6117
+ ...props,
6118
+ children: [
6119
+ /* @__PURE__ */ jsx(
6120
+ Text,
6121
+ {
6122
+ className: cn(
6123
+ "text-[10px] font-bold uppercase tracking-wider text-text-tertiary",
6124
+ isCenter ? "text-center" : "text-left"
6125
+ ),
6126
+ children: label
6127
+ }
6128
+ ),
6129
+ /* @__PURE__ */ jsx(
6130
+ Text,
6131
+ {
6132
+ className: cn(
6133
+ "mt-0.5 font-mono text-sm font-bold text-text-primary",
6134
+ isCenter ? "text-center" : "text-left"
6135
+ ),
6136
+ style: valueColor ? { color: valueColor } : void 0,
6137
+ children: value
6138
+ }
6139
+ )
6140
+ ]
6141
+ }
6142
+ );
6143
+ }
6135
6144
  var variantStyles4 = {
6136
6145
  h1: "font-heading text-5xl font-bold leading-tight",
6137
6146
  h2: "font-heading text-4xl font-bold leading-tight",
@@ -6991,6 +7000,79 @@ function formatDateTime(value, format = "datetime", isUTC = false, fallback = "-
6991
7000
  }
6992
7001
  return formatDate(value, format, isUTC);
6993
7002
  }
7003
+ function formatDuration(ms) {
7004
+ const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
7005
+ const minutes = Math.floor(totalSeconds / 60);
7006
+ const seconds = totalSeconds % 60;
7007
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
7008
+ }
7009
+ function useTimer(options = {}) {
7010
+ const {
7011
+ mode = "up",
7012
+ durationMs,
7013
+ elapsedMs: controlledElapsedMs,
7014
+ running = false,
7015
+ autoTick = false,
7016
+ tickMs = 1e3
7017
+ } = options;
7018
+ const isControlled = controlledElapsedMs != null;
7019
+ const [internalElapsedMs, setInternalElapsedMs] = useState(0);
7020
+ useEffect(() => {
7021
+ if (isControlled || !autoTick || !running) return;
7022
+ const id = setInterval(() => {
7023
+ setInternalElapsedMs((prev) => prev + tickMs);
7024
+ }, tickMs);
7025
+ return () => clearInterval(id);
7026
+ }, [isControlled, autoTick, running, tickMs]);
7027
+ const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs;
7028
+ const hasDuration = durationMs != null && durationMs > 0;
7029
+ const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0;
7030
+ let progress;
7031
+ if (hasDuration) {
7032
+ progress = Math.min(1, Math.max(0, elapsedMs / durationMs));
7033
+ } else {
7034
+ progress = mode === "down" ? 1 : 0;
7035
+ }
7036
+ const label = formatDuration(mode === "down" ? remainingMs : elapsedMs);
7037
+ const done = durationMs != null && elapsedMs >= durationMs;
7038
+ return { elapsedMs, remainingMs, progress, label, done };
7039
+ }
7040
+
7041
+ // src/components/custom/TimerReadout/TimerReadout.tsx
7042
+ var semantic = getSemanticColors("dark");
7043
+ var LIVE_GREEN = primitiveRamps.green[500];
7044
+ var SECONDARY = semantic["text-secondary"];
7045
+ var TERTIARY = semantic["text-tertiary"];
7046
+ var DEFAULT_GLYPH = "\u23F1";
7047
+ function TimerReadout({
7048
+ mode = "up",
7049
+ elapsedMs,
7050
+ durationMs,
7051
+ running = false,
7052
+ autoTick = false,
7053
+ showTotal = false,
7054
+ glyph = DEFAULT_GLYPH,
7055
+ className,
7056
+ ...rest
7057
+ }) {
7058
+ const { label } = useTimer({ mode, durationMs, elapsedMs, running, autoTick });
7059
+ const currentColor = running ? LIVE_GREEN : SECONDARY;
7060
+ const total = showTotal && durationMs != null ? formatDuration(durationMs) : null;
7061
+ return /* @__PURE__ */ jsx(
7062
+ View,
7063
+ {
7064
+ accessibilityRole: "timer",
7065
+ accessibilityLabel: total != null ? `${label} of ${total}` : label,
7066
+ className: cn("flex-row items-baseline justify-end", className),
7067
+ ...rest,
7068
+ children: /* @__PURE__ */ jsxs(Typography, { variant: "mono", style: { fontSize: 11, textAlign: "right" }, children: [
7069
+ /* @__PURE__ */ jsx(Text, { testID: "timer-readout-glyph", style: { color: currentColor, marginRight: 3 }, children: glyph }),
7070
+ /* @__PURE__ */ jsx(Text, { testID: "timer-readout-current", style: { color: currentColor }, children: label }),
7071
+ total != null ? /* @__PURE__ */ jsx(Text, { testID: "timer-readout-total", style: { color: TERTIARY }, children: ` / ${total}` }) : null
7072
+ ] })
7073
+ }
7074
+ );
7075
+ }
6994
7076
  var sizeConfig2 = {
6995
7077
  sm: { value: "text-lg font-bold", label: "text-xs", unit: "text-xs" },
6996
7078
  md: { value: "text-2xl font-bold", label: "text-xs", unit: "text-sm" },
@@ -7420,15 +7502,22 @@ var WORKOUT_TOKENS = {
7420
7502
  // Canonical 4-band performance scale — the single source for BOTH the
7421
7503
  // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
7422
7504
  // is intentionally inverted between the two consumers: for velocity, green =
7423
- // fastest/best; for RPE, green = easiest. This is the [0,1,2,4] subsample of
7424
- // the canonical `sequentialEffort` primitive. The `orange` key is the band
7425
- // label. Consumed inline (RN).
7505
+ // fastest/best; for RPE, green = easiest. This is the [0,2,3,4] subsample of
7506
+ // the canonical `sequentialEffort` primitive: an EVEN hue walk across the ramp
7507
+ // (green gold → orange → red). The earlier [0,1,2,4] spent two of its four
7508
+ // bands on adjacent yellows (amber-200 + amber-300) and skipped orange, reading
7509
+ // yellow-heavy with an abrupt jump to red; sampling amber-300 + orange-400
7510
+ // instead gives four cleanly separable bands that hold at 3px strip scale.
7511
+ // Consumed inline (RN).
7426
7512
  scale: {
7427
7513
  green: sequentialEffort[0],
7428
- yellow: sequentialEffort[1],
7429
- orange: sequentialEffort[2],
7430
- // gold band
7514
+ // green-300
7515
+ yellow: sequentialEffort[2],
7516
+ // amber-300 (gold)
7517
+ orange: sequentialEffort[3],
7518
+ // orange-400 (true orange)
7431
7519
  red: sequentialEffort[4]
7520
+ // red-600
7432
7521
  },
7433
7522
  // BodyMap volume heatmap — the canonical `divergingScale` (under → optimal →
7434
7523
  // over): a true diverging shape with a light green center, cool-blue under-
@@ -7553,8 +7642,8 @@ function roundTempo(tempo) {
7553
7642
  return tempo.map((v) => Math.round(v * 10) / 10);
7554
7643
  }
7555
7644
  function formatSignedPct(ratio) {
7556
- const pct = Math.round(ratio * 100);
7557
- return `${pct > 0 ? "+" : ""}${pct}%`;
7645
+ const pct2 = Math.round(ratio * 100);
7646
+ return `${pct2 > 0 ? "+" : ""}${pct2}%`;
7558
7647
  }
7559
7648
  function formatPrescription(p) {
7560
7649
  if (p == null) return null;
@@ -7700,7 +7789,7 @@ function ActiveSegment({
7700
7789
  const pacing = getTempoPacingState(phaseElapsedMs, targetMs);
7701
7790
  const barColor = pacing === "behind" ? t2["status-error"] : config.color;
7702
7791
  const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
7703
- const label = targetMs ? `${formatDuration(phaseElapsedMs)} / ${formatDuration(targetMs)}` : formatDuration(phaseElapsedMs);
7792
+ const label = targetMs ? `${formatDuration2(phaseElapsedMs)} / ${formatDuration2(targetMs)}` : formatDuration2(phaseElapsedMs);
7704
7793
  return /* @__PURE__ */ jsxs(View, { style: { height: "100%", position: "relative" }, testID: `tempo-segment-active-${config.label}`, children: [
7705
7794
  /* @__PURE__ */ jsx(
7706
7795
  View,
@@ -7750,16 +7839,32 @@ function CompletedSegment({
7750
7839
  },
7751
7840
  testID: `tempo-segment-completed-${config.label}`,
7752
7841
  children: /* @__PURE__ */ jsxs(Text, { style: { fontSize: 12, fontWeight: "500", color: alpha(config.color, 0.7) }, children: [
7753
- formatDuration(completedMs),
7842
+ formatDuration2(completedMs),
7754
7843
  indicator
7755
7844
  ] })
7756
7845
  }
7757
7846
  );
7758
7847
  }
7759
- function formatDuration(ms) {
7848
+ function formatDuration2(ms) {
7760
7849
  const s = ms / 1e3;
7761
7850
  return s < 10 ? s.toFixed(1) : Math.round(s).toString();
7762
7851
  }
7852
+ var METRIC_FONT = "Inter, sans-serif";
7853
+ function MetricCell({ children, color, fontSize = 11 }) {
7854
+ return /* @__PURE__ */ jsx(
7855
+ Text,
7856
+ {
7857
+ style: {
7858
+ fontFamily: METRIC_FONT,
7859
+ fontSize,
7860
+ color,
7861
+ fontWeight: "600",
7862
+ letterSpacing: 1
7863
+ },
7864
+ children
7865
+ }
7866
+ );
7867
+ }
7763
7868
 
7764
7869
  // src/components/custom/Workout/TempoDisplay.tsx
7765
7870
  var t3 = getSemanticColors("dark");
@@ -7778,34 +7883,10 @@ function TempoValue({
7778
7883
  color,
7779
7884
  fontSize
7780
7885
  }) {
7781
- return /* @__PURE__ */ jsx(
7782
- Text,
7783
- {
7784
- style: {
7785
- fontFamily: INTER,
7786
- fontSize,
7787
- color,
7788
- fontWeight: "600",
7789
- letterSpacing: 1
7790
- },
7791
- children: value
7792
- }
7793
- );
7886
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: value });
7794
7887
  }
7795
7888
  function TempoSeparator({ color, fontSize }) {
7796
- return /* @__PURE__ */ jsx(
7797
- Text,
7798
- {
7799
- style: {
7800
- fontFamily: INTER,
7801
- fontSize,
7802
- color,
7803
- fontWeight: "600",
7804
- letterSpacing: 1
7805
- },
7806
- children: "-"
7807
- }
7808
- );
7889
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: "-" });
7809
7890
  }
7810
7891
  var LIVE_PHASES = [
7811
7892
  { key: "eccentric", color: phaseColors.eccentric },
@@ -7867,6 +7948,7 @@ function TempoDisplay({
7867
7948
  tempo,
7868
7949
  size = "md",
7869
7950
  colored = true,
7951
+ showLabel = true,
7870
7952
  showInfo = true,
7871
7953
  live,
7872
7954
  onPress,
@@ -7900,7 +7982,7 @@ function TempoDisplay({
7900
7982
  },
7901
7983
  ...props,
7902
7984
  children: [
7903
- /* @__PURE__ */ jsx(
7985
+ showLabel && /* @__PURE__ */ jsx(
7904
7986
  Text,
7905
7987
  {
7906
7988
  style: {
@@ -8128,12 +8210,12 @@ var ZONE = {
8128
8210
  // severe over-target
8129
8211
  };
8130
8212
  var BULGE_SIZE = { 1: 8, 2: 10, 3: 11 };
8131
- function zoneForPct(pct) {
8132
- if (pct >= 125) return { color: ZONE.over3, over: 3 };
8133
- if (pct >= 115) return { color: ZONE.over2, over: 2 };
8134
- if (pct > 100) return { color: ZONE.over1, over: 1 };
8135
- if (pct === 100) return { color: ZONE.target, over: 0 };
8136
- if (pct >= 75) return { color: ZONE.approaching, over: 0 };
8213
+ function zoneForPct(pct2) {
8214
+ if (pct2 >= 125) return { color: ZONE.over3, over: 3 };
8215
+ if (pct2 >= 115) return { color: ZONE.over2, over: 2 };
8216
+ if (pct2 > 100) return { color: ZONE.over1, over: 1 };
8217
+ if (pct2 === 100) return { color: ZONE.target, over: 0 };
8218
+ if (pct2 >= 75) return { color: ZONE.approaching, over: 0 };
8137
8219
  return { color: ZONE.building, over: 0 };
8138
8220
  }
8139
8221
  function clampFill(value) {
@@ -8141,9 +8223,9 @@ function clampFill(value) {
8141
8223
  if (value > 1) return 1;
8142
8224
  return value;
8143
8225
  }
8144
- function isAtTarget(pct, threshold) {
8226
+ function isAtTarget(pct2, threshold) {
8145
8227
  if (threshold == null) return false;
8146
- return Math.abs(pct - Math.round(threshold * 100)) <= 5;
8228
+ return Math.abs(pct2 - Math.round(threshold * 100)) <= 5;
8147
8229
  }
8148
8230
  function IntensityBar({
8149
8231
  level,
@@ -8157,9 +8239,9 @@ function IntensityBar({
8157
8239
  }) {
8158
8240
  const isVertical = orientation === "vertical";
8159
8241
  const fillLevel = clampFill(level);
8160
- const pct = Math.round(Math.max(0, level) * 100);
8161
- const zone = zoneForPct(pct);
8162
- const atTarget = isAtTarget(pct, threshold);
8242
+ const pct2 = Math.round(Math.max(0, level) * 100);
8243
+ const zone = zoneForPct(pct2);
8244
+ const atTarget = isAtTarget(pct2, threshold);
8163
8245
  const [fillAnim] = useState(() => new Animated.Value(fillLevel));
8164
8246
  useEffect(() => {
8165
8247
  const animation = Animated.timing(fillAnim, {
@@ -8210,8 +8292,8 @@ function IntensityBar({
8210
8292
  className,
8211
8293
  style: { alignItems: "center" },
8212
8294
  accessibilityRole: "progressbar",
8213
- accessibilityValue: { min: 0, max: 100, now: pct },
8214
- accessibilityLabel: `Intensity level: ${pct}%`,
8295
+ accessibilityValue: { min: 0, max: 100, now: pct2 },
8296
+ accessibilityLabel: `Intensity level: ${pct2}%`,
8215
8297
  testID: "intensity-bar",
8216
8298
  ...props,
8217
8299
  children: [
@@ -8257,7 +8339,7 @@ function IntensityBar({
8257
8339
  accessibilityElementsHidden: true,
8258
8340
  testID: "intensity-label",
8259
8341
  children: [
8260
- pct,
8342
+ pct2,
8261
8343
  "%"
8262
8344
  ]
8263
8345
  }
@@ -8442,13 +8524,257 @@ function WorkoutPill({
8442
8524
  }
8443
8525
  return pill;
8444
8526
  }
8527
+ var PULSE_HALF_MS = 950;
8528
+ var PULSE_MIN_OPACITY = 0.45;
8529
+ var SEGMENTED_BAR_GAP = 5;
8530
+ function usePulse2(active) {
8531
+ const [value] = useState(() => new Animated.Value(0));
8532
+ useEffect(() => {
8533
+ if (!active) return;
8534
+ const loop = Animated.loop(
8535
+ Animated.sequence([
8536
+ Animated.timing(value, {
8537
+ toValue: 1,
8538
+ duration: PULSE_HALF_MS,
8539
+ easing: Easing.inOut(Easing.ease),
8540
+ useNativeDriver: false
8541
+ }),
8542
+ Animated.timing(value, {
8543
+ toValue: 0,
8544
+ duration: PULSE_HALF_MS,
8545
+ easing: Easing.inOut(Easing.ease),
8546
+ useNativeDriver: false
8547
+ })
8548
+ ])
8549
+ );
8550
+ loop.start();
8551
+ return () => loop.stop();
8552
+ }, [active, value]);
8553
+ return value;
8554
+ }
8555
+ function SegmentedBar({
8556
+ segments,
8557
+ height = 8,
8558
+ gap = SEGMENTED_BAR_GAP,
8559
+ radius = 2,
8560
+ marker = null,
8561
+ segmentTestID,
8562
+ style,
8563
+ ...props
8564
+ }) {
8565
+ const pulse = usePulse2(segments.some((s) => s.pulse));
8566
+ return /* @__PURE__ */ jsxs(View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
8567
+ segments.map((seg, i) => {
8568
+ const fillStyle = {
8569
+ width: `${(seg.fill ?? 1) * 100}%`,
8570
+ height: "100%",
8571
+ backgroundColor: seg.color,
8572
+ opacity: seg.opacity
8573
+ };
8574
+ const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
8575
+ return /* @__PURE__ */ jsx(
8576
+ View,
8577
+ {
8578
+ style: {
8579
+ flex: seg.weight ?? 1,
8580
+ minWidth: 0,
8581
+ height: "100%",
8582
+ marginLeft: seg.leadingGap,
8583
+ borderRadius: radius,
8584
+ overflow: "hidden"
8585
+ },
8586
+ children: seg.pulse ? /* @__PURE__ */ jsx(
8587
+ Animated.View,
8588
+ {
8589
+ style: {
8590
+ ...fillStyle,
8591
+ ...seg.pulseColor ? {
8592
+ backgroundColor: pulse.interpolate({
8593
+ inputRange: [0, 1],
8594
+ outputRange: seg.pulseColor
8595
+ })
8596
+ } : {
8597
+ opacity: pulse.interpolate({
8598
+ inputRange: [0, 1],
8599
+ outputRange: [PULSE_MIN_OPACITY, 1]
8600
+ })
8601
+ }
8602
+ },
8603
+ accessibilityElementsHidden: true,
8604
+ testID
8605
+ }
8606
+ ) : /* @__PURE__ */ jsx(View, { style: fillStyle, accessibilityElementsHidden: true, testID })
8607
+ },
8608
+ i
8609
+ );
8610
+ }),
8611
+ marker ? /* @__PURE__ */ jsx(
8612
+ View,
8613
+ {
8614
+ style: {
8615
+ position: "absolute",
8616
+ left: `${marker.position * 100}%`,
8617
+ top: 0,
8618
+ bottom: 0,
8619
+ width: 2,
8620
+ backgroundColor: marker.color
8621
+ },
8622
+ accessibilityElementsHidden: true,
8623
+ testID: "segmented-bar-marker"
8624
+ }
8625
+ ) : null
8626
+ ] });
8627
+ }
8628
+
8629
+ // src/components/custom/Workout/SetBar.tsx
8630
+ var SET_STRIP_ZONES = {
8631
+ slow: primitiveRamps.red[600],
8632
+ moderate: primitiveRamps.orange[400],
8633
+ fast: primitiveRamps.amber[300],
8634
+ fastest: primitiveRamps.green[300]
8635
+ };
8636
+ var TODO_COLOR = primitiveColors.charcoal[300];
8637
+ var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
8638
+ var NOTCH_GAP = 2;
8639
+ var CLUSTER_GAP = 3;
8640
+ var MYO_UPCOMING_CLUSTERS = 3;
8641
+ var MYO_UPCOMING_CLUSTER_LEN = 5;
8642
+ var MYO_UPCOMING_FADE = 0.6;
8643
+ var PULSE_RANGE = {
8644
+ [SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
8645
+ [SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
8646
+ [SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
8647
+ [SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
8648
+ };
8649
+ function velocityZoneColor(v) {
8650
+ if (v < 0.5) return SET_STRIP_ZONES.slow;
8651
+ if (v < 0.75) return SET_STRIP_ZONES.moderate;
8652
+ if (v < 1) return SET_STRIP_ZONES.fast;
8653
+ return SET_STRIP_ZONES.fastest;
8654
+ }
8655
+ function chunkedSegments(chunks, gap) {
8656
+ return chunks.flatMap(
8657
+ (vels, ci) => vels.map((v, ri) => ({
8658
+ color: velocityZoneColor(v),
8659
+ ...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
8660
+ }))
8661
+ );
8662
+ }
8663
+ function rangeSegments(floor, max, doneVels) {
8664
+ return Array.from({ length: max }, (_, i) => {
8665
+ if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
8666
+ return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
8667
+ });
8668
+ }
8669
+ function myoUpcomingSegments(activationLen, clusterCount) {
8670
+ const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
8671
+ const trail = Array.from(
8672
+ { length: clusterCount },
8673
+ (_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
8674
+ color: SET_STRIP_VARIABLE_COLOR,
8675
+ opacity: MYO_UPCOMING_FADE ** ci,
8676
+ ...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
8677
+ }))
8678
+ ).flat();
8679
+ return [...activation, ...trail];
8680
+ }
8681
+ function setSegments(set) {
8682
+ if (set.status === "todo") {
8683
+ return [{ color: TODO_COLOR }];
8684
+ }
8685
+ if (set.status === "done") {
8686
+ return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
8687
+ }
8688
+ if (set.status === "range") {
8689
+ return rangeSegments(set.floor, set.max, set.doneVels);
8690
+ }
8691
+ if (set.status === "drop") {
8692
+ return chunkedSegments(set.subloads, NOTCH_GAP);
8693
+ }
8694
+ if (set.status === "myo") {
8695
+ return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
8696
+ }
8697
+ if (set.status === "myo-upcoming") {
8698
+ return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
8699
+ }
8700
+ const performed = set.velocities.map((v) => {
8701
+ const color = velocityZoneColor(v);
8702
+ return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
8703
+ });
8704
+ const remaining = Math.max(0, set.planned - set.velocities.length);
8705
+ return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
8706
+ }
8707
+ function setBarSegmentTestID(seg) {
8708
+ if (seg.pulse) return "set-strip-pulse";
8709
+ if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
8710
+ return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
8711
+ }
8712
+ function SetBar({ set, height = 8 }) {
8713
+ return /* @__PURE__ */ jsx(
8714
+ SegmentedBar,
8715
+ {
8716
+ segments: setSegments(set),
8717
+ height,
8718
+ gap: 0,
8719
+ radius: 0,
8720
+ segmentTestID: setBarSegmentTestID,
8721
+ style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
8722
+ testID: "set-strip-set"
8723
+ }
8724
+ );
8725
+ }
8726
+
8727
+ // src/components/custom/Workout/SetStrip.tsx
8728
+ var SET_STRIP_GAP = 5;
8729
+ function setCategory(set) {
8730
+ switch (set.status) {
8731
+ case "done":
8732
+ case "drop":
8733
+ case "myo":
8734
+ return "done";
8735
+ case "active":
8736
+ return "active";
8737
+ case "range":
8738
+ return set.doneVels.length > 0 ? "active" : "upcoming";
8739
+ case "todo":
8740
+ case "myo-upcoming":
8741
+ return "upcoming";
8742
+ }
8743
+ }
8744
+ function describeSets(sets) {
8745
+ const done = sets.filter((s) => setCategory(s) === "done").length;
8746
+ const active = sets.filter((s) => setCategory(s) === "active").length;
8747
+ const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
8748
+ return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
8749
+ }
8750
+ function SetStrip({ sets, height = 8, className, ...props }) {
8751
+ return /* @__PURE__ */ jsx(
8752
+ View,
8753
+ {
8754
+ className,
8755
+ style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
8756
+ accessibilityRole: "image",
8757
+ accessibilityLabel: describeSets(sets),
8758
+ testID: "set-strip",
8759
+ ...props,
8760
+ children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
8761
+ }
8762
+ );
8763
+ }
8764
+
8765
+ // src/components/custom/Workout/VelocityStrip.tsx
8445
8766
  var VEL_COLORS = WORKOUT_TOKENS.scale;
8446
- var zoneIdToScaleToken = {
8447
- speed: "green",
8448
- power: "yellow",
8449
- strengthSpeed: "orange",
8450
- maximalStrength: "red",
8451
- grinding: "red"
8767
+ var bandIdToEffortColor = {
8768
+ speed: sequentialEffort[0],
8769
+ // green-300
8770
+ power: sequentialEffort[2],
8771
+ // amber-300 (gold)
8772
+ strengthSpeed: sequentialEffort[3],
8773
+ // orange-400
8774
+ maximalStrength: sequentialEffort[4],
8775
+ // red-600
8776
+ grinding: sequentialEffort[5]
8777
+ // red-700 — the distinct 5th band
8452
8778
  };
8453
8779
  function getVelocityZoneColor(velocity) {
8454
8780
  if (velocity >= 1) return "vel-green";
@@ -8487,14 +8813,132 @@ function classifyBand(velocity, bands) {
8487
8813
  return bands[bands.length - 1];
8488
8814
  }
8489
8815
  function bandColor(band) {
8490
- const token = zoneIdToScaleToken[band.id];
8491
- return token ? VEL_COLORS[token] : VEL_COLORS.green;
8816
+ return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
8492
8817
  }
8493
8818
  function getLossStyle(loss) {
8494
8819
  if (loss > 25) return { color: VEL_COLORS.red };
8495
8820
  if (loss > 20) return { color: VEL_COLORS.orange };
8496
8821
  return null;
8497
8822
  }
8823
+ var REP_GAP = 2;
8824
+ var WIDE_GAP = 8;
8825
+ var TODO_COLOR2 = primitiveColors.charcoal[300];
8826
+ var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
8827
+ function deriveDoneVelocities(set) {
8828
+ switch (set.type) {
8829
+ case "drop":
8830
+ return set.subloads.flat();
8831
+ case "myo":
8832
+ return [...set.activation, ...set.clusters.flat()];
8833
+ default:
8834
+ return set.velocities;
8835
+ }
8836
+ }
8837
+ function chunkedRepSlots(chunks) {
8838
+ const slots = [];
8839
+ chunks.forEach((chunk) => {
8840
+ chunk.forEach((velocity, ri) => {
8841
+ const first = slots.length === 0;
8842
+ slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP });
8843
+ });
8844
+ });
8845
+ return slots;
8846
+ }
8847
+ function buildSlots(set) {
8848
+ switch (set.type) {
8849
+ case "straight": {
8850
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
8851
+ return Array.from({ length: total }, (_, i) => {
8852
+ const done = i < set.velocities.length;
8853
+ return {
8854
+ kind: done ? "rep" : "todo",
8855
+ velocity: done ? set.velocities[i] : void 0,
8856
+ leadingGap: i === 0 ? 0 : REP_GAP
8857
+ };
8858
+ });
8859
+ }
8860
+ case "range": {
8861
+ const total = Math.max(set.max, set.velocities.length);
8862
+ return Array.from({ length: total }, (_, i) => {
8863
+ const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
8864
+ return {
8865
+ kind,
8866
+ velocity: kind === "rep" ? set.velocities[i] : void 0,
8867
+ leadingGap: i === 0 ? 0 : REP_GAP
8868
+ };
8869
+ });
8870
+ }
8871
+ case "amrap": {
8872
+ const reps = set.velocities.map((velocity, i) => ({
8873
+ kind: "rep",
8874
+ velocity,
8875
+ leadingGap: i === 0 ? 0 : REP_GAP
8876
+ }));
8877
+ reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP });
8878
+ return reps;
8879
+ }
8880
+ case "drop":
8881
+ return chunkedRepSlots(set.subloads);
8882
+ case "myo": {
8883
+ const slots = chunkedRepSlots([set.activation, ...set.clusters]);
8884
+ if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP });
8885
+ return slots;
8886
+ }
8887
+ case "cluster": {
8888
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
8889
+ return Array.from({ length: total }, (_, i) => {
8890
+ const done = i < set.velocities.length;
8891
+ const boundary = i > 0 && i % set.groupSize === 0;
8892
+ return {
8893
+ kind: done ? "rep" : "todo",
8894
+ velocity: done ? set.velocities[i] : void 0,
8895
+ leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP
8896
+ };
8897
+ });
8898
+ }
8899
+ }
8900
+ }
8901
+ function setAccessibilityLabel(set, repCount) {
8902
+ switch (set.type) {
8903
+ case "straight":
8904
+ return `Velocity strip, straight set, ${repCount} reps`;
8905
+ case "range":
8906
+ return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
8907
+ case "amrap":
8908
+ return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
8909
+ case "drop":
8910
+ return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
8911
+ case "myo":
8912
+ return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
8913
+ case "cluster":
8914
+ return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
8915
+ }
8916
+ }
8917
+ function slotAccessibilityLabel(slot, index) {
8918
+ switch (slot.kind) {
8919
+ case "rep":
8920
+ return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
8921
+ case "todo":
8922
+ return `Rep ${index + 1}: planned`;
8923
+ case "variable":
8924
+ return `Rep ${index + 1}: variable`;
8925
+ case "continue":
8926
+ return "Keep going";
8927
+ }
8928
+ }
8929
+ var EXPANDED_TODO_STUB_PCT = 16;
8930
+ var EXPANDED_ENCODED_PCT = 45;
8931
+ var EXPANDED_HEIGHT = 60;
8932
+ var FIXED_MAX_VELOCITY = 1.15;
8933
+ var BARE_STUB_HEIGHT = 3;
8934
+ function scaleDenominator(scale, maxVelocity) {
8935
+ return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
8936
+ }
8937
+ function bareSlotHeight(slot, height, denom) {
8938
+ if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
8939
+ const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
8940
+ return Math.max(BARE_STUB_HEIGHT, ratio * height);
8941
+ }
8498
8942
  function getReducedMotionPreference() {
8499
8943
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
8500
8944
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
@@ -8516,31 +8960,48 @@ var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
8516
8960
  var POP_EASING = Easing.bezier(0.34, 1.56, 0.64, 1);
8517
8961
  function VelocityStrip({
8518
8962
  velocities,
8963
+ set,
8519
8964
  zones,
8520
8965
  liveRepIndex,
8521
- expanded = false,
8966
+ expanded = true,
8522
8967
  onToggle,
8523
8968
  onRepPress,
8524
- variant = "full",
8969
+ variant = "expanded",
8970
+ height = EXPANDED_HEIGHT,
8971
+ scale = "peak",
8972
+ showNumbers = true,
8525
8973
  showInfo = true,
8526
8974
  className,
8527
8975
  ...props
8528
8976
  }) {
8529
- const maxVelocity = Math.max(...velocities, 0);
8530
- const meanVelocity = calculateMeanVelocity(velocities);
8531
- const loss = calculateVelocityLoss(velocities);
8977
+ const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
8978
+ const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
8979
+ kind: "rep",
8980
+ velocity: v,
8981
+ leadingGap: i === 0 ? 0 : REP_GAP
8982
+ }));
8983
+ const maxVelocity = Math.max(...doneVelocities, 0);
8984
+ const meanVelocity = calculateMeanVelocity(doneVelocities);
8985
+ const loss = calculateVelocityLoss(doneVelocities);
8986
+ const framed = showNumbers || showInfo;
8987
+ const scaleDenom = scaleDenominator(scale, maxVelocity);
8532
8988
  const hasZones = zones != null && zones.length > 0;
8533
8989
  const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
8990
+ const slotColor = (slot) => {
8991
+ if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
8992
+ if (slot.kind === "todo") return TODO_COLOR2;
8993
+ return SET_STRIP_VARIABLE_COLOR;
8994
+ };
8534
8995
  const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
8535
8996
  const prefersReducedMotion = usePrefersReducedMotion();
8536
- const [heightAnim] = useState(() => new Animated.Value(expanded ? 60 : 3));
8997
+ const [heightAnim] = useState(() => new Animated.Value(expanded ? height : 3));
8537
8998
  const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
8538
8999
  const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
8539
9000
  const [liveScale] = useState(() => new Animated.Value(1));
8540
- const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
9001
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
8541
9002
  const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
8542
9003
  useEffect(() => {
8543
- if (variant === "mini" || liveRepIndex == null || liveVelocity == null) return;
9004
+ if (variant !== "expanded" || !framed || liveRepIndex == null || liveVelocity == null) return;
8544
9005
  if (prefersReducedMotion) {
8545
9006
  liveScale.setValue(1);
8546
9007
  return;
@@ -8570,12 +9031,12 @@ function VelocityStrip({
8570
9031
  useNativeDriver: true
8571
9032
  }).start();
8572
9033
  }
8573
- }, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
9034
+ }, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
8574
9035
  useEffect(() => {
8575
- if (variant === "mini") return;
9036
+ if (variant !== "expanded" || !framed) return;
8576
9037
  Animated.parallel([
8577
9038
  Animated.timing(heightAnim, {
8578
- toValue: expanded ? 60 : 3,
9039
+ toValue: expanded ? height : 3,
8579
9040
  duration: ANIMATION_DURATION,
8580
9041
  easing: ANIMATION_EASING,
8581
9042
  useNativeDriver: false
@@ -8593,52 +9054,133 @@ function VelocityStrip({
8593
9054
  useNativeDriver: false
8594
9055
  })
8595
9056
  ]).start();
8596
- }, [expanded, variant, heightAnim, labelOpacity, infoOpacity]);
9057
+ }, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
9058
+ if (set == null && velocities == null) return null;
9059
+ const repCount = doneVelocities.length;
9060
+ const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
8597
9061
  if (variant === "mini") {
8598
9062
  const { style: externalStyle, ...restProps } = props;
8599
9063
  return /* @__PURE__ */ jsx(
8600
9064
  View,
8601
9065
  {
8602
9066
  className,
8603
- style: [{ flexDirection: "row", height: 3, gap: 2, borderRadius: 2, overflow: "hidden" }, externalStyle],
9067
+ style: [
9068
+ { flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
9069
+ externalStyle
9070
+ ],
8604
9071
  accessibilityRole: "image",
8605
- accessibilityLabel: `Velocity strip, ${velocities.length} reps`,
9072
+ accessibilityLabel: miniLabel,
8606
9073
  testID: "velocity-strip-mini",
8607
9074
  ...restProps,
8608
- children: velocities.map((v, i) => /* @__PURE__ */ jsx(
9075
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
8609
9076
  View,
8610
9077
  {
8611
9078
  style: {
8612
9079
  flex: 1,
8613
- backgroundColor: barColorFor(v),
9080
+ backgroundColor: slotColor(slot),
8614
9081
  borderRadius: 1,
8615
9082
  minWidth: 4,
8616
- height: "100%"
9083
+ height: "100%",
9084
+ // The container's uniform 2px gap covers rep spacing; a wide slot adds
9085
+ // only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
9086
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
9087
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
9088
+ },
9089
+ accessibilityElementsHidden: true,
9090
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
9091
+ },
9092
+ i
9093
+ ))
9094
+ }
9095
+ );
9096
+ }
9097
+ if (!framed) {
9098
+ const { style: externalStyle, ...restProps } = props;
9099
+ return /* @__PURE__ */ jsx(
9100
+ View,
9101
+ {
9102
+ className,
9103
+ style: [
9104
+ { flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
9105
+ externalStyle
9106
+ ],
9107
+ accessibilityRole: "image",
9108
+ accessibilityLabel: miniLabel,
9109
+ testID: "velocity-strip-compact",
9110
+ ...restProps,
9111
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
9112
+ View,
9113
+ {
9114
+ style: {
9115
+ flex: 1,
9116
+ minWidth: 4,
9117
+ height: bareSlotHeight(slot, height, scaleDenom),
9118
+ // Top-rounded to match the framed chart's bars (the "rounded tops from
9119
+ // the numbers one"); square bottoms since bars sit on the baseline.
9120
+ borderTopLeftRadius: 2,
9121
+ borderTopRightRadius: 2,
9122
+ backgroundColor: slotColor(slot),
9123
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
9124
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
8617
9125
  },
8618
9126
  accessibilityElementsHidden: true,
8619
- testID: `velocity-bar-${i}`
9127
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
8620
9128
  },
8621
9129
  i
8622
9130
  ))
8623
9131
  }
8624
9132
  );
8625
9133
  }
8626
- const stripLabel = `Velocity chart for set, ${velocities.length} reps, tap to ${expanded ? "collapse" : "expand"}`;
9134
+ const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
8627
9135
  const hasInteractiveContainer = onToggle != null;
8628
9136
  const hasInteractiveReps = onRepPress != null && expanded;
8629
- const stripContent = /* @__PURE__ */ jsxs(
8630
- Animated.View,
8631
- {
8632
- className: [className, expanded ? "bg-surface-raised" : "bg-transparent"].filter(Boolean).join(" "),
8633
- style: [
8634
- {
8635
- height: heightAnim,
8636
- width: "100%",
8637
- gap: 2,
9137
+ const setBars = set != null && slots.map((slot, i) => {
9138
+ const isStraightRep = set.type === "straight" && slot.kind === "rep";
9139
+ const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
9140
+ return /* @__PURE__ */ jsx(
9141
+ View,
9142
+ {
9143
+ style: {
9144
+ flex: 1,
9145
+ height: "100%",
9146
+ justifyContent: "flex-end",
9147
+ marginLeft: slot.leadingGap
9148
+ },
9149
+ children: /* @__PURE__ */ jsx(
9150
+ View,
9151
+ {
9152
+ style: {
9153
+ height: `${heightPct}%`,
9154
+ minHeight: 2,
9155
+ borderTopLeftRadius: 2,
9156
+ borderTopRightRadius: 2,
9157
+ backgroundColor: slotColor(slot),
9158
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
9159
+ },
9160
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
9161
+ accessibilityRole: "image",
9162
+ accessibilityLabel: slotAccessibilityLabel(slot, i)
9163
+ }
9164
+ )
9165
+ },
9166
+ i
9167
+ );
9168
+ });
9169
+ const stripContent = /* @__PURE__ */ jsxs(
9170
+ Animated.View,
9171
+ {
9172
+ className: [className, expanded ? "bg-surface-raised" : "bg-transparent"].filter(Boolean).join(" "),
9173
+ style: [
9174
+ {
9175
+ height: heightAnim,
9176
+ width: "100%",
9177
+ gap: 2,
8638
9178
  borderRadius: expanded ? 6 : 2,
8639
9179
  paddingTop: expanded ? 16 : 0,
8640
9180
  paddingBottom: expanded ? 24 : 0,
8641
- paddingHorizontal: expanded ? 6 : 0,
9181
+ // No horizontal inset the framed chart's bars span full width, matching
9182
+ // the bare spotlight and mini. Numbers/info sit in the vertical padding.
9183
+ paddingHorizontal: 0,
8642
9184
  paddingVertical: expanded ? void 0 : 8,
8643
9185
  overflow: expanded ? "visible" : "hidden"
8644
9186
  }
@@ -8648,46 +9190,65 @@ function VelocityStrip({
8648
9190
  testID: "velocity-strip",
8649
9191
  ...props,
8650
9192
  children: [
8651
- /* @__PURE__ */ jsx(
8652
- View,
8653
- {
8654
- style: { flexDirection: "row", flex: 1, gap: 2, alignItems: "flex-end" },
8655
- children: velocities.map((v, i) => {
8656
- const barBackground = barColorFor(v);
8657
- const barHeightPct = maxVelocity > 0 ? Math.round(v / (maxVelocity * 1.15) * 100) : 0;
8658
- const isLive = liveRepIndex === i;
8659
- const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
8660
- const barStyle = expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: barBackground } : {
8661
- minHeight: "100%",
8662
- borderTopLeftRadius: 2,
8663
- borderTopRightRadius: 2,
8664
- borderBottomLeftRadius: 0,
8665
- borderBottomRightRadius: 0,
8666
- backgroundColor: barBackground
8667
- };
8668
- const barInner = isLive ? /* @__PURE__ */ jsx(
8669
- Animated.View,
8670
- {
8671
- style: [barStyle, { transform: [{ scale: liveScale }] }],
8672
- testID: `velocity-bar-${i}`,
8673
- accessibilityRole: "image",
8674
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
8675
- }
8676
- ) : /* @__PURE__ */ jsx(
8677
- View,
8678
- {
8679
- style: barStyle,
8680
- testID: `velocity-bar-${i}`,
8681
- accessibilityRole: "image",
8682
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
8683
- }
8684
- );
8685
- const bar = /* @__PURE__ */ jsxs(
8686
- View,
8687
- {
8688
- style: { flex: 1, height: "100%", justifyContent: "flex-end", position: "relative" },
8689
- children: [
8690
- expanded && /* @__PURE__ */ jsx(Animated.View, { style: { opacity: labelOpacity, alignItems: "center", position: "absolute", top: -13, left: 0, right: 0 }, accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(
9193
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
9194
+ const barBackground = barColorFor(v);
9195
+ const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
9196
+ const isLive = liveRepIndex === i;
9197
+ const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
9198
+ const barStyle = expanded ? {
9199
+ height: `${barHeightPct}%`,
9200
+ minHeight: 2,
9201
+ borderTopLeftRadius: 2,
9202
+ borderTopRightRadius: 2,
9203
+ backgroundColor: barBackground
9204
+ } : {
9205
+ minHeight: "100%",
9206
+ borderTopLeftRadius: 2,
9207
+ borderTopRightRadius: 2,
9208
+ borderBottomLeftRadius: 0,
9209
+ borderBottomRightRadius: 0,
9210
+ backgroundColor: barBackground
9211
+ };
9212
+ const barInner = isLive ? /* @__PURE__ */ jsx(
9213
+ Animated.View,
9214
+ {
9215
+ style: [barStyle, { transform: [{ scale: liveScale }] }],
9216
+ testID: `velocity-bar-${i}`,
9217
+ accessibilityRole: "image",
9218
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
9219
+ }
9220
+ ) : /* @__PURE__ */ jsx(
9221
+ View,
9222
+ {
9223
+ style: barStyle,
9224
+ testID: `velocity-bar-${i}`,
9225
+ accessibilityRole: "image",
9226
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
9227
+ }
9228
+ );
9229
+ const bar = /* @__PURE__ */ jsxs(
9230
+ View,
9231
+ {
9232
+ style: {
9233
+ flex: 1,
9234
+ height: "100%",
9235
+ justifyContent: "flex-end",
9236
+ position: "relative"
9237
+ },
9238
+ children: [
9239
+ expanded && showNumbers && /* @__PURE__ */ jsx(
9240
+ Animated.View,
9241
+ {
9242
+ style: {
9243
+ opacity: labelOpacity,
9244
+ alignItems: "center",
9245
+ position: "absolute",
9246
+ top: -13,
9247
+ left: 0,
9248
+ right: 0
9249
+ },
9250
+ accessibilityElementsHidden: true,
9251
+ children: /* @__PURE__ */ jsx(
8691
9252
  Text,
8692
9253
  {
8693
9254
  className: "text-text-secondary",
@@ -8695,30 +9256,30 @@ function VelocityStrip({
8695
9256
  testID: `velocity-label-${i}`,
8696
9257
  children: formatVelocity(v)
8697
9258
  }
8698
- ) }),
8699
- barInner
8700
- ]
8701
- },
8702
- i
8703
- );
8704
- if (onRepPress && expanded) {
8705
- return /* @__PURE__ */ jsx(
8706
- Pressable,
8707
- {
8708
- style: { flex: 1, height: "100%" },
8709
- onPress: () => onRepPress(i, v),
8710
- accessibilityRole: "button",
8711
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
8712
- testID: `velocity-bar-pressable-${i}`,
8713
- children: bar
8714
- },
8715
- i
8716
- );
8717
- }
8718
- return bar;
8719
- })
9259
+ )
9260
+ }
9261
+ ),
9262
+ barInner
9263
+ ]
9264
+ },
9265
+ i
9266
+ );
9267
+ if (onRepPress && expanded) {
9268
+ return /* @__PURE__ */ jsx(
9269
+ Pressable,
9270
+ {
9271
+ style: { flex: 1, height: "100%" },
9272
+ onPress: () => onRepPress(i, v),
9273
+ accessibilityRole: "button",
9274
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
9275
+ testID: `velocity-bar-pressable-${i}`,
9276
+ children: bar
9277
+ },
9278
+ i
9279
+ );
8720
9280
  }
8721
- ),
9281
+ return bar;
9282
+ }) }),
8722
9283
  expanded && showInfo && /* @__PURE__ */ jsxs(
8723
9284
  Animated.View,
8724
9285
  {
@@ -9016,233 +9577,90 @@ function Sparkline({
9016
9577
  }
9017
9578
  );
9018
9579
  }
9019
- function formatAccessibilityLabel(setNumber, reps, weight, unit) {
9020
- const repsText = reps != null ? `${reps} reps` : "no reps";
9021
- const weightText = weight != null ? `at ${roundWeight(weight)} ${unit}` : "";
9022
- return `Set ${setNumber}: ${repsText}${weightText ? ` ${weightText}` : ""}`;
9580
+ var DARK = getSemanticColors("dark");
9581
+ var TEXT_ACTIVE = DARK["text-primary"];
9582
+ var TEXT_MUTED = DARK["text-secondary"];
9583
+ var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
9584
+ var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
9585
+ var typeBadge = {
9586
+ fontSize: 10,
9587
+ fontWeight: "700",
9588
+ fontFamily: "Inter, sans-serif",
9589
+ color: WORKOUT_TOKENS.scale.orange,
9590
+ backgroundColor: "rgba(255,165,2,0.12)",
9591
+ paddingVertical: 1,
9592
+ paddingHorizontal: 5,
9593
+ borderRadius: 3,
9594
+ overflow: "hidden"
9595
+ };
9596
+ function displayReps(set) {
9597
+ return set.state === "done" ? set.reps : set.target.reps;
9598
+ }
9599
+ function displayWeight(set) {
9600
+ return set.state === "done" ? set.weight : set.target.weight;
9601
+ }
9602
+ function plannedReps(set) {
9603
+ if (set.state === "done") return set.velocities.length;
9604
+ if (set.state === "live") return set.target.reps;
9605
+ return set.planned ?? set.target.reps;
9606
+ }
9607
+ function accessibilityLabel(set) {
9608
+ const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
9609
+ if (set.state === "live") return `${base}, in progress`;
9610
+ if (set.state === "todo") return `${base}, upcoming`;
9611
+ return base;
9612
+ }
9613
+ function Cell({
9614
+ width,
9615
+ children,
9616
+ testID
9617
+ }) {
9618
+ return /* @__PURE__ */ jsx(View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
9023
9619
  }
9024
- function SetRow({
9025
- mode,
9026
- setNumber,
9027
- previous,
9028
- reps,
9029
- weight,
9030
- rpe,
9031
- unit,
9032
- velocities,
9033
- velocityZones,
9034
- velocityLiveRepIndex,
9035
- velocityExpanded,
9036
- onVelocityToggle,
9037
- setType,
9038
- prBadges,
9039
- isNextSet,
9040
- targets
9041
- }) {
9042
- const isActive = mode === "active";
9043
- const isCompleted = mode === "completed";
9044
- const rowStyle = {
9045
- flexDirection: "row",
9046
- alignItems: "center",
9047
- padding: 7,
9048
- paddingHorizontal: 8,
9049
- gap: 8,
9050
- borderRadius: 8
9051
- };
9052
- if (isActive && isNextSet) {
9053
- rowStyle.backgroundColor = "rgba(255,121,0,0.06)";
9054
- rowStyle.borderWidth = 1;
9055
- rowStyle.borderColor = "rgba(255,121,0,0.15)";
9056
- }
9057
- if (isCompleted && !isNextSet) {
9058
- rowStyle.opacity = 0.55;
9620
+ function RowStrip({ set }) {
9621
+ const zones = set.velocityZones;
9622
+ if (set.state === "live") {
9623
+ return /* @__PURE__ */ jsx(
9624
+ VelocityStrip,
9625
+ {
9626
+ variant: "expanded",
9627
+ showNumbers: false,
9628
+ showInfo: false,
9629
+ height: 24,
9630
+ scale: "fixed",
9631
+ set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
9632
+ zones
9633
+ }
9634
+ );
9059
9635
  }
9636
+ const velocities = set.state === "done" ? set.velocities : [];
9637
+ return /* @__PURE__ */ jsx(
9638
+ VelocityStrip,
9639
+ {
9640
+ variant: "mini",
9641
+ set: { type: "straight", velocities, planned: plannedReps(set) },
9642
+ zones
9643
+ }
9644
+ );
9645
+ }
9646
+ function SetRow(set) {
9647
+ const live = set.state === "live";
9648
+ const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
9649
+ const rpe = set.state === "todo" ? null : set.rpe;
9060
9650
  return /* @__PURE__ */ jsxs(
9061
9651
  View,
9062
9652
  {
9063
- style: rowStyle,
9064
- accessibilityLabel: formatAccessibilityLabel(
9065
- setNumber,
9066
- reps,
9067
- weight,
9068
- unit
9069
- ),
9653
+ style: { paddingVertical: 6, paddingHorizontal: 8 },
9654
+ accessibilityLabel: accessibilityLabel(set),
9070
9655
  testID: "set-row",
9071
9656
  children: [
9072
- /* @__PURE__ */ jsx(
9073
- View,
9074
- {
9075
- style: { width: 36, alignItems: "center", justifyContent: "center" },
9076
- testID: "set-row-set-number",
9077
- children: setType ? /* @__PURE__ */ jsx(
9078
- Text,
9079
- {
9080
- style: {
9081
- fontSize: 10,
9082
- fontWeight: "700",
9083
- fontFamily: "Inter, sans-serif",
9084
- color: WORKOUT_TOKENS.scale.orange,
9085
- backgroundColor: "rgba(255,165,2,0.12)",
9086
- paddingVertical: 1,
9087
- paddingHorizontal: 5,
9088
- borderRadius: 3,
9089
- overflow: "hidden"
9090
- },
9091
- testID: "set-row-type-badge",
9092
- children: setType
9093
- }
9094
- ) : /* @__PURE__ */ jsx(
9095
- Text,
9096
- {
9097
- className: "text-text-secondary",
9098
- style: {
9099
- fontSize: 13,
9100
- fontWeight: "600",
9101
- fontFamily: "Inter, sans-serif"
9102
- },
9103
- children: setNumber
9104
- }
9105
- )
9106
- }
9107
- ),
9108
- /* @__PURE__ */ jsx(
9109
- View,
9110
- {
9111
- className: "flex-1",
9112
- testID: "set-row-previous",
9113
- children: /* @__PURE__ */ jsx(
9114
- Text,
9115
- {
9116
- className: "text-text-secondary",
9117
- style: {
9118
- fontSize: 12,
9119
- fontFamily: "Inter, sans-serif"
9120
- },
9121
- children: previous ? `${previous.reps} x ${roundWeight(previous.weight)}` : "\u2014"
9122
- }
9123
- )
9124
- }
9125
- ),
9126
- /* @__PURE__ */ jsx(
9127
- View,
9128
- {
9129
- style: { width: 44, alignItems: "center", justifyContent: "center" },
9130
- testID: "set-row-reps",
9131
- children: isActive && reps == null && targets ? /* @__PURE__ */ jsx(
9132
- Text,
9133
- {
9134
- className: "text-text-tertiary",
9135
- style: {
9136
- fontSize: 13,
9137
- fontStyle: "italic",
9138
- fontFamily: "Inter, sans-serif"
9139
- },
9140
- testID: "set-row-target-reps",
9141
- children: targets.reps
9142
- }
9143
- ) : /* @__PURE__ */ jsx(
9144
- Text,
9145
- {
9146
- className: reps != null ? "text-text-primary" : "text-text-tertiary",
9147
- style: {
9148
- fontSize: 14,
9149
- fontWeight: "600",
9150
- fontFamily: "Inter, sans-serif"
9151
- },
9152
- children: reps != null ? reps : "\u2014"
9153
- }
9154
- )
9155
- }
9156
- ),
9157
- /* @__PURE__ */ jsx(
9158
- View,
9159
- {
9160
- style: { width: 56, alignItems: "center", justifyContent: "center" },
9161
- testID: "set-row-weight",
9162
- children: isActive && weight == null && targets ? /* @__PURE__ */ jsx(
9163
- Text,
9164
- {
9165
- className: "text-text-tertiary",
9166
- style: {
9167
- fontSize: 13,
9168
- fontStyle: "italic",
9169
- fontFamily: "Inter, sans-serif"
9170
- },
9171
- testID: "set-row-target-weight",
9172
- children: roundWeight(targets.weight)
9173
- }
9174
- ) : /* @__PURE__ */ jsx(
9175
- Text,
9176
- {
9177
- className: weight != null ? "text-text-primary" : "text-text-tertiary",
9178
- style: {
9179
- fontSize: 14,
9180
- fontWeight: "600",
9181
- fontFamily: "Inter, sans-serif"
9182
- },
9183
- children: weight != null ? roundWeight(weight) : "\u2014"
9184
- }
9185
- )
9186
- }
9187
- ),
9188
- /* @__PURE__ */ jsx(
9189
- View,
9190
- {
9191
- style: { width: 36, alignItems: "center", justifyContent: "center" },
9192
- testID: "set-row-rpe",
9193
- children: /* @__PURE__ */ jsx(
9194
- Text,
9195
- {
9196
- className: rpe != null ? void 0 : "text-text-tertiary",
9197
- style: {
9198
- fontSize: 13,
9199
- fontWeight: "600",
9200
- fontFamily: "Inter, sans-serif",
9201
- // rpeColor returns a concrete hex (native-safe); only the tertiary
9202
- // fallback needs the className token.
9203
- color: rpe != null ? rpeColor(rpe) : void 0
9204
- },
9205
- children: rpe != null ? roundRpe(rpe) : "\u2014"
9206
- }
9207
- )
9208
- }
9209
- ),
9210
- prBadges && prBadges.length > 0 && /* @__PURE__ */ jsx(
9211
- View,
9212
- {
9213
- className: "flex-row items-center",
9214
- style: { gap: 4 },
9215
- testID: "set-row-pr-badges",
9216
- children: prBadges.map((badge, i) => /* @__PURE__ */ jsx(
9217
- PrBadge,
9218
- {
9219
- type: badge.type,
9220
- label: badge.label,
9221
- animate: false,
9222
- compact: true
9223
- },
9224
- i
9225
- ))
9226
- }
9227
- ),
9228
- velocities && velocities.length > 0 && /* @__PURE__ */ jsx(
9229
- View,
9230
- {
9231
- style: { position: "absolute", bottom: 0, left: 8, right: 8 },
9232
- testID: "set-row-velocity-strip",
9233
- children: /* @__PURE__ */ jsx(
9234
- VelocityStrip,
9235
- {
9236
- velocities,
9237
- zones: velocityZones,
9238
- liveRepIndex: velocityLiveRepIndex,
9239
- expanded: velocityExpanded,
9240
- onToggle: onVelocityToggle,
9241
- variant: onVelocityToggle ? "full" : "mini"
9242
- }
9243
- )
9244
- }
9245
- )
9657
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
9658
+ /* @__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 }) }),
9659
+ /* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
9660
+ /* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
9661
+ /* @__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" }) })
9662
+ ] }),
9663
+ /* @__PURE__ */ jsx(View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
9246
9664
  ]
9247
9665
  }
9248
9666
  );
@@ -9413,14 +9831,18 @@ function RestTimer({
9413
9831
  visible,
9414
9832
  displayOnly = false
9415
9833
  }) {
9416
- if (!visible) return null;
9417
- const remainingMs = Math.max(0, totalSeconds * 1e3 - elapsedMs);
9834
+ const {
9835
+ remainingMs,
9836
+ progress,
9837
+ label: timeDisplay
9838
+ } = useTimer({
9839
+ mode: "down",
9840
+ durationMs: totalSeconds * 1e3,
9841
+ elapsedMs
9842
+ });
9418
9843
  const remainingSec = Math.ceil(remainingMs / 1e3);
9419
- const minutes = Math.floor(remainingSec / 60);
9420
- const seconds = remainingSec % 60;
9421
- const timeDisplay = `${minutes}:${String(seconds).padStart(2, "0")}`;
9422
- const totalMs = totalSeconds * 1e3;
9423
- const progressPct = totalMs > 0 ? Math.min(100, elapsedMs / totalMs * 100) : 100;
9844
+ const progressPct = progress * 100;
9845
+ if (!visible) return null;
9424
9846
  return /* @__PURE__ */ jsxs(
9425
9847
  View,
9426
9848
  {
@@ -9578,15 +10000,319 @@ function RestTimer({
9578
10000
  }
9579
10001
  );
9580
10002
  }
9581
- function buildColumnHeaders(unit) {
9582
- return ["SET", "PREV", "REPS", unit.toUpperCase(), "RPE"];
10003
+ var t8 = getSemanticColors("dark");
10004
+ var TIMES = "\xD7";
10005
+ var AT = "@";
10006
+ function SetsRepsLoad({
10007
+ sets,
10008
+ reps,
10009
+ load,
10010
+ unit = "lb",
10011
+ className,
10012
+ ...props
10013
+ }) {
10014
+ const value = t8["text-primary"];
10015
+ const sep = t8["text-tertiary"];
10016
+ return /* @__PURE__ */ jsxs(
10017
+ View,
10018
+ {
10019
+ className,
10020
+ style: { flexDirection: "row", alignItems: "baseline" },
10021
+ accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
10022
+ testID: "sets-reps-load",
10023
+ ...props,
10024
+ children: [
10025
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
10026
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
10027
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
10028
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
10029
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
10030
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
10031
+ ]
10032
+ }
10033
+ );
9583
10034
  }
9584
- function getSupersetBorderRadius(position) {
9585
- switch (position) {
9586
- case "first":
9587
- return {
9588
- borderTopLeftRadius: 12,
9589
- borderTopRightRadius: 12,
10035
+
10036
+ // src/components/custom/Workout/paceTone.ts
10037
+ function paceTone(progress, target) {
10038
+ if (target === void 0) return "neutral";
10039
+ return progress >= target ? "ahead" : "behind";
10040
+ }
10041
+ var PACE_TONE_COLORS = {
10042
+ ahead: getSemanticColors("dark")["status-success"],
10043
+ behind: getSemanticColors("dark")["status-warning"],
10044
+ neutral: primitiveRamps.cyan[400]
10045
+ };
10046
+ function paceToneColor(tone) {
10047
+ return PACE_TONE_COLORS[tone];
10048
+ }
10049
+
10050
+ // src/components/custom/Workout/SegmentedProgressBar.tsx
10051
+ var DEFAULT_MARKER_COLOR = primitiveRamps.green[500];
10052
+ function SegmentedProgressBar({
10053
+ segments,
10054
+ value,
10055
+ target,
10056
+ markerColor = DEFAULT_MARKER_COLOR,
10057
+ color,
10058
+ height,
10059
+ gap,
10060
+ ...props
10061
+ }) {
10062
+ const total = segments.reduce((sum, s) => sum + s.weight, 0);
10063
+ const fillColor = color ?? paceToneColor(paceTone(total > 0 ? value / total : 0, target));
10064
+ const barSegments = segments.map((seg, i) => {
10065
+ const cumulativeBefore = segments.slice(0, i).reduce((sum, s) => sum + s.weight, 0);
10066
+ const fill = Math.max(0, Math.min(1, (value - cumulativeBefore) / seg.weight));
10067
+ return { weight: seg.weight, fill, color: fillColor };
10068
+ });
10069
+ return /* @__PURE__ */ jsx(
10070
+ SegmentedBar,
10071
+ {
10072
+ segments: barSegments,
10073
+ marker: target != null ? { position: target, color: markerColor } : null,
10074
+ height,
10075
+ gap,
10076
+ ...props
10077
+ }
10078
+ );
10079
+ }
10080
+
10081
+ // src/components/custom/Workout/MetricTiles.tsx
10082
+ function MetricTiles({ metrics, gap = 1, ...props }) {
10083
+ return /* @__PURE__ */ jsx(HStack, { gap, align: "stretch", ...props, children: metrics.map((metric) => /* @__PURE__ */ jsx(
10084
+ Tile,
10085
+ {
10086
+ label: metric.label,
10087
+ value: metric.value,
10088
+ valueColor: metric.valueColor
10089
+ },
10090
+ metric.label
10091
+ )) });
10092
+ }
10093
+ var DAY_MS = 24 * 60 * 60 * 1e3;
10094
+ var SOON_ACCENT = primitiveRamps.orange[400];
10095
+ function compactUntil(ms) {
10096
+ if (ms <= 0) return "now";
10097
+ const min = Math.round(ms / 6e4);
10098
+ if (min < 60) return `${min}m`;
10099
+ const hr = Math.round(min / 60);
10100
+ if (hr < 24) return `${hr}h`;
10101
+ const d = Math.round(hr / 24);
10102
+ if (d < 7) return `${d}d`;
10103
+ return `${Math.round(d / 7)}w`;
10104
+ }
10105
+ function ScheduleTiles({ when, now, gap = 1, ...props }) {
10106
+ const [mountNow] = useState(() => Date.now());
10107
+ const reference = now ?? mountNow;
10108
+ const whenMs = when instanceof Date ? when.getTime() : when;
10109
+ const soon = whenMs - reference < DAY_MS;
10110
+ const time = new Date(whenMs).toLocaleTimeString(void 0, {
10111
+ hour: "numeric",
10112
+ minute: "2-digit",
10113
+ hour12: true
10114
+ });
10115
+ return /* @__PURE__ */ jsxs(HStack, { gap, ...props, children: [
10116
+ /* @__PURE__ */ jsx(Tile, { label: "Date", value: formatDateTime(when, "short") }),
10117
+ /* @__PURE__ */ jsx(Tile, { label: "Time", value: time }),
10118
+ /* @__PURE__ */ jsx(
10119
+ Tile,
10120
+ {
10121
+ label: "Until",
10122
+ value: compactUntil(whenMs - reference),
10123
+ valueColor: soon ? SOON_ACCENT : void 0
10124
+ }
10125
+ )
10126
+ ] });
10127
+ }
10128
+ var t9 = getSemanticColors("dark");
10129
+ var INDICATOR_CONFIG = {
10130
+ imbalance: { Icon: ScaleIcon, color: t9["status-error"], label: "Left/right imbalance" },
10131
+ overshoot: { Icon: AlertTriangleIcon, color: t9["status-error"], label: "Load overshoot" },
10132
+ "velocity-loss": { Icon: TrendingDownIcon, color: t9["status-warning"], label: "Velocity loss" },
10133
+ "missed-reps": { Icon: CircleSlashIcon, color: t9["status-warning"], label: "Missed reps" },
10134
+ pr: { Icon: AwardIcon, color: t9["status-success"], label: "Personal record" },
10135
+ info: { Icon: InfoIcon, color: t9["status-info"], label: "More info" }
10136
+ };
10137
+ var INDICATOR_PRECEDENCE = [
10138
+ "imbalance",
10139
+ "overshoot",
10140
+ "velocity-loss",
10141
+ "missed-reps",
10142
+ "pr",
10143
+ "info"
10144
+ ];
10145
+ function resolveIndicator(candidates) {
10146
+ return INDICATOR_PRECEDENCE.find((kind) => candidates.includes(kind));
10147
+ }
10148
+ function ExerciseIndicator({ kind, onPress, className, ...props }) {
10149
+ const { Icon, color, label } = INDICATOR_CONFIG[kind];
10150
+ const chip = /* @__PURE__ */ jsx(
10151
+ View,
10152
+ {
10153
+ className,
10154
+ style: {
10155
+ width: 16,
10156
+ height: 16,
10157
+ borderRadius: 8,
10158
+ borderWidth: 1,
10159
+ borderColor: color,
10160
+ alignItems: "center",
10161
+ justifyContent: "center"
10162
+ },
10163
+ accessibilityRole: "image",
10164
+ accessibilityLabel: label,
10165
+ testID: "exercise-indicator",
10166
+ ...props,
10167
+ children: /* @__PURE__ */ jsx(Icon, { size: 11, color })
10168
+ }
10169
+ );
10170
+ if (onPress) {
10171
+ return /* @__PURE__ */ jsx(Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, children: chip });
10172
+ }
10173
+ return chip;
10174
+ }
10175
+ function headingLabel(name, sets, reps, load, unit) {
10176
+ return `${name}, ${sets}\xD7${reps} @ ${roundWeight(load)} ${unit}`;
10177
+ }
10178
+ function ExerciseHeading({
10179
+ name,
10180
+ sets,
10181
+ reps,
10182
+ load,
10183
+ unit = "lbs",
10184
+ tempo,
10185
+ indicator,
10186
+ dimmed,
10187
+ onPress
10188
+ }) {
10189
+ return /* @__PURE__ */ jsxs(View, { style: { opacity: dimmed ? 0.55 : 1 }, testID: "exercise-heading", children: [
10190
+ /* @__PURE__ */ jsxs(
10191
+ Pressable,
10192
+ {
10193
+ onPress,
10194
+ accessibilityRole: "button",
10195
+ accessibilityLabel: headingLabel(name, sets, reps, load, unit),
10196
+ className: "flex-row items-center",
10197
+ style: { gap: 8 },
10198
+ testID: "exercise-card-header",
10199
+ children: [
10200
+ /* @__PURE__ */ jsx(
10201
+ Text,
10202
+ {
10203
+ className: "text-text-primary",
10204
+ style: { fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: "700" },
10205
+ testID: "exercise-card-name",
10206
+ children: name
10207
+ }
10208
+ ),
10209
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10210
+ indicator && /* @__PURE__ */ jsx(ExerciseIndicator, { kind: indicator })
10211
+ ]
10212
+ }
10213
+ ),
10214
+ /* @__PURE__ */ jsxs(
10215
+ View,
10216
+ {
10217
+ className: "flex-row items-center",
10218
+ style: { gap: 8, marginTop: 1 },
10219
+ testID: "exercise-card-summary",
10220
+ children: [
10221
+ /* @__PURE__ */ jsx(SetsRepsLoad, { sets, reps, load, unit }),
10222
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10223
+ tempo && /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm", showLabel: false, showInfo: false })
10224
+ ]
10225
+ }
10226
+ )
10227
+ ] });
10228
+ }
10229
+ function ExerciseCardHeading({
10230
+ setStates,
10231
+ stripHeight = 8,
10232
+ dimmed,
10233
+ testID = "exercise-card",
10234
+ ...heading
10235
+ }) {
10236
+ return /* @__PURE__ */ jsxs(
10237
+ View,
10238
+ {
10239
+ style: { paddingVertical: 9, paddingHorizontal: 12, opacity: dimmed ? 0.55 : 1 },
10240
+ testID,
10241
+ children: [
10242
+ /* @__PURE__ */ jsx(ExerciseHeading, { ...heading }),
10243
+ setStates.length > 0 && /* @__PURE__ */ jsx(View, { style: { marginTop: 7 }, testID: "exercise-card-strip", children: /* @__PURE__ */ jsx(SetStrip, { sets: setStates, height: stripHeight }) })
10244
+ ]
10245
+ }
10246
+ );
10247
+ }
10248
+ var PREV_COLUMN = { label: "PREV", width: void 0 };
10249
+ function buildColumns(unit, showPrevious) {
10250
+ return [
10251
+ { label: "SET", width: 36 },
10252
+ ...showPrevious ? [PREV_COLUMN] : [],
10253
+ { label: "REPS", width: 44 },
10254
+ { label: unit.toUpperCase(), width: 56 },
10255
+ { label: "RPE", width: 36 }
10256
+ ];
10257
+ }
10258
+ function SetTableHeader({
10259
+ unit = "lbs",
10260
+ showPrevious = true,
10261
+ testID = "table-header"
10262
+ }) {
10263
+ const columns = buildColumns(unit, showPrevious);
10264
+ return /* @__PURE__ */ jsx(
10265
+ View,
10266
+ {
10267
+ className: "flex-row",
10268
+ style: {
10269
+ paddingVertical: 8,
10270
+ paddingHorizontal: 8,
10271
+ paddingBottom: 4,
10272
+ ...showPrevious ? {} : { justifyContent: "space-between" }
10273
+ },
10274
+ testID,
10275
+ children: columns.map(({ label, width }) => {
10276
+ const isFlex = width === void 0;
10277
+ return /* @__PURE__ */ jsx(
10278
+ View,
10279
+ {
10280
+ style: {
10281
+ ...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
10282
+ alignItems: "center",
10283
+ justifyContent: "center",
10284
+ gap: 8
10285
+ },
10286
+ ...isFlex ? { className: "flex-1" } : {},
10287
+ children: /* @__PURE__ */ jsx(
10288
+ Text,
10289
+ {
10290
+ className: "text-text-tertiary",
10291
+ style: {
10292
+ fontSize: 10,
10293
+ fontWeight: "600",
10294
+ fontFamily: "Inter, sans-serif",
10295
+ textTransform: "uppercase",
10296
+ letterSpacing: 0.8
10297
+ },
10298
+ children: label
10299
+ }
10300
+ )
10301
+ },
10302
+ label
10303
+ );
10304
+ })
10305
+ }
10306
+ );
10307
+ }
10308
+
10309
+ // src/components/custom/Workout/ExerciseCard.tsx
10310
+ function getSupersetBorderRadius(position) {
10311
+ switch (position) {
10312
+ case "first":
10313
+ return {
10314
+ borderTopLeftRadius: 12,
10315
+ borderTopRightRadius: 12,
9590
10316
  borderBottomLeftRadius: 8,
9591
10317
  borderBottomRightRadius: 8
9592
10318
  };
@@ -9623,7 +10349,7 @@ function formatSummary(summary) {
9623
10349
  if (!summary) return "";
9624
10350
  return `${summary.sets}\xD7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`;
9625
10351
  }
9626
- function formatAccessibilityLabel2(name, summary, prescription) {
10352
+ function formatAccessibilityLabel(name, summary, prescription) {
9627
10353
  if (summary) return `${name}, ${formatSummary(summary)}`;
9628
10354
  if (prescription) return `${name}, ${prescription}`;
9629
10355
  return name;
@@ -9632,7 +10358,6 @@ function CollapsedCard({
9632
10358
  name,
9633
10359
  onToggle,
9634
10360
  summary,
9635
- e1rm,
9636
10361
  isPR,
9637
10362
  setVelocities,
9638
10363
  velocityZones,
@@ -9651,7 +10376,7 @@ function CollapsedCard({
9651
10376
  onPressIn: () => setPressed(true),
9652
10377
  onPressOut: () => setPressed(false),
9653
10378
  accessibilityRole: "button",
9654
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
10379
+ accessibilityLabel: formatAccessibilityLabel(name, summary, void 0),
9655
10380
  testID: "exercise-card",
9656
10381
  children: /* @__PURE__ */ jsxs(
9657
10382
  View,
@@ -9665,100 +10390,93 @@ function CollapsedCard({
9665
10390
  ...supersetMargin
9666
10391
  },
9667
10392
  children: [
9668
- /* @__PURE__ */ jsxs(
9669
- View,
9670
- {
9671
- className: "flex-row items-center",
9672
- testID: "exercise-card-header",
9673
- children: [
9674
- /* @__PURE__ */ jsx(
9675
- Text,
9676
- {
9677
- className: "text-text-primary",
9678
- style: {
9679
- fontSize: 14,
9680
- fontFamily: '"Space Grotesk", sans-serif',
9681
- fontWeight: "700"
9682
- },
9683
- testID: "exercise-card-name",
9684
- children: name
9685
- }
9686
- ),
9687
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
9688
- summary && /* @__PURE__ */ jsx(
9689
- Text,
9690
- {
9691
- className: "text-text-secondary",
9692
- style: {
9693
- fontSize: 12,
9694
- fontFamily: "Inter, sans-serif",
9695
- marginRight: 8
9696
- },
9697
- testID: "exercise-card-summary",
9698
- children: formatSummary(summary)
9699
- }
9700
- ),
9701
- e1rm && /* @__PURE__ */ jsx(
9702
- WeightBadge,
9703
- {
9704
- value: roundWeight(e1rm.value),
9705
- unit: e1rm.unit,
9706
- size: "sm",
9707
- showIcon: false,
9708
- testID: "exercise-card-e1rm"
9709
- }
9710
- ),
9711
- isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
9712
- ]
9713
- }
9714
- ),
9715
- (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(
9716
- View,
9717
- {
9718
- className: "flex-row",
9719
- style: { marginTop: 6, gap: 4 },
9720
- testID: "exercise-card-strips",
9721
- children: [
9722
- setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
9723
- VelocityStrip,
9724
- {
9725
- velocities,
9726
- zones: velocityZones,
9727
- variant: "mini",
9728
- testID: `exercise-card-velocity-strip-${i}`
9729
- },
9730
- i
9731
- )),
9732
- Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
9733
- PlaceholderStrip,
9734
- {
9735
- testID: `exercise-card-placeholder-${i}`
9736
- },
9737
- `placeholder-${i}`
9738
- ))
9739
- ]
9740
- }
9741
- )
10393
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", testID: "exercise-card-header", children: [
10394
+ /* @__PURE__ */ jsx(
10395
+ Text,
10396
+ {
10397
+ className: "text-text-primary",
10398
+ style: {
10399
+ fontSize: 14,
10400
+ fontFamily: '"Space Grotesk", sans-serif',
10401
+ fontWeight: "700"
10402
+ },
10403
+ testID: "exercise-card-name",
10404
+ children: name
10405
+ }
10406
+ ),
10407
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10408
+ summary && /* @__PURE__ */ jsx(
10409
+ Text,
10410
+ {
10411
+ className: "text-text-secondary",
10412
+ style: {
10413
+ fontSize: 12,
10414
+ fontFamily: "Inter, sans-serif",
10415
+ marginRight: 8
10416
+ },
10417
+ testID: "exercise-card-summary",
10418
+ children: formatSummary(summary)
10419
+ }
10420
+ ),
10421
+ isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
10422
+ ] }),
10423
+ (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(View, { className: "flex-row", style: { marginTop: 6, gap: 4 }, testID: "exercise-card-strips", children: [
10424
+ setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
10425
+ VelocityStrip,
10426
+ {
10427
+ velocities,
10428
+ zones: velocityZones,
10429
+ variant: "mini",
10430
+ testID: `exercise-card-velocity-strip-${i}`
10431
+ },
10432
+ i
10433
+ )),
10434
+ Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
10435
+ PlaceholderStrip,
10436
+ {
10437
+ testID: `exercise-card-placeholder-${i}`
10438
+ },
10439
+ `placeholder-${i}`
10440
+ ))
10441
+ ] })
9742
10442
  ]
9743
10443
  }
9744
10444
  )
9745
10445
  }
9746
10446
  );
9747
10447
  }
10448
+ var DARK2 = getSemanticColors("dark");
10449
+ var BODY_DIVIDER = DARK2["border-subtle"];
10450
+ function deriveHeaderSetStates(sets) {
10451
+ return sets.map((set) => {
10452
+ switch (set.state) {
10453
+ case "live":
10454
+ return { status: "active", velocities: set.velocities, planned: set.target.reps };
10455
+ case "done":
10456
+ return { status: "done", velocities: set.velocities };
10457
+ case "todo":
10458
+ return { status: "todo", planned: set.planned ?? set.target.reps };
10459
+ }
10460
+ });
10461
+ }
9748
10462
  function ExpandedCard({
9749
10463
  name,
9750
10464
  onToggle,
9751
10465
  summary,
9752
- e1rm,
9753
10466
  isPR,
9754
10467
  sets,
9755
10468
  tempo,
10469
+ indicator,
10470
+ setStates,
10471
+ stripHeight = 8,
10472
+ velocityZones,
9756
10473
  supersetPosition
9757
10474
  }) {
9758
10475
  const borderRadius = getSupersetBorderRadius(supersetPosition);
9759
10476
  const supersetMargin = getSupersetMargin(supersetPosition);
9760
10477
  const unit = summary?.unit ?? sets?.[0]?.unit ?? "lbs";
9761
- const columnHeaders = buildColumnHeaders(unit);
10478
+ const headerStates = setStates ?? (sets ? deriveHeaderSetStates(sets) : []);
10479
+ const headerIndicator = indicator ?? (isPR ? "pr" : void 0);
9762
10480
  return /* @__PURE__ */ jsxs(
9763
10481
  View,
9764
10482
  {
@@ -9771,114 +10489,32 @@ function ExpandedCard({
9771
10489
  testID: "exercise-card",
9772
10490
  children: [
9773
10491
  /* @__PURE__ */ jsx(
9774
- Pressable,
10492
+ ExerciseCardHeading,
9775
10493
  {
10494
+ name,
10495
+ sets: summary?.sets ?? sets?.length ?? 0,
10496
+ reps: summary?.reps ?? 0,
10497
+ load: summary?.weight ?? 0,
10498
+ unit,
10499
+ tempo,
10500
+ indicator: headerIndicator,
10501
+ setStates: headerStates,
10502
+ stripHeight,
9776
10503
  onPress: onToggle,
9777
- accessibilityRole: "button",
9778
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
9779
- "aria-expanded": true,
9780
- testID: "exercise-card-header",
9781
- children: /* @__PURE__ */ jsxs(
9782
- View,
9783
- {
9784
- className: "flex-row items-center",
9785
- style: {
9786
- padding: 12,
9787
- paddingHorizontal: 14,
9788
- paddingBottom: 10,
9789
- borderBottomWidth: 1,
9790
- borderBottomColor: resolveColor("border-default")
9791
- },
9792
- children: [
9793
- /* @__PURE__ */ jsx(
9794
- Text,
9795
- {
9796
- className: "text-text-primary",
9797
- style: {
9798
- fontSize: 14,
9799
- fontFamily: '"Space Grotesk", sans-serif',
9800
- fontWeight: "700"
9801
- },
9802
- testID: "exercise-card-name",
9803
- children: name
9804
- }
9805
- ),
9806
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
9807
- summary && /* @__PURE__ */ jsx(
9808
- Text,
9809
- {
9810
- className: "text-text-secondary",
9811
- style: {
9812
- fontSize: 12,
9813
- fontFamily: "Inter, sans-serif",
9814
- marginRight: 8
9815
- },
9816
- testID: "exercise-card-summary",
9817
- children: formatSummary(summary)
9818
- }
9819
- ),
9820
- e1rm && /* @__PURE__ */ jsx(
9821
- WeightBadge,
9822
- {
9823
- value: roundWeight(e1rm.value),
9824
- unit: e1rm.unit,
9825
- size: "sm",
9826
- showIcon: false,
9827
- testID: "exercise-card-e1rm"
9828
- }
9829
- ),
9830
- isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
9831
- ]
9832
- }
9833
- )
10504
+ testID: "exercise-card-heading"
9834
10505
  }
9835
10506
  ),
9836
- tempo && /* @__PURE__ */ jsx(View, { style: { marginTop: 8, paddingHorizontal: 14 }, testID: "exercise-card-tempo", children: /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm" }) }),
9837
- /* @__PURE__ */ jsx(
10507
+ /* @__PURE__ */ jsxs(
9838
10508
  View,
9839
10509
  {
9840
- className: "flex-row",
9841
- style: {
9842
- paddingVertical: 8,
9843
- paddingHorizontal: 8,
9844
- paddingBottom: 4
9845
- },
9846
- testID: "exercise-card-column-headers",
9847
- children: columnHeaders.map((header, i) => {
9848
- const widths = [36, void 0, 44, 56, 36];
9849
- const width = widths[i];
9850
- const isFlex = width === void 0;
9851
- return /* @__PURE__ */ jsx(
9852
- View,
9853
- {
9854
- style: {
9855
- ...isFlex ? {} : { width },
9856
- alignItems: "center",
9857
- justifyContent: "center",
9858
- gap: 8
9859
- },
9860
- ...isFlex ? { className: "flex-1" } : {},
9861
- children: /* @__PURE__ */ jsx(
9862
- Text,
9863
- {
9864
- className: "text-text-tertiary",
9865
- style: {
9866
- fontSize: 10,
9867
- fontWeight: "600",
9868
- fontFamily: "Inter, sans-serif",
9869
- textTransform: "uppercase",
9870
- letterSpacing: 0.8
9871
- },
9872
- children: header
9873
- }
9874
- )
9875
- },
9876
- header
9877
- );
9878
- })
10510
+ style: { borderTopWidth: 1, borderTopColor: BODY_DIVIDER, paddingBottom: 6 },
10511
+ testID: "exercise-card-body",
10512
+ children: [
10513
+ /* @__PURE__ */ jsx(SetTableHeader, { unit, showPrevious: false, testID: "exercise-card-column-headers" }),
10514
+ sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((set, i) => /* @__PURE__ */ jsx(SetRow, { ...set, velocityZones: set.velocityZones ?? velocityZones }, i)) })
10515
+ ]
9879
10516
  }
9880
- ),
9881
- sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((setProps, i) => /* @__PURE__ */ jsx(SetRow, { ...setProps }, i)) })
10517
+ )
9882
10518
  ]
9883
10519
  }
9884
10520
  );
@@ -9887,11 +10523,12 @@ function UpcomingCard({
9887
10523
  name,
9888
10524
  prescription,
9889
10525
  previousBest,
9890
- supersetPosition
10526
+ supersetPosition,
10527
+ onToggle
9891
10528
  }) {
9892
10529
  const borderRadius = getSupersetBorderRadius(supersetPosition);
9893
10530
  const supersetMargin = getSupersetMargin(supersetPosition);
9894
- return /* @__PURE__ */ jsx(
10531
+ return /* @__PURE__ */ jsx(Pressable, { onPress: onToggle, accessibilityRole: "button", children: /* @__PURE__ */ jsx(
9895
10532
  View,
9896
10533
  {
9897
10534
  style: {
@@ -9901,7 +10538,7 @@ function UpcomingCard({
9901
10538
  ...borderRadius,
9902
10539
  ...supersetMargin
9903
10540
  },
9904
- accessibilityLabel: formatAccessibilityLabel2(name, void 0, prescription),
10541
+ accessibilityLabel: formatAccessibilityLabel(name, void 0, prescription),
9905
10542
  testID: "exercise-card",
9906
10543
  children: /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", children: [
9907
10544
  /* @__PURE__ */ jsx(
@@ -9909,6 +10546,7 @@ function UpcomingCard({
9909
10546
  {
9910
10547
  className: "text-text-primary",
9911
10548
  style: {
10549
+ flexShrink: 0,
9912
10550
  fontSize: 14,
9913
10551
  fontFamily: '"Space Grotesk", sans-serif',
9914
10552
  fontWeight: "700"
@@ -9921,7 +10559,9 @@ function UpcomingCard({
9921
10559
  Text,
9922
10560
  {
9923
10561
  className: "text-text-secondary",
10562
+ numberOfLines: 1,
9924
10563
  style: {
10564
+ flexShrink: 1,
9925
10565
  fontSize: 12,
9926
10566
  fontFamily: "Inter, sans-serif",
9927
10567
  marginLeft: 8
@@ -9930,12 +10570,14 @@ function UpcomingCard({
9930
10570
  children: prescription
9931
10571
  }
9932
10572
  ),
9933
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10573
+ /* @__PURE__ */ jsx(View, { className: "flex-1", style: { minWidth: 8 } }),
9934
10574
  previousBest && /* @__PURE__ */ jsx(
9935
10575
  Text,
9936
10576
  {
9937
10577
  className: "text-text-tertiary",
10578
+ numberOfLines: 1,
9938
10579
  style: {
10580
+ flexShrink: 1,
9939
10581
  fontSize: 11,
9940
10582
  fontFamily: "Inter, sans-serif"
9941
10583
  },
@@ -9945,17 +10587,208 @@ function UpcomingCard({
9945
10587
  )
9946
10588
  ] })
9947
10589
  }
10590
+ ) });
10591
+ }
10592
+ function ExerciseCard({
10593
+ upcoming,
10594
+ expanded,
10595
+ defaultExpanded,
10596
+ onExpandedChange,
10597
+ ...rest
10598
+ }) {
10599
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded ?? false);
10600
+ const isControlled = expanded !== void 0;
10601
+ const isExpanded = isControlled ? expanded : internalExpanded;
10602
+ const onToggle = () => {
10603
+ const next = !isExpanded;
10604
+ onExpandedChange?.(next);
10605
+ if (!isControlled) setInternalExpanded(next);
10606
+ };
10607
+ if (upcoming) return /* @__PURE__ */ jsx(UpcomingCard, { ...rest, onToggle });
10608
+ return isExpanded ? /* @__PURE__ */ jsx(ExpandedCard, { ...rest, onToggle }) : /* @__PURE__ */ jsx(CollapsedCard, { ...rest, onToggle });
10609
+ }
10610
+ var t10 = getSemanticColors("dark");
10611
+ var RAISED = primitiveColors.charcoal[400];
10612
+ var BAR_HEIGHT = 9;
10613
+ var BAR_GAP = 3;
10614
+ function SessionHeader({
10615
+ title,
10616
+ plan,
10617
+ setsDone = 0,
10618
+ elapsedMs,
10619
+ budgetMs,
10620
+ running = true,
10621
+ metrics = [],
10622
+ next,
10623
+ className,
10624
+ style,
10625
+ ...props
10626
+ }) {
10627
+ const upcoming = next != null;
10628
+ const totalSets = plan.reduce((sum, e) => sum + e.sets, 0);
10629
+ const hasPace = !upcoming && budgetMs != null && elapsedMs != null;
10630
+ const target = hasPace ? elapsedMs / budgetMs : void 0;
10631
+ const setsLabel = upcoming ? `${totalSets} sets` : `${Math.floor(setsDone)}/${totalSets} sets`;
10632
+ const setsLabelColor = upcoming ? t10["text-secondary"] : paceToneColor(paceTone(totalSets > 0 ? setsDone / totalSets : 0, target));
10633
+ return /* @__PURE__ */ jsxs(
10634
+ View,
10635
+ {
10636
+ className,
10637
+ style: [
10638
+ {
10639
+ backgroundColor: RAISED,
10640
+ paddingTop: 11,
10641
+ paddingRight: 12,
10642
+ paddingBottom: 12,
10643
+ paddingLeft: 12,
10644
+ zIndex: 2
10645
+ },
10646
+ style
10647
+ ],
10648
+ testID: "session-rail-header",
10649
+ ...props,
10650
+ children: [
10651
+ /* @__PURE__ */ jsx(
10652
+ Text,
10653
+ {
10654
+ accessibilityRole: "header",
10655
+ style: {
10656
+ fontSize: 16,
10657
+ lineHeight: 18,
10658
+ fontWeight: "700",
10659
+ fontFamily: '"Space Grotesk", sans-serif',
10660
+ color: t10["text-primary"],
10661
+ marginBottom: 10
10662
+ },
10663
+ testID: "session-rail-title",
10664
+ children: title
10665
+ }
10666
+ ),
10667
+ upcoming ? /* @__PURE__ */ jsx(ScheduleTiles, { when: next }) : /* @__PURE__ */ jsx(MetricTiles, { metrics }),
10668
+ /* @__PURE__ */ jsxs(View, { style: { marginTop: 10 }, children: [
10669
+ /* @__PURE__ */ jsx(
10670
+ SegmentedProgressBar,
10671
+ {
10672
+ segments: plan.map((e) => ({ weight: e.sets })),
10673
+ value: upcoming ? 0 : setsDone,
10674
+ target,
10675
+ height: BAR_HEIGHT,
10676
+ gap: BAR_GAP
10677
+ }
10678
+ ),
10679
+ /* @__PURE__ */ jsxs(
10680
+ View,
10681
+ {
10682
+ style: {
10683
+ flexDirection: "row",
10684
+ justifyContent: "space-between",
10685
+ alignItems: "baseline",
10686
+ marginTop: 5
10687
+ },
10688
+ children: [
10689
+ /* @__PURE__ */ jsx(
10690
+ Typography,
10691
+ {
10692
+ variant: "mono",
10693
+ style: { fontSize: 10, color: setsLabelColor },
10694
+ testID: "session-rail-sets",
10695
+ children: setsLabel
10696
+ }
10697
+ ),
10698
+ /* @__PURE__ */ jsx(
10699
+ TimerReadout,
10700
+ {
10701
+ mode: "up",
10702
+ elapsedMs: upcoming ? budgetMs : elapsedMs,
10703
+ durationMs: budgetMs,
10704
+ running: running && !upcoming,
10705
+ showTotal: budgetMs != null && !upcoming
10706
+ }
10707
+ )
10708
+ ]
10709
+ }
10710
+ )
10711
+ ] })
10712
+ ]
10713
+ }
9948
10714
  );
9949
10715
  }
9950
- function ExerciseCard(props) {
9951
- switch (props.state) {
9952
- case "collapsed":
9953
- return /* @__PURE__ */ jsx(CollapsedCard, { ...props });
9954
- case "expanded":
9955
- return /* @__PURE__ */ jsx(ExpandedCard, { ...props });
9956
- case "upcoming":
9957
- return /* @__PURE__ */ jsx(UpcomingCard, { ...props });
9958
- }
10716
+ var INSET = primitiveColors.charcoal[800];
10717
+ var DIVIDER = primitiveColors.charcoal[500];
10718
+ var DEFAULT_WIDTH = 246;
10719
+ function SessionRail({
10720
+ title,
10721
+ exercises,
10722
+ setsDone,
10723
+ elapsedMs,
10724
+ budgetMs,
10725
+ running,
10726
+ metrics,
10727
+ next,
10728
+ stripHeight = 8,
10729
+ width = DEFAULT_WIDTH,
10730
+ onExercisePress,
10731
+ className,
10732
+ style,
10733
+ ...props
10734
+ }) {
10735
+ return /* @__PURE__ */ jsxs(
10736
+ View,
10737
+ {
10738
+ className,
10739
+ style: [{ width, flexDirection: "column", backgroundColor: INSET }, style],
10740
+ testID: "session-rail",
10741
+ ...props,
10742
+ children: [
10743
+ /* @__PURE__ */ jsx(
10744
+ SessionHeader,
10745
+ {
10746
+ title,
10747
+ plan: exercises.map((ex) => ({ name: ex.name, sets: ex.summary.sets })),
10748
+ setsDone,
10749
+ elapsedMs,
10750
+ budgetMs,
10751
+ running,
10752
+ metrics,
10753
+ next
10754
+ }
10755
+ ),
10756
+ /* @__PURE__ */ jsx(
10757
+ View,
10758
+ {
10759
+ style: [{ flex: 1, backgroundColor: INSET }, neumorphicShadows.charcoal.pressed.subtle],
10760
+ testID: "session-rail-list",
10761
+ children: exercises.map((ex, i) => /* @__PURE__ */ jsx(
10762
+ View,
10763
+ {
10764
+ style: {
10765
+ borderBottomWidth: i < exercises.length - 1 ? 1 : 0,
10766
+ borderBottomColor: DIVIDER
10767
+ },
10768
+ children: /* @__PURE__ */ jsx(
10769
+ ExerciseCardHeading,
10770
+ {
10771
+ name: ex.name,
10772
+ sets: ex.summary.sets,
10773
+ reps: ex.summary.reps,
10774
+ load: ex.summary.weight,
10775
+ unit: ex.summary.unit,
10776
+ tempo: ex.tempo,
10777
+ indicator: ex.indicator,
10778
+ setStates: ex.setStates,
10779
+ stripHeight,
10780
+ dimmed: ex.upcoming,
10781
+ onPress: () => onExercisePress?.(ex, i)
10782
+ }
10783
+ )
10784
+ },
10785
+ ex.id ?? i
10786
+ ))
10787
+ }
10788
+ )
10789
+ ]
10790
+ }
10791
+ );
9959
10792
  }
9960
10793
  var DEFAULTS = {
9961
10794
  label: "SS",
@@ -10219,10 +11052,10 @@ function WeekRow({
10219
11052
  }
10220
11053
  );
10221
11054
  }
10222
- var t8 = getSemanticColors("dark");
11055
+ var t11 = getSemanticColors("dark");
10223
11056
  var COLORS = {
10224
- statusSuccess: t8["status-success"],
10225
- brandPrimary: t8["brand-primary"],
11057
+ statusSuccess: t11["status-success"],
11058
+ brandPrimary: t11["brand-primary"],
10226
11059
  borderDefault: "#1F1F1F",
10227
11060
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
10228
11061
  };
@@ -10706,12 +11539,12 @@ function PrHistoryModal({
10706
11539
  }
10707
11540
  );
10708
11541
  }
10709
- var t9 = getSemanticColors("dark");
10710
- var BRAND_PRIMARY9 = t9["brand-primary"];
11542
+ var t12 = getSemanticColors("dark");
11543
+ var BRAND_PRIMARY9 = t12["brand-primary"];
10711
11544
  var BRAND_PRIMARY_SUBTLE = "rgba(255,121,0,0.12)";
10712
- var STATUS_SUCCESS = t9["status-success"];
10713
- var STATUS_WARNING = t9["status-warning"];
10714
- var STATUS_ERROR2 = t9["status-error"];
11545
+ var STATUS_SUCCESS = t12["status-success"];
11546
+ var STATUS_WARNING = t12["status-warning"];
11547
+ var STATUS_ERROR2 = t12["status-error"];
10715
11548
  var EMOJI_SETS = {
10716
11549
  sleep: ["\u{1F634}", "\u{1F610}", "\u{1F642}", "\u{1F60A}", "\u{1F929}"],
10717
11550
  soreness: ["\u{1F635}", "\u{1F623}", "\u{1F610}", "\u{1F642}", "\u{1F4AA}"],
@@ -10901,13 +11734,13 @@ function ReadinessCheck({
10901
11734
  )
10902
11735
  ] }) });
10903
11736
  }
10904
- var t10 = getSemanticColors("dark");
10905
- var BRAND_PRIMARY10 = t10["brand-primary"];
11737
+ var t13 = getSemanticColors("dark");
11738
+ var BRAND_PRIMARY10 = t13["brand-primary"];
10906
11739
  var BRAND_PRIMARY_DARK2 = MESO_ACCENT_GRADIENT_DARK;
10907
11740
  var BRAND_PRIMARY_LIGHT2 = MESO_ACCENT_GRADIENT_LIGHT;
10908
- var SUCCESS = t10["status-success"];
10909
- var WARNING = t10["status-warning"];
10910
- var ERROR = t10["status-error"];
11741
+ var SUCCESS = t13["status-success"];
11742
+ var WARNING = t13["status-warning"];
11743
+ var ERROR = t13["status-error"];
10911
11744
  var ACCENT_STOPS2 = [BRAND_PRIMARY_DARK2, BRAND_PRIMARY10, BRAND_PRIMARY_LIGHT2];
10912
11745
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
10913
11746
  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%)";
@@ -10979,7 +11812,7 @@ function StatusPill({ badge }) {
10979
11812
  }
10980
11813
  );
10981
11814
  }
10982
- function MetricCell({ metric }) {
11815
+ function MetricCell2({ metric }) {
10983
11816
  return /* @__PURE__ */ jsxs(View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
10984
11817
  /* @__PURE__ */ jsx(
10985
11818
  Text,
@@ -11197,7 +12030,7 @@ function MesoStatusCard({
11197
12030
  className: "flex-row flex-wrap",
11198
12031
  style: { gap: 8 },
11199
12032
  testID: "meso-status-card-metrics",
11200
- children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
12033
+ children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell2, { metric }, `${metric.label}-${index}`))
11201
12034
  }
11202
12035
  ),
11203
12036
  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}`)) }),
@@ -11278,8 +12111,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
11278
12111
  var PLOT_LEFT = 26;
11279
12112
  var TOOLTIP_WIDTH = 124;
11280
12113
  function timeOf(dateStr) {
11281
- const t12 = Date.parse(dateStr);
11282
- return Number.isNaN(t12) ? 0 : t12;
12114
+ const t18 = Date.parse(dateStr);
12115
+ return Number.isNaN(t18) ? 0 : t18;
11283
12116
  }
11284
12117
  function formatValue(value) {
11285
12118
  return `${Math.round(value)}`;
@@ -11768,10 +12601,10 @@ function StrengthTrendChart({
11768
12601
  }
11769
12602
  );
11770
12603
  }
11771
- var t11 = getSemanticColors("dark");
11772
- var STATUS_SUCCESS3 = t11["status-success"];
11773
- var STATUS_WARNING3 = t11["status-warning"];
11774
- var STATUS_INFO = t11["status-info"];
12604
+ var t14 = getSemanticColors("dark");
12605
+ var STATUS_SUCCESS3 = t14["status-success"];
12606
+ var STATUS_WARNING3 = t14["status-warning"];
12607
+ var STATUS_INFO = t14["status-info"];
11775
12608
  var DOT_BORDER = "#F3F4F6";
11776
12609
  var BAND_FILL = "rgba(46,213,115,0.1)";
11777
12610
  var BAND_EDGE = "rgba(46,213,115,0.45)";
@@ -11814,13 +12647,13 @@ function interpEdge(pixels, x, key) {
11814
12647
  const a = pixels[i];
11815
12648
  const b = pixels[i + 1];
11816
12649
  if (x >= a.x && x <= b.x) {
11817
- const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
11818
- return a[key] + t12 * (b[key] - a[key]);
12650
+ const t18 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
12651
+ return a[key] + t18 * (b[key] - a[key]);
11819
12652
  }
11820
12653
  }
11821
12654
  return pixels[pixels.length - 1][key];
11822
12655
  }
11823
- function buildColumns(pixels, step) {
12656
+ function buildColumns2(pixels, step) {
11824
12657
  if (pixels.length < 2) return [];
11825
12658
  const start = pixels[0].x;
11826
12659
  const end = pixels[pixels.length - 1].x;
@@ -11932,7 +12765,7 @@ function CapacityBandChart({
11932
12765
  }
11933
12766
  const { toX, toY } = scale;
11934
12767
  const bandPixels = toPixels(band, toX, toY);
11935
- const columns = buildColumns(bandPixels, COLUMN_STEP);
12768
+ const columns = buildColumns2(bandPixels, COLUMN_STEP);
11936
12769
  const topEdge = buildEdges(bandPixels, "yHigh");
11937
12770
  const bottomEdge = buildEdges(bandPixels, "yLow");
11938
12771
  const lastBandPoint = band[band.length - 1];
@@ -12007,8 +12840,8 @@ function CapacityBandChart({
12007
12840
  testID: "capacity-band-chart-reveal",
12008
12841
  children: /* @__PURE__ */ jsxs(View, { style: { width, height, position: "relative" }, children: [
12009
12842
  projection && /* @__PURE__ */ jsxs(View, { testID: "capacity-band-chart-projection", children: [
12010
- renderColumns(buildColumns(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
12011
- renderColumns(buildColumns(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
12843
+ renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
12844
+ renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
12012
12845
  renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
12013
12846
  renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
12014
12847
  renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
@@ -12075,72 +12908,546 @@ function CapacityBandChart({
12075
12908
  return /* @__PURE__ */ jsx(
12076
12909
  Text,
12077
12910
  {
12078
- className: "text-text-tertiary",
12911
+ className: "text-text-tertiary",
12912
+ style: {
12913
+ position: "absolute",
12914
+ left: toX(point.date) - 14,
12915
+ top: height - PADDING_BOTTOM + 2,
12916
+ width: 28,
12917
+ textAlign: "center",
12918
+ fontSize: 10,
12919
+ fontFamily: "Inter, sans-serif"
12920
+ },
12921
+ accessibilityElementsHidden: true,
12922
+ importantForAccessibility: "no-hide-descendants",
12923
+ testID: "capacity-band-chart-x-label",
12924
+ children: formatDate2(point.date)
12925
+ },
12926
+ `x-${point.date}`
12927
+ );
12928
+ })
12929
+ ]
12930
+ }
12931
+ ),
12932
+ workouts.map((workout, i) => {
12933
+ const cx = toX(workout.date);
12934
+ const cy = toY(workout.load);
12935
+ const color = DOT_COLORS[workout.status];
12936
+ const dotStyle = {
12937
+ width: DOT_SIZE,
12938
+ height: DOT_SIZE,
12939
+ borderRadius: DOT_SIZE / 2,
12940
+ backgroundColor: color,
12941
+ borderWidth: 2,
12942
+ borderColor: DOT_BORDER
12943
+ };
12944
+ const label = `Workout on ${formatDate2(workout.date)}, load ${workout.load}, ${STATUS_PHRASES[workout.status]}`;
12945
+ return /* @__PURE__ */ jsx(
12946
+ Animated.View,
12947
+ {
12948
+ style: {
12949
+ position: "absolute",
12950
+ left: cx - DOT_SIZE / 2,
12951
+ top: cy - DOT_SIZE / 2,
12952
+ transform: [{ scale: dotAnims[i] ?? 1 }]
12953
+ },
12954
+ testID: "capacity-band-chart-dot-wrapper",
12955
+ children: onWorkoutPress ? /* @__PURE__ */ jsx(
12956
+ Pressable,
12957
+ {
12958
+ onPress: () => onWorkoutPress(workout),
12959
+ accessibilityRole: "button",
12960
+ accessibilityLabel: label,
12961
+ style: dotStyle,
12962
+ testID: "capacity-band-chart-dot"
12963
+ }
12964
+ ) : /* @__PURE__ */ jsx(
12965
+ View,
12966
+ {
12967
+ accessibilityElementsHidden: true,
12968
+ importantForAccessibility: "no-hide-descendants",
12969
+ style: dotStyle,
12970
+ testID: "capacity-band-chart-dot"
12971
+ }
12972
+ )
12973
+ },
12974
+ `dot-${workout.date}-${i}`
12975
+ );
12976
+ })
12977
+ ]
12978
+ }
12979
+ );
12980
+ }
12981
+ var t15 = getSemanticColors("dark");
12982
+ function glowShadow(color) {
12983
+ return `0 0 5px 1px ${alpha(color, 0.3)}, 0 0 10px 3px ${alpha(color, 0.12)}`;
12984
+ }
12985
+ var DEFAULT_TRACK_COLOR = primitiveColors.charcoal[200];
12986
+ var DEFAULT_MARKER_COLOR2 = primitiveColors.white;
12987
+ var TICK_COLOR = t15["text-tertiary"];
12988
+ var DEFAULT_TRACK_HEIGHT = 14;
12989
+ var DEFAULT_NEEDLE_OVERHANG = 6;
12990
+ var NEEDLE_WIDTH = 4;
12991
+ function fraction(value, min, max) {
12992
+ if (max <= min) return 0;
12993
+ return Math.max(0, Math.min(1, (value - min) / (max - min)));
12994
+ }
12995
+ function pct(frac) {
12996
+ return `${frac * 100}%`;
12997
+ }
12998
+ function ZoneTrack({
12999
+ zones,
13000
+ max,
13001
+ min = 0,
13002
+ marker = null,
13003
+ ticks,
13004
+ band = null,
13005
+ trackHeight = DEFAULT_TRACK_HEIGHT,
13006
+ needleOverhang = DEFAULT_NEEDLE_OVERHANG,
13007
+ trackColor = DEFAULT_TRACK_COLOR,
13008
+ className,
13009
+ style,
13010
+ accessibilityLabel: accessibilityLabel2,
13011
+ ...props
13012
+ }) {
13013
+ const isNeedle = marker?.type === "needle";
13014
+ const markerHeight = isNeedle ? trackHeight + needleOverhang * 2 : trackHeight;
13015
+ const markerFrac = marker != null ? fraction(marker.value, min, max) : 0;
13016
+ const bands = zones.map((zone, i) => {
13017
+ const prev = i === 0 ? min : zones[i - 1].upTo;
13018
+ const start = Math.max(min, Math.min(max, prev));
13019
+ const end = Math.max(min, Math.min(max, zone.upTo));
13020
+ return { color: zone.color, weight: Math.max(0, end - start) };
13021
+ });
13022
+ return /* @__PURE__ */ jsxs(
13023
+ View,
13024
+ {
13025
+ className,
13026
+ style: [{ width: "100%" }, style],
13027
+ accessibilityRole: "progressbar",
13028
+ accessibilityValue: marker != null ? { min, max, now: marker.value } : { min, max, now: min },
13029
+ accessibilityLabel: accessibilityLabel2 ?? `Zone track: ${marker?.value ?? min}`,
13030
+ testID: "zone-track",
13031
+ ...props,
13032
+ children: [
13033
+ /* @__PURE__ */ jsxs(View, { style: { position: "relative", height: markerHeight }, children: [
13034
+ /* @__PURE__ */ jsxs(
13035
+ View,
13036
+ {
13037
+ testID: "zone-track-track",
13038
+ accessibilityElementsHidden: true,
13039
+ style: {
13040
+ position: "absolute",
13041
+ top: (markerHeight - trackHeight) / 2,
13042
+ left: 0,
13043
+ right: 0,
13044
+ height: trackHeight,
13045
+ borderRadius: trackHeight / 2,
13046
+ backgroundColor: trackColor,
13047
+ overflow: "hidden",
13048
+ flexDirection: "row",
13049
+ // The pill's own box-shadow renders outside its overflow:hidden, so
13050
+ // `glow` haloes the whole track in the marker colour.
13051
+ ...marker?.glow ? { boxShadow: glowShadow(marker.color ?? DEFAULT_MARKER_COLOR2) } : null
13052
+ },
13053
+ children: [
13054
+ bands.map((b, i) => /* @__PURE__ */ jsx(
13055
+ View,
13056
+ {
13057
+ testID: "zone-track-band",
13058
+ style: {
13059
+ flexGrow: b.weight,
13060
+ flexShrink: 1,
13061
+ flexBasis: 0,
13062
+ minWidth: 0,
13063
+ height: "100%",
13064
+ backgroundColor: b.color
13065
+ }
13066
+ },
13067
+ i
13068
+ )),
13069
+ band != null && /* @__PURE__ */ jsx(
13070
+ View,
13071
+ {
13072
+ testID: "zone-track-band-highlight",
13073
+ style: {
13074
+ position: "absolute",
13075
+ top: 0,
13076
+ bottom: 0,
13077
+ left: pct(fraction(band.from, min, max)),
13078
+ width: pct(fraction(band.to, min, max) - fraction(band.from, min, max)),
13079
+ backgroundColor: band.color
13080
+ }
13081
+ }
13082
+ ),
13083
+ marker?.type === "fill" && /* @__PURE__ */ jsx(
13084
+ View,
13085
+ {
13086
+ testID: "zone-track-unfilled",
13087
+ style: {
13088
+ position: "absolute",
13089
+ top: 0,
13090
+ bottom: 0,
13091
+ left: pct(markerFrac),
13092
+ right: 0,
13093
+ backgroundColor: trackColor
13094
+ }
13095
+ }
13096
+ ),
13097
+ marker?.type === "fill" && marker.color != null && /* @__PURE__ */ jsx(
13098
+ View,
13099
+ {
13100
+ testID: "zone-track-fill",
12079
13101
  style: {
12080
13102
  position: "absolute",
12081
- left: toX(point.date) - 14,
12082
- top: height - PADDING_BOTTOM + 2,
12083
- width: 28,
12084
- textAlign: "center",
12085
- fontSize: 10,
12086
- fontFamily: "Inter, sans-serif"
12087
- },
12088
- accessibilityElementsHidden: true,
12089
- importantForAccessibility: "no-hide-descendants",
12090
- testID: "capacity-band-chart-x-label",
12091
- children: formatDate2(point.date)
12092
- },
12093
- `x-${point.date}`
12094
- );
12095
- })
12096
- ]
12097
- }
12098
- ),
12099
- workouts.map((workout, i) => {
12100
- const cx = toX(workout.date);
12101
- const cy = toY(workout.load);
12102
- const color = DOT_COLORS[workout.status];
12103
- const dotStyle = {
12104
- width: DOT_SIZE,
12105
- height: DOT_SIZE,
12106
- borderRadius: DOT_SIZE / 2,
12107
- backgroundColor: color,
12108
- borderWidth: 2,
12109
- borderColor: DOT_BORDER
12110
- };
12111
- const label = `Workout on ${formatDate2(workout.date)}, load ${workout.load}, ${STATUS_PHRASES[workout.status]}`;
13103
+ top: 0,
13104
+ bottom: 0,
13105
+ left: 0,
13106
+ width: pct(markerFrac),
13107
+ backgroundColor: marker.color
13108
+ }
13109
+ }
13110
+ )
13111
+ ]
13112
+ }
13113
+ ),
13114
+ isNeedle && /* @__PURE__ */ jsx(
13115
+ View,
13116
+ {
13117
+ testID: "zone-track-needle",
13118
+ style: {
13119
+ position: "absolute",
13120
+ top: 0,
13121
+ left: pct(markerFrac),
13122
+ width: NEEDLE_WIDTH,
13123
+ height: markerHeight,
13124
+ borderRadius: NEEDLE_WIDTH / 2,
13125
+ backgroundColor: marker.color ?? DEFAULT_MARKER_COLOR2,
13126
+ transform: [{ translateX: -NEEDLE_WIDTH / 2 }]
13127
+ }
13128
+ }
13129
+ ),
13130
+ ticks?.map((tick, i) => {
13131
+ const emphasized = tick.emphasized === true;
13132
+ const lineColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13133
+ const lineWidth = emphasized ? 2 : 1.5;
13134
+ return /* @__PURE__ */ jsx(
13135
+ View,
13136
+ {
13137
+ testID: "zone-track-tick-line",
13138
+ accessibilityElementsHidden: true,
13139
+ style: {
13140
+ position: "absolute",
13141
+ top: (markerHeight - trackHeight) / 2 - 2,
13142
+ left: pct(fraction(tick.value, min, max)),
13143
+ width: lineWidth,
13144
+ height: trackHeight + 4,
13145
+ borderRadius: lineWidth / 2,
13146
+ backgroundColor: lineColor,
13147
+ transform: [{ translateX: -lineWidth / 2 }],
13148
+ zIndex: 3
13149
+ }
13150
+ },
13151
+ `line-${i}`
13152
+ );
13153
+ })
13154
+ ] }),
13155
+ ticks != null && ticks.length > 0 && /* @__PURE__ */ jsx(View, { style: { position: "relative", height: 16, marginTop: 5 }, children: ticks.map((tick, i) => {
13156
+ if (tick.label == null) return null;
13157
+ const emphasized = tick.emphasized === true;
13158
+ const labelColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13159
+ const labelNode = /* @__PURE__ */ jsx(
13160
+ Text,
13161
+ {
13162
+ testID: "zone-track-tick-label",
13163
+ style: {
13164
+ fontSize: 9,
13165
+ letterSpacing: 0.5,
13166
+ color: labelColor,
13167
+ fontFamily: "monospace",
13168
+ fontWeight: emphasized ? "700" : "400"
13169
+ },
13170
+ children: tick.label
13171
+ }
13172
+ );
12112
13173
  return /* @__PURE__ */ jsx(
12113
- Animated.View,
13174
+ View,
12114
13175
  {
13176
+ testID: "zone-track-tick",
12115
13177
  style: {
12116
13178
  position: "absolute",
12117
- left: cx - DOT_SIZE / 2,
12118
- top: cy - DOT_SIZE / 2,
12119
- transform: [{ scale: dotAnims[i] ?? 1 }]
13179
+ left: pct(fraction(tick.value, min, max)),
13180
+ transform: [{ translateX: -14 }],
13181
+ width: 28,
13182
+ alignItems: "center"
12120
13183
  },
12121
- testID: "capacity-band-chart-dot-wrapper",
12122
- children: onWorkoutPress ? /* @__PURE__ */ jsx(
12123
- Pressable,
12124
- {
12125
- onPress: () => onWorkoutPress(workout),
12126
- accessibilityRole: "button",
12127
- accessibilityLabel: label,
12128
- style: dotStyle,
12129
- testID: "capacity-band-chart-dot"
12130
- }
12131
- ) : /* @__PURE__ */ jsx(
12132
- View,
12133
- {
12134
- accessibilityElementsHidden: true,
12135
- importantForAccessibility: "no-hide-descendants",
12136
- style: dotStyle,
12137
- testID: "capacity-band-chart-dot"
12138
- }
12139
- )
13184
+ children: tick.tooltip != null ? /* @__PURE__ */ jsx(Tooltip, { label: tick.tooltip, placement: "top", children: labelNode }) : labelNode
12140
13185
  },
12141
- `dot-${workout.date}-${i}`
13186
+ `label-${i}`
12142
13187
  );
12143
- })
13188
+ }) })
13189
+ ]
13190
+ }
13191
+ );
13192
+ }
13193
+
13194
+ // src/components/custom/Workout/FatigueMeter.tsx
13195
+ var { green, yellow, orange, red } = WORKOUT_TOKENS.scale;
13196
+ var DEFAULT_ZONE_COLORS = [green, yellow, orange, red];
13197
+ var DEFAULT_THRESHOLDS = [10, 20, 30];
13198
+ var DEFAULT_LABELS = ["fresh", "VL10", "VL20", "VL30", "stop"];
13199
+ var DEFAULT_MAX = 40;
13200
+ function FatigueMeter({
13201
+ value,
13202
+ max = DEFAULT_MAX,
13203
+ thresholds = DEFAULT_THRESHOLDS,
13204
+ zoneColors = DEFAULT_ZONE_COLORS,
13205
+ labels = DEFAULT_LABELS,
13206
+ trackHeight = 14,
13207
+ needleColor,
13208
+ className,
13209
+ ...props
13210
+ }) {
13211
+ const [t1, t22, t32] = thresholds;
13212
+ const zones = [
13213
+ { upTo: t1, color: zoneColors[0] },
13214
+ { upTo: t22, color: zoneColors[1] },
13215
+ { upTo: t32, color: zoneColors[2] },
13216
+ { upTo: max, color: zoneColors[3] }
13217
+ ];
13218
+ const tickValues = [0, t1, t22, t32, max];
13219
+ const ticks = tickValues.map((v, i) => ({ value: v, label: labels[i] }));
13220
+ return /* @__PURE__ */ jsx(
13221
+ ZoneTrack,
13222
+ {
13223
+ className,
13224
+ zones,
13225
+ min: 0,
13226
+ max,
13227
+ trackHeight,
13228
+ ticks,
13229
+ marker: { type: "needle", value, color: needleColor },
13230
+ accessibilityLabel: `Fatigue: ${value}% velocity loss`,
13231
+ testID: "fatigue-meter",
13232
+ ...props
13233
+ }
13234
+ );
13235
+ }
13236
+ var HEAT = WORKOUT_TOKENS.heatmap;
13237
+ var NEUTRAL_TRACK = primitiveColors.charcoal[200];
13238
+ var MONO = "monospace";
13239
+ var HEAT_BY_ZONE = {
13240
+ under: HEAT.under,
13241
+ maintenance: HEAT.maintenance,
13242
+ productive: HEAT.productive,
13243
+ approaching: HEAT.approaching,
13244
+ over: HEAT.over
13245
+ };
13246
+ var ZONE_DESCRIPTION = {
13247
+ under: "below MEV",
13248
+ maintenance: "building toward MAV",
13249
+ productive: "in the productive zone",
13250
+ approaching: "approaching MRV",
13251
+ over: "over MRV"
13252
+ };
13253
+ var LANDMARK_NAME = {
13254
+ MEV: "Minimum Effective Volume",
13255
+ MAV: "Maximum Adaptive Volume",
13256
+ MRV: "Maximum Recoverable Volume"
13257
+ };
13258
+ function zoneForSets(sets, { mev, mav, mrv }) {
13259
+ if (sets < mev) return "under";
13260
+ if (sets < mav) return "maintenance";
13261
+ if (sets >= mrv) return "over";
13262
+ const midpoint = (mav + mrv) / 2;
13263
+ return sets < midpoint ? "productive" : "approaching";
13264
+ }
13265
+ function VolumeLandmarkBar({
13266
+ muscle,
13267
+ currentSets,
13268
+ landmarks,
13269
+ width = 220,
13270
+ trackHeight = 10,
13271
+ scaleMax,
13272
+ className,
13273
+ style,
13274
+ ...props
13275
+ }) {
13276
+ const { mev, mav, mrv } = landmarks;
13277
+ const max = scaleMax ?? mrv * 1.2;
13278
+ const zone = zoneForSets(currentSets, landmarks);
13279
+ const fillColor = HEAT_BY_ZONE[zone];
13280
+ const pct2 = mav > 0 ? Math.round(currentSets / mav * 100) : 0;
13281
+ return /* @__PURE__ */ jsxs(
13282
+ View,
13283
+ {
13284
+ className,
13285
+ style: [{ width, gap: 3 }, style],
13286
+ testID: "volume-landmark-bar",
13287
+ ...props,
13288
+ children: [
13289
+ /* @__PURE__ */ jsx(
13290
+ DataRow,
13291
+ {
13292
+ label: muscle,
13293
+ value: /* @__PURE__ */ jsxs(
13294
+ Text,
13295
+ {
13296
+ testID: "volume-landmark-pct",
13297
+ style: { fontSize: 14, fontWeight: "700", fontFamily: MONO, color: fillColor },
13298
+ children: [
13299
+ pct2,
13300
+ "%"
13301
+ ]
13302
+ }
13303
+ ),
13304
+ className: "py-0",
13305
+ testID: "volume-landmark-header"
13306
+ }
13307
+ ),
13308
+ /* @__PURE__ */ jsx(
13309
+ ZoneTrack,
13310
+ {
13311
+ zones: [{ upTo: max, color: NEUTRAL_TRACK }],
13312
+ max,
13313
+ marker: {
13314
+ type: "fill",
13315
+ value: currentSets,
13316
+ color: fillColor,
13317
+ // Glow when in the optimal productive band — the "sweet spot" cue.
13318
+ glow: zone === "productive"
13319
+ },
13320
+ trackColor: NEUTRAL_TRACK,
13321
+ trackHeight,
13322
+ ticks: [
13323
+ { value: mev, label: "MEV", tooltip: `${LANDMARK_NAME.MEV} \xB7 ${mev} sets/wk` },
13324
+ {
13325
+ value: mav,
13326
+ label: "MAV",
13327
+ emphasized: true,
13328
+ tooltip: `${LANDMARK_NAME.MAV} (target) \xB7 ${mav} sets/wk`
13329
+ },
13330
+ { value: mrv, label: "MRV", tooltip: `${LANDMARK_NAME.MRV} \xB7 ${mrv} sets/wk` }
13331
+ ],
13332
+ accessibilityLabel: `${muscle} weekly volume: ${currentSets} sets, ${pct2}% of MAV target, ${ZONE_DESCRIPTION[zone]}`,
13333
+ testID: "volume-landmark-track"
13334
+ }
13335
+ )
13336
+ ]
13337
+ }
13338
+ );
13339
+ }
13340
+ var t16 = getSemanticColors("dark");
13341
+ var statusToken = {
13342
+ productive: "status-success",
13343
+ threshold: "status-warning",
13344
+ stop: "status-error"
13345
+ };
13346
+ var statusDotVariant = {
13347
+ productive: "success",
13348
+ threshold: "warning",
13349
+ stop: "error"
13350
+ };
13351
+ var defaultLabel = {
13352
+ productive: "Productive",
13353
+ threshold: "Threshold",
13354
+ stop: "Stop"
13355
+ };
13356
+ function statusPillColor(status) {
13357
+ return t16[statusToken[status]];
13358
+ }
13359
+ function StatusPill2({ status, label, className, style, ...props }) {
13360
+ const color = statusPillColor(status);
13361
+ const text = label ?? defaultLabel[status];
13362
+ return /* @__PURE__ */ jsxs(
13363
+ View,
13364
+ {
13365
+ testID: "status-pill",
13366
+ className,
13367
+ style: [
13368
+ {
13369
+ flexDirection: "row",
13370
+ alignItems: "center",
13371
+ gap: 7,
13372
+ alignSelf: "flex-start",
13373
+ paddingVertical: 7,
13374
+ paddingHorizontal: 13,
13375
+ borderRadius: 9,
13376
+ backgroundColor: alpha(color, 0.14),
13377
+ borderWidth: 1,
13378
+ borderColor: alpha(color, 0.4)
13379
+ },
13380
+ style
13381
+ ],
13382
+ ...props,
13383
+ children: [
13384
+ /* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(StatusDot, { variant: statusDotVariant[status], glow: true }) }),
13385
+ /* @__PURE__ */ jsx(Text, { testID: "status-pill-label", style: { fontSize: 13, fontWeight: "800", color }, children: text })
13386
+ ]
13387
+ }
13388
+ );
13389
+ }
13390
+ var t17 = getSemanticColors("dark");
13391
+ var categoryToken = {
13392
+ productive: null,
13393
+ threshold: "status-warning",
13394
+ stop: "status-error"
13395
+ };
13396
+ var floodOpacity = {
13397
+ productive: 0,
13398
+ threshold: 0.12,
13399
+ stop: 0.18
13400
+ };
13401
+ function liveAuraColor(category) {
13402
+ const token = categoryToken[category];
13403
+ return token ? t17[token] : null;
13404
+ }
13405
+ function LiveAuraFrame({
13406
+ category,
13407
+ pulse = true,
13408
+ className,
13409
+ children,
13410
+ style,
13411
+ ...props
13412
+ }) {
13413
+ const color = liveAuraColor(category);
13414
+ const tint = color ? alpha(color, floodOpacity[category]) : "transparent";
13415
+ const floodStyle = {
13416
+ ...StyleSheet.absoluteFillObject,
13417
+ ...Platform.OS === "web" ? (
13418
+ // backgroundImage is a runtime RNW style, not in RN ViewStyle types.
13419
+ { backgroundImage: `radial-gradient(130% 95% at 50% 0%, ${tint}, transparent 62%)` }
13420
+ ) : { backgroundColor: tint }
13421
+ };
13422
+ return /* @__PURE__ */ jsxs(
13423
+ View,
13424
+ {
13425
+ testID: "live-aura-frame",
13426
+ className,
13427
+ style: [
13428
+ {
13429
+ position: "relative",
13430
+ overflow: "hidden",
13431
+ borderRadius: 10,
13432
+ backgroundColor: t17["background-base"],
13433
+ borderWidth: 1,
13434
+ borderColor: t17["border-default"]
13435
+ },
13436
+ style
13437
+ ],
13438
+ ...props,
13439
+ children: [
13440
+ color && /* @__PURE__ */ jsx(
13441
+ View,
13442
+ {
13443
+ testID: "live-aura-flood",
13444
+ accessibilityElementsHidden: true,
13445
+ pointerEvents: "none",
13446
+ className: category === "stop" && pulse ? "animate-pulse" : void 0,
13447
+ style: floodStyle
13448
+ }
13449
+ ),
13450
+ children
12144
13451
  ]
12145
13452
  }
12146
13453
  );
@@ -12351,28 +13658,28 @@ function Scatter({
12351
13658
  testID: "scatter-canvas",
12352
13659
  style: { position: "absolute", top: 0, left: 0, width, height, overflow: "hidden" },
12353
13660
  children: [
12354
- ticksOf(yd, TICK_COUNT).map((t12, i) => {
12355
- const y = toY(t12);
13661
+ ticksOf(yd, TICK_COUNT).map((t18, i) => {
13662
+ const y = toY(t18);
12356
13663
  return /* @__PURE__ */ jsx(
12357
13664
  View,
12358
13665
  {
12359
13666
  accessibilityElementsHidden: true,
12360
13667
  testID: "scatter-gridline-y",
12361
13668
  style: { position: "absolute", left: PLOT_LEFT2, width: innerW, top: y, height: 1, backgroundColor: GRID_LINE2 },
12362
- children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", left: -PLOT_LEFT2, top: -6, width: PLOT_LEFT2 - 6, textAlign: "right" }, children: formatTick(t12) })
13669
+ children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", left: -PLOT_LEFT2, top: -6, width: PLOT_LEFT2 - 6, textAlign: "right" }, children: formatTick(t18) })
12363
13670
  },
12364
13671
  `y-${i}`
12365
13672
  );
12366
13673
  }),
12367
- ticksOf(xd, TICK_COUNT).map((t12, i) => {
12368
- const x = toX(t12);
13674
+ ticksOf(xd, TICK_COUNT).map((t18, i) => {
13675
+ const x = toX(t18);
12369
13676
  return /* @__PURE__ */ jsx(
12370
13677
  View,
12371
13678
  {
12372
13679
  accessibilityElementsHidden: true,
12373
13680
  testID: "scatter-gridline-x",
12374
13681
  style: { position: "absolute", top: PLOT_TOP, height: innerH, left: x, width: 1, backgroundColor: GRID_LINE2 },
12375
- children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", top: innerH + 4, left: -16, width: 32, textAlign: "center" }, children: formatTick(t12) })
13682
+ children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", top: innerH + 4, left: -16, width: 32, textAlign: "center" }, children: formatTick(t18) })
12376
13683
  },
12377
13684
  `x-${i}`
12378
13685
  );
@@ -12437,7 +13744,7 @@ var STATUS_SUCCESS4 = sem2["status-success"];
12437
13744
  var STATUS_WARNING4 = sem2["status-warning"];
12438
13745
  var STATUS_ERROR4 = sem2["status-error"];
12439
13746
  var TRACK = "rgba(255,255,255,0.08)";
12440
- var DEFAULT_THRESHOLDS = [
13747
+ var DEFAULT_THRESHOLDS2 = [
12441
13748
  { value: 0, color: STATUS_ERROR4 },
12442
13749
  { value: 60, color: STATUS_WARNING4 },
12443
13750
  { value: 80, color: STATUS_SUCCESS4 }
@@ -12452,8 +13759,8 @@ function clamp012(n) {
12452
13759
  function formatValue2(v) {
12453
13760
  return Number.isInteger(v) ? `${v}` : v.toFixed(1);
12454
13761
  }
12455
- function bandColor2(fraction, bands, min, max) {
12456
- const value = min + fraction * (max - min);
13762
+ function bandColor2(fraction2, bands, min, max) {
13763
+ const value = min + fraction2 * (max - min);
12457
13764
  const sorted = [...bands].sort((a, b) => a.value - b.value);
12458
13765
  let color = sorted[0]?.color ?? STATUS_SUCCESS4;
12459
13766
  for (const band of sorted) {
@@ -12465,15 +13772,15 @@ function buildTicks(fill, size, activeColor) {
12465
13772
  const center = size / 2;
12466
13773
  const radius = center - size * 0.08;
12467
13774
  return Array.from({ length: SEGMENTS }, (_, i) => {
12468
- const fraction = (i + 0.5) / SEGMENTS;
12469
- const deg = ARC_START + fraction * ARC_SWEEP;
13775
+ const fraction2 = (i + 0.5) / SEGMENTS;
13776
+ const deg = ARC_START + fraction2 * ARC_SWEEP;
12470
13777
  const rad = deg * Math.PI / 180;
12471
13778
  return {
12472
13779
  key: i,
12473
13780
  left: center + radius * Math.cos(rad),
12474
13781
  top: center + radius * Math.sin(rad),
12475
13782
  rotate: `${deg - 90}deg`,
12476
- color: fraction <= fill ? activeColor(fraction) : TRACK
13783
+ color: fraction2 <= fill ? activeColor(fraction2) : TRACK
12477
13784
  };
12478
13785
  });
12479
13786
  }
@@ -12484,7 +13791,7 @@ function Gauge2({
12484
13791
  size = 160,
12485
13792
  label,
12486
13793
  unit,
12487
- thresholds = DEFAULT_THRESHOLDS,
13794
+ thresholds = DEFAULT_THRESHOLDS2,
12488
13795
  color,
12489
13796
  className,
12490
13797
  ...props
@@ -12507,23 +13814,23 @@ function Gauge2({
12507
13814
  testID: "gauge",
12508
13815
  ...props,
12509
13816
  children: [
12510
- ticks.map((t12) => /* @__PURE__ */ jsx(
13817
+ ticks.map((t18) => /* @__PURE__ */ jsx(
12511
13818
  View,
12512
13819
  {
12513
13820
  accessibilityElementsHidden: true,
12514
13821
  testID: "gauge-segment",
12515
13822
  style: {
12516
13823
  position: "absolute",
12517
- left: t12.left - tickThickness / 2,
12518
- top: t12.top - tickLength / 2,
13824
+ left: t18.left - tickThickness / 2,
13825
+ top: t18.top - tickLength / 2,
12519
13826
  width: tickThickness,
12520
13827
  height: tickLength,
12521
13828
  borderRadius: tickThickness / 2,
12522
- backgroundColor: t12.color,
12523
- transform: [{ rotate: t12.rotate }]
13829
+ backgroundColor: t18.color,
13830
+ transform: [{ rotate: t18.rotate }]
12524
13831
  }
12525
13832
  },
12526
- t12.key
13833
+ t18.key
12527
13834
  )),
12528
13835
  /* @__PURE__ */ jsxs(View, { style: { position: "absolute", top: 0, left: 0, width: size, height: size }, className: "items-center justify-center", children: [
12529
13836
  /* @__PURE__ */ jsxs(View, { className: "flex-row items-baseline gap-0.5", children: [
@@ -12569,7 +13876,7 @@ function BrandLockup({
12569
13876
  }
12570
13877
  var stateConfig = {
12571
13878
  // live = vivid green with an expanding ring; rest = solid amber (no pulse — operator); idle is dim.
12572
- live: { label: "LIVE", color: "success", pulse: "ping", textColor: "primary" },
13879
+ live: { label: "LIVE", color: "live", pulse: "ping", textColor: "primary" },
12573
13880
  rest: { label: "REST", color: "warning", pulse: false, textColor: "primary" },
12574
13881
  idle: { label: "IDLE", color: "default", pulse: false, textColor: "secondary" }
12575
13882
  };
@@ -12733,6 +14040,127 @@ function TopBar({
12733
14040
  }
12734
14041
  );
12735
14042
  }
14043
+ function NavItem({
14044
+ icon,
14045
+ label,
14046
+ active = false,
14047
+ live = false,
14048
+ onPress,
14049
+ className
14050
+ }) {
14051
+ const glyphColor = active ? "text-brand-primary" : "text-text-tertiary";
14052
+ const labelColor = active ? "text-brand-primary" : live ? "text-status-live-muted" : "text-text-tertiary";
14053
+ return /* @__PURE__ */ jsxs(
14054
+ Pressable,
14055
+ {
14056
+ accessibilityRole: "tab",
14057
+ accessibilityState: { selected: active },
14058
+ accessibilityLabel: label,
14059
+ onPress,
14060
+ className: cn("relative h-[46px] w-[60px] items-center justify-center", className),
14061
+ children: [
14062
+ active ? /* @__PURE__ */ jsx(
14063
+ View,
14064
+ {
14065
+ testID: "nav-item-accent",
14066
+ className: "absolute left-0 top-[13px] bottom-[13px] w-[3px] rounded-r-[3px] bg-brand-primary"
14067
+ }
14068
+ ) : null,
14069
+ /* @__PURE__ */ jsxs(
14070
+ View,
14071
+ {
14072
+ className: cn(
14073
+ "h-[46px] w-[46px] items-center justify-center gap-[3px] rounded-[11px]",
14074
+ glyphColor
14075
+ ),
14076
+ children: [
14077
+ icon,
14078
+ /* @__PURE__ */ jsx(
14079
+ Typography,
14080
+ {
14081
+ variant: "button",
14082
+ color: "inherit",
14083
+ className: cn("text-[8.5px] font-bold uppercase tracking-[0.4px]", labelColor),
14084
+ children: label
14085
+ }
14086
+ )
14087
+ ]
14088
+ }
14089
+ )
14090
+ ]
14091
+ }
14092
+ );
14093
+ }
14094
+ var defaultNavItems = [
14095
+ { key: "live", label: "Live", icon: /* @__PURE__ */ jsx(ActivityIcon, { size: 20, color: "currentColor" }) },
14096
+ { key: "review", label: "Review", icon: /* @__PURE__ */ jsx(HistoryIcon, { size: 20, color: "currentColor" }) },
14097
+ { key: "program", label: "Plan", icon: /* @__PURE__ */ jsx(LayersIcon, { size: 20, color: "currentColor" }) },
14098
+ { key: "body", label: "Body", icon: /* @__PURE__ */ jsx(PersonStandingIcon, { size: 20, color: "currentColor" }) }
14099
+ ];
14100
+ function SideNav({
14101
+ items = defaultNavItems,
14102
+ activeKey,
14103
+ onNavigate,
14104
+ liveKey = null,
14105
+ className
14106
+ }) {
14107
+ return /* @__PURE__ */ jsx(
14108
+ View,
14109
+ {
14110
+ accessibilityRole: "tablist",
14111
+ className: cn(
14112
+ "w-[60px] items-center gap-[6px] border-r border-border bg-background-base py-3",
14113
+ className
14114
+ ),
14115
+ children: items.map((item) => /* @__PURE__ */ jsx(
14116
+ NavItem,
14117
+ {
14118
+ icon: item.icon,
14119
+ label: item.label,
14120
+ active: item.key === activeKey,
14121
+ live: item.key === liveKey && item.key !== activeKey,
14122
+ onPress: () => onNavigate?.(item.key)
14123
+ },
14124
+ item.key
14125
+ ))
14126
+ }
14127
+ );
14128
+ }
14129
+ var DEMO_DEVICES = [
14130
+ { id: "Voltra-A3F2", nickname: "Left Cable", slot: "L", state: "connected" },
14131
+ { id: "Voltra-9B1C", nickname: "Right Cable", slot: "R", state: "connected" }
14132
+ ];
14133
+ function ContentPlaceholder() {
14134
+ return /* @__PURE__ */ jsx(View, { className: "flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(Text, { className: "font-heading text-sm text-text-tertiary", children: "main content region" }) });
14135
+ }
14136
+ function DashboardShell({
14137
+ activeKey = "live",
14138
+ navItems,
14139
+ liveKey = null,
14140
+ state = "live",
14141
+ devices = DEMO_DEVICES,
14142
+ subtitle = "wall dashboard",
14143
+ onNavigate,
14144
+ onSelectDevice,
14145
+ children,
14146
+ className
14147
+ }) {
14148
+ return /* @__PURE__ */ jsxs(View, { className: cn("flex-1 flex-row bg-surface-base", className), children: [
14149
+ /* @__PURE__ */ jsx(SideNav, { activeKey, items: navItems, liveKey, onNavigate }),
14150
+ /* @__PURE__ */ jsxs(View, { className: "flex-1", children: [
14151
+ /* @__PURE__ */ jsx(
14152
+ TopBar,
14153
+ {
14154
+ state,
14155
+ devices,
14156
+ subtitle,
14157
+ onSelectDevice
14158
+ }
14159
+ ),
14160
+ /* @__PURE__ */ jsx(View, { className: "flex-1", children: children ?? /* @__PURE__ */ jsx(ContentPlaceholder, {}) })
14161
+ ] })
14162
+ ] });
14163
+ }
12736
14164
 
12737
14165
  // src/utils/form.ts
12738
14166
  function getFieldValidationProps(state) {
@@ -12822,6 +14250,6 @@ function composeValidators(...validators) {
12822
14250
  };
12823
14251
  }
12824
14252
 
12825
- export { Accordion, AccordionButton, AccordionItem, AccordionPanel, Alert, AlertDescription, AlertTitle, Autocomplete, Avatar, AvatarBadge, AvatarGroup, Badge, BadgeText, BaseBadge, BluetoothIcon, BrandLockup, BreadcrumbItem, Breadcrumbs, Button, ButtonIcon, ButtonSpinner, ButtonText, CATEGORICAL_CVD_SAFE_MAX, CapacityBandChart, Caption, Card, CardContent, CardDescription, CardFooter, CardHeader, CardSkeleton, CardTitle, Checkbox, CheckboxGroup, Chip, CircularProgress, Collapse, CollapseButton, CollapseContent, DataRow, DateTime, DeviationBar, DeviceIndicator, DeviceMenu, DeviceRow, Divider, Drawer, DrawerBody, DrawerFooter, DrawerHeader, DumbbellIcon, EmptyState, ExerciseCard, FormActions, FormField, FormRow, FormSection, Gauge2 as Gauge, HStack, Heading, HelpTip, IconBox, Indicator, Input, InputBar, InputGroup, IntensityBar, Label, LabelWithHelp, Link, ListItem, ListItemContent, ListItemDivider, ListItemIcon, ListItemTrailing, Menu, MenuDivider, MenuGroup, MenuItem, MenuList, MenuTrigger, MesoCard, MesoProgressBar, MesoStatusCard, Metric, MetricGroup, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalTitle, MuscleGroupChip, Overline, Paragraph, PasswordInput, Pill, PlaceholderStrip, Popover, PopoverCloseButton, PopoverContent, PopoverTrigger, PrBadge, PrHistoryModal, Progress, ProgressSteps, Radio, RadioGroup, ReadinessCheck, RestTimer, Scatter, Section, SectionContent, SectionHeader, Select, SessionStatePill, SetRow, Sidebar, SidebarContent, SidebarDivider, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, Skeleton, SkeletonCard, SkeletonCircle, SkeletonListItem, SkeletonText, Sparkline, Spinner, Stack, StarIcon, StatusDot, StepContent, StepIndicator, StepLabel, Stepper, Step as StepperStep, StrengthTrendChart, SupersetWrapper, Surface, SvgIcon, Switch, TEMPO_PACING, Tab, TabList, TabPanel, TabPanels, Table, TableBody, TableCell, TableHeader, TableHeaderCell, TablePagination, TableRow, Tabs, TempoBar, TempoDisplay, Toast, ToastProvider, ToolbarButton, ToolbarButtonGroup, Tooltip, TopBar, Treemap, Typography, VStack, VelocityStrip, VoltrasMark, WeekRow, WeightBadge, WorkoutCard, WorkoutPill, alpha, applyThemePreset, audiobookPreset, bestTextColor, buttonSizes, calculateMeanVelocity, calculateVelocityLoss, categoricalPalette, cn, componentElevationRanges, composeValidators, createFieldId, createFlatShadow, createPressedHoverShadow, createPressedShadow, createRaisedHoverShadow, createRaisedShadow, darkThemeCSSVars, darken, defaultPreset, discreteRainbow, divergingScale, elevationSystem, formatDateTime, formatPrescription, formatSignedPct, formatVelocity, getBaseSurfaceColor, getCategoricalColor, getContrastText, getDiscreteRainbowColor, getElevationConfig, getElevationShadow, getElevationSurface, getFieldAriaProps, getFieldValidationProps, getGlowShadow, getHoverColors, getLuminance, getResultColor, getSemanticColors, getStatusColor, getTempoFillPct, getTempoPacingState, getThemeCSSVars, getValidatedElevation, getVelocityZoneColor, getVelocityZoneName, gluestackConfig, hasMaxLength, hasMinLength, hexToRgb, hsvToRgb, isCssPropertyManifest, isDark, isEmpty, isValidEmail, lightThemeCSSVars, lighten, linearGradient, neumorphicShadows, primitiveBorderRadius, primitiveBreakpoints, primitiveColors, primitiveRamps, primitiveShadows, primitiveSpacing, primitiveTypography, primitiveZIndex, resolveColor, rgbToHex, rgbToHsv, roundRpe, roundTempo, roundWeight, rpeColor, semanticColorsDark, semanticColorsLight, semanticTypography, sequentialEffort, shadowColors, surfaceGradient, useTable, useToast, useToolbarButton, validateCssPropertyManifest, validationRules };
14253
+ export { Accordion, AccordionButton, AccordionItem, AccordionPanel, ActivityIcon, Alert, AlertDescription, AlertTitle, AlertTriangleIcon, Autocomplete, Avatar, AvatarBadge, AvatarGroup, AwardIcon, Badge, BadgeText, BaseBadge, BluetoothIcon, BrandLockup, BreadcrumbItem, Breadcrumbs, Button, ButtonIcon, ButtonSpinner, ButtonText, CATEGORICAL_CVD_SAFE_MAX, CapacityBandChart, Caption, Card, CardContent, CardDescription, CardFooter, CardHeader, CardSkeleton, CardTitle, Checkbox, CheckboxGroup, Chip, CircleSlashIcon, CircularProgress, Collapse, CollapseButton, CollapseContent, DashboardShell, DataRow, DateTime, DeviationBar, DeviceIndicator, DeviceMenu, DeviceRow, Divider, Drawer, DrawerBody, DrawerFooter, DrawerHeader, DumbbellIcon, EmptyState, ExerciseCard, ExerciseCardHeading, ExerciseHeading, ExerciseIndicator, FatigueMeter, FormActions, FormField, FormRow, FormSection, Gauge2 as Gauge, HStack, Heading, HelpTip, HistoryIcon, INDICATOR_PRECEDENCE, IconBox, Indicator, InfoIcon, Input, InputBar, InputGroup, IntensityBar, Label, LabelWithHelp, LayersIcon, Link, ListItem, ListItemContent, ListItemDivider, ListItemIcon, ListItemTrailing, LiveAuraFrame, METRIC_FONT, Menu, MenuDivider, MenuGroup, MenuItem, MenuList, MenuTrigger, MesoCard, MesoProgressBar, MesoStatusCard, Metric, MetricCell, MetricGroup, MetricTiles, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalTitle, MuscleGroupChip, NavItem, Overline, Paragraph, PasswordInput, PersonStandingIcon, Pill, PlaceholderStrip, Popover, PopoverCloseButton, PopoverContent, PopoverTrigger, PrBadge, PrHistoryModal, Progress, ProgressSteps, Radio, RadioGroup, ReadinessCheck, RestTimer, SET_STRIP_VARIABLE_COLOR, SET_STRIP_ZONES, ScaleIcon, Scatter, ScheduleTiles, Section, SectionContent, SectionHeader, SegmentedBar, SegmentedProgressBar, Select, SessionHeader, SessionRail, SessionStatePill, SetBar, SetRow, SetStrip, SetTableHeader, SetsRepsLoad, SideNav, Sidebar, SidebarContent, SidebarDivider, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, Skeleton, SkeletonCard, SkeletonCircle, SkeletonListItem, SkeletonText, Sparkline, Spinner, Stack, StarIcon, StatusDot, StatusPill2 as StatusPill, StepContent, StepIndicator, StepLabel, Stepper, Step as StepperStep, StrengthTrendChart, SupersetWrapper, Surface, SvgIcon, Switch, TEMPO_PACING, Tab, TabList, TabPanel, TabPanels, Table, TableBody, TableCell, TableHeader, TableHeaderCell, TablePagination, TableRow, Tabs, TempoBar, TempoDisplay, Tile, TimerReadout, Toast, ToastProvider, ToolbarButton, ToolbarButtonGroup, Tooltip, TopBar, Treemap, TrendingDownIcon, Typography, VStack, VelocityStrip, VoltrasMark, VolumeLandmarkBar, WeekRow, WeightBadge, WorkoutCard, WorkoutPill, ZoneTrack, alpha, applyThemePreset, audiobookPreset, bestTextColor, buttonSizes, calculateMeanVelocity, calculateVelocityLoss, categoricalPalette, cn, componentElevationRanges, composeValidators, createFieldId, createFlatShadow, createPressedHoverShadow, createPressedShadow, createRaisedHoverShadow, createRaisedShadow, darkThemeCSSVars, darken, defaultNavItems, defaultPreset, discreteRainbow, divergingScale, elevationSystem, formatDateTime, formatDuration, formatPrescription, formatSignedPct, formatVelocity, getBaseSurfaceColor, getCategoricalColor, getContrastText, getDiscreteRainbowColor, getElevationConfig, getElevationShadow, getElevationSurface, getFieldAriaProps, getFieldValidationProps, getGlowShadow, getHoverColors, getLuminance, getResultColor, getSemanticColors, getStatusColor, getTempoFillPct, getTempoPacingState, getThemeCSSVars, getValidatedElevation, getVelocityZoneColor, getVelocityZoneName, gluestackConfig, hasMaxLength, hasMinLength, hexToRgb, hsvToRgb, isCssPropertyManifest, isDark, isEmpty, isValidEmail, lightThemeCSSVars, lighten, linearGradient, liveAuraColor, neumorphicShadows, paceTone, paceToneColor, primitiveBorderRadius, primitiveBreakpoints, primitiveColors, primitiveRamps, primitiveShadows, primitiveSpacing, primitiveTypography, primitiveZIndex, resolveColor, resolveIndicator, rgbToHex, rgbToHsv, roundRpe, roundTempo, roundWeight, rpeColor, semanticColorsDark, semanticColorsLight, semanticTypography, sequentialEffort, shadowColors, statusPillColor, surfaceGradient, useTable, useTimer, useToast, useToolbarButton, validateCssPropertyManifest, validationRules, velocityZoneColor };
12826
14254
  //# sourceMappingURL=index.mjs.map
12827
14255
  //# sourceMappingURL=index.mjs.map