@titan-design/react-ui 0.2.6 → 0.3.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 (191) hide show
  1. package/dist/bodymap-BhG55xHM.d.mts +318 -0
  2. package/dist/bodymap-BhG55xHM.d.ts +318 -0
  3. package/dist/bodymap.d.mts +3 -0
  4. package/dist/bodymap.d.ts +3 -0
  5. package/dist/bodymap.js +2670 -0
  6. package/dist/bodymap.js.map +1 -0
  7. package/dist/bodymap.mjs +1101 -0
  8. package/dist/bodymap.mjs.map +1 -0
  9. package/dist/{chunk-UXVRPOME.mjs → chunk-7XPB4NAO.mjs} +125 -785
  10. package/dist/chunk-7XPB4NAO.mjs.map +1 -0
  11. package/dist/chunk-P7TSQUN6.mjs +846 -0
  12. package/dist/chunk-P7TSQUN6.mjs.map +1 -0
  13. package/dist/chunk-TOD2WQBG.mjs +1048 -0
  14. package/dist/chunk-TOD2WQBG.mjs.map +1 -0
  15. package/dist/index.d.mts +1131 -4
  16. package/dist/index.d.ts +1131 -4
  17. package/dist/index.js +6396 -225
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +5572 -641
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/theme/index.d.mts +317 -1
  22. package/dist/theme/index.d.ts +317 -1
  23. package/dist/theme/index.js +185 -2
  24. package/dist/theme/index.js.map +1 -1
  25. package/dist/theme/index.mjs +2 -1
  26. package/dist/theme/tokens-css.d.mts +20 -0
  27. package/dist/theme/tokens-css.d.ts +20 -0
  28. package/dist/theme/tokens-css.js +598 -0
  29. package/dist/theme/tokens-css.js.map +1 -0
  30. package/dist/theme/tokens-css.mjs +24 -0
  31. package/dist/theme/tokens-css.mjs.map +1 -0
  32. package/package.json +25 -5
  33. package/src/bodymap.ts +37 -0
  34. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  35. package/src/components/custom/Gauge/Gauge.stories.tsx +45 -0
  36. package/src/components/custom/Gauge/Gauge.test.tsx +71 -0
  37. package/src/components/custom/Gauge/Gauge.tsx +167 -0
  38. package/src/components/custom/Gauge/index.ts +2 -0
  39. package/src/components/custom/Scatter/Scatter.stories.tsx +85 -0
  40. package/src/components/custom/Scatter/Scatter.test.tsx +96 -0
  41. package/src/components/custom/Scatter/Scatter.tsx +260 -0
  42. package/src/components/custom/Scatter/index.ts +2 -0
  43. package/src/components/custom/Sidebar/Sidebar.tsx +1 -1
  44. package/src/components/custom/Table/Table.test.tsx +27 -0
  45. package/src/components/custom/Table/Table.tsx +33 -17
  46. package/src/components/custom/Treemap/Treemap.stories.tsx +60 -0
  47. package/src/components/custom/Treemap/Treemap.test.tsx +67 -0
  48. package/src/components/custom/Treemap/Treemap.tsx +192 -0
  49. package/src/components/custom/Treemap/index.ts +2 -0
  50. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +118 -0
  51. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +171 -0
  52. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +447 -0
  53. package/src/components/custom/Workout/BaseBadge.stories.tsx +75 -0
  54. package/src/components/custom/Workout/BaseBadge.test.tsx +102 -0
  55. package/src/components/custom/Workout/BaseBadge.tsx +113 -0
  56. package/src/components/custom/Workout/BodyMap.stories.tsx +121 -0
  57. package/src/components/custom/Workout/BodyMap.test.tsx +124 -0
  58. package/src/components/custom/Workout/BodyMap.tsx +347 -0
  59. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +171 -0
  60. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +225 -0
  61. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +477 -0
  62. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +125 -0
  63. package/src/components/custom/Workout/CapacityBandChart.test.tsx +193 -0
  64. package/src/components/custom/Workout/CapacityBandChart.tsx +493 -0
  65. package/src/components/custom/Workout/DeviationBar.stories.tsx +58 -0
  66. package/src/components/custom/Workout/DeviationBar.test.tsx +86 -0
  67. package/src/components/custom/Workout/DeviationBar.tsx +78 -0
  68. package/src/components/custom/Workout/ExerciseCard.stories.tsx +186 -0
  69. package/src/components/custom/Workout/ExerciseCard.test.tsx +343 -0
  70. package/src/components/custom/Workout/ExerciseCard.tsx +442 -0
  71. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +225 -0
  72. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +128 -0
  73. package/src/components/custom/Workout/ExerciseDetailPage.tsx +630 -0
  74. package/src/components/custom/Workout/InputBar.stories.tsx +75 -0
  75. package/src/components/custom/Workout/InputBar.test.tsx +116 -0
  76. package/src/components/custom/Workout/InputBar.tsx +162 -0
  77. package/src/components/custom/Workout/IntensityBar.stories.tsx +60 -0
  78. package/src/components/custom/Workout/IntensityBar.test.tsx +113 -0
  79. package/src/components/custom/Workout/IntensityBar.tsx +166 -0
  80. package/src/components/custom/Workout/MesoCard.stories.tsx +152 -0
  81. package/src/components/custom/Workout/MesoCard.test.tsx +170 -0
  82. package/src/components/custom/Workout/MesoCard.tsx +237 -0
  83. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +85 -0
  84. package/src/components/custom/Workout/MesoProgressBar.test.tsx +153 -0
  85. package/src/components/custom/Workout/MesoProgressBar.tsx +165 -0
  86. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +126 -0
  87. package/src/components/custom/Workout/MesoStatusCard.test.tsx +209 -0
  88. package/src/components/custom/Workout/MesoStatusCard.tsx +456 -0
  89. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +118 -0
  90. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +91 -0
  91. package/src/components/custom/Workout/MuscleGroupChip.tsx +92 -0
  92. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +94 -0
  93. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +83 -0
  94. package/src/components/custom/Workout/PlaceholderStrip.tsx +90 -0
  95. package/src/components/custom/Workout/PrBadge.stories.tsx +105 -0
  96. package/src/components/custom/Workout/PrBadge.test.tsx +111 -0
  97. package/src/components/custom/Workout/PrBadge.tsx +103 -0
  98. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +116 -0
  99. package/src/components/custom/Workout/PrHistoryModal.test.tsx +161 -0
  100. package/src/components/custom/Workout/PrHistoryModal.tsx +244 -0
  101. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +257 -0
  102. package/src/components/custom/Workout/ProgramPlanningPage.test.tsx +142 -0
  103. package/src/components/custom/Workout/ProgramPlanningPage.tsx +422 -0
  104. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +126 -0
  105. package/src/components/custom/Workout/ReadinessCheck.test.tsx +181 -0
  106. package/src/components/custom/Workout/ReadinessCheck.tsx +265 -0
  107. package/src/components/custom/Workout/RestTimer.test.tsx +10 -0
  108. package/src/components/custom/Workout/RestTimer.tsx +15 -11
  109. package/src/components/custom/Workout/SetRow.stories.tsx +165 -0
  110. package/src/components/custom/Workout/SetRow.test.tsx +222 -0
  111. package/src/components/custom/Workout/SetRow.tsx +247 -0
  112. package/src/components/custom/Workout/Sparkline.stories.tsx +125 -0
  113. package/src/components/custom/Workout/Sparkline.test.tsx +113 -0
  114. package/src/components/custom/Workout/Sparkline.tsx +188 -0
  115. package/src/components/custom/Workout/StatusDot.stories.tsx +150 -0
  116. package/src/components/custom/Workout/StatusDot.test.tsx +165 -0
  117. package/src/components/custom/Workout/StatusDot.tsx +160 -0
  118. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +130 -0
  119. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +217 -0
  120. package/src/components/custom/Workout/StrengthTrendChart.tsx +628 -0
  121. package/src/components/custom/Workout/SupersetWrapper.tsx +1 -2
  122. package/src/components/custom/Workout/TempoDisplay.stories.tsx +66 -0
  123. package/src/components/custom/Workout/TempoDisplay.test.tsx +90 -0
  124. package/src/components/custom/Workout/TempoDisplay.tsx +209 -0
  125. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +212 -0
  126. package/src/components/custom/Workout/TrainingStatusPage.test.tsx +131 -0
  127. package/src/components/custom/Workout/TrainingStatusPage.tsx +321 -0
  128. package/src/components/custom/Workout/VelocityStrip.stories.tsx +125 -0
  129. package/src/components/custom/Workout/VelocityStrip.test.tsx +244 -0
  130. package/src/components/custom/Workout/VelocityStrip.tsx +288 -0
  131. package/src/components/custom/Workout/WeekRow.stories.tsx +121 -0
  132. package/src/components/custom/Workout/WeekRow.test.tsx +117 -0
  133. package/src/components/custom/Workout/WeekRow.tsx +140 -0
  134. package/src/components/custom/Workout/WeightBadge.stories.tsx +87 -0
  135. package/src/components/custom/Workout/WeightBadge.test.tsx +164 -0
  136. package/src/components/custom/Workout/WeightBadge.tsx +113 -0
  137. package/src/components/custom/Workout/WorkoutCard.stories.tsx +135 -0
  138. package/src/components/custom/Workout/WorkoutCard.test.tsx +172 -0
  139. package/src/components/custom/Workout/WorkoutCard.tsx +228 -0
  140. package/src/components/custom/Workout/WorkoutPill.stories.tsx +63 -0
  141. package/src/components/custom/Workout/WorkoutPill.test.tsx +153 -0
  142. package/src/components/custom/Workout/WorkoutPill.tsx +177 -0
  143. package/src/components/custom/Workout/index.ts +152 -0
  144. package/src/components/custom/Workout/muscleTaxonomy.ts +252 -0
  145. package/src/components/custom/index.ts +3 -0
  146. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  147. package/src/components/custom/stepper/Stepper.tsx +2 -2
  148. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  149. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  150. package/src/components/ui/card/Card.tsx +1 -1
  151. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  152. package/src/components/ui/collapse/Collapse.tsx +1 -1
  153. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  154. package/src/components/ui/drawer/Drawer.tsx +3 -3
  155. package/src/components/ui/form-field/FormField.tsx +1 -1
  156. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  157. package/src/components/ui/menu/Menu.tsx +2 -2
  158. package/src/components/ui/pill/Pill.tsx +1 -1
  159. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  160. package/src/components/ui/popover/Popover.tsx +1 -1
  161. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  162. package/src/components/ui/section/Section.stories.tsx +4 -4
  163. package/src/components/ui/select/Select.tsx +1 -1
  164. package/src/components/ui/stack/Stack.stories.tsx +4 -4
  165. package/src/components/ui/tabs/Tabs.tsx +2 -2
  166. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  167. package/src/components/ui/toolbar-button/ToolbarButton.tsx +1 -1
  168. package/src/examples/react-hook-form/ControlledFields.tsx +171 -0
  169. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +51 -0
  170. package/src/examples/react-hook-form/ReactHookForm.test.tsx +61 -0
  171. package/src/examples/react-hook-form/RhfContactForm.tsx +99 -0
  172. package/src/stories/PresetShowcase.stories.tsx +15 -15
  173. package/src/theme/config.completeness.test.ts +77 -0
  174. package/src/theme/config.ts +151 -0
  175. package/src/theme/index.ts +2 -1
  176. package/src/theme/manifest/css-property-manifest.example.json +79 -0
  177. package/src/theme/manifest/css-property-manifest.schema.json +93 -0
  178. package/src/theme/manifest/css-property-manifest.test.ts +81 -0
  179. package/src/theme/manifest/css-property-manifest.types.ts +49 -0
  180. package/src/theme/manifest/css-property-manifest.validate.ts +84 -0
  181. package/src/theme/manifest/design-freeze.test.ts +155 -0
  182. package/src/theme/manifest/extract-css-properties.fixture.html +29 -0
  183. package/src/theme/manifest/extract-css-properties.test.ts +112 -0
  184. package/src/theme/manifest/index.ts +11 -0
  185. package/src/theme/tokens-css.test.ts +28 -0
  186. package/src/theme/tokens-css.ts +36 -0
  187. package/src/theme/workout-tokens.ts +59 -0
  188. package/src/utils/index.ts +10 -0
  189. package/src/utils/workout-format.test.ts +81 -0
  190. package/src/utils/workout-format.ts +83 -0
  191. package/dist/chunk-UXVRPOME.mjs.map +0 -1
@@ -0,0 +1,2670 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var reactNative = require('react-native');
5
+ var BodyHighlighter = require('react-native-body-highlighter');
6
+ var clsx = require('clsx');
7
+ var tailwindMerge = require('tailwind-merge');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var BodyHighlighter__default = /*#__PURE__*/_interopDefault(BodyHighlighter);
13
+
14
+ // src/components/custom/Workout/BodyMap.tsx
15
+ function cn(...inputs) {
16
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
17
+ }
18
+
19
+ // src/theme/workout-tokens.ts
20
+ var WORKOUT_TOKENS = {
21
+ // BodyMap volume heatmap scale (drive dynamic SVG fills, so inline values).
22
+ // Maps weekly-volume status against MEV/MAV/MRV landmarks to a fill color.
23
+ heatmap: {
24
+ none: "#E0E0E0",
25
+ // gray — no training data
26
+ under: "#4A90D9",
27
+ // cool blue — below MEV
28
+ maintenance: "#F5C842",
29
+ // warm yellow — MEV to MAV
30
+ productive: "#4CAF50",
31
+ // green — MAV to MRV
32
+ approaching: "#FFA502",
33
+ // orange — near MRV
34
+ over: "#FF6B35"
35
+ // red-orange — over MRV
36
+ }};
37
+
38
+ // src/components/custom/Workout/muscleTaxonomy.ts
39
+ var MuscleGroup = /* @__PURE__ */ ((MuscleGroup5) => {
40
+ MuscleGroup5["CHEST"] = "chest";
41
+ MuscleGroup5["FRONT_DELTS"] = "front_delts";
42
+ MuscleGroup5["SIDE_DELTS"] = "side_delts";
43
+ MuscleGroup5["TRICEPS"] = "triceps";
44
+ MuscleGroup5["LATS"] = "lats";
45
+ MuscleGroup5["UPPER_BACK"] = "upper_back";
46
+ MuscleGroup5["REAR_DELTS"] = "rear_delts";
47
+ MuscleGroup5["BICEPS"] = "biceps";
48
+ MuscleGroup5["FOREARMS"] = "forearms";
49
+ MuscleGroup5["QUADS"] = "quads";
50
+ MuscleGroup5["HAMSTRINGS"] = "hamstrings";
51
+ MuscleGroup5["GLUTES"] = "glutes";
52
+ MuscleGroup5["CALVES"] = "calves";
53
+ MuscleGroup5["ABS"] = "abs";
54
+ MuscleGroup5["OBLIQUES"] = "obliques";
55
+ return MuscleGroup5;
56
+ })(MuscleGroup || {});
57
+ var SimpleMuscleGroup = /* @__PURE__ */ ((SimpleMuscleGroup3) => {
58
+ SimpleMuscleGroup3["CHEST"] = "chest";
59
+ SimpleMuscleGroup3["BACK"] = "back";
60
+ SimpleMuscleGroup3["SHOULDERS"] = "shoulders";
61
+ SimpleMuscleGroup3["BICEPS"] = "biceps";
62
+ SimpleMuscleGroup3["TRICEPS"] = "triceps";
63
+ SimpleMuscleGroup3["LEGS"] = "legs";
64
+ SimpleMuscleGroup3["CORE"] = "core";
65
+ SimpleMuscleGroup3["FOREARMS"] = "forearms";
66
+ return SimpleMuscleGroup3;
67
+ })(SimpleMuscleGroup || {});
68
+ var SIMPLE_TO_DETAILED = {
69
+ ["chest" /* CHEST */]: ["chest" /* CHEST */],
70
+ ["back" /* BACK */]: ["lats" /* LATS */, "upper_back" /* UPPER_BACK */],
71
+ ["shoulders" /* SHOULDERS */]: [
72
+ "front_delts" /* FRONT_DELTS */,
73
+ "side_delts" /* SIDE_DELTS */,
74
+ "rear_delts" /* REAR_DELTS */
75
+ ],
76
+ ["biceps" /* BICEPS */]: ["biceps" /* BICEPS */],
77
+ ["triceps" /* TRICEPS */]: ["triceps" /* TRICEPS */],
78
+ ["legs" /* LEGS */]: [
79
+ "quads" /* QUADS */,
80
+ "hamstrings" /* HAMSTRINGS */,
81
+ "glutes" /* GLUTES */,
82
+ "calves" /* CALVES */
83
+ ],
84
+ ["core" /* CORE */]: ["abs" /* ABS */, "obliques" /* OBLIQUES */],
85
+ ["forearms" /* FOREARMS */]: ["forearms" /* FOREARMS */]
86
+ };
87
+ var DETAILED_TO_SIMPLE = Object.entries(
88
+ SIMPLE_TO_DETAILED
89
+ ).reduce(
90
+ (acc, [simple, groups]) => {
91
+ for (const group of groups) {
92
+ acc[group] = simple;
93
+ }
94
+ return acc;
95
+ },
96
+ {}
97
+ );
98
+ var MUSCLE_TO_CATEGORY = {
99
+ ["chest" /* CHEST */]: "push",
100
+ ["front_delts" /* FRONT_DELTS */]: "push",
101
+ ["side_delts" /* SIDE_DELTS */]: "push",
102
+ ["triceps" /* TRICEPS */]: "push",
103
+ ["lats" /* LATS */]: "pull",
104
+ ["upper_back" /* UPPER_BACK */]: "pull",
105
+ ["rear_delts" /* REAR_DELTS */]: "pull",
106
+ ["biceps" /* BICEPS */]: "pull",
107
+ ["forearms" /* FOREARMS */]: "pull",
108
+ ["quads" /* QUADS */]: "legs",
109
+ ["hamstrings" /* HAMSTRINGS */]: "legs",
110
+ ["glutes" /* GLUTES */]: "legs",
111
+ ["calves" /* CALVES */]: "legs",
112
+ ["abs" /* ABS */]: "core",
113
+ ["obliques" /* OBLIQUES */]: "core"
114
+ };
115
+ var MUSCLE_TO_SVG_SLUGS = {
116
+ ["chest" /* CHEST */]: ["chest"],
117
+ ["front_delts" /* FRONT_DELTS */]: ["deltoids"],
118
+ ["side_delts" /* SIDE_DELTS */]: ["deltoids"],
119
+ ["rear_delts" /* REAR_DELTS */]: ["deltoids"],
120
+ ["triceps" /* TRICEPS */]: ["triceps"],
121
+ ["lats" /* LATS */]: ["upper-back"],
122
+ ["upper_back" /* UPPER_BACK */]: ["upper-back", "trapezius"],
123
+ ["biceps" /* BICEPS */]: ["biceps"],
124
+ ["forearms" /* FOREARMS */]: ["forearm"],
125
+ ["quads" /* QUADS */]: ["quadriceps"],
126
+ ["hamstrings" /* HAMSTRINGS */]: ["hamstring"],
127
+ ["glutes" /* GLUTES */]: ["gluteal"],
128
+ ["calves" /* CALVES */]: ["calves"],
129
+ ["abs" /* ABS */]: ["abs"],
130
+ ["obliques" /* OBLIQUES */]: ["obliques"]
131
+ };
132
+ var MUSCLE_DISPLAY_NAMES = {
133
+ ["chest" /* CHEST */]: "Chest",
134
+ ["front_delts" /* FRONT_DELTS */]: "Front Delts",
135
+ ["side_delts" /* SIDE_DELTS */]: "Side Delts",
136
+ ["rear_delts" /* REAR_DELTS */]: "Rear Delts",
137
+ ["triceps" /* TRICEPS */]: "Triceps",
138
+ ["lats" /* LATS */]: "Lats",
139
+ ["upper_back" /* UPPER_BACK */]: "Upper Back",
140
+ ["biceps" /* BICEPS */]: "Biceps",
141
+ ["forearms" /* FOREARMS */]: "Forearms",
142
+ ["quads" /* QUADS */]: "Quads",
143
+ ["hamstrings" /* HAMSTRINGS */]: "Hamstrings",
144
+ ["glutes" /* GLUTES */]: "Glutes",
145
+ ["calves" /* CALVES */]: "Calves",
146
+ ["abs" /* ABS */]: "Abs",
147
+ ["obliques" /* OBLIQUES */]: "Obliques"
148
+ };
149
+ var SIMPLE_DISPLAY_NAMES = {
150
+ ["chest" /* CHEST */]: "Chest",
151
+ ["back" /* BACK */]: "Back",
152
+ ["shoulders" /* SHOULDERS */]: "Shoulders",
153
+ ["biceps" /* BICEPS */]: "Biceps",
154
+ ["triceps" /* TRICEPS */]: "Triceps",
155
+ ["legs" /* LEGS */]: "Legs",
156
+ ["core" /* CORE */]: "Core",
157
+ ["forearms" /* FOREARMS */]: "Forearms"
158
+ };
159
+ var DEFAULT_VOLUME_LANDMARKS = {
160
+ ["chest" /* CHEST */]: { mev: 8, mav: 14, mrv: 20 },
161
+ ["lats" /* LATS */]: { mev: 8, mav: 14, mrv: 20 },
162
+ ["upper_back" /* UPPER_BACK */]: { mev: 6, mav: 12, mrv: 18 },
163
+ ["front_delts" /* FRONT_DELTS */]: { mev: 2, mav: 6, mrv: 10 },
164
+ ["side_delts" /* SIDE_DELTS */]: { mev: 6, mav: 14, mrv: 22 },
165
+ ["rear_delts" /* REAR_DELTS */]: { mev: 6, mav: 12, mrv: 18 },
166
+ ["biceps" /* BICEPS */]: { mev: 4, mav: 10, mrv: 18 },
167
+ ["triceps" /* TRICEPS */]: { mev: 4, mav: 8, mrv: 14 },
168
+ ["forearms" /* FOREARMS */]: { mev: 2, mav: 6, mrv: 12 },
169
+ ["quads" /* QUADS */]: { mev: 6, mav: 12, mrv: 18 },
170
+ ["hamstrings" /* HAMSTRINGS */]: { mev: 4, mav: 10, mrv: 16 },
171
+ ["glutes" /* GLUTES */]: { mev: 4, mav: 10, mrv: 16 },
172
+ ["calves" /* CALVES */]: { mev: 6, mav: 10, mrv: 16 },
173
+ ["abs" /* ABS */]: { mev: 0, mav: 8, mrv: 16 },
174
+ ["obliques" /* OBLIQUES */]: { mev: 0, mav: 6, mrv: 12 }
175
+ };
176
+ var VOLUME_STATUS_LABELS = {
177
+ under: "under-trained",
178
+ maintenance: "maintenance",
179
+ productive: "productive",
180
+ over: "over-reaching"
181
+ };
182
+ var STATUS_SEVERITY = {
183
+ under: 1,
184
+ maintenance: 2,
185
+ productive: 3,
186
+ over: 4
187
+ };
188
+ function getHeatmapColor(status, intensity = 0) {
189
+ const heatmap = WORKOUT_TOKENS.heatmap;
190
+ switch (status) {
191
+ case "under":
192
+ return heatmap.under;
193
+ case "maintenance":
194
+ return heatmap.maintenance;
195
+ case "productive":
196
+ return intensity >= 0.85 ? heatmap.approaching : heatmap.productive;
197
+ case "over":
198
+ return heatmap.over;
199
+ default:
200
+ return heatmap.none;
201
+ }
202
+ }
203
+ function isMoreSevere(a, b) {
204
+ return STATUS_SEVERITY[a] >= STATUS_SEVERITY[b];
205
+ }
206
+ var SLUG_TO_PRIMARY_MUSCLE = {
207
+ chest: "chest" /* CHEST */,
208
+ deltoids: "side_delts" /* SIDE_DELTS */,
209
+ triceps: "triceps" /* TRICEPS */,
210
+ "upper-back": "lats" /* LATS */,
211
+ trapezius: "upper_back" /* UPPER_BACK */,
212
+ biceps: "biceps" /* BICEPS */,
213
+ forearm: "forearms" /* FOREARMS */,
214
+ quadriceps: "quads" /* QUADS */,
215
+ hamstring: "hamstrings" /* HAMSTRINGS */,
216
+ gluteal: "glutes" /* GLUTES */,
217
+ calves: "calves" /* CALVES */,
218
+ abs: "abs" /* ABS */,
219
+ obliques: "obliques" /* OBLIQUES */
220
+ };
221
+ function wrapJsx(fn) {
222
+ return function(type, props, ...rest) {
223
+ if (props && typeof props.className === "string" && props.className) {
224
+ const cn2 = props.className;
225
+ const cssStyle = { $$css: true, [cn2]: cn2 };
226
+ const existing = props.style;
227
+ props = {
228
+ ...props,
229
+ style: existing ? [cssStyle, existing] : cssStyle
230
+ };
231
+ delete props.className;
232
+ }
233
+ return fn(type, props, ...rest);
234
+ };
235
+ }
236
+ var jsx = wrapJsx(jsxRuntime.jsx);
237
+ var jsxs = wrapJsx(jsxRuntime.jsxs);
238
+
239
+ // src/components/custom/Workout/BodyMap.tsx
240
+ var Body = BodyHighlighter__default.default.default ?? BodyHighlighter__default.default;
241
+ var OUTLINE_FILL = "rgba(255,255,255,0.08)";
242
+ var OUTLINE_BORDER = "rgba(255,255,255,0.12)";
243
+ var BODY_SCALE = 0.8;
244
+ var TEXT_PRIMARY = "var(--color-text-primary)";
245
+ var TEXT_SECONDARY = "var(--color-text-secondary)";
246
+ function buildSlugParts(data) {
247
+ const bySlug = /* @__PURE__ */ new Map();
248
+ for (const d of data) {
249
+ for (const slug of MUSCLE_TO_SVG_SLUGS[d.muscleGroup] ?? []) {
250
+ const existing = bySlug.get(slug);
251
+ if (!existing || isMoreSevere(d.volumeStatus, existing.status)) {
252
+ bySlug.set(slug, { status: d.volumeStatus, intensity: d.intensity });
253
+ }
254
+ }
255
+ }
256
+ return Array.from(bySlug.entries()).map(([slug, v]) => ({
257
+ slug,
258
+ color: getHeatmapColor(v.status, v.intensity)
259
+ }));
260
+ }
261
+ function buildSimpleLegend(data) {
262
+ const bySimple = /* @__PURE__ */ new Map();
263
+ for (const d of data) {
264
+ const simple = DETAILED_TO_SIMPLE[d.muscleGroup];
265
+ const existing = bySimple.get(simple);
266
+ if (!existing) {
267
+ bySimple.set(simple, {
268
+ key: simple,
269
+ name: SIMPLE_DISPLAY_NAMES[simple],
270
+ status: d.volumeStatus,
271
+ intensity: d.intensity,
272
+ sets: d.weeklySets,
273
+ muscle: d.muscleGroup
274
+ });
275
+ continue;
276
+ }
277
+ existing.sets += d.weeklySets;
278
+ existing.intensity = Math.max(existing.intensity, d.intensity);
279
+ if (isMoreSevere(d.volumeStatus, existing.status)) {
280
+ existing.status = d.volumeStatus;
281
+ existing.muscle = d.muscleGroup;
282
+ }
283
+ }
284
+ return Array.from(bySimple.values());
285
+ }
286
+ function buildDetailedLegend(data) {
287
+ return data.map((d) => ({
288
+ key: d.muscleGroup,
289
+ name: MUSCLE_DISPLAY_NAMES[d.muscleGroup],
290
+ status: d.volumeStatus,
291
+ intensity: d.intensity,
292
+ sets: d.weeklySets,
293
+ muscle: d.muscleGroup
294
+ }));
295
+ }
296
+ function BodyMap({
297
+ data,
298
+ view,
299
+ onViewChange,
300
+ onMusclePress,
301
+ highlightedMuscle,
302
+ mode = "detailed",
303
+ className,
304
+ ...props
305
+ }) {
306
+ const slugParts = react.useMemo(() => buildSlugParts(data), [data]);
307
+ const legend = react.useMemo(
308
+ () => mode === "simple" ? buildSimpleLegend(data) : buildDetailedLegend(data),
309
+ [data, mode]
310
+ );
311
+ const [highlight] = react.useState(() => new reactNative.Animated.Value(highlightedMuscle ? 1 : 0));
312
+ react.useEffect(() => {
313
+ const animation = reactNative.Animated.timing(highlight, {
314
+ toValue: highlightedMuscle ? 1 : 0,
315
+ duration: 250,
316
+ easing: reactNative.Easing.out(reactNative.Easing.ease),
317
+ useNativeDriver: false
318
+ });
319
+ animation.start();
320
+ return () => animation.stop();
321
+ }, [highlightedMuscle, highlight]);
322
+ const scale = highlight.interpolate({ inputRange: [0, 1], outputRange: [1, 1.02] });
323
+ const glowColor = highlightedMuscle ? getHeatmapColor(
324
+ data.find((d) => d.muscleGroup === highlightedMuscle)?.volumeStatus,
325
+ data.find((d) => d.muscleGroup === highlightedMuscle)?.intensity ?? 0
326
+ ) : null;
327
+ const handleBodyPress = (part) => {
328
+ if (!onMusclePress || !part.slug) return;
329
+ const fromData = data.find(
330
+ (d) => (MUSCLE_TO_SVG_SLUGS[d.muscleGroup] ?? []).includes(part.slug)
331
+ );
332
+ const muscle = fromData?.muscleGroup ?? SLUG_TO_PRIMARY_MUSCLE[part.slug];
333
+ if (muscle) onMusclePress(muscle);
334
+ };
335
+ return /* @__PURE__ */ jsxs(reactNative.View, { className: cn(className), testID: "body-map", ...props, children: [
336
+ /* @__PURE__ */ jsx(ViewToggle, { view, onViewChange }),
337
+ /* @__PURE__ */ jsx(
338
+ reactNative.Animated.View,
339
+ {
340
+ style: [
341
+ {
342
+ width: 200 * BODY_SCALE,
343
+ alignSelf: "center",
344
+ transform: [{ scale }]
345
+ },
346
+ glowColor ? { boxShadow: `0 0 14px 2px ${glowColor}` } : void 0
347
+ ],
348
+ "aria-hidden": true,
349
+ accessibilityElementsHidden: true,
350
+ importantForAccessibility: "no-hide-descendants",
351
+ testID: "body-map-svg",
352
+ children: /* @__PURE__ */ jsx(
353
+ Body,
354
+ {
355
+ side: view,
356
+ data: slugParts,
357
+ scale: BODY_SCALE,
358
+ gender: "male",
359
+ defaultFill: OUTLINE_FILL,
360
+ border: OUTLINE_BORDER,
361
+ onBodyPartPress: handleBodyPress
362
+ }
363
+ )
364
+ }
365
+ ),
366
+ legend.length === 0 ? /* @__PURE__ */ jsx(
367
+ reactNative.Text,
368
+ {
369
+ style: {
370
+ marginTop: 8,
371
+ fontSize: 12,
372
+ color: TEXT_SECONDARY,
373
+ textAlign: "center",
374
+ fontFamily: "Inter, sans-serif"
375
+ },
376
+ testID: "body-map-empty",
377
+ children: "No training data"
378
+ }
379
+ ) : /* @__PURE__ */ jsx(
380
+ reactNative.View,
381
+ {
382
+ className: "flex-row flex-wrap",
383
+ style: { marginTop: 10, gap: 6, justifyContent: "center" },
384
+ testID: "body-map-legend",
385
+ children: legend.map((entry) => /* @__PURE__ */ jsx(
386
+ MuscleButton,
387
+ {
388
+ entry,
389
+ highlighted: highlightedMuscle === entry.muscle,
390
+ onMusclePress
391
+ },
392
+ entry.key
393
+ ))
394
+ }
395
+ )
396
+ ] });
397
+ }
398
+ function ViewToggle({ view, onViewChange }) {
399
+ return /* @__PURE__ */ jsx(
400
+ reactNative.View,
401
+ {
402
+ className: "flex-row self-center",
403
+ style: { gap: 4, marginBottom: 8 },
404
+ testID: "body-map-view-toggle",
405
+ children: ["front", "back"].map((side) => {
406
+ const active = view === side;
407
+ return /* @__PURE__ */ jsx(
408
+ reactNative.Pressable,
409
+ {
410
+ onPress: () => onViewChange?.(side),
411
+ accessibilityRole: "button",
412
+ accessibilityLabel: `Show ${side} view`,
413
+ "aria-pressed": active,
414
+ disabled: !onViewChange,
415
+ style: {
416
+ paddingHorizontal: 12,
417
+ paddingVertical: 4,
418
+ borderRadius: 9999,
419
+ backgroundColor: active ? "rgba(255,121,0,0.16)" : "transparent",
420
+ borderWidth: 1,
421
+ borderColor: active ? "#FF7900" : "var(--color-border-strong)"
422
+ },
423
+ testID: `body-map-toggle-${side}`,
424
+ children: /* @__PURE__ */ jsx(
425
+ reactNative.Text,
426
+ {
427
+ style: {
428
+ fontSize: 12,
429
+ fontFamily: "Inter, sans-serif",
430
+ fontWeight: active ? "700" : "500",
431
+ color: active ? "#FF7900" : TEXT_SECONDARY,
432
+ textTransform: "capitalize"
433
+ },
434
+ children: side
435
+ }
436
+ )
437
+ },
438
+ side
439
+ );
440
+ })
441
+ }
442
+ );
443
+ }
444
+ function MuscleButton({ entry, highlighted, onMusclePress }) {
445
+ const dotColor = getHeatmapColor(entry.status, entry.intensity);
446
+ const label = `${entry.name}, ${VOLUME_STATUS_LABELS[entry.status]}, ${entry.sets} sets this week`;
447
+ return /* @__PURE__ */ jsxs(
448
+ reactNative.Pressable,
449
+ {
450
+ onPress: () => onMusclePress?.(entry.muscle),
451
+ accessibilityRole: "button",
452
+ accessibilityLabel: label,
453
+ "aria-pressed": highlighted,
454
+ style: {
455
+ flexDirection: "row",
456
+ alignItems: "center",
457
+ gap: 5,
458
+ paddingHorizontal: 8,
459
+ paddingVertical: 3,
460
+ borderRadius: 9999,
461
+ borderWidth: 1,
462
+ borderColor: highlighted ? "#FF7900" : "var(--color-border-default)",
463
+ backgroundColor: "var(--color-surface-raised)"
464
+ },
465
+ testID: `body-map-muscle-${entry.key}`,
466
+ children: [
467
+ /* @__PURE__ */ jsx(
468
+ reactNative.View,
469
+ {
470
+ style: { width: 7, height: 7, borderRadius: 9999, backgroundColor: dotColor },
471
+ accessibilityElementsHidden: true,
472
+ testID: `body-map-muscle-dot-${entry.key}`
473
+ }
474
+ ),
475
+ /* @__PURE__ */ jsx(
476
+ reactNative.Text,
477
+ {
478
+ style: {
479
+ fontSize: 11,
480
+ fontFamily: "Inter, sans-serif",
481
+ fontWeight: "500",
482
+ color: TEXT_PRIMARY
483
+ },
484
+ accessibilityElementsHidden: true,
485
+ children: entry.name
486
+ }
487
+ )
488
+ ]
489
+ }
490
+ );
491
+ }
492
+ var sizeStyles = {
493
+ xs: "w-1 h-1",
494
+ sm: "w-1.5 h-1.5",
495
+ md: "w-2 h-2",
496
+ lg: "w-2.5 h-2.5"
497
+ };
498
+ var colorStyles = {
499
+ default: "bg-text-tertiary",
500
+ primary: "bg-brand-primary",
501
+ success: "bg-status-success",
502
+ error: "bg-status-error",
503
+ warning: "bg-status-warning",
504
+ info: "bg-status-info"
505
+ };
506
+ function Indicator({
507
+ size = "sm",
508
+ color = "default",
509
+ customColor,
510
+ glow = false,
511
+ ring = false,
512
+ className,
513
+ style,
514
+ ...props
515
+ }) {
516
+ const dynamicStyle = {
517
+ ...typeof style === "object" ? style : {},
518
+ ...customColor ? { backgroundColor: customColor } : {},
519
+ ...glow && !customColor ? {} : {},
520
+ ...glow && customColor ? { boxShadow: `0 0 6px ${customColor}` } : {}
521
+ };
522
+ return /* @__PURE__ */ jsx(
523
+ reactNative.View,
524
+ {
525
+ className: cn(
526
+ "rounded-full shrink-0",
527
+ sizeStyles[size],
528
+ !customColor && colorStyles[color],
529
+ ring && "border-2 border-background-base",
530
+ glow && !customColor && color === "success" && "shadow-[0_0_6px_rgba(20,184,166,0.6)]",
531
+ glow && !customColor && color === "error" && "shadow-[0_0_6px_rgba(239,68,68,0.6)]",
532
+ glow && !customColor && color === "warning" && "shadow-[0_0_6px_rgba(245,158,11,0.6)]",
533
+ glow && !customColor && color === "primary" && "shadow-[0_0_6px_rgba(255,121,0,0.6)]",
534
+ className
535
+ ),
536
+ style: Object.keys(dynamicStyle).length > 0 ? dynamicStyle : style,
537
+ ...props
538
+ }
539
+ );
540
+ }
541
+
542
+ // src/components/ui/badge/Badge.tsx
543
+ var variantColorStyles = {
544
+ solid: {
545
+ default: "bg-border-strong text-text-inverse",
546
+ primary: "bg-brand-primary text-text-inverse",
547
+ secondary: "bg-brand-secondary text-text-inverse",
548
+ success: "bg-status-success text-text-inverse",
549
+ error: "bg-status-error text-text-inverse",
550
+ warning: "bg-status-warning text-text-inverse",
551
+ info: "bg-status-info text-text-inverse"
552
+ },
553
+ subtle: {
554
+ default: "bg-surface-raised text-text-secondary",
555
+ primary: "bg-brand-primary-subtle text-brand-primary",
556
+ secondary: "bg-brand-secondary-subtle text-brand-secondary",
557
+ success: "bg-status-success-subtle text-status-success",
558
+ error: "bg-status-error-subtle text-status-error",
559
+ warning: "bg-status-warning-subtle text-status-warning",
560
+ info: "bg-status-info-subtle text-status-info"
561
+ },
562
+ outline: {
563
+ default: "border border-border-strong text-text-secondary",
564
+ primary: "border border-brand-primary text-brand-primary",
565
+ secondary: "border border-brand-secondary text-brand-secondary",
566
+ success: "border border-status-success text-status-success",
567
+ error: "border border-status-error text-status-error",
568
+ warning: "border border-status-warning text-status-warning",
569
+ info: "border border-status-info text-status-info"
570
+ }
571
+ };
572
+ var sizeStyles2 = {
573
+ sm: "px-1.5 py-0.5 text-xs",
574
+ md: "px-2 py-0.5 text-xs",
575
+ lg: "px-2.5 py-1 text-sm"
576
+ };
577
+ function Badge({
578
+ variant = "subtle",
579
+ color = "default",
580
+ size = "md",
581
+ dot,
582
+ dotColor,
583
+ className,
584
+ children,
585
+ ...props
586
+ }) {
587
+ return /* @__PURE__ */ jsxs(
588
+ reactNative.View,
589
+ {
590
+ className: cn(
591
+ "inline-flex items-center justify-center rounded-full font-medium",
592
+ variantColorStyles[variant][color],
593
+ sizeStyles2[size],
594
+ className
595
+ ),
596
+ ...props,
597
+ children: [
598
+ dot && /* @__PURE__ */ jsx(
599
+ Indicator,
600
+ {
601
+ size: "xs",
602
+ color: dotColor ?? (color === "default" || color === "secondary" ? "default" : color),
603
+ className: "mr-0.5"
604
+ }
605
+ ),
606
+ typeof children === "string" ? /* @__PURE__ */ jsx(reactNative.Text, { className: "text-inherit font-medium", children }) : children
607
+ ]
608
+ }
609
+ );
610
+ }
611
+ var BRAND_PRIMARY = "#FF7900";
612
+ function normalizeData(data, height) {
613
+ if (data.length === 0) return [];
614
+ const min = Math.min(...data);
615
+ const max = Math.max(...data);
616
+ const range = max - min || 1;
617
+ return data.map((v) => height - (v - min) / range * height);
618
+ }
619
+ function normalizeValue(value, data, height) {
620
+ const min = Math.min(...data);
621
+ const max = Math.max(...data);
622
+ const range = max - min || 1;
623
+ return height - (value - min) / range * height;
624
+ }
625
+ function Sparkline({
626
+ data,
627
+ width = 80,
628
+ height = 30,
629
+ color,
630
+ showDots = false,
631
+ referenceLines,
632
+ highlightLast = false,
633
+ className,
634
+ ...props
635
+ }) {
636
+ if (data.length === 0) {
637
+ return /* @__PURE__ */ jsx(
638
+ reactNative.View,
639
+ {
640
+ style: { width, height },
641
+ className,
642
+ testID: "sparkline-empty",
643
+ accessibilityLabel: "Sparkline chart, no data",
644
+ ...props
645
+ }
646
+ );
647
+ }
648
+ const resolvedColor = color ?? BRAND_PRIMARY;
649
+ const normalized = normalizeData(data, height);
650
+ const stepX = data.length > 1 ? width / (data.length - 1) : 0;
651
+ const dotSize = 3;
652
+ const highlightSize = 6;
653
+ return /* @__PURE__ */ jsxs(
654
+ reactNative.View,
655
+ {
656
+ style: { width, height, position: "relative" },
657
+ className: cn(className),
658
+ accessibilityRole: "image",
659
+ accessibilityLabel: `Sparkline chart with ${data.length} data points`,
660
+ testID: "sparkline",
661
+ ...props,
662
+ children: [
663
+ referenceLines?.map((line, i) => {
664
+ const y = normalizeValue(line.value, data, height);
665
+ return /* @__PURE__ */ jsx(
666
+ reactNative.View,
667
+ {
668
+ style: [
669
+ {
670
+ position: "absolute",
671
+ top: y,
672
+ left: 0,
673
+ right: 0,
674
+ opacity: 0.6
675
+ },
676
+ line.dashed ? {
677
+ height: 0,
678
+ borderStyle: "dashed",
679
+ borderTopWidth: 1,
680
+ borderTopColor: line.color
681
+ } : {
682
+ height: 1,
683
+ backgroundColor: line.color
684
+ }
685
+ ],
686
+ accessibilityElementsHidden: true,
687
+ testID: `sparkline-reference-${i}`,
688
+ children: line.label && /* @__PURE__ */ jsx(
689
+ reactNative.Text,
690
+ {
691
+ style: {
692
+ position: "absolute",
693
+ right: 0,
694
+ top: -10,
695
+ fontSize: 7,
696
+ color: line.color,
697
+ opacity: 1
698
+ },
699
+ testID: `sparkline-reference-label-${i}`,
700
+ children: line.label
701
+ }
702
+ )
703
+ },
704
+ `ref-${i}`
705
+ );
706
+ }),
707
+ normalized.map((y, i) => {
708
+ if (i === 0) return null;
709
+ const prevY = normalized[i - 1];
710
+ const x1 = (i - 1) * stepX;
711
+ const x2 = i * stepX;
712
+ const dx = x2 - x1;
713
+ const dy = y - prevY;
714
+ const length = Math.sqrt(dx * dx + dy * dy);
715
+ const angle = Math.atan2(dy, dx) * (180 / Math.PI);
716
+ return /* @__PURE__ */ jsx(
717
+ reactNative.View,
718
+ {
719
+ style: {
720
+ position: "absolute",
721
+ left: x1,
722
+ top: prevY,
723
+ width: length,
724
+ height: 1.5,
725
+ backgroundColor: resolvedColor,
726
+ transformOrigin: "0 0",
727
+ transform: [{ rotate: `${angle}deg` }]
728
+ },
729
+ accessibilityElementsHidden: true,
730
+ testID: `sparkline-segment-${i}`
731
+ },
732
+ `line-${i}`
733
+ );
734
+ }),
735
+ (showDots || highlightLast) && normalized.map((y, i) => {
736
+ const isLast = i === data.length - 1;
737
+ const shouldShow = showDots || highlightLast && isLast;
738
+ if (!shouldShow) return null;
739
+ const size = highlightLast && isLast ? highlightSize : dotSize;
740
+ return /* @__PURE__ */ jsx(
741
+ reactNative.View,
742
+ {
743
+ style: {
744
+ position: "absolute",
745
+ left: i * stepX - size / 2,
746
+ top: y - size / 2,
747
+ width: size,
748
+ height: size,
749
+ borderRadius: size / 2,
750
+ backgroundColor: resolvedColor
751
+ },
752
+ accessibilityElementsHidden: true,
753
+ testID: `sparkline-dot-${i}`
754
+ },
755
+ `dot-${i}`
756
+ );
757
+ })
758
+ ]
759
+ }
760
+ );
761
+ }
762
+
763
+ // src/components/custom/Workout/BodyMapDetailPanel.tsx
764
+ var SURFACE_ELEVATED = "var(--color-surface-elevated)";
765
+ var SURFACE_RAISED = "var(--color-surface-raised)";
766
+ var BORDER_STRONG = "var(--color-border-strong)";
767
+ var BORDER_DEFAULT = "var(--color-border-default)";
768
+ var BRAND_PRIMARY2 = "#FF7900";
769
+ var TEXT_PRIMARY2 = "var(--color-text-primary)";
770
+ var TEXT_SECONDARY2 = "var(--color-text-secondary)";
771
+ var TEXT_TERTIARY = "var(--color-text-tertiary)";
772
+ var VOLUME_GRADIENT = "linear-gradient(90deg, #2196F3 0%, #14B8A6 50%, #D14343 100%)";
773
+ var SLIDE_OFFSET = 400;
774
+ var BACKDROP_OPACITY = 0.4;
775
+ var STATUS_BADGE_COLOR = {
776
+ under: "info",
777
+ maintenance: "warning",
778
+ productive: "success",
779
+ over: "error"
780
+ };
781
+ function clamp01(value) {
782
+ if (value < 0) return 0;
783
+ if (value > 1) return 1;
784
+ return value;
785
+ }
786
+ function markerFraction(weeklySets, { mev, mrv }) {
787
+ const span = mrv - mev;
788
+ if (span <= 0) return weeklySets >= mrv ? 1 : 0;
789
+ return clamp01((weeklySets - mev) / span);
790
+ }
791
+ function BodyMapDetailPanel({
792
+ muscleGroup,
793
+ displayName,
794
+ weeklySets,
795
+ landmarks,
796
+ volumeStatus,
797
+ lastTrained,
798
+ weeklyHistory,
799
+ contributingExercises,
800
+ upcomingExercises,
801
+ isOpen,
802
+ visible,
803
+ onClose,
804
+ onViewExercises,
805
+ ...props
806
+ }) {
807
+ const open = isOpen ?? visible ?? false;
808
+ const [translateY] = react.useState(() => new reactNative.Animated.Value(SLIDE_OFFSET));
809
+ const [backdrop] = react.useState(() => new reactNative.Animated.Value(0));
810
+ react.useEffect(() => {
811
+ if (!open) return;
812
+ const animation = reactNative.Animated.parallel([
813
+ reactNative.Animated.timing(translateY, {
814
+ toValue: 0,
815
+ duration: 400,
816
+ easing: reactNative.Easing.out(reactNative.Easing.ease),
817
+ useNativeDriver: false
818
+ }),
819
+ reactNative.Animated.timing(backdrop, {
820
+ toValue: BACKDROP_OPACITY,
821
+ duration: 300,
822
+ easing: reactNative.Easing.out(reactNative.Easing.ease),
823
+ useNativeDriver: false
824
+ })
825
+ ]);
826
+ animation.start();
827
+ return () => animation.stop();
828
+ }, [open, translateY, backdrop]);
829
+ if (!open) return null;
830
+ const dialogLabel = `${displayName} volume details`;
831
+ const markerLeft = markerFraction(weeklySets, landmarks) * 100;
832
+ const hasContributing = (contributingExercises?.length ?? 0) > 0;
833
+ const hasUpcoming = (upcomingExercises?.length ?? 0) > 0;
834
+ return /* @__PURE__ */ jsxs(
835
+ reactNative.View,
836
+ {
837
+ style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, justifyContent: "flex-end" },
838
+ testID: "body-map-detail-panel-root",
839
+ children: [
840
+ /* @__PURE__ */ jsx(
841
+ reactNative.Animated.View,
842
+ {
843
+ style: {
844
+ position: "absolute",
845
+ top: 0,
846
+ left: 0,
847
+ right: 0,
848
+ bottom: 0,
849
+ backgroundColor: "#000000",
850
+ opacity: backdrop
851
+ },
852
+ testID: "body-map-detail-panel-backdrop-anim",
853
+ children: /* @__PURE__ */ jsx(
854
+ reactNative.Pressable,
855
+ {
856
+ onPress: onClose,
857
+ accessibilityRole: "button",
858
+ accessibilityLabel: `Close ${displayName} details`,
859
+ style: { flex: 1 },
860
+ testID: "body-map-detail-panel-backdrop"
861
+ }
862
+ )
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsxs(
866
+ reactNative.Animated.View,
867
+ {
868
+ accessibilityRole: "dialog",
869
+ accessibilityLabel: dialogLabel,
870
+ "aria-label": dialogLabel,
871
+ style: {
872
+ backgroundColor: SURFACE_ELEVATED,
873
+ borderTopLeftRadius: 16,
874
+ borderTopRightRadius: 16,
875
+ maxHeight: "88%",
876
+ transform: [{ translateY }]
877
+ },
878
+ testID: "body-map-detail-panel",
879
+ ...props,
880
+ children: [
881
+ /* @__PURE__ */ jsx(
882
+ reactNative.Pressable,
883
+ {
884
+ onPress: onClose,
885
+ accessibilityRole: "button",
886
+ accessibilityLabel: `Close ${displayName} details`,
887
+ hitSlop: 12,
888
+ style: { alignSelf: "center", paddingVertical: 8 },
889
+ testID: "body-map-detail-panel-handle",
890
+ children: /* @__PURE__ */ jsx(
891
+ reactNative.View,
892
+ {
893
+ style: { width: 40, height: 4, borderRadius: 2, backgroundColor: BORDER_STRONG },
894
+ accessibilityElementsHidden: true
895
+ }
896
+ )
897
+ }
898
+ ),
899
+ /* @__PURE__ */ jsxs(
900
+ reactNative.ScrollView,
901
+ {
902
+ style: { paddingHorizontal: 16 },
903
+ contentContainerStyle: { paddingBottom: 20 },
904
+ testID: "body-map-detail-panel-scroll",
905
+ children: [
906
+ /* @__PURE__ */ jsxs(
907
+ reactNative.View,
908
+ {
909
+ className: "flex-row items-center justify-between",
910
+ style: { gap: 8, paddingTop: 4, paddingBottom: 4 },
911
+ testID: "body-map-detail-panel-header",
912
+ children: [
913
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { gap: 8, flexShrink: 1 }, children: [
914
+ /* @__PURE__ */ jsx(
915
+ reactNative.Text,
916
+ {
917
+ accessibilityRole: "header",
918
+ style: {
919
+ fontSize: 16,
920
+ fontFamily: '"Space Grotesk", sans-serif',
921
+ fontWeight: "700",
922
+ color: TEXT_PRIMARY2
923
+ },
924
+ testID: "body-map-detail-panel-title",
925
+ children: displayName
926
+ }
927
+ ),
928
+ /* @__PURE__ */ jsx(
929
+ Badge,
930
+ {
931
+ variant: "subtle",
932
+ color: STATUS_BADGE_COLOR[volumeStatus],
933
+ size: "sm",
934
+ testID: "body-map-detail-panel-status-badge",
935
+ children: VOLUME_STATUS_LABELS[volumeStatus]
936
+ }
937
+ )
938
+ ] }),
939
+ /* @__PURE__ */ jsx(
940
+ reactNative.Pressable,
941
+ {
942
+ onPress: onClose,
943
+ accessibilityRole: "button",
944
+ accessibilityLabel: `Close ${displayName} details`,
945
+ hitSlop: 8,
946
+ style: { padding: 4, margin: -4 },
947
+ testID: "body-map-detail-panel-close",
948
+ children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 22, lineHeight: 22, color: TEXT_SECONDARY2 }, children: "\xD7" })
949
+ }
950
+ )
951
+ ]
952
+ }
953
+ ),
954
+ lastTrained != null && /* @__PURE__ */ jsx(
955
+ reactNative.Text,
956
+ {
957
+ style: { fontSize: 12, fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY2 },
958
+ testID: "body-map-detail-panel-last-trained",
959
+ children: `Last trained ${lastTrained}`
960
+ }
961
+ ),
962
+ /* @__PURE__ */ jsxs(reactNative.View, { style: { marginTop: 16 }, testID: "body-map-detail-panel-volume", children: [
963
+ /* @__PURE__ */ jsxs(
964
+ reactNative.View,
965
+ {
966
+ className: "flex-row items-center justify-between",
967
+ style: { marginBottom: 6 },
968
+ accessibilityElementsHidden: true,
969
+ children: [
970
+ /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 10, fontFamily: "Inter, sans-serif", fontWeight: "600", color: TEXT_TERTIARY }, children: `MEV ${landmarks.mev}` }),
971
+ /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 10, fontFamily: "Inter, sans-serif", fontWeight: "600", color: TEXT_TERTIARY }, children: `MRV ${landmarks.mrv}` })
972
+ ]
973
+ }
974
+ ),
975
+ /* @__PURE__ */ jsxs(
976
+ reactNative.View,
977
+ {
978
+ style: { height: 14, justifyContent: "center" },
979
+ accessibilityRole: "progressbar",
980
+ accessibilityValue: { min: landmarks.mev, max: landmarks.mrv, now: weeklySets },
981
+ accessibilityLabel: `${weeklySets} weekly sets, between MEV ${landmarks.mev} and MRV ${landmarks.mrv}`,
982
+ "aria-valuenow": weeklySets,
983
+ "aria-valuemin": landmarks.mev,
984
+ "aria-valuemax": landmarks.mrv,
985
+ testID: "body-map-detail-panel-volume-bar",
986
+ children: [
987
+ /* @__PURE__ */ jsx(
988
+ reactNative.View,
989
+ {
990
+ style: { height: 8, borderRadius: 4, backgroundImage: VOLUME_GRADIENT },
991
+ accessibilityElementsHidden: true
992
+ }
993
+ ),
994
+ /* @__PURE__ */ jsx(
995
+ reactNative.View,
996
+ {
997
+ style: {
998
+ position: "absolute",
999
+ left: `${markerLeft}%`,
1000
+ marginLeft: -7,
1001
+ width: 14,
1002
+ height: 14,
1003
+ borderRadius: 7,
1004
+ borderWidth: 2,
1005
+ borderColor: TEXT_PRIMARY2,
1006
+ backgroundColor: SURFACE_ELEVATED,
1007
+ boxShadow: "0 0 4px rgba(0,0,0,0.5)"
1008
+ },
1009
+ accessibilityElementsHidden: true,
1010
+ testID: "body-map-detail-panel-volume-marker"
1011
+ }
1012
+ )
1013
+ ]
1014
+ }
1015
+ )
1016
+ ] }),
1017
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-baseline", style: { marginTop: 14, gap: 4 }, children: [
1018
+ /* @__PURE__ */ jsx(
1019
+ reactNative.Text,
1020
+ {
1021
+ style: {
1022
+ fontSize: 24,
1023
+ fontFamily: '"Space Grotesk", sans-serif',
1024
+ fontWeight: "700",
1025
+ color: TEXT_PRIMARY2
1026
+ },
1027
+ testID: "body-map-detail-panel-set-count",
1028
+ children: weeklySets
1029
+ }
1030
+ ),
1031
+ /* @__PURE__ */ jsx(
1032
+ reactNative.Text,
1033
+ {
1034
+ style: { fontSize: 12, fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY2 },
1035
+ testID: "body-map-detail-panel-mrv-suffix",
1036
+ children: `/ ${landmarks.mrv} MRV`
1037
+ }
1038
+ )
1039
+ ] }),
1040
+ weeklyHistory != null && weeklyHistory.length > 0 && /* @__PURE__ */ jsxs(reactNative.View, { style: { marginTop: 14 }, testID: "body-map-detail-panel-sparkline", children: [
1041
+ /* @__PURE__ */ jsx(
1042
+ reactNative.Text,
1043
+ {
1044
+ style: {
1045
+ fontSize: 10,
1046
+ fontFamily: "Inter, sans-serif",
1047
+ fontWeight: "600",
1048
+ color: TEXT_TERTIARY,
1049
+ marginBottom: 6
1050
+ },
1051
+ accessibilityElementsHidden: true,
1052
+ children: "VOLUME TREND"
1053
+ }
1054
+ ),
1055
+ /* @__PURE__ */ jsx(Sparkline, { data: weeklyHistory, width: 80, height: 30, highlightLast: true })
1056
+ ] }),
1057
+ hasContributing && /* @__PURE__ */ jsxs(reactNative.View, { style: { marginTop: 16 }, testID: "body-map-detail-panel-contributing", children: [
1058
+ /* @__PURE__ */ jsx(
1059
+ reactNative.Text,
1060
+ {
1061
+ style: {
1062
+ fontSize: 10,
1063
+ fontFamily: "Inter, sans-serif",
1064
+ fontWeight: "600",
1065
+ color: TEXT_TERTIARY,
1066
+ marginBottom: 8
1067
+ },
1068
+ children: "CONTRIBUTING EXERCISES"
1069
+ }
1070
+ ),
1071
+ contributingExercises.map((exercise, index) => /* @__PURE__ */ jsxs(
1072
+ reactNative.View,
1073
+ {
1074
+ className: "flex-row items-center justify-between",
1075
+ style: {
1076
+ gap: 10,
1077
+ paddingVertical: 8,
1078
+ paddingHorizontal: 12,
1079
+ marginBottom: 6,
1080
+ borderRadius: 8,
1081
+ backgroundColor: SURFACE_RAISED,
1082
+ borderWidth: 1,
1083
+ borderColor: BORDER_DEFAULT
1084
+ },
1085
+ testID: `body-map-detail-panel-contributing-${index}`,
1086
+ children: [
1087
+ /* @__PURE__ */ jsx(
1088
+ reactNative.Text,
1089
+ {
1090
+ style: { flex: 1, fontSize: 13, fontFamily: "Inter, sans-serif", fontWeight: "600", color: TEXT_PRIMARY2 },
1091
+ testID: `body-map-detail-panel-contributing-${index}-name`,
1092
+ children: exercise.name
1093
+ }
1094
+ ),
1095
+ /* @__PURE__ */ jsx(
1096
+ reactNative.Text,
1097
+ {
1098
+ style: { fontSize: 12, fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY2 },
1099
+ testID: `body-map-detail-panel-contributing-${index}-detail`,
1100
+ children: `${exercise.sets} sets \xB7 ${Math.round(exercise.contributionWeight * 100)}%`
1101
+ }
1102
+ )
1103
+ ]
1104
+ },
1105
+ `${exercise.name}-${index}`
1106
+ ))
1107
+ ] }),
1108
+ hasUpcoming && /* @__PURE__ */ jsxs(reactNative.View, { style: { marginTop: 16 }, testID: "body-map-detail-panel-upcoming", children: [
1109
+ /* @__PURE__ */ jsx(
1110
+ reactNative.Text,
1111
+ {
1112
+ style: {
1113
+ fontSize: 10,
1114
+ fontFamily: "Inter, sans-serif",
1115
+ fontWeight: "600",
1116
+ color: TEXT_TERTIARY,
1117
+ marginBottom: 8
1118
+ },
1119
+ children: "UPCOMING"
1120
+ }
1121
+ ),
1122
+ upcomingExercises.map((exercise, index) => /* @__PURE__ */ jsxs(
1123
+ reactNative.View,
1124
+ {
1125
+ className: "flex-row items-center justify-between",
1126
+ style: { gap: 10, paddingVertical: 6 },
1127
+ testID: `body-map-detail-panel-upcoming-${index}`,
1128
+ children: [
1129
+ /* @__PURE__ */ jsx(
1130
+ reactNative.Text,
1131
+ {
1132
+ style: { flex: 1, fontSize: 13, fontFamily: "Inter, sans-serif", fontWeight: "600", color: TEXT_PRIMARY2 },
1133
+ testID: `body-map-detail-panel-upcoming-${index}-name`,
1134
+ children: exercise.name
1135
+ }
1136
+ ),
1137
+ /* @__PURE__ */ jsx(
1138
+ reactNative.Text,
1139
+ {
1140
+ style: { fontSize: 12, fontFamily: "Inter, sans-serif", color: TEXT_TERTIARY },
1141
+ testID: `body-map-detail-panel-upcoming-${index}-detail`,
1142
+ children: `${exercise.workoutName} \xB7 ${exercise.sets} sets`
1143
+ }
1144
+ )
1145
+ ]
1146
+ },
1147
+ `${exercise.name}-${index}`
1148
+ ))
1149
+ ] }),
1150
+ onViewExercises != null && /* @__PURE__ */ jsx(
1151
+ reactNative.Pressable,
1152
+ {
1153
+ onPress: onViewExercises,
1154
+ accessibilityRole: "button",
1155
+ accessibilityLabel: `View ${displayName} exercises`,
1156
+ style: {
1157
+ marginTop: 16,
1158
+ paddingVertical: 10,
1159
+ borderRadius: 8,
1160
+ alignItems: "center",
1161
+ backgroundColor: "rgba(255,121,0,0.12)",
1162
+ borderWidth: 1,
1163
+ borderColor: "rgba(255,121,0,0.3)"
1164
+ },
1165
+ testID: "body-map-detail-panel-view-exercises",
1166
+ children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 13, fontFamily: "Inter, sans-serif", fontWeight: "700", color: BRAND_PRIMARY2 }, children: "View exercises" })
1167
+ }
1168
+ )
1169
+ ]
1170
+ }
1171
+ )
1172
+ ]
1173
+ }
1174
+ )
1175
+ ]
1176
+ }
1177
+ );
1178
+ }
1179
+
1180
+ // src/theme/tokens/primitives.ts
1181
+ var primitiveColors = {
1182
+ // Neutral/Gray scale
1183
+ neutral: {
1184
+ 100: "#F3F4F6"},
1185
+ // Charcoal scale (dark backgrounds)
1186
+ charcoal: {
1187
+ 600: "#191919"}};
1188
+
1189
+ // src/theme/tokens/semantic.ts
1190
+ var semanticColorsLight = {
1191
+ // #FAFAFA - slightly off-white for elevated cards
1192
+ "surface-raised": primitiveColors.neutral[100]};
1193
+ var semanticColorsDark = {
1194
+ // #161616 - main surface
1195
+ "surface-elevated": primitiveColors.charcoal[600]};
1196
+ function hexToRgb(hex) {
1197
+ const cleaned = hex.replace("#", "");
1198
+ let r, g, b;
1199
+ if (cleaned.length === 3) {
1200
+ r = parseInt(cleaned[0] + cleaned[0], 16);
1201
+ g = parseInt(cleaned[1] + cleaned[1], 16);
1202
+ b = parseInt(cleaned[2] + cleaned[2], 16);
1203
+ } else if (cleaned.length === 6) {
1204
+ r = parseInt(cleaned.slice(0, 2), 16);
1205
+ g = parseInt(cleaned.slice(2, 4), 16);
1206
+ b = parseInt(cleaned.slice(4, 6), 16);
1207
+ } else {
1208
+ return null;
1209
+ }
1210
+ return { r, g, b };
1211
+ }
1212
+ function rgbToHsv(r, g, b) {
1213
+ const rNorm = r / 255;
1214
+ const gNorm = g / 255;
1215
+ const bNorm = b / 255;
1216
+ const max = Math.max(rNorm, gNorm, bNorm);
1217
+ const min = Math.min(rNorm, gNorm, bNorm);
1218
+ const delta = max - min;
1219
+ const v = max;
1220
+ const s = max === 0 ? 0 : delta / max;
1221
+ let h = 0;
1222
+ if (delta !== 0) {
1223
+ if (max === rNorm) {
1224
+ h = 60 * ((gNorm - bNorm) / delta % 6);
1225
+ } else if (max === gNorm) {
1226
+ h = 60 * ((bNorm - rNorm) / delta + 2);
1227
+ } else {
1228
+ h = 60 * ((rNorm - gNorm) / delta + 4);
1229
+ }
1230
+ }
1231
+ if (h < 0) h += 360;
1232
+ return { h, s, v };
1233
+ }
1234
+ function hsvToRgb(h, s, v) {
1235
+ const c = v * s;
1236
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
1237
+ const m = v - c;
1238
+ let rPrime, gPrime, bPrime;
1239
+ if (h >= 0 && h < 60) {
1240
+ rPrime = c;
1241
+ gPrime = x;
1242
+ bPrime = 0;
1243
+ } else if (h >= 60 && h < 120) {
1244
+ rPrime = x;
1245
+ gPrime = c;
1246
+ bPrime = 0;
1247
+ } else if (h >= 120 && h < 180) {
1248
+ rPrime = 0;
1249
+ gPrime = c;
1250
+ bPrime = x;
1251
+ } else if (h >= 180 && h < 240) {
1252
+ rPrime = 0;
1253
+ gPrime = x;
1254
+ bPrime = c;
1255
+ } else if (h >= 240 && h < 300) {
1256
+ rPrime = x;
1257
+ gPrime = 0;
1258
+ bPrime = c;
1259
+ } else {
1260
+ rPrime = c;
1261
+ gPrime = 0;
1262
+ bPrime = x;
1263
+ }
1264
+ return {
1265
+ r: Math.round((rPrime + m) * 255),
1266
+ g: Math.round((gPrime + m) * 255),
1267
+ b: Math.round((bPrime + m) * 255)
1268
+ };
1269
+ }
1270
+ function rgbToHex(r, g, b) {
1271
+ const toHex = (n) => {
1272
+ const clamped = Math.max(0, Math.min(255, Math.round(n)));
1273
+ return clamped.toString(16).padStart(2, "0");
1274
+ };
1275
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1276
+ }
1277
+ function lighten(hex, amount = 0.1) {
1278
+ const rgb = hexToRgb(hex);
1279
+ if (!rgb) return hex;
1280
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
1281
+ const newV = Math.min(1, hsv.v + amount);
1282
+ const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
1283
+ return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
1284
+ }
1285
+ var shadowColors = {
1286
+ // For charcoal surfaces (~#2C2C2C to #3C3C3C)
1287
+ charcoal: {
1288
+ dark: "rgba(0, 0, 0, 0.5)",
1289
+ light: "rgba(255, 255, 255, 0.12)",
1290
+ // brighter for crisp rim light
1291
+ darker: "rgba(0, 0, 0, 0.6)"
1292
+ },
1293
+ // For neutral dark surfaces (~#1F2937)
1294
+ neutralDark: {
1295
+ dark: "rgba(0, 0, 0, 0.4)",
1296
+ light: "rgba(255, 255, 255, 0.08)",
1297
+ darker: "rgba(0, 0, 0, 0.5)"
1298
+ },
1299
+ // For light surfaces (white/light gray backgrounds)
1300
+ neutralLight: {
1301
+ dark: "rgba(0, 0, 0, 0.15)",
1302
+ // Visible dark shadow for depth on light backgrounds
1303
+ light: "rgba(255, 255, 255, 1)",
1304
+ // Bright highlight for raised effect (visible on light gray)
1305
+ darker: "rgba(0, 0, 0, 0.2)"
1306
+ // Deeper shadow for pressed states
1307
+ }
1308
+ };
1309
+ var intensityConfig = {
1310
+ subtle: { offset: 1, blur: 2 },
1311
+ // very light, barely visible
1312
+ medium: { offset: 2, blur: 4 },
1313
+ // noticeable but restrained
1314
+ strong: { offset: 2, blur: 5 }
1315
+ // slightly deeper blur, same offset as medium
1316
+ };
1317
+ function createRaisedShadow(surface = "charcoal", intensity = "medium") {
1318
+ const colors = shadowColors[surface];
1319
+ const { offset, blur } = intensityConfig[intensity];
1320
+ const rimBlur = 1;
1321
+ return reactNative.Platform.select({
1322
+ web: {
1323
+ boxShadow: `${offset}px ${offset}px ${blur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
1324
+ },
1325
+ default: {
1326
+ // React Native shadow approximation (limited compared to web)
1327
+ shadowColor: "#000",
1328
+ shadowOffset: { width: offset, height: offset },
1329
+ shadowOpacity: 0.3,
1330
+ shadowRadius: blur / 2,
1331
+ elevation: offset
1332
+ }
1333
+ });
1334
+ }
1335
+ function createPressedShadow(surface = "charcoal", intensity = "medium") {
1336
+ const colors = shadowColors[surface];
1337
+ const { offset, blur } = intensityConfig[intensity];
1338
+ const rimOffset = 1;
1339
+ const rimBlur = 1;
1340
+ return reactNative.Platform.select({
1341
+ web: {
1342
+ boxShadow: `inset ${offset}px ${offset}px ${blur}px ${colors.darker}, inset -${rimOffset}px -${rimOffset}px ${rimBlur}px ${colors.light}`
1343
+ },
1344
+ default: {
1345
+ // React Native doesn't support inset shadows, approximate with darker bg
1346
+ shadowOpacity: 0,
1347
+ elevation: 0
1348
+ }
1349
+ });
1350
+ }
1351
+ function createFlatShadow() {
1352
+ return reactNative.Platform.select({
1353
+ web: {
1354
+ boxShadow: "none"
1355
+ },
1356
+ default: {
1357
+ shadowOpacity: 0,
1358
+ elevation: 0
1359
+ }
1360
+ });
1361
+ }
1362
+ function createRaisedHoverShadow(surface = "charcoal", intensity = "medium") {
1363
+ const colors = shadowColors[surface];
1364
+ const { offset, blur } = intensityConfig[intensity];
1365
+ const hoverOffset = Math.max(1, Math.round(offset / 2));
1366
+ const hoverBlur = Math.max(1, Math.round(blur / 2));
1367
+ const rimBlur = 1;
1368
+ return reactNative.Platform.select({
1369
+ web: {
1370
+ // Keep same offsets as normal state for rim to prevent "shrinking" effect
1371
+ boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
1372
+ },
1373
+ default: {
1374
+ shadowColor: "#000",
1375
+ shadowOffset: { width: hoverOffset, height: hoverOffset },
1376
+ shadowOpacity: 0.2,
1377
+ shadowRadius: hoverBlur / 2,
1378
+ elevation: hoverOffset
1379
+ }
1380
+ });
1381
+ }
1382
+ function createPressedHoverShadow(surface = "charcoal", intensity = "medium") {
1383
+ const colors = shadowColors[surface];
1384
+ const { offset, blur } = intensityConfig[intensity];
1385
+ const hoverOffset = Math.max(1, Math.round(offset / 2));
1386
+ const hoverBlur = Math.max(1, Math.round(blur / 2));
1387
+ const rimBlur = 1;
1388
+ return reactNative.Platform.select({
1389
+ web: {
1390
+ // Keep rim offset at 1px (same as pressed normal) to prevent "shrinking" effect
1391
+ boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.darker}, inset -1px -1px ${rimBlur}px ${colors.light}`
1392
+ },
1393
+ default: {
1394
+ shadowOpacity: 0,
1395
+ elevation: 0
1396
+ }
1397
+ });
1398
+ }
1399
+ ({
1400
+ // Charcoal surface shadows (for dark toolbars, cards)
1401
+ charcoal: {
1402
+ raised: {
1403
+ subtle: createRaisedShadow("charcoal", "subtle"),
1404
+ medium: createRaisedShadow("charcoal", "medium"),
1405
+ strong: createRaisedShadow("charcoal", "strong")
1406
+ },
1407
+ raisedHover: {
1408
+ subtle: createRaisedHoverShadow("charcoal", "subtle"),
1409
+ medium: createRaisedHoverShadow("charcoal", "medium"),
1410
+ strong: createRaisedHoverShadow("charcoal", "strong")
1411
+ },
1412
+ pressed: {
1413
+ subtle: createPressedShadow("charcoal", "subtle"),
1414
+ medium: createPressedShadow("charcoal", "medium"),
1415
+ strong: createPressedShadow("charcoal", "strong")
1416
+ },
1417
+ pressedHover: {
1418
+ subtle: createPressedHoverShadow("charcoal", "subtle"),
1419
+ medium: createPressedHoverShadow("charcoal", "medium"),
1420
+ strong: createPressedHoverShadow("charcoal", "strong")
1421
+ },
1422
+ flat: createFlatShadow()
1423
+ },
1424
+ // Neutral dark surface shadows
1425
+ neutralDark: {
1426
+ raised: {
1427
+ subtle: createRaisedShadow("neutralDark", "subtle"),
1428
+ medium: createRaisedShadow("neutralDark", "medium"),
1429
+ strong: createRaisedShadow("neutralDark", "strong")
1430
+ },
1431
+ raisedHover: {
1432
+ subtle: createRaisedHoverShadow("neutralDark", "subtle"),
1433
+ medium: createRaisedHoverShadow("neutralDark", "medium"),
1434
+ strong: createRaisedHoverShadow("neutralDark", "strong")
1435
+ },
1436
+ pressed: {
1437
+ subtle: createPressedShadow("neutralDark", "subtle"),
1438
+ medium: createPressedShadow("neutralDark", "medium"),
1439
+ strong: createPressedShadow("neutralDark", "strong")
1440
+ },
1441
+ pressedHover: {
1442
+ subtle: createPressedHoverShadow("neutralDark", "subtle"),
1443
+ medium: createPressedHoverShadow("neutralDark", "medium"),
1444
+ strong: createPressedHoverShadow("neutralDark", "strong")
1445
+ },
1446
+ flat: createFlatShadow()
1447
+ },
1448
+ // Neutral light surface shadows
1449
+ neutralLight: {
1450
+ raised: {
1451
+ subtle: createRaisedShadow("neutralLight", "subtle"),
1452
+ medium: createRaisedShadow("neutralLight", "medium"),
1453
+ strong: createRaisedShadow("neutralLight", "strong")
1454
+ },
1455
+ raisedHover: {
1456
+ subtle: createRaisedHoverShadow("neutralLight", "subtle"),
1457
+ medium: createRaisedHoverShadow("neutralLight", "medium"),
1458
+ strong: createRaisedHoverShadow("neutralLight", "strong")
1459
+ },
1460
+ pressed: {
1461
+ subtle: createPressedShadow("neutralLight", "subtle"),
1462
+ medium: createPressedShadow("neutralLight", "medium"),
1463
+ strong: createPressedShadow("neutralLight", "strong")
1464
+ },
1465
+ pressedHover: {
1466
+ subtle: createPressedHoverShadow("neutralLight", "subtle"),
1467
+ medium: createPressedHoverShadow("neutralLight", "medium"),
1468
+ strong: createPressedHoverShadow("neutralLight", "strong")
1469
+ },
1470
+ flat: createFlatShadow()
1471
+ }
1472
+ });
1473
+ var elevationSystem = {
1474
+ // Deep inset (recessed input fields, deeply pressed buttons)
1475
+ [-2]: {
1476
+ name: "deep-inset",
1477
+ colorAdjustment: 0,
1478
+ shadowIntensity: "strong",
1479
+ shadowStyle: "pressed",
1480
+ description: "Deeply recessed elements (e.g., input fields, deeply pressed buttons)"
1481
+ },
1482
+ // Shallow inset (pressed buttons, sunken panels)
1483
+ [-1]: {
1484
+ name: "inset",
1485
+ colorAdjustment: 0,
1486
+ shadowIntensity: "medium",
1487
+ shadowStyle: "pressed",
1488
+ description: "Shallow inset elements (e.g., pressed buttons, sunken panels within cards)"
1489
+ },
1490
+ // Base level (page background, default surface)
1491
+ [0]: {
1492
+ name: "base",
1493
+ colorAdjustment: 0,
1494
+ shadowIntensity: "subtle",
1495
+ shadowStyle: "raised",
1496
+ description: "Base surface level (page background, default containers)"
1497
+ },
1498
+ // Level 1: Subtle elevation (outline cards, subtle containers)
1499
+ [1]: {
1500
+ name: "subtle",
1501
+ colorAdjustment: 0.01,
1502
+ shadowIntensity: "subtle",
1503
+ shadowStyle: "raised",
1504
+ description: "Subtle elevation (outline cards, subtle containers)"
1505
+ },
1506
+ // Level 2: Standard elevation (default cards, buttons)
1507
+ [2]: {
1508
+ name: "standard",
1509
+ colorAdjustment: 0.02,
1510
+ shadowIntensity: "medium",
1511
+ shadowStyle: "raised",
1512
+ description: "Standard elevation (default cards, raised buttons)"
1513
+ },
1514
+ // Level 3: Prominent elevation (important cards, modals)
1515
+ [3]: {
1516
+ name: "prominent",
1517
+ colorAdjustment: 0.04,
1518
+ shadowIntensity: "strong",
1519
+ shadowStyle: "raised",
1520
+ description: "Prominent elevation (important cards, modals, dropdowns)"
1521
+ },
1522
+ // Level 4: High elevation (overlays, popovers)
1523
+ [4]: {
1524
+ name: "high",
1525
+ colorAdjustment: 0.06,
1526
+ shadowIntensity: "strong",
1527
+ shadowStyle: "raised",
1528
+ description: "High elevation (overlays, popovers, tooltips)"
1529
+ },
1530
+ // Level 5: Floating (toasts, floating action buttons)
1531
+ [5]: {
1532
+ name: "floating",
1533
+ colorAdjustment: 0.08,
1534
+ shadowIntensity: "strong",
1535
+ shadowStyle: "raised",
1536
+ description: "Floating elevation (toasts, floating action buttons, highest z-index)"
1537
+ }
1538
+ };
1539
+ function calculateRimLightColor(baseColor, level) {
1540
+ const rgb = hexToRgb(baseColor);
1541
+ if (!rgb) {
1542
+ return "rgba(255, 255, 255, 0.5)";
1543
+ }
1544
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
1545
+ const isLight = hsv.v > 0.6;
1546
+ const isSaturated = hsv.s > 0.3;
1547
+ const absLevel = Math.abs(level);
1548
+ const opacityBoost = Math.max(0, (absLevel - 1) * 0.05);
1549
+ if (isSaturated) {
1550
+ const rimLightValue = 0.95;
1551
+ const rimLightRgb = hsvToRgb(hsv.h, hsv.s * 0.3, rimLightValue);
1552
+ const baseOpacity = isLight ? 0.9 : 0.5;
1553
+ const finalOpacity = Math.min(1, baseOpacity + opacityBoost);
1554
+ return `rgba(${rimLightRgb.r}, ${rimLightRgb.g}, ${rimLightRgb.b}, ${finalOpacity.toFixed(2)})`;
1555
+ } else {
1556
+ const baseOpacity = isLight ? 0.9 : 0.12;
1557
+ const finalOpacity = Math.min(1, baseOpacity + opacityBoost);
1558
+ return `rgba(255, 255, 255, ${finalOpacity.toFixed(2)})`;
1559
+ }
1560
+ }
1561
+ function calculateDarkShadowColors(surfaceColor) {
1562
+ const rgb = hexToRgb(surfaceColor);
1563
+ if (!rgb) {
1564
+ return {
1565
+ dark: "rgba(0, 0, 0, 0.3)",
1566
+ darker: "rgba(0, 0, 0, 0.4)"
1567
+ };
1568
+ }
1569
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
1570
+ const isLight = hsv.v > 0.6;
1571
+ const isSaturated = hsv.s > 0.3;
1572
+ if (isSaturated) {
1573
+ const darkShadowValue = 0.15;
1574
+ const darkShadowRgb = hsvToRgb(hsv.h, hsv.s, darkShadowValue);
1575
+ if (isLight) {
1576
+ return {
1577
+ dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.3)`,
1578
+ darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.4)`
1579
+ };
1580
+ } else {
1581
+ return {
1582
+ dark: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.5)`,
1583
+ darker: `rgba(${darkShadowRgb.r}, ${darkShadowRgb.g}, ${darkShadowRgb.b}, 0.6)`
1584
+ };
1585
+ }
1586
+ } else {
1587
+ if (isLight) {
1588
+ return {
1589
+ dark: "rgba(0, 0, 0, 0.2)",
1590
+ darker: "rgba(0, 0, 0, 0.3)"
1591
+ };
1592
+ } else {
1593
+ return {
1594
+ dark: "rgba(0, 0, 0, 0.5)",
1595
+ darker: "rgba(0, 0, 0, 0.6)"
1596
+ };
1597
+ }
1598
+ }
1599
+ }
1600
+ function getRimLightConfig() {
1601
+ return {
1602
+ offset: 1,
1603
+ // Fixed 1px for crisp rim
1604
+ blur: 1
1605
+ // Tight blur for crisp edge
1606
+ };
1607
+ }
1608
+ var elevationSurfaceCache = /* @__PURE__ */ new Map();
1609
+ var MAX_CACHE_SIZE = 100;
1610
+ function getCacheKey(baseColor, level, theme) {
1611
+ return `${baseColor}-${level}-${theme}`;
1612
+ }
1613
+ function calculateElevationSurface(baseColor, level, theme) {
1614
+ const config = elevationSystem[level];
1615
+ if (config.colorAdjustment === 0) {
1616
+ return baseColor;
1617
+ }
1618
+ if (level < 0) {
1619
+ return baseColor;
1620
+ }
1621
+ return lighten(baseColor, config.colorAdjustment);
1622
+ }
1623
+ function getElevationSurface(baseColor, level, theme) {
1624
+ const cacheKey = getCacheKey(baseColor, level, theme);
1625
+ if (elevationSurfaceCache.has(cacheKey)) {
1626
+ return elevationSurfaceCache.get(cacheKey);
1627
+ }
1628
+ const result = calculateElevationSurface(baseColor, level);
1629
+ if (elevationSurfaceCache.size >= MAX_CACHE_SIZE) {
1630
+ const firstKey = elevationSurfaceCache.keys().next().value;
1631
+ elevationSurfaceCache.delete(firstKey);
1632
+ }
1633
+ elevationSurfaceCache.set(cacheKey, result);
1634
+ return result;
1635
+ }
1636
+ var elevationShadowCache = /* @__PURE__ */ new Map();
1637
+ var MAX_SHADOW_CACHE_SIZE = 50;
1638
+ function getShadowCacheKey(baseColor, level, theme, isHovered) {
1639
+ return `${baseColor}-${level}-${theme}-${isHovered}`;
1640
+ }
1641
+ function calculateElevationShadow(baseColor, level, theme, isHovered = false) {
1642
+ const config = elevationSystem[level];
1643
+ if (level === 0) {
1644
+ return {};
1645
+ }
1646
+ const elevatedSurfaceColor = calculateElevationSurface(baseColor, level);
1647
+ const rimLightColor = calculateRimLightColor(baseColor, level);
1648
+ const darkShadowColors = calculateDarkShadowColors(elevatedSurfaceColor);
1649
+ const intensityConfig2 = {
1650
+ subtle: { offset: 1, blur: 2 },
1651
+ medium: { offset: 2, blur: 4 },
1652
+ strong: { offset: 2, blur: 5 }
1653
+ };
1654
+ const { offset, blur } = intensityConfig2[config.shadowIntensity];
1655
+ const rimConfig = getRimLightConfig();
1656
+ if (config.shadowStyle === "pressed") {
1657
+ if (isHovered) {
1658
+ const hoverOffset = Math.max(1, Math.round(offset / 2));
1659
+ const hoverBlur = Math.max(1, Math.round(blur / 2));
1660
+ return reactNative.Platform.select({
1661
+ web: {
1662
+ boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1663
+ },
1664
+ default: {
1665
+ shadowOpacity: 0,
1666
+ elevation: 0
1667
+ }
1668
+ });
1669
+ }
1670
+ return reactNative.Platform.select({
1671
+ web: {
1672
+ boxShadow: `inset ${offset}px ${offset}px ${blur}px ${darkShadowColors.darker}, inset -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1673
+ },
1674
+ default: {
1675
+ shadowOpacity: 0,
1676
+ elevation: 0
1677
+ }
1678
+ });
1679
+ } else {
1680
+ if (isHovered) {
1681
+ const hoverOffset = Math.max(1, Math.round(offset / 2));
1682
+ const hoverBlur = Math.max(1, Math.round(blur / 2));
1683
+ return reactNative.Platform.select({
1684
+ web: {
1685
+ boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1686
+ },
1687
+ default: {
1688
+ shadowColor: "#000",
1689
+ shadowOffset: { width: hoverOffset, height: hoverOffset },
1690
+ shadowOpacity: 0.2,
1691
+ shadowRadius: hoverBlur / 2,
1692
+ elevation: hoverOffset
1693
+ }
1694
+ });
1695
+ }
1696
+ return reactNative.Platform.select({
1697
+ web: {
1698
+ boxShadow: `${offset}px ${offset}px ${blur}px ${darkShadowColors.dark}, -${rimConfig.offset}px -${rimConfig.offset}px ${rimConfig.blur}px ${rimLightColor}`
1699
+ },
1700
+ default: {
1701
+ shadowColor: "#000",
1702
+ shadowOffset: { width: offset, height: offset },
1703
+ shadowOpacity: 0.3,
1704
+ shadowRadius: blur / 2,
1705
+ elevation: offset
1706
+ }
1707
+ });
1708
+ }
1709
+ }
1710
+ function getElevationShadow(baseColor, level, theme, isHovered = false) {
1711
+ const cacheKey = getShadowCacheKey(baseColor, level, theme, isHovered);
1712
+ if (elevationShadowCache.has(cacheKey)) {
1713
+ return elevationShadowCache.get(cacheKey);
1714
+ }
1715
+ const result = calculateElevationShadow(baseColor, level, theme, isHovered);
1716
+ if (elevationShadowCache.size >= MAX_SHADOW_CACHE_SIZE) {
1717
+ const firstKey = elevationShadowCache.keys().next().value;
1718
+ elevationShadowCache.delete(firstKey);
1719
+ }
1720
+ elevationShadowCache.set(cacheKey, result);
1721
+ return result;
1722
+ }
1723
+ function getBaseSurfaceColor(theme) {
1724
+ return theme === "light" ? semanticColorsLight["surface-raised"] : semanticColorsDark["surface-elevated"];
1725
+ }
1726
+ var componentElevationRanges = {
1727
+ button: {
1728
+ allowed: [2],
1729
+ default: 2,
1730
+ hover: 3
1731
+ },
1732
+ card: {
1733
+ allowed: [1, 2, 3],
1734
+ default: 2,
1735
+ hover: 3
1736
+ },
1737
+ input: {
1738
+ allowed: [-2, -1, 0],
1739
+ default: -1
1740
+ },
1741
+ overlay: {
1742
+ allowed: [4, 5],
1743
+ default: 4
1744
+ }
1745
+ };
1746
+ function getValidatedElevation(component, requested) {
1747
+ const range = componentElevationRanges[component];
1748
+ if (range.allowed.includes(requested)) {
1749
+ return requested;
1750
+ }
1751
+ const sorted = [...range.allowed].sort((a, b) => a - b);
1752
+ if (requested < sorted[0]) return sorted[0];
1753
+ if (requested > sorted[sorted.length - 1]) return sorted[sorted.length - 1];
1754
+ return sorted.reduce(
1755
+ (prev, curr) => Math.abs(curr - requested) < Math.abs(prev - requested) ? curr : prev
1756
+ );
1757
+ }
1758
+ function getTheme() {
1759
+ if (typeof document === "undefined") return "dark";
1760
+ return document.documentElement.classList.contains("light") ? "light" : "dark";
1761
+ }
1762
+ var listeners = [];
1763
+ function subscribe(listener) {
1764
+ listeners = [...listeners, listener];
1765
+ if (typeof document !== "undefined" && listeners.length === 1) {
1766
+ const observer = new MutationObserver((mutations) => {
1767
+ for (const mutation of mutations) {
1768
+ if (mutation.type === "attributes" && mutation.attributeName === "class") {
1769
+ listeners.forEach((l) => l());
1770
+ }
1771
+ }
1772
+ });
1773
+ observer.observe(document.documentElement, {
1774
+ attributes: true,
1775
+ attributeFilter: ["class"]
1776
+ });
1777
+ subscribe._observer = observer;
1778
+ }
1779
+ return () => {
1780
+ listeners = listeners.filter((l) => l !== listener);
1781
+ if (listeners.length === 0 && subscribe._observer) {
1782
+ subscribe._observer.disconnect();
1783
+ subscribe._observer = null;
1784
+ }
1785
+ };
1786
+ }
1787
+ function getSnapshot() {
1788
+ return getTheme();
1789
+ }
1790
+ function getServerSnapshot() {
1791
+ return "dark";
1792
+ }
1793
+ function useTheme() {
1794
+ return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1795
+ }
1796
+
1797
+ // src/components/ui/card/Card.tsx
1798
+ var variantStyles = {
1799
+ elevated: "",
1800
+ // Will be set dynamically via elevation system
1801
+ outline: "border-2 border-border-strong",
1802
+ // Thicker border with stronger contrast
1803
+ filled: "",
1804
+ // Will be set dynamically via elevation system
1805
+ accent: "border border-border",
1806
+ subtle: "border border-border-subtle"
1807
+ };
1808
+ function Card({
1809
+ variant = "elevated",
1810
+ elevation = 2,
1811
+ isInteractive = false,
1812
+ isLoading = false,
1813
+ onPress,
1814
+ borderColor,
1815
+ bgColor,
1816
+ accentColor,
1817
+ accentWidth,
1818
+ className,
1819
+ children,
1820
+ style,
1821
+ skeletonHasHeader = true,
1822
+ skeletonHasFooter = false,
1823
+ skeletonContentLines = 3,
1824
+ ...props
1825
+ }) {
1826
+ const isClickable = isInteractive || !!onPress;
1827
+ const [isHovered, setIsHovered] = react.useState(false);
1828
+ const theme = useTheme();
1829
+ const elevationLevel = react.useMemo(() => {
1830
+ const validated = getValidatedElevation("card", elevation);
1831
+ if (variant === "outline" || variant === "accent" || variant === "subtle") {
1832
+ return 1;
1833
+ }
1834
+ return validated;
1835
+ }, [variant, elevation]);
1836
+ const baseColor = react.useMemo(() => getBaseSurfaceColor(theme), [theme]);
1837
+ const surfaceColor = react.useMemo(() => {
1838
+ if ((variant === "outline" || variant === "accent") && theme === "light") {
1839
+ return "#FAFAFA";
1840
+ }
1841
+ if (variant === "subtle") {
1842
+ return getElevationSurface(baseColor, 0, theme);
1843
+ }
1844
+ return getElevationSurface(baseColor, elevationLevel, theme);
1845
+ }, [baseColor, elevationLevel, theme, variant]);
1846
+ const shadowStyle = react.useMemo(() => {
1847
+ const shouldHover = isHovered && isClickable;
1848
+ return getElevationShadow(baseColor, elevationLevel, theme, shouldHover);
1849
+ }, [baseColor, elevationLevel, theme, isHovered, isClickable]);
1850
+ const cardContent = isLoading ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
1851
+ skeletonHasHeader && /* @__PURE__ */ jsxs(CardHeader, { children: [
1852
+ /* @__PURE__ */ jsx(reactNative.View, { className: "h-5 w-1/3 bg-interactive-disabled rounded" }),
1853
+ /* @__PURE__ */ jsx(reactNative.View, { className: "mt-2 h-4 w-2/3 bg-interactive-disabled rounded" })
1854
+ ] }),
1855
+ /* @__PURE__ */ jsx(CardContent, { children: Array.from({ length: skeletonContentLines }).map((_, i) => /* @__PURE__ */ jsx(
1856
+ reactNative.View,
1857
+ {
1858
+ className: cn(
1859
+ "h-4 bg-interactive-disabled rounded",
1860
+ i < skeletonContentLines - 1 ? "mb-2 w-full" : "w-4/5"
1861
+ )
1862
+ },
1863
+ i
1864
+ )) }),
1865
+ skeletonHasFooter && /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(reactNative.View, { className: "h-9 w-24 bg-interactive-disabled rounded" }) })
1866
+ ] }) : children;
1867
+ const baseClassName = cn(
1868
+ "rounded-lg overflow-hidden relative",
1869
+ // Apply variant styles, but only use default border color if no custom borderColor
1870
+ variant === "outline" ? borderColor ? "border-2" : variantStyles[variant] : variantStyles[variant],
1871
+ // Remove Tailwind shadow classes - we're using elevation shadows via style prop
1872
+ isClickable && "web:cursor-pointer web:transition-all web:duration-150",
1873
+ isClickable && "web:hover:-translate-y-0.5 active:scale-[0.99]",
1874
+ isLoading && "pointer-events-none animate-pulse",
1875
+ className
1876
+ );
1877
+ const accentStyle = react.useMemo(() => variant === "accent" ? {
1878
+ borderLeftWidth: accentWidth ?? 3,
1879
+ borderLeftColor: accentColor ?? "var(--color-brand-primary)"
1880
+ } : {}, [variant, accentWidth, accentColor]);
1881
+ const mergedStyle = react.useMemo(() => {
1882
+ const elevationStyle = {
1883
+ backgroundColor: bgColor || surfaceColor,
1884
+ borderRadius: 8,
1885
+ overflow: "hidden",
1886
+ ...shadowStyle,
1887
+ ...accentStyle
1888
+ };
1889
+ if (borderColor) {
1890
+ elevationStyle.borderColor = borderColor;
1891
+ }
1892
+ if (!style) return elevationStyle;
1893
+ return [elevationStyle, style];
1894
+ }, [style, surfaceColor, shadowStyle, borderColor, bgColor, accentStyle]);
1895
+ if (isClickable) {
1896
+ return /* @__PURE__ */ jsx(
1897
+ reactNative.Pressable,
1898
+ {
1899
+ onPress,
1900
+ onHoverIn: () => setIsHovered(true),
1901
+ onHoverOut: () => setIsHovered(false),
1902
+ accessibilityRole: "button",
1903
+ className: baseClassName,
1904
+ style: mergedStyle,
1905
+ ...props,
1906
+ children: cardContent
1907
+ }
1908
+ );
1909
+ }
1910
+ return /* @__PURE__ */ jsx(reactNative.View, { className: baseClassName, style: mergedStyle, ...props, children: cardContent });
1911
+ }
1912
+ function CardHeader({ children, className }) {
1913
+ return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-6", className), children });
1914
+ }
1915
+ function CardContent({ children, className }) {
1916
+ return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4", className), children });
1917
+ }
1918
+ function CardFooter({ children, className }) {
1919
+ return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
1920
+ }
1921
+ var solidVariantColors = {
1922
+ success: "#14B8A6",
1923
+ warning: "#FFB020",
1924
+ error: "#D14343",
1925
+ neutral: "#6B7280"
1926
+ };
1927
+ var ringVariantStyles = {
1928
+ "on-track": {
1929
+ backgroundColor: "rgba(20,184,166,0.15)",
1930
+ borderWidth: 1,
1931
+ borderColor: "rgba(20,184,166,0.3)"
1932
+ },
1933
+ deviation: {
1934
+ backgroundColor: "rgba(245,158,11,0.15)",
1935
+ borderWidth: 1,
1936
+ borderColor: "rgba(245,158,11,0.25)"
1937
+ },
1938
+ future: {
1939
+ backgroundColor: "rgba(107,114,128,0.1)",
1940
+ borderWidth: 1,
1941
+ borderStyle: "dashed",
1942
+ borderColor: "rgba(107,114,128,0.2)"
1943
+ }
1944
+ };
1945
+ var glowStyles = {
1946
+ success: { boxShadow: "0 0 4px rgba(20,184,166,0.4)" },
1947
+ warning: { boxShadow: "0 0 4px rgba(245,158,11,0.4)" },
1948
+ error: { boxShadow: "0 0 4px rgba(239,68,68,0.4)" },
1949
+ neutral: { boxShadow: "0 0 4px rgba(107,114,128,0.4)" },
1950
+ "on-track": { boxShadow: "0 0 4px rgba(20,184,166,0.4)" },
1951
+ deviation: { boxShadow: "0 0 4px rgba(245,158,11,0.4)" },
1952
+ future: { boxShadow: "0 0 4px rgba(107,114,128,0.4)" }
1953
+ };
1954
+ var iconChars = {
1955
+ check: "\u2713",
1956
+ exclamation: "!",
1957
+ dash: "\u2014"
1958
+ };
1959
+ var iconColors = {
1960
+ success: "#0A5C52",
1961
+ warning: "#6B4000",
1962
+ error: "#5C1A1A",
1963
+ neutral: "#D1D5DB",
1964
+ "on-track": "#14B8A6",
1965
+ deviation: "#FFB020",
1966
+ future: "#6B7280"
1967
+ };
1968
+ var sizeDimensions = {
1969
+ sm: { size: 8, showIcon: false },
1970
+ md: { size: 18, showIcon: true }
1971
+ };
1972
+ function isSolidVariant(variant) {
1973
+ return variant in solidVariantColors;
1974
+ }
1975
+ function StatusDot({
1976
+ variant,
1977
+ icon,
1978
+ size = "sm",
1979
+ glow,
1980
+ label,
1981
+ className,
1982
+ ...props
1983
+ }) {
1984
+ const config = sizeDimensions[size];
1985
+ const solid = isSolidVariant(variant);
1986
+ const ring = ringVariantStyles[variant];
1987
+ const glowStyle = glow ? glowStyles[variant] : null;
1988
+ const dot = /* @__PURE__ */ jsx(
1989
+ reactNative.View,
1990
+ {
1991
+ style: [
1992
+ {
1993
+ width: config.size,
1994
+ height: config.size,
1995
+ borderRadius: 9999,
1996
+ alignItems: "center",
1997
+ justifyContent: "center",
1998
+ flexShrink: 0
1999
+ },
2000
+ solid ? { backgroundColor: solidVariantColors[variant] } : void 0,
2001
+ ring ? ring : void 0,
2002
+ glowStyle ? glowStyle : void 0
2003
+ ],
2004
+ accessibilityRole: "image",
2005
+ accessibilityLabel: `${variant} status`,
2006
+ testID: "status-dot",
2007
+ children: config.showIcon && icon && /* @__PURE__ */ jsx(
2008
+ reactNative.Text,
2009
+ {
2010
+ style: {
2011
+ fontSize: 11,
2012
+ lineHeight: 11,
2013
+ fontWeight: "900",
2014
+ color: iconColors[variant]
2015
+ },
2016
+ accessibilityElementsHidden: true,
2017
+ children: iconChars[icon]
2018
+ }
2019
+ )
2020
+ }
2021
+ );
2022
+ if (label) {
2023
+ return /* @__PURE__ */ jsxs(
2024
+ reactNative.View,
2025
+ {
2026
+ className,
2027
+ style: { flexDirection: "row", alignItems: "center", gap: 6 },
2028
+ accessibilityLabel: `${variant} status: ${label}`,
2029
+ ...props,
2030
+ children: [
2031
+ /* @__PURE__ */ jsx(reactNative.View, { accessibilityElementsHidden: true, children: dot }),
2032
+ /* @__PURE__ */ jsx(
2033
+ reactNative.Text,
2034
+ {
2035
+ style: { fontSize: 12, lineHeight: 16, color: "#9CA3AF" },
2036
+ accessibilityElementsHidden: true,
2037
+ children: label
2038
+ }
2039
+ )
2040
+ ]
2041
+ }
2042
+ );
2043
+ }
2044
+ return /* @__PURE__ */ jsx(reactNative.View, { className, ...props, children: dot });
2045
+ }
2046
+
2047
+ // src/components/custom/Workout/MesoStatusCard.tsx
2048
+ var BRAND_PRIMARY3 = "#FF7900";
2049
+ var BRAND_PRIMARY_DARK = "#C45E00";
2050
+ var BRAND_PRIMARY_LIGHT = "#FFB066";
2051
+ var TEXT_PRIMARY3 = "var(--color-text-primary)";
2052
+ var TEXT_SECONDARY3 = "var(--color-text-secondary)";
2053
+ var TEXT_TERTIARY2 = "var(--color-text-tertiary)";
2054
+ var SUCCESS = "#14B8A6";
2055
+ var WARNING = "#FFB020";
2056
+ var ERROR = "#D14343";
2057
+ var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY3, BRAND_PRIMARY_LIGHT];
2058
+ var CARD_GRADIENT = "linear-gradient(135deg, var(--color-surface-elevated) 0%, var(--color-surface-raised) 100%)";
2059
+ var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(20,184,166,0.25) 0%, rgba(255,176,32,0.25) 50%, rgba(209,67,67,0.25) 100%)";
2060
+ var STATUS_VARIANTS = {
2061
+ success: { bg: "rgba(20,184,166,0.15)", border: "rgba(20,184,166,0.3)", text: SUCCESS },
2062
+ warning: { bg: "rgba(255,176,32,0.15)", border: "rgba(255,176,32,0.3)", text: WARNING },
2063
+ error: { bg: "rgba(209,67,67,0.15)", border: "rgba(209,67,67,0.25)", text: ERROR }
2064
+ };
2065
+ function clamp012(value) {
2066
+ if (!Number.isFinite(value)) return 0;
2067
+ if (value < 0) return 0;
2068
+ if (value > 1) return 1;
2069
+ return value;
2070
+ }
2071
+ function getGaugeZoneColor(level) {
2072
+ if (level >= 0.7) return ERROR;
2073
+ if (level >= 0.4) return WARNING;
2074
+ return SUCCESS;
2075
+ }
2076
+ function renderCoachingText(text, highlights) {
2077
+ if (!highlights || highlights.length === 0) return text;
2078
+ const escaped = highlights.filter(Boolean).map((h) => h.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
2079
+ if (escaped.length === 0) return text;
2080
+ const regex = new RegExp(`(${escaped.join("|")})`, "g");
2081
+ return text.split(regex).map(
2082
+ (part, index) => highlights.includes(part) ? /* @__PURE__ */ jsx(
2083
+ reactNative.Text,
2084
+ {
2085
+ style: { fontWeight: "700", color: TEXT_PRIMARY3 },
2086
+ children: part
2087
+ },
2088
+ `${part}-${index}`
2089
+ ) : /* @__PURE__ */ jsx(react.Fragment, { children: part }, `t-${index}`)
2090
+ );
2091
+ }
2092
+ function StatusPill({ badge }) {
2093
+ const colors = STATUS_VARIANTS[badge.variant];
2094
+ return /* @__PURE__ */ jsxs(
2095
+ reactNative.View,
2096
+ {
2097
+ className: "flex-row items-center",
2098
+ style: {
2099
+ gap: 5,
2100
+ paddingVertical: 3,
2101
+ paddingHorizontal: 8,
2102
+ borderRadius: 4,
2103
+ borderWidth: 1,
2104
+ backgroundColor: colors.bg,
2105
+ borderColor: colors.border
2106
+ },
2107
+ testID: "meso-status-card-badge",
2108
+ children: [
2109
+ /* @__PURE__ */ jsx(reactNative.View, { accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(StatusDot, { variant: badge.variant, size: "sm" }) }),
2110
+ /* @__PURE__ */ jsx(
2111
+ reactNative.Text,
2112
+ {
2113
+ style: {
2114
+ fontSize: 10,
2115
+ fontFamily: "Inter, sans-serif",
2116
+ fontWeight: "700",
2117
+ letterSpacing: 0.3,
2118
+ color: colors.text
2119
+ },
2120
+ children: badge.label
2121
+ }
2122
+ )
2123
+ ]
2124
+ }
2125
+ );
2126
+ }
2127
+ function MetricCell({ metric }) {
2128
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { width: "48%" }, testID: "meso-status-card-metric", children: [
2129
+ /* @__PURE__ */ jsx(
2130
+ reactNative.Text,
2131
+ {
2132
+ style: {
2133
+ fontSize: 10,
2134
+ fontFamily: "Inter, sans-serif",
2135
+ fontWeight: "600",
2136
+ letterSpacing: 0.5,
2137
+ textTransform: "uppercase",
2138
+ color: TEXT_TERTIARY2
2139
+ },
2140
+ children: metric.label
2141
+ }
2142
+ ),
2143
+ /* @__PURE__ */ jsx(
2144
+ reactNative.Text,
2145
+ {
2146
+ style: {
2147
+ marginTop: 2,
2148
+ fontSize: 13,
2149
+ fontFamily: "Inter, sans-serif",
2150
+ fontWeight: "600",
2151
+ color: TEXT_PRIMARY3
2152
+ },
2153
+ children: metric.value
2154
+ }
2155
+ )
2156
+ ] });
2157
+ }
2158
+ function Gauge({ gauge }) {
2159
+ const level = clamp012(gauge.level);
2160
+ const percentage = Math.round(level * 100);
2161
+ const markerColor = getGaugeZoneColor(level);
2162
+ return /* @__PURE__ */ jsxs(
2163
+ reactNative.View,
2164
+ {
2165
+ style: { gap: 6 },
2166
+ accessibilityRole: "progressbar",
2167
+ accessibilityValue: { min: 0, max: 100, now: percentage },
2168
+ accessibilityLabel: `${gauge.label}: ${percentage}%`,
2169
+ "aria-valuenow": percentage,
2170
+ "aria-valuemin": 0,
2171
+ "aria-valuemax": 100,
2172
+ testID: "meso-status-card-gauge",
2173
+ children: [
2174
+ /* @__PURE__ */ jsxs(
2175
+ reactNative.View,
2176
+ {
2177
+ className: "flex-row items-center justify-between",
2178
+ accessibilityElementsHidden: true,
2179
+ children: [
2180
+ /* @__PURE__ */ jsx(
2181
+ reactNative.Text,
2182
+ {
2183
+ style: {
2184
+ fontSize: 10,
2185
+ fontFamily: "Inter, sans-serif",
2186
+ fontWeight: "500",
2187
+ letterSpacing: 0.5,
2188
+ textTransform: "uppercase",
2189
+ color: TEXT_SECONDARY3
2190
+ },
2191
+ children: gauge.label
2192
+ }
2193
+ ),
2194
+ gauge.sublabel != null && /* @__PURE__ */ jsx(
2195
+ reactNative.Text,
2196
+ {
2197
+ style: {
2198
+ fontSize: 10,
2199
+ fontFamily: "Inter, sans-serif",
2200
+ fontWeight: "500",
2201
+ color: TEXT_TERTIARY2
2202
+ },
2203
+ children: gauge.sublabel
2204
+ }
2205
+ )
2206
+ ]
2207
+ }
2208
+ ),
2209
+ /* @__PURE__ */ jsxs(
2210
+ reactNative.View,
2211
+ {
2212
+ style: {
2213
+ height: 8,
2214
+ borderRadius: 4,
2215
+ position: "relative",
2216
+ backgroundImage: GAUGE_GRADIENT
2217
+ },
2218
+ accessibilityElementsHidden: true,
2219
+ children: [
2220
+ /* @__PURE__ */ jsx(
2221
+ reactNative.View,
2222
+ {
2223
+ style: {
2224
+ position: "absolute",
2225
+ left: "50%",
2226
+ top: 0,
2227
+ bottom: 0,
2228
+ width: 1,
2229
+ marginLeft: -0.5,
2230
+ backgroundColor: "rgba(255,255,255,0.3)"
2231
+ },
2232
+ testID: "meso-status-card-gauge-center"
2233
+ }
2234
+ ),
2235
+ /* @__PURE__ */ jsx(
2236
+ reactNative.View,
2237
+ {
2238
+ style: {
2239
+ position: "absolute",
2240
+ left: `${percentage}%`,
2241
+ top: -3,
2242
+ marginLeft: -7,
2243
+ width: 14,
2244
+ height: 14,
2245
+ borderRadius: 9999,
2246
+ borderWidth: 2,
2247
+ borderColor: TEXT_PRIMARY3,
2248
+ backgroundColor: markerColor,
2249
+ boxShadow: "0 0 4px rgba(0,0,0,0.5)"
2250
+ },
2251
+ testID: "meso-status-card-gauge-marker"
2252
+ }
2253
+ )
2254
+ ]
2255
+ }
2256
+ )
2257
+ ]
2258
+ }
2259
+ );
2260
+ }
2261
+ function MesoStatusCard({
2262
+ mesoName,
2263
+ mesoSubtitle,
2264
+ statusBadge,
2265
+ metrics,
2266
+ gauges,
2267
+ coaching,
2268
+ nextTarget,
2269
+ className,
2270
+ ...props
2271
+ }) {
2272
+ return /* @__PURE__ */ jsxs(
2273
+ Card,
2274
+ {
2275
+ variant: "outline",
2276
+ elevation: 1,
2277
+ borderColor: BRAND_PRIMARY_DARK,
2278
+ className,
2279
+ style: {
2280
+ borderColor: BRAND_PRIMARY_DARK,
2281
+ borderWidth: 1,
2282
+ backgroundImage: CARD_GRADIENT
2283
+ },
2284
+ role: "article",
2285
+ "aria-label": `Mesocycle status for ${mesoName}`,
2286
+ testID: "meso-status-card",
2287
+ ...props,
2288
+ children: [
2289
+ /* @__PURE__ */ jsx(
2290
+ reactNative.View,
2291
+ {
2292
+ className: "flex-row",
2293
+ style: { position: "absolute", top: 0, left: 0, right: 0, height: 3, zIndex: 1 },
2294
+ accessibilityElementsHidden: true,
2295
+ testID: "meso-status-card-accent",
2296
+ children: ACCENT_STOPS.map((color) => /* @__PURE__ */ jsx(reactNative.View, { style: { flex: 1, backgroundColor: color } }, color))
2297
+ }
2298
+ ),
2299
+ /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 14, gap: 14 }, testID: "meso-status-card-body", children: [
2300
+ /* @__PURE__ */ jsxs(reactNative.View, { testID: "meso-status-card-header", children: [
2301
+ /* @__PURE__ */ jsxs(
2302
+ reactNative.View,
2303
+ {
2304
+ className: "flex-row items-center justify-between",
2305
+ style: { gap: 8 },
2306
+ children: [
2307
+ /* @__PURE__ */ jsx(
2308
+ reactNative.Text,
2309
+ {
2310
+ style: {
2311
+ flexShrink: 1,
2312
+ fontSize: 15,
2313
+ fontFamily: '"Space Grotesk", sans-serif',
2314
+ fontWeight: "700",
2315
+ color: TEXT_PRIMARY3
2316
+ },
2317
+ testID: "meso-status-card-name",
2318
+ children: mesoName
2319
+ }
2320
+ ),
2321
+ /* @__PURE__ */ jsx(StatusPill, { badge: statusBadge })
2322
+ ]
2323
+ }
2324
+ ),
2325
+ /* @__PURE__ */ jsx(
2326
+ reactNative.Text,
2327
+ {
2328
+ style: {
2329
+ marginTop: 4,
2330
+ fontSize: 12,
2331
+ fontFamily: "Inter, sans-serif",
2332
+ color: TEXT_SECONDARY3
2333
+ },
2334
+ testID: "meso-status-card-subtitle",
2335
+ children: mesoSubtitle
2336
+ }
2337
+ )
2338
+ ] }),
2339
+ metrics.length > 0 && /* @__PURE__ */ jsx(
2340
+ reactNative.View,
2341
+ {
2342
+ className: "flex-row flex-wrap",
2343
+ style: { gap: 8 },
2344
+ testID: "meso-status-card-metrics",
2345
+ children: metrics.map((metric, index) => /* @__PURE__ */ jsx(MetricCell, { metric }, `${metric.label}-${index}`))
2346
+ }
2347
+ ),
2348
+ gauges.length > 0 && /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 12 }, testID: "meso-status-card-gauges", children: gauges.map((gauge, index) => /* @__PURE__ */ jsx(Gauge, { gauge }, `${gauge.label}-${index}`)) }),
2349
+ coaching != null && /* @__PURE__ */ jsx(
2350
+ reactNative.View,
2351
+ {
2352
+ style: {
2353
+ backgroundColor: "rgba(255,176,32,0.06)",
2354
+ borderWidth: 1,
2355
+ borderColor: "rgba(255,176,32,0.15)",
2356
+ borderRadius: 8,
2357
+ paddingVertical: 10,
2358
+ paddingHorizontal: 12
2359
+ },
2360
+ testID: "meso-status-card-coaching",
2361
+ children: /* @__PURE__ */ jsx(
2362
+ reactNative.Text,
2363
+ {
2364
+ style: {
2365
+ fontSize: 12,
2366
+ lineHeight: 17,
2367
+ fontFamily: "Inter, sans-serif",
2368
+ color: TEXT_SECONDARY3
2369
+ },
2370
+ children: renderCoachingText(coaching.text, coaching.highlights)
2371
+ }
2372
+ )
2373
+ }
2374
+ ),
2375
+ nextTarget != null && /* @__PURE__ */ jsxs(
2376
+ reactNative.View,
2377
+ {
2378
+ className: "flex-row items-center",
2379
+ style: {
2380
+ gap: 8,
2381
+ backgroundColor: "rgba(20,184,166,0.06)",
2382
+ borderWidth: 1,
2383
+ borderColor: "rgba(20,184,166,0.2)",
2384
+ borderRadius: 8,
2385
+ paddingVertical: 10,
2386
+ paddingHorizontal: 12
2387
+ },
2388
+ testID: "meso-status-card-next-target",
2389
+ children: [
2390
+ /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 14, color: SUCCESS }, accessibilityElementsHidden: true, children: nextTarget.icon }),
2391
+ /* @__PURE__ */ jsx(
2392
+ reactNative.Text,
2393
+ {
2394
+ style: {
2395
+ flexShrink: 1,
2396
+ fontSize: 12,
2397
+ fontFamily: "Inter, sans-serif",
2398
+ fontWeight: "600",
2399
+ color: SUCCESS
2400
+ },
2401
+ children: nextTarget.text
2402
+ }
2403
+ )
2404
+ ]
2405
+ }
2406
+ )
2407
+ ] })
2408
+ ]
2409
+ }
2410
+ );
2411
+ }
2412
+
2413
+ // src/components/custom/Workout/TrainingStatusPage.tsx
2414
+ var SURFACE_ELEVATED2 = "var(--color-surface-elevated)";
2415
+ var BORDER_DEFAULT2 = "var(--color-border-default)";
2416
+ var TEXT_PRIMARY4 = "var(--color-text-primary)";
2417
+ var TEXT_SECONDARY4 = "var(--color-text-secondary)";
2418
+ var TEXT_TERTIARY3 = "var(--color-text-tertiary)";
2419
+ var PAGE_BG = "var(--color-background-base)";
2420
+ var STATUS_ORDER = ["under", "maintenance", "productive", "over"];
2421
+ function deriveTrainingSummary(muscles) {
2422
+ const statusCounts = {
2423
+ under: 0,
2424
+ maintenance: 0,
2425
+ productive: 0,
2426
+ over: 0
2427
+ };
2428
+ let totalWeeklySets = 0;
2429
+ for (const muscle of muscles) {
2430
+ totalWeeklySets += muscle.weeklySets;
2431
+ statusCounts[muscle.volumeStatus] += 1;
2432
+ }
2433
+ return { totalWeeklySets, trackedMuscles: muscles.length, statusCounts };
2434
+ }
2435
+ function toBodyMapData(muscles, side) {
2436
+ return muscles.filter((muscle) => muscle.side === side).map(({ muscleGroup, intensity, volumeStatus, weeklySets }) => ({
2437
+ muscleGroup,
2438
+ intensity,
2439
+ volumeStatus,
2440
+ weeklySets
2441
+ }));
2442
+ }
2443
+ var SUMMARY_CARDS = [
2444
+ { key: "total", label: "Weekly Sets" },
2445
+ { key: "productive", label: "Productive" },
2446
+ { key: "under", label: "Under" },
2447
+ { key: "over", label: "Over" }
2448
+ ];
2449
+ function SummaryCards({ summary }) {
2450
+ return /* @__PURE__ */ jsx(reactNative.View, { className: "flex-row flex-wrap", style: { gap: 8 }, testID: "training-status-page-summary", children: SUMMARY_CARDS.map(({ key, label }) => {
2451
+ const value = key === "total" ? summary.totalWeeklySets : summary.statusCounts[key];
2452
+ return /* @__PURE__ */ jsxs(
2453
+ reactNative.View,
2454
+ {
2455
+ style: {
2456
+ flexGrow: 1,
2457
+ flexBasis: "46%",
2458
+ padding: 12,
2459
+ borderRadius: 10,
2460
+ backgroundColor: SURFACE_ELEVATED2,
2461
+ borderWidth: 1,
2462
+ borderColor: BORDER_DEFAULT2
2463
+ },
2464
+ testID: `training-status-page-summary-${key}`,
2465
+ children: [
2466
+ /* @__PURE__ */ jsx(
2467
+ reactNative.Text,
2468
+ {
2469
+ style: {
2470
+ fontSize: 22,
2471
+ fontFamily: '"Space Grotesk", sans-serif',
2472
+ fontWeight: "700",
2473
+ color: TEXT_PRIMARY4
2474
+ },
2475
+ children: value
2476
+ }
2477
+ ),
2478
+ /* @__PURE__ */ jsx(
2479
+ reactNative.Text,
2480
+ {
2481
+ style: {
2482
+ marginTop: 2,
2483
+ fontSize: 11,
2484
+ fontFamily: "Inter, sans-serif",
2485
+ fontWeight: "600",
2486
+ letterSpacing: 0.4,
2487
+ textTransform: "uppercase",
2488
+ color: TEXT_TERTIARY3
2489
+ },
2490
+ children: label
2491
+ }
2492
+ )
2493
+ ]
2494
+ },
2495
+ key
2496
+ );
2497
+ }) });
2498
+ }
2499
+ function StatusLegend() {
2500
+ return /* @__PURE__ */ jsx(
2501
+ reactNative.View,
2502
+ {
2503
+ className: "flex-row flex-wrap",
2504
+ style: { gap: 10, justifyContent: "center" },
2505
+ testID: "training-status-page-legend",
2506
+ children: STATUS_ORDER.map((status) => /* @__PURE__ */ jsxs(
2507
+ reactNative.View,
2508
+ {
2509
+ className: "flex-row items-center",
2510
+ style: { gap: 5 },
2511
+ testID: `training-status-page-legend-${status}`,
2512
+ children: [
2513
+ /* @__PURE__ */ jsx(
2514
+ reactNative.View,
2515
+ {
2516
+ style: {
2517
+ width: 8,
2518
+ height: 8,
2519
+ borderRadius: 9999,
2520
+ backgroundColor: getHeatmapColor(status, 0.6)
2521
+ },
2522
+ accessibilityElementsHidden: true
2523
+ }
2524
+ ),
2525
+ /* @__PURE__ */ jsx(
2526
+ reactNative.Text,
2527
+ {
2528
+ style: {
2529
+ fontSize: 11,
2530
+ fontFamily: "Inter, sans-serif",
2531
+ color: TEXT_SECONDARY4,
2532
+ textTransform: "capitalize"
2533
+ },
2534
+ children: VOLUME_STATUS_LABELS[status]
2535
+ }
2536
+ )
2537
+ ]
2538
+ },
2539
+ status
2540
+ ))
2541
+ }
2542
+ );
2543
+ }
2544
+ function BodyMapColumn({ side, muscles, selected, onSelect }) {
2545
+ const data = react.useMemo(() => toBodyMapData(muscles, side), [muscles, side]);
2546
+ return /* @__PURE__ */ jsx(reactNative.View, { style: { flexGrow: 1, flexBasis: "46%" }, testID: `training-status-page-bodymap-${side}`, children: /* @__PURE__ */ jsx(
2547
+ BodyMap,
2548
+ {
2549
+ data,
2550
+ view: side,
2551
+ onMusclePress: onSelect,
2552
+ highlightedMuscle: selected,
2553
+ mode: "detailed"
2554
+ }
2555
+ ) });
2556
+ }
2557
+ function TrainingStatusPage({
2558
+ title = "Training Status",
2559
+ meso,
2560
+ muscles,
2561
+ className,
2562
+ ...props
2563
+ }) {
2564
+ const [selected, setSelected] = react.useState(null);
2565
+ const summary = react.useMemo(() => deriveTrainingSummary(muscles), [muscles]);
2566
+ const active = selected != null ? muscles.find((m) => m.muscleGroup === selected) ?? null : null;
2567
+ return /* @__PURE__ */ jsxs(
2568
+ reactNative.View,
2569
+ {
2570
+ className,
2571
+ style: { position: "relative", width: 390, backgroundColor: PAGE_BG },
2572
+ accessibilityRole: "main",
2573
+ "aria-label": title,
2574
+ testID: "training-status-page",
2575
+ ...props,
2576
+ children: [
2577
+ /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 16, gap: 16 }, testID: "training-status-page-content", children: [
2578
+ /* @__PURE__ */ jsx(
2579
+ reactNative.Text,
2580
+ {
2581
+ accessibilityRole: "header",
2582
+ style: {
2583
+ fontSize: 20,
2584
+ fontFamily: '"Space Grotesk", sans-serif',
2585
+ fontWeight: "700",
2586
+ color: TEXT_PRIMARY4
2587
+ },
2588
+ testID: "training-status-page-title",
2589
+ children: title
2590
+ }
2591
+ ),
2592
+ /* @__PURE__ */ jsx(MesoStatusCard, { ...meso }),
2593
+ /* @__PURE__ */ jsx(SummaryCards, { summary }),
2594
+ /* @__PURE__ */ jsxs(
2595
+ reactNative.View,
2596
+ {
2597
+ style: {
2598
+ backgroundColor: SURFACE_ELEVATED2,
2599
+ borderWidth: 1,
2600
+ borderColor: BORDER_DEFAULT2,
2601
+ borderRadius: 12,
2602
+ padding: 12,
2603
+ gap: 12
2604
+ },
2605
+ testID: "training-status-page-bodymaps",
2606
+ children: [
2607
+ /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row", style: { gap: 8 }, children: [
2608
+ /* @__PURE__ */ jsx(
2609
+ BodyMapColumn,
2610
+ {
2611
+ side: "front",
2612
+ muscles,
2613
+ selected,
2614
+ onSelect: setSelected
2615
+ }
2616
+ ),
2617
+ /* @__PURE__ */ jsx(
2618
+ BodyMapColumn,
2619
+ {
2620
+ side: "back",
2621
+ muscles,
2622
+ selected,
2623
+ onSelect: setSelected
2624
+ }
2625
+ )
2626
+ ] }),
2627
+ /* @__PURE__ */ jsx(StatusLegend, {})
2628
+ ]
2629
+ }
2630
+ )
2631
+ ] }),
2632
+ active != null && /* @__PURE__ */ jsx(
2633
+ BodyMapDetailPanel,
2634
+ {
2635
+ muscleGroup: active.muscleGroup,
2636
+ displayName: active.displayName,
2637
+ weeklySets: active.weeklySets,
2638
+ landmarks: active.landmarks,
2639
+ volumeStatus: active.volumeStatus,
2640
+ lastTrained: active.lastTrained,
2641
+ weeklyHistory: active.weeklyHistory,
2642
+ contributingExercises: active.contributingExercises,
2643
+ upcomingExercises: active.upcomingExercises,
2644
+ isOpen: true,
2645
+ onClose: () => setSelected(null)
2646
+ }
2647
+ )
2648
+ ]
2649
+ }
2650
+ );
2651
+ }
2652
+
2653
+ exports.BodyMap = BodyMap;
2654
+ exports.BodyMapDetailPanel = BodyMapDetailPanel;
2655
+ exports.DEFAULT_VOLUME_LANDMARKS = DEFAULT_VOLUME_LANDMARKS;
2656
+ exports.DETAILED_TO_SIMPLE = DETAILED_TO_SIMPLE;
2657
+ exports.MUSCLE_DISPLAY_NAMES = MUSCLE_DISPLAY_NAMES;
2658
+ exports.MUSCLE_TO_CATEGORY = MUSCLE_TO_CATEGORY;
2659
+ exports.MUSCLE_TO_SVG_SLUGS = MUSCLE_TO_SVG_SLUGS;
2660
+ exports.MuscleGroup = MuscleGroup;
2661
+ exports.SIMPLE_DISPLAY_NAMES = SIMPLE_DISPLAY_NAMES;
2662
+ exports.SIMPLE_TO_DETAILED = SIMPLE_TO_DETAILED;
2663
+ exports.SimpleMuscleGroup = SimpleMuscleGroup;
2664
+ exports.TrainingStatusPage = TrainingStatusPage;
2665
+ exports.VOLUME_STATUS_LABELS = VOLUME_STATUS_LABELS;
2666
+ exports.deriveTrainingSummary = deriveTrainingSummary;
2667
+ exports.getHeatmapColor = getHeatmapColor;
2668
+ exports.toBodyMapData = toBodyMapData;
2669
+ //# sourceMappingURL=bodymap.js.map
2670
+ //# sourceMappingURL=bodymap.js.map