@titan-design/react-ui 0.9.3 → 0.11.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.
- package/dist/bodymap.js +78 -24
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +78 -24
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +429 -11
- package/dist/index.d.ts +429 -11
- package/dist/index.js +2386 -885
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2306 -836
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1900 -1609
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1901 -1610
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +129 -34
- package/dist/theme/index.d.ts +129 -34
- package/dist/theme/index.js +95 -19
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +92 -20
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +88 -24
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +88 -24
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +8 -3
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Workout/BaseBadge.tsx +5 -5
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/RowInventory.stories.tsx +5 -2
- package/src/components/custom/Workout/S3FullRail.stories.tsx +13 -1
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionHeader.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.stories.tsx +4 -1
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +55 -18
- package/src/components/custom/Workout/SetRow.test.tsx +6 -6
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +16 -25
- package/src/components/custom/Workout/SupersetWrapper.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +3 -7
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +196 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +4 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +2 -2
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/ui/surface/Surface.stories.tsx +195 -1
- package/src/components/ui/surface/Surface.test.tsx +148 -9
- package/src/components/ui/surface/Surface.tsx +37 -9
- package/src/components/ui/surface/SurfaceContext.ts +48 -8
- package/src/components/ui/surface/index.ts +1 -0
- package/src/components/ui/surface/surface.contract.test.ts +283 -0
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +871 -0
- package/src/theme/ColorPrimitives.stories.tsx +300 -0
- package/src/theme/bar-paper.ts +59 -0
- package/src/theme/color-stories.coverage.test.ts +130 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/theme/config.ts +12 -0
- package/src/theme/elevation.ts +90 -67
- package/src/theme/global.css +35 -9
- package/src/theme/tokens/primitives.ts +40 -0
- package/src/theme/tokens/semantic.ts +63 -14
- package/tailwind.config.js +8 -0
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -323
- package/src/theme/Colors.stories.tsx +0 -323
package/dist/pages.js
CHANGED
|
@@ -36,17 +36,27 @@ var primitiveColors = {
|
|
|
36
36
|
// Charcoal scale (dark backgrounds)
|
|
37
37
|
charcoal: {
|
|
38
38
|
0: "#6E6E6E",
|
|
39
|
-
100: "#4C4C4C",
|
|
40
39
|
200: "#3C3C3C",
|
|
41
40
|
300: "#2C2C2C",
|
|
42
41
|
400: "#1F1F1F",
|
|
43
|
-
500: "#1C1C1C",
|
|
44
|
-
600: "#191919",
|
|
45
|
-
700: "#161616",
|
|
46
|
-
900: "#101010"
|
|
47
|
-
},
|
|
42
|
+
500: "#1C1C1C"},
|
|
48
43
|
// Pure colors
|
|
49
44
|
white: "#FFFFFF"};
|
|
45
|
+
var surfaceRampDark = {
|
|
46
|
+
inset: "#13100D",
|
|
47
|
+
// L*4.5 — sub-shell well / pressed
|
|
48
|
+
background: "#1C1916",
|
|
49
|
+
// L*9 — shell / frame
|
|
50
|
+
base: "#252321",
|
|
51
|
+
// L*13.5 — main content plane
|
|
52
|
+
elevated: "#2C2A28",
|
|
53
|
+
// L*17 — nav / rail
|
|
54
|
+
raised: "#31302F",
|
|
55
|
+
// L*20 — cards
|
|
56
|
+
overlay: "#373635"
|
|
57
|
+
// L*22.5 — hero / popover
|
|
58
|
+
};
|
|
59
|
+
var backgroundFrameDark = "#100D0A";
|
|
50
60
|
var primitiveRamps = {
|
|
51
61
|
red: {
|
|
52
62
|
50: "#FFF4F4",
|
|
@@ -171,275 +181,1077 @@ var WORKOUT_TOKENS = {
|
|
|
171
181
|
fill: "#3A3A3A"
|
|
172
182
|
}};
|
|
173
183
|
|
|
174
|
-
// src/
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
// src/theme/tokens/semantic.ts
|
|
185
|
+
var semanticColorsLight = {
|
|
186
|
+
// Brand colors (brand-*)
|
|
187
|
+
"brand-primary": primitiveRamps.orange[400],
|
|
188
|
+
"brand-primary-light": primitiveRamps.orange[300],
|
|
189
|
+
"brand-primary-dark": primitiveRamps.orange[500],
|
|
190
|
+
"brand-primary-subtle": "rgba(255, 121, 0, 0.08)",
|
|
191
|
+
"brand-primary-hover": primitiveRamps.orange[500],
|
|
192
|
+
"brand-primary-active": primitiveRamps.orange[600],
|
|
193
|
+
"brand-secondary": primitiveRamps.cyan[600],
|
|
194
|
+
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
195
|
+
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
196
|
+
"brand-secondary-subtle": "rgba(48, 123, 155, 0.08)",
|
|
197
|
+
"brand-secondary-hover": primitiveRamps.cyan[700],
|
|
198
|
+
"brand-secondary-active": primitiveRamps.cyan[800],
|
|
199
|
+
// Text on brand backgrounds (on-*)
|
|
200
|
+
"on-brand-primary": primitiveColors.white,
|
|
201
|
+
"on-brand-secondary": primitiveColors.white,
|
|
202
|
+
// Status colors (status-*)
|
|
203
|
+
"status-success": primitiveRamps.green[300],
|
|
204
|
+
"status-success-light": primitiveRamps.green[200],
|
|
205
|
+
"status-success-dark": primitiveRamps.green[600],
|
|
206
|
+
"status-success-subtle": primitiveRamps.green[50],
|
|
207
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
208
|
+
"status-live": primitiveRamps.green[300],
|
|
209
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
210
|
+
"status-error": primitiveRamps.red[600],
|
|
211
|
+
"status-error-light": primitiveRamps.red[500],
|
|
212
|
+
"status-error-dark": primitiveRamps.red[700],
|
|
213
|
+
"status-error-subtle": primitiveRamps.red[50],
|
|
214
|
+
"status-error-vivid": primitiveColors.redVivid[500],
|
|
215
|
+
// vivid alert red
|
|
216
|
+
"status-error-vivid-light": primitiveColors.redVivid[400],
|
|
217
|
+
"status-error-vivid-dark": primitiveColors.redVivid[700],
|
|
218
|
+
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
219
|
+
"status-warning": primitiveRamps.amber[300],
|
|
220
|
+
"status-warning-light": primitiveRamps.amber[200],
|
|
221
|
+
"status-warning-dark": primitiveRamps.amber[500],
|
|
222
|
+
"status-warning-subtle": primitiveRamps.amber[50],
|
|
223
|
+
"status-info": primitiveRamps.blue[500],
|
|
224
|
+
"status-info-light": primitiveRamps.blue[300],
|
|
225
|
+
"status-info-dark": primitiveRamps.blue[600],
|
|
226
|
+
"status-info-subtle": primitiveRamps.blue[50],
|
|
227
|
+
// Text on status backgrounds (on-status-*)
|
|
228
|
+
"on-status-success": primitiveColors.white,
|
|
229
|
+
"on-status-error": primitiveColors.white,
|
|
230
|
+
"on-status-warning": primitiveColors.white,
|
|
231
|
+
"on-status-info": primitiveColors.white,
|
|
232
|
+
// Result/outcome indicators (result-*)
|
|
233
|
+
"result-improve": "#4caf50",
|
|
234
|
+
// Green - positive outcome
|
|
235
|
+
"result-improve-light": "rgba(76, 175, 80, 0.12)",
|
|
236
|
+
"result-improve-dark": "#248a24",
|
|
237
|
+
"result-degrade": "#ef5350",
|
|
238
|
+
// Red - negative outcome
|
|
239
|
+
"result-degrade-light": "rgba(239, 83, 80, 0.12)",
|
|
240
|
+
"result-degrade-dark": "#b30000",
|
|
241
|
+
"result-inconclusive": "#9E9A97",
|
|
242
|
+
// Gray - no clear result
|
|
243
|
+
"result-inconclusive-light": "rgba(158, 154, 151, 0.12)",
|
|
244
|
+
"result-neutral": primitiveColors.neutral[500],
|
|
245
|
+
// Neutral baseline
|
|
246
|
+
// Text on result backgrounds (on-result-*)
|
|
247
|
+
"on-result-improve": primitiveColors.white,
|
|
248
|
+
"on-result-degrade": primitiveColors.white,
|
|
249
|
+
"on-result-inconclusive": primitiveColors.white,
|
|
250
|
+
// Data visualization colors (data-*)
|
|
251
|
+
// First 10 colors from discrete rainbow optimized for charts
|
|
252
|
+
"data-1": discreteRainbow[9],
|
|
253
|
+
// Blue
|
|
254
|
+
"data-2": discreteRainbow[14],
|
|
255
|
+
// Green
|
|
256
|
+
"data-3": discreteRainbow[17],
|
|
257
|
+
// Yellow
|
|
258
|
+
"data-4": discreteRainbow[25],
|
|
259
|
+
// Red
|
|
260
|
+
"data-5": discreteRainbow[8],
|
|
261
|
+
// Purple
|
|
262
|
+
"data-6": discreteRainbow[20],
|
|
263
|
+
// Orange
|
|
264
|
+
"data-7": discreteRainbow[13],
|
|
265
|
+
// Light Blue
|
|
266
|
+
"data-8": discreteRainbow[15],
|
|
267
|
+
// Light Green
|
|
268
|
+
"data-9": discreteRainbow[3],
|
|
269
|
+
// Lavender
|
|
270
|
+
"data-10": discreteRainbow[22],
|
|
271
|
+
// Dark Orange
|
|
272
|
+
// Text colors (text-*)
|
|
273
|
+
"text-primary": "#121828",
|
|
274
|
+
"text-secondary": "#65748B",
|
|
275
|
+
"text-tertiary": primitiveColors.neutral[400],
|
|
276
|
+
"text-disabled": "rgba(55, 65, 81, 0.48)",
|
|
277
|
+
"text-inverse": primitiveColors.white,
|
|
278
|
+
"text-link": primitiveColors.blue[600],
|
|
279
|
+
"text-link-hover": primitiveColors.blue[700],
|
|
280
|
+
// Surface colors (surface-*) - for elevated containers
|
|
281
|
+
"surface-base": primitiveColors.white,
|
|
282
|
+
"surface-elevated": primitiveColors.neutral[50],
|
|
283
|
+
// slightly off-white for elevated cards
|
|
284
|
+
"surface-raised": primitiveColors.neutral[100],
|
|
285
|
+
// light gray for raised cards
|
|
286
|
+
"surface-overlay": primitiveColors.white,
|
|
287
|
+
"surface-input": primitiveColors.neutral[50],
|
|
288
|
+
// Input field background (filled variant)
|
|
289
|
+
// Deepest pressed pit (TD-surface-tokens, S-3) — light-mode counterpart of
|
|
290
|
+
// the dark ramp's `inset` plane; a placeholder pairing, not part of the
|
|
291
|
+
// dark-ramp investigation (surface.contract.test.ts is dark-mode only).
|
|
292
|
+
"surface-inset": primitiveColors.neutral[300],
|
|
293
|
+
// Background colors (background-*)
|
|
294
|
+
"background-base": "#EBEBEB",
|
|
295
|
+
"background-default": primitiveColors.white,
|
|
296
|
+
"background-subtle": primitiveColors.neutral[50],
|
|
297
|
+
// Frame/bezel chrome (TD-surface-tokens, S-3) — top bar + side nav shell,
|
|
298
|
+
// one step below `background-base` (darker still than `surface-inset`
|
|
299
|
+
// above, mirroring the dark-mode ordering). Placeholder pairing for light
|
|
300
|
+
// mode (see dark-mode note on `surface-inset` above).
|
|
301
|
+
"background-frame": primitiveColors.neutral[400],
|
|
302
|
+
// Border colors (border-*)
|
|
303
|
+
"border-default": "#E8E9EB",
|
|
304
|
+
"border-subtle": primitiveColors.neutral[100],
|
|
305
|
+
"border-strong": primitiveColors.neutral[300],
|
|
306
|
+
"border-prominent": primitiveColors.neutral[400],
|
|
307
|
+
// high-visibility divider
|
|
308
|
+
"border-focus": primitiveColors.blue[600],
|
|
309
|
+
"border-input": primitiveColors.neutral[300],
|
|
310
|
+
// Input field border
|
|
311
|
+
"border-input-hover": primitiveColors.neutral[400],
|
|
312
|
+
// Input field border on hover
|
|
313
|
+
"border-input-focus": primitiveColors.blue[600],
|
|
314
|
+
// Input field border on focus
|
|
315
|
+
"border-input-error": primitiveColors.red[600],
|
|
316
|
+
// Input field border on error
|
|
317
|
+
// Alpha hairline separators (surface-independent — composite toward black on
|
|
318
|
+
// light surfaces, mirroring the dark-mode white-alpha family). See §4/S-2.
|
|
319
|
+
"hairline-subtle": "rgba(0, 0, 0, 0.06)",
|
|
320
|
+
"hairline-default": "rgba(0, 0, 0, 0.09)",
|
|
321
|
+
"hairline-strong": "rgba(0, 0, 0, 0.14)",
|
|
322
|
+
// Interactive states (interactive-*)
|
|
323
|
+
"interactive-hover": "rgba(55, 65, 81, 0.04)",
|
|
324
|
+
"interactive-focus": "rgba(55, 65, 81, 0.12)",
|
|
325
|
+
"interactive-active": "rgba(55, 65, 81, 0.16)",
|
|
326
|
+
"interactive-selected": "rgba(55, 65, 81, 0.08)",
|
|
327
|
+
"interactive-disabled": "rgba(55, 65, 81, 0.12)",
|
|
328
|
+
"interactive-disabled-text": "rgba(55, 65, 81, 0.26)",
|
|
329
|
+
// Divider
|
|
330
|
+
"divider": "#E8E9EB",
|
|
331
|
+
// Avatar default
|
|
332
|
+
"avatar-background": primitiveColors.neutral[500],
|
|
333
|
+
"avatar-text": primitiveColors.white
|
|
334
|
+
};
|
|
335
|
+
var semanticColorsDark = {
|
|
336
|
+
// Brand colors stay the same in dark mode
|
|
337
|
+
"brand-primary": primitiveRamps.orange[400],
|
|
338
|
+
"brand-primary-light": primitiveRamps.orange[300],
|
|
339
|
+
"brand-primary-dark": primitiveRamps.orange[500],
|
|
340
|
+
"brand-primary-subtle": "rgba(255, 121, 0, 0.12)",
|
|
341
|
+
"brand-primary-hover": primitiveRamps.orange[300],
|
|
342
|
+
"brand-primary-active": primitiveRamps.orange[200],
|
|
343
|
+
"brand-secondary": primitiveRamps.cyan[600],
|
|
344
|
+
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
345
|
+
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
346
|
+
"brand-secondary-subtle": "rgba(48, 123, 155, 0.12)",
|
|
347
|
+
"brand-secondary-hover": primitiveRamps.cyan[500],
|
|
348
|
+
"brand-secondary-active": primitiveRamps.cyan[400],
|
|
349
|
+
// Text on brand backgrounds
|
|
350
|
+
"on-brand-primary": primitiveColors.white,
|
|
351
|
+
"on-brand-secondary": primitiveColors.white,
|
|
352
|
+
// Status colors
|
|
353
|
+
"status-success": primitiveRamps.green[300],
|
|
354
|
+
"status-success-light": primitiveRamps.green[200],
|
|
355
|
+
"status-success-dark": primitiveRamps.green[600],
|
|
356
|
+
"status-success-subtle": "rgba(46, 213, 115, 0.12)",
|
|
357
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
358
|
+
"status-live": primitiveRamps.green[300],
|
|
359
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
360
|
+
"status-error": primitiveRamps.red[600],
|
|
361
|
+
"status-error-light": primitiveRamps.red[500],
|
|
362
|
+
"status-error-dark": primitiveRamps.red[700],
|
|
363
|
+
"status-error-subtle": "rgba(209, 67, 67, 0.12)",
|
|
364
|
+
"status-error-vivid": primitiveColors.redVivid[500],
|
|
365
|
+
"status-error-vivid-light": primitiveColors.redVivid[400],
|
|
366
|
+
"status-error-vivid-dark": primitiveColors.redVivid[700],
|
|
367
|
+
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
368
|
+
"status-warning": primitiveRamps.amber[300],
|
|
369
|
+
"status-warning-light": primitiveRamps.amber[200],
|
|
370
|
+
"status-warning-dark": primitiveRamps.amber[500],
|
|
371
|
+
"status-warning-subtle": "rgba(249, 180, 21, 0.12)",
|
|
372
|
+
"status-info": primitiveRamps.blue[500],
|
|
373
|
+
"status-info-light": primitiveRamps.blue[300],
|
|
374
|
+
"status-info-dark": primitiveRamps.blue[600],
|
|
375
|
+
"status-info-subtle": "rgba(33, 150, 243, 0.12)",
|
|
376
|
+
// Text on status backgrounds
|
|
377
|
+
"on-status-success": primitiveColors.white,
|
|
378
|
+
"on-status-error": primitiveColors.white,
|
|
379
|
+
"on-status-warning": primitiveColors.white,
|
|
380
|
+
"on-status-info": primitiveColors.white,
|
|
381
|
+
// Result/outcome indicators (result-*)
|
|
382
|
+
"result-improve": "#4caf50",
|
|
383
|
+
"result-improve-light": "rgba(76, 175, 80, 0.16)",
|
|
384
|
+
"result-improve-dark": "#248a24",
|
|
385
|
+
"result-degrade": "#ef5350",
|
|
386
|
+
"result-degrade-light": "rgba(239, 83, 80, 0.16)",
|
|
387
|
+
"result-degrade-dark": "#b30000",
|
|
388
|
+
"result-inconclusive": "#9E9A97",
|
|
389
|
+
"result-inconclusive-light": "rgba(158, 154, 151, 0.16)",
|
|
390
|
+
"result-neutral": primitiveColors.neutral[400],
|
|
391
|
+
// Text on result backgrounds
|
|
392
|
+
"on-result-improve": primitiveColors.white,
|
|
393
|
+
"on-result-degrade": primitiveColors.white,
|
|
394
|
+
"on-result-inconclusive": primitiveColors.white,
|
|
395
|
+
// Data visualization colors (same in dark mode for consistency)
|
|
396
|
+
"data-1": discreteRainbow[9],
|
|
397
|
+
"data-2": discreteRainbow[14],
|
|
398
|
+
"data-3": discreteRainbow[17],
|
|
399
|
+
"data-4": discreteRainbow[25],
|
|
400
|
+
"data-5": discreteRainbow[8],
|
|
401
|
+
"data-6": discreteRainbow[20],
|
|
402
|
+
"data-7": discreteRainbow[13],
|
|
403
|
+
"data-8": discreteRainbow[15],
|
|
404
|
+
"data-9": discreteRainbow[3],
|
|
405
|
+
"data-10": discreteRainbow[22],
|
|
406
|
+
// Text colors - inverted for dark mode
|
|
407
|
+
"text-primary": primitiveColors.neutral[100],
|
|
408
|
+
"text-secondary": primitiveColors.neutral[400],
|
|
409
|
+
"text-tertiary": primitiveColors.neutral[500],
|
|
410
|
+
"text-disabled": "rgba(255, 255, 255, 0.38)",
|
|
411
|
+
"text-inverse": primitiveColors.neutral[900],
|
|
412
|
+
"text-link": "#828DF8",
|
|
413
|
+
"text-link-hover": primitiveColors.blue[400],
|
|
414
|
+
// Surface colors - dark backgrounds — warm-tapered DERIVED ramp (TD-surface-tokens,
|
|
415
|
+
// S-1, re-spaced S-3). Shipped verbatim from `deriveSurfaceRamp()` — see
|
|
416
|
+
// `surfaceRampDark` in primitives.ts for the full derivation note. `surface-overlay`
|
|
417
|
+
// and `surface-elevated` are distinct (were both #191919 pre-S-1); `surface-input`
|
|
418
|
+
// tracks one plane above `surface-base`, same relative position as before the remap.
|
|
419
|
+
// `background-base` backs `Surface level="background"` (SurfaceContext.SURFACE_LEVEL_TOKEN),
|
|
420
|
+
// so it takes the ramp's `background` (shell/frame) role, NOT `inset`. The ramp's
|
|
421
|
+
// deepest `inset` plane is now promoted to `surface-inset` below (S-3) — still no
|
|
422
|
+
// `SurfaceLevel` member for it (follow-up, alongside the elevation.ts -1/-2 pressed
|
|
423
|
+
// levels — parallel workstream, not this branch).
|
|
424
|
+
"surface-base": surfaceRampDark.base,
|
|
425
|
+
// main surface (#252321, L*13.5)
|
|
426
|
+
"surface-elevated": surfaceRampDark.elevated,
|
|
427
|
+
// elevated surface (#2C2A28, L*17 — nav/rail)
|
|
428
|
+
"surface-raised": surfaceRampDark.raised,
|
|
429
|
+
// raised surface (#31302F, L*20 — cards)
|
|
430
|
+
"surface-overlay": surfaceRampDark.overlay,
|
|
431
|
+
// overlay surface (#373635, L*22.5 — hero/popover)
|
|
432
|
+
"surface-input": surfaceRampDark.elevated,
|
|
433
|
+
// input surface (#2C2A28 — one plane above base)
|
|
434
|
+
"surface-inset": surfaceRampDark.inset,
|
|
435
|
+
// deepest pressed pit (#13100D, L*4.5 — sub-shell well)
|
|
436
|
+
// Background colors — same ramp, the frame/shell end of it.
|
|
437
|
+
"background-base": surfaceRampDark.background,
|
|
438
|
+
// shell / frame (#1C1916, L*9 — Surface level="background")
|
|
439
|
+
"background-default": surfaceRampDark.base,
|
|
440
|
+
// main background (#252321, L*13.5 — matches surface-base)
|
|
441
|
+
"background-subtle": surfaceRampDark.elevated,
|
|
442
|
+
// subtle background (#2C2A28, L*17 — matches surface-elevated)
|
|
443
|
+
// Frame/bezel chrome (S-3) — the top bar + side nav shell; one step BELOW
|
|
444
|
+
// `background-base`, darker even than `surface-inset` — it exits the content
|
|
445
|
+
// ramp entirely rather than sitting in it. See `backgroundFrameDark` in
|
|
446
|
+
// primitives.ts.
|
|
447
|
+
"background-frame": backgroundFrameDark,
|
|
448
|
+
// frame / bezel (#100D0A, L*3.79)
|
|
449
|
+
// Border colors — `border-subtle` (#1C1C1C) previously collided with
|
|
450
|
+
// `surface-raised` (also #1C1C1C, an invisible border on raised cards). The
|
|
451
|
+
// re-spaced ramp above moved `surface-raised` to #2D2C2B, so this value is now
|
|
452
|
+
// distinct from every surface hex without needing to change it — see
|
|
453
|
+
// `surface.contract.test.ts` R2/R4.
|
|
454
|
+
"border-default": primitiveColors.charcoal[400],
|
|
455
|
+
// default border
|
|
456
|
+
"border-subtle": primitiveColors.charcoal[500],
|
|
457
|
+
// subtle border
|
|
458
|
+
"border-strong": primitiveColors.charcoal[300],
|
|
459
|
+
// strong border
|
|
460
|
+
"border-prominent": primitiveColors.charcoal[200],
|
|
461
|
+
// high-visibility divider
|
|
462
|
+
"border-focus": "#828DF8",
|
|
463
|
+
"border-input": primitiveColors.neutral[600],
|
|
464
|
+
"border-input-hover": primitiveColors.neutral[500],
|
|
465
|
+
"border-input-focus": "#828DF8",
|
|
466
|
+
"border-input-error": primitiveColors.red[500],
|
|
467
|
+
// Alpha hairline separators — the primary separation cue (§4/S-2). Self-
|
|
468
|
+
// normalizing: composites toward white by ~the same amount on ANY plane, so
|
|
469
|
+
// one family works at every elevation instead of per-surface border tokens.
|
|
470
|
+
// Shadows are demoted to floating-overlay use only (not shipped as a fill
|
|
471
|
+
// separator here — see elevation.ts, follow-up S-4).
|
|
472
|
+
"hairline-subtle": "rgba(255, 255, 255, 0.06)",
|
|
473
|
+
"hairline-default": "rgba(255, 255, 255, 0.09)",
|
|
474
|
+
"hairline-strong": "rgba(255, 255, 255, 0.14)",
|
|
475
|
+
// Interactive states
|
|
476
|
+
"interactive-hover": "rgba(255, 255, 255, 0.04)",
|
|
477
|
+
"interactive-focus": "rgba(255, 255, 255, 0.12)",
|
|
478
|
+
"interactive-active": "rgba(255, 255, 255, 0.16)",
|
|
479
|
+
"interactive-selected": "rgba(255, 255, 255, 0.08)",
|
|
480
|
+
"interactive-disabled": "rgba(255, 255, 255, 0.12)",
|
|
481
|
+
"interactive-disabled-text": "rgba(255, 255, 255, 0.26)",
|
|
482
|
+
// Divider
|
|
483
|
+
"divider": primitiveColors.charcoal[400],
|
|
484
|
+
// divider color
|
|
485
|
+
// Avatar default
|
|
486
|
+
"avatar-background": primitiveColors.neutral[600],
|
|
487
|
+
"avatar-text": primitiveColors.white
|
|
488
|
+
};
|
|
489
|
+
function getSemanticColors(mode) {
|
|
490
|
+
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
183
491
|
}
|
|
184
|
-
function
|
|
185
|
-
|
|
492
|
+
function hexToRgb(hex) {
|
|
493
|
+
const cleaned = hex.replace("#", "");
|
|
494
|
+
let r, g, b;
|
|
495
|
+
if (cleaned.length === 3) {
|
|
496
|
+
r = parseInt(cleaned[0] + cleaned[0], 16);
|
|
497
|
+
g = parseInt(cleaned[1] + cleaned[1], 16);
|
|
498
|
+
b = parseInt(cleaned[2] + cleaned[2], 16);
|
|
499
|
+
} else if (cleaned.length === 6) {
|
|
500
|
+
r = parseInt(cleaned.slice(0, 2), 16);
|
|
501
|
+
g = parseInt(cleaned.slice(2, 4), 16);
|
|
502
|
+
b = parseInt(cleaned.slice(4, 6), 16);
|
|
503
|
+
} else {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
return { r, g, b };
|
|
186
507
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
508
|
+
function rgbToHsv(r, g, b) {
|
|
509
|
+
const rNorm = r / 255;
|
|
510
|
+
const gNorm = g / 255;
|
|
511
|
+
const bNorm = b / 255;
|
|
512
|
+
const max = Math.max(rNorm, gNorm, bNorm);
|
|
513
|
+
const min = Math.min(rNorm, gNorm, bNorm);
|
|
514
|
+
const delta = max - min;
|
|
515
|
+
const v = max;
|
|
516
|
+
const s = max === 0 ? 0 : delta / max;
|
|
517
|
+
let h = 0;
|
|
518
|
+
if (delta !== 0) {
|
|
519
|
+
if (max === rNorm) {
|
|
520
|
+
h = 60 * ((gNorm - bNorm) / delta % 6);
|
|
521
|
+
} else if (max === gNorm) {
|
|
522
|
+
h = 60 * ((bNorm - rNorm) / delta + 2);
|
|
523
|
+
} else {
|
|
524
|
+
h = 60 * ((rNorm - gNorm) / delta + 4);
|
|
198
525
|
}
|
|
199
|
-
|
|
526
|
+
}
|
|
527
|
+
if (h < 0) h += 360;
|
|
528
|
+
return { h, s, v };
|
|
529
|
+
}
|
|
530
|
+
function hsvToRgb(h, s, v) {
|
|
531
|
+
const c = v * s;
|
|
532
|
+
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
533
|
+
const m = v - c;
|
|
534
|
+
let rPrime, gPrime, bPrime;
|
|
535
|
+
if (h >= 0 && h < 60) {
|
|
536
|
+
rPrime = c;
|
|
537
|
+
gPrime = x;
|
|
538
|
+
bPrime = 0;
|
|
539
|
+
} else if (h >= 60 && h < 120) {
|
|
540
|
+
rPrime = x;
|
|
541
|
+
gPrime = c;
|
|
542
|
+
bPrime = 0;
|
|
543
|
+
} else if (h >= 120 && h < 180) {
|
|
544
|
+
rPrime = 0;
|
|
545
|
+
gPrime = c;
|
|
546
|
+
bPrime = x;
|
|
547
|
+
} else if (h >= 180 && h < 240) {
|
|
548
|
+
rPrime = 0;
|
|
549
|
+
gPrime = x;
|
|
550
|
+
bPrime = c;
|
|
551
|
+
} else if (h >= 240 && h < 300) {
|
|
552
|
+
rPrime = x;
|
|
553
|
+
gPrime = 0;
|
|
554
|
+
bPrime = c;
|
|
555
|
+
} else {
|
|
556
|
+
rPrime = c;
|
|
557
|
+
gPrime = 0;
|
|
558
|
+
bPrime = x;
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
r: Math.round((rPrime + m) * 255),
|
|
562
|
+
g: Math.round((gPrime + m) * 255),
|
|
563
|
+
b: Math.round((bPrime + m) * 255)
|
|
200
564
|
};
|
|
201
565
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
var SEGMENTED_BAR_GAP = 5;
|
|
209
|
-
function usePulse(active) {
|
|
210
|
-
const [value] = react.useState(() => new reactNative.Animated.Value(0));
|
|
211
|
-
react.useEffect(() => {
|
|
212
|
-
if (!active) return;
|
|
213
|
-
const loop = reactNative.Animated.loop(
|
|
214
|
-
reactNative.Animated.sequence([
|
|
215
|
-
reactNative.Animated.timing(value, {
|
|
216
|
-
toValue: 1,
|
|
217
|
-
duration: PULSE_HALF_MS,
|
|
218
|
-
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
219
|
-
useNativeDriver: false
|
|
220
|
-
}),
|
|
221
|
-
reactNative.Animated.timing(value, {
|
|
222
|
-
toValue: 0,
|
|
223
|
-
duration: PULSE_HALF_MS,
|
|
224
|
-
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
225
|
-
useNativeDriver: false
|
|
226
|
-
})
|
|
227
|
-
])
|
|
228
|
-
);
|
|
229
|
-
loop.start();
|
|
230
|
-
return () => loop.stop();
|
|
231
|
-
}, [active, value]);
|
|
232
|
-
return value;
|
|
566
|
+
function rgbToHex(r, g, b) {
|
|
567
|
+
const toHex = (n) => {
|
|
568
|
+
const clamped = Math.max(0, Math.min(255, Math.round(n)));
|
|
569
|
+
return clamped.toString(16).padStart(2, "0");
|
|
570
|
+
};
|
|
571
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
233
572
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
style,
|
|
242
|
-
...props
|
|
243
|
-
}) {
|
|
244
|
-
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
245
|
-
return /* @__PURE__ */ jsxs(reactNative.View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
246
|
-
segments.map((seg, i) => {
|
|
247
|
-
const fillStyle = {
|
|
248
|
-
width: `${(seg.fill ?? 1) * 100}%`,
|
|
249
|
-
height: "100%",
|
|
250
|
-
backgroundColor: seg.color,
|
|
251
|
-
opacity: seg.opacity
|
|
252
|
-
};
|
|
253
|
-
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
254
|
-
return /* @__PURE__ */ jsx(
|
|
255
|
-
reactNative.View,
|
|
256
|
-
{
|
|
257
|
-
style: {
|
|
258
|
-
flex: seg.weight ?? 1,
|
|
259
|
-
minWidth: 0,
|
|
260
|
-
height: "100%",
|
|
261
|
-
marginLeft: seg.leadingGap,
|
|
262
|
-
borderRadius: radius,
|
|
263
|
-
overflow: "hidden"
|
|
264
|
-
},
|
|
265
|
-
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
266
|
-
reactNative.Animated.View,
|
|
267
|
-
{
|
|
268
|
-
style: {
|
|
269
|
-
...fillStyle,
|
|
270
|
-
...seg.pulseColor ? {
|
|
271
|
-
backgroundColor: pulse.interpolate({
|
|
272
|
-
inputRange: [0, 1],
|
|
273
|
-
outputRange: seg.pulseColor
|
|
274
|
-
})
|
|
275
|
-
} : {
|
|
276
|
-
opacity: pulse.interpolate({
|
|
277
|
-
inputRange: [0, 1],
|
|
278
|
-
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
accessibilityElementsHidden: true,
|
|
283
|
-
testID
|
|
284
|
-
}
|
|
285
|
-
) : /* @__PURE__ */ jsx(reactNative.View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
286
|
-
},
|
|
287
|
-
i
|
|
288
|
-
);
|
|
289
|
-
}),
|
|
290
|
-
marker ? /* @__PURE__ */ jsx(
|
|
291
|
-
reactNative.View,
|
|
292
|
-
{
|
|
293
|
-
style: {
|
|
294
|
-
position: "absolute",
|
|
295
|
-
left: `${marker.position * 100}%`,
|
|
296
|
-
top: 0,
|
|
297
|
-
bottom: 0,
|
|
298
|
-
width: 2,
|
|
299
|
-
backgroundColor: marker.color
|
|
300
|
-
},
|
|
301
|
-
accessibilityElementsHidden: true,
|
|
302
|
-
testID: "segmented-bar-marker"
|
|
303
|
-
}
|
|
304
|
-
) : null
|
|
305
|
-
] });
|
|
573
|
+
function lighten(hex, amount = 0.1) {
|
|
574
|
+
const rgb = hexToRgb(hex);
|
|
575
|
+
if (!rgb) return hex;
|
|
576
|
+
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
577
|
+
const newV = Math.min(1, hsv.v + amount);
|
|
578
|
+
const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
|
|
579
|
+
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
306
580
|
}
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
581
|
+
var shadowColors = {
|
|
582
|
+
// For charcoal surfaces (~#2C2C2C to #3C3C3C)
|
|
583
|
+
charcoal: {
|
|
584
|
+
dark: "rgba(0, 0, 0, 0.5)",
|
|
585
|
+
light: "rgba(255, 255, 255, 0.12)",
|
|
586
|
+
// brighter for crisp rim light
|
|
587
|
+
darker: "rgba(0, 0, 0, 0.6)"
|
|
588
|
+
},
|
|
589
|
+
// For neutral dark surfaces (~#1F2937)
|
|
590
|
+
neutralDark: {
|
|
591
|
+
dark: "rgba(0, 0, 0, 0.4)",
|
|
592
|
+
light: "rgba(255, 255, 255, 0.08)",
|
|
593
|
+
darker: "rgba(0, 0, 0, 0.5)"
|
|
594
|
+
},
|
|
595
|
+
// For light surfaces (white/light gray backgrounds)
|
|
596
|
+
neutralLight: {
|
|
597
|
+
dark: "rgba(0, 0, 0, 0.15)",
|
|
598
|
+
// Visible dark shadow for depth on light backgrounds
|
|
599
|
+
light: "rgba(255, 255, 255, 1)",
|
|
600
|
+
// Bright highlight for raised effect (visible on light gray)
|
|
601
|
+
darker: "rgba(0, 0, 0, 0.2)"
|
|
602
|
+
// Deeper shadow for pressed states
|
|
603
|
+
}
|
|
314
604
|
};
|
|
315
|
-
var
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
var PULSE_RANGE = {
|
|
323
|
-
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
324
|
-
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
325
|
-
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
326
|
-
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
605
|
+
var intensityConfig = {
|
|
606
|
+
subtle: { offset: 1, blur: 2 },
|
|
607
|
+
// very light, barely visible
|
|
608
|
+
medium: { offset: 2, blur: 4 },
|
|
609
|
+
// noticeable but restrained
|
|
610
|
+
strong: { offset: 2, blur: 5 }
|
|
611
|
+
// slightly deeper blur, same offset as medium
|
|
327
612
|
};
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
613
|
+
function createRaisedShadow(surface = "charcoal", intensity = "medium") {
|
|
614
|
+
const colors = shadowColors[surface];
|
|
615
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
616
|
+
const rimBlur = 1;
|
|
617
|
+
return reactNative.Platform.select({
|
|
618
|
+
web: {
|
|
619
|
+
boxShadow: `${offset}px ${offset}px ${blur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
620
|
+
},
|
|
621
|
+
default: {
|
|
622
|
+
// React Native shadow approximation (limited compared to web)
|
|
623
|
+
shadowColor: "#000",
|
|
624
|
+
shadowOffset: { width: offset, height: offset },
|
|
625
|
+
shadowOpacity: 0.3,
|
|
626
|
+
shadowRadius: blur / 2,
|
|
627
|
+
elevation: offset
|
|
628
|
+
}
|
|
629
|
+
});
|
|
341
630
|
}
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
631
|
+
function createPressedShadow(surface = "charcoal", intensity = "medium") {
|
|
632
|
+
const colors = shadowColors[surface];
|
|
633
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
634
|
+
const rimOffset = 1;
|
|
635
|
+
const rimBlur = 1;
|
|
636
|
+
return reactNative.Platform.select({
|
|
637
|
+
web: {
|
|
638
|
+
boxShadow: `inset ${offset}px ${offset}px ${blur}px ${colors.darker}, inset -${rimOffset}px -${rimOffset}px ${rimBlur}px ${colors.light}`
|
|
639
|
+
},
|
|
640
|
+
default: {
|
|
641
|
+
// React Native doesn't support inset shadows, approximate with darker bg
|
|
642
|
+
shadowOpacity: 0,
|
|
643
|
+
elevation: 0
|
|
644
|
+
}
|
|
346
645
|
});
|
|
347
646
|
}
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
)
|
|
358
|
-
return [...activation, ...trail];
|
|
647
|
+
function createFlatShadow() {
|
|
648
|
+
return reactNative.Platform.select({
|
|
649
|
+
web: {
|
|
650
|
+
boxShadow: "none"
|
|
651
|
+
},
|
|
652
|
+
default: {
|
|
653
|
+
shadowOpacity: 0,
|
|
654
|
+
elevation: 0
|
|
655
|
+
}
|
|
656
|
+
});
|
|
359
657
|
}
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
658
|
+
function createRaisedHoverShadow(surface = "charcoal", intensity = "medium") {
|
|
659
|
+
const colors = shadowColors[surface];
|
|
660
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
661
|
+
const hoverOffset = Math.max(1, Math.round(offset / 2));
|
|
662
|
+
const hoverBlur = Math.max(1, Math.round(blur / 2));
|
|
663
|
+
const rimBlur = 1;
|
|
664
|
+
return reactNative.Platform.select({
|
|
665
|
+
web: {
|
|
666
|
+
// Keep same offsets as normal state for rim to prevent "shrinking" effect
|
|
667
|
+
boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
668
|
+
},
|
|
669
|
+
default: {
|
|
670
|
+
shadowColor: "#000",
|
|
671
|
+
shadowOffset: { width: hoverOffset, height: hoverOffset },
|
|
672
|
+
shadowOpacity: 0.2,
|
|
673
|
+
shadowRadius: hoverBlur / 2,
|
|
674
|
+
elevation: hoverOffset
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function createPressedHoverShadow(surface = "charcoal", intensity = "medium") {
|
|
679
|
+
const colors = shadowColors[surface];
|
|
680
|
+
const { offset, blur } = intensityConfig[intensity];
|
|
681
|
+
const hoverOffset = Math.max(1, Math.round(offset / 2));
|
|
682
|
+
const hoverBlur = Math.max(1, Math.round(blur / 2));
|
|
683
|
+
const rimBlur = 1;
|
|
684
|
+
return reactNative.Platform.select({
|
|
685
|
+
web: {
|
|
686
|
+
// Keep rim offset at 1px (same as pressed normal) to prevent "shrinking" effect
|
|
687
|
+
boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.darker}, inset -1px -1px ${rimBlur}px ${colors.light}`
|
|
688
|
+
},
|
|
689
|
+
default: {
|
|
690
|
+
shadowOpacity: 0,
|
|
691
|
+
elevation: 0
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
({
|
|
696
|
+
// Charcoal surface shadows (for dark toolbars, cards)
|
|
697
|
+
charcoal: {
|
|
698
|
+
raised: {
|
|
699
|
+
subtle: createRaisedShadow("charcoal", "subtle"),
|
|
700
|
+
medium: createRaisedShadow("charcoal", "medium"),
|
|
701
|
+
strong: createRaisedShadow("charcoal", "strong")
|
|
702
|
+
},
|
|
703
|
+
raisedHover: {
|
|
704
|
+
subtle: createRaisedHoverShadow("charcoal", "subtle"),
|
|
705
|
+
medium: createRaisedHoverShadow("charcoal", "medium"),
|
|
706
|
+
strong: createRaisedHoverShadow("charcoal", "strong")
|
|
707
|
+
},
|
|
708
|
+
pressed: {
|
|
709
|
+
subtle: createPressedShadow("charcoal", "subtle"),
|
|
710
|
+
medium: createPressedShadow("charcoal", "medium"),
|
|
711
|
+
strong: createPressedShadow("charcoal", "strong")
|
|
712
|
+
},
|
|
713
|
+
pressedHover: {
|
|
714
|
+
subtle: createPressedHoverShadow("charcoal", "subtle"),
|
|
715
|
+
medium: createPressedHoverShadow("charcoal", "medium"),
|
|
716
|
+
strong: createPressedHoverShadow("charcoal", "strong")
|
|
717
|
+
},
|
|
718
|
+
flat: createFlatShadow()
|
|
719
|
+
},
|
|
720
|
+
// Neutral dark surface shadows
|
|
721
|
+
neutralDark: {
|
|
722
|
+
raised: {
|
|
723
|
+
subtle: createRaisedShadow("neutralDark", "subtle"),
|
|
724
|
+
medium: createRaisedShadow("neutralDark", "medium"),
|
|
725
|
+
strong: createRaisedShadow("neutralDark", "strong")
|
|
726
|
+
},
|
|
727
|
+
raisedHover: {
|
|
728
|
+
subtle: createRaisedHoverShadow("neutralDark", "subtle"),
|
|
729
|
+
medium: createRaisedHoverShadow("neutralDark", "medium"),
|
|
730
|
+
strong: createRaisedHoverShadow("neutralDark", "strong")
|
|
731
|
+
},
|
|
732
|
+
pressed: {
|
|
733
|
+
subtle: createPressedShadow("neutralDark", "subtle"),
|
|
734
|
+
medium: createPressedShadow("neutralDark", "medium"),
|
|
735
|
+
strong: createPressedShadow("neutralDark", "strong")
|
|
736
|
+
},
|
|
737
|
+
pressedHover: {
|
|
738
|
+
subtle: createPressedHoverShadow("neutralDark", "subtle"),
|
|
739
|
+
medium: createPressedHoverShadow("neutralDark", "medium"),
|
|
740
|
+
strong: createPressedHoverShadow("neutralDark", "strong")
|
|
741
|
+
},
|
|
742
|
+
flat: createFlatShadow()
|
|
743
|
+
},
|
|
744
|
+
// Neutral light surface shadows
|
|
745
|
+
neutralLight: {
|
|
746
|
+
raised: {
|
|
747
|
+
subtle: createRaisedShadow("neutralLight", "subtle"),
|
|
748
|
+
medium: createRaisedShadow("neutralLight", "medium"),
|
|
749
|
+
strong: createRaisedShadow("neutralLight", "strong")
|
|
750
|
+
},
|
|
751
|
+
raisedHover: {
|
|
752
|
+
subtle: createRaisedHoverShadow("neutralLight", "subtle"),
|
|
753
|
+
medium: createRaisedHoverShadow("neutralLight", "medium"),
|
|
754
|
+
strong: createRaisedHoverShadow("neutralLight", "strong")
|
|
755
|
+
},
|
|
756
|
+
pressed: {
|
|
757
|
+
subtle: createPressedShadow("neutralLight", "subtle"),
|
|
758
|
+
medium: createPressedShadow("neutralLight", "medium"),
|
|
759
|
+
strong: createPressedShadow("neutralLight", "strong")
|
|
760
|
+
},
|
|
761
|
+
pressedHover: {
|
|
762
|
+
subtle: createPressedHoverShadow("neutralLight", "subtle"),
|
|
763
|
+
medium: createPressedHoverShadow("neutralLight", "medium"),
|
|
764
|
+
strong: createPressedHoverShadow("neutralLight", "strong")
|
|
765
|
+
},
|
|
766
|
+
flat: createFlatShadow()
|
|
369
767
|
}
|
|
370
|
-
|
|
371
|
-
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// src/utils/colors.ts
|
|
771
|
+
function alpha(color, opacity) {
|
|
772
|
+
const clampedOpacity = Math.max(0, Math.min(1, opacity));
|
|
773
|
+
if (color.startsWith("#")) {
|
|
774
|
+
const hex = color.slice(1);
|
|
775
|
+
let r, g, b;
|
|
776
|
+
if (hex.length === 3) {
|
|
777
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
778
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
779
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
780
|
+
} else if (hex.length === 6) {
|
|
781
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
782
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
783
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
784
|
+
} else {
|
|
785
|
+
return color;
|
|
786
|
+
}
|
|
787
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
372
788
|
}
|
|
373
|
-
|
|
374
|
-
|
|
789
|
+
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
790
|
+
if (rgbMatch) {
|
|
791
|
+
const [, r, g, b] = rgbMatch;
|
|
792
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
375
793
|
}
|
|
376
|
-
|
|
377
|
-
|
|
794
|
+
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
795
|
+
if (rgbaMatch) {
|
|
796
|
+
const [, r, g, b] = rgbaMatch;
|
|
797
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
378
798
|
}
|
|
379
|
-
|
|
380
|
-
const color = velocityZoneColor(v);
|
|
381
|
-
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
382
|
-
});
|
|
383
|
-
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
384
|
-
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
799
|
+
return color;
|
|
385
800
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return
|
|
801
|
+
|
|
802
|
+
// src/utils/workout-format.ts
|
|
803
|
+
function roundRpe(rpe) {
|
|
804
|
+
return Math.round(rpe * 2) / 2;
|
|
390
805
|
}
|
|
391
|
-
function
|
|
392
|
-
return
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
segmentTestID: setBarSegmentTestID,
|
|
400
|
-
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
401
|
-
testID: "set-strip-set"
|
|
402
|
-
}
|
|
403
|
-
);
|
|
806
|
+
function roundWeight(weight) {
|
|
807
|
+
return Math.round(weight);
|
|
808
|
+
}
|
|
809
|
+
function formatVelocity(velocity) {
|
|
810
|
+
return velocity.toFixed(2);
|
|
811
|
+
}
|
|
812
|
+
function roundTempo(tempo) {
|
|
813
|
+
return tempo.map((v) => Math.round(v * 10) / 10);
|
|
404
814
|
}
|
|
405
815
|
|
|
406
|
-
// src/
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
816
|
+
// src/theme/bar-paper.ts
|
|
817
|
+
function grainOpacityForColor(hex) {
|
|
818
|
+
const h = hex.replace("#", "");
|
|
819
|
+
const full = h.length === 3 ? h.split("").map((ch) => ch + ch).join("") : h;
|
|
820
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
821
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
822
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
823
|
+
if ([r, g, b].some(Number.isNaN)) return 0.14;
|
|
824
|
+
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
825
|
+
return Math.min(0.24, Math.max(0.04, 0.02 + 0.31 * lum));
|
|
826
|
+
}
|
|
827
|
+
function barGrain(color) {
|
|
828
|
+
const op = grainOpacityForColor(color).toFixed(3);
|
|
829
|
+
return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='${op}'/%3E%3C/svg%3E")`;
|
|
830
|
+
}
|
|
831
|
+
function barPaper(color, flip = false) {
|
|
832
|
+
return {
|
|
833
|
+
backgroundImage: barGrain(color),
|
|
834
|
+
boxShadow: flip ? "0 -6px 16px rgba(0,0,0,0.45)" : "inset 0 1.5px 0 rgba(255,255,255,0.22), 0 6px 16px rgba(0,0,0,0.45)"
|
|
835
|
+
};
|
|
422
836
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
837
|
+
var SURFACE_LEVEL_TOKEN = {
|
|
838
|
+
background: "background-base",
|
|
839
|
+
base: "surface-base",
|
|
840
|
+
elevated: "surface-elevated",
|
|
841
|
+
raised: "surface-raised",
|
|
842
|
+
overlay: "surface-overlay"
|
|
843
|
+
};
|
|
844
|
+
var ON_SURFACE_TOKEN = {
|
|
845
|
+
primary: "text-primary",
|
|
846
|
+
secondary: "text-secondary",
|
|
847
|
+
tertiary: "text-tertiary"
|
|
848
|
+
};
|
|
849
|
+
var DEFAULT_CONTEXT = { mode: "dark", level: "base" };
|
|
850
|
+
var SurfaceContext = react.createContext(DEFAULT_CONTEXT);
|
|
851
|
+
function useSurface() {
|
|
852
|
+
return react.useContext(SurfaceContext);
|
|
428
853
|
}
|
|
429
|
-
function
|
|
430
|
-
return
|
|
854
|
+
function useSurfaceMode() {
|
|
855
|
+
return react.useContext(SurfaceContext).mode;
|
|
856
|
+
}
|
|
857
|
+
function useOnSurfaceColor(role = "primary") {
|
|
858
|
+
return getSemanticColors(useSurfaceMode())[ON_SURFACE_TOKEN[role]];
|
|
859
|
+
}
|
|
860
|
+
function insetFloor(mode) {
|
|
861
|
+
const colors = getSemanticColors(mode);
|
|
862
|
+
return colors["surface-inset"] ?? surfaceRampDark.inset;
|
|
863
|
+
}
|
|
864
|
+
function surfaceBackground(level, mode) {
|
|
865
|
+
if (level === "inset") return insetFloor(mode);
|
|
866
|
+
return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]];
|
|
867
|
+
}
|
|
868
|
+
var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
869
|
+
var PEAK_OVERSHOOT = 1.12;
|
|
870
|
+
function getReducedMotionPreference() {
|
|
871
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
872
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
873
|
+
}
|
|
874
|
+
function usePrefersReducedMotion() {
|
|
875
|
+
const [reduced, setReduced] = react.useState(getReducedMotionPreference);
|
|
876
|
+
react.useEffect(() => {
|
|
877
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
878
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
879
|
+
const handler = () => setReduced(mq.matches);
|
|
880
|
+
handler();
|
|
881
|
+
mq.addEventListener?.("change", handler);
|
|
882
|
+
return () => mq.removeEventListener?.("change", handler);
|
|
883
|
+
}, []);
|
|
884
|
+
return reduced;
|
|
885
|
+
}
|
|
886
|
+
function useLiveRepGrowth(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
887
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
888
|
+
const [liveGrowth] = react.useState(() => new reactNative.Animated.Value(1));
|
|
889
|
+
react.useEffect(() => {
|
|
890
|
+
if (liveRepIndex == null || liveVelocity == null) return;
|
|
891
|
+
if (prefersReducedMotion) {
|
|
892
|
+
liveGrowth.setValue(1);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
liveGrowth.setValue(0);
|
|
896
|
+
if (isNewPeak) {
|
|
897
|
+
reactNative.Animated.sequence([
|
|
898
|
+
reactNative.Animated.timing(liveGrowth, {
|
|
899
|
+
toValue: PEAK_OVERSHOOT,
|
|
900
|
+
duration: 220,
|
|
901
|
+
easing: reactNative.Easing.out(reactNative.Easing.quad),
|
|
902
|
+
useNativeDriver: false
|
|
903
|
+
}),
|
|
904
|
+
reactNative.Animated.spring(liveGrowth, {
|
|
905
|
+
toValue: 1,
|
|
906
|
+
friction: 5,
|
|
907
|
+
tension: 140,
|
|
908
|
+
useNativeDriver: false
|
|
909
|
+
})
|
|
910
|
+
]).start();
|
|
911
|
+
} else {
|
|
912
|
+
reactNative.Animated.timing(liveGrowth, {
|
|
913
|
+
toValue: 1,
|
|
914
|
+
duration: 300,
|
|
915
|
+
easing: ANIMATION_EASING,
|
|
916
|
+
useNativeDriver: false
|
|
917
|
+
}).start();
|
|
918
|
+
}
|
|
919
|
+
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveGrowth]);
|
|
920
|
+
return liveGrowth;
|
|
921
|
+
}
|
|
922
|
+
function wrapJsx(fn) {
|
|
923
|
+
return function(type, props, ...rest) {
|
|
924
|
+
if (props && typeof props.className === "string" && props.className) {
|
|
925
|
+
const cn2 = props.className;
|
|
926
|
+
const cssStyle = { $$css: true, [cn2]: cn2 };
|
|
927
|
+
const existing = props.style;
|
|
928
|
+
props = {
|
|
929
|
+
...props,
|
|
930
|
+
style: existing ? [cssStyle, existing] : cssStyle
|
|
931
|
+
};
|
|
932
|
+
delete props.className;
|
|
933
|
+
}
|
|
934
|
+
return fn(type, props, ...rest);
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
var jsx = wrapJsx(jsxRuntime.jsx);
|
|
938
|
+
var jsxs = wrapJsx(jsxRuntime.jsxs);
|
|
939
|
+
|
|
940
|
+
// src/components/custom/charts/SetBarChart.tsx
|
|
941
|
+
function mixHex(a, b, t12) {
|
|
942
|
+
const parse = (h) => {
|
|
943
|
+
const s = h.replace("#", "");
|
|
944
|
+
return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
|
|
945
|
+
};
|
|
946
|
+
const [ar, ag, ab] = parse(a);
|
|
947
|
+
const [br, bg, bb] = parse(b);
|
|
948
|
+
const ch = (x, y) => Math.round(x + (y - x) * t12).toString(16).padStart(2, "0");
|
|
949
|
+
return `#${ch(ar, br)}${ch(ag, bg)}${ch(ab, bb)}`;
|
|
950
|
+
}
|
|
951
|
+
var VARIABLE_FILL = primitiveRamps.cyan[900];
|
|
952
|
+
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
953
|
+
var FIXED_MAX_VALUE = 1.15;
|
|
954
|
+
var PEAK_HEADROOM = 1.03;
|
|
955
|
+
function scaleDenominator(scale, maxValue) {
|
|
956
|
+
return scale === "fixed" ? FIXED_MAX_VALUE : maxValue * PEAK_HEADROOM;
|
|
957
|
+
}
|
|
958
|
+
function valueLabelFontSize(height) {
|
|
959
|
+
return Math.round(Math.max(8, Math.min(12, height * 0.11)));
|
|
960
|
+
}
|
|
961
|
+
var SET_BAR_DEFAULT_HEIGHT = 220;
|
|
962
|
+
var LABEL_ROW_HEIGHT = 16;
|
|
963
|
+
var LABEL_GAP = 3;
|
|
964
|
+
var GAP_RATIO = 0.08;
|
|
965
|
+
var MIN_BAR_GAP = 2;
|
|
966
|
+
var CHUNK_NOTCH_RATIO = 0.25;
|
|
967
|
+
var CHUNK_NOTCH_MIN_PX = 10;
|
|
968
|
+
var BAR_MAX_WIDTH = 120;
|
|
969
|
+
var LABEL_MIN_BAR_WIDTH = 30;
|
|
970
|
+
var DEFAULT_BAR_RADIUS = 5;
|
|
971
|
+
var MIN_BAR_HEIGHT = 4;
|
|
972
|
+
var FLAT_BAR_FRACTION = 0.5;
|
|
973
|
+
var STUB_HEIGHT = 9;
|
|
974
|
+
var REP_GAP = 2;
|
|
975
|
+
function computeBarLayout(plotWidth, count, gapRatio = GAP_RATIO) {
|
|
976
|
+
const rawBarWidth = plotWidth > 0 && count > 0 ? plotWidth / (count + (count - 1) * gapRatio) : BAR_MAX_WIDTH;
|
|
977
|
+
const barWidth = Math.min(BAR_MAX_WIDTH, rawBarWidth);
|
|
978
|
+
const gap = plotWidth === 0 ? MIN_BAR_GAP : Math.max(MIN_BAR_GAP, gapRatio * barWidth);
|
|
979
|
+
return { barWidth, gap };
|
|
980
|
+
}
|
|
981
|
+
function isRep(slot) {
|
|
982
|
+
return slot.kind === "rep";
|
|
983
|
+
}
|
|
984
|
+
function repIndexByCell(cells) {
|
|
985
|
+
let count = 0;
|
|
986
|
+
return cells.map((slot) => isRep(slot) ? count++ : -1);
|
|
987
|
+
}
|
|
988
|
+
function SetBarChart({
|
|
989
|
+
slots,
|
|
990
|
+
colorFor,
|
|
991
|
+
height,
|
|
992
|
+
scale = "peak",
|
|
993
|
+
scaleMax,
|
|
994
|
+
orientation = "up",
|
|
995
|
+
liveRepIndex,
|
|
996
|
+
isNewPeak = false,
|
|
997
|
+
targetReps,
|
|
998
|
+
gapRatio = GAP_RATIO,
|
|
999
|
+
barRadius = DEFAULT_BAR_RADIUS,
|
|
1000
|
+
cornerStyle = "top",
|
|
1001
|
+
flat = false,
|
|
1002
|
+
showValueLabels: showValueLabelsProp = false,
|
|
1003
|
+
formatValue: formatValue2 = String,
|
|
1004
|
+
todoVariant = "solid",
|
|
1005
|
+
minColumns,
|
|
1006
|
+
renderReference,
|
|
1007
|
+
renderBarOverlay,
|
|
1008
|
+
expandProgress,
|
|
1009
|
+
hideBaseline = false,
|
|
1010
|
+
testID,
|
|
1011
|
+
testIDPrefix = "setbar",
|
|
1012
|
+
accessibilityLabel: accessibilityLabel2,
|
|
1013
|
+
label,
|
|
1014
|
+
className,
|
|
1015
|
+
viewProps = {}
|
|
1016
|
+
}) {
|
|
1017
|
+
const targetPadded = Math.max(slots.length, targetReps ?? 0, minColumns ?? 0);
|
|
1018
|
+
const cells = targetPadded > slots.length ? [
|
|
1019
|
+
...slots,
|
|
1020
|
+
...Array.from({ length: targetPadded - slots.length }, () => ({ kind: "todo" }))
|
|
1021
|
+
] : slots;
|
|
1022
|
+
const repValues = cells.filter(isRep).map((s) => s.value ?? 0);
|
|
1023
|
+
const best = repValues.length > 0 ? Math.max(...repValues) : 0;
|
|
1024
|
+
const flip = orientation === "down";
|
|
1025
|
+
const flipStyle = flip ? { transform: [{ scaleY: -1 }] } : null;
|
|
1026
|
+
const placeholderColor = useOnSurfaceColor("tertiary");
|
|
1027
|
+
const surface = useSurface();
|
|
1028
|
+
const surfaceBg = surfaceBackground(surface.level, surface.mode);
|
|
1029
|
+
const solidTodoColor = mixHex(surfaceBg, placeholderColor, 0.55);
|
|
1030
|
+
const emptyColor = mixHex(surfaceBg, placeholderColor, 0.28);
|
|
1031
|
+
const barCorners = cornerStyle === "all" ? { borderRadius: barRadius } : { borderTopLeftRadius: barRadius, borderTopRightRadius: barRadius };
|
|
1032
|
+
const scaleDenom = scaleMax != null && scaleMax > 0 ? scaleMax * PEAK_HEADROOM : scaleDenominator(scale, best);
|
|
1033
|
+
const showValueLabels = showValueLabelsProp && !flat;
|
|
1034
|
+
const plotHeight = Math.max(0, height - (showValueLabels ? LABEL_ROW_HEIGHT + LABEL_GAP : 0));
|
|
1035
|
+
const yOf = (value) => scaleDenom > 0 ? value / scaleDenom * plotHeight : 0;
|
|
1036
|
+
const flatBarHeight = Math.max(MIN_BAR_HEIGHT, FLAT_BAR_FRACTION * plotHeight);
|
|
1037
|
+
const compactBarHeight = Math.max(MIN_BAR_HEIGHT, plotHeight);
|
|
1038
|
+
const valueBarHeight = (value) => scaleDenom > 0 ? Math.max(MIN_BAR_HEIGHT, Math.min(1, value / scaleDenom) * plotHeight) : MIN_BAR_HEIGHT;
|
|
1039
|
+
const barHeight = (value) => flat ? compactBarHeight : valueBarHeight(value);
|
|
1040
|
+
const liveValue = liveRepIndex != null && liveRepIndex < repValues.length ? repValues[liveRepIndex] : void 0;
|
|
1041
|
+
const liveGrowth = useLiveRepGrowth(true, liveRepIndex, liveValue, isNewPeak);
|
|
1042
|
+
const [plotW, setPlotW] = react.useState(0);
|
|
1043
|
+
const onPlotLayout = (e) => setPlotW(e.nativeEvent.layout.width);
|
|
1044
|
+
const totalCells = cells.length;
|
|
1045
|
+
const { barWidth, gap } = computeBarLayout(plotW, totalCells, gapRatio);
|
|
1046
|
+
const labelsCrowded = plotW > 0 && barWidth < LABEL_MIN_BAR_WIDTH;
|
|
1047
|
+
const peakRepIndex = repValues.reduce((b, v, i) => v > repValues[b] ? i : b, 0);
|
|
1048
|
+
const showBarLabel = (repIndex) => !labelsCrowded || repIndex === peakRepIndex || repIndex === liveRepIndex;
|
|
1049
|
+
const geometry = { scaleDenom, plotHeight, yOf, best, flip };
|
|
1050
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
1051
|
+
const a11y = accessibilityLabel2 != null ? { accessibilityRole: "image", accessibilityLabel: accessibilityLabel2 } : {};
|
|
1052
|
+
const repIndices = repIndexByCell(cells);
|
|
1053
|
+
return /* @__PURE__ */ jsxs(
|
|
431
1054
|
reactNative.View,
|
|
432
1055
|
{
|
|
433
1056
|
className,
|
|
434
|
-
style: { flexDirection: "row"
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
1057
|
+
style: [{ height, flexDirection: "row" }, externalStyle],
|
|
1058
|
+
testID,
|
|
1059
|
+
...a11y,
|
|
1060
|
+
...restProps,
|
|
1061
|
+
children: [
|
|
1062
|
+
label ? /* @__PURE__ */ jsx(
|
|
1063
|
+
ChartSideRail,
|
|
1064
|
+
{
|
|
1065
|
+
sections: [{ label, testID: `${testIDPrefix}-side-label` }],
|
|
1066
|
+
sectionExtent: height
|
|
1067
|
+
}
|
|
1068
|
+
) : null,
|
|
1069
|
+
/* @__PURE__ */ jsxs(
|
|
1070
|
+
reactNative.View,
|
|
1071
|
+
{
|
|
1072
|
+
onLayout: onPlotLayout,
|
|
1073
|
+
style: {
|
|
1074
|
+
flex: 1,
|
|
1075
|
+
flexDirection: "row",
|
|
1076
|
+
alignItems: "flex-end",
|
|
1077
|
+
gap,
|
|
1078
|
+
position: "relative",
|
|
1079
|
+
borderBottomWidth: hideBaseline ? 0 : 2,
|
|
1080
|
+
borderBottomColor: placeholderColor,
|
|
1081
|
+
...flipStyle
|
|
1082
|
+
},
|
|
1083
|
+
children: [
|
|
1084
|
+
renderReference?.(geometry),
|
|
1085
|
+
cells.map((slot, i) => {
|
|
1086
|
+
const isChunkBoundary = (slot.leadingGap ?? REP_GAP) > REP_GAP;
|
|
1087
|
+
const extraGap = isChunkBoundary ? Math.max(CHUNK_NOTCH_MIN_PX, CHUNK_NOTCH_RATIO * barWidth) : 0;
|
|
1088
|
+
const column = {
|
|
1089
|
+
flex: 1,
|
|
1090
|
+
maxWidth: BAR_MAX_WIDTH,
|
|
1091
|
+
height: "100%",
|
|
1092
|
+
alignItems: "center",
|
|
1093
|
+
justifyContent: "flex-end",
|
|
1094
|
+
marginLeft: extraGap
|
|
1095
|
+
};
|
|
1096
|
+
if (!isRep(slot)) {
|
|
1097
|
+
return /* @__PURE__ */ jsx(reactNative.View, { accessibilityElementsHidden: true, style: column, children: renderStub(
|
|
1098
|
+
slot.kind,
|
|
1099
|
+
barCorners,
|
|
1100
|
+
// Flat (compact) stubs fill the plot like the compact bars; else the fixed short stub.
|
|
1101
|
+
flat ? compactBarHeight : STUB_HEIGHT,
|
|
1102
|
+
placeholderColor,
|
|
1103
|
+
testIDPrefix,
|
|
1104
|
+
todoVariant,
|
|
1105
|
+
solidTodoColor,
|
|
1106
|
+
emptyColor
|
|
1107
|
+
) }, i);
|
|
1108
|
+
}
|
|
1109
|
+
const repIndex = repIndices[i];
|
|
1110
|
+
const value = slot.value ?? 0;
|
|
1111
|
+
const isLive = liveRepIndex === repIndex;
|
|
1112
|
+
const color = colorFor(value);
|
|
1113
|
+
const barHeightStyle = expandProgress ? expandProgress.interpolate({
|
|
1114
|
+
inputRange: [0, 1],
|
|
1115
|
+
outputRange: [flatBarHeight, valueBarHeight(value)]
|
|
1116
|
+
}) : isLive ? liveGrowth.interpolate({ inputRange: [0, 1], outputRange: [0, barHeight(value)] }) : barHeight(value);
|
|
1117
|
+
const ownLabel = showValueLabels && !renderBarOverlay && showBarLabel(repIndex);
|
|
1118
|
+
const labelBottom = (expandProgress ? valueBarHeight(best) : barHeight(best)) + LABEL_GAP;
|
|
1119
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { accessibilityElementsHidden: renderBarOverlay == null, style: column, children: [
|
|
1120
|
+
ownLabel && // All labels pin to one aligned row JUST ABOVE THE PEAK BAR (not the container top),
|
|
1121
|
+
// in the muted on-surface-secondary tone — so the row hugs the bars, no floating void.
|
|
1122
|
+
/* @__PURE__ */ jsx(
|
|
1123
|
+
reactNative.Animated.View,
|
|
1124
|
+
{
|
|
1125
|
+
style: {
|
|
1126
|
+
position: "absolute",
|
|
1127
|
+
bottom: labelBottom,
|
|
1128
|
+
left: 0,
|
|
1129
|
+
right: 0,
|
|
1130
|
+
alignItems: "center",
|
|
1131
|
+
...expandProgress ? { opacity: expandProgress } : null
|
|
1132
|
+
},
|
|
1133
|
+
pointerEvents: "none",
|
|
1134
|
+
children: /* @__PURE__ */ jsx(
|
|
1135
|
+
reactNative.Text,
|
|
1136
|
+
{
|
|
1137
|
+
className: "text-text-secondary",
|
|
1138
|
+
style: [{ fontSize: valueLabelFontSize(height), fontWeight: "700" }, flipStyle],
|
|
1139
|
+
testID: `${testIDPrefix}-label-${repIndex}`,
|
|
1140
|
+
children: formatValue2(value)
|
|
1141
|
+
}
|
|
1142
|
+
)
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
/* @__PURE__ */ jsx(
|
|
1146
|
+
reactNative.Animated.View,
|
|
1147
|
+
{
|
|
1148
|
+
style: [
|
|
1149
|
+
{ width: "100%", height: barHeightStyle, backgroundColor: color },
|
|
1150
|
+
barCorners,
|
|
1151
|
+
barPaper(color, flip)
|
|
1152
|
+
],
|
|
1153
|
+
testID: `${testIDPrefix}-bar-${repIndex}`
|
|
1154
|
+
}
|
|
1155
|
+
),
|
|
1156
|
+
renderBarOverlay?.(repIndex, value)
|
|
1157
|
+
] }, i);
|
|
1158
|
+
})
|
|
1159
|
+
]
|
|
1160
|
+
}
|
|
1161
|
+
)
|
|
1162
|
+
]
|
|
440
1163
|
}
|
|
441
1164
|
);
|
|
442
1165
|
}
|
|
1166
|
+
function renderStub(kind, barCorners, stubHeight, placeholderColor, testIDPrefix, todoVariant, solidTodoColor, emptyColor) {
|
|
1167
|
+
const base = {
|
|
1168
|
+
width: "100%",
|
|
1169
|
+
height: stubHeight,
|
|
1170
|
+
...barCorners
|
|
1171
|
+
};
|
|
1172
|
+
if (kind === "variable") {
|
|
1173
|
+
return /* @__PURE__ */ jsx(
|
|
1174
|
+
reactNative.View,
|
|
1175
|
+
{
|
|
1176
|
+
style: { ...base, backgroundColor: VARIABLE_FILL },
|
|
1177
|
+
testID: `${testIDPrefix}-slot-variable`
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
if (kind === "continue") {
|
|
1182
|
+
return /* @__PURE__ */ jsx(
|
|
1183
|
+
reactNative.View,
|
|
1184
|
+
{
|
|
1185
|
+
style: { ...base, borderWidth: 1, borderColor: CONTINUE_OUTLINE },
|
|
1186
|
+
testID: `${testIDPrefix}-slot-continue`
|
|
1187
|
+
}
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
if (kind === "empty") {
|
|
1191
|
+
return /* @__PURE__ */ jsx(
|
|
1192
|
+
reactNative.View,
|
|
1193
|
+
{
|
|
1194
|
+
style: { ...base, backgroundColor: emptyColor },
|
|
1195
|
+
testID: `${testIDPrefix}-slot-empty`
|
|
1196
|
+
}
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
const todoStyle = todoVariant === "solid" ? { ...base, backgroundColor: solidTodoColor } : { ...base, borderWidth: 1, borderStyle: "dashed", borderColor: placeholderColor };
|
|
1200
|
+
return /* @__PURE__ */ jsx(reactNative.View, { style: todoStyle, testID: `${testIDPrefix}-slot-todo` });
|
|
1201
|
+
}
|
|
1202
|
+
function sideLabelFontSize(height) {
|
|
1203
|
+
return Math.round(Math.max(7, Math.min(13, height * 0.06)));
|
|
1204
|
+
}
|
|
1205
|
+
var SIDE_LABEL_INITIALS_BELOW = 70;
|
|
1206
|
+
function sideLabelText(label, extent) {
|
|
1207
|
+
if (extent >= SIDE_LABEL_INITIALS_BELOW) return label;
|
|
1208
|
+
return label.trim().split(/\s+/).map((word) => word.charAt(0)).join(" ").toUpperCase();
|
|
1209
|
+
}
|
|
1210
|
+
function ChartSideRail({
|
|
1211
|
+
sections,
|
|
1212
|
+
sectionExtent,
|
|
1213
|
+
variant = "hero"
|
|
1214
|
+
}) {
|
|
1215
|
+
if (!sections.some((s) => s.label)) return null;
|
|
1216
|
+
const isRail = variant === "rail";
|
|
1217
|
+
const labelLength = Math.max(0, sectionExtent - (isRail ? 0 : 6));
|
|
1218
|
+
const fontSize = isRail ? 7 : sideLabelFontSize(sectionExtent);
|
|
1219
|
+
const textStyle = {
|
|
1220
|
+
maxWidth: "100%",
|
|
1221
|
+
textAlign: "center",
|
|
1222
|
+
fontSize,
|
|
1223
|
+
fontWeight: "700",
|
|
1224
|
+
letterSpacing: isRail || fontSize < 10 ? 0 : 2,
|
|
1225
|
+
textTransform: "uppercase"
|
|
1226
|
+
};
|
|
1227
|
+
return /* @__PURE__ */ jsx(reactNative.View, { style: { width: isRail ? 18 : 34 }, accessibilityElementsHidden: true, children: sections.map((s) => /* @__PURE__ */ jsx(
|
|
1228
|
+
reactNative.View,
|
|
1229
|
+
{
|
|
1230
|
+
style: { height: sectionExtent, alignItems: "center", justifyContent: "center" },
|
|
1231
|
+
children: s.label ? /* @__PURE__ */ jsx(
|
|
1232
|
+
reactNative.View,
|
|
1233
|
+
{
|
|
1234
|
+
style: {
|
|
1235
|
+
width: labelLength,
|
|
1236
|
+
alignItems: "center",
|
|
1237
|
+
transform: [{ rotate: "-90deg" }]
|
|
1238
|
+
},
|
|
1239
|
+
children: /* @__PURE__ */ jsx(
|
|
1240
|
+
reactNative.Text,
|
|
1241
|
+
{
|
|
1242
|
+
className: "text-text-tertiary",
|
|
1243
|
+
style: textStyle,
|
|
1244
|
+
numberOfLines: 1,
|
|
1245
|
+
testID: s.testID,
|
|
1246
|
+
children: isRail ? s.label : sideLabelText(s.label, sectionExtent)
|
|
1247
|
+
}
|
|
1248
|
+
)
|
|
1249
|
+
}
|
|
1250
|
+
) : null
|
|
1251
|
+
},
|
|
1252
|
+
s.testID
|
|
1253
|
+
)) });
|
|
1254
|
+
}
|
|
443
1255
|
|
|
444
1256
|
// src/components/custom/Workout/VelocityStrip.tsx
|
|
445
1257
|
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
@@ -485,6 +1297,18 @@ function calculateMeanVelocity(velocities) {
|
|
|
485
1297
|
const sum = velocities.reduce((acc, v) => acc + v, 0);
|
|
486
1298
|
return sum / velocities.length;
|
|
487
1299
|
}
|
|
1300
|
+
var VL_LOSS_THRESHOLDS = [10, 20, 30];
|
|
1301
|
+
function getVelocityLossColor(lossPct) {
|
|
1302
|
+
const [t1, t22, t32] = VL_LOSS_THRESHOLDS;
|
|
1303
|
+
if (lossPct < t1) return VEL_COLORS.green;
|
|
1304
|
+
if (lossPct < t22) return VEL_COLORS.yellow;
|
|
1305
|
+
if (lossPct < t32) return VEL_COLORS.orange;
|
|
1306
|
+
return VEL_COLORS.red;
|
|
1307
|
+
}
|
|
1308
|
+
function velocityLossForRep(velocity, best) {
|
|
1309
|
+
if (best <= 0) return 0;
|
|
1310
|
+
return Math.max(0, Math.round((best - velocity) / best * 100));
|
|
1311
|
+
}
|
|
488
1312
|
function classifyBand(velocity, bands) {
|
|
489
1313
|
for (const band of bands) {
|
|
490
1314
|
if (band.max === null || velocity < band.max) return band;
|
|
@@ -494,15 +1318,17 @@ function classifyBand(velocity, bands) {
|
|
|
494
1318
|
function bandColor(band) {
|
|
495
1319
|
return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
|
|
496
1320
|
}
|
|
1321
|
+
function makeBarColorFor(zones) {
|
|
1322
|
+
const hasZones = zones != null && zones.length > 0;
|
|
1323
|
+
return (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
1324
|
+
}
|
|
497
1325
|
function getLossStyle(loss) {
|
|
498
1326
|
if (loss > 25) return { color: VEL_COLORS.red };
|
|
499
1327
|
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
500
1328
|
return null;
|
|
501
1329
|
}
|
|
502
|
-
var
|
|
1330
|
+
var REP_GAP2 = 2;
|
|
503
1331
|
var WIDE_GAP = 8;
|
|
504
|
-
var TODO_COLOR2 = primitiveColors.charcoal[300];
|
|
505
|
-
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
506
1332
|
function deriveDoneVelocities(set) {
|
|
507
1333
|
switch (set.type) {
|
|
508
1334
|
case "drop":
|
|
@@ -518,7 +1344,7 @@ function chunkedRepSlots(chunks) {
|
|
|
518
1344
|
chunks.forEach((chunk) => {
|
|
519
1345
|
chunk.forEach((velocity, ri) => {
|
|
520
1346
|
const first = slots.length === 0;
|
|
521
|
-
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP :
|
|
1347
|
+
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP2 });
|
|
522
1348
|
});
|
|
523
1349
|
});
|
|
524
1350
|
return slots;
|
|
@@ -532,7 +1358,7 @@ function buildSlots(set) {
|
|
|
532
1358
|
return {
|
|
533
1359
|
kind: done ? "rep" : "todo",
|
|
534
1360
|
velocity: done ? set.velocities[i] : void 0,
|
|
535
|
-
leadingGap: i === 0 ? 0 :
|
|
1361
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
536
1362
|
};
|
|
537
1363
|
});
|
|
538
1364
|
}
|
|
@@ -543,7 +1369,7 @@ function buildSlots(set) {
|
|
|
543
1369
|
return {
|
|
544
1370
|
kind,
|
|
545
1371
|
velocity: kind === "rep" ? set.velocities[i] : void 0,
|
|
546
|
-
leadingGap: i === 0 ? 0 :
|
|
1372
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
547
1373
|
};
|
|
548
1374
|
});
|
|
549
1375
|
}
|
|
@@ -551,16 +1377,16 @@ function buildSlots(set) {
|
|
|
551
1377
|
const reps = set.velocities.map((velocity, i) => ({
|
|
552
1378
|
kind: "rep",
|
|
553
1379
|
velocity,
|
|
554
|
-
leadingGap: i === 0 ? 0 :
|
|
1380
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
555
1381
|
}));
|
|
556
|
-
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 :
|
|
1382
|
+
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP2 });
|
|
557
1383
|
return reps;
|
|
558
1384
|
}
|
|
559
1385
|
case "drop":
|
|
560
1386
|
return chunkedRepSlots(set.subloads);
|
|
561
1387
|
case "myo": {
|
|
562
1388
|
const slots = chunkedRepSlots([set.activation, ...set.clusters]);
|
|
563
|
-
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 :
|
|
1389
|
+
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP2 });
|
|
564
1390
|
return slots;
|
|
565
1391
|
}
|
|
566
1392
|
case "cluster": {
|
|
@@ -571,7 +1397,7 @@ function buildSlots(set) {
|
|
|
571
1397
|
return {
|
|
572
1398
|
kind: done ? "rep" : "todo",
|
|
573
1399
|
velocity: done ? set.velocities[i] : void 0,
|
|
574
|
-
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP :
|
|
1400
|
+
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP2
|
|
575
1401
|
};
|
|
576
1402
|
});
|
|
577
1403
|
}
|
|
@@ -593,253 +1419,148 @@ function setAccessibilityLabel(set, repCount) {
|
|
|
593
1419
|
return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
|
|
594
1420
|
}
|
|
595
1421
|
}
|
|
596
|
-
function slotAccessibilityLabel(slot, index) {
|
|
597
|
-
switch (slot.kind) {
|
|
598
|
-
case "rep":
|
|
599
|
-
return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
|
|
600
|
-
case "todo":
|
|
601
|
-
return `Rep ${index + 1}: planned`;
|
|
602
|
-
case "variable":
|
|
603
|
-
return `Rep ${index + 1}: variable`;
|
|
604
|
-
case "continue":
|
|
605
|
-
return "Keep going";
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
var EXPANDED_TODO_STUB_PCT = 16;
|
|
609
|
-
var EXPANDED_ENCODED_PCT = 45;
|
|
610
1422
|
var EXPANDED_HEIGHT = 60;
|
|
611
|
-
var
|
|
612
|
-
var BARE_STUB_HEIGHT = 3;
|
|
613
|
-
var HERO_HEIGHT = 220;
|
|
614
|
-
var HERO_LABEL_HEADROOM = 20;
|
|
615
|
-
var HERO_BAR_GAP = 8;
|
|
616
|
-
var HERO_BAR_MAX_WIDTH = 120;
|
|
617
|
-
var HERO_LABEL_MIN_BAR_WIDTH = 30;
|
|
618
|
-
var HERO_BAR_RADIUS = 5;
|
|
619
|
-
var HERO_MIN_BAR_HEIGHT = 4;
|
|
620
|
-
var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
|
|
1423
|
+
var COMPACT_HEIGHT = 8;
|
|
621
1424
|
var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
return () => mq.removeEventListener?.("change", handler);
|
|
644
|
-
}, []);
|
|
645
|
-
return reduced;
|
|
646
|
-
}
|
|
647
|
-
var ANIMATION_DURATION = 400;
|
|
648
|
-
var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
649
|
-
var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
|
|
650
|
-
function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
651
|
-
const prefersReducedMotion = usePrefersReducedMotion();
|
|
652
|
-
const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
|
|
653
|
-
react.useEffect(() => {
|
|
654
|
-
if (!active || liveRepIndex == null || liveVelocity == null) return;
|
|
655
|
-
if (prefersReducedMotion) {
|
|
656
|
-
liveScale.setValue(1);
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
if (isNewPeak) {
|
|
660
|
-
liveScale.setValue(1);
|
|
661
|
-
reactNative.Animated.sequence([
|
|
662
|
-
reactNative.Animated.timing(liveScale, {
|
|
663
|
-
toValue: 1.25,
|
|
664
|
-
duration: 150,
|
|
665
|
-
easing: reactNative.Easing.out(reactNative.Easing.quad),
|
|
666
|
-
useNativeDriver: true
|
|
667
|
-
}),
|
|
668
|
-
reactNative.Animated.spring(liveScale, {
|
|
669
|
-
toValue: 1,
|
|
670
|
-
friction: 3,
|
|
671
|
-
tension: 140,
|
|
672
|
-
useNativeDriver: true
|
|
673
|
-
})
|
|
674
|
-
]).start();
|
|
675
|
-
} else {
|
|
676
|
-
liveScale.setValue(0.8);
|
|
677
|
-
reactNative.Animated.timing(liveScale, {
|
|
678
|
-
toValue: 1,
|
|
679
|
-
duration: 300,
|
|
680
|
-
easing: POP_EASING,
|
|
681
|
-
useNativeDriver: true
|
|
682
|
-
}).start();
|
|
1425
|
+
function DashedReferenceLine({
|
|
1426
|
+
anchor,
|
|
1427
|
+
offset,
|
|
1428
|
+
testID
|
|
1429
|
+
}) {
|
|
1430
|
+
const edge = anchor === "top" ? { top: offset } : { bottom: offset };
|
|
1431
|
+
return /* @__PURE__ */ jsx(
|
|
1432
|
+
reactNative.View,
|
|
1433
|
+
{
|
|
1434
|
+
accessibilityElementsHidden: true,
|
|
1435
|
+
style: {
|
|
1436
|
+
position: "absolute",
|
|
1437
|
+
left: 0,
|
|
1438
|
+
right: 0,
|
|
1439
|
+
borderTopWidth: 1,
|
|
1440
|
+
borderStyle: "dashed",
|
|
1441
|
+
borderColor: HERO_REFERENCE_COLOR,
|
|
1442
|
+
pointerEvents: "none",
|
|
1443
|
+
...edge
|
|
1444
|
+
},
|
|
1445
|
+
testID
|
|
683
1446
|
}
|
|
684
|
-
|
|
685
|
-
return liveScale;
|
|
1447
|
+
);
|
|
686
1448
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1449
|
+
var BAND_LABEL_FONT = "monospace";
|
|
1450
|
+
var VL_LABEL_MIN_PLOT = 65;
|
|
1451
|
+
var VL_SEMANTIC = getSemanticColors("dark");
|
|
1452
|
+
function VelocityLossBands({
|
|
1453
|
+
best,
|
|
690
1454
|
scaleDenom,
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
isNewPeak,
|
|
694
|
-
targetReps,
|
|
695
|
-
height,
|
|
696
|
-
className,
|
|
697
|
-
viewProps
|
|
1455
|
+
plotHeight,
|
|
1456
|
+
flip = false
|
|
698
1457
|
}) {
|
|
699
|
-
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
const
|
|
703
|
-
const
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
|
|
707
|
-
const totalSlots = doneVelocities.length + pendingCount;
|
|
708
|
-
const barWidth = plotW > 0 && totalSlots > 0 ? Math.min(HERO_BAR_MAX_WIDTH, (plotW - HERO_BAR_GAP * (totalSlots - 1)) / totalSlots) : HERO_BAR_MAX_WIDTH;
|
|
709
|
-
const labelsCrowded = plotW > 0 && barWidth < HERO_LABEL_MIN_BAR_WIDTH;
|
|
710
|
-
const heroGap = plotW === 0 ? HERO_BAR_GAP : barWidth < 16 ? 2 : barWidth < 28 ? 4 : HERO_BAR_GAP;
|
|
711
|
-
const peakIndex = doneVelocities.reduce(
|
|
712
|
-
(best, v, i) => v > doneVelocities[best] ? i : best,
|
|
713
|
-
0
|
|
714
|
-
);
|
|
715
|
-
const showBarLabel = (i) => !labelsCrowded || i === peakIndex || i === liveRepIndex;
|
|
716
|
-
const repCount = doneVelocities.length;
|
|
717
|
-
const total = Math.max(repCount, targetReps ?? repCount);
|
|
718
|
-
const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
719
|
-
const { style: externalStyle, ...restProps } = viewProps;
|
|
720
|
-
return /* @__PURE__ */ jsx(
|
|
1458
|
+
if (best <= 0 || scaleDenom <= 0 || plotHeight <= 0) return null;
|
|
1459
|
+
const yOf = (v) => v / scaleDenom * plotHeight;
|
|
1460
|
+
const vl20 = best * 0.8;
|
|
1461
|
+
const vl30 = best * 0.7;
|
|
1462
|
+
const vlFont = Math.round(Math.max(7, Math.min(9, plotHeight * 0.05)));
|
|
1463
|
+
const showLabels = plotHeight >= VL_LABEL_MIN_PLOT;
|
|
1464
|
+
const band = (loV, hiV, color) => /* @__PURE__ */ jsx(
|
|
721
1465
|
reactNative.View,
|
|
722
1466
|
{
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const isLive = liveRepIndex === i;
|
|
762
|
-
return /* @__PURE__ */ jsxs(
|
|
763
|
-
reactNative.View,
|
|
764
|
-
{
|
|
765
|
-
accessibilityElementsHidden: true,
|
|
766
|
-
style: {
|
|
767
|
-
flex: 1,
|
|
768
|
-
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
769
|
-
height: "100%",
|
|
770
|
-
alignItems: "center",
|
|
771
|
-
justifyContent: "flex-end"
|
|
772
|
-
},
|
|
773
|
-
children: [
|
|
774
|
-
showBarLabel(i) && /* @__PURE__ */ jsx(
|
|
775
|
-
reactNative.Text,
|
|
776
|
-
{
|
|
777
|
-
className: "text-text-primary",
|
|
778
|
-
style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
|
|
779
|
-
testID: `velocity-label-${i}`,
|
|
780
|
-
children: formatVelocity(velocity)
|
|
781
|
-
}
|
|
782
|
-
),
|
|
783
|
-
/* @__PURE__ */ jsx(
|
|
784
|
-
reactNative.Animated.View,
|
|
785
|
-
{
|
|
786
|
-
style: [
|
|
787
|
-
{
|
|
788
|
-
width: "100%",
|
|
789
|
-
height: barHeight(velocity),
|
|
790
|
-
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
791
|
-
borderTopRightRadius: HERO_BAR_RADIUS,
|
|
792
|
-
backgroundColor: barColorFor(velocity)
|
|
793
|
-
},
|
|
794
|
-
isLive ? { transform: [{ scale: liveScale }] } : null
|
|
795
|
-
],
|
|
796
|
-
testID: `velocity-bar-${i}`
|
|
797
|
-
}
|
|
798
|
-
)
|
|
799
|
-
]
|
|
800
|
-
},
|
|
801
|
-
i
|
|
802
|
-
);
|
|
803
|
-
}),
|
|
804
|
-
Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
|
|
805
|
-
reactNative.View,
|
|
806
|
-
{
|
|
807
|
-
accessibilityElementsHidden: true,
|
|
808
|
-
style: {
|
|
809
|
-
flex: 1,
|
|
810
|
-
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
811
|
-
height: "100%",
|
|
812
|
-
alignItems: "center",
|
|
813
|
-
justifyContent: "flex-end"
|
|
814
|
-
},
|
|
815
|
-
children: /* @__PURE__ */ jsx(
|
|
816
|
-
reactNative.View,
|
|
817
|
-
{
|
|
818
|
-
style: {
|
|
819
|
-
width: "100%",
|
|
820
|
-
height: HERO_MIN_BAR_HEIGHT * 3,
|
|
821
|
-
borderWidth: 1,
|
|
822
|
-
borderStyle: "dashed",
|
|
823
|
-
borderColor: HERO_PENDING_COLOR,
|
|
824
|
-
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
825
|
-
borderTopRightRadius: HERO_BAR_RADIUS
|
|
826
|
-
},
|
|
827
|
-
testID: "velocity-slot-todo"
|
|
828
|
-
}
|
|
829
|
-
)
|
|
1467
|
+
style: {
|
|
1468
|
+
position: "absolute",
|
|
1469
|
+
left: 0,
|
|
1470
|
+
right: 0,
|
|
1471
|
+
bottom: yOf(loV),
|
|
1472
|
+
height: Math.max(0, yOf(hiV) - yOf(loV)),
|
|
1473
|
+
backgroundColor: color
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
);
|
|
1477
|
+
const threshold = (v, color, label) => /* @__PURE__ */ jsxs(
|
|
1478
|
+
reactNative.View,
|
|
1479
|
+
{
|
|
1480
|
+
style: {
|
|
1481
|
+
position: "absolute",
|
|
1482
|
+
left: 0,
|
|
1483
|
+
right: 0,
|
|
1484
|
+
bottom: yOf(v),
|
|
1485
|
+
// Collapse to zero height so the dashed border lands EXACTLY on `yOf(v)` — the
|
|
1486
|
+
// band edge — instead of floating up half a text-row (the label then centres on
|
|
1487
|
+
// the line, breaking it). Matches DashedReferenceLine's bare-border anchoring.
|
|
1488
|
+
height: 0,
|
|
1489
|
+
flexDirection: "row",
|
|
1490
|
+
alignItems: "center"
|
|
1491
|
+
},
|
|
1492
|
+
children: [
|
|
1493
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { flex: 9, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } }),
|
|
1494
|
+
showLabels ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
1495
|
+
/* @__PURE__ */ jsx(
|
|
1496
|
+
reactNative.Text,
|
|
1497
|
+
{
|
|
1498
|
+
style: {
|
|
1499
|
+
marginHorizontal: 6,
|
|
1500
|
+
fontSize: vlFont,
|
|
1501
|
+
fontWeight: "800",
|
|
1502
|
+
fontFamily: BAND_LABEL_FONT,
|
|
1503
|
+
color,
|
|
1504
|
+
...flip ? { transform: [{ scaleY: -1 }] } : null
|
|
830
1505
|
},
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1506
|
+
children: label
|
|
1507
|
+
}
|
|
1508
|
+
),
|
|
1509
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { flex: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } })
|
|
1510
|
+
] }) : null
|
|
1511
|
+
]
|
|
836
1512
|
}
|
|
837
1513
|
);
|
|
1514
|
+
return /* @__PURE__ */ jsxs(
|
|
1515
|
+
reactNative.View,
|
|
1516
|
+
{
|
|
1517
|
+
accessibilityElementsHidden: true,
|
|
1518
|
+
pointerEvents: "none",
|
|
1519
|
+
style: { position: "absolute", left: 0, right: 0, bottom: 0, top: 0 },
|
|
1520
|
+
testID: "velocity-loss-bands",
|
|
1521
|
+
children: [
|
|
1522
|
+
band(0, vl30, alpha(VL_SEMANTIC["status-error"], 0.09)),
|
|
1523
|
+
band(vl30, vl20, alpha(VL_SEMANTIC["status-warning"], 0.08)),
|
|
1524
|
+
threshold(vl20, alpha(VL_SEMANTIC["status-warning"], 0.75), "VL 20%"),
|
|
1525
|
+
threshold(vl30, alpha(VL_SEMANTIC["status-error"], 0.75), "VL 30%")
|
|
1526
|
+
]
|
|
1527
|
+
}
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
function velocityReferenceOverlay(g, showLossBands) {
|
|
1531
|
+
const referencePx = g.best > 0 && g.scaleDenom > 0 ? Math.min(g.plotHeight, g.yOf(g.best)) : 0;
|
|
1532
|
+
return /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
1533
|
+
showLossBands && /* @__PURE__ */ jsx(
|
|
1534
|
+
VelocityLossBands,
|
|
1535
|
+
{
|
|
1536
|
+
best: g.best,
|
|
1537
|
+
scaleDenom: g.scaleDenom,
|
|
1538
|
+
plotHeight: g.plotHeight,
|
|
1539
|
+
flip: g.flip
|
|
1540
|
+
}
|
|
1541
|
+
),
|
|
1542
|
+
referencePx > 0 && /* @__PURE__ */ jsx(
|
|
1543
|
+
DashedReferenceLine,
|
|
1544
|
+
{
|
|
1545
|
+
anchor: "bottom",
|
|
1546
|
+
offset: referencePx,
|
|
1547
|
+
testID: "velocity-hero-reference"
|
|
1548
|
+
}
|
|
1549
|
+
)
|
|
1550
|
+
] });
|
|
838
1551
|
}
|
|
1552
|
+
var ANIMATION_DURATION = 400;
|
|
839
1553
|
function VelocityStrip({
|
|
840
1554
|
velocities,
|
|
841
1555
|
set,
|
|
842
1556
|
zones,
|
|
1557
|
+
barColor = "loss",
|
|
1558
|
+
showLossBands,
|
|
1559
|
+
orientation = "up",
|
|
1560
|
+
scaleMax,
|
|
1561
|
+
hideBaseline,
|
|
1562
|
+
columnSlots,
|
|
1563
|
+
label,
|
|
843
1564
|
liveRepIndex,
|
|
844
1565
|
expanded = true,
|
|
845
1566
|
onToggle,
|
|
@@ -853,51 +1574,29 @@ function VelocityStrip({
|
|
|
853
1574
|
className,
|
|
854
1575
|
...props
|
|
855
1576
|
}) {
|
|
1577
|
+
const lossBandsOn = showLossBands ?? barColor === "loss";
|
|
856
1578
|
const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
|
|
857
|
-
const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
|
|
858
|
-
kind: "rep",
|
|
859
|
-
velocity: v,
|
|
860
|
-
leadingGap: i === 0 ? 0 : REP_GAP
|
|
861
|
-
}));
|
|
862
1579
|
const maxVelocity = Math.max(...doneVelocities, 0);
|
|
863
1580
|
const meanVelocity = calculateMeanVelocity(doneVelocities);
|
|
864
1581
|
const loss = calculateVelocityLoss(doneVelocities);
|
|
865
1582
|
const framed = showNumbers || showInfo;
|
|
866
|
-
const scaleDenom = scaleDenominator(scale, maxVelocity);
|
|
867
1583
|
const hasZones = zones != null && zones.length > 0;
|
|
868
|
-
const
|
|
869
|
-
const
|
|
870
|
-
if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
|
|
871
|
-
if (slot.kind === "todo") return TODO_COLOR2;
|
|
872
|
-
return SET_STRIP_VARIABLE_COLOR;
|
|
873
|
-
};
|
|
1584
|
+
const zoneColorFor = makeBarColorFor(zones);
|
|
1585
|
+
const barColorFor = (v) => barColor === "loss" ? getVelocityLossColor(velocityLossForRep(v, maxVelocity)) : zoneColorFor(v);
|
|
874
1586
|
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
875
|
-
const [
|
|
876
|
-
const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
1587
|
+
const [expandProgress] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
877
1588
|
const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
878
1589
|
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
879
1590
|
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
880
|
-
const liveScale = useLiveRepPop(
|
|
881
|
-
variant === "expanded" && framed,
|
|
882
|
-
liveRepIndex,
|
|
883
|
-
liveVelocity,
|
|
884
|
-
isNewPeak
|
|
885
|
-
);
|
|
886
1591
|
react.useEffect(() => {
|
|
887
1592
|
if (variant !== "expanded" || !framed) return;
|
|
888
1593
|
reactNative.Animated.parallel([
|
|
889
|
-
reactNative.Animated.timing(
|
|
890
|
-
toValue: expanded ?
|
|
1594
|
+
reactNative.Animated.timing(expandProgress, {
|
|
1595
|
+
toValue: expanded ? 1 : 0,
|
|
891
1596
|
duration: ANIMATION_DURATION,
|
|
892
1597
|
easing: ANIMATION_EASING,
|
|
893
1598
|
useNativeDriver: false
|
|
894
1599
|
}),
|
|
895
|
-
reactNative.Animated.timing(labelOpacity, {
|
|
896
|
-
toValue: expanded ? 1 : 0,
|
|
897
|
-
duration: 300,
|
|
898
|
-
delay: expanded ? 150 : 0,
|
|
899
|
-
useNativeDriver: false
|
|
900
|
-
}),
|
|
901
1600
|
reactNative.Animated.timing(infoOpacity, {
|
|
902
1601
|
toValue: expanded ? 1 : 0,
|
|
903
1602
|
duration: 300,
|
|
@@ -905,250 +1604,176 @@ function VelocityStrip({
|
|
|
905
1604
|
useNativeDriver: false
|
|
906
1605
|
})
|
|
907
1606
|
]).start();
|
|
908
|
-
}, [expanded, variant, framed,
|
|
1607
|
+
}, [expanded, variant, framed, expandProgress, infoOpacity]);
|
|
909
1608
|
if (set == null && velocities == null) return null;
|
|
910
1609
|
const repCount = doneVelocities.length;
|
|
911
1610
|
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
912
1611
|
if (variant === "hero") {
|
|
913
|
-
const heroHeight = height === EXPANDED_HEIGHT ?
|
|
1612
|
+
const heroHeight = height === EXPANDED_HEIGHT && columnSlots == null ? SET_BAR_DEFAULT_HEIGHT : height;
|
|
1613
|
+
const heroSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1614
|
+
kind: s.kind,
|
|
1615
|
+
value: s.velocity,
|
|
1616
|
+
leadingGap: s.leadingGap
|
|
1617
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
1618
|
+
const total = Math.max(repCount, targetReps ?? repCount);
|
|
1619
|
+
const heroLabel = maxVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(maxVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
914
1620
|
return /* @__PURE__ */ jsx(
|
|
915
|
-
|
|
1621
|
+
SetBarChart,
|
|
916
1622
|
{
|
|
917
|
-
|
|
918
|
-
barColorFor,
|
|
919
|
-
|
|
920
|
-
|
|
1623
|
+
slots: heroSlots,
|
|
1624
|
+
colorFor: barColorFor,
|
|
1625
|
+
height: heroHeight,
|
|
1626
|
+
scale,
|
|
1627
|
+
scaleMax,
|
|
1628
|
+
orientation,
|
|
921
1629
|
liveRepIndex,
|
|
922
1630
|
isNewPeak,
|
|
923
|
-
targetReps,
|
|
924
|
-
|
|
1631
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
1632
|
+
label,
|
|
1633
|
+
showValueLabels: true,
|
|
1634
|
+
formatValue: formatVelocity,
|
|
1635
|
+
renderReference: (g) => velocityReferenceOverlay(g, lossBandsOn),
|
|
1636
|
+
hideBaseline: true,
|
|
1637
|
+
testID: "velocity-strip-hero",
|
|
1638
|
+
testIDPrefix: "velocity",
|
|
1639
|
+
accessibilityLabel: heroLabel,
|
|
925
1640
|
className,
|
|
926
1641
|
viewProps: props
|
|
927
1642
|
}
|
|
928
1643
|
);
|
|
929
1644
|
}
|
|
930
|
-
if (variant === "
|
|
931
|
-
const
|
|
1645
|
+
if (variant === "compact") {
|
|
1646
|
+
const compactSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1647
|
+
kind: s.kind,
|
|
1648
|
+
value: s.velocity,
|
|
1649
|
+
leadingGap: s.leadingGap
|
|
1650
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
1651
|
+
const compactHeight = height === EXPANDED_HEIGHT ? COMPACT_HEIGHT : height;
|
|
932
1652
|
return /* @__PURE__ */ jsx(
|
|
933
|
-
|
|
1653
|
+
SetBarChart,
|
|
934
1654
|
{
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1655
|
+
slots: compactSlots,
|
|
1656
|
+
colorFor: barColorFor,
|
|
1657
|
+
height: compactHeight,
|
|
1658
|
+
scale,
|
|
1659
|
+
scaleMax,
|
|
1660
|
+
orientation,
|
|
1661
|
+
flat: true,
|
|
1662
|
+
barRadius: 2,
|
|
1663
|
+
cornerStyle: "all",
|
|
1664
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
1665
|
+
label,
|
|
1666
|
+
hideBaseline: true,
|
|
1667
|
+
testID: "velocity-strip-compact",
|
|
1668
|
+
testIDPrefix: "velocity",
|
|
941
1669
|
accessibilityLabel: miniLabel,
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
945
|
-
reactNative.View,
|
|
946
|
-
{
|
|
947
|
-
style: {
|
|
948
|
-
flex: 1,
|
|
949
|
-
backgroundColor: slotColor(slot),
|
|
950
|
-
borderRadius: 1,
|
|
951
|
-
minWidth: 4,
|
|
952
|
-
height: "100%",
|
|
953
|
-
// The container's uniform 2px gap covers rep spacing; a wide slot adds
|
|
954
|
-
// only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
|
|
955
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
956
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
957
|
-
},
|
|
958
|
-
accessibilityElementsHidden: true,
|
|
959
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
960
|
-
},
|
|
961
|
-
i
|
|
962
|
-
))
|
|
1670
|
+
className,
|
|
1671
|
+
viewProps: props
|
|
963
1672
|
}
|
|
964
1673
|
);
|
|
965
1674
|
}
|
|
966
1675
|
if (!framed) {
|
|
967
|
-
const
|
|
1676
|
+
const spotlightSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1677
|
+
kind: s.kind,
|
|
1678
|
+
value: s.velocity,
|
|
1679
|
+
leadingGap: s.leadingGap
|
|
1680
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
968
1681
|
return /* @__PURE__ */ jsx(
|
|
969
|
-
|
|
1682
|
+
SetBarChart,
|
|
970
1683
|
{
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1684
|
+
slots: spotlightSlots,
|
|
1685
|
+
colorFor: barColorFor,
|
|
1686
|
+
height,
|
|
1687
|
+
scale,
|
|
1688
|
+
scaleMax,
|
|
1689
|
+
orientation,
|
|
1690
|
+
liveRepIndex,
|
|
1691
|
+
isNewPeak,
|
|
1692
|
+
barRadius: 2,
|
|
1693
|
+
cornerStyle: "top",
|
|
1694
|
+
targetReps: set ? void 0 : targetReps,
|
|
1695
|
+
label,
|
|
1696
|
+
hideBaseline: true,
|
|
1697
|
+
testID: "velocity-strip-spotlight",
|
|
1698
|
+
testIDPrefix: "velocity",
|
|
977
1699
|
accessibilityLabel: miniLabel,
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
981
|
-
reactNative.View,
|
|
982
|
-
{
|
|
983
|
-
style: {
|
|
984
|
-
flex: 1,
|
|
985
|
-
minWidth: 4,
|
|
986
|
-
height: bareSlotHeight(slot, height, scaleDenom),
|
|
987
|
-
// Top-rounded to match the framed chart's bars (the "rounded tops from
|
|
988
|
-
// the numbers one"); square bottoms since bars sit on the baseline.
|
|
989
|
-
borderTopLeftRadius: 2,
|
|
990
|
-
borderTopRightRadius: 2,
|
|
991
|
-
backgroundColor: slotColor(slot),
|
|
992
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
993
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
994
|
-
},
|
|
995
|
-
accessibilityElementsHidden: true,
|
|
996
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
997
|
-
},
|
|
998
|
-
i
|
|
999
|
-
))
|
|
1700
|
+
className,
|
|
1701
|
+
viewProps: props
|
|
1000
1702
|
}
|
|
1001
1703
|
);
|
|
1002
1704
|
}
|
|
1003
1705
|
const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
|
|
1004
1706
|
const hasInteractiveContainer = onToggle != null;
|
|
1005
1707
|
const hasInteractiveReps = onRepPress != null && expanded;
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1708
|
+
const framedSlots = set ? buildSlots(set).map((sl) => ({
|
|
1709
|
+
kind: sl.kind,
|
|
1710
|
+
value: sl.velocity,
|
|
1711
|
+
leadingGap: sl.leadingGap
|
|
1712
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v }));
|
|
1713
|
+
const needsBarOverlay = showNumbers || onRepPress != null;
|
|
1714
|
+
const renderFramedBarOverlay = needsBarOverlay ? (repIndex, value) => /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
1715
|
+
showNumbers && /* @__PURE__ */ jsx(
|
|
1716
|
+
reactNative.Animated.View,
|
|
1011
1717
|
{
|
|
1012
1718
|
style: {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1719
|
+
opacity: expandProgress,
|
|
1720
|
+
position: "absolute",
|
|
1721
|
+
top: -13,
|
|
1722
|
+
left: 0,
|
|
1723
|
+
right: 0,
|
|
1724
|
+
alignItems: "center"
|
|
1017
1725
|
},
|
|
1726
|
+
accessibilityElementsHidden: true,
|
|
1727
|
+
pointerEvents: "none",
|
|
1018
1728
|
children: /* @__PURE__ */ jsx(
|
|
1019
|
-
reactNative.
|
|
1729
|
+
reactNative.Text,
|
|
1020
1730
|
{
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
borderTopRightRadius: 2,
|
|
1026
|
-
backgroundColor: slotColor(slot),
|
|
1027
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
1028
|
-
},
|
|
1029
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
|
|
1030
|
-
accessibilityRole: "image",
|
|
1031
|
-
accessibilityLabel: slotAccessibilityLabel(slot, i)
|
|
1731
|
+
className: "text-text-secondary",
|
|
1732
|
+
style: { fontSize: 8, fontWeight: "600" },
|
|
1733
|
+
testID: `velocity-label-${repIndex}`,
|
|
1734
|
+
children: formatVelocity(value)
|
|
1032
1735
|
}
|
|
1033
1736
|
)
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1737
|
+
}
|
|
1738
|
+
),
|
|
1739
|
+
onRepPress && expanded && /* @__PURE__ */ jsx(
|
|
1740
|
+
reactNative.Pressable,
|
|
1741
|
+
{
|
|
1742
|
+
style: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0 },
|
|
1743
|
+
onPress: () => onRepPress(repIndex, value),
|
|
1744
|
+
accessibilityRole: "button",
|
|
1745
|
+
accessibilityLabel: `Rep ${repIndex + 1}: ${formatVelocity(value)} meters per second, tap for details`,
|
|
1746
|
+
testID: `velocity-bar-pressable-${repIndex}`
|
|
1747
|
+
}
|
|
1748
|
+
)
|
|
1749
|
+
] }) : void 0;
|
|
1038
1750
|
const stripContent = /* @__PURE__ */ jsxs(
|
|
1039
1751
|
reactNative.Animated.View,
|
|
1040
1752
|
{
|
|
1041
|
-
className: [className,
|
|
1042
|
-
style:
|
|
1043
|
-
{
|
|
1044
|
-
height: heightAnim,
|
|
1045
|
-
width: "100%",
|
|
1046
|
-
gap: 2,
|
|
1047
|
-
borderRadius: expanded ? 6 : 2,
|
|
1048
|
-
paddingTop: expanded ? 16 : 0,
|
|
1049
|
-
paddingBottom: expanded ? 24 : 0,
|
|
1050
|
-
// No horizontal inset — the framed chart's bars span full width, matching
|
|
1051
|
-
// the bare spotlight and mini. Numbers/info sit in the vertical padding.
|
|
1052
|
-
paddingHorizontal: 0,
|
|
1053
|
-
paddingVertical: expanded ? void 0 : 8,
|
|
1054
|
-
overflow: expanded ? "visible" : "hidden"
|
|
1055
|
-
}
|
|
1056
|
-
],
|
|
1753
|
+
className: [className, "bg-surface-raised"].filter(Boolean).join(" "),
|
|
1754
|
+
style: { width: "100%", borderRadius: 6, paddingTop: 16, paddingBottom: showInfo ? 8 : 4 },
|
|
1057
1755
|
accessibilityRole: hasInteractiveContainer || hasInteractiveReps ? "none" : "button",
|
|
1058
1756
|
accessibilityLabel: hasInteractiveContainer || hasInteractiveReps ? void 0 : stripLabel,
|
|
1059
1757
|
testID: "velocity-strip",
|
|
1060
1758
|
...props,
|
|
1061
1759
|
children: [
|
|
1062
|
-
/* @__PURE__ */ jsx(
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
borderBottomLeftRadius: 0,
|
|
1078
|
-
borderBottomRightRadius: 0,
|
|
1079
|
-
backgroundColor: barBackground
|
|
1080
|
-
};
|
|
1081
|
-
const barInner = isLive ? /* @__PURE__ */ jsx(
|
|
1082
|
-
reactNative.Animated.View,
|
|
1083
|
-
{
|
|
1084
|
-
style: [barStyle, { transform: [{ scale: liveScale }] }],
|
|
1085
|
-
testID: `velocity-bar-${i}`,
|
|
1086
|
-
accessibilityRole: "image",
|
|
1087
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
|
|
1088
|
-
}
|
|
1089
|
-
) : /* @__PURE__ */ jsx(
|
|
1090
|
-
reactNative.View,
|
|
1091
|
-
{
|
|
1092
|
-
style: barStyle,
|
|
1093
|
-
testID: `velocity-bar-${i}`,
|
|
1094
|
-
accessibilityRole: "image",
|
|
1095
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
1096
|
-
}
|
|
1097
|
-
);
|
|
1098
|
-
const bar = /* @__PURE__ */ jsxs(
|
|
1099
|
-
reactNative.View,
|
|
1100
|
-
{
|
|
1101
|
-
style: {
|
|
1102
|
-
flex: 1,
|
|
1103
|
-
height: "100%",
|
|
1104
|
-
justifyContent: "flex-end",
|
|
1105
|
-
position: "relative"
|
|
1106
|
-
},
|
|
1107
|
-
children: [
|
|
1108
|
-
expanded && showNumbers && /* @__PURE__ */ jsx(
|
|
1109
|
-
reactNative.Animated.View,
|
|
1110
|
-
{
|
|
1111
|
-
style: {
|
|
1112
|
-
opacity: labelOpacity,
|
|
1113
|
-
alignItems: "center",
|
|
1114
|
-
position: "absolute",
|
|
1115
|
-
top: -13,
|
|
1116
|
-
left: 0,
|
|
1117
|
-
right: 0
|
|
1118
|
-
},
|
|
1119
|
-
accessibilityElementsHidden: true,
|
|
1120
|
-
children: /* @__PURE__ */ jsx(
|
|
1121
|
-
reactNative.Text,
|
|
1122
|
-
{
|
|
1123
|
-
className: "text-text-secondary",
|
|
1124
|
-
style: { fontSize: 8, fontWeight: "600" },
|
|
1125
|
-
testID: `velocity-label-${i}`,
|
|
1126
|
-
children: formatVelocity(v)
|
|
1127
|
-
}
|
|
1128
|
-
)
|
|
1129
|
-
}
|
|
1130
|
-
),
|
|
1131
|
-
barInner
|
|
1132
|
-
]
|
|
1133
|
-
},
|
|
1134
|
-
i
|
|
1135
|
-
);
|
|
1136
|
-
if (onRepPress && expanded) {
|
|
1137
|
-
return /* @__PURE__ */ jsx(
|
|
1138
|
-
reactNative.Pressable,
|
|
1139
|
-
{
|
|
1140
|
-
style: { flex: 1, height: "100%" },
|
|
1141
|
-
onPress: () => onRepPress(i, v),
|
|
1142
|
-
accessibilityRole: "button",
|
|
1143
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
|
|
1144
|
-
testID: `velocity-bar-pressable-${i}`,
|
|
1145
|
-
children: bar
|
|
1146
|
-
},
|
|
1147
|
-
i
|
|
1148
|
-
);
|
|
1760
|
+
/* @__PURE__ */ jsx(
|
|
1761
|
+
SetBarChart,
|
|
1762
|
+
{
|
|
1763
|
+
slots: framedSlots,
|
|
1764
|
+
colorFor: barColorFor,
|
|
1765
|
+
height,
|
|
1766
|
+
scale,
|
|
1767
|
+
scaleMax,
|
|
1768
|
+
expandProgress,
|
|
1769
|
+
renderBarOverlay: renderFramedBarOverlay,
|
|
1770
|
+
targetReps: set ? void 0 : targetReps,
|
|
1771
|
+
barRadius: 2,
|
|
1772
|
+
cornerStyle: "top",
|
|
1773
|
+
hideBaseline: true,
|
|
1774
|
+
testIDPrefix: "velocity"
|
|
1149
1775
|
}
|
|
1150
|
-
|
|
1151
|
-
}) }),
|
|
1776
|
+
),
|
|
1152
1777
|
expanded && showInfo && /* @__PURE__ */ jsxs(
|
|
1153
1778
|
reactNative.Animated.View,
|
|
1154
1779
|
{
|
|
@@ -1156,10 +1781,8 @@ function VelocityStrip({
|
|
|
1156
1781
|
flexDirection: "row",
|
|
1157
1782
|
justifyContent: "space-between",
|
|
1158
1783
|
opacity: infoOpacity,
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
left: 6,
|
|
1162
|
-
right: 6
|
|
1784
|
+
marginTop: 6,
|
|
1785
|
+
paddingHorizontal: 6
|
|
1163
1786
|
},
|
|
1164
1787
|
testID: "velocity-info-row",
|
|
1165
1788
|
children: [
|
|
@@ -1167,10 +1790,7 @@ function VelocityStrip({
|
|
|
1167
1790
|
reactNative.Text,
|
|
1168
1791
|
{
|
|
1169
1792
|
className: "text-text-secondary",
|
|
1170
|
-
style: {
|
|
1171
|
-
fontSize: 10,
|
|
1172
|
-
fontFamily: "Inter, sans-serif"
|
|
1173
|
-
},
|
|
1793
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif" },
|
|
1174
1794
|
children: [
|
|
1175
1795
|
meanZone,
|
|
1176
1796
|
" ",
|
|
@@ -1185,11 +1805,7 @@ function VelocityStrip({
|
|
|
1185
1805
|
reactNative.Text,
|
|
1186
1806
|
{
|
|
1187
1807
|
className: "text-text-secondary",
|
|
1188
|
-
style: {
|
|
1189
|
-
fontSize: 10,
|
|
1190
|
-
fontFamily: "Inter, sans-serif",
|
|
1191
|
-
...getLossStyle(loss)
|
|
1192
|
-
},
|
|
1808
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif", ...getLossStyle(loss) },
|
|
1193
1809
|
children: [
|
|
1194
1810
|
"Loss: ",
|
|
1195
1811
|
loss,
|
|
@@ -1368,13 +1984,14 @@ function InfoIcon(props) {
|
|
|
1368
1984
|
/* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
|
|
1369
1985
|
] });
|
|
1370
1986
|
}
|
|
1987
|
+
var t = getSemanticColors("dark");
|
|
1371
1988
|
var baseBadgeSizeConfig = {
|
|
1372
1989
|
sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
|
|
1373
1990
|
md: { fontSize: 10, paddingH: 8, paddingV: 2, iconSize: 12 },
|
|
1374
1991
|
lg: { fontSize: 12, paddingH: 10, paddingV: 4, iconSize: 14 }
|
|
1375
1992
|
};
|
|
1376
1993
|
var variantColors = {
|
|
1377
|
-
plain: { backgroundColor: "
|
|
1994
|
+
plain: { backgroundColor: t["surface-raised"], borderColor: "#1F1F1F" },
|
|
1378
1995
|
pr: {
|
|
1379
1996
|
backgroundColor: "rgba(255, 121, 0, 0.12)",
|
|
1380
1997
|
borderColor: "rgba(255, 121, 0, 0.3)"
|
|
@@ -1406,310 +2023,44 @@ function BaseBadge({
|
|
|
1406
2023
|
useNativeDriver: true
|
|
1407
2024
|
}).start();
|
|
1408
2025
|
};
|
|
1409
|
-
const badge = /* @__PURE__ */ jsxs(
|
|
1410
|
-
reactNative.View,
|
|
1411
|
-
{
|
|
1412
|
-
className,
|
|
1413
|
-
style: {
|
|
1414
|
-
flexDirection: "row",
|
|
1415
|
-
alignItems: "center",
|
|
1416
|
-
gap: 3,
|
|
1417
|
-
// borderRadius 2 is intentional per workout-tokens.ts (squared-off pill)
|
|
1418
|
-
borderRadius: 2,
|
|
1419
|
-
borderWidth: 1,
|
|
1420
|
-
borderColor: colors.borderColor,
|
|
1421
|
-
backgroundColor: colors.backgroundColor,
|
|
1422
|
-
paddingHorizontal: config.paddingH,
|
|
1423
|
-
paddingVertical: config.paddingV
|
|
1424
|
-
},
|
|
1425
|
-
...props,
|
|
1426
|
-
children: [
|
|
1427
|
-
icon != null && /* @__PURE__ */ jsx(reactNative.View, { accessibilityElementsHidden: true, testID: "base-badge-icon", children: icon }),
|
|
1428
|
-
children
|
|
1429
|
-
]
|
|
1430
|
-
}
|
|
1431
|
-
);
|
|
1432
|
-
if (onPress) {
|
|
1433
|
-
return /* @__PURE__ */ jsx(reactNative.Animated.View, { style: { transform: [{ scale: scaleAnim }] }, children: /* @__PURE__ */ jsx(
|
|
1434
|
-
reactNative.Pressable,
|
|
1435
|
-
{
|
|
1436
|
-
onPress,
|
|
1437
|
-
onPressIn: handlePressIn,
|
|
1438
|
-
onPressOut: handlePressOut,
|
|
1439
|
-
accessibilityRole: "button",
|
|
1440
|
-
accessibilityLabel: props.accessibilityLabel,
|
|
1441
|
-
testID: "base-badge-pressable",
|
|
1442
|
-
children: badge
|
|
1443
|
-
}
|
|
1444
|
-
) });
|
|
1445
|
-
}
|
|
1446
|
-
return badge;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
// src/theme/tokens/semantic.ts
|
|
1450
|
-
var semanticColorsLight = {
|
|
1451
|
-
// Brand colors (brand-*)
|
|
1452
|
-
"brand-primary": primitiveRamps.orange[400],
|
|
1453
|
-
"brand-primary-light": primitiveRamps.orange[300],
|
|
1454
|
-
"brand-primary-dark": primitiveRamps.orange[500],
|
|
1455
|
-
"brand-primary-subtle": "rgba(255, 121, 0, 0.08)",
|
|
1456
|
-
"brand-primary-hover": primitiveRamps.orange[500],
|
|
1457
|
-
"brand-primary-active": primitiveRamps.orange[600],
|
|
1458
|
-
"brand-secondary": primitiveRamps.cyan[600],
|
|
1459
|
-
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
1460
|
-
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
1461
|
-
"brand-secondary-subtle": "rgba(48, 123, 155, 0.08)",
|
|
1462
|
-
"brand-secondary-hover": primitiveRamps.cyan[700],
|
|
1463
|
-
"brand-secondary-active": primitiveRamps.cyan[800],
|
|
1464
|
-
// Text on brand backgrounds (on-*)
|
|
1465
|
-
"on-brand-primary": primitiveColors.white,
|
|
1466
|
-
"on-brand-secondary": primitiveColors.white,
|
|
1467
|
-
// Status colors (status-*)
|
|
1468
|
-
"status-success": primitiveRamps.green[300],
|
|
1469
|
-
"status-success-light": primitiveRamps.green[200],
|
|
1470
|
-
"status-success-dark": primitiveRamps.green[600],
|
|
1471
|
-
"status-success-subtle": primitiveRamps.green[50],
|
|
1472
|
-
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1473
|
-
"status-live": primitiveRamps.green[300],
|
|
1474
|
-
"status-live-muted": primitiveRamps.green[500],
|
|
1475
|
-
"status-error": primitiveRamps.red[600],
|
|
1476
|
-
"status-error-light": primitiveRamps.red[500],
|
|
1477
|
-
"status-error-dark": primitiveRamps.red[700],
|
|
1478
|
-
"status-error-subtle": primitiveRamps.red[50],
|
|
1479
|
-
"status-error-vivid": primitiveColors.redVivid[500],
|
|
1480
|
-
// vivid alert red
|
|
1481
|
-
"status-error-vivid-light": primitiveColors.redVivid[400],
|
|
1482
|
-
"status-error-vivid-dark": primitiveColors.redVivid[700],
|
|
1483
|
-
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
1484
|
-
"status-warning": primitiveRamps.amber[300],
|
|
1485
|
-
"status-warning-light": primitiveRamps.amber[200],
|
|
1486
|
-
"status-warning-dark": primitiveRamps.amber[500],
|
|
1487
|
-
"status-warning-subtle": primitiveRamps.amber[50],
|
|
1488
|
-
"status-info": primitiveRamps.blue[500],
|
|
1489
|
-
"status-info-light": primitiveRamps.blue[300],
|
|
1490
|
-
"status-info-dark": primitiveRamps.blue[600],
|
|
1491
|
-
"status-info-subtle": primitiveRamps.blue[50],
|
|
1492
|
-
// Text on status backgrounds (on-status-*)
|
|
1493
|
-
"on-status-success": primitiveColors.white,
|
|
1494
|
-
"on-status-error": primitiveColors.white,
|
|
1495
|
-
"on-status-warning": primitiveColors.white,
|
|
1496
|
-
"on-status-info": primitiveColors.white,
|
|
1497
|
-
// Result/outcome indicators (result-*)
|
|
1498
|
-
"result-improve": "#4caf50",
|
|
1499
|
-
// Green - positive outcome
|
|
1500
|
-
"result-improve-light": "rgba(76, 175, 80, 0.12)",
|
|
1501
|
-
"result-improve-dark": "#248a24",
|
|
1502
|
-
"result-degrade": "#ef5350",
|
|
1503
|
-
// Red - negative outcome
|
|
1504
|
-
"result-degrade-light": "rgba(239, 83, 80, 0.12)",
|
|
1505
|
-
"result-degrade-dark": "#b30000",
|
|
1506
|
-
"result-inconclusive": "#9E9A97",
|
|
1507
|
-
// Gray - no clear result
|
|
1508
|
-
"result-inconclusive-light": "rgba(158, 154, 151, 0.12)",
|
|
1509
|
-
"result-neutral": primitiveColors.neutral[500],
|
|
1510
|
-
// Neutral baseline
|
|
1511
|
-
// Text on result backgrounds (on-result-*)
|
|
1512
|
-
"on-result-improve": primitiveColors.white,
|
|
1513
|
-
"on-result-degrade": primitiveColors.white,
|
|
1514
|
-
"on-result-inconclusive": primitiveColors.white,
|
|
1515
|
-
// Data visualization colors (data-*)
|
|
1516
|
-
// First 10 colors from discrete rainbow optimized for charts
|
|
1517
|
-
"data-1": discreteRainbow[9],
|
|
1518
|
-
// Blue
|
|
1519
|
-
"data-2": discreteRainbow[14],
|
|
1520
|
-
// Green
|
|
1521
|
-
"data-3": discreteRainbow[17],
|
|
1522
|
-
// Yellow
|
|
1523
|
-
"data-4": discreteRainbow[25],
|
|
1524
|
-
// Red
|
|
1525
|
-
"data-5": discreteRainbow[8],
|
|
1526
|
-
// Purple
|
|
1527
|
-
"data-6": discreteRainbow[20],
|
|
1528
|
-
// Orange
|
|
1529
|
-
"data-7": discreteRainbow[13],
|
|
1530
|
-
// Light Blue
|
|
1531
|
-
"data-8": discreteRainbow[15],
|
|
1532
|
-
// Light Green
|
|
1533
|
-
"data-9": discreteRainbow[3],
|
|
1534
|
-
// Lavender
|
|
1535
|
-
"data-10": discreteRainbow[22],
|
|
1536
|
-
// Dark Orange
|
|
1537
|
-
// Text colors (text-*)
|
|
1538
|
-
"text-primary": "#121828",
|
|
1539
|
-
"text-secondary": "#65748B",
|
|
1540
|
-
"text-tertiary": primitiveColors.neutral[400],
|
|
1541
|
-
"text-disabled": "rgba(55, 65, 81, 0.48)",
|
|
1542
|
-
"text-inverse": primitiveColors.white,
|
|
1543
|
-
"text-link": primitiveColors.blue[600],
|
|
1544
|
-
"text-link-hover": primitiveColors.blue[700],
|
|
1545
|
-
// Surface colors (surface-*) - for elevated containers
|
|
1546
|
-
"surface-base": primitiveColors.white,
|
|
1547
|
-
"surface-elevated": primitiveColors.neutral[50],
|
|
1548
|
-
// slightly off-white for elevated cards
|
|
1549
|
-
"surface-raised": primitiveColors.neutral[100],
|
|
1550
|
-
// light gray for raised cards
|
|
1551
|
-
"surface-overlay": primitiveColors.white,
|
|
1552
|
-
"surface-input": primitiveColors.neutral[50],
|
|
1553
|
-
// Input field background (filled variant)
|
|
1554
|
-
// Background colors (background-*)
|
|
1555
|
-
"background-base": "#EBEBEB",
|
|
1556
|
-
"background-default": primitiveColors.white,
|
|
1557
|
-
"background-subtle": primitiveColors.neutral[50],
|
|
1558
|
-
// Border colors (border-*)
|
|
1559
|
-
"border-default": "#E8E9EB",
|
|
1560
|
-
"border-subtle": primitiveColors.neutral[100],
|
|
1561
|
-
"border-strong": primitiveColors.neutral[300],
|
|
1562
|
-
"border-prominent": primitiveColors.neutral[400],
|
|
1563
|
-
// high-visibility divider
|
|
1564
|
-
"border-focus": primitiveColors.blue[600],
|
|
1565
|
-
"border-input": primitiveColors.neutral[300],
|
|
1566
|
-
// Input field border
|
|
1567
|
-
"border-input-hover": primitiveColors.neutral[400],
|
|
1568
|
-
// Input field border on hover
|
|
1569
|
-
"border-input-focus": primitiveColors.blue[600],
|
|
1570
|
-
// Input field border on focus
|
|
1571
|
-
"border-input-error": primitiveColors.red[600],
|
|
1572
|
-
// Input field border on error
|
|
1573
|
-
// Interactive states (interactive-*)
|
|
1574
|
-
"interactive-hover": "rgba(55, 65, 81, 0.04)",
|
|
1575
|
-
"interactive-focus": "rgba(55, 65, 81, 0.12)",
|
|
1576
|
-
"interactive-active": "rgba(55, 65, 81, 0.16)",
|
|
1577
|
-
"interactive-selected": "rgba(55, 65, 81, 0.08)",
|
|
1578
|
-
"interactive-disabled": "rgba(55, 65, 81, 0.12)",
|
|
1579
|
-
"interactive-disabled-text": "rgba(55, 65, 81, 0.26)",
|
|
1580
|
-
// Divider
|
|
1581
|
-
"divider": "#E8E9EB",
|
|
1582
|
-
// Avatar default
|
|
1583
|
-
"avatar-background": primitiveColors.neutral[500],
|
|
1584
|
-
"avatar-text": primitiveColors.white
|
|
1585
|
-
};
|
|
1586
|
-
var semanticColorsDark = {
|
|
1587
|
-
// Brand colors stay the same in dark mode
|
|
1588
|
-
"brand-primary": primitiveRamps.orange[400],
|
|
1589
|
-
"brand-primary-light": primitiveRamps.orange[300],
|
|
1590
|
-
"brand-primary-dark": primitiveRamps.orange[500],
|
|
1591
|
-
"brand-primary-subtle": "rgba(255, 121, 0, 0.12)",
|
|
1592
|
-
"brand-primary-hover": primitiveRamps.orange[300],
|
|
1593
|
-
"brand-primary-active": primitiveRamps.orange[200],
|
|
1594
|
-
"brand-secondary": primitiveRamps.cyan[600],
|
|
1595
|
-
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
1596
|
-
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
1597
|
-
"brand-secondary-subtle": "rgba(48, 123, 155, 0.12)",
|
|
1598
|
-
"brand-secondary-hover": primitiveRamps.cyan[500],
|
|
1599
|
-
"brand-secondary-active": primitiveRamps.cyan[400],
|
|
1600
|
-
// Text on brand backgrounds
|
|
1601
|
-
"on-brand-primary": primitiveColors.white,
|
|
1602
|
-
"on-brand-secondary": primitiveColors.white,
|
|
1603
|
-
// Status colors
|
|
1604
|
-
"status-success": primitiveRamps.green[300],
|
|
1605
|
-
"status-success-light": primitiveRamps.green[200],
|
|
1606
|
-
"status-success-dark": primitiveRamps.green[600],
|
|
1607
|
-
"status-success-subtle": "rgba(46, 213, 115, 0.12)",
|
|
1608
|
-
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1609
|
-
"status-live": primitiveRamps.green[300],
|
|
1610
|
-
"status-live-muted": primitiveRamps.green[500],
|
|
1611
|
-
"status-error": primitiveRamps.red[600],
|
|
1612
|
-
"status-error-light": primitiveRamps.red[500],
|
|
1613
|
-
"status-error-dark": primitiveRamps.red[700],
|
|
1614
|
-
"status-error-subtle": "rgba(209, 67, 67, 0.12)",
|
|
1615
|
-
"status-error-vivid": primitiveColors.redVivid[500],
|
|
1616
|
-
"status-error-vivid-light": primitiveColors.redVivid[400],
|
|
1617
|
-
"status-error-vivid-dark": primitiveColors.redVivid[700],
|
|
1618
|
-
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
1619
|
-
"status-warning": primitiveRamps.amber[300],
|
|
1620
|
-
"status-warning-light": primitiveRamps.amber[200],
|
|
1621
|
-
"status-warning-dark": primitiveRamps.amber[500],
|
|
1622
|
-
"status-warning-subtle": "rgba(249, 180, 21, 0.12)",
|
|
1623
|
-
"status-info": primitiveRamps.blue[500],
|
|
1624
|
-
"status-info-light": primitiveRamps.blue[300],
|
|
1625
|
-
"status-info-dark": primitiveRamps.blue[600],
|
|
1626
|
-
"status-info-subtle": "rgba(33, 150, 243, 0.12)",
|
|
1627
|
-
// Text on status backgrounds
|
|
1628
|
-
"on-status-success": primitiveColors.white,
|
|
1629
|
-
"on-status-error": primitiveColors.white,
|
|
1630
|
-
"on-status-warning": primitiveColors.white,
|
|
1631
|
-
"on-status-info": primitiveColors.white,
|
|
1632
|
-
// Result/outcome indicators (result-*)
|
|
1633
|
-
"result-improve": "#4caf50",
|
|
1634
|
-
"result-improve-light": "rgba(76, 175, 80, 0.16)",
|
|
1635
|
-
"result-improve-dark": "#248a24",
|
|
1636
|
-
"result-degrade": "#ef5350",
|
|
1637
|
-
"result-degrade-light": "rgba(239, 83, 80, 0.16)",
|
|
1638
|
-
"result-degrade-dark": "#b30000",
|
|
1639
|
-
"result-inconclusive": "#9E9A97",
|
|
1640
|
-
"result-inconclusive-light": "rgba(158, 154, 151, 0.16)",
|
|
1641
|
-
"result-neutral": primitiveColors.neutral[400],
|
|
1642
|
-
// Text on result backgrounds
|
|
1643
|
-
"on-result-improve": primitiveColors.white,
|
|
1644
|
-
"on-result-degrade": primitiveColors.white,
|
|
1645
|
-
"on-result-inconclusive": primitiveColors.white,
|
|
1646
|
-
// Data visualization colors (same in dark mode for consistency)
|
|
1647
|
-
"data-1": discreteRainbow[9],
|
|
1648
|
-
"data-2": discreteRainbow[14],
|
|
1649
|
-
"data-3": discreteRainbow[17],
|
|
1650
|
-
"data-4": discreteRainbow[25],
|
|
1651
|
-
"data-5": discreteRainbow[8],
|
|
1652
|
-
"data-6": discreteRainbow[20],
|
|
1653
|
-
"data-7": discreteRainbow[13],
|
|
1654
|
-
"data-8": discreteRainbow[15],
|
|
1655
|
-
"data-9": discreteRainbow[3],
|
|
1656
|
-
"data-10": discreteRainbow[22],
|
|
1657
|
-
// Text colors - inverted for dark mode
|
|
1658
|
-
"text-primary": primitiveColors.neutral[100],
|
|
1659
|
-
"text-secondary": primitiveColors.neutral[400],
|
|
1660
|
-
"text-tertiary": primitiveColors.neutral[500],
|
|
1661
|
-
"text-disabled": "rgba(255, 255, 255, 0.38)",
|
|
1662
|
-
"text-inverse": primitiveColors.neutral[900],
|
|
1663
|
-
"text-link": "#828DF8",
|
|
1664
|
-
"text-link-hover": primitiveColors.blue[400],
|
|
1665
|
-
// Surface colors - dark backgrounds
|
|
1666
|
-
"surface-base": primitiveColors.charcoal[700],
|
|
1667
|
-
// main surface
|
|
1668
|
-
"surface-elevated": primitiveColors.charcoal[600],
|
|
1669
|
-
// elevated surface
|
|
1670
|
-
"surface-raised": primitiveColors.charcoal[500],
|
|
1671
|
-
// raised surface
|
|
1672
|
-
"surface-overlay": primitiveColors.charcoal[600],
|
|
1673
|
-
// overlay surface
|
|
1674
|
-
"surface-input": primitiveColors.charcoal[600],
|
|
1675
|
-
// input surface
|
|
1676
|
-
// Background colors
|
|
1677
|
-
"background-base": primitiveColors.charcoal[900],
|
|
1678
|
-
// darkest charcoal
|
|
1679
|
-
"background-default": primitiveColors.charcoal[700],
|
|
1680
|
-
// main background
|
|
1681
|
-
"background-subtle": primitiveColors.charcoal[500],
|
|
1682
|
-
// subtle background
|
|
1683
|
-
// Border colors
|
|
1684
|
-
"border-default": primitiveColors.charcoal[400],
|
|
1685
|
-
// default border
|
|
1686
|
-
"border-subtle": primitiveColors.charcoal[500],
|
|
1687
|
-
// subtle border
|
|
1688
|
-
"border-strong": primitiveColors.charcoal[300],
|
|
1689
|
-
// strong border
|
|
1690
|
-
"border-prominent": primitiveColors.charcoal[200],
|
|
1691
|
-
// high-visibility divider
|
|
1692
|
-
"border-focus": "#828DF8",
|
|
1693
|
-
"border-input": primitiveColors.neutral[600],
|
|
1694
|
-
"border-input-hover": primitiveColors.neutral[500],
|
|
1695
|
-
"border-input-focus": "#828DF8",
|
|
1696
|
-
"border-input-error": primitiveColors.red[500],
|
|
1697
|
-
// Interactive states
|
|
1698
|
-
"interactive-hover": "rgba(255, 255, 255, 0.04)",
|
|
1699
|
-
"interactive-focus": "rgba(255, 255, 255, 0.12)",
|
|
1700
|
-
"interactive-active": "rgba(255, 255, 255, 0.16)",
|
|
1701
|
-
"interactive-selected": "rgba(255, 255, 255, 0.08)",
|
|
1702
|
-
"interactive-disabled": "rgba(255, 255, 255, 0.12)",
|
|
1703
|
-
"interactive-disabled-text": "rgba(255, 255, 255, 0.26)",
|
|
1704
|
-
// Divider
|
|
1705
|
-
"divider": primitiveColors.charcoal[400],
|
|
1706
|
-
// divider color
|
|
1707
|
-
// Avatar default
|
|
1708
|
-
"avatar-background": primitiveColors.neutral[600],
|
|
1709
|
-
"avatar-text": primitiveColors.white
|
|
1710
|
-
};
|
|
1711
|
-
function getSemanticColors(mode) {
|
|
1712
|
-
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
2026
|
+
const badge = /* @__PURE__ */ jsxs(
|
|
2027
|
+
reactNative.View,
|
|
2028
|
+
{
|
|
2029
|
+
className,
|
|
2030
|
+
style: {
|
|
2031
|
+
flexDirection: "row",
|
|
2032
|
+
alignItems: "center",
|
|
2033
|
+
gap: 3,
|
|
2034
|
+
// borderRadius 2 is intentional per workout-tokens.ts (squared-off pill)
|
|
2035
|
+
borderRadius: 2,
|
|
2036
|
+
borderWidth: 1,
|
|
2037
|
+
borderColor: colors.borderColor,
|
|
2038
|
+
backgroundColor: colors.backgroundColor,
|
|
2039
|
+
paddingHorizontal: config.paddingH,
|
|
2040
|
+
paddingVertical: config.paddingV
|
|
2041
|
+
},
|
|
2042
|
+
...props,
|
|
2043
|
+
children: [
|
|
2044
|
+
icon != null && /* @__PURE__ */ jsx(reactNative.View, { accessibilityElementsHidden: true, testID: "base-badge-icon", children: icon }),
|
|
2045
|
+
children
|
|
2046
|
+
]
|
|
2047
|
+
}
|
|
2048
|
+
);
|
|
2049
|
+
if (onPress) {
|
|
2050
|
+
return /* @__PURE__ */ jsx(reactNative.Animated.View, { style: { transform: [{ scale: scaleAnim }] }, children: /* @__PURE__ */ jsx(
|
|
2051
|
+
reactNative.Pressable,
|
|
2052
|
+
{
|
|
2053
|
+
onPress,
|
|
2054
|
+
onPressIn: handlePressIn,
|
|
2055
|
+
onPressOut: handlePressOut,
|
|
2056
|
+
accessibilityRole: "button",
|
|
2057
|
+
accessibilityLabel: props.accessibilityLabel,
|
|
2058
|
+
testID: "base-badge-pressable",
|
|
2059
|
+
children: badge
|
|
2060
|
+
}
|
|
2061
|
+
) });
|
|
2062
|
+
}
|
|
2063
|
+
return badge;
|
|
1713
2064
|
}
|
|
1714
2065
|
|
|
1715
2066
|
// src/components/custom/Workout/PrBadge.tsx
|
|
@@ -1762,563 +2113,255 @@ function PrBadge({
|
|
|
1762
2113
|
) : /* @__PURE__ */ jsx(
|
|
1763
2114
|
BaseBadge,
|
|
1764
2115
|
{
|
|
1765
|
-
variant: "pr",
|
|
1766
|
-
className,
|
|
1767
|
-
accessibilityLabel: `Personal record: ${resolvedLabel}`,
|
|
1768
|
-
...props,
|
|
1769
|
-
children: /* @__PURE__ */ jsxs(
|
|
1770
|
-
reactNative.Text,
|
|
1771
|
-
{
|
|
1772
|
-
style: {
|
|
1773
|
-
fontSize: 12,
|
|
1774
|
-
color: BRAND_PRIMARY,
|
|
1775
|
-
fontWeight: "700",
|
|
1776
|
-
fontFamily: "Inter, sans-serif"
|
|
1777
|
-
},
|
|
1778
|
-
children: [
|
|
1779
|
-
"\u2605",
|
|
1780
|
-
" ",
|
|
1781
|
-
resolvedLabel
|
|
1782
|
-
]
|
|
1783
|
-
}
|
|
1784
|
-
)
|
|
1785
|
-
}
|
|
1786
|
-
);
|
|
1787
|
-
if (animate) {
|
|
1788
|
-
return /* @__PURE__ */ jsx(
|
|
1789
|
-
reactNative.Animated.View,
|
|
1790
|
-
{
|
|
1791
|
-
style: { transform: [{ scale }], opacity },
|
|
1792
|
-
testID: "pr-badge-animated",
|
|
1793
|
-
children: badge
|
|
1794
|
-
}
|
|
1795
|
-
);
|
|
1796
|
-
}
|
|
1797
|
-
return badge;
|
|
1798
|
-
}
|
|
1799
|
-
var DARK = getSemanticColors("dark");
|
|
1800
|
-
var TEXT_ACTIVE = DARK["text-primary"];
|
|
1801
|
-
var TEXT_MUTED = DARK["text-secondary"];
|
|
1802
|
-
var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
|
|
1803
|
-
var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
|
|
1804
|
-
var typeBadge = {
|
|
1805
|
-
fontSize: 10,
|
|
1806
|
-
fontWeight: "700",
|
|
1807
|
-
fontFamily: "Inter, sans-serif",
|
|
1808
|
-
color: WORKOUT_TOKENS.scale.orange,
|
|
1809
|
-
backgroundColor: "rgba(255,165,2,0.12)",
|
|
1810
|
-
paddingVertical: 1,
|
|
1811
|
-
paddingHorizontal: 5,
|
|
1812
|
-
borderRadius: 3,
|
|
1813
|
-
overflow: "hidden"
|
|
1814
|
-
};
|
|
1815
|
-
function displayReps(set) {
|
|
1816
|
-
return set.state === "done" ? set.reps : set.target.reps;
|
|
1817
|
-
}
|
|
1818
|
-
function displayWeight(set) {
|
|
1819
|
-
return set.state === "done" ? set.weight : set.target.weight;
|
|
1820
|
-
}
|
|
1821
|
-
function plannedReps(set) {
|
|
1822
|
-
if (set.state === "done") return set.velocities.length;
|
|
1823
|
-
if (set.state === "live") return set.target.reps;
|
|
1824
|
-
return set.planned ?? set.target.reps;
|
|
1825
|
-
}
|
|
1826
|
-
function accessibilityLabel(set) {
|
|
1827
|
-
const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
|
|
1828
|
-
if (set.state === "live") return `${base}, in progress`;
|
|
1829
|
-
if (set.state === "todo") return `${base}, upcoming`;
|
|
1830
|
-
return base;
|
|
1831
|
-
}
|
|
1832
|
-
function Cell({
|
|
1833
|
-
width,
|
|
1834
|
-
children,
|
|
1835
|
-
testID
|
|
1836
|
-
}) {
|
|
1837
|
-
return /* @__PURE__ */ jsx(reactNative.View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
|
|
1838
|
-
}
|
|
1839
|
-
function RowStrip({ set }) {
|
|
1840
|
-
const zones = set.velocityZones;
|
|
1841
|
-
if (set.state === "live") {
|
|
1842
|
-
return /* @__PURE__ */ jsx(
|
|
1843
|
-
VelocityStrip,
|
|
1844
|
-
{
|
|
1845
|
-
variant: "expanded",
|
|
1846
|
-
showNumbers: false,
|
|
1847
|
-
showInfo: false,
|
|
1848
|
-
height: 24,
|
|
1849
|
-
scale: "fixed",
|
|
1850
|
-
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
1851
|
-
zones
|
|
1852
|
-
}
|
|
1853
|
-
);
|
|
1854
|
-
}
|
|
1855
|
-
const velocities = set.state === "done" ? set.velocities : [];
|
|
1856
|
-
return /* @__PURE__ */ jsx(
|
|
1857
|
-
VelocityStrip,
|
|
1858
|
-
{
|
|
1859
|
-
variant: "mini",
|
|
1860
|
-
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
1861
|
-
zones
|
|
1862
|
-
}
|
|
1863
|
-
);
|
|
1864
|
-
}
|
|
1865
|
-
function SetRow(set) {
|
|
1866
|
-
const live = set.state === "live";
|
|
1867
|
-
const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
|
|
1868
|
-
const rpe = set.state === "todo" ? null : set.rpe;
|
|
1869
|
-
return /* @__PURE__ */ jsxs(
|
|
1870
|
-
reactNative.View,
|
|
1871
|
-
{
|
|
1872
|
-
style: { paddingVertical: 6, paddingHorizontal: 8 },
|
|
1873
|
-
accessibilityLabel: accessibilityLabel(set),
|
|
1874
|
-
testID: "set-row",
|
|
1875
|
-
children: [
|
|
1876
|
-
/* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
|
|
1877
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(reactNative.Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
|
|
1878
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
|
|
1879
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
|
|
1880
|
-
/* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
|
|
1881
|
-
] }),
|
|
1882
|
-
/* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
|
|
1883
|
-
]
|
|
1884
|
-
}
|
|
1885
|
-
);
|
|
1886
|
-
}
|
|
1887
|
-
var PREV_COLUMN = { label: "PREV", width: void 0 };
|
|
1888
|
-
function buildColumns(unit, showPrevious) {
|
|
1889
|
-
return [
|
|
1890
|
-
{ label: "SET", width: 36 },
|
|
1891
|
-
...showPrevious ? [PREV_COLUMN] : [],
|
|
1892
|
-
{ label: "REPS", width: 44 },
|
|
1893
|
-
{ label: unit.toUpperCase(), width: 56 },
|
|
1894
|
-
{ label: "RPE", width: 36 }
|
|
1895
|
-
];
|
|
1896
|
-
}
|
|
1897
|
-
function SetTableHeader({
|
|
1898
|
-
unit = "lbs",
|
|
1899
|
-
showPrevious = true,
|
|
1900
|
-
testID = "table-header"
|
|
1901
|
-
}) {
|
|
1902
|
-
const columns = buildColumns(unit, showPrevious);
|
|
1903
|
-
return /* @__PURE__ */ jsx(
|
|
1904
|
-
reactNative.View,
|
|
1905
|
-
{
|
|
1906
|
-
className: "flex-row",
|
|
1907
|
-
style: {
|
|
1908
|
-
paddingVertical: 8,
|
|
1909
|
-
paddingHorizontal: 8,
|
|
1910
|
-
paddingBottom: 4,
|
|
1911
|
-
...showPrevious ? {} : { justifyContent: "space-between" }
|
|
1912
|
-
},
|
|
1913
|
-
testID,
|
|
1914
|
-
children: columns.map(({ label, width }) => {
|
|
1915
|
-
const isFlex = width === void 0;
|
|
1916
|
-
return /* @__PURE__ */ jsx(
|
|
1917
|
-
reactNative.View,
|
|
1918
|
-
{
|
|
1919
|
-
style: {
|
|
1920
|
-
...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
|
|
1921
|
-
alignItems: "center",
|
|
1922
|
-
justifyContent: "center",
|
|
1923
|
-
gap: 8
|
|
1924
|
-
},
|
|
1925
|
-
...isFlex ? { className: "flex-1" } : {},
|
|
1926
|
-
children: /* @__PURE__ */ jsx(
|
|
1927
|
-
reactNative.Text,
|
|
1928
|
-
{
|
|
1929
|
-
className: "text-text-tertiary",
|
|
1930
|
-
style: {
|
|
1931
|
-
fontSize: 10,
|
|
1932
|
-
fontWeight: "600",
|
|
1933
|
-
fontFamily: "Inter, sans-serif",
|
|
1934
|
-
textTransform: "uppercase",
|
|
1935
|
-
letterSpacing: 0.8
|
|
1936
|
-
},
|
|
1937
|
-
children: label
|
|
1938
|
-
}
|
|
1939
|
-
)
|
|
1940
|
-
},
|
|
1941
|
-
label
|
|
1942
|
-
);
|
|
1943
|
-
})
|
|
1944
|
-
}
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1947
|
-
var METRIC_FONT = "Inter, sans-serif";
|
|
1948
|
-
function MetricCell({ children, color, fontSize = 11 }) {
|
|
1949
|
-
return /* @__PURE__ */ jsx(
|
|
1950
|
-
reactNative.Text,
|
|
1951
|
-
{
|
|
1952
|
-
style: {
|
|
1953
|
-
fontFamily: METRIC_FONT,
|
|
1954
|
-
fontSize,
|
|
1955
|
-
color,
|
|
1956
|
-
fontWeight: "600",
|
|
1957
|
-
letterSpacing: 1
|
|
1958
|
-
},
|
|
1959
|
-
children
|
|
1960
|
-
}
|
|
1961
|
-
);
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
// src/components/custom/Workout/SetsRepsLoad.tsx
|
|
1965
|
-
var t = getSemanticColors("dark");
|
|
1966
|
-
var TIMES = "\xD7";
|
|
1967
|
-
var AT = "@";
|
|
1968
|
-
function SetsRepsLoad({
|
|
1969
|
-
sets,
|
|
1970
|
-
reps,
|
|
1971
|
-
load,
|
|
1972
|
-
unit = "lb",
|
|
1973
|
-
fontSize,
|
|
1974
|
-
className,
|
|
1975
|
-
...props
|
|
1976
|
-
}) {
|
|
1977
|
-
const value = t["text-primary"];
|
|
1978
|
-
const sep = t["text-tertiary"];
|
|
1979
|
-
return /* @__PURE__ */ jsxs(
|
|
1980
|
-
reactNative.View,
|
|
1981
|
-
{
|
|
1982
|
-
className,
|
|
1983
|
-
style: { flexDirection: "row", alignItems: "baseline" },
|
|
1984
|
-
accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
|
|
1985
|
-
testID: "sets-reps-load",
|
|
2116
|
+
variant: "pr",
|
|
2117
|
+
className,
|
|
2118
|
+
accessibilityLabel: `Personal record: ${resolvedLabel}`,
|
|
1986
2119
|
...props,
|
|
1987
|
-
children:
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2120
|
+
children: /* @__PURE__ */ jsxs(
|
|
2121
|
+
reactNative.Text,
|
|
2122
|
+
{
|
|
2123
|
+
style: {
|
|
2124
|
+
fontSize: 12,
|
|
2125
|
+
color: BRAND_PRIMARY,
|
|
2126
|
+
fontWeight: "700",
|
|
2127
|
+
fontFamily: "Inter, sans-serif"
|
|
2128
|
+
},
|
|
2129
|
+
children: [
|
|
2130
|
+
"\u2605",
|
|
2131
|
+
" ",
|
|
2132
|
+
resolvedLabel
|
|
2133
|
+
]
|
|
2134
|
+
}
|
|
2135
|
+
)
|
|
1995
2136
|
}
|
|
1996
2137
|
);
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
r = parseInt(cleaned.slice(0, 2), 16);
|
|
2007
|
-
g = parseInt(cleaned.slice(2, 4), 16);
|
|
2008
|
-
b = parseInt(cleaned.slice(4, 6), 16);
|
|
2009
|
-
} else {
|
|
2010
|
-
return null;
|
|
2138
|
+
if (animate) {
|
|
2139
|
+
return /* @__PURE__ */ jsx(
|
|
2140
|
+
reactNative.Animated.View,
|
|
2141
|
+
{
|
|
2142
|
+
style: { transform: [{ scale }], opacity },
|
|
2143
|
+
testID: "pr-badge-animated",
|
|
2144
|
+
children: badge
|
|
2145
|
+
}
|
|
2146
|
+
);
|
|
2011
2147
|
}
|
|
2012
|
-
return
|
|
2148
|
+
return badge;
|
|
2013
2149
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
}
|
|
2033
|
-
if (h < 0) h += 360;
|
|
2034
|
-
return { h, s, v };
|
|
2150
|
+
var DARK = getSemanticColors("dark");
|
|
2151
|
+
var TEXT_ACTIVE = DARK["text-primary"];
|
|
2152
|
+
var TEXT_MUTED = DARK["text-secondary"];
|
|
2153
|
+
var COL = { set: 36, reps: 44, load: 56, rpe: 36 };
|
|
2154
|
+
var cellText = { fontFamily: "Inter, sans-serif", fontSize: 13, fontWeight: "600" };
|
|
2155
|
+
var typeBadge = {
|
|
2156
|
+
fontSize: 10,
|
|
2157
|
+
fontWeight: "700",
|
|
2158
|
+
fontFamily: "Inter, sans-serif",
|
|
2159
|
+
color: WORKOUT_TOKENS.scale.orange,
|
|
2160
|
+
backgroundColor: "rgba(255,165,2,0.12)",
|
|
2161
|
+
paddingVertical: 1,
|
|
2162
|
+
paddingHorizontal: 5,
|
|
2163
|
+
borderRadius: 3,
|
|
2164
|
+
overflow: "hidden"
|
|
2165
|
+
};
|
|
2166
|
+
function displayReps(set) {
|
|
2167
|
+
return set.state === "done" ? set.reps : set.target.reps;
|
|
2035
2168
|
}
|
|
2036
|
-
function
|
|
2037
|
-
|
|
2038
|
-
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
2039
|
-
const m = v - c;
|
|
2040
|
-
let rPrime, gPrime, bPrime;
|
|
2041
|
-
if (h >= 0 && h < 60) {
|
|
2042
|
-
rPrime = c;
|
|
2043
|
-
gPrime = x;
|
|
2044
|
-
bPrime = 0;
|
|
2045
|
-
} else if (h >= 60 && h < 120) {
|
|
2046
|
-
rPrime = x;
|
|
2047
|
-
gPrime = c;
|
|
2048
|
-
bPrime = 0;
|
|
2049
|
-
} else if (h >= 120 && h < 180) {
|
|
2050
|
-
rPrime = 0;
|
|
2051
|
-
gPrime = c;
|
|
2052
|
-
bPrime = x;
|
|
2053
|
-
} else if (h >= 180 && h < 240) {
|
|
2054
|
-
rPrime = 0;
|
|
2055
|
-
gPrime = x;
|
|
2056
|
-
bPrime = c;
|
|
2057
|
-
} else if (h >= 240 && h < 300) {
|
|
2058
|
-
rPrime = x;
|
|
2059
|
-
gPrime = 0;
|
|
2060
|
-
bPrime = c;
|
|
2061
|
-
} else {
|
|
2062
|
-
rPrime = c;
|
|
2063
|
-
gPrime = 0;
|
|
2064
|
-
bPrime = x;
|
|
2065
|
-
}
|
|
2066
|
-
return {
|
|
2067
|
-
r: Math.round((rPrime + m) * 255),
|
|
2068
|
-
g: Math.round((gPrime + m) * 255),
|
|
2069
|
-
b: Math.round((bPrime + m) * 255)
|
|
2070
|
-
};
|
|
2169
|
+
function displayWeight(set) {
|
|
2170
|
+
return set.state === "done" ? set.weight : set.target.weight;
|
|
2071
2171
|
}
|
|
2072
|
-
function
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
};
|
|
2077
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
2172
|
+
function plannedReps(set) {
|
|
2173
|
+
if (set.state === "done") return set.velocities.length;
|
|
2174
|
+
if (set.state === "live") return set.target.reps;
|
|
2175
|
+
return set.planned ?? set.target.reps;
|
|
2078
2176
|
}
|
|
2079
|
-
function
|
|
2080
|
-
const
|
|
2081
|
-
if (
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
|
|
2085
|
-
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
2177
|
+
function accessibilityLabel(set) {
|
|
2178
|
+
const base = `Set ${set.setNumber}: ${displayReps(set)} reps at ${roundWeight(displayWeight(set))} ${set.unit}`;
|
|
2179
|
+
if (set.state === "live") return `${base}, in progress`;
|
|
2180
|
+
if (set.state === "todo") return `${base}, upcoming`;
|
|
2181
|
+
return base;
|
|
2086
2182
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2183
|
+
function Cell({
|
|
2184
|
+
width,
|
|
2185
|
+
children,
|
|
2186
|
+
testID
|
|
2187
|
+
}) {
|
|
2188
|
+
return /* @__PURE__ */ jsx(reactNative.View, { style: { width, alignItems: "center", justifyContent: "center" }, testID, children });
|
|
2189
|
+
}
|
|
2190
|
+
function RowStrip({ set }) {
|
|
2191
|
+
const zones = set.velocityZones;
|
|
2192
|
+
if (set.state === "live") {
|
|
2193
|
+
return /* @__PURE__ */ jsx(
|
|
2194
|
+
VelocityStrip,
|
|
2195
|
+
{
|
|
2196
|
+
variant: "expanded",
|
|
2197
|
+
showNumbers: false,
|
|
2198
|
+
showInfo: false,
|
|
2199
|
+
height: 24,
|
|
2200
|
+
scale: "fixed",
|
|
2201
|
+
liveRepIndex: set.velocities.length - 1,
|
|
2202
|
+
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
2203
|
+
zones
|
|
2204
|
+
}
|
|
2205
|
+
);
|
|
2109
2206
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
function createRaisedShadow(surface = "charcoal", intensity = "medium") {
|
|
2120
|
-
const colors = shadowColors[surface];
|
|
2121
|
-
const { offset, blur } = intensityConfig[intensity];
|
|
2122
|
-
const rimBlur = 1;
|
|
2123
|
-
return reactNative.Platform.select({
|
|
2124
|
-
web: {
|
|
2125
|
-
boxShadow: `${offset}px ${offset}px ${blur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
2126
|
-
},
|
|
2127
|
-
default: {
|
|
2128
|
-
// React Native shadow approximation (limited compared to web)
|
|
2129
|
-
shadowColor: "#000",
|
|
2130
|
-
shadowOffset: { width: offset, height: offset },
|
|
2131
|
-
shadowOpacity: 0.3,
|
|
2132
|
-
shadowRadius: blur / 2,
|
|
2133
|
-
elevation: offset
|
|
2207
|
+
const velocities = set.state === "done" ? set.velocities : [];
|
|
2208
|
+
return /* @__PURE__ */ jsx(
|
|
2209
|
+
VelocityStrip,
|
|
2210
|
+
{
|
|
2211
|
+
variant: "compact",
|
|
2212
|
+
height: 8,
|
|
2213
|
+
hideBaseline: true,
|
|
2214
|
+
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
2215
|
+
zones
|
|
2134
2216
|
}
|
|
2135
|
-
|
|
2217
|
+
);
|
|
2136
2218
|
}
|
|
2137
|
-
function
|
|
2138
|
-
const
|
|
2139
|
-
const
|
|
2140
|
-
const
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2219
|
+
function SetRow(set) {
|
|
2220
|
+
const live = set.state === "live";
|
|
2221
|
+
const valueColor = live ? TEXT_ACTIVE : TEXT_MUTED;
|
|
2222
|
+
const rpe = set.state === "todo" ? null : set.rpe;
|
|
2223
|
+
return /* @__PURE__ */ jsxs(
|
|
2224
|
+
reactNative.View,
|
|
2225
|
+
{
|
|
2226
|
+
style: { paddingVertical: 6, paddingHorizontal: 8 },
|
|
2227
|
+
accessibilityLabel: accessibilityLabel(set),
|
|
2228
|
+
testID: "set-row",
|
|
2229
|
+
children: [
|
|
2230
|
+
/* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { justifyContent: "space-between" }, children: [
|
|
2231
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.set, testID: "set-row-set-number", children: set.setType ? /* @__PURE__ */ jsx(reactNative.Text, { style: typeBadge, testID: "set-row-type-badge", children: set.setType }) : /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor, fontWeight: live ? "700" : "600" }, children: set.setNumber }) }),
|
|
2232
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.reps, testID: "set-row-reps", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: displayReps(set) }) }),
|
|
2233
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.load, testID: "set-row-weight", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: valueColor }, children: roundWeight(displayWeight(set)) }) }),
|
|
2234
|
+
/* @__PURE__ */ jsx(Cell, { width: COL.rpe, testID: "set-row-rpe", children: /* @__PURE__ */ jsx(reactNative.Text, { style: { ...cellText, color: TEXT_MUTED }, children: rpe != null ? roundRpe(rpe) : "\u2014" }) })
|
|
2235
|
+
] }),
|
|
2236
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { marginTop: live ? 6 : 4 }, testID: "set-row-strip", children: /* @__PURE__ */ jsx(RowStrip, { set }) })
|
|
2237
|
+
]
|
|
2150
2238
|
}
|
|
2151
|
-
|
|
2239
|
+
);
|
|
2152
2240
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
});
|
|
2241
|
+
var PREV_COLUMN = { label: "PREV", width: void 0 };
|
|
2242
|
+
function buildColumns(unit, showPrevious) {
|
|
2243
|
+
return [
|
|
2244
|
+
{ label: "SET", width: 36 },
|
|
2245
|
+
...showPrevious ? [PREV_COLUMN] : [],
|
|
2246
|
+
{ label: "REPS", width: 44 },
|
|
2247
|
+
{ label: unit.toUpperCase(), width: 56 },
|
|
2248
|
+
{ label: "RPE", width: 36 }
|
|
2249
|
+
];
|
|
2163
2250
|
}
|
|
2164
|
-
function
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
const
|
|
2170
|
-
return
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2251
|
+
function SetTableHeader({
|
|
2252
|
+
unit = "lbs",
|
|
2253
|
+
showPrevious = true,
|
|
2254
|
+
testID = "table-header"
|
|
2255
|
+
}) {
|
|
2256
|
+
const columns = buildColumns(unit, showPrevious);
|
|
2257
|
+
return /* @__PURE__ */ jsx(
|
|
2258
|
+
reactNative.View,
|
|
2259
|
+
{
|
|
2260
|
+
className: "flex-row",
|
|
2261
|
+
style: {
|
|
2262
|
+
paddingVertical: 8,
|
|
2263
|
+
paddingHorizontal: 8,
|
|
2264
|
+
paddingBottom: 4,
|
|
2265
|
+
...showPrevious ? {} : { justifyContent: "space-between" }
|
|
2266
|
+
},
|
|
2267
|
+
testID,
|
|
2268
|
+
children: columns.map(({ label, width }) => {
|
|
2269
|
+
const isFlex = width === void 0;
|
|
2270
|
+
return /* @__PURE__ */ jsx(
|
|
2271
|
+
reactNative.View,
|
|
2272
|
+
{
|
|
2273
|
+
style: {
|
|
2274
|
+
...isFlex ? { minWidth: 44 } : { width, flexShrink: 1 },
|
|
2275
|
+
alignItems: "center",
|
|
2276
|
+
justifyContent: "center",
|
|
2277
|
+
gap: 8
|
|
2278
|
+
},
|
|
2279
|
+
...isFlex ? { className: "flex-1" } : {},
|
|
2280
|
+
children: /* @__PURE__ */ jsx(
|
|
2281
|
+
reactNative.Text,
|
|
2282
|
+
{
|
|
2283
|
+
className: "text-text-tertiary",
|
|
2284
|
+
style: {
|
|
2285
|
+
fontSize: 10,
|
|
2286
|
+
fontWeight: "600",
|
|
2287
|
+
fontFamily: "Inter, sans-serif",
|
|
2288
|
+
textTransform: "uppercase",
|
|
2289
|
+
letterSpacing: 0.8
|
|
2290
|
+
},
|
|
2291
|
+
children: label
|
|
2292
|
+
}
|
|
2293
|
+
)
|
|
2294
|
+
},
|
|
2295
|
+
label
|
|
2296
|
+
);
|
|
2297
|
+
})
|
|
2181
2298
|
}
|
|
2182
|
-
|
|
2299
|
+
);
|
|
2183
2300
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
elevation: 0
|
|
2301
|
+
var METRIC_FONT = "Inter, sans-serif";
|
|
2302
|
+
function MetricCell({ children, color, fontSize = 11 }) {
|
|
2303
|
+
return /* @__PURE__ */ jsx(
|
|
2304
|
+
reactNative.Text,
|
|
2305
|
+
{
|
|
2306
|
+
style: {
|
|
2307
|
+
fontFamily: METRIC_FONT,
|
|
2308
|
+
fontSize,
|
|
2309
|
+
color,
|
|
2310
|
+
fontWeight: "600",
|
|
2311
|
+
letterSpacing: 1
|
|
2312
|
+
},
|
|
2313
|
+
children
|
|
2198
2314
|
}
|
|
2199
|
-
|
|
2315
|
+
);
|
|
2200
2316
|
}
|
|
2201
|
-
({
|
|
2202
|
-
// Charcoal surface shadows (for dark toolbars, cards)
|
|
2203
|
-
charcoal: {
|
|
2204
|
-
raised: {
|
|
2205
|
-
subtle: createRaisedShadow("charcoal", "subtle"),
|
|
2206
|
-
medium: createRaisedShadow("charcoal", "medium"),
|
|
2207
|
-
strong: createRaisedShadow("charcoal", "strong")
|
|
2208
|
-
},
|
|
2209
|
-
raisedHover: {
|
|
2210
|
-
subtle: createRaisedHoverShadow("charcoal", "subtle"),
|
|
2211
|
-
medium: createRaisedHoverShadow("charcoal", "medium"),
|
|
2212
|
-
strong: createRaisedHoverShadow("charcoal", "strong")
|
|
2213
|
-
},
|
|
2214
|
-
pressed: {
|
|
2215
|
-
subtle: createPressedShadow("charcoal", "subtle"),
|
|
2216
|
-
medium: createPressedShadow("charcoal", "medium"),
|
|
2217
|
-
strong: createPressedShadow("charcoal", "strong")
|
|
2218
|
-
},
|
|
2219
|
-
pressedHover: {
|
|
2220
|
-
subtle: createPressedHoverShadow("charcoal", "subtle"),
|
|
2221
|
-
medium: createPressedHoverShadow("charcoal", "medium"),
|
|
2222
|
-
strong: createPressedHoverShadow("charcoal", "strong")
|
|
2223
|
-
},
|
|
2224
|
-
flat: createFlatShadow()
|
|
2225
|
-
},
|
|
2226
|
-
// Neutral dark surface shadows
|
|
2227
|
-
neutralDark: {
|
|
2228
|
-
raised: {
|
|
2229
|
-
subtle: createRaisedShadow("neutralDark", "subtle"),
|
|
2230
|
-
medium: createRaisedShadow("neutralDark", "medium"),
|
|
2231
|
-
strong: createRaisedShadow("neutralDark", "strong")
|
|
2232
|
-
},
|
|
2233
|
-
raisedHover: {
|
|
2234
|
-
subtle: createRaisedHoverShadow("neutralDark", "subtle"),
|
|
2235
|
-
medium: createRaisedHoverShadow("neutralDark", "medium"),
|
|
2236
|
-
strong: createRaisedHoverShadow("neutralDark", "strong")
|
|
2237
|
-
},
|
|
2238
|
-
pressed: {
|
|
2239
|
-
subtle: createPressedShadow("neutralDark", "subtle"),
|
|
2240
|
-
medium: createPressedShadow("neutralDark", "medium"),
|
|
2241
|
-
strong: createPressedShadow("neutralDark", "strong")
|
|
2242
|
-
},
|
|
2243
|
-
pressedHover: {
|
|
2244
|
-
subtle: createPressedHoverShadow("neutralDark", "subtle"),
|
|
2245
|
-
medium: createPressedHoverShadow("neutralDark", "medium"),
|
|
2246
|
-
strong: createPressedHoverShadow("neutralDark", "strong")
|
|
2247
|
-
},
|
|
2248
|
-
flat: createFlatShadow()
|
|
2249
|
-
},
|
|
2250
|
-
// Neutral light surface shadows
|
|
2251
|
-
neutralLight: {
|
|
2252
|
-
raised: {
|
|
2253
|
-
subtle: createRaisedShadow("neutralLight", "subtle"),
|
|
2254
|
-
medium: createRaisedShadow("neutralLight", "medium"),
|
|
2255
|
-
strong: createRaisedShadow("neutralLight", "strong")
|
|
2256
|
-
},
|
|
2257
|
-
raisedHover: {
|
|
2258
|
-
subtle: createRaisedHoverShadow("neutralLight", "subtle"),
|
|
2259
|
-
medium: createRaisedHoverShadow("neutralLight", "medium"),
|
|
2260
|
-
strong: createRaisedHoverShadow("neutralLight", "strong")
|
|
2261
|
-
},
|
|
2262
|
-
pressed: {
|
|
2263
|
-
subtle: createPressedShadow("neutralLight", "subtle"),
|
|
2264
|
-
medium: createPressedShadow("neutralLight", "medium"),
|
|
2265
|
-
strong: createPressedShadow("neutralLight", "strong")
|
|
2266
|
-
},
|
|
2267
|
-
pressedHover: {
|
|
2268
|
-
subtle: createPressedHoverShadow("neutralLight", "subtle"),
|
|
2269
|
-
medium: createPressedHoverShadow("neutralLight", "medium"),
|
|
2270
|
-
strong: createPressedHoverShadow("neutralLight", "strong")
|
|
2271
|
-
},
|
|
2272
|
-
flat: createFlatShadow()
|
|
2273
|
-
}
|
|
2274
|
-
});
|
|
2275
2317
|
|
|
2276
|
-
// src/
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2318
|
+
// src/components/custom/Workout/SetsRepsLoad.tsx
|
|
2319
|
+
var t2 = getSemanticColors("dark");
|
|
2320
|
+
var TIMES = "\xD7";
|
|
2321
|
+
var AT = "@";
|
|
2322
|
+
function SetsRepsLoad({
|
|
2323
|
+
sets,
|
|
2324
|
+
reps,
|
|
2325
|
+
load,
|
|
2326
|
+
unit = "lb",
|
|
2327
|
+
fontSize,
|
|
2328
|
+
className,
|
|
2329
|
+
...props
|
|
2330
|
+
}) {
|
|
2331
|
+
const value = t2["text-primary"];
|
|
2332
|
+
const sep = t2["text-tertiary"];
|
|
2333
|
+
return /* @__PURE__ */ jsxs(
|
|
2334
|
+
reactNative.View,
|
|
2335
|
+
{
|
|
2336
|
+
className,
|
|
2337
|
+
style: { flexDirection: "row", alignItems: "baseline" },
|
|
2338
|
+
accessibilityLabel: `${sets} sets of ${reps} reps at ${load} ${unit}`,
|
|
2339
|
+
testID: "sets-reps-load",
|
|
2340
|
+
...props,
|
|
2341
|
+
children: [
|
|
2342
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: sets }),
|
|
2343
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${TIMES} ` }),
|
|
2344
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: reps }),
|
|
2345
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${AT} ` }),
|
|
2346
|
+
/* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: load }),
|
|
2347
|
+
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${unit}` })
|
|
2348
|
+
]
|
|
2292
2349
|
}
|
|
2293
|
-
|
|
2294
|
-
}
|
|
2295
|
-
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
2296
|
-
if (rgbMatch) {
|
|
2297
|
-
const [, r, g, b] = rgbMatch;
|
|
2298
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
2299
|
-
}
|
|
2300
|
-
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
2301
|
-
if (rgbaMatch) {
|
|
2302
|
-
const [, r, g, b] = rgbaMatch;
|
|
2303
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
2304
|
-
}
|
|
2305
|
-
return color;
|
|
2350
|
+
);
|
|
2306
2351
|
}
|
|
2307
|
-
|
|
2308
|
-
// src/components/custom/Workout/TempoDisplay.tsx
|
|
2309
|
-
var t2 = getSemanticColors("dark");
|
|
2352
|
+
var t3 = getSemanticColors("dark");
|
|
2310
2353
|
var INTER = "Inter, sans-serif";
|
|
2311
2354
|
var TEXT_TERTIARY = "#6B7280";
|
|
2312
|
-
var STATUS_ERROR =
|
|
2313
|
-
var STATUS_SUCCESS =
|
|
2314
|
-
var STATUS_WARNING =
|
|
2355
|
+
var STATUS_ERROR = t3["status-error"];
|
|
2356
|
+
var STATUS_SUCCESS = t3["status-success"];
|
|
2357
|
+
var STATUS_WARNING = t3["status-warning"];
|
|
2315
2358
|
var ON_TARGET_MS = 100;
|
|
2316
2359
|
function getTempoFillPct(elapsedMs, targetMs) {
|
|
2317
2360
|
if (!targetMs) return 100;
|
|
2318
2361
|
return Math.min(100, elapsedMs / targetMs * 100);
|
|
2319
2362
|
}
|
|
2320
2363
|
function activeNumberTone(elapsedMs, targetMs) {
|
|
2321
|
-
if (targetMs == null) return
|
|
2364
|
+
if (targetMs == null) return t3["text-primary"];
|
|
2322
2365
|
const remainingMs = targetMs - elapsedMs;
|
|
2323
2366
|
if (remainingMs > ON_TARGET_MS) return STATUS_WARNING;
|
|
2324
2367
|
if (remainingMs >= -ON_TARGET_MS) return STATUS_SUCCESS;
|
|
@@ -2470,7 +2513,7 @@ function TempoDisplay({
|
|
|
2470
2513
|
style: {
|
|
2471
2514
|
flexDirection: "row",
|
|
2472
2515
|
alignItems: "center",
|
|
2473
|
-
backgroundColor: "
|
|
2516
|
+
backgroundColor: t3["surface-raised"],
|
|
2474
2517
|
paddingHorizontal: chromePadX,
|
|
2475
2518
|
paddingVertical: chromePadY,
|
|
2476
2519
|
borderRadius: chromeRadius
|
|
@@ -2485,7 +2528,7 @@ function TempoDisplay({
|
|
|
2485
2528
|
fontSize: labelFont,
|
|
2486
2529
|
fontWeight: "500",
|
|
2487
2530
|
// Live-muted green while a rep is running, tertiary at rest.
|
|
2488
|
-
color: live ?
|
|
2531
|
+
color: live ? t3["status-live-muted"] : TEXT_TERTIARY,
|
|
2489
2532
|
letterSpacing: 0.5,
|
|
2490
2533
|
textTransform: "uppercase",
|
|
2491
2534
|
marginRight: Math.round(fontSize * 0.5)
|
|
@@ -2543,7 +2586,7 @@ function TempoDisplay({
|
|
|
2543
2586
|
reactNative.View,
|
|
2544
2587
|
{
|
|
2545
2588
|
style: {
|
|
2546
|
-
backgroundColor: "
|
|
2589
|
+
backgroundColor: t3["surface-overlay"],
|
|
2547
2590
|
borderRadius: 6,
|
|
2548
2591
|
paddingVertical: 8,
|
|
2549
2592
|
paddingHorizontal: 12,
|
|
@@ -2596,14 +2639,14 @@ function TempoDisplay({
|
|
|
2596
2639
|
}
|
|
2597
2640
|
);
|
|
2598
2641
|
}
|
|
2599
|
-
var
|
|
2642
|
+
var t4 = getSemanticColors("dark");
|
|
2600
2643
|
var INDICATOR_CONFIG = {
|
|
2601
|
-
imbalance: { Icon: ScaleIcon, color:
|
|
2602
|
-
overshoot: { Icon: AlertTriangleIcon, color:
|
|
2603
|
-
"velocity-loss": { Icon: TrendingDownIcon, color:
|
|
2604
|
-
"missed-reps": { Icon: CircleSlashIcon, color:
|
|
2605
|
-
pr: { Icon: AwardIcon, color:
|
|
2606
|
-
info: { Icon: InfoIcon, color:
|
|
2644
|
+
imbalance: { Icon: ScaleIcon, color: t4["status-error"], label: "Left/right imbalance" },
|
|
2645
|
+
overshoot: { Icon: AlertTriangleIcon, color: t4["status-error"], label: "Load overshoot" },
|
|
2646
|
+
"velocity-loss": { Icon: TrendingDownIcon, color: t4["status-warning"], label: "Velocity loss" },
|
|
2647
|
+
"missed-reps": { Icon: CircleSlashIcon, color: t4["status-warning"], label: "Missed reps" },
|
|
2648
|
+
pr: { Icon: AwardIcon, color: t4["status-success"], label: "Personal record" },
|
|
2649
|
+
info: { Icon: InfoIcon, color: t4["status-info"], label: "More info" }
|
|
2607
2650
|
};
|
|
2608
2651
|
function ExerciseIndicator({ kind, onPress, className, ...props }) {
|
|
2609
2652
|
const { Icon, color, label } = INDICATOR_CONFIG[kind];
|
|
@@ -2689,6 +2732,243 @@ function ExerciseHeading({
|
|
|
2689
2732
|
)
|
|
2690
2733
|
] });
|
|
2691
2734
|
}
|
|
2735
|
+
var PULSE_HALF_MS = 950;
|
|
2736
|
+
var PULSE_MIN_OPACITY = 0.45;
|
|
2737
|
+
var SEGMENTED_BAR_GAP = 5;
|
|
2738
|
+
function usePulse(active) {
|
|
2739
|
+
const [value] = react.useState(() => new reactNative.Animated.Value(0));
|
|
2740
|
+
react.useEffect(() => {
|
|
2741
|
+
if (!active) return;
|
|
2742
|
+
const loop = reactNative.Animated.loop(
|
|
2743
|
+
reactNative.Animated.sequence([
|
|
2744
|
+
reactNative.Animated.timing(value, {
|
|
2745
|
+
toValue: 1,
|
|
2746
|
+
duration: PULSE_HALF_MS,
|
|
2747
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
2748
|
+
useNativeDriver: false
|
|
2749
|
+
}),
|
|
2750
|
+
reactNative.Animated.timing(value, {
|
|
2751
|
+
toValue: 0,
|
|
2752
|
+
duration: PULSE_HALF_MS,
|
|
2753
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
2754
|
+
useNativeDriver: false
|
|
2755
|
+
})
|
|
2756
|
+
])
|
|
2757
|
+
);
|
|
2758
|
+
loop.start();
|
|
2759
|
+
return () => loop.stop();
|
|
2760
|
+
}, [active, value]);
|
|
2761
|
+
return value;
|
|
2762
|
+
}
|
|
2763
|
+
function SegmentedBar({
|
|
2764
|
+
segments,
|
|
2765
|
+
height = 8,
|
|
2766
|
+
gap = SEGMENTED_BAR_GAP,
|
|
2767
|
+
radius = 2,
|
|
2768
|
+
marker = null,
|
|
2769
|
+
segmentTestID,
|
|
2770
|
+
style,
|
|
2771
|
+
...props
|
|
2772
|
+
}) {
|
|
2773
|
+
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
2774
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
2775
|
+
segments.map((seg, i) => {
|
|
2776
|
+
const fillStyle = {
|
|
2777
|
+
width: `${(seg.fill ?? 1) * 100}%`,
|
|
2778
|
+
height: "100%",
|
|
2779
|
+
backgroundColor: seg.color,
|
|
2780
|
+
opacity: seg.opacity
|
|
2781
|
+
};
|
|
2782
|
+
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
2783
|
+
return /* @__PURE__ */ jsx(
|
|
2784
|
+
reactNative.View,
|
|
2785
|
+
{
|
|
2786
|
+
style: {
|
|
2787
|
+
flex: seg.weight ?? 1,
|
|
2788
|
+
minWidth: 0,
|
|
2789
|
+
height: "100%",
|
|
2790
|
+
marginLeft: seg.leadingGap,
|
|
2791
|
+
borderRadius: radius,
|
|
2792
|
+
overflow: "hidden"
|
|
2793
|
+
},
|
|
2794
|
+
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
2795
|
+
reactNative.Animated.View,
|
|
2796
|
+
{
|
|
2797
|
+
style: {
|
|
2798
|
+
...fillStyle,
|
|
2799
|
+
...seg.pulseColor ? {
|
|
2800
|
+
backgroundColor: pulse.interpolate({
|
|
2801
|
+
inputRange: [0, 1],
|
|
2802
|
+
outputRange: seg.pulseColor
|
|
2803
|
+
})
|
|
2804
|
+
} : {
|
|
2805
|
+
opacity: pulse.interpolate({
|
|
2806
|
+
inputRange: [0, 1],
|
|
2807
|
+
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
2808
|
+
})
|
|
2809
|
+
}
|
|
2810
|
+
},
|
|
2811
|
+
accessibilityElementsHidden: true,
|
|
2812
|
+
testID
|
|
2813
|
+
}
|
|
2814
|
+
) : /* @__PURE__ */ jsx(reactNative.View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
2815
|
+
},
|
|
2816
|
+
i
|
|
2817
|
+
);
|
|
2818
|
+
}),
|
|
2819
|
+
marker ? /* @__PURE__ */ jsx(
|
|
2820
|
+
reactNative.View,
|
|
2821
|
+
{
|
|
2822
|
+
style: {
|
|
2823
|
+
position: "absolute",
|
|
2824
|
+
left: `${marker.position * 100}%`,
|
|
2825
|
+
top: 0,
|
|
2826
|
+
bottom: 0,
|
|
2827
|
+
width: 2,
|
|
2828
|
+
backgroundColor: marker.color
|
|
2829
|
+
},
|
|
2830
|
+
accessibilityElementsHidden: true,
|
|
2831
|
+
testID: "segmented-bar-marker"
|
|
2832
|
+
}
|
|
2833
|
+
) : null
|
|
2834
|
+
] });
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
// src/components/custom/Workout/SetBar.tsx
|
|
2838
|
+
var SET_STRIP_ZONES = {
|
|
2839
|
+
slow: primitiveRamps.red[600],
|
|
2840
|
+
moderate: primitiveRamps.orange[400],
|
|
2841
|
+
fast: primitiveRamps.amber[300],
|
|
2842
|
+
fastest: primitiveRamps.green[300]
|
|
2843
|
+
};
|
|
2844
|
+
var TODO_COLOR = primitiveColors.charcoal[300];
|
|
2845
|
+
var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
|
|
2846
|
+
var NOTCH_GAP = 2;
|
|
2847
|
+
var CLUSTER_GAP = 3;
|
|
2848
|
+
var MYO_UPCOMING_CLUSTERS = 3;
|
|
2849
|
+
var MYO_UPCOMING_CLUSTER_LEN = 5;
|
|
2850
|
+
var MYO_UPCOMING_FADE = 0.6;
|
|
2851
|
+
var PULSE_RANGE = {
|
|
2852
|
+
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
2853
|
+
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
2854
|
+
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
2855
|
+
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
2856
|
+
};
|
|
2857
|
+
function velocityZoneColor(v) {
|
|
2858
|
+
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
2859
|
+
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
2860
|
+
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
2861
|
+
return SET_STRIP_ZONES.fastest;
|
|
2862
|
+
}
|
|
2863
|
+
function chunkedSegments(chunks, gap) {
|
|
2864
|
+
return chunks.flatMap(
|
|
2865
|
+
(vels, ci) => vels.map((v, ri) => ({
|
|
2866
|
+
color: velocityZoneColor(v),
|
|
2867
|
+
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
2868
|
+
}))
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
function rangeSegments(floor, max, doneVels) {
|
|
2872
|
+
return Array.from({ length: max }, (_, i) => {
|
|
2873
|
+
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
2874
|
+
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
2878
|
+
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
2879
|
+
const trail = Array.from(
|
|
2880
|
+
{ length: clusterCount },
|
|
2881
|
+
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
2882
|
+
color: SET_STRIP_VARIABLE_COLOR,
|
|
2883
|
+
opacity: MYO_UPCOMING_FADE ** ci,
|
|
2884
|
+
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
2885
|
+
}))
|
|
2886
|
+
).flat();
|
|
2887
|
+
return [...activation, ...trail];
|
|
2888
|
+
}
|
|
2889
|
+
function setSegments(set) {
|
|
2890
|
+
if (set.status === "todo") {
|
|
2891
|
+
return [{ color: TODO_COLOR }];
|
|
2892
|
+
}
|
|
2893
|
+
if (set.status === "done") {
|
|
2894
|
+
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
2895
|
+
}
|
|
2896
|
+
if (set.status === "range") {
|
|
2897
|
+
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
2898
|
+
}
|
|
2899
|
+
if (set.status === "drop") {
|
|
2900
|
+
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
2901
|
+
}
|
|
2902
|
+
if (set.status === "myo") {
|
|
2903
|
+
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
2904
|
+
}
|
|
2905
|
+
if (set.status === "myo-upcoming") {
|
|
2906
|
+
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
2907
|
+
}
|
|
2908
|
+
const performed = set.velocities.map((v) => {
|
|
2909
|
+
const color = velocityZoneColor(v);
|
|
2910
|
+
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
2911
|
+
});
|
|
2912
|
+
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
2913
|
+
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
2914
|
+
}
|
|
2915
|
+
function setBarSegmentTestID(seg) {
|
|
2916
|
+
if (seg.pulse) return "set-strip-pulse";
|
|
2917
|
+
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
2918
|
+
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
2919
|
+
}
|
|
2920
|
+
function SetBar({ set, height = 8 }) {
|
|
2921
|
+
return /* @__PURE__ */ jsx(
|
|
2922
|
+
SegmentedBar,
|
|
2923
|
+
{
|
|
2924
|
+
segments: setSegments(set),
|
|
2925
|
+
height,
|
|
2926
|
+
gap: 0,
|
|
2927
|
+
radius: 0,
|
|
2928
|
+
segmentTestID: setBarSegmentTestID,
|
|
2929
|
+
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
2930
|
+
testID: "set-strip-set"
|
|
2931
|
+
}
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
// src/components/custom/Workout/SetStrip.tsx
|
|
2936
|
+
var SET_STRIP_GAP = 5;
|
|
2937
|
+
function setCategory(set) {
|
|
2938
|
+
switch (set.status) {
|
|
2939
|
+
case "done":
|
|
2940
|
+
case "drop":
|
|
2941
|
+
case "myo":
|
|
2942
|
+
return "done";
|
|
2943
|
+
case "active":
|
|
2944
|
+
return "active";
|
|
2945
|
+
case "range":
|
|
2946
|
+
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
2947
|
+
case "todo":
|
|
2948
|
+
case "myo-upcoming":
|
|
2949
|
+
return "upcoming";
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
function describeSets(sets) {
|
|
2953
|
+
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
2954
|
+
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
2955
|
+
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
2956
|
+
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
2957
|
+
}
|
|
2958
|
+
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
2959
|
+
return /* @__PURE__ */ jsx(
|
|
2960
|
+
reactNative.View,
|
|
2961
|
+
{
|
|
2962
|
+
className,
|
|
2963
|
+
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
2964
|
+
accessibilityRole: "image",
|
|
2965
|
+
accessibilityLabel: describeSets(sets),
|
|
2966
|
+
testID: "set-strip",
|
|
2967
|
+
...props,
|
|
2968
|
+
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
2969
|
+
}
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2692
2972
|
|
|
2693
2973
|
// src/components/custom/Workout/ExerciseCardHeading.tsx
|
|
2694
2974
|
function ExerciseCardHeading({
|
|
@@ -2831,7 +3111,9 @@ function CollapsedCard({
|
|
|
2831
3111
|
{
|
|
2832
3112
|
velocities,
|
|
2833
3113
|
zones: velocityZones,
|
|
2834
|
-
variant: "
|
|
3114
|
+
variant: "compact",
|
|
3115
|
+
height: 8,
|
|
3116
|
+
hideBaseline: true,
|
|
2835
3117
|
testID: `exercise-card-velocity-strip-${i}`
|
|
2836
3118
|
},
|
|
2837
3119
|
i
|
|
@@ -3619,7 +3901,7 @@ function RestTimer({
|
|
|
3619
3901
|
var DEFAULTS = {
|
|
3620
3902
|
label: "SS",
|
|
3621
3903
|
color: getSemanticColors("dark")["brand-primary"],
|
|
3622
|
-
bgBase: "
|
|
3904
|
+
bgBase: getSemanticColors("dark")["background-base"]
|
|
3623
3905
|
};
|
|
3624
3906
|
function SupersetWrapper({
|
|
3625
3907
|
label = DEFAULTS.label,
|
|
@@ -4401,11 +4683,11 @@ function CardContent({ children, className }) {
|
|
|
4401
4683
|
function CardFooter({ children, className }) {
|
|
4402
4684
|
return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
|
|
4403
4685
|
}
|
|
4404
|
-
var
|
|
4686
|
+
var t5 = getSemanticColors("dark");
|
|
4405
4687
|
var solidVariantColors = {
|
|
4406
|
-
success:
|
|
4407
|
-
warning:
|
|
4408
|
-
error:
|
|
4688
|
+
success: t5["status-success"],
|
|
4689
|
+
warning: t5["status-warning"],
|
|
4690
|
+
error: t5["status-error"],
|
|
4409
4691
|
neutral: "#6B7280"
|
|
4410
4692
|
};
|
|
4411
4693
|
var ringVariantStyles = {
|
|
@@ -4445,8 +4727,8 @@ var iconColors = {
|
|
|
4445
4727
|
warning: "#6B4000",
|
|
4446
4728
|
error: "#5C1A1A",
|
|
4447
4729
|
neutral: "#D1D5DB",
|
|
4448
|
-
"on-track":
|
|
4449
|
-
deviation:
|
|
4730
|
+
"on-track": t5["status-success"],
|
|
4731
|
+
deviation: t5["status-warning"],
|
|
4450
4732
|
future: "#6B7280"
|
|
4451
4733
|
};
|
|
4452
4734
|
var sizeDimensions = {
|
|
@@ -4532,13 +4814,13 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
|
|
|
4532
4814
|
var WORKOUT_PILL_DELOAD = primitiveRamps.magenta[600];
|
|
4533
4815
|
|
|
4534
4816
|
// src/components/custom/Workout/MesoStatusCard.tsx
|
|
4535
|
-
var
|
|
4536
|
-
var BRAND_PRIMARY5 =
|
|
4817
|
+
var t6 = getSemanticColors("dark");
|
|
4818
|
+
var BRAND_PRIMARY5 = t6["brand-primary"];
|
|
4537
4819
|
var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
|
|
4538
4820
|
var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
|
|
4539
|
-
var SUCCESS =
|
|
4540
|
-
var WARNING =
|
|
4541
|
-
var ERROR =
|
|
4821
|
+
var SUCCESS = t6["status-success"];
|
|
4822
|
+
var WARNING = t6["status-warning"];
|
|
4823
|
+
var ERROR = t6["status-error"];
|
|
4542
4824
|
var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY5, BRAND_PRIMARY_LIGHT];
|
|
4543
4825
|
var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
|
|
4544
4826
|
var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)";
|
|
@@ -4909,8 +5191,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
|
|
|
4909
5191
|
var PLOT_LEFT = 26;
|
|
4910
5192
|
var TOOLTIP_WIDTH = 124;
|
|
4911
5193
|
function timeOf(dateStr) {
|
|
4912
|
-
const
|
|
4913
|
-
return Number.isNaN(
|
|
5194
|
+
const t12 = Date.parse(dateStr);
|
|
5195
|
+
return Number.isNaN(t12) ? 0 : t12;
|
|
4914
5196
|
}
|
|
4915
5197
|
function formatValue(value) {
|
|
4916
5198
|
return `${Math.round(value)}`;
|
|
@@ -5399,10 +5681,10 @@ function StrengthTrendChart({
|
|
|
5399
5681
|
}
|
|
5400
5682
|
);
|
|
5401
5683
|
}
|
|
5402
|
-
var
|
|
5403
|
-
var STATUS_SUCCESS3 =
|
|
5404
|
-
var STATUS_WARNING3 =
|
|
5405
|
-
var STATUS_INFO =
|
|
5684
|
+
var t7 = getSemanticColors("dark");
|
|
5685
|
+
var STATUS_SUCCESS3 = t7["status-success"];
|
|
5686
|
+
var STATUS_WARNING3 = t7["status-warning"];
|
|
5687
|
+
var STATUS_INFO = t7["status-info"];
|
|
5406
5688
|
var DOT_BORDER = "#F3F4F6";
|
|
5407
5689
|
var BAND_FILL = "rgba(46,213,115,0.1)";
|
|
5408
5690
|
var BAND_EDGE = "rgba(46,213,115,0.45)";
|
|
@@ -5445,8 +5727,8 @@ function interpEdge(pixels, x, key) {
|
|
|
5445
5727
|
const a = pixels[i];
|
|
5446
5728
|
const b = pixels[i + 1];
|
|
5447
5729
|
if (x >= a.x && x <= b.x) {
|
|
5448
|
-
const
|
|
5449
|
-
return a[key] +
|
|
5730
|
+
const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
|
|
5731
|
+
return a[key] + t12 * (b[key] - a[key]);
|
|
5450
5732
|
}
|
|
5451
5733
|
}
|
|
5452
5734
|
return pixels[pixels.length - 1][key];
|
|
@@ -6000,7 +6282,16 @@ function VbtBreakdown({
|
|
|
6000
6282
|
children: set.label
|
|
6001
6283
|
}
|
|
6002
6284
|
),
|
|
6003
|
-
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(
|
|
6285
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(
|
|
6286
|
+
VelocityStrip,
|
|
6287
|
+
{
|
|
6288
|
+
velocities: set.velocities,
|
|
6289
|
+
zones,
|
|
6290
|
+
variant: "compact",
|
|
6291
|
+
height: 8,
|
|
6292
|
+
hideBaseline: true
|
|
6293
|
+
}
|
|
6294
|
+
) }),
|
|
6004
6295
|
/* @__PURE__ */ jsx(
|
|
6005
6296
|
reactNative.Text,
|
|
6006
6297
|
{
|
|
@@ -6485,12 +6776,12 @@ function Badge({
|
|
|
6485
6776
|
}
|
|
6486
6777
|
);
|
|
6487
6778
|
}
|
|
6488
|
-
var
|
|
6779
|
+
var t8 = getSemanticColors("dark");
|
|
6489
6780
|
var statusBgColors = {
|
|
6490
6781
|
completed: "rgba(46,213,115,0.15)",
|
|
6491
6782
|
current: "rgba(255,121,0,0.15)",
|
|
6492
6783
|
next: "transparent",
|
|
6493
|
-
upcoming: "
|
|
6784
|
+
upcoming: t8["surface-raised"],
|
|
6494
6785
|
missed: "rgba(209,67,67,0.1)",
|
|
6495
6786
|
deload: "rgba(186,41,150,0.15)"
|
|
6496
6787
|
};
|
|
@@ -6503,9 +6794,9 @@ var statusBorderStyles = {
|
|
|
6503
6794
|
deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
|
|
6504
6795
|
};
|
|
6505
6796
|
var textColors = {
|
|
6506
|
-
completed:
|
|
6507
|
-
current:
|
|
6508
|
-
next:
|
|
6797
|
+
completed: t8["status-success"],
|
|
6798
|
+
current: t8["brand-primary"],
|
|
6799
|
+
next: t8["brand-primary"],
|
|
6509
6800
|
upcoming: "#6B7280",
|
|
6510
6801
|
missed: "rgba(209,67,67,0.7)",
|
|
6511
6802
|
deload: WORKOUT_PILL_DELOAD
|
|
@@ -6638,19 +6929,19 @@ function WorkoutPill({
|
|
|
6638
6929
|
}
|
|
6639
6930
|
return pill;
|
|
6640
6931
|
}
|
|
6641
|
-
var
|
|
6932
|
+
var t9 = getSemanticColors("dark");
|
|
6642
6933
|
var TRACK_BG = "#333333";
|
|
6643
6934
|
var LABEL_COLOR = "#6B7280";
|
|
6644
6935
|
var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
|
|
6645
6936
|
var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
|
|
6646
6937
|
var ZONE = {
|
|
6647
|
-
building:
|
|
6938
|
+
building: t9["status-success"],
|
|
6648
6939
|
// below target ramp-up
|
|
6649
|
-
approaching:
|
|
6940
|
+
approaching: t9["status-warning"],
|
|
6650
6941
|
// nearing target
|
|
6651
|
-
target:
|
|
6942
|
+
target: t9["brand-primary"],
|
|
6652
6943
|
// exactly at target
|
|
6653
|
-
over1:
|
|
6944
|
+
over1: t9["status-error"],
|
|
6654
6945
|
// mild over-target
|
|
6655
6946
|
over2: WORKOUT_TOKENS.intensity.over2,
|
|
6656
6947
|
// moderate over-target
|
|
@@ -7073,13 +7364,13 @@ function MesoCard({
|
|
|
7073
7364
|
}
|
|
7074
7365
|
) });
|
|
7075
7366
|
}
|
|
7076
|
-
var
|
|
7367
|
+
var t10 = getSemanticColors("dark");
|
|
7077
7368
|
var dotColorMap = {
|
|
7078
7369
|
untrained: "#2C2C2C",
|
|
7079
|
-
behind:
|
|
7080
|
-
ontrack:
|
|
7081
|
-
target:
|
|
7082
|
-
over:
|
|
7370
|
+
behind: t10["brand-secondary"],
|
|
7371
|
+
ontrack: t10["status-success"],
|
|
7372
|
+
target: t10["brand-primary"],
|
|
7373
|
+
over: t10["status-error"]
|
|
7083
7374
|
};
|
|
7084
7375
|
function MuscleGroupChip({
|
|
7085
7376
|
name,
|
|
@@ -7153,10 +7444,10 @@ function MuscleGroupChip({
|
|
|
7153
7444
|
}
|
|
7154
7445
|
|
|
7155
7446
|
// src/components/custom/Workout/WorkoutCard.tsx
|
|
7156
|
-
var
|
|
7447
|
+
var t11 = getSemanticColors("dark");
|
|
7157
7448
|
var COLORS = {
|
|
7158
|
-
statusSuccess:
|
|
7159
|
-
brandPrimary:
|
|
7449
|
+
statusSuccess: t11["status-success"],
|
|
7450
|
+
brandPrimary: t11["brand-primary"],
|
|
7160
7451
|
borderDefault: "#1F1F1F",
|
|
7161
7452
|
brandPrimarySubtle: "rgba(255,121,0,0.06)"
|
|
7162
7453
|
};
|