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