@titan-design/react-ui 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist/bodymap.js +12 -7
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +12 -7
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +956 -128
  6. package/dist/index.d.ts +956 -128
  7. package/dist/index.js +2873 -981
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +2826 -982
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
  12. package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
  13. package/dist/pages.d.mts +1 -1
  14. package/dist/pages.d.ts +1 -1
  15. package/dist/pages.js +1659 -799
  16. package/dist/pages.js.map +1 -1
  17. package/dist/pages.mjs +1660 -800
  18. package/dist/pages.mjs.map +1 -1
  19. package/dist/theme/index.d.mts +16 -82
  20. package/dist/theme/index.d.ts +16 -82
  21. package/dist/theme/index.js +13 -115
  22. package/dist/theme/index.js.map +1 -1
  23. package/dist/theme/index.mjs +13 -115
  24. package/dist/theme/index.mjs.map +1 -1
  25. package/dist/theme/tokens-css.js +14 -6
  26. package/dist/theme/tokens-css.js.map +1 -1
  27. package/dist/theme/tokens-css.mjs +14 -6
  28. package/dist/theme/tokens-css.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/arch/Architecture.stories.tsx +870 -0
  31. package/src/arch/annotations.json +48 -0
  32. package/src/arch/arch-graph.json +4301 -0
  33. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
  34. package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
  35. package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
  36. package/src/components/custom/CircularTimer/index.ts +1 -0
  37. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  38. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  39. package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
  40. package/src/components/custom/Metric/Metric.stories.tsx +1 -1
  41. package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
  42. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  43. package/src/components/custom/Table/Table.stories.tsx +1 -1
  44. package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
  45. package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
  46. package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
  47. package/src/components/custom/TimerReadout/index.ts +1 -0
  48. package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
  49. package/src/components/custom/Typography/Typography.stories.tsx +1 -1
  50. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
  51. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
  52. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
  53. package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
  54. package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
  55. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
  56. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
  57. package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
  58. package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
  59. package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
  60. package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
  61. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
  62. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
  63. package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
  64. package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
  65. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
  66. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
  67. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
  68. package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
  69. package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
  70. package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
  71. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
  72. package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
  73. package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
  74. package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
  75. package/src/components/custom/Workout/FatigueMeter.test.tsx +85 -0
  76. package/src/components/custom/Workout/FatigueMeter.tsx +86 -0
  77. package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
  78. package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
  79. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
  80. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
  81. package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
  82. package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
  83. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
  84. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
  85. package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
  86. package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
  87. package/src/components/custom/Workout/MetricTiles.tsx +48 -0
  88. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
  89. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
  90. package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
  91. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
  92. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
  93. package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
  94. package/src/components/custom/Workout/README.md +193 -4
  95. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
  96. package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
  97. package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
  98. package/src/components/custom/Workout/RestTimer.tsx +155 -59
  99. package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
  100. package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
  101. package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
  102. package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
  103. package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
  104. package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
  105. package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
  106. package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
  107. package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
  108. package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
  109. package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
  110. package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
  111. package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
  112. package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
  113. package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
  114. package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
  115. package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
  116. package/src/components/custom/Workout/SessionHeader.tsx +152 -0
  117. package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
  118. package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
  119. package/src/components/custom/Workout/SessionRail.tsx +126 -0
  120. package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
  121. package/src/components/custom/Workout/SetBar.test.tsx +123 -0
  122. package/src/components/custom/Workout/SetBar.tsx +171 -0
  123. package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
  124. package/src/components/custom/Workout/SetRow.test.tsx +94 -166
  125. package/src/components/custom/Workout/SetRow.tsx +169 -232
  126. package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
  127. package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
  128. package/src/components/custom/Workout/SetStrip.tsx +71 -0
  129. package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
  130. package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
  131. package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
  132. package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
  133. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
  134. package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
  135. package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
  136. package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
  137. package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
  138. package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
  139. package/src/components/custom/Workout/StatusPill.tsx +88 -0
  140. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
  141. package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
  142. package/src/components/custom/Workout/TempoBar.stories.tsx +70 -1
  143. package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
  144. package/src/components/custom/Workout/TempoBar.tsx +107 -22
  145. package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
  146. package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
  147. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
  148. package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
  149. package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
  150. package/src/components/custom/Workout/VelocityStrip.stories.tsx +262 -56
  151. package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
  152. package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
  153. package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
  154. package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
  155. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
  156. package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
  157. package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
  158. package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
  159. package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
  160. package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
  161. package/src/components/custom/Workout/ZoneTrack.stories.tsx +139 -0
  162. package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
  163. package/src/components/custom/Workout/ZoneTrack.tsx +373 -0
  164. package/src/components/custom/Workout/index.ts +55 -2
  165. package/src/components/custom/Workout/metricText.stories.tsx +69 -0
  166. package/src/components/custom/Workout/metricText.tsx +34 -0
  167. package/src/components/custom/Workout/paceTone.test.ts +29 -0
  168. package/src/components/custom/Workout/paceTone.ts +27 -0
  169. package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
  170. package/src/components/custom/index.ts +2 -0
  171. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  172. package/src/components/icons/icons.stories.tsx +67 -2
  173. package/src/components/icons/icons.test.tsx +32 -2
  174. package/src/components/icons/icons.tsx +108 -0
  175. package/src/components/shell/BrandLockup.stories.tsx +3 -3
  176. package/src/components/shell/DashboardShell.stories.tsx +65 -0
  177. package/src/components/shell/DashboardShell.test.tsx +42 -0
  178. package/src/components/shell/DashboardShell.tsx +74 -0
  179. package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
  180. package/src/components/shell/DeviceMenu.stories.tsx +5 -5
  181. package/src/components/shell/DeviceRow.stories.tsx +3 -3
  182. package/src/components/shell/NavItem.stories.tsx +52 -0
  183. package/src/components/shell/NavItem.test.tsx +35 -0
  184. package/src/components/shell/NavItem.tsx +75 -0
  185. package/src/components/shell/README.md +30 -0
  186. package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
  187. package/src/components/shell/SessionStatePill.stories.tsx +3 -3
  188. package/src/components/shell/SessionStatePill.tsx +1 -1
  189. package/src/components/shell/SideNav.stories.tsx +81 -0
  190. package/src/components/shell/SideNav.test.tsx +51 -0
  191. package/src/components/shell/SideNav.tsx +74 -0
  192. package/src/components/shell/TopBar.stories.tsx +5 -5
  193. package/src/components/shell/index.ts +5 -0
  194. package/src/components/ui/alert/Alert.stories.tsx +86 -3
  195. package/src/components/ui/alert/Alert.test.tsx +52 -0
  196. package/src/components/ui/alert/Alert.tsx +54 -20
  197. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  198. package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
  199. package/src/components/ui/badge/Badge.stories.tsx +1 -1
  200. package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
  201. package/src/components/ui/button/Button.stories.tsx +1 -1
  202. package/src/components/ui/card/Card.stories.tsx +1 -1
  203. package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
  204. package/src/components/ui/chip/Chip.stories.tsx +1 -1
  205. package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
  206. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  207. package/src/components/ui/divider/Divider.stories.tsx +1 -1
  208. package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
  209. package/src/components/ui/form-field/FormField.stories.tsx +1 -1
  210. package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
  211. package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
  212. package/src/components/ui/index.ts +1 -0
  213. package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
  214. package/src/components/ui/indicator/Indicator.test.tsx +1 -0
  215. package/src/components/ui/indicator/Indicator.tsx +6 -1
  216. package/src/components/ui/input/Input.stories.tsx +1 -1
  217. package/src/components/ui/link/Link.stories.tsx +1 -1
  218. package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
  219. package/src/components/ui/menu/Menu.stories.tsx +1 -1
  220. package/src/components/ui/modal/Modal.stories.tsx +1 -1
  221. package/src/components/ui/pill/Pill.stories.tsx +1 -1
  222. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  223. package/src/components/ui/progress/Progress.stories.tsx +1 -1
  224. package/src/components/ui/progress/Progress.tsx +47 -22
  225. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  226. package/src/components/ui/section/Section.stories.tsx +1 -1
  227. package/src/components/ui/select/Select.stories.tsx +1 -1
  228. package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
  229. package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
  230. package/src/components/ui/stack/Stack.stories.tsx +1 -1
  231. package/src/components/ui/surface/Surface.stories.tsx +1 -1
  232. package/src/components/ui/switch/Switch.stories.tsx +1 -1
  233. package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
  234. package/src/components/ui/tile/Tile.stories.tsx +79 -0
  235. package/src/components/ui/tile/Tile.test.tsx +48 -0
  236. package/src/components/ui/tile/Tile.tsx +64 -0
  237. package/src/components/ui/tile/index.ts +2 -0
  238. package/src/components/ui/toast/Toast.stories.tsx +1 -1
  239. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  240. package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
  241. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
  242. package/src/hooks/index.ts +2 -0
  243. package/src/hooks/useTimer.test.ts +150 -0
  244. package/src/hooks/useTimer.ts +77 -0
  245. package/src/index.ts +3 -0
  246. package/src/pages.ts +1 -1
  247. package/src/stories/PresetShowcase.stories.tsx +1 -1
  248. package/src/stories/ThemePresets.stories.tsx +1 -1
  249. package/src/theme/ColorSystem.stories.tsx +1 -1
  250. package/src/theme/Colors.stories.tsx +5 -8
  251. package/src/theme/config.ts +4 -0
  252. package/src/theme/elevation.stories.tsx +1 -1
  253. package/src/theme/global.css +5 -1
  254. package/src/theme/shadows.stories.tsx +1 -1
  255. package/src/theme/tokens/primitives.ts +11 -107
  256. package/src/theme/tokens/semantic.ts +8 -0
  257. package/src/theme/workout-tokens.ts +11 -7
  258. package/tailwind.config.js +5 -0
package/dist/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
  }
@@ -86,6 +152,7 @@ var statusColors = {
86
152
  subtle: "bg-status-success-subtle",
87
153
  outline: "border-2 border-status-success bg-transparent",
88
154
  solid: "bg-status-success",
155
+ border: "border-status-success",
89
156
  icon: "text-status-success",
90
157
  text: "text-status-success"
91
158
  },
@@ -93,6 +160,7 @@ var statusColors = {
93
160
  subtle: "bg-status-info-subtle",
94
161
  outline: "border-2 border-status-info bg-transparent",
95
162
  solid: "bg-status-info",
163
+ border: "border-status-info",
96
164
  icon: "text-status-info",
97
165
  text: "text-status-info"
98
166
  },
@@ -100,6 +168,7 @@ var statusColors = {
100
168
  subtle: "bg-status-warning-subtle",
101
169
  outline: "border-2 border-status-warning bg-transparent",
102
170
  solid: "bg-status-warning",
171
+ border: "border-status-warning",
103
172
  icon: "text-status-warning",
104
173
  text: "text-status-warning"
105
174
  },
@@ -107,6 +176,7 @@ var statusColors = {
107
176
  subtle: "bg-status-error-subtle",
108
177
  outline: "border-2 border-status-error bg-transparent",
109
178
  solid: "bg-status-error",
179
+ border: "border-status-error",
110
180
  icon: "text-status-error",
111
181
  text: "text-status-error"
112
182
  }
@@ -120,6 +190,8 @@ var defaultIcons = {
120
190
  function Alert({
121
191
  status = "info",
122
192
  variant = "subtle",
193
+ size = "default",
194
+ message,
123
195
  icon,
124
196
  showIcon = true,
125
197
  onClose,
@@ -129,28 +201,43 @@ function Alert({
129
201
  }) {
130
202
  const colors = statusColors[status];
131
203
  const isSolid = variant === "solid";
204
+ const isCompact = size === "compact";
132
205
  return /* @__PURE__ */ jsxs(
133
206
  View,
134
207
  {
135
208
  accessibilityRole: "alert",
136
209
  className: cn(
137
- "flex-row items-start p-4 rounded-lg",
210
+ isCompact ? "flex-row items-center px-3 py-2 rounded-lg" : "flex-row items-start p-4 rounded-lg",
138
211
  colors[variant],
212
+ // A compact `subtle` pill gets a hairline status border so it reads as a
213
+ // defined cue on a dark wall (the CueFlag look); other variants carry their own.
214
+ isCompact && variant === "subtle" && cn("border", colors.border),
139
215
  className
140
216
  ),
141
217
  ...props,
142
218
  children: [
143
- showIcon && /* @__PURE__ */ jsx(View, { className: "mr-3 mt-0.5", children: icon || /* @__PURE__ */ jsx(
219
+ showIcon && /* @__PURE__ */ jsx(View, { className: isCompact ? "mr-2" : "mr-3 mt-0.5", children: icon || /* @__PURE__ */ jsx(
144
220
  Text,
145
221
  {
146
222
  className: cn(
147
- "text-lg font-bold",
223
+ "font-bold",
224
+ isCompact ? "text-base" : "text-lg",
148
225
  isSolid ? "text-white" : colors.icon
149
226
  ),
150
227
  children: defaultIcons[status]
151
228
  }
152
229
  ) }),
153
- /* @__PURE__ */ jsx(View, { className: "flex-1", children }),
230
+ /* @__PURE__ */ jsxs(View, { className: "flex-1", children: [
231
+ message != null && /* @__PURE__ */ jsx(
232
+ Text,
233
+ {
234
+ className: cn("text-sm font-semibold", isSolid ? "text-white" : colors.text),
235
+ testID: "alert-message",
236
+ children: message
237
+ }
238
+ ),
239
+ children
240
+ ] }),
154
241
  onClose && /* @__PURE__ */ jsx(
155
242
  Pressable,
156
243
  {
@@ -174,7 +261,7 @@ function AlertDescription({ children, className }) {
174
261
 
175
262
  // src/theme/tokens/primitives.ts
176
263
  var primitiveColors = {
177
- // Blue scale (primary brand)
264
+ // Blue scale
178
265
  blue: {
179
266
  50: "#EFF6FF",
180
267
  100: "#DBEAFE",
@@ -183,124 +270,36 @@ var primitiveColors = {
183
270
  400: "#60A5FA",
184
271
  500: "#3B82F6",
185
272
  600: "#5048E5",
186
- // Primary main
187
273
  700: "#3832A0",
188
- // Primary dark
189
274
  800: "#1E40AF",
190
275
  900: "#1E3A8A"
191
276
  },
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)
277
+ // Red scale
225
278
  red: {
226
279
  50: "#FEF2F2",
227
280
  100: "#FEE2E2",
228
281
  200: "#FECACA",
229
282
  300: "#FCA5A5",
230
283
  400: "#DA6868",
231
- // Error light
232
284
  500: "#EF4444",
233
285
  600: "#D14343",
234
- // Error main
235
286
  700: "#922E2E",
236
- // Error dark
237
287
  800: "#991B1B",
238
288
  900: "#7F1D1D"
239
289
  },
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)
290
+ // Vivid red scale
257
291
  redVivid: {
258
292
  50: "#FFECEE",
259
293
  100: "#FFD6DB",
260
294
  200: "#FFB3BC",
261
295
  300: "#FF8593",
262
296
  400: "#FF6070",
263
- // Error vivid light
264
297
  500: "#FF4757",
265
- // Error vivid main
266
298
  600: "#E63548",
267
299
  700: "#C42539",
268
- // Error vivid dark
269
300
  800: "#9E1C2C",
270
301
  900: "#7A1520"
271
302
  },
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
303
  // Neutral/Gray scale
305
304
  neutral: {
306
305
  50: "#FAFAFA",
@@ -315,18 +314,6 @@ var primitiveColors = {
315
314
  900: "#111827",
316
315
  950: "#030712"
317
316
  },
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
317
  // Charcoal scale (dark backgrounds)
331
318
  charcoal: {
332
319
  0: "#6E6E6E",
@@ -341,18 +328,6 @@ var primitiveColors = {
341
328
  800: "#131313",
342
329
  900: "#101010"
343
330
  },
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
331
  // Pure colors
357
332
  white: "#FFFFFF",
358
333
  black: "#000000",
@@ -743,6 +718,9 @@ var semanticColorsLight = {
743
718
  "status-success-light": primitiveRamps.green[200],
744
719
  "status-success-dark": primitiveRamps.green[600],
745
720
  "status-success-subtle": primitiveRamps.green[50],
721
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
722
+ "status-live": primitiveRamps.green[300],
723
+ "status-live-muted": primitiveRamps.green[500],
746
724
  "status-error": primitiveRamps.red[600],
747
725
  "status-error-light": primitiveRamps.red[500],
748
726
  "status-error-dark": primitiveRamps.red[700],
@@ -876,6 +854,9 @@ var semanticColorsDark = {
876
854
  "status-success-light": primitiveRamps.green[200],
877
855
  "status-success-dark": primitiveRamps.green[600],
878
856
  "status-success-subtle": "rgba(46, 213, 115, 0.12)",
857
+ // Live-session accent — its OWN role, decoupled from success so the two can diverge
858
+ "status-live": primitiveRamps.green[300],
859
+ "status-live-muted": primitiveRamps.green[500],
879
860
  "status-error": primitiveRamps.red[600],
880
861
  "status-error-light": primitiveRamps.red[500],
881
862
  "status-error-dark": primitiveRamps.red[700],
@@ -1470,6 +1451,8 @@ var lightThemeCSSVars = {
1470
1451
  "--color-on-brand-secondary": semanticColorsLight["on-brand-secondary"],
1471
1452
  "--color-status-success": semanticColorsLight["status-success"],
1472
1453
  "--color-status-success-subtle": semanticColorsLight["status-success-subtle"],
1454
+ "--color-status-live": semanticColorsLight["status-live"],
1455
+ "--color-status-live-muted": semanticColorsLight["status-live-muted"],
1473
1456
  "--color-status-error": semanticColorsLight["status-error"],
1474
1457
  "--color-status-error-subtle": semanticColorsLight["status-error-subtle"],
1475
1458
  "--color-status-warning": semanticColorsLight["status-warning"],
@@ -1566,6 +1549,8 @@ var darkThemeCSSVars = {
1566
1549
  "--color-on-brand-secondary": semanticColorsDark["on-brand-secondary"],
1567
1550
  "--color-status-success": semanticColorsDark["status-success"],
1568
1551
  "--color-status-success-subtle": semanticColorsDark["status-success-subtle"],
1552
+ "--color-status-live": semanticColorsDark["status-live"],
1553
+ "--color-status-live-muted": semanticColorsDark["status-live-muted"],
1569
1554
  "--color-status-error": semanticColorsDark["status-error"],
1570
1555
  "--color-status-error-subtle": semanticColorsDark["status-error-subtle"],
1571
1556
  "--color-status-warning": semanticColorsDark["status-warning"],
@@ -2783,6 +2768,7 @@ var colorStyles = {
2783
2768
  default: "bg-text-tertiary",
2784
2769
  primary: "bg-brand-primary",
2785
2770
  success: "bg-status-success",
2771
+ live: "bg-status-live",
2786
2772
  error: "bg-status-error",
2787
2773
  warning: "bg-status-warning",
2788
2774
  info: "bg-status-info",
@@ -2839,7 +2825,7 @@ function Indicator({
2839
2825
  colorClass,
2840
2826
  pulseMode === "opacity" && "animate-pulse",
2841
2827
  ring && "border-2 border-background-base",
2842
- glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
2828
+ glow && !customColor && (color === "success" || color === "live") && "shadow-[0_0_6px_rgba(46,213,115,0.6)]",
2843
2829
  glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
2844
2830
  glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
2845
2831
  glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
@@ -5101,6 +5087,8 @@ function CircularProgress({
5101
5087
  color = "primary",
5102
5088
  showValue = false,
5103
5089
  formatValue: formatValue3 = (v, m) => `${Math.round(v / m * 100)}%`,
5090
+ children,
5091
+ fill = false,
5104
5092
  className,
5105
5093
  ...props
5106
5094
  }) {
@@ -5109,11 +5097,12 @@ function CircularProgress({
5109
5097
  const circumference = 2 * Math.PI * radius;
5110
5098
  const strokeDashoffset = circumference - percentage / 100 * circumference;
5111
5099
  const center = size / 2;
5100
+ const boxStyle = fill ? { width: "100%", aspectRatio: 1 } : { width: size, height: size };
5112
5101
  return /* @__PURE__ */ jsxs(
5113
5102
  View,
5114
5103
  {
5115
5104
  className: cn("items-center justify-center", className),
5116
- style: { width: size, height: size },
5105
+ style: boxStyle,
5117
5106
  accessibilityRole: "progressbar",
5118
5107
  accessibilityValue: {
5119
5108
  min: 0,
@@ -5122,52 +5111,56 @@ function CircularProgress({
5122
5111
  },
5123
5112
  ...props,
5124
5113
  children: [
5125
- /* @__PURE__ */ jsx(
5114
+ /* @__PURE__ */ jsx(View, { className: cn(isIndeterminate && "animate-spin"), style: boxStyle, children: /* @__PURE__ */ jsxs(
5115
+ "svg",
5116
+ {
5117
+ width: "100%",
5118
+ height: "100%",
5119
+ viewBox: `0 0 ${size} ${size}`,
5120
+ style: { position: "absolute" },
5121
+ children: [
5122
+ /* @__PURE__ */ jsx(
5123
+ "circle",
5124
+ {
5125
+ cx: center,
5126
+ cy: center,
5127
+ r: radius,
5128
+ fill: "none",
5129
+ stroke: "var(--color-border-default)",
5130
+ strokeWidth
5131
+ }
5132
+ ),
5133
+ /* @__PURE__ */ jsx(
5134
+ "circle",
5135
+ {
5136
+ cx: center,
5137
+ cy: center,
5138
+ r: radius,
5139
+ fill: "none",
5140
+ stroke: colorVarMap[color],
5141
+ strokeWidth,
5142
+ strokeLinecap: "round",
5143
+ strokeDasharray: circumference,
5144
+ strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
5145
+ transform: `rotate(-90 ${center} ${center})`,
5146
+ style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
5147
+ }
5148
+ )
5149
+ ]
5150
+ }
5151
+ ) }),
5152
+ children != null ? /* @__PURE__ */ jsx(
5126
5153
  View,
5127
5154
  {
5128
- className: cn(isIndeterminate && "animate-spin"),
5129
- style: { width: size, height: size },
5130
- children: /* @__PURE__ */ jsxs(
5131
- "svg",
5132
- {
5133
- width: size,
5134
- height: size,
5135
- viewBox: `0 0 ${size} ${size}`,
5136
- style: { position: "absolute" },
5137
- children: [
5138
- /* @__PURE__ */ jsx(
5139
- "circle",
5140
- {
5141
- cx: center,
5142
- cy: center,
5143
- r: radius,
5144
- fill: "none",
5145
- stroke: "var(--color-border-default)",
5146
- strokeWidth
5147
- }
5148
- ),
5149
- /* @__PURE__ */ jsx(
5150
- "circle",
5151
- {
5152
- cx: center,
5153
- cy: center,
5154
- r: radius,
5155
- fill: "none",
5156
- stroke: colorVarMap[color],
5157
- strokeWidth,
5158
- strokeLinecap: "round",
5159
- strokeDasharray: circumference,
5160
- strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
5161
- transform: `rotate(-90 ${center} ${center})`,
5162
- style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
5163
- }
5164
- )
5165
- ]
5166
- }
5167
- )
5155
+ style: {
5156
+ position: "absolute",
5157
+ alignItems: "center",
5158
+ justifyContent: "center",
5159
+ pointerEvents: "none"
5160
+ },
5161
+ children
5168
5162
  }
5169
- ),
5170
- showValue && !isIndeterminate && /* @__PURE__ */ jsx(
5163
+ ) : showValue && !isIndeterminate && /* @__PURE__ */ jsx(
5171
5164
  Text,
5172
5165
  {
5173
5166
  className: "text-xs font-semibold text-text-primary absolute",
@@ -5263,7 +5256,7 @@ function ToastProvider({
5263
5256
  return id;
5264
5257
  }, [defaultDuration, maxToasts]);
5265
5258
  const removeToast = useCallback((id) => {
5266
- setToasts((prev) => prev.filter((t12) => t12.id !== id));
5259
+ setToasts((prev) => prev.filter((t18) => t18.id !== id));
5267
5260
  }, []);
5268
5261
  const removeAllToasts = useCallback(() => {
5269
5262
  setToasts([]);
@@ -6132,6 +6125,50 @@ function Pill({
6132
6125
  }
6133
6126
  return /* @__PURE__ */ jsx(View, { className: containerClasses, ...props, children: content });
6134
6127
  }
6128
+ function Tile({
6129
+ label,
6130
+ value,
6131
+ valueColor,
6132
+ align = "center",
6133
+ className,
6134
+ ...props
6135
+ }) {
6136
+ const isCenter = align === "center";
6137
+ return /* @__PURE__ */ jsxs(
6138
+ View,
6139
+ {
6140
+ className: cn(
6141
+ "flex-1 rounded-md bg-surface-raised px-1.5 py-2",
6142
+ isCenter ? "items-center" : "items-start",
6143
+ className
6144
+ ),
6145
+ ...props,
6146
+ children: [
6147
+ /* @__PURE__ */ jsx(
6148
+ Text,
6149
+ {
6150
+ className: cn(
6151
+ "text-[10px] font-bold uppercase tracking-wider text-text-tertiary",
6152
+ isCenter ? "text-center" : "text-left"
6153
+ ),
6154
+ children: label
6155
+ }
6156
+ ),
6157
+ /* @__PURE__ */ jsx(
6158
+ Text,
6159
+ {
6160
+ className: cn(
6161
+ "mt-0.5 font-mono text-sm font-bold text-text-primary",
6162
+ isCenter ? "text-center" : "text-left"
6163
+ ),
6164
+ style: valueColor ? { color: valueColor } : void 0,
6165
+ children: value
6166
+ }
6167
+ )
6168
+ ]
6169
+ }
6170
+ );
6171
+ }
6135
6172
  var variantStyles4 = {
6136
6173
  h1: "font-heading text-5xl font-bold leading-tight",
6137
6174
  h2: "font-heading text-4xl font-bold leading-tight",
@@ -6991,6 +7028,143 @@ function formatDateTime(value, format = "datetime", isUTC = false, fallback = "-
6991
7028
  }
6992
7029
  return formatDate(value, format, isUTC);
6993
7030
  }
7031
+ function formatDuration(ms) {
7032
+ const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
7033
+ const minutes = Math.floor(totalSeconds / 60);
7034
+ const seconds = totalSeconds % 60;
7035
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
7036
+ }
7037
+ function useTimer(options = {}) {
7038
+ const {
7039
+ mode = "up",
7040
+ durationMs,
7041
+ elapsedMs: controlledElapsedMs,
7042
+ running = false,
7043
+ autoTick = false,
7044
+ tickMs = 1e3
7045
+ } = options;
7046
+ const isControlled = controlledElapsedMs != null;
7047
+ const [internalElapsedMs, setInternalElapsedMs] = useState(0);
7048
+ useEffect(() => {
7049
+ if (isControlled || !autoTick || !running) return;
7050
+ const id = setInterval(() => {
7051
+ setInternalElapsedMs((prev) => prev + tickMs);
7052
+ }, tickMs);
7053
+ return () => clearInterval(id);
7054
+ }, [isControlled, autoTick, running, tickMs]);
7055
+ const elapsedMs = isControlled ? controlledElapsedMs : internalElapsedMs;
7056
+ const hasDuration = durationMs != null && durationMs > 0;
7057
+ const remainingMs = durationMs != null ? Math.max(0, durationMs - elapsedMs) : 0;
7058
+ let progress;
7059
+ if (hasDuration) {
7060
+ progress = Math.min(1, Math.max(0, elapsedMs / durationMs));
7061
+ } else {
7062
+ progress = mode === "down" ? 1 : 0;
7063
+ }
7064
+ const label = formatDuration(mode === "down" ? remainingMs : elapsedMs);
7065
+ const done = durationMs != null && elapsedMs >= durationMs;
7066
+ return { elapsedMs, remainingMs, progress, label, done };
7067
+ }
7068
+
7069
+ // src/components/custom/TimerReadout/TimerReadout.tsx
7070
+ var semantic = getSemanticColors("dark");
7071
+ var LIVE_GREEN = primitiveRamps.green[500];
7072
+ var SECONDARY = semantic["text-secondary"];
7073
+ var TERTIARY = semantic["text-tertiary"];
7074
+ var DEFAULT_GLYPH = "\u23F1";
7075
+ function TimerReadout({
7076
+ mode = "up",
7077
+ elapsedMs,
7078
+ durationMs,
7079
+ running = false,
7080
+ autoTick = false,
7081
+ showTotal = false,
7082
+ glyph = DEFAULT_GLYPH,
7083
+ className,
7084
+ ...rest
7085
+ }) {
7086
+ const { label } = useTimer({ mode, durationMs, elapsedMs, running, autoTick });
7087
+ const currentColor = running ? LIVE_GREEN : SECONDARY;
7088
+ const total = showTotal && durationMs != null ? formatDuration(durationMs) : null;
7089
+ return /* @__PURE__ */ jsx(
7090
+ View,
7091
+ {
7092
+ accessibilityRole: "timer",
7093
+ accessibilityLabel: total != null ? `${label} of ${total}` : label,
7094
+ className: cn("flex-row items-baseline justify-end", className),
7095
+ ...rest,
7096
+ children: /* @__PURE__ */ jsxs(Typography, { variant: "mono", style: { fontSize: 11, textAlign: "right" }, children: [
7097
+ /* @__PURE__ */ jsx(Text, { testID: "timer-readout-glyph", style: { color: currentColor, marginRight: 3 }, children: glyph }),
7098
+ /* @__PURE__ */ jsx(Text, { testID: "timer-readout-current", style: { color: currentColor }, children: label }),
7099
+ total != null ? /* @__PURE__ */ jsx(Text, { testID: "timer-readout-total", style: { color: TERTIARY }, children: ` / ${total}` }) : null
7100
+ ] })
7101
+ }
7102
+ );
7103
+ }
7104
+ function CircularTimer({
7105
+ durationMs,
7106
+ elapsedMs,
7107
+ mode = "down",
7108
+ size = 160,
7109
+ strokeWidth = 8,
7110
+ fill = false,
7111
+ color = "primary",
7112
+ doneLabel,
7113
+ doneColor = "success",
7114
+ controls,
7115
+ accessibilityLabel: accessibilityLabel2,
7116
+ testID
7117
+ }) {
7118
+ const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs });
7119
+ const isDone = done && mode === "down";
7120
+ const showDone = isDone && doneLabel != null;
7121
+ const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0;
7122
+ const arc = mode === "down" ? showDone ? 1 : remainingFrac : progress;
7123
+ const ringColor = showDone ? doneColor : color;
7124
+ const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1e3);
7125
+ const a11y = accessibilityLabel2 ?? (isDone ? "Timer complete" : `${remainingSec} seconds remaining`);
7126
+ return /* @__PURE__ */ jsxs(
7127
+ View,
7128
+ {
7129
+ style: { alignItems: "center", gap: 16 },
7130
+ accessibilityRole: "timer",
7131
+ accessibilityLabel: a11y,
7132
+ testID: testID ?? "circular-timer",
7133
+ children: [
7134
+ /* @__PURE__ */ jsx(
7135
+ CircularProgress,
7136
+ {
7137
+ value: arc,
7138
+ max: 1,
7139
+ size,
7140
+ strokeWidth,
7141
+ fill,
7142
+ color: ringColor,
7143
+ accessibilityLabel: "Timer ring",
7144
+ testID: "circular-timer-ring",
7145
+ children: /* @__PURE__ */ jsx(
7146
+ Text,
7147
+ {
7148
+ className: showDone ? void 0 : "text-text-primary",
7149
+ style: {
7150
+ fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
7151
+ fontFamily: '"Space Grotesk", sans-serif',
7152
+ fontWeight: "800",
7153
+ fontVariant: ["tabular-nums"],
7154
+ letterSpacing: showDone ? 1 : -1,
7155
+ ...showDone ? { color: colorVarMap[doneColor] } : null
7156
+ },
7157
+ testID: "circular-timer-label",
7158
+ children: showDone ? doneLabel : label
7159
+ }
7160
+ )
7161
+ }
7162
+ ),
7163
+ controls
7164
+ ]
7165
+ }
7166
+ );
7167
+ }
6994
7168
  var sizeConfig2 = {
6995
7169
  sm: { value: "text-lg font-bold", label: "text-xs", unit: "text-xs" },
6996
7170
  md: { value: "text-2xl font-bold", label: "text-xs", unit: "text-sm" },
@@ -7420,15 +7594,22 @@ var WORKOUT_TOKENS = {
7420
7594
  // Canonical 4-band performance scale — the single source for BOTH the
7421
7595
  // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
7422
7596
  // 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).
7597
+ // fastest/best; for RPE, green = easiest. This is the [0,2,3,4] subsample of
7598
+ // the canonical `sequentialEffort` primitive: an EVEN hue walk across the ramp
7599
+ // (green gold → orange → red). The earlier [0,1,2,4] spent two of its four
7600
+ // bands on adjacent yellows (amber-200 + amber-300) and skipped orange, reading
7601
+ // yellow-heavy with an abrupt jump to red; sampling amber-300 + orange-400
7602
+ // instead gives four cleanly separable bands that hold at 3px strip scale.
7603
+ // Consumed inline (RN).
7426
7604
  scale: {
7427
7605
  green: sequentialEffort[0],
7428
- yellow: sequentialEffort[1],
7429
- orange: sequentialEffort[2],
7430
- // gold band
7606
+ // green-300
7607
+ yellow: sequentialEffort[2],
7608
+ // amber-300 (gold)
7609
+ orange: sequentialEffort[3],
7610
+ // orange-400 (true orange)
7431
7611
  red: sequentialEffort[4]
7612
+ // red-600
7432
7613
  },
7433
7614
  // BodyMap volume heatmap — the canonical `divergingScale` (under → optimal →
7434
7615
  // over): a true diverging shape with a light green center, cool-blue under-
@@ -7553,8 +7734,8 @@ function roundTempo(tempo) {
7553
7734
  return tempo.map((v) => Math.round(v * 10) / 10);
7554
7735
  }
7555
7736
  function formatSignedPct(ratio) {
7556
- const pct = Math.round(ratio * 100);
7557
- return `${pct > 0 ? "+" : ""}${pct}%`;
7737
+ const pct2 = Math.round(ratio * 100);
7738
+ return `${pct2 > 0 ? "+" : ""}${pct2}%`;
7558
7739
  }
7559
7740
  function formatPrescription(p) {
7560
7741
  if (p == null) return null;
@@ -7634,6 +7815,11 @@ var TEMPO_PACING = {
7634
7815
  behindThresholdPct: 0.15,
7635
7816
  minPhaseDurationMs: 500
7636
7817
  };
7818
+ function formatTempoDelta(remainingMs) {
7819
+ const s = remainingMs / 1e3;
7820
+ const rounded = Math.abs(s) < 0.05 ? 0 : s;
7821
+ return rounded.toFixed(1);
7822
+ }
7637
7823
  function getTempoPacingState(elapsedMs, targetMs) {
7638
7824
  if (!targetMs || targetMs < TEMPO_PACING.minPhaseDurationMs) return "none";
7639
7825
  const ratio = elapsedMs / targetMs;
@@ -7646,24 +7832,33 @@ function getTempoFillPct(elapsedMs, targetMs) {
7646
7832
  }
7647
7833
  var PHASE_ORDER = ["concentric", "hold", "eccentric"];
7648
7834
  var PHASE_CONFIG = {
7649
- concentric: { label: "Con", color: t2["status-success"], flex: 2 },
7650
- hold: { label: "Hold", color: t2["brand-primary"], flex: 1 },
7651
- eccentric: { label: "Ecc", color: t2["status-warning"], flex: 3 }
7835
+ concentric: { label: "Con", wallLabel: "Concentric", color: t2["status-success"], flex: 2 },
7836
+ hold: { label: "Hold", wallLabel: "Hold", color: t2["brand-primary"], flex: 1 },
7837
+ eccentric: { label: "Ecc", wallLabel: "Eccentric", color: t2["status-warning"], flex: 3 }
7838
+ };
7839
+ var TEMPO_SIZES = {
7840
+ default: { labelMargin: 4, labelFont: 10, barGap: 2, barHeight: 20, radius: 4, segFont: 12 },
7841
+ wall: { labelMargin: 8, labelFont: 15, barGap: 4, barHeight: 40, radius: 6, segFont: 22 }
7652
7842
  };
7653
7843
  function TempoBar({
7654
7844
  activePhase,
7655
7845
  phaseElapsedMs,
7656
7846
  completed,
7657
7847
  target,
7848
+ size = "default",
7849
+ activeDisplay = "time",
7658
7850
  className,
7659
7851
  ...props
7660
7852
  }) {
7853
+ const s = TEMPO_SIZES[size];
7854
+ const [display, setDisplay] = useState(activeDisplay);
7855
+ const toggleDisplay = () => setDisplay((d) => d === "time" ? "delta" : "time");
7661
7856
  return /* @__PURE__ */ jsxs(View, { className, testID: "tempo-bar", ...props, children: [
7662
- /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", marginBottom: 4 }, children: PHASE_ORDER.map((phase) => {
7857
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", marginBottom: s.labelMargin }, children: PHASE_ORDER.map((phase) => {
7663
7858
  const config = PHASE_CONFIG[phase];
7664
- return /* @__PURE__ */ jsx(View, { style: { flex: config.flex, alignItems: "center" }, children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 10, color: t2["text-disabled"] }, children: config.label }) }, phase);
7859
+ return /* @__PURE__ */ jsx(View, { style: { flex: config.flex, alignItems: "center" }, children: /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: { fontSize: s.labelFont, color: t2["text-disabled"] }, children: size === "wall" ? config.wallLabel : config.label }) }, phase);
7665
7860
  }) }),
7666
- /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", gap: 2, height: 20 }, children: PHASE_ORDER.map((phase) => {
7861
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", gap: s.barGap, height: s.barHeight }, children: PHASE_ORDER.map((phase) => {
7667
7862
  const config = PHASE_CONFIG[phase];
7668
7863
  const isActive = activePhase === phase;
7669
7864
  const completedMs = completed?.[phase];
@@ -7675,7 +7870,7 @@ function TempoBar({
7675
7870
  style: {
7676
7871
  flex: config.flex,
7677
7872
  backgroundColor: alpha("#ffffff", 0.06),
7678
- borderRadius: 4,
7873
+ borderRadius: s.radius,
7679
7874
  overflow: "hidden"
7680
7875
  },
7681
7876
  children: isActive ? /* @__PURE__ */ jsx(
@@ -7683,9 +7878,21 @@ function TempoBar({
7683
7878
  {
7684
7879
  config,
7685
7880
  phaseElapsedMs,
7686
- targetMs
7881
+ targetMs,
7882
+ radius: s.radius,
7883
+ font: s.segFont,
7884
+ display,
7885
+ onToggleDisplay: toggleDisplay
7886
+ }
7887
+ ) : completedMs != null ? /* @__PURE__ */ jsx(
7888
+ CompletedSegment,
7889
+ {
7890
+ config,
7891
+ completedMs,
7892
+ targetMs,
7893
+ font: s.segFont
7687
7894
  }
7688
- ) : completedMs != null ? /* @__PURE__ */ jsx(CompletedSegment, { config, completedMs, targetMs }) : null
7895
+ ) : null
7689
7896
  },
7690
7897
  phase
7691
7898
  );
@@ -7695,49 +7902,65 @@ function TempoBar({
7695
7902
  function ActiveSegment({
7696
7903
  config,
7697
7904
  phaseElapsedMs,
7698
- targetMs
7905
+ targetMs,
7906
+ radius,
7907
+ font,
7908
+ display,
7909
+ onToggleDisplay
7699
7910
  }) {
7700
7911
  const pacing = getTempoPacingState(phaseElapsedMs, targetMs);
7701
7912
  const barColor = pacing === "behind" ? t2["status-error"] : config.color;
7702
7913
  const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
7703
- const label = targetMs ? `${formatDuration(phaseElapsedMs)} / ${formatDuration(targetMs)}` : formatDuration(phaseElapsedMs);
7704
- return /* @__PURE__ */ jsxs(View, { style: { height: "100%", position: "relative" }, testID: `tempo-segment-active-${config.label}`, children: [
7705
- /* @__PURE__ */ jsx(
7706
- View,
7707
- {
7708
- style: {
7709
- position: "absolute",
7710
- left: 0,
7711
- top: 0,
7712
- bottom: 0,
7713
- width: `${fillPct}%`,
7714
- backgroundColor: alpha(barColor, 0.3),
7715
- borderRadius: 4
7716
- }
7717
- }
7718
- ),
7719
- /* @__PURE__ */ jsx(
7720
- View,
7721
- {
7722
- style: {
7723
- height: "100%",
7724
- flexDirection: "row",
7725
- alignItems: "center",
7726
- justifyContent: "center"
7727
- },
7728
- children: /* @__PURE__ */ jsx(Text, { style: { fontSize: 12, fontWeight: "700", color: barColor }, children: label })
7729
- }
7730
- )
7731
- ] });
7914
+ const label = display === "delta" && targetMs != null ? formatTempoDelta(targetMs - phaseElapsedMs) : targetMs != null ? `${formatDuration2(phaseElapsedMs)} / ${formatDuration2(targetMs)}` : formatDuration2(phaseElapsedMs);
7915
+ return /* @__PURE__ */ jsxs(
7916
+ Pressable,
7917
+ {
7918
+ onPress: onToggleDisplay,
7919
+ style: { height: "100%", position: "relative" },
7920
+ accessibilityRole: "button",
7921
+ accessibilityLabel: `Tempo readout: ${display === "delta" ? "delta" : "time"} \u2014 tap to switch`,
7922
+ testID: `tempo-segment-active-${config.label}`,
7923
+ children: [
7924
+ /* @__PURE__ */ jsx(
7925
+ View,
7926
+ {
7927
+ style: {
7928
+ position: "absolute",
7929
+ left: 0,
7930
+ top: 0,
7931
+ bottom: 0,
7932
+ width: `${fillPct}%`,
7933
+ backgroundColor: alpha(barColor, 0.3),
7934
+ borderRadius: radius
7935
+ }
7936
+ }
7937
+ ),
7938
+ /* @__PURE__ */ jsx(
7939
+ View,
7940
+ {
7941
+ style: {
7942
+ height: "100%",
7943
+ flexDirection: "row",
7944
+ alignItems: "center",
7945
+ justifyContent: "center"
7946
+ },
7947
+ children: /* @__PURE__ */ jsx(Text, { style: { fontSize: font, fontWeight: "700", color: barColor }, children: label })
7948
+ }
7949
+ )
7950
+ ]
7951
+ }
7952
+ );
7732
7953
  }
7733
7954
  function CompletedSegment({
7734
7955
  config,
7735
7956
  completedMs,
7736
- targetMs
7957
+ targetMs,
7958
+ font
7737
7959
  }) {
7738
7960
  const pacing = getTempoPacingState(completedMs, targetMs);
7739
7961
  const hitTarget = pacing !== "behind";
7740
7962
  const indicator = targetMs && targetMs >= TEMPO_PACING.minPhaseDurationMs ? hitTarget ? " \u2713" : " \u2717" : "";
7963
+ const cueColor = hitTarget ? config.color : t2["status-error"];
7741
7964
  return /* @__PURE__ */ jsx(
7742
7965
  View,
7743
7966
  {
@@ -7746,20 +7969,46 @@ function CompletedSegment({
7746
7969
  flexDirection: "row",
7747
7970
  alignItems: "center",
7748
7971
  justifyContent: "center",
7749
- backgroundColor: alpha(config.color, 0.15)
7972
+ backgroundColor: alpha(cueColor, 0.15)
7750
7973
  },
7751
7974
  testID: `tempo-segment-completed-${config.label}`,
7752
- children: /* @__PURE__ */ jsxs(Text, { style: { fontSize: 12, fontWeight: "500", color: alpha(config.color, 0.7) }, children: [
7753
- formatDuration(completedMs),
7754
- indicator
7755
- ] })
7975
+ children: /* @__PURE__ */ jsxs(
7976
+ Text,
7977
+ {
7978
+ style: {
7979
+ fontSize: font,
7980
+ fontWeight: "500",
7981
+ color: hitTarget ? alpha(config.color, 0.7) : cueColor
7982
+ },
7983
+ children: [
7984
+ formatDuration2(completedMs),
7985
+ indicator
7986
+ ]
7987
+ }
7988
+ )
7756
7989
  }
7757
7990
  );
7758
7991
  }
7759
- function formatDuration(ms) {
7992
+ function formatDuration2(ms) {
7760
7993
  const s = ms / 1e3;
7761
7994
  return s < 10 ? s.toFixed(1) : Math.round(s).toString();
7762
7995
  }
7996
+ var METRIC_FONT = "Inter, sans-serif";
7997
+ function MetricCell({ children, color, fontSize = 11 }) {
7998
+ return /* @__PURE__ */ jsx(
7999
+ Text,
8000
+ {
8001
+ style: {
8002
+ fontFamily: METRIC_FONT,
8003
+ fontSize,
8004
+ color,
8005
+ fontWeight: "600",
8006
+ letterSpacing: 1
8007
+ },
8008
+ children
8009
+ }
8010
+ );
8011
+ }
7763
8012
 
7764
8013
  // src/components/custom/Workout/TempoDisplay.tsx
7765
8014
  var t3 = getSemanticColors("dark");
@@ -7778,34 +8027,10 @@ function TempoValue({
7778
8027
  color,
7779
8028
  fontSize
7780
8029
  }) {
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
- );
8030
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: value });
7794
8031
  }
7795
8032
  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
- );
8033
+ return /* @__PURE__ */ jsx(MetricCell, { color, fontSize, children: "-" });
7809
8034
  }
7810
8035
  var LIVE_PHASES = [
7811
8036
  { key: "eccentric", color: phaseColors.eccentric },
@@ -7867,6 +8092,7 @@ function TempoDisplay({
7867
8092
  tempo,
7868
8093
  size = "md",
7869
8094
  colored = true,
8095
+ showLabel = true,
7870
8096
  showInfo = true,
7871
8097
  live,
7872
8098
  onPress,
@@ -7900,7 +8126,7 @@ function TempoDisplay({
7900
8126
  },
7901
8127
  ...props,
7902
8128
  children: [
7903
- /* @__PURE__ */ jsx(
8129
+ showLabel && /* @__PURE__ */ jsx(
7904
8130
  Text,
7905
8131
  {
7906
8132
  style: {
@@ -8128,12 +8354,12 @@ var ZONE = {
8128
8354
  // severe over-target
8129
8355
  };
8130
8356
  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 };
8357
+ function zoneForPct(pct2) {
8358
+ if (pct2 >= 125) return { color: ZONE.over3, over: 3 };
8359
+ if (pct2 >= 115) return { color: ZONE.over2, over: 2 };
8360
+ if (pct2 > 100) return { color: ZONE.over1, over: 1 };
8361
+ if (pct2 === 100) return { color: ZONE.target, over: 0 };
8362
+ if (pct2 >= 75) return { color: ZONE.approaching, over: 0 };
8137
8363
  return { color: ZONE.building, over: 0 };
8138
8364
  }
8139
8365
  function clampFill(value) {
@@ -8141,9 +8367,9 @@ function clampFill(value) {
8141
8367
  if (value > 1) return 1;
8142
8368
  return value;
8143
8369
  }
8144
- function isAtTarget(pct, threshold) {
8370
+ function isAtTarget(pct2, threshold) {
8145
8371
  if (threshold == null) return false;
8146
- return Math.abs(pct - Math.round(threshold * 100)) <= 5;
8372
+ return Math.abs(pct2 - Math.round(threshold * 100)) <= 5;
8147
8373
  }
8148
8374
  function IntensityBar({
8149
8375
  level,
@@ -8157,9 +8383,9 @@ function IntensityBar({
8157
8383
  }) {
8158
8384
  const isVertical = orientation === "vertical";
8159
8385
  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);
8386
+ const pct2 = Math.round(Math.max(0, level) * 100);
8387
+ const zone = zoneForPct(pct2);
8388
+ const atTarget = isAtTarget(pct2, threshold);
8163
8389
  const [fillAnim] = useState(() => new Animated.Value(fillLevel));
8164
8390
  useEffect(() => {
8165
8391
  const animation = Animated.timing(fillAnim, {
@@ -8210,8 +8436,8 @@ function IntensityBar({
8210
8436
  className,
8211
8437
  style: { alignItems: "center" },
8212
8438
  accessibilityRole: "progressbar",
8213
- accessibilityValue: { min: 0, max: 100, now: pct },
8214
- accessibilityLabel: `Intensity level: ${pct}%`,
8439
+ accessibilityValue: { min: 0, max: 100, now: pct2 },
8440
+ accessibilityLabel: `Intensity level: ${pct2}%`,
8215
8441
  testID: "intensity-bar",
8216
8442
  ...props,
8217
8443
  children: [
@@ -8257,7 +8483,7 @@ function IntensityBar({
8257
8483
  accessibilityElementsHidden: true,
8258
8484
  testID: "intensity-label",
8259
8485
  children: [
8260
- pct,
8486
+ pct2,
8261
8487
  "%"
8262
8488
  ]
8263
8489
  }
@@ -8442,13 +8668,257 @@ function WorkoutPill({
8442
8668
  }
8443
8669
  return pill;
8444
8670
  }
8671
+ var PULSE_HALF_MS = 950;
8672
+ var PULSE_MIN_OPACITY = 0.45;
8673
+ var SEGMENTED_BAR_GAP = 5;
8674
+ function usePulse2(active) {
8675
+ const [value] = useState(() => new Animated.Value(0));
8676
+ useEffect(() => {
8677
+ if (!active) return;
8678
+ const loop = Animated.loop(
8679
+ Animated.sequence([
8680
+ Animated.timing(value, {
8681
+ toValue: 1,
8682
+ duration: PULSE_HALF_MS,
8683
+ easing: Easing.inOut(Easing.ease),
8684
+ useNativeDriver: false
8685
+ }),
8686
+ Animated.timing(value, {
8687
+ toValue: 0,
8688
+ duration: PULSE_HALF_MS,
8689
+ easing: Easing.inOut(Easing.ease),
8690
+ useNativeDriver: false
8691
+ })
8692
+ ])
8693
+ );
8694
+ loop.start();
8695
+ return () => loop.stop();
8696
+ }, [active, value]);
8697
+ return value;
8698
+ }
8699
+ function SegmentedBar({
8700
+ segments,
8701
+ height = 8,
8702
+ gap = SEGMENTED_BAR_GAP,
8703
+ radius = 2,
8704
+ marker = null,
8705
+ segmentTestID,
8706
+ style,
8707
+ ...props
8708
+ }) {
8709
+ const pulse = usePulse2(segments.some((s) => s.pulse));
8710
+ return /* @__PURE__ */ jsxs(View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
8711
+ segments.map((seg, i) => {
8712
+ const fillStyle = {
8713
+ width: `${(seg.fill ?? 1) * 100}%`,
8714
+ height: "100%",
8715
+ backgroundColor: seg.color,
8716
+ opacity: seg.opacity
8717
+ };
8718
+ const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
8719
+ return /* @__PURE__ */ jsx(
8720
+ View,
8721
+ {
8722
+ style: {
8723
+ flex: seg.weight ?? 1,
8724
+ minWidth: 0,
8725
+ height: "100%",
8726
+ marginLeft: seg.leadingGap,
8727
+ borderRadius: radius,
8728
+ overflow: "hidden"
8729
+ },
8730
+ children: seg.pulse ? /* @__PURE__ */ jsx(
8731
+ Animated.View,
8732
+ {
8733
+ style: {
8734
+ ...fillStyle,
8735
+ ...seg.pulseColor ? {
8736
+ backgroundColor: pulse.interpolate({
8737
+ inputRange: [0, 1],
8738
+ outputRange: seg.pulseColor
8739
+ })
8740
+ } : {
8741
+ opacity: pulse.interpolate({
8742
+ inputRange: [0, 1],
8743
+ outputRange: [PULSE_MIN_OPACITY, 1]
8744
+ })
8745
+ }
8746
+ },
8747
+ accessibilityElementsHidden: true,
8748
+ testID
8749
+ }
8750
+ ) : /* @__PURE__ */ jsx(View, { style: fillStyle, accessibilityElementsHidden: true, testID })
8751
+ },
8752
+ i
8753
+ );
8754
+ }),
8755
+ marker ? /* @__PURE__ */ jsx(
8756
+ View,
8757
+ {
8758
+ style: {
8759
+ position: "absolute",
8760
+ left: `${marker.position * 100}%`,
8761
+ top: 0,
8762
+ bottom: 0,
8763
+ width: 2,
8764
+ backgroundColor: marker.color
8765
+ },
8766
+ accessibilityElementsHidden: true,
8767
+ testID: "segmented-bar-marker"
8768
+ }
8769
+ ) : null
8770
+ ] });
8771
+ }
8772
+
8773
+ // src/components/custom/Workout/SetBar.tsx
8774
+ var SET_STRIP_ZONES = {
8775
+ slow: primitiveRamps.red[600],
8776
+ moderate: primitiveRamps.orange[400],
8777
+ fast: primitiveRamps.amber[300],
8778
+ fastest: primitiveRamps.green[300]
8779
+ };
8780
+ var TODO_COLOR = primitiveColors.charcoal[300];
8781
+ var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
8782
+ var NOTCH_GAP = 2;
8783
+ var CLUSTER_GAP = 3;
8784
+ var MYO_UPCOMING_CLUSTERS = 3;
8785
+ var MYO_UPCOMING_CLUSTER_LEN = 5;
8786
+ var MYO_UPCOMING_FADE = 0.6;
8787
+ var PULSE_RANGE = {
8788
+ [SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
8789
+ [SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
8790
+ [SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
8791
+ [SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
8792
+ };
8793
+ function velocityZoneColor(v) {
8794
+ if (v < 0.5) return SET_STRIP_ZONES.slow;
8795
+ if (v < 0.75) return SET_STRIP_ZONES.moderate;
8796
+ if (v < 1) return SET_STRIP_ZONES.fast;
8797
+ return SET_STRIP_ZONES.fastest;
8798
+ }
8799
+ function chunkedSegments(chunks, gap) {
8800
+ return chunks.flatMap(
8801
+ (vels, ci) => vels.map((v, ri) => ({
8802
+ color: velocityZoneColor(v),
8803
+ ...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
8804
+ }))
8805
+ );
8806
+ }
8807
+ function rangeSegments(floor, max, doneVels) {
8808
+ return Array.from({ length: max }, (_, i) => {
8809
+ if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
8810
+ return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
8811
+ });
8812
+ }
8813
+ function myoUpcomingSegments(activationLen, clusterCount) {
8814
+ const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
8815
+ const trail = Array.from(
8816
+ { length: clusterCount },
8817
+ (_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
8818
+ color: SET_STRIP_VARIABLE_COLOR,
8819
+ opacity: MYO_UPCOMING_FADE ** ci,
8820
+ ...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
8821
+ }))
8822
+ ).flat();
8823
+ return [...activation, ...trail];
8824
+ }
8825
+ function setSegments(set) {
8826
+ if (set.status === "todo") {
8827
+ return [{ color: TODO_COLOR }];
8828
+ }
8829
+ if (set.status === "done") {
8830
+ return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
8831
+ }
8832
+ if (set.status === "range") {
8833
+ return rangeSegments(set.floor, set.max, set.doneVels);
8834
+ }
8835
+ if (set.status === "drop") {
8836
+ return chunkedSegments(set.subloads, NOTCH_GAP);
8837
+ }
8838
+ if (set.status === "myo") {
8839
+ return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
8840
+ }
8841
+ if (set.status === "myo-upcoming") {
8842
+ return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
8843
+ }
8844
+ const performed = set.velocities.map((v) => {
8845
+ const color = velocityZoneColor(v);
8846
+ return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
8847
+ });
8848
+ const remaining = Math.max(0, set.planned - set.velocities.length);
8849
+ return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
8850
+ }
8851
+ function setBarSegmentTestID(seg) {
8852
+ if (seg.pulse) return "set-strip-pulse";
8853
+ if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
8854
+ return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
8855
+ }
8856
+ function SetBar({ set, height = 8 }) {
8857
+ return /* @__PURE__ */ jsx(
8858
+ SegmentedBar,
8859
+ {
8860
+ segments: setSegments(set),
8861
+ height,
8862
+ gap: 0,
8863
+ radius: 0,
8864
+ segmentTestID: setBarSegmentTestID,
8865
+ style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
8866
+ testID: "set-strip-set"
8867
+ }
8868
+ );
8869
+ }
8870
+
8871
+ // src/components/custom/Workout/SetStrip.tsx
8872
+ var SET_STRIP_GAP = 5;
8873
+ function setCategory(set) {
8874
+ switch (set.status) {
8875
+ case "done":
8876
+ case "drop":
8877
+ case "myo":
8878
+ return "done";
8879
+ case "active":
8880
+ return "active";
8881
+ case "range":
8882
+ return set.doneVels.length > 0 ? "active" : "upcoming";
8883
+ case "todo":
8884
+ case "myo-upcoming":
8885
+ return "upcoming";
8886
+ }
8887
+ }
8888
+ function describeSets(sets) {
8889
+ const done = sets.filter((s) => setCategory(s) === "done").length;
8890
+ const active = sets.filter((s) => setCategory(s) === "active").length;
8891
+ const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
8892
+ return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
8893
+ }
8894
+ function SetStrip({ sets, height = 8, className, ...props }) {
8895
+ return /* @__PURE__ */ jsx(
8896
+ View,
8897
+ {
8898
+ className,
8899
+ style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
8900
+ accessibilityRole: "image",
8901
+ accessibilityLabel: describeSets(sets),
8902
+ testID: "set-strip",
8903
+ ...props,
8904
+ children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
8905
+ }
8906
+ );
8907
+ }
8908
+
8909
+ // src/components/custom/Workout/VelocityStrip.tsx
8445
8910
  var VEL_COLORS = WORKOUT_TOKENS.scale;
8446
- var zoneIdToScaleToken = {
8447
- speed: "green",
8448
- power: "yellow",
8449
- strengthSpeed: "orange",
8450
- maximalStrength: "red",
8451
- grinding: "red"
8911
+ var bandIdToEffortColor = {
8912
+ speed: sequentialEffort[0],
8913
+ // green-300
8914
+ power: sequentialEffort[2],
8915
+ // amber-300 (gold)
8916
+ strengthSpeed: sequentialEffort[3],
8917
+ // orange-400
8918
+ maximalStrength: sequentialEffort[4],
8919
+ // red-600
8920
+ grinding: sequentialEffort[5]
8921
+ // red-700 — the distinct 5th band
8452
8922
  };
8453
8923
  function getVelocityZoneColor(velocity) {
8454
8924
  if (velocity >= 1) return "vel-green";
@@ -8487,14 +8957,140 @@ function classifyBand(velocity, bands) {
8487
8957
  return bands[bands.length - 1];
8488
8958
  }
8489
8959
  function bandColor(band) {
8490
- const token = zoneIdToScaleToken[band.id];
8491
- return token ? VEL_COLORS[token] : VEL_COLORS.green;
8960
+ return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
8492
8961
  }
8493
8962
  function getLossStyle(loss) {
8494
8963
  if (loss > 25) return { color: VEL_COLORS.red };
8495
8964
  if (loss > 20) return { color: VEL_COLORS.orange };
8496
8965
  return null;
8497
8966
  }
8967
+ var REP_GAP = 2;
8968
+ var WIDE_GAP = 8;
8969
+ var TODO_COLOR2 = primitiveColors.charcoal[300];
8970
+ var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
8971
+ function deriveDoneVelocities(set) {
8972
+ switch (set.type) {
8973
+ case "drop":
8974
+ return set.subloads.flat();
8975
+ case "myo":
8976
+ return [...set.activation, ...set.clusters.flat()];
8977
+ default:
8978
+ return set.velocities;
8979
+ }
8980
+ }
8981
+ function chunkedRepSlots(chunks) {
8982
+ const slots = [];
8983
+ chunks.forEach((chunk) => {
8984
+ chunk.forEach((velocity, ri) => {
8985
+ const first = slots.length === 0;
8986
+ slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP });
8987
+ });
8988
+ });
8989
+ return slots;
8990
+ }
8991
+ function buildSlots(set) {
8992
+ switch (set.type) {
8993
+ case "straight": {
8994
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
8995
+ return Array.from({ length: total }, (_, i) => {
8996
+ const done = i < set.velocities.length;
8997
+ return {
8998
+ kind: done ? "rep" : "todo",
8999
+ velocity: done ? set.velocities[i] : void 0,
9000
+ leadingGap: i === 0 ? 0 : REP_GAP
9001
+ };
9002
+ });
9003
+ }
9004
+ case "range": {
9005
+ const total = Math.max(set.max, set.velocities.length);
9006
+ return Array.from({ length: total }, (_, i) => {
9007
+ const kind = i < set.velocities.length ? "rep" : i < set.floor ? "todo" : "variable";
9008
+ return {
9009
+ kind,
9010
+ velocity: kind === "rep" ? set.velocities[i] : void 0,
9011
+ leadingGap: i === 0 ? 0 : REP_GAP
9012
+ };
9013
+ });
9014
+ }
9015
+ case "amrap": {
9016
+ const reps = set.velocities.map((velocity, i) => ({
9017
+ kind: "rep",
9018
+ velocity,
9019
+ leadingGap: i === 0 ? 0 : REP_GAP
9020
+ }));
9021
+ reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP });
9022
+ return reps;
9023
+ }
9024
+ case "drop":
9025
+ return chunkedRepSlots(set.subloads);
9026
+ case "myo": {
9027
+ const slots = chunkedRepSlots([set.activation, ...set.clusters]);
9028
+ if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP });
9029
+ return slots;
9030
+ }
9031
+ case "cluster": {
9032
+ const total = Math.max(set.velocities.length, set.planned ?? set.velocities.length);
9033
+ return Array.from({ length: total }, (_, i) => {
9034
+ const done = i < set.velocities.length;
9035
+ const boundary = i > 0 && i % set.groupSize === 0;
9036
+ return {
9037
+ kind: done ? "rep" : "todo",
9038
+ velocity: done ? set.velocities[i] : void 0,
9039
+ leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP
9040
+ };
9041
+ });
9042
+ }
9043
+ }
9044
+ }
9045
+ function setAccessibilityLabel(set, repCount) {
9046
+ switch (set.type) {
9047
+ case "straight":
9048
+ return `Velocity strip, straight set, ${repCount} reps`;
9049
+ case "range":
9050
+ return `Velocity strip, rep-range set, floor ${set.floor} to ${set.max}, ${repCount} reps done`;
9051
+ case "amrap":
9052
+ return `Velocity strip, AMRAP set, ${repCount} reps and counting`;
9053
+ case "drop":
9054
+ return `Velocity strip, drop set, ${set.subloads.length} loads, ${repCount} reps`;
9055
+ case "myo":
9056
+ return `Velocity strip, myo-reps set, ${set.clusters.length} clusters${set.open ? ", open" : ""}, ${repCount} reps`;
9057
+ case "cluster":
9058
+ return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
9059
+ }
9060
+ }
9061
+ function slotAccessibilityLabel(slot, index) {
9062
+ switch (slot.kind) {
9063
+ case "rep":
9064
+ return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
9065
+ case "todo":
9066
+ return `Rep ${index + 1}: planned`;
9067
+ case "variable":
9068
+ return `Rep ${index + 1}: variable`;
9069
+ case "continue":
9070
+ return "Keep going";
9071
+ }
9072
+ }
9073
+ var EXPANDED_TODO_STUB_PCT = 16;
9074
+ var EXPANDED_ENCODED_PCT = 45;
9075
+ var EXPANDED_HEIGHT = 60;
9076
+ var FIXED_MAX_VELOCITY = 1.15;
9077
+ var BARE_STUB_HEIGHT = 3;
9078
+ var HERO_HEIGHT = 220;
9079
+ var HERO_LABEL_HEADROOM = 20;
9080
+ var HERO_BAR_GAP = 8;
9081
+ var HERO_BAR_MAX_WIDTH = 52;
9082
+ var HERO_BAR_RADIUS = 5;
9083
+ var HERO_MIN_BAR_HEIGHT = 4;
9084
+ var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
9085
+ var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
9086
+ function scaleDenominator(scale, maxVelocity) {
9087
+ return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
9088
+ }
9089
+ function bareSlotHeight(slot, height, denom) {
9090
+ if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
9091
+ const ratio = Math.min(1, (slot.velocity ?? 0) / denom);
9092
+ return Math.max(BARE_STUB_HEIGHT, ratio * height);
9093
+ }
8498
9094
  function getReducedMotionPreference() {
8499
9095
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
8500
9096
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
@@ -8514,33 +9110,11 @@ function usePrefersReducedMotion() {
8514
9110
  var ANIMATION_DURATION = 400;
8515
9111
  var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
8516
9112
  var POP_EASING = Easing.bezier(0.34, 1.56, 0.64, 1);
8517
- function VelocityStrip({
8518
- velocities,
8519
- zones,
8520
- liveRepIndex,
8521
- expanded = false,
8522
- onToggle,
8523
- onRepPress,
8524
- variant = "full",
8525
- showInfo = true,
8526
- className,
8527
- ...props
8528
- }) {
8529
- const maxVelocity = Math.max(...velocities, 0);
8530
- const meanVelocity = calculateMeanVelocity(velocities);
8531
- const loss = calculateVelocityLoss(velocities);
8532
- const hasZones = zones != null && zones.length > 0;
8533
- const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
8534
- const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
9113
+ function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
8535
9114
  const prefersReducedMotion = usePrefersReducedMotion();
8536
- const [heightAnim] = useState(() => new Animated.Value(expanded ? 60 : 3));
8537
- const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
8538
- const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
8539
9115
  const [liveScale] = useState(() => new Animated.Value(1));
8540
- const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
8541
- const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
8542
9116
  useEffect(() => {
8543
- if (variant === "mini" || liveRepIndex == null || liveVelocity == null) return;
9117
+ if (!active || liveRepIndex == null || liveVelocity == null) return;
8544
9118
  if (prefersReducedMotion) {
8545
9119
  liveScale.setValue(1);
8546
9120
  return;
@@ -8570,12 +9144,201 @@ function VelocityStrip({
8570
9144
  useNativeDriver: true
8571
9145
  }).start();
8572
9146
  }
8573
- }, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
9147
+ }, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
9148
+ return liveScale;
9149
+ }
9150
+ function HeroVelocityChart({
9151
+ doneVelocities,
9152
+ barColorFor,
9153
+ scaleDenom,
9154
+ referenceVelocity,
9155
+ liveRepIndex,
9156
+ isNewPeak,
9157
+ targetReps,
9158
+ height,
9159
+ className,
9160
+ viewProps
9161
+ }) {
9162
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
9163
+ const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
9164
+ const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
9165
+ const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
9166
+ const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
9167
+ const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
9168
+ const repCount = doneVelocities.length;
9169
+ const total = Math.max(repCount, targetReps ?? repCount);
9170
+ const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
9171
+ const { style: externalStyle, ...restProps } = viewProps;
9172
+ return /* @__PURE__ */ jsx(
9173
+ View,
9174
+ {
9175
+ className,
9176
+ style: [{ height }, externalStyle],
9177
+ accessibilityRole: "image",
9178
+ accessibilityLabel: label,
9179
+ testID: "velocity-strip-hero",
9180
+ ...restProps,
9181
+ children: /* @__PURE__ */ jsxs(
9182
+ View,
9183
+ {
9184
+ style: {
9185
+ flex: 1,
9186
+ flexDirection: "row",
9187
+ alignItems: "flex-end",
9188
+ gap: HERO_BAR_GAP,
9189
+ position: "relative",
9190
+ borderBottomWidth: 2,
9191
+ borderBottomColor: HERO_PENDING_COLOR
9192
+ },
9193
+ children: [
9194
+ referencePx > 0 && /* @__PURE__ */ jsx(
9195
+ View,
9196
+ {
9197
+ accessibilityElementsHidden: true,
9198
+ style: {
9199
+ position: "absolute",
9200
+ left: 0,
9201
+ right: 0,
9202
+ bottom: referencePx,
9203
+ borderTopWidth: 1,
9204
+ borderStyle: "dashed",
9205
+ borderColor: HERO_REFERENCE_COLOR,
9206
+ pointerEvents: "none"
9207
+ },
9208
+ testID: "velocity-hero-reference"
9209
+ }
9210
+ ),
9211
+ doneVelocities.map((velocity, i) => {
9212
+ const isLive = liveRepIndex === i;
9213
+ return /* @__PURE__ */ jsxs(
9214
+ View,
9215
+ {
9216
+ accessibilityElementsHidden: true,
9217
+ style: {
9218
+ flex: 1,
9219
+ maxWidth: HERO_BAR_MAX_WIDTH,
9220
+ height: "100%",
9221
+ alignItems: "center",
9222
+ justifyContent: "flex-end"
9223
+ },
9224
+ children: [
9225
+ /* @__PURE__ */ jsx(
9226
+ Text,
9227
+ {
9228
+ className: "text-text-primary",
9229
+ style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
9230
+ testID: `velocity-label-${i}`,
9231
+ children: formatVelocity(velocity)
9232
+ }
9233
+ ),
9234
+ /* @__PURE__ */ jsx(
9235
+ Animated.View,
9236
+ {
9237
+ style: [
9238
+ {
9239
+ width: "100%",
9240
+ height: barHeight(velocity),
9241
+ borderTopLeftRadius: HERO_BAR_RADIUS,
9242
+ borderTopRightRadius: HERO_BAR_RADIUS,
9243
+ backgroundColor: barColorFor(velocity)
9244
+ },
9245
+ isLive ? { transform: [{ scale: liveScale }] } : null
9246
+ ],
9247
+ testID: `velocity-bar-${i}`
9248
+ }
9249
+ )
9250
+ ]
9251
+ },
9252
+ i
9253
+ );
9254
+ }),
9255
+ Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
9256
+ View,
9257
+ {
9258
+ accessibilityElementsHidden: true,
9259
+ style: {
9260
+ flex: 1,
9261
+ maxWidth: HERO_BAR_MAX_WIDTH,
9262
+ height: "100%",
9263
+ alignItems: "center",
9264
+ justifyContent: "flex-end"
9265
+ },
9266
+ children: /* @__PURE__ */ jsx(
9267
+ View,
9268
+ {
9269
+ style: {
9270
+ width: "100%",
9271
+ height: HERO_MIN_BAR_HEIGHT * 3,
9272
+ borderWidth: 1,
9273
+ borderStyle: "dashed",
9274
+ borderColor: HERO_PENDING_COLOR,
9275
+ borderTopLeftRadius: HERO_BAR_RADIUS,
9276
+ borderTopRightRadius: HERO_BAR_RADIUS
9277
+ },
9278
+ testID: "velocity-slot-todo"
9279
+ }
9280
+ )
9281
+ },
9282
+ `pending-${i}`
9283
+ ))
9284
+ ]
9285
+ }
9286
+ )
9287
+ }
9288
+ );
9289
+ }
9290
+ function VelocityStrip({
9291
+ velocities,
9292
+ set,
9293
+ zones,
9294
+ liveRepIndex,
9295
+ expanded = true,
9296
+ onToggle,
9297
+ onRepPress,
9298
+ variant = "expanded",
9299
+ height = EXPANDED_HEIGHT,
9300
+ targetReps,
9301
+ scale = "peak",
9302
+ showNumbers = true,
9303
+ showInfo = true,
9304
+ className,
9305
+ ...props
9306
+ }) {
9307
+ const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
9308
+ const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
9309
+ kind: "rep",
9310
+ velocity: v,
9311
+ leadingGap: i === 0 ? 0 : REP_GAP
9312
+ }));
9313
+ const maxVelocity = Math.max(...doneVelocities, 0);
9314
+ const meanVelocity = calculateMeanVelocity(doneVelocities);
9315
+ const loss = calculateVelocityLoss(doneVelocities);
9316
+ const framed = showNumbers || showInfo;
9317
+ const scaleDenom = scaleDenominator(scale, maxVelocity);
9318
+ const hasZones = zones != null && zones.length > 0;
9319
+ const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
9320
+ const slotColor = (slot) => {
9321
+ if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
9322
+ if (slot.kind === "todo") return TODO_COLOR2;
9323
+ return SET_STRIP_VARIABLE_COLOR;
9324
+ };
9325
+ const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
9326
+ const [heightAnim] = useState(() => new Animated.Value(expanded ? height : 3));
9327
+ const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
9328
+ const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
9329
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
9330
+ const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
9331
+ const liveScale = useLiveRepPop(
9332
+ variant === "expanded" && framed,
9333
+ liveRepIndex,
9334
+ liveVelocity,
9335
+ isNewPeak
9336
+ );
8574
9337
  useEffect(() => {
8575
- if (variant === "mini") return;
9338
+ if (variant !== "expanded" || !framed) return;
8576
9339
  Animated.parallel([
8577
9340
  Animated.timing(heightAnim, {
8578
- toValue: expanded ? 60 : 3,
9341
+ toValue: expanded ? height : 3,
8579
9342
  duration: ANIMATION_DURATION,
8580
9343
  easing: ANIMATION_EASING,
8581
9344
  useNativeDriver: false
@@ -8593,101 +9356,219 @@ function VelocityStrip({
8593
9356
  useNativeDriver: false
8594
9357
  })
8595
9358
  ]).start();
8596
- }, [expanded, variant, heightAnim, labelOpacity, infoOpacity]);
9359
+ }, [expanded, variant, framed, height, heightAnim, labelOpacity, infoOpacity]);
9360
+ if (set == null && velocities == null) return null;
9361
+ const repCount = doneVelocities.length;
9362
+ const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
9363
+ if (variant === "hero") {
9364
+ const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
9365
+ return /* @__PURE__ */ jsx(
9366
+ HeroVelocityChart,
9367
+ {
9368
+ doneVelocities,
9369
+ barColorFor,
9370
+ scaleDenom,
9371
+ referenceVelocity: maxVelocity,
9372
+ liveRepIndex,
9373
+ isNewPeak,
9374
+ targetReps,
9375
+ height: heroHeight,
9376
+ className,
9377
+ viewProps: props
9378
+ }
9379
+ );
9380
+ }
8597
9381
  if (variant === "mini") {
8598
9382
  const { style: externalStyle, ...restProps } = props;
8599
9383
  return /* @__PURE__ */ jsx(
8600
9384
  View,
8601
9385
  {
8602
9386
  className,
8603
- style: [{ flexDirection: "row", height: 3, gap: 2, borderRadius: 2, overflow: "hidden" }, externalStyle],
9387
+ style: [
9388
+ { flexDirection: "row", height: 3, gap: REP_GAP, borderRadius: 2, overflow: "hidden" },
9389
+ externalStyle
9390
+ ],
8604
9391
  accessibilityRole: "image",
8605
- accessibilityLabel: `Velocity strip, ${velocities.length} reps`,
9392
+ accessibilityLabel: miniLabel,
8606
9393
  testID: "velocity-strip-mini",
8607
9394
  ...restProps,
8608
- children: velocities.map((v, i) => /* @__PURE__ */ jsx(
9395
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
8609
9396
  View,
8610
9397
  {
8611
9398
  style: {
8612
9399
  flex: 1,
8613
- backgroundColor: barColorFor(v),
9400
+ backgroundColor: slotColor(slot),
8614
9401
  borderRadius: 1,
8615
9402
  minWidth: 4,
8616
- height: "100%"
9403
+ height: "100%",
9404
+ // The container's uniform 2px gap covers rep spacing; a wide slot adds
9405
+ // only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
9406
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
9407
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
8617
9408
  },
8618
9409
  accessibilityElementsHidden: true,
8619
- testID: `velocity-bar-${i}`
9410
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
8620
9411
  },
8621
9412
  i
8622
9413
  ))
8623
9414
  }
8624
9415
  );
8625
9416
  }
8626
- const stripLabel = `Velocity chart for set, ${velocities.length} reps, tap to ${expanded ? "collapse" : "expand"}`;
8627
- const hasInteractiveContainer = onToggle != null;
8628
- 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,
8638
- borderRadius: expanded ? 6 : 2,
8639
- paddingTop: expanded ? 16 : 0,
8640
- paddingBottom: expanded ? 24 : 0,
8641
- paddingHorizontal: expanded ? 6 : 0,
8642
- paddingVertical: expanded ? void 0 : 8,
8643
- overflow: expanded ? "visible" : "hidden"
8644
- }
8645
- ],
8646
- accessibilityRole: hasInteractiveContainer || hasInteractiveReps ? "none" : "button",
8647
- accessibilityLabel: hasInteractiveContainer || hasInteractiveReps ? void 0 : stripLabel,
8648
- testID: "velocity-strip",
8649
- ...props,
8650
- children: [
8651
- /* @__PURE__ */ jsx(
9417
+ if (!framed) {
9418
+ const { style: externalStyle, ...restProps } = props;
9419
+ return /* @__PURE__ */ jsx(
9420
+ View,
9421
+ {
9422
+ className,
9423
+ style: [
9424
+ { flexDirection: "row", height, gap: REP_GAP, alignItems: "flex-end" },
9425
+ externalStyle
9426
+ ],
9427
+ accessibilityRole: "image",
9428
+ accessibilityLabel: miniLabel,
9429
+ testID: "velocity-strip-compact",
9430
+ ...restProps,
9431
+ children: slots.map((slot, i) => /* @__PURE__ */ jsx(
8652
9432
  View,
8653
9433
  {
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(
9434
+ style: {
9435
+ flex: 1,
9436
+ minWidth: 4,
9437
+ height: bareSlotHeight(slot, height, scaleDenom),
9438
+ // Top-rounded to match the framed chart's bars (the "rounded tops from
9439
+ // the numbers one"); square bottoms since bars sit on the baseline.
9440
+ borderTopLeftRadius: 2,
9441
+ borderTopRightRadius: 2,
9442
+ backgroundColor: slotColor(slot),
9443
+ marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
9444
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
9445
+ },
9446
+ accessibilityElementsHidden: true,
9447
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
9448
+ },
9449
+ i
9450
+ ))
9451
+ }
9452
+ );
9453
+ }
9454
+ const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
9455
+ const hasInteractiveContainer = onToggle != null;
9456
+ const hasInteractiveReps = onRepPress != null && expanded;
9457
+ const setBars = set != null && slots.map((slot, i) => {
9458
+ const isStraightRep = set.type === "straight" && slot.kind === "rep";
9459
+ const heightPct = isStraightRep ? scaleDenom > 0 ? Math.round((slot.velocity ?? 0) / scaleDenom * 100) : 0 : set.type === "straight" ? EXPANDED_TODO_STUB_PCT : EXPANDED_ENCODED_PCT;
9460
+ return /* @__PURE__ */ jsx(
9461
+ View,
9462
+ {
9463
+ style: {
9464
+ flex: 1,
9465
+ height: "100%",
9466
+ justifyContent: "flex-end",
9467
+ marginLeft: slot.leadingGap
9468
+ },
9469
+ children: /* @__PURE__ */ jsx(
9470
+ View,
9471
+ {
9472
+ style: {
9473
+ height: `${heightPct}%`,
9474
+ minHeight: 2,
9475
+ borderTopLeftRadius: 2,
9476
+ borderTopRightRadius: 2,
9477
+ backgroundColor: slotColor(slot),
9478
+ ...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
9479
+ },
9480
+ testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
9481
+ accessibilityRole: "image",
9482
+ accessibilityLabel: slotAccessibilityLabel(slot, i)
9483
+ }
9484
+ )
9485
+ },
9486
+ i
9487
+ );
9488
+ });
9489
+ const stripContent = /* @__PURE__ */ jsxs(
9490
+ Animated.View,
9491
+ {
9492
+ className: [className, expanded ? "bg-surface-raised" : "bg-transparent"].filter(Boolean).join(" "),
9493
+ style: [
9494
+ {
9495
+ height: heightAnim,
9496
+ width: "100%",
9497
+ gap: 2,
9498
+ borderRadius: expanded ? 6 : 2,
9499
+ paddingTop: expanded ? 16 : 0,
9500
+ paddingBottom: expanded ? 24 : 0,
9501
+ // No horizontal inset — the framed chart's bars span full width, matching
9502
+ // the bare spotlight and mini. Numbers/info sit in the vertical padding.
9503
+ paddingHorizontal: 0,
9504
+ paddingVertical: expanded ? void 0 : 8,
9505
+ overflow: expanded ? "visible" : "hidden"
9506
+ }
9507
+ ],
9508
+ accessibilityRole: hasInteractiveContainer || hasInteractiveReps ? "none" : "button",
9509
+ accessibilityLabel: hasInteractiveContainer || hasInteractiveReps ? void 0 : stripLabel,
9510
+ testID: "velocity-strip",
9511
+ ...props,
9512
+ children: [
9513
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", flex: 1, gap: set ? 0 : 2, alignItems: "flex-end" }, children: set ? setBars : doneVelocities.map((v, i) => {
9514
+ const barBackground = barColorFor(v);
9515
+ const barHeightPct = scaleDenom > 0 ? Math.round(v / scaleDenom * 100) : 0;
9516
+ const isLive = liveRepIndex === i;
9517
+ const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
9518
+ const barStyle = expanded ? {
9519
+ height: `${barHeightPct}%`,
9520
+ minHeight: 2,
9521
+ borderTopLeftRadius: 2,
9522
+ borderTopRightRadius: 2,
9523
+ backgroundColor: barBackground
9524
+ } : {
9525
+ minHeight: "100%",
9526
+ borderTopLeftRadius: 2,
9527
+ borderTopRightRadius: 2,
9528
+ borderBottomLeftRadius: 0,
9529
+ borderBottomRightRadius: 0,
9530
+ backgroundColor: barBackground
9531
+ };
9532
+ const barInner = isLive ? /* @__PURE__ */ jsx(
9533
+ Animated.View,
9534
+ {
9535
+ style: [barStyle, { transform: [{ scale: liveScale }] }],
9536
+ testID: `velocity-bar-${i}`,
9537
+ accessibilityRole: "image",
9538
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
9539
+ }
9540
+ ) : /* @__PURE__ */ jsx(
9541
+ View,
9542
+ {
9543
+ style: barStyle,
9544
+ testID: `velocity-bar-${i}`,
9545
+ accessibilityRole: "image",
9546
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
9547
+ }
9548
+ );
9549
+ const bar = /* @__PURE__ */ jsxs(
9550
+ View,
9551
+ {
9552
+ style: {
9553
+ flex: 1,
9554
+ height: "100%",
9555
+ justifyContent: "flex-end",
9556
+ position: "relative"
9557
+ },
9558
+ children: [
9559
+ expanded && showNumbers && /* @__PURE__ */ jsx(
9560
+ Animated.View,
9561
+ {
9562
+ style: {
9563
+ opacity: labelOpacity,
9564
+ alignItems: "center",
9565
+ position: "absolute",
9566
+ top: -13,
9567
+ left: 0,
9568
+ right: 0
9569
+ },
9570
+ accessibilityElementsHidden: true,
9571
+ children: /* @__PURE__ */ jsx(
8691
9572
  Text,
8692
9573
  {
8693
9574
  className: "text-text-secondary",
@@ -8695,30 +9576,30 @@ function VelocityStrip({
8695
9576
  testID: `velocity-label-${i}`,
8696
9577
  children: formatVelocity(v)
8697
9578
  }
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
- })
9579
+ )
9580
+ }
9581
+ ),
9582
+ barInner
9583
+ ]
9584
+ },
9585
+ i
9586
+ );
9587
+ if (onRepPress && expanded) {
9588
+ return /* @__PURE__ */ jsx(
9589
+ Pressable,
9590
+ {
9591
+ style: { flex: 1, height: "100%" },
9592
+ onPress: () => onRepPress(i, v),
9593
+ accessibilityRole: "button",
9594
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
9595
+ testID: `velocity-bar-pressable-${i}`,
9596
+ children: bar
9597
+ },
9598
+ i
9599
+ );
8720
9600
  }
8721
- ),
9601
+ return bar;
9602
+ }) }),
8722
9603
  expanded && showInfo && /* @__PURE__ */ jsxs(
8723
9604
  Animated.View,
8724
9605
  {
@@ -9016,233 +9897,90 @@ function Sparkline({
9016
9897
  }
9017
9898
  );
9018
9899
  }
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}` : ""}`;
9900
+ var DARK = getSemanticColors("dark");
9901
+ var TEXT_ACTIVE = DARK["text-primary"];
9902
+ var TEXT_MUTED = DARK["text-secondary"];
9903
+ var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
9904
+ var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
9905
+ var typeBadge = {
9906
+ fontSize: 10,
9907
+ fontWeight: "700",
9908
+ fontFamily: "Inter, sans-serif",
9909
+ color: WORKOUT_TOKENS.scale.orange,
9910
+ backgroundColor: "rgba(255,165,2,0.12)",
9911
+ paddingVertical: 1,
9912
+ paddingHorizontal: 5,
9913
+ borderRadius: 3,
9914
+ overflow: "hidden"
9915
+ };
9916
+ function displayReps(set) {
9917
+ return set.state === "done" ? set.reps : set.target.reps;
9918
+ }
9919
+ function displayWeight(set) {
9920
+ return set.state === "done" ? set.weight : set.target.weight;
9921
+ }
9922
+ function plannedReps(set) {
9923
+ if (set.state === "done") return set.velocities.length;
9924
+ if (set.state === "live") return set.target.reps;
9925
+ return set.planned ?? set.target.reps;
9926
+ }
9927
+ function accessibilityLabel(set) {
9928
+ const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
9929
+ if (set.state === "live") return `${base}, in progress`;
9930
+ if (set.state === "todo") return `${base}, upcoming`;
9931
+ return base;
9932
+ }
9933
+ function Cell({
9934
+ width,
9935
+ children,
9936
+ testID
9937
+ }) {
9938
+ return /* @__PURE__ */ jsx(View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
9023
9939
  }
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;
9940
+ function RowStrip({ set }) {
9941
+ const zones = set.velocityZones;
9942
+ if (set.state === "live") {
9943
+ return /* @__PURE__ */ jsx(
9944
+ VelocityStrip,
9945
+ {
9946
+ variant: "expanded",
9947
+ showNumbers: false,
9948
+ showInfo: false,
9949
+ height: 24,
9950
+ scale: "fixed",
9951
+ set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
9952
+ zones
9953
+ }
9954
+ );
9059
9955
  }
9956
+ const velocities = set.state === "done" ? set.velocities : [];
9957
+ return /* @__PURE__ */ jsx(
9958
+ VelocityStrip,
9959
+ {
9960
+ variant: "mini",
9961
+ set: { type: "straight", velocities, planned: plannedReps(set) },
9962
+ zones
9963
+ }
9964
+ );
9965
+ }
9966
+ function SetRow(set) {
9967
+ const live = set.state === "live";
9968
+ const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
9969
+ const rpe = set.state === "todo" ? null : set.rpe;
9060
9970
  return /* @__PURE__ */ jsxs(
9061
9971
  View,
9062
9972
  {
9063
- style: rowStyle,
9064
- accessibilityLabel: formatAccessibilityLabel(
9065
- setNumber,
9066
- reps,
9067
- weight,
9068
- unit
9069
- ),
9973
+ style: { paddingVertical: 6, paddingHorizontal: 8 },
9974
+ accessibilityLabel: accessibilityLabel(set),
9070
9975
  testID: "set-row",
9071
9976
  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
- )
9977
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
9978
+ /* @__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 }) }),
9979
+ /* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
9980
+ /* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
9981
+ /* @__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" }) })
9982
+ ] }),
9983
+ /* @__PURE__ */ jsx(View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
9246
9984
  ]
9247
9985
  }
9248
9986
  );
@@ -9404,23 +10142,139 @@ function InputBar({
9404
10142
  );
9405
10143
  }
9406
10144
  var BRAND_PRIMARY4 = getSemanticColors("dark")["brand-primary"];
9407
- function RestTimer({
9408
- totalSeconds,
9409
- elapsedMs,
10145
+ var RING_DEFAULT_SIZE = 180;
10146
+ function RestActions({ onAddTime, onSkip }) {
10147
+ return /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
10148
+ /* @__PURE__ */ jsx(
10149
+ Pressable,
10150
+ {
10151
+ onPress: onAddTime,
10152
+ style: {
10153
+ backgroundColor: "rgba(255,255,255,0.06)",
10154
+ paddingVertical: 8,
10155
+ paddingHorizontal: 20,
10156
+ borderRadius: 8
10157
+ },
10158
+ accessibilityRole: "button",
10159
+ accessibilityLabel: "Add 30 seconds",
10160
+ testID: "rest-timer-add-time",
10161
+ children: /* @__PURE__ */ jsx(
10162
+ Text,
10163
+ {
10164
+ className: "text-text-secondary",
10165
+ style: { fontSize: 11, fontFamily: "Inter, sans-serif", fontWeight: "600" },
10166
+ children: "+30s"
10167
+ }
10168
+ )
10169
+ }
10170
+ ),
10171
+ /* @__PURE__ */ jsx(
10172
+ Pressable,
10173
+ {
10174
+ onPress: onSkip,
10175
+ style: {
10176
+ backgroundColor: "rgba(255,121,0,0.12)",
10177
+ paddingVertical: 8,
10178
+ paddingHorizontal: 20,
10179
+ borderRadius: 8
10180
+ },
10181
+ accessibilityRole: "button",
10182
+ accessibilityLabel: "Skip rest",
10183
+ testID: "rest-timer-skip",
10184
+ children: /* @__PURE__ */ jsx(
10185
+ Text,
10186
+ {
10187
+ style: {
10188
+ fontSize: 11,
10189
+ fontFamily: "Inter, sans-serif",
10190
+ fontWeight: "600",
10191
+ color: BRAND_PRIMARY4
10192
+ },
10193
+ children: "Skip"
10194
+ }
10195
+ )
10196
+ }
10197
+ )
10198
+ ] });
10199
+ }
10200
+ function RestTimerRing({
10201
+ totalSeconds,
10202
+ elapsedMs,
10203
+ remainingSec,
10204
+ done,
10205
+ size,
10206
+ nextSetInfo,
10207
+ displayOnly,
10208
+ onSkip,
10209
+ onAddTime
10210
+ }) {
10211
+ const a11yLabel = done ? "Rest complete, next set ready" : `Rest timer, ${Math.max(0, remainingSec)} seconds remaining`;
10212
+ return /* @__PURE__ */ jsxs(View, { style: { alignItems: "center", gap: 16 }, testID: "rest-timer", children: [
10213
+ /* @__PURE__ */ jsx(
10214
+ CircularTimer,
10215
+ {
10216
+ durationMs: totalSeconds * 1e3,
10217
+ elapsedMs,
10218
+ mode: "down",
10219
+ size,
10220
+ doneLabel: "GO",
10221
+ doneColor: "success",
10222
+ accessibilityLabel: a11yLabel,
10223
+ controls: !displayOnly ? /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip }) : void 0,
10224
+ testID: "rest-timer-ring"
10225
+ }
10226
+ ),
10227
+ nextSetInfo != null && /* @__PURE__ */ jsx(
10228
+ Text,
10229
+ {
10230
+ className: "text-text-tertiary",
10231
+ style: { fontSize: 13, fontFamily: "Inter, sans-serif" },
10232
+ testID: "rest-timer-next-set",
10233
+ children: nextSetInfo
10234
+ }
10235
+ )
10236
+ ] });
10237
+ }
10238
+ function RestTimer({
10239
+ totalSeconds,
10240
+ elapsedMs,
9410
10241
  onSkip,
9411
10242
  onAddTime,
9412
10243
  nextSetInfo,
9413
10244
  visible,
9414
- displayOnly = false
10245
+ displayOnly = false,
10246
+ variant = "bar",
10247
+ size = RING_DEFAULT_SIZE
9415
10248
  }) {
9416
- if (!visible) return null;
9417
- const remainingMs = Math.max(0, totalSeconds * 1e3 - elapsedMs);
10249
+ const {
10250
+ remainingMs,
10251
+ progress,
10252
+ label: timeDisplay,
10253
+ done
10254
+ } = useTimer({
10255
+ mode: "down",
10256
+ durationMs: totalSeconds * 1e3,
10257
+ elapsedMs
10258
+ });
9418
10259
  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;
10260
+ const progressPct = progress * 100;
10261
+ if (!visible) return null;
10262
+ if (variant === "ring") {
10263
+ return /* @__PURE__ */ jsx(
10264
+ RestTimerRing,
10265
+ {
10266
+ totalSeconds,
10267
+ elapsedMs,
10268
+ remainingSec,
10269
+ done,
10270
+ size,
10271
+ nextSetInfo,
10272
+ displayOnly,
10273
+ onSkip,
10274
+ onAddTime
10275
+ }
10276
+ );
10277
+ }
9424
10278
  return /* @__PURE__ */ jsxs(
9425
10279
  View,
9426
10280
  {
@@ -9518,69 +10372,318 @@ function RestTimer({
9518
10372
  )
9519
10373
  }
9520
10374
  ),
9521
- !displayOnly && /* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", gap: 8 }, children: [
10375
+ !displayOnly && /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip })
10376
+ ]
10377
+ }
10378
+ );
10379
+ }
10380
+ var t8 = getSemanticColors("dark");
10381
+ var TIMES = "\xD7";
10382
+ var AT = "@";
10383
+ function SetsRepsLoad({
10384
+ sets,
10385
+ reps,
10386
+ load,
10387
+ unit = "lb",
10388
+ className,
10389
+ ...props
10390
+ }) {
10391
+ const value = t8["text-primary"];
10392
+ const sep = t8["text-tertiary"];
10393
+ return /* @__PURE__ */ jsxs(
10394
+ View,
10395
+ {
10396
+ className,
10397
+ style: { flexDirection: "row", alignItems: "baseline" },
10398
+ accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
10399
+ testID: "sets-reps-load",
10400
+ ...props,
10401
+ children: [
10402
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
10403
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
10404
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
10405
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
10406
+ /* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
10407
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
10408
+ ]
10409
+ }
10410
+ );
10411
+ }
10412
+
10413
+ // src/components/custom/Workout/paceTone.ts
10414
+ function paceTone(progress, target) {
10415
+ if (target === void 0) return "neutral";
10416
+ return progress >= target ? "ahead" : "behind";
10417
+ }
10418
+ var PACE_TONE_COLORS = {
10419
+ ahead: getSemanticColors("dark")["status-success"],
10420
+ behind: getSemanticColors("dark")["status-warning"],
10421
+ neutral: primitiveRamps.cyan[400]
10422
+ };
10423
+ function paceToneColor(tone) {
10424
+ return PACE_TONE_COLORS[tone];
10425
+ }
10426
+
10427
+ // src/components/custom/Workout/SegmentedProgressBar.tsx
10428
+ var DEFAULT_MARKER_COLOR = primitiveRamps.green[500];
10429
+ function SegmentedProgressBar({
10430
+ segments,
10431
+ value,
10432
+ target,
10433
+ markerColor = DEFAULT_MARKER_COLOR,
10434
+ color,
10435
+ height,
10436
+ gap,
10437
+ ...props
10438
+ }) {
10439
+ const total = segments.reduce((sum, s) => sum + s.weight, 0);
10440
+ const fillColor = color ?? paceToneColor(paceTone(total > 0 ? value / total : 0, target));
10441
+ const barSegments = segments.map((seg, i) => {
10442
+ const cumulativeBefore = segments.slice(0, i).reduce((sum, s) => sum + s.weight, 0);
10443
+ const fill = Math.max(0, Math.min(1, (value - cumulativeBefore) / seg.weight));
10444
+ return { weight: seg.weight, fill, color: fillColor };
10445
+ });
10446
+ return /* @__PURE__ */ jsx(
10447
+ SegmentedBar,
10448
+ {
10449
+ segments: barSegments,
10450
+ marker: target != null ? { position: target, color: markerColor } : null,
10451
+ height,
10452
+ gap,
10453
+ ...props
10454
+ }
10455
+ );
10456
+ }
10457
+
10458
+ // src/components/custom/Workout/MetricTiles.tsx
10459
+ function MetricTiles({ metrics, gap = 1, ...props }) {
10460
+ return /* @__PURE__ */ jsx(HStack, { gap, align: "stretch", ...props, children: metrics.map((metric) => /* @__PURE__ */ jsx(
10461
+ Tile,
10462
+ {
10463
+ label: metric.label,
10464
+ value: metric.value,
10465
+ valueColor: metric.valueColor
10466
+ },
10467
+ metric.label
10468
+ )) });
10469
+ }
10470
+ var DAY_MS = 24 * 60 * 60 * 1e3;
10471
+ var SOON_ACCENT = primitiveRamps.orange[400];
10472
+ function compactUntil(ms) {
10473
+ if (ms <= 0) return "now";
10474
+ const min = Math.round(ms / 6e4);
10475
+ if (min < 60) return `${min}m`;
10476
+ const hr = Math.round(min / 60);
10477
+ if (hr < 24) return `${hr}h`;
10478
+ const d = Math.round(hr / 24);
10479
+ if (d < 7) return `${d}d`;
10480
+ return `${Math.round(d / 7)}w`;
10481
+ }
10482
+ function ScheduleTiles({ when, now, gap = 1, ...props }) {
10483
+ const [mountNow] = useState(() => Date.now());
10484
+ const reference = now ?? mountNow;
10485
+ const whenMs = when instanceof Date ? when.getTime() : when;
10486
+ const soon = whenMs - reference < DAY_MS;
10487
+ const time = new Date(whenMs).toLocaleTimeString(void 0, {
10488
+ hour: "numeric",
10489
+ minute: "2-digit",
10490
+ hour12: true
10491
+ });
10492
+ return /* @__PURE__ */ jsxs(HStack, { gap, ...props, children: [
10493
+ /* @__PURE__ */ jsx(Tile, { label: "Date", value: formatDateTime(when, "short") }),
10494
+ /* @__PURE__ */ jsx(Tile, { label: "Time", value: time }),
10495
+ /* @__PURE__ */ jsx(
10496
+ Tile,
10497
+ {
10498
+ label: "Until",
10499
+ value: compactUntil(whenMs - reference),
10500
+ valueColor: soon ? SOON_ACCENT : void 0
10501
+ }
10502
+ )
10503
+ ] });
10504
+ }
10505
+ var t9 = getSemanticColors("dark");
10506
+ var INDICATOR_CONFIG = {
10507
+ imbalance: { Icon: ScaleIcon, color: t9["status-error"], label: "Left/right imbalance" },
10508
+ overshoot: { Icon: AlertTriangleIcon, color: t9["status-error"], label: "Load overshoot" },
10509
+ "velocity-loss": { Icon: TrendingDownIcon, color: t9["status-warning"], label: "Velocity loss" },
10510
+ "missed-reps": { Icon: CircleSlashIcon, color: t9["status-warning"], label: "Missed reps" },
10511
+ pr: { Icon: AwardIcon, color: t9["status-success"], label: "Personal record" },
10512
+ info: { Icon: InfoIcon, color: t9["status-info"], label: "More info" }
10513
+ };
10514
+ var INDICATOR_PRECEDENCE = [
10515
+ "imbalance",
10516
+ "overshoot",
10517
+ "velocity-loss",
10518
+ "missed-reps",
10519
+ "pr",
10520
+ "info"
10521
+ ];
10522
+ function resolveIndicator(candidates) {
10523
+ return INDICATOR_PRECEDENCE.find((kind) => candidates.includes(kind));
10524
+ }
10525
+ function ExerciseIndicator({ kind, onPress, className, ...props }) {
10526
+ const { Icon, color, label } = INDICATOR_CONFIG[kind];
10527
+ const chip = /* @__PURE__ */ jsx(
10528
+ View,
10529
+ {
10530
+ className,
10531
+ style: {
10532
+ width: 16,
10533
+ height: 16,
10534
+ borderRadius: 8,
10535
+ borderWidth: 1,
10536
+ borderColor: color,
10537
+ alignItems: "center",
10538
+ justifyContent: "center"
10539
+ },
10540
+ accessibilityRole: "image",
10541
+ accessibilityLabel: label,
10542
+ testID: "exercise-indicator",
10543
+ ...props,
10544
+ children: /* @__PURE__ */ jsx(Icon, { size: 11, color })
10545
+ }
10546
+ );
10547
+ if (onPress) {
10548
+ return /* @__PURE__ */ jsx(Pressable, { onPress, accessibilityRole: "button", accessibilityLabel: label, children: chip });
10549
+ }
10550
+ return chip;
10551
+ }
10552
+ function headingLabel(name, sets, reps, load, unit) {
10553
+ return `${name}, ${sets}\xD7${reps} @ ${roundWeight(load)} ${unit}`;
10554
+ }
10555
+ function ExerciseHeading({
10556
+ name,
10557
+ sets,
10558
+ reps,
10559
+ load,
10560
+ unit = "lbs",
10561
+ tempo,
10562
+ indicator,
10563
+ dimmed,
10564
+ onPress
10565
+ }) {
10566
+ return /* @__PURE__ */ jsxs(View, { style: { opacity: dimmed ? 0.55 : 1 }, testID: "exercise-heading", children: [
10567
+ /* @__PURE__ */ jsxs(
10568
+ Pressable,
10569
+ {
10570
+ onPress,
10571
+ accessibilityRole: "button",
10572
+ accessibilityLabel: headingLabel(name, sets, reps, load, unit),
10573
+ className: "flex-row items-center",
10574
+ style: { gap: 8 },
10575
+ testID: "exercise-card-header",
10576
+ children: [
9522
10577
  /* @__PURE__ */ jsx(
9523
- Pressable,
10578
+ Text,
9524
10579
  {
9525
- onPress: onAddTime,
9526
- style: {
9527
- backgroundColor: "rgba(255,255,255,0.06)",
9528
- paddingVertical: 8,
9529
- paddingHorizontal: 20,
9530
- borderRadius: 8
9531
- },
9532
- accessibilityRole: "button",
9533
- accessibilityLabel: "Add 30 seconds",
9534
- testID: "rest-timer-add-time",
9535
- children: /* @__PURE__ */ jsx(
9536
- Text,
9537
- {
9538
- className: "text-text-secondary",
9539
- style: {
9540
- fontSize: 11,
9541
- fontFamily: "Inter, sans-serif",
9542
- fontWeight: "600"
9543
- },
9544
- children: "+30s"
9545
- }
9546
- )
10580
+ className: "text-text-primary",
10581
+ style: { fontSize: 14, fontFamily: '"Space Grotesk", sans-serif', fontWeight: "700" },
10582
+ testID: "exercise-card-name",
10583
+ children: name
9547
10584
  }
9548
10585
  ),
9549
- /* @__PURE__ */ jsx(
9550
- Pressable,
9551
- {
9552
- onPress: onSkip,
9553
- style: {
9554
- backgroundColor: "rgba(255,121,0,0.12)",
9555
- paddingVertical: 8,
9556
- paddingHorizontal: 20,
9557
- borderRadius: 8
9558
- },
9559
- accessibilityRole: "button",
9560
- accessibilityLabel: "Skip rest",
9561
- testID: "rest-timer-skip",
9562
- children: /* @__PURE__ */ jsx(
9563
- Text,
9564
- {
9565
- style: {
9566
- fontSize: 11,
9567
- fontFamily: "Inter, sans-serif",
9568
- fontWeight: "600",
9569
- color: BRAND_PRIMARY4
9570
- },
9571
- children: "Skip"
9572
- }
9573
- )
9574
- }
9575
- )
9576
- ] })
10586
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10587
+ indicator && /* @__PURE__ */ jsx(ExerciseIndicator, { kind: indicator })
10588
+ ]
10589
+ }
10590
+ ),
10591
+ /* @__PURE__ */ jsxs(
10592
+ View,
10593
+ {
10594
+ className: "flex-row items-center",
10595
+ style: { gap: 8, marginTop: 1 },
10596
+ testID: "exercise-card-summary",
10597
+ children: [
10598
+ /* @__PURE__ */ jsx(SetsRepsLoad, { sets, reps, load, unit }),
10599
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10600
+ tempo && /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm", showLabel: false, showInfo: false })
10601
+ ]
10602
+ }
10603
+ )
10604
+ ] });
10605
+ }
10606
+ function ExerciseCardHeading({
10607
+ setStates,
10608
+ stripHeight = 8,
10609
+ dimmed,
10610
+ testID = "exercise-card",
10611
+ ...heading
10612
+ }) {
10613
+ return /* @__PURE__ */ jsxs(
10614
+ View,
10615
+ {
10616
+ style: { paddingVertical: 9, paddingHorizontal: 12, opacity: dimmed ? 0.55 : 1 },
10617
+ testID,
10618
+ children: [
10619
+ /* @__PURE__ */ jsx(ExerciseHeading, { ...heading }),
10620
+ setStates.length > 0 && /* @__PURE__ */ jsx(View, { style: { marginTop: 7 }, testID: "exercise-card-strip", children: /* @__PURE__ */ jsx(SetStrip, { sets: setStates, height: stripHeight }) })
9577
10621
  ]
9578
10622
  }
9579
10623
  );
9580
10624
  }
9581
- function buildColumnHeaders(unit) {
9582
- return ["SET", "PREV", "REPS", unit.toUpperCase(), "RPE"];
10625
+ var PREV_COLUMN = { label: "PREV", width: void 0 };
10626
+ function buildColumns(unit, showPrevious) {
10627
+ return [
10628
+ { label: "SET", width: 36 },
10629
+ ...showPrevious ? [PREV_COLUMN] : [],
10630
+ { label: "REPS", width: 44 },
10631
+ { label: unit.toUpperCase(), width: 56 },
10632
+ { label: "RPE", width: 36 }
10633
+ ];
9583
10634
  }
10635
+ function SetTableHeader({
10636
+ unit = "lbs",
10637
+ showPrevious = true,
10638
+ testID = "table-header"
10639
+ }) {
10640
+ const columns = buildColumns(unit, showPrevious);
10641
+ return /* @__PURE__ */ jsx(
10642
+ View,
10643
+ {
10644
+ className: "flex-row",
10645
+ style: {
10646
+ paddingVertical: 8,
10647
+ paddingHorizontal: 8,
10648
+ paddingBottom: 4,
10649
+ ...showPrevious ? {} : { justifyContent: "space-between" }
10650
+ },
10651
+ testID,
10652
+ children: columns.map(({ label, width }) => {
10653
+ const isFlex = width === void 0;
10654
+ return /* @__PURE__ */ jsx(
10655
+ View,
10656
+ {
10657
+ style: {
10658
+ ...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
10659
+ alignItems: "center",
10660
+ justifyContent: "center",
10661
+ gap: 8
10662
+ },
10663
+ ...isFlex ? { className: "flex-1" } : {},
10664
+ children: /* @__PURE__ */ jsx(
10665
+ Text,
10666
+ {
10667
+ className: "text-text-tertiary",
10668
+ style: {
10669
+ fontSize: 10,
10670
+ fontWeight: "600",
10671
+ fontFamily: "Inter, sans-serif",
10672
+ textTransform: "uppercase",
10673
+ letterSpacing: 0.8
10674
+ },
10675
+ children: label
10676
+ }
10677
+ )
10678
+ },
10679
+ label
10680
+ );
10681
+ })
10682
+ }
10683
+ );
10684
+ }
10685
+
10686
+ // src/components/custom/Workout/ExerciseCard.tsx
9584
10687
  function getSupersetBorderRadius(position) {
9585
10688
  switch (position) {
9586
10689
  case "first":
@@ -9623,7 +10726,7 @@ function formatSummary(summary) {
9623
10726
  if (!summary) return "";
9624
10727
  return `${summary.sets}\xD7${summary.reps} @ ${roundWeight(summary.weight)} ${summary.unit}`;
9625
10728
  }
9626
- function formatAccessibilityLabel2(name, summary, prescription) {
10729
+ function formatAccessibilityLabel(name, summary, prescription) {
9627
10730
  if (summary) return `${name}, ${formatSummary(summary)}`;
9628
10731
  if (prescription) return `${name}, ${prescription}`;
9629
10732
  return name;
@@ -9632,7 +10735,6 @@ function CollapsedCard({
9632
10735
  name,
9633
10736
  onToggle,
9634
10737
  summary,
9635
- e1rm,
9636
10738
  isPR,
9637
10739
  setVelocities,
9638
10740
  velocityZones,
@@ -9651,7 +10753,7 @@ function CollapsedCard({
9651
10753
  onPressIn: () => setPressed(true),
9652
10754
  onPressOut: () => setPressed(false),
9653
10755
  accessibilityRole: "button",
9654
- accessibilityLabel: formatAccessibilityLabel2(name, summary, void 0),
10756
+ accessibilityLabel: formatAccessibilityLabel(name, summary, void 0),
9655
10757
  testID: "exercise-card",
9656
10758
  children: /* @__PURE__ */ jsxs(
9657
10759
  View,
@@ -9665,220 +10767,131 @@ function CollapsedCard({
9665
10767
  ...supersetMargin
9666
10768
  },
9667
10769
  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
- )
10770
+ /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", testID: "exercise-card-header", children: [
10771
+ /* @__PURE__ */ jsx(
10772
+ Text,
10773
+ {
10774
+ className: "text-text-primary",
10775
+ style: {
10776
+ fontSize: 14,
10777
+ fontFamily: '"Space Grotesk", sans-serif',
10778
+ fontWeight: "700"
10779
+ },
10780
+ testID: "exercise-card-name",
10781
+ children: name
10782
+ }
10783
+ ),
10784
+ /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10785
+ summary && /* @__PURE__ */ jsx(
10786
+ Text,
10787
+ {
10788
+ className: "text-text-secondary",
10789
+ style: {
10790
+ fontSize: 12,
10791
+ fontFamily: "Inter, sans-serif",
10792
+ marginRight: 8
10793
+ },
10794
+ testID: "exercise-card-summary",
10795
+ children: formatSummary(summary)
10796
+ }
10797
+ ),
10798
+ isPR && /* @__PURE__ */ jsx(View, { style: { marginLeft: 4 }, children: /* @__PURE__ */ jsx(PrBadge, { type: "e1rm", compact: true, animate: false }) })
10799
+ ] }),
10800
+ (completedSets > 0 || remaining > 0) && /* @__PURE__ */ jsxs(View, { className: "flex-row", style: { marginTop: 6, gap: 4 }, testID: "exercise-card-strips", children: [
10801
+ setVelocities?.map((velocities, i) => /* @__PURE__ */ jsx(
10802
+ VelocityStrip,
10803
+ {
10804
+ velocities,
10805
+ zones: velocityZones,
10806
+ variant: "mini",
10807
+ testID: `exercise-card-velocity-strip-${i}`
10808
+ },
10809
+ i
10810
+ )),
10811
+ Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx(
10812
+ PlaceholderStrip,
10813
+ {
10814
+ testID: `exercise-card-placeholder-${i}`
10815
+ },
10816
+ `placeholder-${i}`
10817
+ ))
10818
+ ] })
9742
10819
  ]
9743
10820
  }
9744
10821
  )
9745
10822
  }
9746
10823
  );
9747
10824
  }
10825
+ var DARK2 = getSemanticColors("dark");
10826
+ var BODY_DIVIDER = DARK2["border-subtle"];
10827
+ function deriveHeaderSetStates(sets) {
10828
+ return sets.map((set) => {
10829
+ switch (set.state) {
10830
+ case "live":
10831
+ return { status: "active", velocities: set.velocities, planned: set.target.reps };
10832
+ case "done":
10833
+ return { status: "done", velocities: set.velocities };
10834
+ case "todo":
10835
+ return { status: "todo", planned: set.planned ?? set.target.reps };
10836
+ }
10837
+ });
10838
+ }
9748
10839
  function ExpandedCard({
9749
10840
  name,
9750
10841
  onToggle,
9751
10842
  summary,
9752
- e1rm,
9753
10843
  isPR,
9754
10844
  sets,
9755
10845
  tempo,
10846
+ indicator,
10847
+ setStates,
10848
+ stripHeight = 8,
10849
+ velocityZones,
9756
10850
  supersetPosition
9757
10851
  }) {
9758
10852
  const borderRadius = getSupersetBorderRadius(supersetPosition);
9759
10853
  const supersetMargin = getSupersetMargin(supersetPosition);
9760
10854
  const unit = summary?.unit ?? sets?.[0]?.unit ?? "lbs";
9761
- const columnHeaders = buildColumnHeaders(unit);
10855
+ const headerStates = setStates ?? (sets ? deriveHeaderSetStates(sets) : []);
10856
+ const headerIndicator = indicator ?? (isPR ? "pr" : void 0);
9762
10857
  return /* @__PURE__ */ jsxs(
9763
- View,
9764
- {
9765
- className: "bg-surface-elevated border-border",
9766
- style: {
9767
- borderWidth: 1,
9768
- ...borderRadius,
9769
- ...supersetMargin
9770
- },
9771
- testID: "exercise-card",
9772
- children: [
9773
- /* @__PURE__ */ jsx(
9774
- Pressable,
9775
- {
9776
- 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
- )
10858
+ View,
10859
+ {
10860
+ className: "bg-surface-elevated border-border",
10861
+ style: {
10862
+ borderWidth: 1,
10863
+ ...borderRadius,
10864
+ ...supersetMargin
10865
+ },
10866
+ testID: "exercise-card",
10867
+ children: [
10868
+ /* @__PURE__ */ jsx(
10869
+ ExerciseCardHeading,
10870
+ {
10871
+ name,
10872
+ sets: summary?.sets ?? sets?.length ?? 0,
10873
+ reps: summary?.reps ?? 0,
10874
+ load: summary?.weight ?? 0,
10875
+ unit,
10876
+ tempo,
10877
+ indicator: headerIndicator,
10878
+ setStates: headerStates,
10879
+ stripHeight,
10880
+ onPress: onToggle,
10881
+ testID: "exercise-card-heading"
9834
10882
  }
9835
10883
  ),
9836
- tempo && /* @__PURE__ */ jsx(View, { style: { marginTop: 8, paddingHorizontal: 14 }, testID: "exercise-card-tempo", children: /* @__PURE__ */ jsx(TempoDisplay, { tempo, size: "sm" }) }),
9837
- /* @__PURE__ */ jsx(
10884
+ /* @__PURE__ */ jsxs(
9838
10885
  View,
9839
10886
  {
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
- })
10887
+ style: { borderTopWidth: 1, borderTopColor: BODY_DIVIDER, paddingBottom: 6 },
10888
+ testID: "exercise-card-body",
10889
+ children: [
10890
+ /* @__PURE__ */ jsx(SetTableHeader, { unit, showPrevious: false, testID: "exercise-card-column-headers" }),
10891
+ sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((set, i) => /* @__PURE__ */ jsx(SetRow, { ...set, velocityZones: set.velocityZones ?? velocityZones }, i)) })
10892
+ ]
9879
10893
  }
9880
- ),
9881
- sets && /* @__PURE__ */ jsx(View, { testID: "exercise-card-sets", children: sets.map((setProps, i) => /* @__PURE__ */ jsx(SetRow, { ...setProps }, i)) })
10894
+ )
9882
10895
  ]
9883
10896
  }
9884
10897
  );
@@ -9887,11 +10900,12 @@ function UpcomingCard({
9887
10900
  name,
9888
10901
  prescription,
9889
10902
  previousBest,
9890
- supersetPosition
10903
+ supersetPosition,
10904
+ onToggle
9891
10905
  }) {
9892
10906
  const borderRadius = getSupersetBorderRadius(supersetPosition);
9893
10907
  const supersetMargin = getSupersetMargin(supersetPosition);
9894
- return /* @__PURE__ */ jsx(
10908
+ return /* @__PURE__ */ jsx(Pressable, { onPress: onToggle, accessibilityRole: "button", children: /* @__PURE__ */ jsx(
9895
10909
  View,
9896
10910
  {
9897
10911
  style: {
@@ -9901,7 +10915,7 @@ function UpcomingCard({
9901
10915
  ...borderRadius,
9902
10916
  ...supersetMargin
9903
10917
  },
9904
- accessibilityLabel: formatAccessibilityLabel2(name, void 0, prescription),
10918
+ accessibilityLabel: formatAccessibilityLabel(name, void 0, prescription),
9905
10919
  testID: "exercise-card",
9906
10920
  children: /* @__PURE__ */ jsxs(View, { className: "flex-row items-center", children: [
9907
10921
  /* @__PURE__ */ jsx(
@@ -9909,6 +10923,7 @@ function UpcomingCard({
9909
10923
  {
9910
10924
  className: "text-text-primary",
9911
10925
  style: {
10926
+ flexShrink: 0,
9912
10927
  fontSize: 14,
9913
10928
  fontFamily: '"Space Grotesk", sans-serif',
9914
10929
  fontWeight: "700"
@@ -9921,7 +10936,9 @@ function UpcomingCard({
9921
10936
  Text,
9922
10937
  {
9923
10938
  className: "text-text-secondary",
10939
+ numberOfLines: 1,
9924
10940
  style: {
10941
+ flexShrink: 1,
9925
10942
  fontSize: 12,
9926
10943
  fontFamily: "Inter, sans-serif",
9927
10944
  marginLeft: 8
@@ -9930,12 +10947,14 @@ function UpcomingCard({
9930
10947
  children: prescription
9931
10948
  }
9932
10949
  ),
9933
- /* @__PURE__ */ jsx(View, { className: "flex-1" }),
10950
+ /* @__PURE__ */ jsx(View, { className: "flex-1", style: { minWidth: 8 } }),
9934
10951
  previousBest && /* @__PURE__ */ jsx(
9935
10952
  Text,
9936
10953
  {
9937
10954
  className: "text-text-tertiary",
10955
+ numberOfLines: 1,
9938
10956
  style: {
10957
+ flexShrink: 1,
9939
10958
  fontSize: 11,
9940
10959
  fontFamily: "Inter, sans-serif"
9941
10960
  },
@@ -9945,17 +10964,208 @@ function UpcomingCard({
9945
10964
  )
9946
10965
  ] })
9947
10966
  }
10967
+ ) });
10968
+ }
10969
+ function ExerciseCard({
10970
+ upcoming,
10971
+ expanded,
10972
+ defaultExpanded,
10973
+ onExpandedChange,
10974
+ ...rest
10975
+ }) {
10976
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded ?? false);
10977
+ const isControlled = expanded !== void 0;
10978
+ const isExpanded = isControlled ? expanded : internalExpanded;
10979
+ const onToggle = () => {
10980
+ const next = !isExpanded;
10981
+ onExpandedChange?.(next);
10982
+ if (!isControlled) setInternalExpanded(next);
10983
+ };
10984
+ if (upcoming) return /* @__PURE__ */ jsx(UpcomingCard, { ...rest, onToggle });
10985
+ return isExpanded ? /* @__PURE__ */ jsx(ExpandedCard, { ...rest, onToggle }) : /* @__PURE__ */ jsx(CollapsedCard, { ...rest, onToggle });
10986
+ }
10987
+ var t10 = getSemanticColors("dark");
10988
+ var RAISED = primitiveColors.charcoal[400];
10989
+ var BAR_HEIGHT = 9;
10990
+ var BAR_GAP = 3;
10991
+ function SessionHeader({
10992
+ title,
10993
+ plan,
10994
+ setsDone = 0,
10995
+ elapsedMs,
10996
+ budgetMs,
10997
+ running = true,
10998
+ metrics = [],
10999
+ next,
11000
+ className,
11001
+ style,
11002
+ ...props
11003
+ }) {
11004
+ const upcoming = next != null;
11005
+ const totalSets = plan.reduce((sum, e) => sum + e.sets, 0);
11006
+ const hasPace = !upcoming && budgetMs != null && elapsedMs != null;
11007
+ const target = hasPace ? elapsedMs / budgetMs : void 0;
11008
+ const setsLabel = upcoming ? `${totalSets} sets` : `${Math.floor(setsDone)}/${totalSets} sets`;
11009
+ const setsLabelColor = upcoming ? t10["text-secondary"] : paceToneColor(paceTone(totalSets > 0 ? setsDone / totalSets : 0, target));
11010
+ return /* @__PURE__ */ jsxs(
11011
+ View,
11012
+ {
11013
+ className,
11014
+ style: [
11015
+ {
11016
+ backgroundColor: RAISED,
11017
+ paddingTop: 11,
11018
+ paddingRight: 12,
11019
+ paddingBottom: 12,
11020
+ paddingLeft: 12,
11021
+ zIndex: 2
11022
+ },
11023
+ style
11024
+ ],
11025
+ testID: "session-rail-header",
11026
+ ...props,
11027
+ children: [
11028
+ /* @__PURE__ */ jsx(
11029
+ Text,
11030
+ {
11031
+ accessibilityRole: "header",
11032
+ style: {
11033
+ fontSize: 16,
11034
+ lineHeight: 18,
11035
+ fontWeight: "700",
11036
+ fontFamily: '"Space Grotesk", sans-serif',
11037
+ color: t10["text-primary"],
11038
+ marginBottom: 10
11039
+ },
11040
+ testID: "session-rail-title",
11041
+ children: title
11042
+ }
11043
+ ),
11044
+ upcoming ? /* @__PURE__ */ jsx(ScheduleTiles, { when: next }) : /* @__PURE__ */ jsx(MetricTiles, { metrics }),
11045
+ /* @__PURE__ */ jsxs(View, { style: { marginTop: 10 }, children: [
11046
+ /* @__PURE__ */ jsx(
11047
+ SegmentedProgressBar,
11048
+ {
11049
+ segments: plan.map((e) => ({ weight: e.sets })),
11050
+ value: upcoming ? 0 : setsDone,
11051
+ target,
11052
+ height: BAR_HEIGHT,
11053
+ gap: BAR_GAP
11054
+ }
11055
+ ),
11056
+ /* @__PURE__ */ jsxs(
11057
+ View,
11058
+ {
11059
+ style: {
11060
+ flexDirection: "row",
11061
+ justifyContent: "space-between",
11062
+ alignItems: "baseline",
11063
+ marginTop: 5
11064
+ },
11065
+ children: [
11066
+ /* @__PURE__ */ jsx(
11067
+ Typography,
11068
+ {
11069
+ variant: "mono",
11070
+ style: { fontSize: 10, color: setsLabelColor },
11071
+ testID: "session-rail-sets",
11072
+ children: setsLabel
11073
+ }
11074
+ ),
11075
+ /* @__PURE__ */ jsx(
11076
+ TimerReadout,
11077
+ {
11078
+ mode: "up",
11079
+ elapsedMs: upcoming ? budgetMs : elapsedMs,
11080
+ durationMs: budgetMs,
11081
+ running: running && !upcoming,
11082
+ showTotal: budgetMs != null && !upcoming
11083
+ }
11084
+ )
11085
+ ]
11086
+ }
11087
+ )
11088
+ ] })
11089
+ ]
11090
+ }
9948
11091
  );
9949
11092
  }
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
- }
11093
+ var INSET = primitiveColors.charcoal[800];
11094
+ var DIVIDER = primitiveColors.charcoal[500];
11095
+ var DEFAULT_WIDTH = 246;
11096
+ function SessionRail({
11097
+ title,
11098
+ exercises,
11099
+ setsDone,
11100
+ elapsedMs,
11101
+ budgetMs,
11102
+ running,
11103
+ metrics,
11104
+ next,
11105
+ stripHeight = 8,
11106
+ width = DEFAULT_WIDTH,
11107
+ onExercisePress,
11108
+ className,
11109
+ style,
11110
+ ...props
11111
+ }) {
11112
+ return /* @__PURE__ */ jsxs(
11113
+ View,
11114
+ {
11115
+ className,
11116
+ style: [{ width, flexDirection: "column", backgroundColor: INSET }, style],
11117
+ testID: "session-rail",
11118
+ ...props,
11119
+ children: [
11120
+ /* @__PURE__ */ jsx(
11121
+ SessionHeader,
11122
+ {
11123
+ title,
11124
+ plan: exercises.map((ex) => ({ name: ex.name, sets: ex.summary.sets })),
11125
+ setsDone,
11126
+ elapsedMs,
11127
+ budgetMs,
11128
+ running,
11129
+ metrics,
11130
+ next
11131
+ }
11132
+ ),
11133
+ /* @__PURE__ */ jsx(
11134
+ View,
11135
+ {
11136
+ style: [{ flex: 1, backgroundColor: INSET }, neumorphicShadows.charcoal.pressed.subtle],
11137
+ testID: "session-rail-list",
11138
+ children: exercises.map((ex, i) => /* @__PURE__ */ jsx(
11139
+ View,
11140
+ {
11141
+ style: {
11142
+ borderBottomWidth: i < exercises.length - 1 ? 1 : 0,
11143
+ borderBottomColor: DIVIDER
11144
+ },
11145
+ children: /* @__PURE__ */ jsx(
11146
+ ExerciseCardHeading,
11147
+ {
11148
+ name: ex.name,
11149
+ sets: ex.summary.sets,
11150
+ reps: ex.summary.reps,
11151
+ load: ex.summary.weight,
11152
+ unit: ex.summary.unit,
11153
+ tempo: ex.tempo,
11154
+ indicator: ex.indicator,
11155
+ setStates: ex.setStates,
11156
+ stripHeight,
11157
+ dimmed: ex.upcoming,
11158
+ onPress: () => onExercisePress?.(ex, i)
11159
+ }
11160
+ )
11161
+ },
11162
+ ex.id ?? i
11163
+ ))
11164
+ }
11165
+ )
11166
+ ]
11167
+ }
11168
+ );
9959
11169
  }
9960
11170
  var DEFAULTS = {
9961
11171
  label: "SS",
@@ -10219,10 +11429,10 @@ function WeekRow({
10219
11429
  }
10220
11430
  );
10221
11431
  }
10222
- var t8 = getSemanticColors("dark");
11432
+ var t11 = getSemanticColors("dark");
10223
11433
  var COLORS = {
10224
- statusSuccess: t8["status-success"],
10225
- brandPrimary: t8["brand-primary"],
11434
+ statusSuccess: t11["status-success"],
11435
+ brandPrimary: t11["brand-primary"],
10226
11436
  borderDefault: "#1F1F1F",
10227
11437
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
10228
11438
  };
@@ -10706,12 +11916,12 @@ function PrHistoryModal({
10706
11916
  }
10707
11917
  );
10708
11918
  }
10709
- var t9 = getSemanticColors("dark");
10710
- var BRAND_PRIMARY9 = t9["brand-primary"];
11919
+ var t12 = getSemanticColors("dark");
11920
+ var BRAND_PRIMARY9 = t12["brand-primary"];
10711
11921
  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"];
11922
+ var STATUS_SUCCESS = t12["status-success"];
11923
+ var STATUS_WARNING = t12["status-warning"];
11924
+ var STATUS_ERROR2 = t12["status-error"];
10715
11925
  var EMOJI_SETS = {
10716
11926
  sleep: ["\u{1F634}", "\u{1F610}", "\u{1F642}", "\u{1F60A}", "\u{1F929}"],
10717
11927
  soreness: ["\u{1F635}", "\u{1F623}", "\u{1F610}", "\u{1F642}", "\u{1F4AA}"],
@@ -10901,13 +12111,13 @@ function ReadinessCheck({
10901
12111
  )
10902
12112
  ] }) });
10903
12113
  }
10904
- var t10 = getSemanticColors("dark");
10905
- var BRAND_PRIMARY10 = t10["brand-primary"];
12114
+ var t13 = getSemanticColors("dark");
12115
+ var BRAND_PRIMARY10 = t13["brand-primary"];
10906
12116
  var BRAND_PRIMARY_DARK2 = MESO_ACCENT_GRADIENT_DARK;
10907
12117
  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"];
12118
+ var SUCCESS = t13["status-success"];
12119
+ var WARNING = t13["status-warning"];
12120
+ var ERROR = t13["status-error"];
10911
12121
  var ACCENT_STOPS2 = [BRAND_PRIMARY_DARK2, BRAND_PRIMARY10, BRAND_PRIMARY_LIGHT2];
10912
12122
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
10913
12123
  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 +12189,7 @@ function StatusPill({ badge }) {
10979
12189
  }
10980
12190
  );
10981
12191
  }
10982
- function MetricCell({ metric }) {
12192
+ function MetricCell2({ metric }) {
10983
12193
  return /* @__PURE__ */ jsxs(View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
10984
12194
  /* @__PURE__ */ jsx(
10985
12195
  Text,
@@ -11197,7 +12407,7 @@ function MesoStatusCard({
11197
12407
  className: "flex-row flex-wrap",
11198
12408
  style: { gap: 8 },
11199
12409
  testID: "meso-status-card-metrics",
11200
- children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
12410
+ children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell2, { metric }, `${metric.label}-${index}`))
11201
12411
  }
11202
12412
  ),
11203
12413
  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 +12488,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
11278
12488
  var PLOT_LEFT = 26;
11279
12489
  var TOOLTIP_WIDTH = 124;
11280
12490
  function timeOf(dateStr) {
11281
- const t12 = Date.parse(dateStr);
11282
- return Number.isNaN(t12) ? 0 : t12;
12491
+ const t18 = Date.parse(dateStr);
12492
+ return Number.isNaN(t18) ? 0 : t18;
11283
12493
  }
11284
12494
  function formatValue(value) {
11285
12495
  return `${Math.round(value)}`;
@@ -11768,10 +12978,10 @@ function StrengthTrendChart({
11768
12978
  }
11769
12979
  );
11770
12980
  }
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"];
12981
+ var t14 = getSemanticColors("dark");
12982
+ var STATUS_SUCCESS3 = t14["status-success"];
12983
+ var STATUS_WARNING3 = t14["status-warning"];
12984
+ var STATUS_INFO = t14["status-info"];
11775
12985
  var DOT_BORDER = "#F3F4F6";
11776
12986
  var BAND_FILL = "rgba(46,213,115,0.1)";
11777
12987
  var BAND_EDGE = "rgba(46,213,115,0.45)";
@@ -11814,13 +13024,13 @@ function interpEdge(pixels, x, key) {
11814
13024
  const a = pixels[i];
11815
13025
  const b = pixels[i + 1];
11816
13026
  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]);
13027
+ const t18 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
13028
+ return a[key] + t18 * (b[key] - a[key]);
11819
13029
  }
11820
13030
  }
11821
13031
  return pixels[pixels.length - 1][key];
11822
13032
  }
11823
- function buildColumns(pixels, step) {
13033
+ function buildColumns2(pixels, step) {
11824
13034
  if (pixels.length < 2) return [];
11825
13035
  const start = pixels[0].x;
11826
13036
  const end = pixels[pixels.length - 1].x;
@@ -11932,7 +13142,7 @@ function CapacityBandChart({
11932
13142
  }
11933
13143
  const { toX, toY } = scale;
11934
13144
  const bandPixels = toPixels(band, toX, toY);
11935
- const columns = buildColumns(bandPixels, COLUMN_STEP);
13145
+ const columns = buildColumns2(bandPixels, COLUMN_STEP);
11936
13146
  const topEdge = buildEdges(bandPixels, "yHigh");
11937
13147
  const bottomEdge = buildEdges(bandPixels, "yLow");
11938
13148
  const lastBandPoint = band[band.length - 1];
@@ -12007,8 +13217,8 @@ function CapacityBandChart({
12007
13217
  testID: "capacity-band-chart-reveal",
12008
13218
  children: /* @__PURE__ */ jsxs(View, { style: { width, height, position: "relative" }, children: [
12009
13219
  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"),
13220
+ renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
13221
+ renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
12012
13222
  renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
12013
13223
  renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
12014
13224
  renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
@@ -12075,72 +13285,585 @@ function CapacityBandChart({
12075
13285
  return /* @__PURE__ */ jsx(
12076
13286
  Text,
12077
13287
  {
12078
- className: "text-text-tertiary",
13288
+ className: "text-text-tertiary",
13289
+ style: {
13290
+ position: "absolute",
13291
+ left: toX(point.date) - 14,
13292
+ top: height - PADDING_BOTTOM + 2,
13293
+ width: 28,
13294
+ textAlign: "center",
13295
+ fontSize: 10,
13296
+ fontFamily: "Inter, sans-serif"
13297
+ },
13298
+ accessibilityElementsHidden: true,
13299
+ importantForAccessibility: "no-hide-descendants",
13300
+ testID: "capacity-band-chart-x-label",
13301
+ children: formatDate2(point.date)
13302
+ },
13303
+ `x-${point.date}`
13304
+ );
13305
+ })
13306
+ ]
13307
+ }
13308
+ ),
13309
+ workouts.map((workout, i) => {
13310
+ const cx = toX(workout.date);
13311
+ const cy = toY(workout.load);
13312
+ const color = DOT_COLORS[workout.status];
13313
+ const dotStyle = {
13314
+ width: DOT_SIZE,
13315
+ height: DOT_SIZE,
13316
+ borderRadius: DOT_SIZE / 2,
13317
+ backgroundColor: color,
13318
+ borderWidth: 2,
13319
+ borderColor: DOT_BORDER
13320
+ };
13321
+ const label = `Workout on ${formatDate2(workout.date)}, load ${workout.load}, ${STATUS_PHRASES[workout.status]}`;
13322
+ return /* @__PURE__ */ jsx(
13323
+ Animated.View,
13324
+ {
13325
+ style: {
13326
+ position: "absolute",
13327
+ left: cx - DOT_SIZE / 2,
13328
+ top: cy - DOT_SIZE / 2,
13329
+ transform: [{ scale: dotAnims[i] ?? 1 }]
13330
+ },
13331
+ testID: "capacity-band-chart-dot-wrapper",
13332
+ children: onWorkoutPress ? /* @__PURE__ */ jsx(
13333
+ Pressable,
13334
+ {
13335
+ onPress: () => onWorkoutPress(workout),
13336
+ accessibilityRole: "button",
13337
+ accessibilityLabel: label,
13338
+ style: dotStyle,
13339
+ testID: "capacity-band-chart-dot"
13340
+ }
13341
+ ) : /* @__PURE__ */ jsx(
13342
+ View,
13343
+ {
13344
+ accessibilityElementsHidden: true,
13345
+ importantForAccessibility: "no-hide-descendants",
13346
+ style: dotStyle,
13347
+ testID: "capacity-band-chart-dot"
13348
+ }
13349
+ )
13350
+ },
13351
+ `dot-${workout.date}-${i}`
13352
+ );
13353
+ })
13354
+ ]
13355
+ }
13356
+ );
13357
+ }
13358
+ var t15 = getSemanticColors("dark");
13359
+ function glowShadow(color) {
13360
+ return `0 0 5px 1px ${alpha(color, 0.3)}, 0 0 10px 3px ${alpha(color, 0.12)}`;
13361
+ }
13362
+ var DEFAULT_TRACK_COLOR = primitiveColors.charcoal[200];
13363
+ var DEFAULT_MARKER_COLOR2 = primitiveColors.white;
13364
+ var TICK_COLOR = t15["text-tertiary"];
13365
+ var ZONE_SIZES = {
13366
+ default: {
13367
+ trackHeight: 14,
13368
+ needleOverhang: 6,
13369
+ needleWidth: 4,
13370
+ tickLineNormal: 1.5,
13371
+ tickLineEmphasized: 2,
13372
+ tickLineHeightPad: 4,
13373
+ tickLineTopPad: 2,
13374
+ labelRowHeight: 16,
13375
+ labelMarginTop: 5,
13376
+ tickFont: 9,
13377
+ tickLetterSpacing: 0.5,
13378
+ tickCellWidth: 28
13379
+ },
13380
+ wall: {
13381
+ trackHeight: 24,
13382
+ needleOverhang: 9,
13383
+ needleWidth: 7,
13384
+ tickLineNormal: 2.5,
13385
+ tickLineEmphasized: 3.5,
13386
+ tickLineHeightPad: 6,
13387
+ tickLineTopPad: 3,
13388
+ labelRowHeight: 24,
13389
+ labelMarginTop: 8,
13390
+ tickFont: 14,
13391
+ tickLetterSpacing: 0.5,
13392
+ tickCellWidth: 48
13393
+ }
13394
+ };
13395
+ function fraction(value, min, max) {
13396
+ if (max <= min) return 0;
13397
+ return Math.max(0, Math.min(1, (value - min) / (max - min)));
13398
+ }
13399
+ function pct(frac) {
13400
+ return `${frac * 100}%`;
13401
+ }
13402
+ function ZoneTrack({
13403
+ zones,
13404
+ max,
13405
+ min = 0,
13406
+ marker = null,
13407
+ ticks,
13408
+ band = null,
13409
+ size = "default",
13410
+ trackHeight: trackHeightProp,
13411
+ needleOverhang: needleOverhangProp,
13412
+ trackColor = DEFAULT_TRACK_COLOR,
13413
+ className,
13414
+ style,
13415
+ accessibilityLabel: accessibilityLabel2,
13416
+ ...props
13417
+ }) {
13418
+ const s = ZONE_SIZES[size];
13419
+ const trackHeight = trackHeightProp ?? s.trackHeight;
13420
+ const needleOverhang = needleOverhangProp ?? s.needleOverhang;
13421
+ const isNeedle = marker?.type === "needle";
13422
+ const markerHeight = isNeedle ? trackHeight + needleOverhang * 2 : trackHeight;
13423
+ const markerFrac = marker != null ? fraction(marker.value, min, max) : 0;
13424
+ const bands = zones.map((zone, i) => {
13425
+ const prev = i === 0 ? min : zones[i - 1].upTo;
13426
+ const start = Math.max(min, Math.min(max, prev));
13427
+ const end = Math.max(min, Math.min(max, zone.upTo));
13428
+ return { color: zone.color, weight: Math.max(0, end - start) };
13429
+ });
13430
+ return /* @__PURE__ */ jsxs(
13431
+ View,
13432
+ {
13433
+ className,
13434
+ style: [{ width: "100%" }, style],
13435
+ accessibilityRole: "progressbar",
13436
+ accessibilityValue: marker != null ? { min, max, now: marker.value } : { min, max, now: min },
13437
+ accessibilityLabel: accessibilityLabel2 ?? `Zone track: ${marker?.value ?? min}`,
13438
+ testID: "zone-track",
13439
+ ...props,
13440
+ children: [
13441
+ /* @__PURE__ */ jsxs(View, { style: { position: "relative", height: markerHeight }, children: [
13442
+ /* @__PURE__ */ jsxs(
13443
+ View,
13444
+ {
13445
+ testID: "zone-track-track",
13446
+ accessibilityElementsHidden: true,
13447
+ style: {
13448
+ position: "absolute",
13449
+ top: (markerHeight - trackHeight) / 2,
13450
+ left: 0,
13451
+ right: 0,
13452
+ height: trackHeight,
13453
+ borderRadius: trackHeight / 2,
13454
+ backgroundColor: trackColor,
13455
+ overflow: "hidden",
13456
+ flexDirection: "row",
13457
+ // The pill's own box-shadow renders outside its overflow:hidden, so
13458
+ // `glow` haloes the whole track in the marker colour.
13459
+ ...marker?.glow ? { boxShadow: glowShadow(marker.color ?? DEFAULT_MARKER_COLOR2) } : null
13460
+ },
13461
+ children: [
13462
+ bands.map((b, i) => /* @__PURE__ */ jsx(
13463
+ View,
13464
+ {
13465
+ testID: "zone-track-band",
13466
+ style: {
13467
+ flexGrow: b.weight,
13468
+ flexShrink: 1,
13469
+ flexBasis: 0,
13470
+ minWidth: 0,
13471
+ height: "100%",
13472
+ backgroundColor: b.color
13473
+ }
13474
+ },
13475
+ i
13476
+ )),
13477
+ band != null && /* @__PURE__ */ jsx(
13478
+ View,
13479
+ {
13480
+ testID: "zone-track-band-highlight",
13481
+ style: {
13482
+ position: "absolute",
13483
+ top: 0,
13484
+ bottom: 0,
13485
+ left: pct(fraction(band.from, min, max)),
13486
+ width: pct(fraction(band.to, min, max) - fraction(band.from, min, max)),
13487
+ backgroundColor: band.color
13488
+ }
13489
+ }
13490
+ ),
13491
+ marker?.type === "fill" && /* @__PURE__ */ jsx(
13492
+ View,
13493
+ {
13494
+ testID: "zone-track-unfilled",
13495
+ style: {
13496
+ position: "absolute",
13497
+ top: 0,
13498
+ bottom: 0,
13499
+ left: pct(markerFrac),
13500
+ right: 0,
13501
+ backgroundColor: trackColor
13502
+ }
13503
+ }
13504
+ ),
13505
+ marker?.type === "fill" && marker.color != null && /* @__PURE__ */ jsx(
13506
+ View,
13507
+ {
13508
+ testID: "zone-track-fill",
12079
13509
  style: {
12080
13510
  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]}`;
12112
- return /* @__PURE__ */ jsx(
12113
- Animated.View,
13511
+ top: 0,
13512
+ bottom: 0,
13513
+ left: 0,
13514
+ width: pct(markerFrac),
13515
+ backgroundColor: marker.color
13516
+ }
13517
+ }
13518
+ )
13519
+ ]
13520
+ }
13521
+ ),
13522
+ isNeedle && /* @__PURE__ */ jsx(
13523
+ View,
12114
13524
  {
13525
+ testID: "zone-track-needle",
12115
13526
  style: {
12116
13527
  position: "absolute",
12117
- left: cx - DOT_SIZE / 2,
12118
- top: cy - DOT_SIZE / 2,
12119
- transform: [{ scale: dotAnims[i] ?? 1 }]
13528
+ top: 0,
13529
+ left: pct(markerFrac),
13530
+ width: s.needleWidth,
13531
+ height: markerHeight,
13532
+ borderRadius: s.needleWidth / 2,
13533
+ backgroundColor: marker.color ?? DEFAULT_MARKER_COLOR2,
13534
+ transform: [{ translateX: -s.needleWidth / 2 }]
13535
+ }
13536
+ }
13537
+ ),
13538
+ ticks?.map((tick, i) => {
13539
+ const emphasized = tick.emphasized === true;
13540
+ const lineColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13541
+ const lineWidth = emphasized ? s.tickLineEmphasized : s.tickLineNormal;
13542
+ return /* @__PURE__ */ jsx(
13543
+ View,
13544
+ {
13545
+ testID: "zone-track-tick-line",
13546
+ accessibilityElementsHidden: true,
13547
+ style: {
13548
+ position: "absolute",
13549
+ top: (markerHeight - trackHeight) / 2 - s.tickLineTopPad,
13550
+ left: pct(fraction(tick.value, min, max)),
13551
+ width: lineWidth,
13552
+ height: trackHeight + s.tickLineHeightPad,
13553
+ borderRadius: lineWidth / 2,
13554
+ backgroundColor: lineColor,
13555
+ transform: [{ translateX: -lineWidth / 2 }],
13556
+ zIndex: 3
13557
+ }
12120
13558
  },
12121
- testID: "capacity-band-chart-dot-wrapper",
12122
- children: onWorkoutPress ? /* @__PURE__ */ jsx(
12123
- Pressable,
13559
+ `line-${i}`
13560
+ );
13561
+ })
13562
+ ] }),
13563
+ ticks != null && ticks.length > 0 && /* @__PURE__ */ jsx(
13564
+ View,
13565
+ {
13566
+ style: { position: "relative", height: s.labelRowHeight, marginTop: s.labelMarginTop },
13567
+ children: ticks.map((tick, i) => {
13568
+ if (tick.label == null) return null;
13569
+ const emphasized = tick.emphasized === true;
13570
+ const labelColor = tick.color ?? (emphasized ? t15["brand-primary"] : TICK_COLOR);
13571
+ const labelNode = /* @__PURE__ */ jsx(
13572
+ Text,
12124
13573
  {
12125
- onPress: () => onWorkoutPress(workout),
12126
- accessibilityRole: "button",
12127
- accessibilityLabel: label,
12128
- style: dotStyle,
12129
- testID: "capacity-band-chart-dot"
13574
+ testID: "zone-track-tick-label",
13575
+ style: {
13576
+ fontSize: s.tickFont,
13577
+ letterSpacing: s.tickLetterSpacing,
13578
+ color: labelColor,
13579
+ fontFamily: "monospace",
13580
+ fontWeight: emphasized ? "700" : "400"
13581
+ },
13582
+ children: tick.label
12130
13583
  }
12131
- ) : /* @__PURE__ */ jsx(
13584
+ );
13585
+ return /* @__PURE__ */ jsx(
12132
13586
  View,
12133
13587
  {
12134
- accessibilityElementsHidden: true,
12135
- importantForAccessibility: "no-hide-descendants",
12136
- style: dotStyle,
12137
- testID: "capacity-band-chart-dot"
12138
- }
12139
- )
13588
+ testID: "zone-track-tick",
13589
+ style: {
13590
+ position: "absolute",
13591
+ left: pct(fraction(tick.value, min, max)),
13592
+ transform: [{ translateX: -s.tickCellWidth / 2 }],
13593
+ width: s.tickCellWidth,
13594
+ alignItems: "center"
13595
+ },
13596
+ children: tick.tooltip != null ? /* @__PURE__ */ jsx(Tooltip, { label: tick.tooltip, placement: "top", children: labelNode }) : labelNode
13597
+ },
13598
+ `label-${i}`
13599
+ );
13600
+ })
13601
+ }
13602
+ )
13603
+ ]
13604
+ }
13605
+ );
13606
+ }
13607
+
13608
+ // src/components/custom/Workout/FatigueMeter.tsx
13609
+ var { green, yellow, orange, red } = WORKOUT_TOKENS.scale;
13610
+ var DEFAULT_ZONE_COLORS = [green, yellow, orange, red];
13611
+ var DEFAULT_THRESHOLDS = [10, 20, 30];
13612
+ var DEFAULT_LABELS = ["fresh", "VL10", "VL20", "VL30", "stop"];
13613
+ var DEFAULT_MAX = 40;
13614
+ function FatigueMeter({
13615
+ value,
13616
+ max = DEFAULT_MAX,
13617
+ thresholds = DEFAULT_THRESHOLDS,
13618
+ zoneColors = DEFAULT_ZONE_COLORS,
13619
+ labels = DEFAULT_LABELS,
13620
+ size = "default",
13621
+ trackHeight,
13622
+ needleColor,
13623
+ className,
13624
+ ...props
13625
+ }) {
13626
+ const [t1, t22, t32] = thresholds;
13627
+ const zones = [
13628
+ { upTo: t1, color: zoneColors[0] },
13629
+ { upTo: t22, color: zoneColors[1] },
13630
+ { upTo: t32, color: zoneColors[2] },
13631
+ { upTo: max, color: zoneColors[3] }
13632
+ ];
13633
+ const tickValues = [0, t1, t22, t32, max];
13634
+ const ticks = tickValues.map((v, i) => ({ value: v, label: labels[i] }));
13635
+ return /* @__PURE__ */ jsx(
13636
+ ZoneTrack,
13637
+ {
13638
+ className,
13639
+ zones,
13640
+ min: 0,
13641
+ max,
13642
+ size,
13643
+ trackHeight,
13644
+ ticks,
13645
+ marker: { type: "needle", value, color: needleColor },
13646
+ accessibilityLabel: `Fatigue: ${value}% velocity loss`,
13647
+ testID: "fatigue-meter",
13648
+ ...props
13649
+ }
13650
+ );
13651
+ }
13652
+ var HEAT = WORKOUT_TOKENS.heatmap;
13653
+ var NEUTRAL_TRACK = primitiveColors.charcoal[200];
13654
+ var MONO = "monospace";
13655
+ var HEAT_BY_ZONE = {
13656
+ under: HEAT.under,
13657
+ maintenance: HEAT.maintenance,
13658
+ productive: HEAT.productive,
13659
+ approaching: HEAT.approaching,
13660
+ over: HEAT.over
13661
+ };
13662
+ var ZONE_DESCRIPTION = {
13663
+ under: "below MEV",
13664
+ maintenance: "building toward MAV",
13665
+ productive: "in the productive zone",
13666
+ approaching: "approaching MRV",
13667
+ over: "over MRV"
13668
+ };
13669
+ var LANDMARK_NAME = {
13670
+ MEV: "Minimum Effective Volume",
13671
+ MAV: "Maximum Adaptive Volume",
13672
+ MRV: "Maximum Recoverable Volume"
13673
+ };
13674
+ function zoneForSets(sets, { mev, mav, mrv }) {
13675
+ if (sets < mev) return "under";
13676
+ if (sets < mav) return "maintenance";
13677
+ if (sets >= mrv) return "over";
13678
+ const midpoint = (mav + mrv) / 2;
13679
+ return sets < midpoint ? "productive" : "approaching";
13680
+ }
13681
+ function VolumeLandmarkBar({
13682
+ muscle,
13683
+ currentSets,
13684
+ landmarks,
13685
+ width = 220,
13686
+ trackHeight = 10,
13687
+ scaleMax,
13688
+ className,
13689
+ style,
13690
+ ...props
13691
+ }) {
13692
+ const { mev, mav, mrv } = landmarks;
13693
+ const max = scaleMax ?? mrv * 1.2;
13694
+ const zone = zoneForSets(currentSets, landmarks);
13695
+ const fillColor = HEAT_BY_ZONE[zone];
13696
+ const pct2 = mav > 0 ? Math.round(currentSets / mav * 100) : 0;
13697
+ return /* @__PURE__ */ jsxs(
13698
+ View,
13699
+ {
13700
+ className,
13701
+ style: [{ width, gap: 3 }, style],
13702
+ testID: "volume-landmark-bar",
13703
+ ...props,
13704
+ children: [
13705
+ /* @__PURE__ */ jsx(
13706
+ DataRow,
13707
+ {
13708
+ label: muscle,
13709
+ value: /* @__PURE__ */ jsxs(
13710
+ Text,
13711
+ {
13712
+ testID: "volume-landmark-pct",
13713
+ style: { fontSize: 14, fontWeight: "700", fontFamily: MONO, color: fillColor },
13714
+ children: [
13715
+ pct2,
13716
+ "%"
13717
+ ]
13718
+ }
13719
+ ),
13720
+ className: "py-0",
13721
+ testID: "volume-landmark-header"
13722
+ }
13723
+ ),
13724
+ /* @__PURE__ */ jsx(
13725
+ ZoneTrack,
13726
+ {
13727
+ zones: [{ upTo: max, color: NEUTRAL_TRACK }],
13728
+ max,
13729
+ marker: {
13730
+ type: "fill",
13731
+ value: currentSets,
13732
+ color: fillColor,
13733
+ // Glow when in the optimal productive band — the "sweet spot" cue.
13734
+ glow: zone === "productive"
12140
13735
  },
12141
- `dot-${workout.date}-${i}`
12142
- );
12143
- })
13736
+ trackColor: NEUTRAL_TRACK,
13737
+ trackHeight,
13738
+ ticks: [
13739
+ { value: mev, label: "MEV", tooltip: `${LANDMARK_NAME.MEV} \xB7 ${mev} sets/wk` },
13740
+ {
13741
+ value: mav,
13742
+ label: "MAV",
13743
+ emphasized: true,
13744
+ tooltip: `${LANDMARK_NAME.MAV} (target) \xB7 ${mav} sets/wk`
13745
+ },
13746
+ { value: mrv, label: "MRV", tooltip: `${LANDMARK_NAME.MRV} \xB7 ${mrv} sets/wk` }
13747
+ ],
13748
+ accessibilityLabel: `${muscle} weekly volume: ${currentSets} sets, ${pct2}% of MAV target, ${ZONE_DESCRIPTION[zone]}`,
13749
+ testID: "volume-landmark-track"
13750
+ }
13751
+ )
13752
+ ]
13753
+ }
13754
+ );
13755
+ }
13756
+ var t16 = getSemanticColors("dark");
13757
+ var statusToken = {
13758
+ productive: "status-success",
13759
+ threshold: "status-warning",
13760
+ stop: "status-error"
13761
+ };
13762
+ var statusDotVariant = {
13763
+ productive: "success",
13764
+ threshold: "warning",
13765
+ stop: "error"
13766
+ };
13767
+ var defaultLabel = {
13768
+ productive: "Productive",
13769
+ threshold: "Threshold",
13770
+ stop: "Stop"
13771
+ };
13772
+ function statusPillColor(status) {
13773
+ return t16[statusToken[status]];
13774
+ }
13775
+ function StatusPill2({ status, label, className, style, ...props }) {
13776
+ const color = statusPillColor(status);
13777
+ const text = label ?? defaultLabel[status];
13778
+ return /* @__PURE__ */ jsxs(
13779
+ View,
13780
+ {
13781
+ testID: "status-pill",
13782
+ className,
13783
+ style: [
13784
+ {
13785
+ flexDirection: "row",
13786
+ alignItems: "center",
13787
+ gap: 7,
13788
+ alignSelf: "flex-start",
13789
+ paddingVertical: 7,
13790
+ paddingHorizontal: 13,
13791
+ borderRadius: 9,
13792
+ backgroundColor: alpha(color, 0.14),
13793
+ borderWidth: 1,
13794
+ borderColor: alpha(color, 0.4)
13795
+ },
13796
+ style
13797
+ ],
13798
+ ...props,
13799
+ children: [
13800
+ /* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(StatusDot, { variant: statusDotVariant[status], glow: true }) }),
13801
+ /* @__PURE__ */ jsx(Text, { testID: "status-pill-label", style: { fontSize: 13, fontWeight: "800", color }, children: text })
13802
+ ]
13803
+ }
13804
+ );
13805
+ }
13806
+ var t17 = getSemanticColors("dark");
13807
+ var categoryToken = {
13808
+ productive: null,
13809
+ threshold: "status-warning",
13810
+ stop: "status-error"
13811
+ };
13812
+ var floodOpacity = {
13813
+ productive: 0,
13814
+ threshold: 0.12,
13815
+ stop: 0.18
13816
+ };
13817
+ function liveAuraColor(category) {
13818
+ const token = categoryToken[category];
13819
+ return token ? t17[token] : null;
13820
+ }
13821
+ function LiveAuraFrame({
13822
+ category,
13823
+ pulse = true,
13824
+ className,
13825
+ children,
13826
+ style,
13827
+ ...props
13828
+ }) {
13829
+ const color = liveAuraColor(category);
13830
+ const tint = color ? alpha(color, floodOpacity[category]) : "transparent";
13831
+ const floodStyle = {
13832
+ ...StyleSheet.absoluteFillObject,
13833
+ ...Platform.OS === "web" ? (
13834
+ // backgroundImage is a runtime RNW style, not in RN ViewStyle types.
13835
+ { backgroundImage: `radial-gradient(130% 95% at 50% 0%, ${tint}, transparent 62%)` }
13836
+ ) : { backgroundColor: tint }
13837
+ };
13838
+ return /* @__PURE__ */ jsxs(
13839
+ View,
13840
+ {
13841
+ testID: "live-aura-frame",
13842
+ className,
13843
+ style: [
13844
+ {
13845
+ position: "relative",
13846
+ overflow: "hidden",
13847
+ borderRadius: 10,
13848
+ backgroundColor: t17["background-base"],
13849
+ borderWidth: 1,
13850
+ borderColor: t17["border-default"]
13851
+ },
13852
+ style
13853
+ ],
13854
+ ...props,
13855
+ children: [
13856
+ color && /* @__PURE__ */ jsx(
13857
+ View,
13858
+ {
13859
+ testID: "live-aura-flood",
13860
+ accessibilityElementsHidden: true,
13861
+ pointerEvents: "none",
13862
+ className: category === "stop" && pulse ? "animate-pulse" : void 0,
13863
+ style: floodStyle
13864
+ }
13865
+ ),
13866
+ children
12144
13867
  ]
12145
13868
  }
12146
13869
  );
@@ -12351,28 +14074,28 @@ function Scatter({
12351
14074
  testID: "scatter-canvas",
12352
14075
  style: { position: "absolute", top: 0, left: 0, width, height, overflow: "hidden" },
12353
14076
  children: [
12354
- ticksOf(yd, TICK_COUNT).map((t12, i) => {
12355
- const y = toY(t12);
14077
+ ticksOf(yd, TICK_COUNT).map((t18, i) => {
14078
+ const y = toY(t18);
12356
14079
  return /* @__PURE__ */ jsx(
12357
14080
  View,
12358
14081
  {
12359
14082
  accessibilityElementsHidden: true,
12360
14083
  testID: "scatter-gridline-y",
12361
14084
  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) })
14085
+ 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
14086
  },
12364
14087
  `y-${i}`
12365
14088
  );
12366
14089
  }),
12367
- ticksOf(xd, TICK_COUNT).map((t12, i) => {
12368
- const x = toX(t12);
14090
+ ticksOf(xd, TICK_COUNT).map((t18, i) => {
14091
+ const x = toX(t18);
12369
14092
  return /* @__PURE__ */ jsx(
12370
14093
  View,
12371
14094
  {
12372
14095
  accessibilityElementsHidden: true,
12373
14096
  testID: "scatter-gridline-x",
12374
14097
  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) })
14098
+ 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
14099
  },
12377
14100
  `x-${i}`
12378
14101
  );
@@ -12437,7 +14160,7 @@ var STATUS_SUCCESS4 = sem2["status-success"];
12437
14160
  var STATUS_WARNING4 = sem2["status-warning"];
12438
14161
  var STATUS_ERROR4 = sem2["status-error"];
12439
14162
  var TRACK = "rgba(255,255,255,0.08)";
12440
- var DEFAULT_THRESHOLDS = [
14163
+ var DEFAULT_THRESHOLDS2 = [
12441
14164
  { value: 0, color: STATUS_ERROR4 },
12442
14165
  { value: 60, color: STATUS_WARNING4 },
12443
14166
  { value: 80, color: STATUS_SUCCESS4 }
@@ -12452,8 +14175,8 @@ function clamp012(n) {
12452
14175
  function formatValue2(v) {
12453
14176
  return Number.isInteger(v) ? `${v}` : v.toFixed(1);
12454
14177
  }
12455
- function bandColor2(fraction, bands, min, max) {
12456
- const value = min + fraction * (max - min);
14178
+ function bandColor2(fraction2, bands, min, max) {
14179
+ const value = min + fraction2 * (max - min);
12457
14180
  const sorted = [...bands].sort((a, b) => a.value - b.value);
12458
14181
  let color = sorted[0]?.color ?? STATUS_SUCCESS4;
12459
14182
  for (const band of sorted) {
@@ -12465,15 +14188,15 @@ function buildTicks(fill, size, activeColor) {
12465
14188
  const center = size / 2;
12466
14189
  const radius = center - size * 0.08;
12467
14190
  return Array.from({ length: SEGMENTS }, (_, i) => {
12468
- const fraction = (i + 0.5) / SEGMENTS;
12469
- const deg = ARC_START + fraction * ARC_SWEEP;
14191
+ const fraction2 = (i + 0.5) / SEGMENTS;
14192
+ const deg = ARC_START + fraction2 * ARC_SWEEP;
12470
14193
  const rad = deg * Math.PI / 180;
12471
14194
  return {
12472
14195
  key: i,
12473
14196
  left: center + radius * Math.cos(rad),
12474
14197
  top: center + radius * Math.sin(rad),
12475
14198
  rotate: `${deg - 90}deg`,
12476
- color: fraction <= fill ? activeColor(fraction) : TRACK
14199
+ color: fraction2 <= fill ? activeColor(fraction2) : TRACK
12477
14200
  };
12478
14201
  });
12479
14202
  }
@@ -12484,7 +14207,7 @@ function Gauge2({
12484
14207
  size = 160,
12485
14208
  label,
12486
14209
  unit,
12487
- thresholds = DEFAULT_THRESHOLDS,
14210
+ thresholds = DEFAULT_THRESHOLDS2,
12488
14211
  color,
12489
14212
  className,
12490
14213
  ...props
@@ -12507,23 +14230,23 @@ function Gauge2({
12507
14230
  testID: "gauge",
12508
14231
  ...props,
12509
14232
  children: [
12510
- ticks.map((t12) => /* @__PURE__ */ jsx(
14233
+ ticks.map((t18) => /* @__PURE__ */ jsx(
12511
14234
  View,
12512
14235
  {
12513
14236
  accessibilityElementsHidden: true,
12514
14237
  testID: "gauge-segment",
12515
14238
  style: {
12516
14239
  position: "absolute",
12517
- left: t12.left - tickThickness / 2,
12518
- top: t12.top - tickLength / 2,
14240
+ left: t18.left - tickThickness / 2,
14241
+ top: t18.top - tickLength / 2,
12519
14242
  width: tickThickness,
12520
14243
  height: tickLength,
12521
14244
  borderRadius: tickThickness / 2,
12522
- backgroundColor: t12.color,
12523
- transform: [{ rotate: t12.rotate }]
14245
+ backgroundColor: t18.color,
14246
+ transform: [{ rotate: t18.rotate }]
12524
14247
  }
12525
14248
  },
12526
- t12.key
14249
+ t18.key
12527
14250
  )),
12528
14251
  /* @__PURE__ */ jsxs(View, { style: { position: "absolute", top: 0, left: 0, width: size, height: size }, className: "items-center justify-center", children: [
12529
14252
  /* @__PURE__ */ jsxs(View, { className: "flex-row items-baseline gap-0.5", children: [
@@ -12569,7 +14292,7 @@ function BrandLockup({
12569
14292
  }
12570
14293
  var stateConfig = {
12571
14294
  // 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" },
14295
+ live: { label: "LIVE", color: "live", pulse: "ping", textColor: "primary" },
12573
14296
  rest: { label: "REST", color: "warning", pulse: false, textColor: "primary" },
12574
14297
  idle: { label: "IDLE", color: "default", pulse: false, textColor: "secondary" }
12575
14298
  };
@@ -12733,6 +14456,127 @@ function TopBar({
12733
14456
  }
12734
14457
  );
12735
14458
  }
14459
+ function NavItem({
14460
+ icon,
14461
+ label,
14462
+ active = false,
14463
+ live = false,
14464
+ onPress,
14465
+ className
14466
+ }) {
14467
+ const glyphColor = active ? "text-brand-primary" : "text-text-tertiary";
14468
+ const labelColor = active ? "text-brand-primary" : live ? "text-status-live-muted" : "text-text-tertiary";
14469
+ return /* @__PURE__ */ jsxs(
14470
+ Pressable,
14471
+ {
14472
+ accessibilityRole: "tab",
14473
+ accessibilityState: { selected: active },
14474
+ accessibilityLabel: label,
14475
+ onPress,
14476
+ className: cn("relative h-[46px] w-[60px] items-center justify-center", className),
14477
+ children: [
14478
+ active ? /* @__PURE__ */ jsx(
14479
+ View,
14480
+ {
14481
+ testID: "nav-item-accent",
14482
+ className: "absolute left-0 top-[13px] bottom-[13px] w-[3px] rounded-r-[3px] bg-brand-primary"
14483
+ }
14484
+ ) : null,
14485
+ /* @__PURE__ */ jsxs(
14486
+ View,
14487
+ {
14488
+ className: cn(
14489
+ "h-[46px] w-[46px] items-center justify-center gap-[3px] rounded-[11px]",
14490
+ glyphColor
14491
+ ),
14492
+ children: [
14493
+ icon,
14494
+ /* @__PURE__ */ jsx(
14495
+ Typography,
14496
+ {
14497
+ variant: "button",
14498
+ color: "inherit",
14499
+ className: cn("text-[8.5px] font-bold uppercase tracking-[0.4px]", labelColor),
14500
+ children: label
14501
+ }
14502
+ )
14503
+ ]
14504
+ }
14505
+ )
14506
+ ]
14507
+ }
14508
+ );
14509
+ }
14510
+ var defaultNavItems = [
14511
+ { key: "live", label: "Live", icon: /* @__PURE__ */ jsx(ActivityIcon, { size: 20, color: "currentColor" }) },
14512
+ { key: "review", label: "Review", icon: /* @__PURE__ */ jsx(HistoryIcon, { size: 20, color: "currentColor" }) },
14513
+ { key: "program", label: "Plan", icon: /* @__PURE__ */ jsx(LayersIcon, { size: 20, color: "currentColor" }) },
14514
+ { key: "body", label: "Body", icon: /* @__PURE__ */ jsx(PersonStandingIcon, { size: 20, color: "currentColor" }) }
14515
+ ];
14516
+ function SideNav({
14517
+ items = defaultNavItems,
14518
+ activeKey,
14519
+ onNavigate,
14520
+ liveKey = null,
14521
+ className
14522
+ }) {
14523
+ return /* @__PURE__ */ jsx(
14524
+ View,
14525
+ {
14526
+ accessibilityRole: "tablist",
14527
+ className: cn(
14528
+ "w-[60px] items-center gap-[6px] border-r border-border bg-background-base py-3",
14529
+ className
14530
+ ),
14531
+ children: items.map((item) => /* @__PURE__ */ jsx(
14532
+ NavItem,
14533
+ {
14534
+ icon: item.icon,
14535
+ label: item.label,
14536
+ active: item.key === activeKey,
14537
+ live: item.key === liveKey && item.key !== activeKey,
14538
+ onPress: () => onNavigate?.(item.key)
14539
+ },
14540
+ item.key
14541
+ ))
14542
+ }
14543
+ );
14544
+ }
14545
+ var DEMO_DEVICES = [
14546
+ { id: "Voltra-A3F2", nickname: "Left Cable", slot: "L", state: "connected" },
14547
+ { id: "Voltra-9B1C", nickname: "Right Cable", slot: "R", state: "connected" }
14548
+ ];
14549
+ function ContentPlaceholder() {
14550
+ 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" }) });
14551
+ }
14552
+ function DashboardShell({
14553
+ activeKey = "live",
14554
+ navItems,
14555
+ liveKey = null,
14556
+ state = "live",
14557
+ devices = DEMO_DEVICES,
14558
+ subtitle = "wall dashboard",
14559
+ onNavigate,
14560
+ onSelectDevice,
14561
+ children,
14562
+ className
14563
+ }) {
14564
+ return /* @__PURE__ */ jsxs(View, { className: cn("flex-1 flex-row bg-surface-base", className), children: [
14565
+ /* @__PURE__ */ jsx(SideNav, { activeKey, items: navItems, liveKey, onNavigate }),
14566
+ /* @__PURE__ */ jsxs(View, { className: "flex-1", children: [
14567
+ /* @__PURE__ */ jsx(
14568
+ TopBar,
14569
+ {
14570
+ state,
14571
+ devices,
14572
+ subtitle,
14573
+ onSelectDevice
14574
+ }
14575
+ ),
14576
+ /* @__PURE__ */ jsx(View, { className: "flex-1", children: children ?? /* @__PURE__ */ jsx(ContentPlaceholder, {}) })
14577
+ ] })
14578
+ ] });
14579
+ }
12736
14580
 
12737
14581
  // src/utils/form.ts
12738
14582
  function getFieldValidationProps(state) {
@@ -12822,6 +14666,6 @@ function composeValidators(...validators) {
12822
14666
  };
12823
14667
  }
12824
14668
 
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 };
14669
+ 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, CircularTimer, 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
14670
  //# sourceMappingURL=index.mjs.map
12827
14671
  //# sourceMappingURL=index.mjs.map