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