@titan-design/react-ui 0.10.0 → 0.12.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 +173 -343
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +173 -343
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +416 -18
- package/dist/index.d.ts +416 -18
- package/dist/index.js +2512 -1307
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2480 -1299
- 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 +1644 -1575
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1645 -1576
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +409 -415
- package/dist/theme/index.d.ts +409 -415
- package/dist/theme/index.js +234 -408
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +227 -399
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +141 -140
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +141 -140
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +8 -3
- package/src/arch/Architecture.stories.tsx +9 -9
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- 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/Sidebar/Sidebar.tsx +4 -4
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +2 -2
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
- package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
- package/src/components/custom/Workout/DeviationBar.tsx +3 -2
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +7 -7
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +11 -5
- package/src/components/custom/Workout/InputBar.tsx +2 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
- package/src/components/custom/Workout/MesoCard.tsx +3 -1
- package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
- package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
- package/src/components/custom/Workout/RestTimer.tsx +2 -2
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionHeader.tsx +1 -1
- package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +63 -18
- package/src/components/custom/Workout/SetBar.tsx +3 -3
- package/src/components/custom/Workout/SetRow.test.tsx +12 -9
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/StatusDot.tsx +5 -4
- package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
- package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
- 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 +199 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
- package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
- package/src/components/custom/Workout/WeightBadge.tsx +2 -1
- package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
- package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
- 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/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/custom/stepper/Stepper.tsx +3 -3
- package/src/components/shell/DeviceMenu.tsx +2 -2
- package/src/components/shell/SideNav.tsx +1 -1
- package/src/components/shell/TopBar.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -2
- package/src/components/ui/badge/Badge.tsx +2 -2
- package/src/components/ui/card/Card.tsx +3 -3
- package/src/components/ui/checkbox/Checkbox.tsx +1 -1
- package/src/components/ui/collapse/Collapse.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.tsx +3 -3
- package/src/components/ui/form-field/FormField.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.tsx +1 -1
- package/src/components/ui/menu/Menu.tsx +2 -2
- package/src/components/ui/pill/Pill.tsx +2 -2
- package/src/components/ui/popover/Popover.stories.tsx +2 -2
- package/src/components/ui/popover/Popover.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +3 -3
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.tsx +4 -4
- package/src/components/ui/select/Select.tsx +3 -3
- package/src/components/ui/surface/Surface.stories.tsx +3 -3
- package/src/components/ui/surface/Surface.test.tsx +32 -19
- package/src/components/ui/surface/Surface.tsx +3 -3
- package/src/components/ui/surface/SurfaceContext.ts +19 -29
- package/src/components/ui/surface/surface.contract.test.ts +57 -44
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +2 -2
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
- package/src/stories/PresetShowcase.stories.tsx +21 -21
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +874 -0
- package/src/theme/ColorPrimitives.stories.tsx +348 -0
- package/src/theme/Depth.stories.tsx +312 -0
- package/src/theme/DepthCalibration.stories.tsx +655 -0
- package/src/theme/ThemeProvider.tsx +1 -1
- package/src/theme/barrel.ts +7 -14
- package/src/theme/color-stories.coverage.test.ts +125 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/theme/color-utils.ts +197 -0
- package/src/theme/config.ts +0 -8
- package/src/theme/depth-calibration.test.tsx +97 -0
- package/src/theme/elevation.test.ts +62 -1
- package/src/theme/elevation.ts +34 -8
- package/src/theme/global.css +47 -53
- package/src/theme/grey-ramp.test.ts +166 -0
- package/src/theme/materials.test.ts +106 -0
- package/src/theme/materials.ts +211 -0
- package/src/theme/presets/audiobook.ts +4 -4
- package/src/theme/presets/types.ts +3 -3
- package/src/theme/semantic-contrast.test.ts +83 -0
- package/src/theme/tokens/primitives.ts +62 -115
- package/src/theme/tokens/semantic.ts +130 -82
- package/src/utils/colors.ts +1 -1
- package/tailwind.config.js +2 -5
- 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 -508
- package/src/theme/Colors.stories.tsx +0 -323
- package/src/theme/shadows.stories.tsx +0 -523
- package/src/theme/shadows.ts +0 -439
package/dist/pages.js
CHANGED
|
@@ -10,54 +10,37 @@ var tailwindMerge = require('tailwind-merge');
|
|
|
10
10
|
|
|
11
11
|
// src/theme/tokens/primitives.ts
|
|
12
12
|
var primitiveColors = {
|
|
13
|
-
// Blue scale
|
|
14
|
-
blue: {
|
|
15
|
-
400: "#60A5FA",
|
|
16
|
-
600: "#5048E5",
|
|
17
|
-
700: "#3832A0"},
|
|
18
|
-
// Red scale
|
|
19
|
-
red: {
|
|
20
|
-
500: "#EF4444",
|
|
21
|
-
600: "#D14343"},
|
|
22
|
-
// Vivid red scale
|
|
23
|
-
redVivid: {
|
|
24
|
-
400: "#FF6070",
|
|
25
|
-
500: "#FF4757",
|
|
26
|
-
700: "#C42539"},
|
|
27
|
-
// Neutral/Gray scale
|
|
28
|
-
neutral: {
|
|
29
|
-
50: "#FAFAFA",
|
|
30
|
-
100: "#F3F4F6",
|
|
31
|
-
300: "#D1D5DB",
|
|
32
|
-
400: "#9CA3AF",
|
|
33
|
-
500: "#6B7280",
|
|
34
|
-
600: "#4B5563",
|
|
35
|
-
900: "#111827"},
|
|
36
|
-
// Charcoal scale (dark backgrounds)
|
|
37
|
-
charcoal: {
|
|
38
|
-
0: "#6E6E6E",
|
|
39
|
-
100: "#4C4C4C",
|
|
40
|
-
200: "#3C3C3C",
|
|
41
|
-
300: "#2C2C2C",
|
|
42
|
-
400: "#1F1F1F",
|
|
43
|
-
500: "#1C1C1C"},
|
|
44
|
-
// Pure colors
|
|
45
13
|
white: "#FFFFFF"};
|
|
46
|
-
var
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
// L*
|
|
51
|
-
|
|
52
|
-
// L*
|
|
53
|
-
|
|
54
|
-
// L*
|
|
55
|
-
|
|
56
|
-
// L*
|
|
57
|
-
|
|
58
|
-
// L*
|
|
14
|
+
var greyRamp = {
|
|
15
|
+
50: "#F9F6F3",
|
|
16
|
+
// L*97.0 W6
|
|
17
|
+
100: "#EDEAE7",
|
|
18
|
+
// L*92.8 W6
|
|
19
|
+
200: "#D4D1CE",
|
|
20
|
+
// L*75.7 W6
|
|
21
|
+
400: "#A29F9D",
|
|
22
|
+
// L*65.7 W5
|
|
23
|
+
500: "#888684",
|
|
24
|
+
// L*56.0 W4
|
|
25
|
+
600: "#72716F",
|
|
26
|
+
// L*47.7 W3
|
|
27
|
+
700: "#5A5958",
|
|
28
|
+
// L*37.9 W2
|
|
29
|
+
800: "#424140",
|
|
30
|
+
// L*27.6 W2 — generated bridge
|
|
31
|
+
850: "#373635",
|
|
32
|
+
// L*22.7 W2 — surface overlay
|
|
33
|
+
875: "#31302F",
|
|
34
|
+
// L*19.9 W2 — surface raised
|
|
35
|
+
900: "#2C2A28",
|
|
36
|
+
// L*17.2 W4 — surface elevated
|
|
37
|
+
925: "#252321",
|
|
38
|
+
// L*13.9 W4 — surface base
|
|
39
|
+
950: "#1C1916",
|
|
40
|
+
// L*9.0 W6 — background base / shell
|
|
41
|
+
975: "#100D0A"
|
|
42
|
+
// L*3.8 W6 — background frame / bezel
|
|
59
43
|
};
|
|
60
|
-
var backgroundFrameDark = "#100D0A";
|
|
61
44
|
var primitiveRamps = {
|
|
62
45
|
red: {
|
|
63
46
|
50: "#FFF4F4",
|
|
@@ -100,9 +83,11 @@ var primitiveRamps = {
|
|
|
100
83
|
blue: {
|
|
101
84
|
50: "#EFF8FF",
|
|
102
85
|
300: "#78C2FF",
|
|
86
|
+
400: "#3CA8FF",
|
|
103
87
|
500: "#2196F3",
|
|
104
88
|
// pin
|
|
105
|
-
600: "#1072CB"
|
|
89
|
+
600: "#1072CB",
|
|
90
|
+
700: "#135AA8"},
|
|
106
91
|
magenta: {
|
|
107
92
|
400: "#ED69C3",
|
|
108
93
|
600: "#BA2996"}
|
|
@@ -182,275 +167,916 @@ var WORKOUT_TOKENS = {
|
|
|
182
167
|
fill: "#3A3A3A"
|
|
183
168
|
}};
|
|
184
169
|
|
|
185
|
-
// src/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
170
|
+
// src/theme/tokens/semantic.ts
|
|
171
|
+
var semanticColorsLight = {
|
|
172
|
+
// Brand colors (brand-*)
|
|
173
|
+
"brand-primary": primitiveRamps.orange[400],
|
|
174
|
+
"brand-primary-light": primitiveRamps.orange[300],
|
|
175
|
+
"brand-primary-dark": primitiveRamps.orange[500],
|
|
176
|
+
"brand-primary-subtle": "rgba(255, 121, 0, 0.08)",
|
|
177
|
+
"brand-primary-hover": primitiveRamps.orange[500],
|
|
178
|
+
"brand-primary-active": primitiveRamps.orange[600],
|
|
179
|
+
"brand-secondary": primitiveRamps.cyan[600],
|
|
180
|
+
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
181
|
+
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
182
|
+
"brand-secondary-subtle": "rgba(48, 123, 155, 0.08)",
|
|
183
|
+
"brand-secondary-hover": primitiveRamps.cyan[700],
|
|
184
|
+
"brand-secondary-active": primitiveRamps.cyan[800],
|
|
185
|
+
// Text on brand backgrounds (on-*)
|
|
186
|
+
"on-brand-primary": primitiveColors.white,
|
|
187
|
+
"on-brand-secondary": primitiveColors.white,
|
|
188
|
+
// Status colors (status-*)
|
|
189
|
+
"status-success": primitiveRamps.green[300],
|
|
190
|
+
"status-success-light": primitiveRamps.green[200],
|
|
191
|
+
"status-success-dark": primitiveRamps.green[600],
|
|
192
|
+
"status-success-subtle": primitiveRamps.green[50],
|
|
193
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
194
|
+
"status-live": primitiveRamps.green[300],
|
|
195
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
196
|
+
"status-error": primitiveRamps.red[600],
|
|
197
|
+
"status-error-light": primitiveRamps.red[500],
|
|
198
|
+
"status-error-dark": primitiveRamps.red[700],
|
|
199
|
+
"status-error-subtle": primitiveRamps.red[50],
|
|
200
|
+
"status-error-vivid": primitiveRamps.red[600],
|
|
201
|
+
// vivid alert red
|
|
202
|
+
"status-error-vivid-light": primitiveRamps.red[500],
|
|
203
|
+
"status-error-vivid-dark": primitiveRamps.red[700],
|
|
204
|
+
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
205
|
+
"status-warning": primitiveRamps.amber[300],
|
|
206
|
+
"status-warning-light": primitiveRamps.amber[200],
|
|
207
|
+
"status-warning-dark": primitiveRamps.amber[500],
|
|
208
|
+
"status-warning-subtle": primitiveRamps.amber[50],
|
|
209
|
+
"status-info": primitiveRamps.blue[500],
|
|
210
|
+
"status-info-light": primitiveRamps.blue[300],
|
|
211
|
+
"status-info-dark": primitiveRamps.blue[600],
|
|
212
|
+
"status-info-subtle": primitiveRamps.blue[50],
|
|
213
|
+
// Text on status backgrounds (on-status-*)
|
|
214
|
+
"on-status-success": primitiveColors.white,
|
|
215
|
+
"on-status-error": primitiveColors.white,
|
|
216
|
+
"on-status-warning": primitiveColors.white,
|
|
217
|
+
"on-status-info": primitiveColors.white,
|
|
218
|
+
// Result/outcome indicators (result-*)
|
|
219
|
+
"result-improve": "#4caf50",
|
|
220
|
+
// Green - positive outcome
|
|
221
|
+
"result-improve-light": "rgba(76, 175, 80, 0.12)",
|
|
222
|
+
"result-improve-dark": "#248a24",
|
|
223
|
+
"result-degrade": "#ef5350",
|
|
224
|
+
// Red - negative outcome
|
|
225
|
+
"result-degrade-light": "rgba(239, 83, 80, 0.12)",
|
|
226
|
+
"result-degrade-dark": "#b30000",
|
|
227
|
+
"result-inconclusive": "#9E9A97",
|
|
228
|
+
// Gray - no clear result
|
|
229
|
+
"result-inconclusive-light": "rgba(158, 154, 151, 0.12)",
|
|
230
|
+
"result-neutral": greyRamp[600],
|
|
231
|
+
// Neutral baseline
|
|
232
|
+
// Text on result backgrounds (on-result-*)
|
|
233
|
+
"on-result-improve": primitiveColors.white,
|
|
234
|
+
"on-result-degrade": primitiveColors.white,
|
|
235
|
+
"on-result-inconclusive": primitiveColors.white,
|
|
236
|
+
// Data visualization colors (data-*)
|
|
237
|
+
// First 10 colors from discrete rainbow optimized for charts
|
|
238
|
+
"data-1": discreteRainbow[9],
|
|
239
|
+
// Blue
|
|
240
|
+
"data-2": discreteRainbow[14],
|
|
241
|
+
// Green
|
|
242
|
+
"data-3": discreteRainbow[17],
|
|
243
|
+
// Yellow
|
|
244
|
+
"data-4": discreteRainbow[25],
|
|
245
|
+
// Red
|
|
246
|
+
"data-5": discreteRainbow[8],
|
|
247
|
+
// Purple
|
|
248
|
+
"data-6": discreteRainbow[20],
|
|
249
|
+
// Orange
|
|
250
|
+
"data-7": discreteRainbow[13],
|
|
251
|
+
// Light Blue
|
|
252
|
+
"data-8": discreteRainbow[15],
|
|
253
|
+
// Light Green
|
|
254
|
+
"data-9": discreteRainbow[3],
|
|
255
|
+
// Lavender
|
|
256
|
+
"data-10": discreteRainbow[22],
|
|
257
|
+
// Dark Orange
|
|
258
|
+
// Text colors (text-*)
|
|
259
|
+
"text-primary": "#121828",
|
|
260
|
+
"text-secondary": "#65748B",
|
|
261
|
+
"text-tertiary": greyRamp[400],
|
|
262
|
+
"text-disabled": "rgba(55, 65, 81, 0.48)",
|
|
263
|
+
"text-inverse": primitiveColors.white,
|
|
264
|
+
"text-link": primitiveRamps.blue[600],
|
|
265
|
+
"text-link-hover": primitiveRamps.blue[700],
|
|
266
|
+
// Surface colors (surface-*) - for elevated containers
|
|
267
|
+
"surface-base": primitiveColors.white,
|
|
268
|
+
"surface-elevated": greyRamp[50],
|
|
269
|
+
// slightly off-white for elevated cards
|
|
270
|
+
"surface-raised": greyRamp[100],
|
|
271
|
+
// light gray for raised cards
|
|
272
|
+
"surface-overlay": primitiveColors.white,
|
|
273
|
+
"surface-input": greyRamp[50],
|
|
274
|
+
// Input field background (filled variant)
|
|
275
|
+
// Background colors (background-*)
|
|
276
|
+
"background-base": "#EBEBEB",
|
|
277
|
+
"background-default": primitiveColors.white,
|
|
278
|
+
"background-subtle": greyRamp[50],
|
|
279
|
+
// Frame/bezel chrome — top bar + side nav shell, one step below
|
|
280
|
+
// `background-base`. Placeholder pairing for light mode, which is deferred.
|
|
281
|
+
"background-frame": greyRamp[400],
|
|
282
|
+
// Border colors (border-*)
|
|
283
|
+
"border-prominent": greyRamp[400],
|
|
284
|
+
// high-visibility divider
|
|
285
|
+
"border-focus": primitiveRamps.blue[600],
|
|
286
|
+
"border-input": greyRamp[200],
|
|
287
|
+
// Input field border
|
|
288
|
+
"border-input-hover": greyRamp[400],
|
|
289
|
+
// Input field border on hover
|
|
290
|
+
"border-input-focus": primitiveRamps.blue[600],
|
|
291
|
+
// Input field border on focus
|
|
292
|
+
"border-input-error": primitiveRamps.red[600],
|
|
293
|
+
// Input field border on error
|
|
294
|
+
// Alpha hairline separators (surface-independent — composite toward black on
|
|
295
|
+
// light surfaces, mirroring the dark-mode white-alpha family). See §4/S-2.
|
|
296
|
+
"hairline-subtle": "rgba(0, 0, 0, 0.06)",
|
|
297
|
+
"hairline-default": "rgba(0, 0, 0, 0.09)",
|
|
298
|
+
"hairline-strong": "rgba(0, 0, 0, 0.14)",
|
|
299
|
+
// Interactive states (interactive-*)
|
|
300
|
+
"interactive-hover": "rgba(55, 65, 81, 0.04)",
|
|
301
|
+
"interactive-focus": "rgba(55, 65, 81, 0.12)",
|
|
302
|
+
"interactive-active": "rgba(55, 65, 81, 0.16)",
|
|
303
|
+
"interactive-selected": "rgba(55, 65, 81, 0.08)",
|
|
304
|
+
"interactive-disabled": "rgba(55, 65, 81, 0.12)",
|
|
305
|
+
"interactive-disabled-text": "rgba(55, 65, 81, 0.26)",
|
|
306
|
+
// Divider
|
|
307
|
+
"divider": "#E8E9EB",
|
|
308
|
+
// Avatar default
|
|
309
|
+
"avatar-background": greyRamp[600],
|
|
310
|
+
"avatar-text": primitiveColors.white
|
|
311
|
+
};
|
|
312
|
+
var semanticColorsDark = {
|
|
313
|
+
// Brand colors stay the same in dark mode
|
|
314
|
+
"brand-primary": primitiveRamps.orange[400],
|
|
315
|
+
"brand-primary-light": primitiveRamps.orange[300],
|
|
316
|
+
"brand-primary-dark": primitiveRamps.orange[500],
|
|
317
|
+
"brand-primary-subtle": "rgba(255, 121, 0, 0.12)",
|
|
318
|
+
"brand-primary-hover": primitiveRamps.orange[300],
|
|
319
|
+
"brand-primary-active": primitiveRamps.orange[200],
|
|
320
|
+
"brand-secondary": primitiveRamps.cyan[600],
|
|
321
|
+
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
322
|
+
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
323
|
+
"brand-secondary-subtle": "rgba(48, 123, 155, 0.12)",
|
|
324
|
+
"brand-secondary-hover": primitiveRamps.cyan[500],
|
|
325
|
+
"brand-secondary-active": primitiveRamps.cyan[400],
|
|
326
|
+
// Text on brand backgrounds
|
|
327
|
+
"on-brand-primary": primitiveColors.white,
|
|
328
|
+
"on-brand-secondary": primitiveColors.white,
|
|
329
|
+
// Status colors
|
|
330
|
+
"status-success": primitiveRamps.green[300],
|
|
331
|
+
"status-success-light": primitiveRamps.green[200],
|
|
332
|
+
"status-success-dark": primitiveRamps.green[600],
|
|
333
|
+
"status-success-subtle": "rgba(46, 213, 115, 0.12)",
|
|
334
|
+
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
335
|
+
"status-live": primitiveRamps.green[300],
|
|
336
|
+
"status-live-muted": primitiveRamps.green[500],
|
|
337
|
+
"status-error": primitiveRamps.red[600],
|
|
338
|
+
"status-error-light": primitiveRamps.red[500],
|
|
339
|
+
"status-error-dark": primitiveRamps.red[700],
|
|
340
|
+
"status-error-subtle": "rgba(209, 67, 67, 0.12)",
|
|
341
|
+
"status-error-vivid": primitiveRamps.red[600],
|
|
342
|
+
"status-error-vivid-light": primitiveRamps.red[500],
|
|
343
|
+
"status-error-vivid-dark": primitiveRamps.red[700],
|
|
344
|
+
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
345
|
+
"status-warning": primitiveRamps.amber[300],
|
|
346
|
+
"status-warning-light": primitiveRamps.amber[200],
|
|
347
|
+
"status-warning-dark": primitiveRamps.amber[500],
|
|
348
|
+
"status-warning-subtle": "rgba(249, 180, 21, 0.12)",
|
|
349
|
+
"status-info": primitiveRamps.blue[500],
|
|
350
|
+
"status-info-light": primitiveRamps.blue[300],
|
|
351
|
+
"status-info-dark": primitiveRamps.blue[600],
|
|
352
|
+
"status-info-subtle": "rgba(33, 150, 243, 0.12)",
|
|
353
|
+
// Text on status backgrounds
|
|
354
|
+
"on-status-success": primitiveColors.white,
|
|
355
|
+
"on-status-error": primitiveColors.white,
|
|
356
|
+
"on-status-warning": primitiveColors.white,
|
|
357
|
+
"on-status-info": primitiveColors.white,
|
|
358
|
+
// Result/outcome indicators (result-*)
|
|
359
|
+
"result-improve": "#4caf50",
|
|
360
|
+
"result-improve-light": "rgba(76, 175, 80, 0.16)",
|
|
361
|
+
"result-improve-dark": "#248a24",
|
|
362
|
+
"result-degrade": "#ef5350",
|
|
363
|
+
"result-degrade-light": "rgba(239, 83, 80, 0.16)",
|
|
364
|
+
"result-degrade-dark": "#b30000",
|
|
365
|
+
"result-inconclusive": "#9E9A97",
|
|
366
|
+
"result-inconclusive-light": "rgba(158, 154, 151, 0.16)",
|
|
367
|
+
"result-neutral": greyRamp[400],
|
|
368
|
+
// Text on result backgrounds
|
|
369
|
+
"on-result-improve": primitiveColors.white,
|
|
370
|
+
"on-result-degrade": primitiveColors.white,
|
|
371
|
+
"on-result-inconclusive": primitiveColors.white,
|
|
372
|
+
// Data visualization colors (same in dark mode for consistency)
|
|
373
|
+
"data-1": discreteRainbow[9],
|
|
374
|
+
"data-2": discreteRainbow[14],
|
|
375
|
+
"data-3": discreteRainbow[17],
|
|
376
|
+
"data-4": discreteRainbow[25],
|
|
377
|
+
"data-5": discreteRainbow[8],
|
|
378
|
+
"data-6": discreteRainbow[20],
|
|
379
|
+
"data-7": discreteRainbow[13],
|
|
380
|
+
"data-8": discreteRainbow[15],
|
|
381
|
+
"data-9": discreteRainbow[3],
|
|
382
|
+
"data-10": discreteRainbow[22],
|
|
383
|
+
// Text colors - inverted for dark mode
|
|
384
|
+
"text-primary": greyRamp[50],
|
|
385
|
+
"text-secondary": greyRamp[400],
|
|
386
|
+
// Deliberately ONE step lighter than the L*-nearest match (`greyRamp[600]`).
|
|
387
|
+
// The colour this replaced failed WCAG large-text outright on the three
|
|
388
|
+
// lightest planes — 2.96 / 2.72 / 2.49 against elevated / raised / overlay —
|
|
389
|
+
// and `600` carried that failure forward almost exactly (2.93 / 2.70 / 2.47).
|
|
390
|
+
// `500` clears 3:1 everywhere (3.32–5.34). Contrast beats colour fidelity for
|
|
391
|
+
// text roles; borders and surfaces still use strict L*-nearest. See
|
|
392
|
+
// semantic-contrast.test.ts, which fails if this is moved back down.
|
|
393
|
+
"text-tertiary": greyRamp[500],
|
|
394
|
+
"text-disabled": "rgba(255, 255, 255, 0.38)",
|
|
395
|
+
"text-inverse": greyRamp[950],
|
|
396
|
+
"text-link": "#828DF8",
|
|
397
|
+
"text-link-hover": primitiveRamps.blue[400],
|
|
398
|
+
// Surface colors - dark backgrounds — warm-tapered DERIVED ramp (TD-surface-tokens,
|
|
399
|
+
// S-1, re-spaced S-3). Shipped verbatim from `deriveSurfaceRamp()` — see
|
|
400
|
+
// `surfaceRampDark` in primitives.ts for the full derivation note. `surface-overlay`
|
|
401
|
+
// and `surface-elevated` are distinct (were both #191919 pre-S-1); `surface-input`
|
|
402
|
+
// tracks one plane above `surface-base`, same relative position as before the remap.
|
|
403
|
+
// `background-base` backs `Surface level="background"` (SurfaceContext.SURFACE_LEVEL_TOKEN),
|
|
404
|
+
// so it takes the ramp's shell role. The deepest plane is `background-frame`,
|
|
405
|
+
// which is also the `SurfaceLevel` floor a pressed surface clamps at.
|
|
406
|
+
"surface-base": greyRamp[925],
|
|
407
|
+
// main surface (#252321, L*13.9)
|
|
408
|
+
"surface-elevated": greyRamp[900],
|
|
409
|
+
// elevated surface (#2C2A28, L*17.2 — nav/rail)
|
|
410
|
+
"surface-raised": greyRamp[875],
|
|
411
|
+
// raised surface (#31302F, L*19.9 — cards)
|
|
412
|
+
"surface-overlay": greyRamp[850],
|
|
413
|
+
// overlay surface (#373635, L*22.7 — hero/popover)
|
|
414
|
+
"surface-input": greyRamp[900],
|
|
415
|
+
// input surface (#2C2A28 — one plane above base)
|
|
416
|
+
// Background colors — same ramp, the frame/shell end of it.
|
|
417
|
+
"background-base": greyRamp[950],
|
|
418
|
+
// shell (#1C1916, L*9 — Surface level="background")
|
|
419
|
+
"background-default": greyRamp[925],
|
|
420
|
+
// main background (#252321, L*13.9 — matches surface-base)
|
|
421
|
+
"background-subtle": greyRamp[900],
|
|
422
|
+
// subtle background (#2C2A28, L*17.2 — matches surface-elevated)
|
|
423
|
+
// Frame/bezel chrome — the top bar + side nav shell, one step BELOW
|
|
424
|
+
// `background-base`. It used to be described as sitting OUTSIDE the ramp; it
|
|
425
|
+
// is simply the ramp's last step now, and the floor `<Surface pressed>` clamps at.
|
|
426
|
+
"background-frame": greyRamp[975],
|
|
427
|
+
// frame / bezel (#100D0A, L*3.8)
|
|
428
|
+
// Border colors — solid dark borders are RETIRED (TD-07.14). Not a preference — a structural
|
|
429
|
+
// consequence of one ramp. Every step from 850 to 975 is now a surface plane,
|
|
430
|
+
// so a solid border dark enough to read as a border necessarily equals some
|
|
431
|
+
// plane's fill, which is exactly the collision `surface.contract.test.ts` R2
|
|
432
|
+
// forbids. The only non-plane steps left in that band are 800 and 700, and
|
|
433
|
+
// borders there jump from L*~10-25 to L*~28-38 — a restyle, not a migration.
|
|
434
|
+
//
|
|
435
|
+
// So separation moves to the alpha hairlines, which dark mode already called
|
|
436
|
+
// "the primary separation cue". Being alpha, they composite against whatever
|
|
437
|
+
// plane they land on and cannot collide with any of them — the problem stops
|
|
438
|
+
// existing rather than being re-solved per plane. The three solid tokens were
|
|
439
|
+
// deleted outright; consumers point at the `hairline-*` family below.
|
|
440
|
+
//
|
|
441
|
+
// `border-prominent` stays SOLID: it is the one border meant to be seen
|
|
442
|
+
// outright (4 call sites, high-visibility dividers), and grey-800 is not a
|
|
443
|
+
// plane, so it keeps its job without collision.
|
|
444
|
+
"border-prominent": greyRamp[800],
|
|
445
|
+
// high-visibility divider
|
|
446
|
+
"border-focus": "#828DF8",
|
|
447
|
+
"border-input": greyRamp[700],
|
|
448
|
+
"border-input-hover": greyRamp[600],
|
|
449
|
+
"border-input-focus": "#828DF8",
|
|
450
|
+
"border-input-error": primitiveRamps.red[500],
|
|
451
|
+
// Alpha hairline separators — the primary separation cue (§4/S-2). Self-
|
|
452
|
+
// normalizing: composites toward white by ~the same amount on ANY plane, so
|
|
453
|
+
// one family works at every elevation instead of per-surface border tokens.
|
|
454
|
+
// Shadows are demoted to floating-overlay use only (not shipped as a fill
|
|
455
|
+
// separator here — see elevation.ts, follow-up S-4).
|
|
456
|
+
//
|
|
457
|
+
// RETUNED on the wall (VW-99, S-6). The original .06/.09/.14 were set at a
|
|
458
|
+
// desk. On the panel at ~3 m `subtle` rendered but was indiscernible, and
|
|
459
|
+
// `default` — the load-bearing one, with no solid-border fallback since
|
|
460
|
+
// TD-07.14 — went weak on the lightest plane. `strong` read cleanly with room
|
|
461
|
+
// to spare, which is what made raising the whole family viable.
|
|
462
|
+
//
|
|
463
|
+
// Each tier steps up onto the next MEASURED-GOOD rung rather than an
|
|
464
|
+
// interpolated one: the new `subtle` is the old `default`, and the new
|
|
465
|
+
// `default` is the old `strong` — a value just confirmed legible on all four
|
|
466
|
+
// planes. Spacing goes 1 : 1.56 : 2.33, against the original 1 : 1.5 : 2.33.
|
|
467
|
+
//
|
|
468
|
+
// RUN 2 (VW-99): all three tiers were seen on all four planes — the retune
|
|
469
|
+
// worked — but `subtle` was still "a bit hard to read", so the whole family
|
|
470
|
+
// took a further +.01. That is a comfort margin on a passing row, not a fix
|
|
471
|
+
// for a failing one, which is why it is a flat offset: the 1 : 1.56 : 2.33
|
|
472
|
+
// spacing that the run validated is preserved rather than re-derived.
|
|
473
|
+
//
|
|
474
|
+
// Keep these to TWO decimals. react-native-web quantises alpha to 8 bits, so
|
|
475
|
+
// a 3-decimal value silently rounds on the way to the DOM (.135 renders as
|
|
476
|
+
// .13) — the token would say one thing and paint another.
|
|
477
|
+
//
|
|
478
|
+
// DARK ONLY. The light family above composites toward BLACK on light planes,
|
|
479
|
+
// which this run says nothing about — do not mirror these numbers into it
|
|
480
|
+
// without its own verification.
|
|
481
|
+
"hairline-subtle": "rgba(255, 255, 255, 0.10)",
|
|
482
|
+
"hairline-default": "rgba(255, 255, 255, 0.15)",
|
|
483
|
+
"hairline-strong": "rgba(255, 255, 255, 0.22)",
|
|
484
|
+
// Interactive states
|
|
485
|
+
"interactive-hover": "rgba(255, 255, 255, 0.04)",
|
|
486
|
+
"interactive-focus": "rgba(255, 255, 255, 0.12)",
|
|
487
|
+
"interactive-active": "rgba(255, 255, 255, 0.16)",
|
|
488
|
+
"interactive-selected": "rgba(255, 255, 255, 0.08)",
|
|
489
|
+
"interactive-disabled": "rgba(255, 255, 255, 0.12)",
|
|
490
|
+
"interactive-disabled-text": "rgba(255, 255, 255, 0.26)",
|
|
491
|
+
// Divider
|
|
492
|
+
"divider": "rgba(255, 255, 255, 0.09)",
|
|
493
|
+
// Avatar default
|
|
494
|
+
"avatar-background": greyRamp[700],
|
|
495
|
+
"avatar-text": primitiveColors.white
|
|
496
|
+
};
|
|
497
|
+
function getSemanticColors(mode) {
|
|
498
|
+
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
212
499
|
}
|
|
213
|
-
var jsx = wrapJsx(jsxRuntime.jsx);
|
|
214
|
-
var jsxs = wrapJsx(jsxRuntime.jsxs);
|
|
215
500
|
|
|
216
|
-
// src/
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
reactNative.Animated.timing(value, {
|
|
233
|
-
toValue: 0,
|
|
234
|
-
duration: PULSE_HALF_MS,
|
|
235
|
-
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
236
|
-
useNativeDriver: false
|
|
237
|
-
})
|
|
238
|
-
])
|
|
239
|
-
);
|
|
240
|
-
loop.start();
|
|
241
|
-
return () => loop.stop();
|
|
242
|
-
}, [active, value]);
|
|
243
|
-
return value;
|
|
244
|
-
}
|
|
245
|
-
function SegmentedBar({
|
|
246
|
-
segments,
|
|
247
|
-
height = 8,
|
|
248
|
-
gap = SEGMENTED_BAR_GAP,
|
|
249
|
-
radius = 2,
|
|
250
|
-
marker = null,
|
|
251
|
-
segmentTestID,
|
|
252
|
-
style,
|
|
253
|
-
...props
|
|
254
|
-
}) {
|
|
255
|
-
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
256
|
-
return /* @__PURE__ */ jsxs(reactNative.View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
257
|
-
segments.map((seg, i) => {
|
|
258
|
-
const fillStyle = {
|
|
259
|
-
width: `${(seg.fill ?? 1) * 100}%`,
|
|
260
|
-
height: "100%",
|
|
261
|
-
backgroundColor: seg.color,
|
|
262
|
-
opacity: seg.opacity
|
|
263
|
-
};
|
|
264
|
-
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
265
|
-
return /* @__PURE__ */ jsx(
|
|
266
|
-
reactNative.View,
|
|
267
|
-
{
|
|
268
|
-
style: {
|
|
269
|
-
flex: seg.weight ?? 1,
|
|
270
|
-
minWidth: 0,
|
|
271
|
-
height: "100%",
|
|
272
|
-
marginLeft: seg.leadingGap,
|
|
273
|
-
borderRadius: radius,
|
|
274
|
-
overflow: "hidden"
|
|
275
|
-
},
|
|
276
|
-
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
277
|
-
reactNative.Animated.View,
|
|
278
|
-
{
|
|
279
|
-
style: {
|
|
280
|
-
...fillStyle,
|
|
281
|
-
...seg.pulseColor ? {
|
|
282
|
-
backgroundColor: pulse.interpolate({
|
|
283
|
-
inputRange: [0, 1],
|
|
284
|
-
outputRange: seg.pulseColor
|
|
285
|
-
})
|
|
286
|
-
} : {
|
|
287
|
-
opacity: pulse.interpolate({
|
|
288
|
-
inputRange: [0, 1],
|
|
289
|
-
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
290
|
-
})
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
accessibilityElementsHidden: true,
|
|
294
|
-
testID
|
|
295
|
-
}
|
|
296
|
-
) : /* @__PURE__ */ jsx(reactNative.View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
297
|
-
},
|
|
298
|
-
i
|
|
299
|
-
);
|
|
300
|
-
}),
|
|
301
|
-
marker ? /* @__PURE__ */ jsx(
|
|
302
|
-
reactNative.View,
|
|
303
|
-
{
|
|
304
|
-
style: {
|
|
305
|
-
position: "absolute",
|
|
306
|
-
left: `${marker.position * 100}%`,
|
|
307
|
-
top: 0,
|
|
308
|
-
bottom: 0,
|
|
309
|
-
width: 2,
|
|
310
|
-
backgroundColor: marker.color
|
|
311
|
-
},
|
|
312
|
-
accessibilityElementsHidden: true,
|
|
313
|
-
testID: "segmented-bar-marker"
|
|
314
|
-
}
|
|
315
|
-
) : null
|
|
316
|
-
] });
|
|
501
|
+
// src/theme/color-utils.ts
|
|
502
|
+
function hexToRgb(hex) {
|
|
503
|
+
const cleaned = hex.replace("#", "");
|
|
504
|
+
let r, g, b;
|
|
505
|
+
if (cleaned.length === 3) {
|
|
506
|
+
r = parseInt(cleaned[0] + cleaned[0], 16);
|
|
507
|
+
g = parseInt(cleaned[1] + cleaned[1], 16);
|
|
508
|
+
b = parseInt(cleaned[2] + cleaned[2], 16);
|
|
509
|
+
} else if (cleaned.length === 6) {
|
|
510
|
+
r = parseInt(cleaned.slice(0, 2), 16);
|
|
511
|
+
g = parseInt(cleaned.slice(2, 4), 16);
|
|
512
|
+
b = parseInt(cleaned.slice(4, 6), 16);
|
|
513
|
+
} else {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
return { r, g, b };
|
|
317
517
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
};
|
|
339
|
-
function velocityZoneColor(v) {
|
|
340
|
-
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
341
|
-
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
342
|
-
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
343
|
-
return SET_STRIP_ZONES.fastest;
|
|
518
|
+
function rgbToHsv(r, g, b) {
|
|
519
|
+
const rNorm = r / 255;
|
|
520
|
+
const gNorm = g / 255;
|
|
521
|
+
const bNorm = b / 255;
|
|
522
|
+
const max = Math.max(rNorm, gNorm, bNorm);
|
|
523
|
+
const min = Math.min(rNorm, gNorm, bNorm);
|
|
524
|
+
const delta = max - min;
|
|
525
|
+
const v = max;
|
|
526
|
+
const s = max === 0 ? 0 : delta / max;
|
|
527
|
+
let h = 0;
|
|
528
|
+
if (delta !== 0) {
|
|
529
|
+
if (max === rNorm) {
|
|
530
|
+
h = 60 * ((gNorm - bNorm) / delta % 6);
|
|
531
|
+
} else if (max === gNorm) {
|
|
532
|
+
h = 60 * ((bNorm - rNorm) / delta + 2);
|
|
533
|
+
} else {
|
|
534
|
+
h = 60 * ((rNorm - gNorm) / delta + 4);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (h < 0) h += 360;
|
|
538
|
+
return { h, s, v };
|
|
344
539
|
}
|
|
345
|
-
function
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
540
|
+
function hsvToRgb(h, s, v) {
|
|
541
|
+
const c2 = v * s;
|
|
542
|
+
const x = c2 * (1 - Math.abs(h / 60 % 2 - 1));
|
|
543
|
+
const m = v - c2;
|
|
544
|
+
let rPrime, gPrime, bPrime;
|
|
545
|
+
if (h >= 0 && h < 60) {
|
|
546
|
+
rPrime = c2;
|
|
547
|
+
gPrime = x;
|
|
548
|
+
bPrime = 0;
|
|
549
|
+
} else if (h >= 60 && h < 120) {
|
|
550
|
+
rPrime = x;
|
|
551
|
+
gPrime = c2;
|
|
552
|
+
bPrime = 0;
|
|
553
|
+
} else if (h >= 120 && h < 180) {
|
|
554
|
+
rPrime = 0;
|
|
555
|
+
gPrime = c2;
|
|
556
|
+
bPrime = x;
|
|
557
|
+
} else if (h >= 180 && h < 240) {
|
|
558
|
+
rPrime = 0;
|
|
559
|
+
gPrime = x;
|
|
560
|
+
bPrime = c2;
|
|
561
|
+
} else if (h >= 240 && h < 300) {
|
|
562
|
+
rPrime = x;
|
|
563
|
+
gPrime = 0;
|
|
564
|
+
bPrime = c2;
|
|
565
|
+
} else {
|
|
566
|
+
rPrime = c2;
|
|
567
|
+
gPrime = 0;
|
|
568
|
+
bPrime = x;
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
r: Math.round((rPrime + m) * 255),
|
|
572
|
+
g: Math.round((gPrime + m) * 255),
|
|
573
|
+
b: Math.round((bPrime + m) * 255)
|
|
574
|
+
};
|
|
352
575
|
}
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return
|
|
357
|
-
}
|
|
576
|
+
function rgbToHex(r, g, b) {
|
|
577
|
+
const toHex = (n) => {
|
|
578
|
+
const clamped = Math.max(0, Math.min(255, Math.round(n)));
|
|
579
|
+
return clamped.toString(16).padStart(2, "0");
|
|
580
|
+
};
|
|
581
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
358
582
|
}
|
|
359
|
-
function
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
367
|
-
}))
|
|
368
|
-
).flat();
|
|
369
|
-
return [...activation, ...trail];
|
|
583
|
+
function lighten(hex, amount = 0.1) {
|
|
584
|
+
const rgb = hexToRgb(hex);
|
|
585
|
+
if (!rgb) return hex;
|
|
586
|
+
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
587
|
+
const newV = Math.min(1, hsv.v + amount);
|
|
588
|
+
const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
|
|
589
|
+
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
370
590
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
|
|
591
|
+
|
|
592
|
+
// src/utils/colors.ts
|
|
593
|
+
function alpha(color, opacity) {
|
|
594
|
+
const clampedOpacity = Math.max(0, Math.min(1, opacity));
|
|
595
|
+
if (color.startsWith("#")) {
|
|
596
|
+
const hex = color.slice(1);
|
|
597
|
+
let r, g, b;
|
|
598
|
+
if (hex.length === 3) {
|
|
599
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
600
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
601
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
602
|
+
} else if (hex.length === 6) {
|
|
603
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
604
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
605
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
606
|
+
} else {
|
|
607
|
+
return color;
|
|
608
|
+
}
|
|
609
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
377
610
|
}
|
|
378
|
-
|
|
379
|
-
|
|
611
|
+
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
612
|
+
if (rgbMatch) {
|
|
613
|
+
const [, r, g, b] = rgbMatch;
|
|
614
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
380
615
|
}
|
|
381
|
-
|
|
382
|
-
|
|
616
|
+
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
617
|
+
if (rgbaMatch) {
|
|
618
|
+
const [, r, g, b] = rgbaMatch;
|
|
619
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
383
620
|
}
|
|
384
|
-
|
|
385
|
-
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
386
|
-
}
|
|
387
|
-
if (set.status === "myo-upcoming") {
|
|
388
|
-
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
389
|
-
}
|
|
390
|
-
const performed = set.velocities.map((v) => {
|
|
391
|
-
const color = velocityZoneColor(v);
|
|
392
|
-
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
393
|
-
});
|
|
394
|
-
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
395
|
-
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
621
|
+
return color;
|
|
396
622
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return
|
|
623
|
+
|
|
624
|
+
// src/utils/workout-format.ts
|
|
625
|
+
function roundRpe(rpe) {
|
|
626
|
+
return Math.round(rpe * 2) / 2;
|
|
401
627
|
}
|
|
402
|
-
function
|
|
403
|
-
return
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
628
|
+
function roundWeight(weight) {
|
|
629
|
+
return Math.round(weight);
|
|
630
|
+
}
|
|
631
|
+
function formatVelocity(velocity) {
|
|
632
|
+
return velocity.toFixed(2);
|
|
633
|
+
}
|
|
634
|
+
function roundTempo(tempo) {
|
|
635
|
+
return tempo.map((v) => Math.round(v * 10) / 10);
|
|
636
|
+
}
|
|
637
|
+
function parseHex(hex) {
|
|
638
|
+
const h = hex.replace("#", "");
|
|
639
|
+
const full = h.length === 3 ? h.split("").map((ch) => ch + ch).join("") : h;
|
|
640
|
+
const rgb = [
|
|
641
|
+
parseInt(full.slice(0, 2), 16),
|
|
642
|
+
parseInt(full.slice(2, 4), 16),
|
|
643
|
+
parseInt(full.slice(4, 6), 16)
|
|
644
|
+
];
|
|
645
|
+
return rgb.some(Number.isNaN) ? null : rgb;
|
|
646
|
+
}
|
|
647
|
+
function perceivedLuminance(hex) {
|
|
648
|
+
const rgb = parseHex(hex);
|
|
649
|
+
if (!rgb) return 0.5;
|
|
650
|
+
const [r, g, b] = rgb;
|
|
651
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
652
|
+
}
|
|
653
|
+
function grainOpacityForTone(baseColor) {
|
|
654
|
+
return Math.min(0.24, Math.max(0.04, 0.02 + 0.31 * perceivedLuminance(baseColor)));
|
|
655
|
+
}
|
|
656
|
+
function grainForTone(baseColor) {
|
|
657
|
+
const op = grainOpacityForTone(baseColor).toFixed(3);
|
|
658
|
+
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")`;
|
|
659
|
+
}
|
|
660
|
+
function barPaper(color, flip = false) {
|
|
661
|
+
return {
|
|
662
|
+
backgroundImage: grainForTone(color),
|
|
663
|
+
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)"
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
var SURFACE_LEVEL_TOKEN = {
|
|
667
|
+
frame: "background-frame",
|
|
668
|
+
background: "background-base",
|
|
669
|
+
base: "surface-base",
|
|
670
|
+
elevated: "surface-elevated",
|
|
671
|
+
raised: "surface-raised",
|
|
672
|
+
overlay: "surface-overlay"
|
|
673
|
+
};
|
|
674
|
+
var ON_SURFACE_TOKEN = {
|
|
675
|
+
primary: "text-primary",
|
|
676
|
+
secondary: "text-secondary",
|
|
677
|
+
tertiary: "text-tertiary"
|
|
678
|
+
};
|
|
679
|
+
var DEFAULT_CONTEXT = { mode: "dark", level: "base" };
|
|
680
|
+
var SurfaceContext = react.createContext(DEFAULT_CONTEXT);
|
|
681
|
+
function useSurface() {
|
|
682
|
+
return react.useContext(SurfaceContext);
|
|
683
|
+
}
|
|
684
|
+
function useSurfaceMode() {
|
|
685
|
+
return react.useContext(SurfaceContext).mode;
|
|
686
|
+
}
|
|
687
|
+
function useOnSurfaceColor(role = "primary") {
|
|
688
|
+
return getSemanticColors(useSurfaceMode())[ON_SURFACE_TOKEN[role]];
|
|
689
|
+
}
|
|
690
|
+
function surfaceBackground(level, mode) {
|
|
691
|
+
return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]];
|
|
692
|
+
}
|
|
693
|
+
var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
694
|
+
var PEAK_OVERSHOOT = 1.12;
|
|
695
|
+
function getReducedMotionPreference() {
|
|
696
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
697
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
698
|
+
}
|
|
699
|
+
function usePrefersReducedMotion() {
|
|
700
|
+
const [reduced, setReduced] = react.useState(getReducedMotionPreference);
|
|
701
|
+
react.useEffect(() => {
|
|
702
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
703
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
704
|
+
const handler = () => setReduced(mq.matches);
|
|
705
|
+
handler();
|
|
706
|
+
mq.addEventListener?.("change", handler);
|
|
707
|
+
return () => mq.removeEventListener?.("change", handler);
|
|
708
|
+
}, []);
|
|
709
|
+
return reduced;
|
|
710
|
+
}
|
|
711
|
+
function useLiveRepGrowth(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
712
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
713
|
+
const [liveGrowth] = react.useState(() => new reactNative.Animated.Value(1));
|
|
714
|
+
react.useEffect(() => {
|
|
715
|
+
if (liveRepIndex == null || liveVelocity == null) return;
|
|
716
|
+
if (prefersReducedMotion) {
|
|
717
|
+
liveGrowth.setValue(1);
|
|
718
|
+
return;
|
|
413
719
|
}
|
|
414
|
-
|
|
720
|
+
liveGrowth.setValue(0);
|
|
721
|
+
if (isNewPeak) {
|
|
722
|
+
reactNative.Animated.sequence([
|
|
723
|
+
reactNative.Animated.timing(liveGrowth, {
|
|
724
|
+
toValue: PEAK_OVERSHOOT,
|
|
725
|
+
duration: 220,
|
|
726
|
+
easing: reactNative.Easing.out(reactNative.Easing.quad),
|
|
727
|
+
useNativeDriver: false
|
|
728
|
+
}),
|
|
729
|
+
reactNative.Animated.spring(liveGrowth, {
|
|
730
|
+
toValue: 1,
|
|
731
|
+
friction: 5,
|
|
732
|
+
tension: 140,
|
|
733
|
+
useNativeDriver: false
|
|
734
|
+
})
|
|
735
|
+
]).start();
|
|
736
|
+
} else {
|
|
737
|
+
reactNative.Animated.timing(liveGrowth, {
|
|
738
|
+
toValue: 1,
|
|
739
|
+
duration: 300,
|
|
740
|
+
easing: ANIMATION_EASING,
|
|
741
|
+
useNativeDriver: false
|
|
742
|
+
}).start();
|
|
743
|
+
}
|
|
744
|
+
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveGrowth]);
|
|
745
|
+
return liveGrowth;
|
|
415
746
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
case "myo-upcoming":
|
|
431
|
-
return "upcoming";
|
|
432
|
-
}
|
|
747
|
+
function wrapJsx(fn) {
|
|
748
|
+
return function(type, props, ...rest) {
|
|
749
|
+
if (props && typeof props.className === "string" && props.className) {
|
|
750
|
+
const cn2 = props.className;
|
|
751
|
+
const cssStyle = { $$css: true, [cn2]: cn2 };
|
|
752
|
+
const existing = props.style;
|
|
753
|
+
props = {
|
|
754
|
+
...props,
|
|
755
|
+
style: existing ? [cssStyle, existing] : cssStyle
|
|
756
|
+
};
|
|
757
|
+
delete props.className;
|
|
758
|
+
}
|
|
759
|
+
return fn(type, props, ...rest);
|
|
760
|
+
};
|
|
433
761
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
762
|
+
var jsx = wrapJsx(jsxRuntime.jsx);
|
|
763
|
+
var jsxs = wrapJsx(jsxRuntime.jsxs);
|
|
764
|
+
|
|
765
|
+
// src/components/custom/charts/SetBarChart.tsx
|
|
766
|
+
function mixHex(a, b, t12) {
|
|
767
|
+
const parse = (h) => {
|
|
768
|
+
const s = h.replace("#", "");
|
|
769
|
+
return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
|
|
770
|
+
};
|
|
771
|
+
const [ar, ag, ab] = parse(a);
|
|
772
|
+
const [br, bg, bb] = parse(b);
|
|
773
|
+
const ch = (x, y) => Math.round(x + (y - x) * t12).toString(16).padStart(2, "0");
|
|
774
|
+
return `#${ch(ar, br)}${ch(ag, bg)}${ch(ab, bb)}`;
|
|
439
775
|
}
|
|
440
|
-
|
|
441
|
-
|
|
776
|
+
var VARIABLE_FILL = primitiveRamps.cyan[900];
|
|
777
|
+
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
778
|
+
var FIXED_MAX_VALUE = 1.15;
|
|
779
|
+
var PEAK_HEADROOM = 1.03;
|
|
780
|
+
function scaleDenominator(scale, maxValue) {
|
|
781
|
+
return scale === "fixed" ? FIXED_MAX_VALUE : maxValue * PEAK_HEADROOM;
|
|
782
|
+
}
|
|
783
|
+
function valueLabelFontSize(height) {
|
|
784
|
+
return Math.round(Math.max(8, Math.min(12, height * 0.11)));
|
|
785
|
+
}
|
|
786
|
+
var SET_BAR_DEFAULT_HEIGHT = 220;
|
|
787
|
+
var LABEL_ROW_HEIGHT = 16;
|
|
788
|
+
var LABEL_GAP = 3;
|
|
789
|
+
var GAP_RATIO = 0.08;
|
|
790
|
+
var MIN_BAR_GAP = 2;
|
|
791
|
+
var CHUNK_NOTCH_RATIO = 0.25;
|
|
792
|
+
var CHUNK_NOTCH_MIN_PX = 10;
|
|
793
|
+
var BAR_MAX_WIDTH = 120;
|
|
794
|
+
var LABEL_MIN_BAR_WIDTH = 30;
|
|
795
|
+
var DEFAULT_BAR_RADIUS = 5;
|
|
796
|
+
var MIN_BAR_HEIGHT = 4;
|
|
797
|
+
var FLAT_BAR_FRACTION = 0.5;
|
|
798
|
+
var STUB_HEIGHT = 9;
|
|
799
|
+
var REP_GAP = 2;
|
|
800
|
+
function computeBarLayout(plotWidth, count, gapRatio = GAP_RATIO) {
|
|
801
|
+
const rawBarWidth = plotWidth > 0 && count > 0 ? plotWidth / (count + (count - 1) * gapRatio) : BAR_MAX_WIDTH;
|
|
802
|
+
const barWidth = Math.min(BAR_MAX_WIDTH, rawBarWidth);
|
|
803
|
+
const gap = plotWidth === 0 ? MIN_BAR_GAP : Math.max(MIN_BAR_GAP, gapRatio * barWidth);
|
|
804
|
+
return { barWidth, gap };
|
|
805
|
+
}
|
|
806
|
+
function isRep(slot) {
|
|
807
|
+
return slot.kind === "rep";
|
|
808
|
+
}
|
|
809
|
+
function repIndexByCell(cells) {
|
|
810
|
+
let count = 0;
|
|
811
|
+
return cells.map((slot) => isRep(slot) ? count++ : -1);
|
|
812
|
+
}
|
|
813
|
+
function SetBarChart({
|
|
814
|
+
slots,
|
|
815
|
+
colorFor,
|
|
816
|
+
height,
|
|
817
|
+
scale = "peak",
|
|
818
|
+
scaleMax,
|
|
819
|
+
orientation = "up",
|
|
820
|
+
liveRepIndex,
|
|
821
|
+
isNewPeak = false,
|
|
822
|
+
targetReps,
|
|
823
|
+
gapRatio = GAP_RATIO,
|
|
824
|
+
barRadius = DEFAULT_BAR_RADIUS,
|
|
825
|
+
cornerStyle = "top",
|
|
826
|
+
flat = false,
|
|
827
|
+
showValueLabels: showValueLabelsProp = false,
|
|
828
|
+
formatValue: formatValue2 = String,
|
|
829
|
+
todoVariant = "solid",
|
|
830
|
+
minColumns,
|
|
831
|
+
renderReference,
|
|
832
|
+
renderBarOverlay,
|
|
833
|
+
expandProgress,
|
|
834
|
+
hideBaseline = false,
|
|
835
|
+
testID,
|
|
836
|
+
testIDPrefix = "setbar",
|
|
837
|
+
accessibilityLabel: accessibilityLabel2,
|
|
838
|
+
label,
|
|
839
|
+
className,
|
|
840
|
+
viewProps = {}
|
|
841
|
+
}) {
|
|
842
|
+
const targetPadded = Math.max(slots.length, targetReps ?? 0, minColumns ?? 0);
|
|
843
|
+
const cells = targetPadded > slots.length ? [
|
|
844
|
+
...slots,
|
|
845
|
+
...Array.from({ length: targetPadded - slots.length }, () => ({ kind: "todo" }))
|
|
846
|
+
] : slots;
|
|
847
|
+
const repValues = cells.filter(isRep).map((s) => s.value ?? 0);
|
|
848
|
+
const best = repValues.length > 0 ? Math.max(...repValues) : 0;
|
|
849
|
+
const flip = orientation === "down";
|
|
850
|
+
const flipStyle = flip ? { transform: [{ scaleY: -1 }] } : null;
|
|
851
|
+
const placeholderColor = useOnSurfaceColor("tertiary");
|
|
852
|
+
const surface = useSurface();
|
|
853
|
+
const surfaceBg = surfaceBackground(surface.level, surface.mode);
|
|
854
|
+
const solidTodoColor = mixHex(surfaceBg, placeholderColor, 0.55);
|
|
855
|
+
const emptyColor = mixHex(surfaceBg, placeholderColor, 0.28);
|
|
856
|
+
const barCorners = cornerStyle === "all" ? { borderRadius: barRadius } : { borderTopLeftRadius: barRadius, borderTopRightRadius: barRadius };
|
|
857
|
+
const scaleDenom = scaleMax != null && scaleMax > 0 ? scaleMax * PEAK_HEADROOM : scaleDenominator(scale, best);
|
|
858
|
+
const showValueLabels = showValueLabelsProp && !flat;
|
|
859
|
+
const plotHeight = Math.max(0, height - (showValueLabels ? LABEL_ROW_HEIGHT + LABEL_GAP : 0));
|
|
860
|
+
const yOf = (value) => scaleDenom > 0 ? value / scaleDenom * plotHeight : 0;
|
|
861
|
+
const flatBarHeight = Math.max(MIN_BAR_HEIGHT, FLAT_BAR_FRACTION * plotHeight);
|
|
862
|
+
const compactBarHeight = Math.max(MIN_BAR_HEIGHT, plotHeight);
|
|
863
|
+
const valueBarHeight = (value) => scaleDenom > 0 ? Math.max(MIN_BAR_HEIGHT, Math.min(1, value / scaleDenom) * plotHeight) : MIN_BAR_HEIGHT;
|
|
864
|
+
const barHeight = (value) => flat ? compactBarHeight : valueBarHeight(value);
|
|
865
|
+
const liveValue = liveRepIndex != null && liveRepIndex < repValues.length ? repValues[liveRepIndex] : void 0;
|
|
866
|
+
const liveGrowth = useLiveRepGrowth(true, liveRepIndex, liveValue, isNewPeak);
|
|
867
|
+
const [plotW, setPlotW] = react.useState(0);
|
|
868
|
+
const onPlotLayout = (e) => setPlotW(e.nativeEvent.layout.width);
|
|
869
|
+
const totalCells = cells.length;
|
|
870
|
+
const { barWidth, gap } = computeBarLayout(plotW, totalCells, gapRatio);
|
|
871
|
+
const labelsCrowded = plotW > 0 && barWidth < LABEL_MIN_BAR_WIDTH;
|
|
872
|
+
const peakRepIndex = repValues.reduce((b, v, i) => v > repValues[b] ? i : b, 0);
|
|
873
|
+
const showBarLabel = (repIndex) => !labelsCrowded || repIndex === peakRepIndex || repIndex === liveRepIndex;
|
|
874
|
+
const geometry = { scaleDenom, plotHeight, yOf, best, flip };
|
|
875
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
876
|
+
const a11y = accessibilityLabel2 != null ? { accessibilityRole: "image", accessibilityLabel: accessibilityLabel2 } : {};
|
|
877
|
+
const repIndices = repIndexByCell(cells);
|
|
878
|
+
return /* @__PURE__ */ jsxs(
|
|
442
879
|
reactNative.View,
|
|
443
880
|
{
|
|
444
881
|
className,
|
|
445
|
-
style: { flexDirection: "row"
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
882
|
+
style: [{ height, flexDirection: "row" }, externalStyle],
|
|
883
|
+
testID,
|
|
884
|
+
...a11y,
|
|
885
|
+
...restProps,
|
|
886
|
+
children: [
|
|
887
|
+
label ? /* @__PURE__ */ jsx(
|
|
888
|
+
ChartSideRail,
|
|
889
|
+
{
|
|
890
|
+
sections: [{ label, testID: `${testIDPrefix}-side-label` }],
|
|
891
|
+
sectionExtent: height
|
|
892
|
+
}
|
|
893
|
+
) : null,
|
|
894
|
+
/* @__PURE__ */ jsxs(
|
|
895
|
+
reactNative.View,
|
|
896
|
+
{
|
|
897
|
+
onLayout: onPlotLayout,
|
|
898
|
+
style: {
|
|
899
|
+
flex: 1,
|
|
900
|
+
flexDirection: "row",
|
|
901
|
+
alignItems: "flex-end",
|
|
902
|
+
gap,
|
|
903
|
+
position: "relative",
|
|
904
|
+
borderBottomWidth: hideBaseline ? 0 : 2,
|
|
905
|
+
borderBottomColor: placeholderColor,
|
|
906
|
+
...flipStyle
|
|
907
|
+
},
|
|
908
|
+
children: [
|
|
909
|
+
renderReference?.(geometry),
|
|
910
|
+
cells.map((slot, i) => {
|
|
911
|
+
const isChunkBoundary = (slot.leadingGap ?? REP_GAP) > REP_GAP;
|
|
912
|
+
const extraGap = isChunkBoundary ? Math.max(CHUNK_NOTCH_MIN_PX, CHUNK_NOTCH_RATIO * barWidth) : 0;
|
|
913
|
+
const column = {
|
|
914
|
+
flex: 1,
|
|
915
|
+
maxWidth: BAR_MAX_WIDTH,
|
|
916
|
+
height: "100%",
|
|
917
|
+
alignItems: "center",
|
|
918
|
+
justifyContent: "flex-end",
|
|
919
|
+
marginLeft: extraGap
|
|
920
|
+
};
|
|
921
|
+
if (!isRep(slot)) {
|
|
922
|
+
return /* @__PURE__ */ jsx(reactNative.View, { accessibilityElementsHidden: true, style: column, children: renderStub(
|
|
923
|
+
slot.kind,
|
|
924
|
+
barCorners,
|
|
925
|
+
// Flat (compact) stubs fill the plot like the compact bars; else the fixed short stub.
|
|
926
|
+
flat ? compactBarHeight : STUB_HEIGHT,
|
|
927
|
+
placeholderColor,
|
|
928
|
+
testIDPrefix,
|
|
929
|
+
todoVariant,
|
|
930
|
+
solidTodoColor,
|
|
931
|
+
emptyColor
|
|
932
|
+
) }, i);
|
|
933
|
+
}
|
|
934
|
+
const repIndex = repIndices[i];
|
|
935
|
+
const value = slot.value ?? 0;
|
|
936
|
+
const isLive = liveRepIndex === repIndex;
|
|
937
|
+
const color = colorFor(value);
|
|
938
|
+
const barHeightStyle = expandProgress ? expandProgress.interpolate({
|
|
939
|
+
inputRange: [0, 1],
|
|
940
|
+
outputRange: [flatBarHeight, valueBarHeight(value)]
|
|
941
|
+
}) : isLive ? liveGrowth.interpolate({ inputRange: [0, 1], outputRange: [0, barHeight(value)] }) : barHeight(value);
|
|
942
|
+
const ownLabel = showValueLabels && !renderBarOverlay && showBarLabel(repIndex);
|
|
943
|
+
const labelBottom = (expandProgress ? valueBarHeight(best) : barHeight(best)) + LABEL_GAP;
|
|
944
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { accessibilityElementsHidden: renderBarOverlay == null, style: column, children: [
|
|
945
|
+
ownLabel && // All labels pin to one aligned row JUST ABOVE THE PEAK BAR (not the container top),
|
|
946
|
+
// in the muted on-surface-secondary tone — so the row hugs the bars, no floating void.
|
|
947
|
+
/* @__PURE__ */ jsx(
|
|
948
|
+
reactNative.Animated.View,
|
|
949
|
+
{
|
|
950
|
+
style: {
|
|
951
|
+
position: "absolute",
|
|
952
|
+
bottom: labelBottom,
|
|
953
|
+
left: 0,
|
|
954
|
+
right: 0,
|
|
955
|
+
alignItems: "center",
|
|
956
|
+
...expandProgress ? { opacity: expandProgress } : null
|
|
957
|
+
},
|
|
958
|
+
pointerEvents: "none",
|
|
959
|
+
children: /* @__PURE__ */ jsx(
|
|
960
|
+
reactNative.Text,
|
|
961
|
+
{
|
|
962
|
+
className: "text-text-secondary",
|
|
963
|
+
style: [{ fontSize: valueLabelFontSize(height), fontWeight: "700" }, flipStyle],
|
|
964
|
+
testID: `${testIDPrefix}-label-${repIndex}`,
|
|
965
|
+
children: formatValue2(value)
|
|
966
|
+
}
|
|
967
|
+
)
|
|
968
|
+
}
|
|
969
|
+
),
|
|
970
|
+
/* @__PURE__ */ jsx(
|
|
971
|
+
reactNative.Animated.View,
|
|
972
|
+
{
|
|
973
|
+
style: [
|
|
974
|
+
{ width: "100%", height: barHeightStyle, backgroundColor: color },
|
|
975
|
+
barCorners,
|
|
976
|
+
barPaper(color, flip)
|
|
977
|
+
],
|
|
978
|
+
testID: `${testIDPrefix}-bar-${repIndex}`
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
renderBarOverlay?.(repIndex, value)
|
|
982
|
+
] }, i);
|
|
983
|
+
})
|
|
984
|
+
]
|
|
985
|
+
}
|
|
986
|
+
)
|
|
987
|
+
]
|
|
451
988
|
}
|
|
452
989
|
);
|
|
453
990
|
}
|
|
991
|
+
function renderStub(kind, barCorners, stubHeight, placeholderColor, testIDPrefix, todoVariant, solidTodoColor, emptyColor) {
|
|
992
|
+
const base = {
|
|
993
|
+
width: "100%",
|
|
994
|
+
height: stubHeight,
|
|
995
|
+
...barCorners
|
|
996
|
+
};
|
|
997
|
+
if (kind === "variable") {
|
|
998
|
+
return /* @__PURE__ */ jsx(
|
|
999
|
+
reactNative.View,
|
|
1000
|
+
{
|
|
1001
|
+
style: { ...base, backgroundColor: VARIABLE_FILL },
|
|
1002
|
+
testID: `${testIDPrefix}-slot-variable`
|
|
1003
|
+
}
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
if (kind === "continue") {
|
|
1007
|
+
return /* @__PURE__ */ jsx(
|
|
1008
|
+
reactNative.View,
|
|
1009
|
+
{
|
|
1010
|
+
style: { ...base, borderWidth: 1, borderColor: CONTINUE_OUTLINE },
|
|
1011
|
+
testID: `${testIDPrefix}-slot-continue`
|
|
1012
|
+
}
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
if (kind === "empty") {
|
|
1016
|
+
return /* @__PURE__ */ jsx(
|
|
1017
|
+
reactNative.View,
|
|
1018
|
+
{
|
|
1019
|
+
style: { ...base, backgroundColor: emptyColor },
|
|
1020
|
+
testID: `${testIDPrefix}-slot-empty`
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
const todoStyle = todoVariant === "solid" ? { ...base, backgroundColor: solidTodoColor } : { ...base, borderWidth: 1, borderStyle: "dashed", borderColor: placeholderColor };
|
|
1025
|
+
return /* @__PURE__ */ jsx(reactNative.View, { style: todoStyle, testID: `${testIDPrefix}-slot-todo` });
|
|
1026
|
+
}
|
|
1027
|
+
function sideLabelFontSize(height) {
|
|
1028
|
+
return Math.round(Math.max(7, Math.min(13, height * 0.06)));
|
|
1029
|
+
}
|
|
1030
|
+
var SIDE_LABEL_INITIALS_BELOW = 70;
|
|
1031
|
+
function sideLabelText(label, extent) {
|
|
1032
|
+
if (extent >= SIDE_LABEL_INITIALS_BELOW) return label;
|
|
1033
|
+
return label.trim().split(/\s+/).map((word) => word.charAt(0)).join(" ").toUpperCase();
|
|
1034
|
+
}
|
|
1035
|
+
function ChartSideRail({
|
|
1036
|
+
sections,
|
|
1037
|
+
sectionExtent,
|
|
1038
|
+
variant = "hero"
|
|
1039
|
+
}) {
|
|
1040
|
+
if (!sections.some((s) => s.label)) return null;
|
|
1041
|
+
const isRail = variant === "rail";
|
|
1042
|
+
const labelLength = Math.max(0, sectionExtent - (isRail ? 0 : 6));
|
|
1043
|
+
const fontSize = isRail ? 7 : sideLabelFontSize(sectionExtent);
|
|
1044
|
+
const textStyle = {
|
|
1045
|
+
maxWidth: "100%",
|
|
1046
|
+
textAlign: "center",
|
|
1047
|
+
fontSize,
|
|
1048
|
+
fontWeight: "700",
|
|
1049
|
+
letterSpacing: isRail || fontSize < 10 ? 0 : 2,
|
|
1050
|
+
textTransform: "uppercase"
|
|
1051
|
+
};
|
|
1052
|
+
return /* @__PURE__ */ jsx(reactNative.View, { style: { width: isRail ? 18 : 34 }, accessibilityElementsHidden: true, children: sections.map((s) => /* @__PURE__ */ jsx(
|
|
1053
|
+
reactNative.View,
|
|
1054
|
+
{
|
|
1055
|
+
style: { height: sectionExtent, alignItems: "center", justifyContent: "center" },
|
|
1056
|
+
children: s.label ? /* @__PURE__ */ jsx(
|
|
1057
|
+
reactNative.View,
|
|
1058
|
+
{
|
|
1059
|
+
style: {
|
|
1060
|
+
width: labelLength,
|
|
1061
|
+
alignItems: "center",
|
|
1062
|
+
transform: [{ rotate: "-90deg" }]
|
|
1063
|
+
},
|
|
1064
|
+
children: /* @__PURE__ */ jsx(
|
|
1065
|
+
reactNative.Text,
|
|
1066
|
+
{
|
|
1067
|
+
className: "text-text-tertiary",
|
|
1068
|
+
style: textStyle,
|
|
1069
|
+
numberOfLines: 1,
|
|
1070
|
+
testID: s.testID,
|
|
1071
|
+
children: isRail ? s.label : sideLabelText(s.label, sectionExtent)
|
|
1072
|
+
}
|
|
1073
|
+
)
|
|
1074
|
+
}
|
|
1075
|
+
) : null
|
|
1076
|
+
},
|
|
1077
|
+
s.testID
|
|
1078
|
+
)) });
|
|
1079
|
+
}
|
|
454
1080
|
|
|
455
1081
|
// src/components/custom/Workout/VelocityStrip.tsx
|
|
456
1082
|
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
@@ -496,6 +1122,18 @@ function calculateMeanVelocity(velocities) {
|
|
|
496
1122
|
const sum = velocities.reduce((acc, v) => acc + v, 0);
|
|
497
1123
|
return sum / velocities.length;
|
|
498
1124
|
}
|
|
1125
|
+
var VL_LOSS_THRESHOLDS = [10, 20, 30];
|
|
1126
|
+
function getVelocityLossColor(lossPct) {
|
|
1127
|
+
const [t1, t22, t32] = VL_LOSS_THRESHOLDS;
|
|
1128
|
+
if (lossPct < t1) return VEL_COLORS.green;
|
|
1129
|
+
if (lossPct < t22) return VEL_COLORS.yellow;
|
|
1130
|
+
if (lossPct < t32) return VEL_COLORS.orange;
|
|
1131
|
+
return VEL_COLORS.red;
|
|
1132
|
+
}
|
|
1133
|
+
function velocityLossForRep(velocity, best) {
|
|
1134
|
+
if (best <= 0) return 0;
|
|
1135
|
+
return Math.max(0, Math.round((best - velocity) / best * 100));
|
|
1136
|
+
}
|
|
499
1137
|
function classifyBand(velocity, bands) {
|
|
500
1138
|
for (const band of bands) {
|
|
501
1139
|
if (band.max === null || velocity < band.max) return band;
|
|
@@ -505,15 +1143,17 @@ function classifyBand(velocity, bands) {
|
|
|
505
1143
|
function bandColor(band) {
|
|
506
1144
|
return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
|
|
507
1145
|
}
|
|
1146
|
+
function makeBarColorFor(zones) {
|
|
1147
|
+
const hasZones = zones != null && zones.length > 0;
|
|
1148
|
+
return (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
1149
|
+
}
|
|
508
1150
|
function getLossStyle(loss) {
|
|
509
1151
|
if (loss > 25) return { color: VEL_COLORS.red };
|
|
510
1152
|
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
511
1153
|
return null;
|
|
512
1154
|
}
|
|
513
|
-
var
|
|
1155
|
+
var REP_GAP2 = 2;
|
|
514
1156
|
var WIDE_GAP = 8;
|
|
515
|
-
var TODO_COLOR2 = primitiveColors.charcoal[300];
|
|
516
|
-
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
517
1157
|
function deriveDoneVelocities(set) {
|
|
518
1158
|
switch (set.type) {
|
|
519
1159
|
case "drop":
|
|
@@ -529,7 +1169,7 @@ function chunkedRepSlots(chunks) {
|
|
|
529
1169
|
chunks.forEach((chunk) => {
|
|
530
1170
|
chunk.forEach((velocity, ri) => {
|
|
531
1171
|
const first = slots.length === 0;
|
|
532
|
-
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP :
|
|
1172
|
+
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP2 });
|
|
533
1173
|
});
|
|
534
1174
|
});
|
|
535
1175
|
return slots;
|
|
@@ -543,7 +1183,7 @@ function buildSlots(set) {
|
|
|
543
1183
|
return {
|
|
544
1184
|
kind: done ? "rep" : "todo",
|
|
545
1185
|
velocity: done ? set.velocities[i] : void 0,
|
|
546
|
-
leadingGap: i === 0 ? 0 :
|
|
1186
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
547
1187
|
};
|
|
548
1188
|
});
|
|
549
1189
|
}
|
|
@@ -554,7 +1194,7 @@ function buildSlots(set) {
|
|
|
554
1194
|
return {
|
|
555
1195
|
kind,
|
|
556
1196
|
velocity: kind === "rep" ? set.velocities[i] : void 0,
|
|
557
|
-
leadingGap: i === 0 ? 0 :
|
|
1197
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
558
1198
|
};
|
|
559
1199
|
});
|
|
560
1200
|
}
|
|
@@ -562,16 +1202,16 @@ function buildSlots(set) {
|
|
|
562
1202
|
const reps = set.velocities.map((velocity, i) => ({
|
|
563
1203
|
kind: "rep",
|
|
564
1204
|
velocity,
|
|
565
|
-
leadingGap: i === 0 ? 0 :
|
|
1205
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
566
1206
|
}));
|
|
567
|
-
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 :
|
|
1207
|
+
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP2 });
|
|
568
1208
|
return reps;
|
|
569
1209
|
}
|
|
570
1210
|
case "drop":
|
|
571
1211
|
return chunkedRepSlots(set.subloads);
|
|
572
1212
|
case "myo": {
|
|
573
1213
|
const slots = chunkedRepSlots([set.activation, ...set.clusters]);
|
|
574
|
-
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 :
|
|
1214
|
+
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP2 });
|
|
575
1215
|
return slots;
|
|
576
1216
|
}
|
|
577
1217
|
case "cluster": {
|
|
@@ -582,7 +1222,7 @@ function buildSlots(set) {
|
|
|
582
1222
|
return {
|
|
583
1223
|
kind: done ? "rep" : "todo",
|
|
584
1224
|
velocity: done ? set.velocities[i] : void 0,
|
|
585
|
-
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP :
|
|
1225
|
+
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP2
|
|
586
1226
|
};
|
|
587
1227
|
});
|
|
588
1228
|
}
|
|
@@ -604,253 +1244,148 @@ function setAccessibilityLabel(set, repCount) {
|
|
|
604
1244
|
return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
|
|
605
1245
|
}
|
|
606
1246
|
}
|
|
607
|
-
function slotAccessibilityLabel(slot, index) {
|
|
608
|
-
switch (slot.kind) {
|
|
609
|
-
case "rep":
|
|
610
|
-
return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
|
|
611
|
-
case "todo":
|
|
612
|
-
return `Rep ${index + 1}: planned`;
|
|
613
|
-
case "variable":
|
|
614
|
-
return `Rep ${index + 1}: variable`;
|
|
615
|
-
case "continue":
|
|
616
|
-
return "Keep going";
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
var EXPANDED_TODO_STUB_PCT = 16;
|
|
620
|
-
var EXPANDED_ENCODED_PCT = 45;
|
|
621
1247
|
var EXPANDED_HEIGHT = 60;
|
|
622
|
-
var
|
|
623
|
-
var
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1248
|
+
var COMPACT_HEIGHT = 8;
|
|
1249
|
+
var HERO_REFERENCE_COLOR = greyRamp[600];
|
|
1250
|
+
function DashedReferenceLine({
|
|
1251
|
+
anchor,
|
|
1252
|
+
offset,
|
|
1253
|
+
testID
|
|
1254
|
+
}) {
|
|
1255
|
+
const edge = anchor === "top" ? { top: offset } : { bottom: offset };
|
|
1256
|
+
return /* @__PURE__ */ jsx(
|
|
1257
|
+
reactNative.View,
|
|
1258
|
+
{
|
|
1259
|
+
accessibilityElementsHidden: true,
|
|
1260
|
+
style: {
|
|
1261
|
+
position: "absolute",
|
|
1262
|
+
left: 0,
|
|
1263
|
+
right: 0,
|
|
1264
|
+
borderTopWidth: 1,
|
|
1265
|
+
borderStyle: "dashed",
|
|
1266
|
+
borderColor: HERO_REFERENCE_COLOR,
|
|
1267
|
+
pointerEvents: "none",
|
|
1268
|
+
...edge
|
|
1269
|
+
},
|
|
1270
|
+
testID
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
641
1273
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const [reduced, setReduced] = react.useState(getReducedMotionPreference);
|
|
648
|
-
react.useEffect(() => {
|
|
649
|
-
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
650
|
-
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
651
|
-
const handler = () => setReduced(mq.matches);
|
|
652
|
-
handler();
|
|
653
|
-
mq.addEventListener?.("change", handler);
|
|
654
|
-
return () => mq.removeEventListener?.("change", handler);
|
|
655
|
-
}, []);
|
|
656
|
-
return reduced;
|
|
657
|
-
}
|
|
658
|
-
var ANIMATION_DURATION = 400;
|
|
659
|
-
var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
660
|
-
var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
|
|
661
|
-
function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
662
|
-
const prefersReducedMotion = usePrefersReducedMotion();
|
|
663
|
-
const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
|
|
664
|
-
react.useEffect(() => {
|
|
665
|
-
if (!active || liveRepIndex == null || liveVelocity == null) return;
|
|
666
|
-
if (prefersReducedMotion) {
|
|
667
|
-
liveScale.setValue(1);
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
if (isNewPeak) {
|
|
671
|
-
liveScale.setValue(1);
|
|
672
|
-
reactNative.Animated.sequence([
|
|
673
|
-
reactNative.Animated.timing(liveScale, {
|
|
674
|
-
toValue: 1.25,
|
|
675
|
-
duration: 150,
|
|
676
|
-
easing: reactNative.Easing.out(reactNative.Easing.quad),
|
|
677
|
-
useNativeDriver: true
|
|
678
|
-
}),
|
|
679
|
-
reactNative.Animated.spring(liveScale, {
|
|
680
|
-
toValue: 1,
|
|
681
|
-
friction: 3,
|
|
682
|
-
tension: 140,
|
|
683
|
-
useNativeDriver: true
|
|
684
|
-
})
|
|
685
|
-
]).start();
|
|
686
|
-
} else {
|
|
687
|
-
liveScale.setValue(0.8);
|
|
688
|
-
reactNative.Animated.timing(liveScale, {
|
|
689
|
-
toValue: 1,
|
|
690
|
-
duration: 300,
|
|
691
|
-
easing: POP_EASING,
|
|
692
|
-
useNativeDriver: true
|
|
693
|
-
}).start();
|
|
694
|
-
}
|
|
695
|
-
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
696
|
-
return liveScale;
|
|
697
|
-
}
|
|
698
|
-
function HeroVelocityChart({
|
|
699
|
-
doneVelocities,
|
|
700
|
-
barColorFor,
|
|
1274
|
+
var BAND_LABEL_FONT = "monospace";
|
|
1275
|
+
var VL_LABEL_MIN_PLOT = 65;
|
|
1276
|
+
var VL_SEMANTIC = getSemanticColors("dark");
|
|
1277
|
+
function VelocityLossBands({
|
|
1278
|
+
best,
|
|
701
1279
|
scaleDenom,
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
isNewPeak,
|
|
705
|
-
targetReps,
|
|
706
|
-
height,
|
|
707
|
-
className,
|
|
708
|
-
viewProps
|
|
1280
|
+
plotHeight,
|
|
1281
|
+
flip = false
|
|
709
1282
|
}) {
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
const
|
|
715
|
-
const
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1283
|
+
if (best <= 0 || scaleDenom <= 0 || plotHeight <= 0) return null;
|
|
1284
|
+
const yOf = (v) => v / scaleDenom * plotHeight;
|
|
1285
|
+
const vl20 = best * 0.8;
|
|
1286
|
+
const vl30 = best * 0.7;
|
|
1287
|
+
const vlFont = Math.round(Math.max(7, Math.min(9, plotHeight * 0.05)));
|
|
1288
|
+
const showLabels = plotHeight >= VL_LABEL_MIN_PLOT;
|
|
1289
|
+
const band = (loV, hiV, color) => /* @__PURE__ */ jsx(
|
|
1290
|
+
reactNative.View,
|
|
1291
|
+
{
|
|
1292
|
+
style: {
|
|
1293
|
+
position: "absolute",
|
|
1294
|
+
left: 0,
|
|
1295
|
+
right: 0,
|
|
1296
|
+
bottom: yOf(loV),
|
|
1297
|
+
height: Math.max(0, yOf(hiV) - yOf(loV)),
|
|
1298
|
+
backgroundColor: color
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
725
1301
|
);
|
|
726
|
-
const
|
|
727
|
-
const repCount = doneVelocities.length;
|
|
728
|
-
const total = Math.max(repCount, targetReps ?? repCount);
|
|
729
|
-
const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
730
|
-
const { style: externalStyle, ...restProps } = viewProps;
|
|
731
|
-
return /* @__PURE__ */ jsx(
|
|
1302
|
+
const threshold = (v, color, label) => /* @__PURE__ */ jsxs(
|
|
732
1303
|
reactNative.View,
|
|
733
1304
|
{
|
|
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
|
-
position: "absolute",
|
|
760
|
-
left: 0,
|
|
761
|
-
right: 0,
|
|
762
|
-
bottom: referencePx,
|
|
763
|
-
borderTopWidth: 1,
|
|
764
|
-
borderStyle: "dashed",
|
|
765
|
-
borderColor: HERO_REFERENCE_COLOR,
|
|
766
|
-
pointerEvents: "none"
|
|
767
|
-
},
|
|
768
|
-
testID: "velocity-hero-reference"
|
|
769
|
-
}
|
|
770
|
-
),
|
|
771
|
-
doneVelocities.map((velocity, i) => {
|
|
772
|
-
const isLive = liveRepIndex === i;
|
|
773
|
-
return /* @__PURE__ */ jsxs(
|
|
774
|
-
reactNative.View,
|
|
775
|
-
{
|
|
776
|
-
accessibilityElementsHidden: true,
|
|
777
|
-
style: {
|
|
778
|
-
flex: 1,
|
|
779
|
-
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
780
|
-
height: "100%",
|
|
781
|
-
alignItems: "center",
|
|
782
|
-
justifyContent: "flex-end"
|
|
783
|
-
},
|
|
784
|
-
children: [
|
|
785
|
-
showBarLabel(i) && /* @__PURE__ */ jsx(
|
|
786
|
-
reactNative.Text,
|
|
787
|
-
{
|
|
788
|
-
className: "text-text-primary",
|
|
789
|
-
style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
|
|
790
|
-
testID: `velocity-label-${i}`,
|
|
791
|
-
children: formatVelocity(velocity)
|
|
792
|
-
}
|
|
793
|
-
),
|
|
794
|
-
/* @__PURE__ */ jsx(
|
|
795
|
-
reactNative.Animated.View,
|
|
796
|
-
{
|
|
797
|
-
style: [
|
|
798
|
-
{
|
|
799
|
-
width: "100%",
|
|
800
|
-
height: barHeight(velocity),
|
|
801
|
-
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
802
|
-
borderTopRightRadius: HERO_BAR_RADIUS,
|
|
803
|
-
backgroundColor: barColorFor(velocity)
|
|
804
|
-
},
|
|
805
|
-
isLive ? { transform: [{ scale: liveScale }] } : null
|
|
806
|
-
],
|
|
807
|
-
testID: `velocity-bar-${i}`
|
|
808
|
-
}
|
|
809
|
-
)
|
|
810
|
-
]
|
|
811
|
-
},
|
|
812
|
-
i
|
|
813
|
-
);
|
|
814
|
-
}),
|
|
815
|
-
Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
|
|
816
|
-
reactNative.View,
|
|
817
|
-
{
|
|
818
|
-
accessibilityElementsHidden: true,
|
|
819
|
-
style: {
|
|
820
|
-
flex: 1,
|
|
821
|
-
maxWidth: HERO_BAR_MAX_WIDTH,
|
|
822
|
-
height: "100%",
|
|
823
|
-
alignItems: "center",
|
|
824
|
-
justifyContent: "flex-end"
|
|
825
|
-
},
|
|
826
|
-
children: /* @__PURE__ */ jsx(
|
|
827
|
-
reactNative.View,
|
|
828
|
-
{
|
|
829
|
-
style: {
|
|
830
|
-
width: "100%",
|
|
831
|
-
height: HERO_MIN_BAR_HEIGHT * 3,
|
|
832
|
-
borderWidth: 1,
|
|
833
|
-
borderStyle: "dashed",
|
|
834
|
-
borderColor: HERO_PENDING_COLOR,
|
|
835
|
-
borderTopLeftRadius: HERO_BAR_RADIUS,
|
|
836
|
-
borderTopRightRadius: HERO_BAR_RADIUS
|
|
837
|
-
},
|
|
838
|
-
testID: "velocity-slot-todo"
|
|
839
|
-
}
|
|
840
|
-
)
|
|
1305
|
+
style: {
|
|
1306
|
+
position: "absolute",
|
|
1307
|
+
left: 0,
|
|
1308
|
+
right: 0,
|
|
1309
|
+
bottom: yOf(v),
|
|
1310
|
+
// Collapse to zero height so the dashed border lands EXACTLY on `yOf(v)` — the
|
|
1311
|
+
// band edge — instead of floating up half a text-row (the label then centres on
|
|
1312
|
+
// the line, breaking it). Matches DashedReferenceLine's bare-border anchoring.
|
|
1313
|
+
height: 0,
|
|
1314
|
+
flexDirection: "row",
|
|
1315
|
+
alignItems: "center"
|
|
1316
|
+
},
|
|
1317
|
+
children: [
|
|
1318
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { flex: 9, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } }),
|
|
1319
|
+
showLabels ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
1320
|
+
/* @__PURE__ */ jsx(
|
|
1321
|
+
reactNative.Text,
|
|
1322
|
+
{
|
|
1323
|
+
style: {
|
|
1324
|
+
marginHorizontal: 6,
|
|
1325
|
+
fontSize: vlFont,
|
|
1326
|
+
fontWeight: "800",
|
|
1327
|
+
fontFamily: BAND_LABEL_FONT,
|
|
1328
|
+
color,
|
|
1329
|
+
...flip ? { transform: [{ scaleY: -1 }] } : null
|
|
841
1330
|
},
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1331
|
+
children: label
|
|
1332
|
+
}
|
|
1333
|
+
),
|
|
1334
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { flex: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } })
|
|
1335
|
+
] }) : null
|
|
1336
|
+
]
|
|
847
1337
|
}
|
|
848
1338
|
);
|
|
1339
|
+
return /* @__PURE__ */ jsxs(
|
|
1340
|
+
reactNative.View,
|
|
1341
|
+
{
|
|
1342
|
+
accessibilityElementsHidden: true,
|
|
1343
|
+
pointerEvents: "none",
|
|
1344
|
+
style: { position: "absolute", left: 0, right: 0, bottom: 0, top: 0 },
|
|
1345
|
+
testID: "velocity-loss-bands",
|
|
1346
|
+
children: [
|
|
1347
|
+
band(0, vl30, alpha(VL_SEMANTIC["status-error"], 0.09)),
|
|
1348
|
+
band(vl30, vl20, alpha(VL_SEMANTIC["status-warning"], 0.08)),
|
|
1349
|
+
threshold(vl20, alpha(VL_SEMANTIC["status-warning"], 0.75), "VL 20%"),
|
|
1350
|
+
threshold(vl30, alpha(VL_SEMANTIC["status-error"], 0.75), "VL 30%")
|
|
1351
|
+
]
|
|
1352
|
+
}
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
function velocityReferenceOverlay(g, showLossBands) {
|
|
1356
|
+
const referencePx = g.best > 0 && g.scaleDenom > 0 ? Math.min(g.plotHeight, g.yOf(g.best)) : 0;
|
|
1357
|
+
return /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
1358
|
+
showLossBands && /* @__PURE__ */ jsx(
|
|
1359
|
+
VelocityLossBands,
|
|
1360
|
+
{
|
|
1361
|
+
best: g.best,
|
|
1362
|
+
scaleDenom: g.scaleDenom,
|
|
1363
|
+
plotHeight: g.plotHeight,
|
|
1364
|
+
flip: g.flip
|
|
1365
|
+
}
|
|
1366
|
+
),
|
|
1367
|
+
referencePx > 0 && /* @__PURE__ */ jsx(
|
|
1368
|
+
DashedReferenceLine,
|
|
1369
|
+
{
|
|
1370
|
+
anchor: "bottom",
|
|
1371
|
+
offset: referencePx,
|
|
1372
|
+
testID: "velocity-hero-reference"
|
|
1373
|
+
}
|
|
1374
|
+
)
|
|
1375
|
+
] });
|
|
849
1376
|
}
|
|
1377
|
+
var ANIMATION_DURATION = 400;
|
|
850
1378
|
function VelocityStrip({
|
|
851
1379
|
velocities,
|
|
852
1380
|
set,
|
|
853
1381
|
zones,
|
|
1382
|
+
barColor = "loss",
|
|
1383
|
+
showLossBands,
|
|
1384
|
+
orientation = "up",
|
|
1385
|
+
scaleMax,
|
|
1386
|
+
hideBaseline,
|
|
1387
|
+
columnSlots,
|
|
1388
|
+
label,
|
|
854
1389
|
liveRepIndex,
|
|
855
1390
|
expanded = true,
|
|
856
1391
|
onToggle,
|
|
@@ -864,51 +1399,29 @@ function VelocityStrip({
|
|
|
864
1399
|
className,
|
|
865
1400
|
...props
|
|
866
1401
|
}) {
|
|
1402
|
+
const lossBandsOn = showLossBands ?? barColor === "loss";
|
|
867
1403
|
const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
|
|
868
|
-
const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
|
|
869
|
-
kind: "rep",
|
|
870
|
-
velocity: v,
|
|
871
|
-
leadingGap: i === 0 ? 0 : REP_GAP
|
|
872
|
-
}));
|
|
873
1404
|
const maxVelocity = Math.max(...doneVelocities, 0);
|
|
874
1405
|
const meanVelocity = calculateMeanVelocity(doneVelocities);
|
|
875
1406
|
const loss = calculateVelocityLoss(doneVelocities);
|
|
876
1407
|
const framed = showNumbers || showInfo;
|
|
877
|
-
const scaleDenom = scaleDenominator(scale, maxVelocity);
|
|
878
1408
|
const hasZones = zones != null && zones.length > 0;
|
|
879
|
-
const
|
|
880
|
-
const
|
|
881
|
-
if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
|
|
882
|
-
if (slot.kind === "todo") return TODO_COLOR2;
|
|
883
|
-
return SET_STRIP_VARIABLE_COLOR;
|
|
884
|
-
};
|
|
1409
|
+
const zoneColorFor = makeBarColorFor(zones);
|
|
1410
|
+
const barColorFor = (v) => barColor === "loss" ? getVelocityLossColor(velocityLossForRep(v, maxVelocity)) : zoneColorFor(v);
|
|
885
1411
|
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
886
|
-
const [
|
|
887
|
-
const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
1412
|
+
const [expandProgress] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
888
1413
|
const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
889
1414
|
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
890
1415
|
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
891
|
-
const liveScale = useLiveRepPop(
|
|
892
|
-
variant === "expanded" && framed,
|
|
893
|
-
liveRepIndex,
|
|
894
|
-
liveVelocity,
|
|
895
|
-
isNewPeak
|
|
896
|
-
);
|
|
897
1416
|
react.useEffect(() => {
|
|
898
1417
|
if (variant !== "expanded" || !framed) return;
|
|
899
1418
|
reactNative.Animated.parallel([
|
|
900
|
-
reactNative.Animated.timing(
|
|
901
|
-
toValue: expanded ?
|
|
1419
|
+
reactNative.Animated.timing(expandProgress, {
|
|
1420
|
+
toValue: expanded ? 1 : 0,
|
|
902
1421
|
duration: ANIMATION_DURATION,
|
|
903
1422
|
easing: ANIMATION_EASING,
|
|
904
1423
|
useNativeDriver: false
|
|
905
1424
|
}),
|
|
906
|
-
reactNative.Animated.timing(labelOpacity, {
|
|
907
|
-
toValue: expanded ? 1 : 0,
|
|
908
|
-
duration: 300,
|
|
909
|
-
delay: expanded ? 150 : 0,
|
|
910
|
-
useNativeDriver: false
|
|
911
|
-
}),
|
|
912
1425
|
reactNative.Animated.timing(infoOpacity, {
|
|
913
1426
|
toValue: expanded ? 1 : 0,
|
|
914
1427
|
duration: 300,
|
|
@@ -916,250 +1429,176 @@ function VelocityStrip({
|
|
|
916
1429
|
useNativeDriver: false
|
|
917
1430
|
})
|
|
918
1431
|
]).start();
|
|
919
|
-
}, [expanded, variant, framed,
|
|
1432
|
+
}, [expanded, variant, framed, expandProgress, infoOpacity]);
|
|
920
1433
|
if (set == null && velocities == null) return null;
|
|
921
1434
|
const repCount = doneVelocities.length;
|
|
922
1435
|
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
923
1436
|
if (variant === "hero") {
|
|
924
|
-
const heroHeight = height === EXPANDED_HEIGHT ?
|
|
1437
|
+
const heroHeight = height === EXPANDED_HEIGHT && columnSlots == null ? SET_BAR_DEFAULT_HEIGHT : height;
|
|
1438
|
+
const heroSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1439
|
+
kind: s.kind,
|
|
1440
|
+
value: s.velocity,
|
|
1441
|
+
leadingGap: s.leadingGap
|
|
1442
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
1443
|
+
const total = Math.max(repCount, targetReps ?? repCount);
|
|
1444
|
+
const heroLabel = maxVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(maxVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
925
1445
|
return /* @__PURE__ */ jsx(
|
|
926
|
-
|
|
1446
|
+
SetBarChart,
|
|
927
1447
|
{
|
|
928
|
-
|
|
929
|
-
barColorFor,
|
|
930
|
-
|
|
931
|
-
|
|
1448
|
+
slots: heroSlots,
|
|
1449
|
+
colorFor: barColorFor,
|
|
1450
|
+
height: heroHeight,
|
|
1451
|
+
scale,
|
|
1452
|
+
scaleMax,
|
|
1453
|
+
orientation,
|
|
932
1454
|
liveRepIndex,
|
|
933
1455
|
isNewPeak,
|
|
934
|
-
targetReps,
|
|
935
|
-
|
|
1456
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
1457
|
+
label,
|
|
1458
|
+
showValueLabels: true,
|
|
1459
|
+
formatValue: formatVelocity,
|
|
1460
|
+
renderReference: (g) => velocityReferenceOverlay(g, lossBandsOn),
|
|
1461
|
+
hideBaseline: true,
|
|
1462
|
+
testID: "velocity-strip-hero",
|
|
1463
|
+
testIDPrefix: "velocity",
|
|
1464
|
+
accessibilityLabel: heroLabel,
|
|
936
1465
|
className,
|
|
937
1466
|
viewProps: props
|
|
938
1467
|
}
|
|
939
1468
|
);
|
|
940
1469
|
}
|
|
941
|
-
if (variant === "
|
|
942
|
-
const
|
|
1470
|
+
if (variant === "compact") {
|
|
1471
|
+
const compactSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1472
|
+
kind: s.kind,
|
|
1473
|
+
value: s.velocity,
|
|
1474
|
+
leadingGap: s.leadingGap
|
|
1475
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
1476
|
+
const compactHeight = height === EXPANDED_HEIGHT ? COMPACT_HEIGHT : height;
|
|
943
1477
|
return /* @__PURE__ */ jsx(
|
|
944
|
-
|
|
1478
|
+
SetBarChart,
|
|
945
1479
|
{
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1480
|
+
slots: compactSlots,
|
|
1481
|
+
colorFor: barColorFor,
|
|
1482
|
+
height: compactHeight,
|
|
1483
|
+
scale,
|
|
1484
|
+
scaleMax,
|
|
1485
|
+
orientation,
|
|
1486
|
+
flat: true,
|
|
1487
|
+
barRadius: 2,
|
|
1488
|
+
cornerStyle: "all",
|
|
1489
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
1490
|
+
label,
|
|
1491
|
+
hideBaseline: true,
|
|
1492
|
+
testID: "velocity-strip-compact",
|
|
1493
|
+
testIDPrefix: "velocity",
|
|
952
1494
|
accessibilityLabel: miniLabel,
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
956
|
-
reactNative.View,
|
|
957
|
-
{
|
|
958
|
-
style: {
|
|
959
|
-
flex: 1,
|
|
960
|
-
backgroundColor: slotColor(slot),
|
|
961
|
-
borderRadius: 1,
|
|
962
|
-
minWidth: 4,
|
|
963
|
-
height: "100%",
|
|
964
|
-
// The container's uniform 2px gap covers rep spacing; a wide slot adds
|
|
965
|
-
// only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
|
|
966
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
967
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
968
|
-
},
|
|
969
|
-
accessibilityElementsHidden: true,
|
|
970
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
971
|
-
},
|
|
972
|
-
i
|
|
973
|
-
))
|
|
1495
|
+
className,
|
|
1496
|
+
viewProps: props
|
|
974
1497
|
}
|
|
975
1498
|
);
|
|
976
1499
|
}
|
|
977
1500
|
if (!framed) {
|
|
978
|
-
const
|
|
1501
|
+
const spotlightSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
1502
|
+
kind: s.kind,
|
|
1503
|
+
value: s.velocity,
|
|
1504
|
+
leadingGap: s.leadingGap
|
|
1505
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
979
1506
|
return /* @__PURE__ */ jsx(
|
|
980
|
-
|
|
1507
|
+
SetBarChart,
|
|
981
1508
|
{
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1509
|
+
slots: spotlightSlots,
|
|
1510
|
+
colorFor: barColorFor,
|
|
1511
|
+
height,
|
|
1512
|
+
scale,
|
|
1513
|
+
scaleMax,
|
|
1514
|
+
orientation,
|
|
1515
|
+
liveRepIndex,
|
|
1516
|
+
isNewPeak,
|
|
1517
|
+
barRadius: 2,
|
|
1518
|
+
cornerStyle: "top",
|
|
1519
|
+
targetReps: set ? void 0 : targetReps,
|
|
1520
|
+
label,
|
|
1521
|
+
hideBaseline: true,
|
|
1522
|
+
testID: "velocity-strip-spotlight",
|
|
1523
|
+
testIDPrefix: "velocity",
|
|
988
1524
|
accessibilityLabel: miniLabel,
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
992
|
-
reactNative.View,
|
|
993
|
-
{
|
|
994
|
-
style: {
|
|
995
|
-
flex: 1,
|
|
996
|
-
minWidth: 4,
|
|
997
|
-
height: bareSlotHeight(slot, height, scaleDenom),
|
|
998
|
-
// Top-rounded to match the framed chart's bars (the "rounded tops from
|
|
999
|
-
// the numbers one"); square bottoms since bars sit on the baseline.
|
|
1000
|
-
borderTopLeftRadius: 2,
|
|
1001
|
-
borderTopRightRadius: 2,
|
|
1002
|
-
backgroundColor: slotColor(slot),
|
|
1003
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
1004
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
1005
|
-
},
|
|
1006
|
-
accessibilityElementsHidden: true,
|
|
1007
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
1008
|
-
},
|
|
1009
|
-
i
|
|
1010
|
-
))
|
|
1525
|
+
className,
|
|
1526
|
+
viewProps: props
|
|
1011
1527
|
}
|
|
1012
1528
|
);
|
|
1013
1529
|
}
|
|
1014
1530
|
const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
|
|
1015
1531
|
const hasInteractiveContainer = onToggle != null;
|
|
1016
1532
|
const hasInteractiveReps = onRepPress != null && expanded;
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1533
|
+
const framedSlots = set ? buildSlots(set).map((sl) => ({
|
|
1534
|
+
kind: sl.kind,
|
|
1535
|
+
value: sl.velocity,
|
|
1536
|
+
leadingGap: sl.leadingGap
|
|
1537
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v }));
|
|
1538
|
+
const needsBarOverlay = showNumbers || onRepPress != null;
|
|
1539
|
+
const renderFramedBarOverlay = needsBarOverlay ? (repIndex, value) => /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
1540
|
+
showNumbers && /* @__PURE__ */ jsx(
|
|
1541
|
+
reactNative.Animated.View,
|
|
1022
1542
|
{
|
|
1023
1543
|
style: {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1544
|
+
opacity: expandProgress,
|
|
1545
|
+
position: "absolute",
|
|
1546
|
+
top: -13,
|
|
1547
|
+
left: 0,
|
|
1548
|
+
right: 0,
|
|
1549
|
+
alignItems: "center"
|
|
1028
1550
|
},
|
|
1551
|
+
accessibilityElementsHidden: true,
|
|
1552
|
+
pointerEvents: "none",
|
|
1029
1553
|
children: /* @__PURE__ */ jsx(
|
|
1030
|
-
reactNative.
|
|
1554
|
+
reactNative.Text,
|
|
1031
1555
|
{
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
borderTopRightRadius: 2,
|
|
1037
|
-
backgroundColor: slotColor(slot),
|
|
1038
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
1039
|
-
},
|
|
1040
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
|
|
1041
|
-
accessibilityRole: "image",
|
|
1042
|
-
accessibilityLabel: slotAccessibilityLabel(slot, i)
|
|
1556
|
+
className: "text-text-secondary",
|
|
1557
|
+
style: { fontSize: 8, fontWeight: "600" },
|
|
1558
|
+
testID: `velocity-label-${repIndex}`,
|
|
1559
|
+
children: formatVelocity(value)
|
|
1043
1560
|
}
|
|
1044
1561
|
)
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1562
|
+
}
|
|
1563
|
+
),
|
|
1564
|
+
onRepPress && expanded && /* @__PURE__ */ jsx(
|
|
1565
|
+
reactNative.Pressable,
|
|
1566
|
+
{
|
|
1567
|
+
style: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0 },
|
|
1568
|
+
onPress: () => onRepPress(repIndex, value),
|
|
1569
|
+
accessibilityRole: "button",
|
|
1570
|
+
accessibilityLabel: `Rep ${repIndex + 1}: ${formatVelocity(value)} meters per second, tap for details`,
|
|
1571
|
+
testID: `velocity-bar-pressable-${repIndex}`
|
|
1572
|
+
}
|
|
1573
|
+
)
|
|
1574
|
+
] }) : void 0;
|
|
1049
1575
|
const stripContent = /* @__PURE__ */ jsxs(
|
|
1050
1576
|
reactNative.Animated.View,
|
|
1051
1577
|
{
|
|
1052
|
-
className: [className,
|
|
1053
|
-
style:
|
|
1054
|
-
{
|
|
1055
|
-
height: heightAnim,
|
|
1056
|
-
width: "100%",
|
|
1057
|
-
gap: 2,
|
|
1058
|
-
borderRadius: expanded ? 6 : 2,
|
|
1059
|
-
paddingTop: expanded ? 16 : 0,
|
|
1060
|
-
paddingBottom: expanded ? 24 : 0,
|
|
1061
|
-
// No horizontal inset — the framed chart's bars span full width, matching
|
|
1062
|
-
// the bare spotlight and mini. Numbers/info sit in the vertical padding.
|
|
1063
|
-
paddingHorizontal: 0,
|
|
1064
|
-
paddingVertical: expanded ? void 0 : 8,
|
|
1065
|
-
overflow: expanded ? "visible" : "hidden"
|
|
1066
|
-
}
|
|
1067
|
-
],
|
|
1578
|
+
className: [className, "bg-surface-raised"].filter(Boolean).join(" "),
|
|
1579
|
+
style: { width: "100%", borderRadius: 6, paddingTop: 16, paddingBottom: showInfo ? 8 : 4 },
|
|
1068
1580
|
accessibilityRole: hasInteractiveContainer || hasInteractiveReps ? "none" : "button",
|
|
1069
1581
|
accessibilityLabel: hasInteractiveContainer || hasInteractiveReps ? void 0 : stripLabel,
|
|
1070
1582
|
testID: "velocity-strip",
|
|
1071
1583
|
...props,
|
|
1072
1584
|
children: [
|
|
1073
|
-
/* @__PURE__ */ jsx(
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
borderBottomLeftRadius: 0,
|
|
1089
|
-
borderBottomRightRadius: 0,
|
|
1090
|
-
backgroundColor: barBackground
|
|
1091
|
-
};
|
|
1092
|
-
const barInner = isLive ? /* @__PURE__ */ jsx(
|
|
1093
|
-
reactNative.Animated.View,
|
|
1094
|
-
{
|
|
1095
|
-
style: [barStyle, { transform: [{ scale: liveScale }] }],
|
|
1096
|
-
testID: `velocity-bar-${i}`,
|
|
1097
|
-
accessibilityRole: "image",
|
|
1098
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
|
|
1099
|
-
}
|
|
1100
|
-
) : /* @__PURE__ */ jsx(
|
|
1101
|
-
reactNative.View,
|
|
1102
|
-
{
|
|
1103
|
-
style: barStyle,
|
|
1104
|
-
testID: `velocity-bar-${i}`,
|
|
1105
|
-
accessibilityRole: "image",
|
|
1106
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
1107
|
-
}
|
|
1108
|
-
);
|
|
1109
|
-
const bar = /* @__PURE__ */ jsxs(
|
|
1110
|
-
reactNative.View,
|
|
1111
|
-
{
|
|
1112
|
-
style: {
|
|
1113
|
-
flex: 1,
|
|
1114
|
-
height: "100%",
|
|
1115
|
-
justifyContent: "flex-end",
|
|
1116
|
-
position: "relative"
|
|
1117
|
-
},
|
|
1118
|
-
children: [
|
|
1119
|
-
expanded && showNumbers && /* @__PURE__ */ jsx(
|
|
1120
|
-
reactNative.Animated.View,
|
|
1121
|
-
{
|
|
1122
|
-
style: {
|
|
1123
|
-
opacity: labelOpacity,
|
|
1124
|
-
alignItems: "center",
|
|
1125
|
-
position: "absolute",
|
|
1126
|
-
top: -13,
|
|
1127
|
-
left: 0,
|
|
1128
|
-
right: 0
|
|
1129
|
-
},
|
|
1130
|
-
accessibilityElementsHidden: true,
|
|
1131
|
-
children: /* @__PURE__ */ jsx(
|
|
1132
|
-
reactNative.Text,
|
|
1133
|
-
{
|
|
1134
|
-
className: "text-text-secondary",
|
|
1135
|
-
style: { fontSize: 8, fontWeight: "600" },
|
|
1136
|
-
testID: `velocity-label-${i}`,
|
|
1137
|
-
children: formatVelocity(v)
|
|
1138
|
-
}
|
|
1139
|
-
)
|
|
1140
|
-
}
|
|
1141
|
-
),
|
|
1142
|
-
barInner
|
|
1143
|
-
]
|
|
1144
|
-
},
|
|
1145
|
-
i
|
|
1146
|
-
);
|
|
1147
|
-
if (onRepPress && expanded) {
|
|
1148
|
-
return /* @__PURE__ */ jsx(
|
|
1149
|
-
reactNative.Pressable,
|
|
1150
|
-
{
|
|
1151
|
-
style: { flex: 1, height: "100%" },
|
|
1152
|
-
onPress: () => onRepPress(i, v),
|
|
1153
|
-
accessibilityRole: "button",
|
|
1154
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
|
|
1155
|
-
testID: `velocity-bar-pressable-${i}`,
|
|
1156
|
-
children: bar
|
|
1157
|
-
},
|
|
1158
|
-
i
|
|
1159
|
-
);
|
|
1585
|
+
/* @__PURE__ */ jsx(
|
|
1586
|
+
SetBarChart,
|
|
1587
|
+
{
|
|
1588
|
+
slots: framedSlots,
|
|
1589
|
+
colorFor: barColorFor,
|
|
1590
|
+
height,
|
|
1591
|
+
scale,
|
|
1592
|
+
scaleMax,
|
|
1593
|
+
expandProgress,
|
|
1594
|
+
renderBarOverlay: renderFramedBarOverlay,
|
|
1595
|
+
targetReps: set ? void 0 : targetReps,
|
|
1596
|
+
barRadius: 2,
|
|
1597
|
+
cornerStyle: "top",
|
|
1598
|
+
hideBaseline: true,
|
|
1599
|
+
testIDPrefix: "velocity"
|
|
1160
1600
|
}
|
|
1161
|
-
|
|
1162
|
-
}) }),
|
|
1601
|
+
),
|
|
1163
1602
|
expanded && showInfo && /* @__PURE__ */ jsxs(
|
|
1164
1603
|
reactNative.Animated.View,
|
|
1165
1604
|
{
|
|
@@ -1167,10 +1606,8 @@ function VelocityStrip({
|
|
|
1167
1606
|
flexDirection: "row",
|
|
1168
1607
|
justifyContent: "space-between",
|
|
1169
1608
|
opacity: infoOpacity,
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
left: 6,
|
|
1173
|
-
right: 6
|
|
1609
|
+
marginTop: 6,
|
|
1610
|
+
paddingHorizontal: 6
|
|
1174
1611
|
},
|
|
1175
1612
|
testID: "velocity-info-row",
|
|
1176
1613
|
children: [
|
|
@@ -1178,10 +1615,7 @@ function VelocityStrip({
|
|
|
1178
1615
|
reactNative.Text,
|
|
1179
1616
|
{
|
|
1180
1617
|
className: "text-text-secondary",
|
|
1181
|
-
style: {
|
|
1182
|
-
fontSize: 10,
|
|
1183
|
-
fontFamily: "Inter, sans-serif"
|
|
1184
|
-
},
|
|
1618
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif" },
|
|
1185
1619
|
children: [
|
|
1186
1620
|
meanZone,
|
|
1187
1621
|
" ",
|
|
@@ -1196,11 +1630,7 @@ function VelocityStrip({
|
|
|
1196
1630
|
reactNative.Text,
|
|
1197
1631
|
{
|
|
1198
1632
|
className: "text-text-secondary",
|
|
1199
|
-
style: {
|
|
1200
|
-
fontSize: 10,
|
|
1201
|
-
fontFamily: "Inter, sans-serif",
|
|
1202
|
-
...getLossStyle(loss)
|
|
1203
|
-
},
|
|
1633
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif", ...getLossStyle(loss) },
|
|
1204
1634
|
children: [
|
|
1205
1635
|
"Loss: ",
|
|
1206
1636
|
loss,
|
|
@@ -1262,434 +1692,123 @@ function SegmentedStrip({
|
|
|
1262
1692
|
segments = 3,
|
|
1263
1693
|
width,
|
|
1264
1694
|
className,
|
|
1265
|
-
...props
|
|
1266
|
-
}) {
|
|
1267
|
-
return /* @__PURE__ */ jsx(
|
|
1268
|
-
reactNative.View,
|
|
1269
|
-
{
|
|
1270
|
-
className: cn("flex-row", className),
|
|
1271
|
-
style: [
|
|
1272
|
-
{ height: 3, gap: 2, opacity: 0.5 },
|
|
1273
|
-
width != null ? { width } : { flex: 1 }
|
|
1274
|
-
],
|
|
1275
|
-
accessibilityRole: "image",
|
|
1276
|
-
accessibilityLabel: "Planned set, not yet completed",
|
|
1277
|
-
testID: "placeholder-strip",
|
|
1278
|
-
...props,
|
|
1279
|
-
children: Array.from({ length: segments }, (_, i) => /* @__PURE__ */ jsx(
|
|
1280
|
-
reactNative.View,
|
|
1281
|
-
{
|
|
1282
|
-
style: {
|
|
1283
|
-
flex: 1,
|
|
1284
|
-
height: 3,
|
|
1285
|
-
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
1286
|
-
borderRadius: 1,
|
|
1287
|
-
minWidth: 4
|
|
1288
|
-
},
|
|
1289
|
-
accessibilityElementsHidden: true,
|
|
1290
|
-
testID: "placeholder-segment"
|
|
1291
|
-
},
|
|
1292
|
-
i
|
|
1293
|
-
))
|
|
1294
|
-
}
|
|
1295
|
-
);
|
|
1296
|
-
}
|
|
1297
|
-
function PlaceholderStrip({
|
|
1298
|
-
mode = "single",
|
|
1299
|
-
segments,
|
|
1300
|
-
...props
|
|
1301
|
-
}) {
|
|
1302
|
-
if (mode === "segmented") {
|
|
1303
|
-
return /* @__PURE__ */ jsx(SegmentedStrip, { segments, ...props });
|
|
1304
|
-
}
|
|
1305
|
-
return /* @__PURE__ */ jsx(SingleStrip, { ...props });
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
// src/components/icons/SvgIcon.tsx
|
|
1309
|
-
function SvgIcon({
|
|
1310
|
-
size = 24,
|
|
1311
|
-
color = "currentColor",
|
|
1312
|
-
strokeWidth = 2,
|
|
1313
|
-
fill = "none",
|
|
1314
|
-
title,
|
|
1315
|
-
children
|
|
1316
|
-
}) {
|
|
1317
|
-
return /* @__PURE__ */ jsxs(
|
|
1318
|
-
"svg",
|
|
1319
|
-
{
|
|
1320
|
-
width: size,
|
|
1321
|
-
height: size,
|
|
1322
|
-
viewBox: "0 0 24 24",
|
|
1323
|
-
fill,
|
|
1324
|
-
stroke: color,
|
|
1325
|
-
strokeWidth,
|
|
1326
|
-
strokeLinecap: "round",
|
|
1327
|
-
strokeLinejoin: "round",
|
|
1328
|
-
...title ? { role: "img", "aria-label": title } : { "aria-hidden": true },
|
|
1329
|
-
children: [
|
|
1330
|
-
title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
|
|
1331
|
-
children
|
|
1332
|
-
]
|
|
1333
|
-
}
|
|
1334
|
-
);
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// src/components/icons/icons.tsx
|
|
1338
|
-
function StarIcon(props) {
|
|
1339
|
-
return /* @__PURE__ */ jsx(SvgIcon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" }) });
|
|
1340
|
-
}
|
|
1341
|
-
function ScaleIcon(props) {
|
|
1342
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1343
|
-
/* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1344
|
-
/* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1345
|
-
/* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
|
|
1346
|
-
/* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
|
|
1347
|
-
/* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
|
|
1348
|
-
] });
|
|
1349
|
-
}
|
|
1350
|
-
function AlertTriangleIcon(props) {
|
|
1351
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1352
|
-
/* @__PURE__ */ jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }),
|
|
1353
|
-
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
1354
|
-
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
1355
|
-
] });
|
|
1356
|
-
}
|
|
1357
|
-
function TrendingDownIcon(props) {
|
|
1358
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1359
|
-
/* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
|
|
1360
|
-
/* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
|
|
1361
|
-
] });
|
|
1362
|
-
}
|
|
1363
|
-
function CircleSlashIcon(props) {
|
|
1364
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1365
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1366
|
-
/* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
|
|
1367
|
-
] });
|
|
1368
|
-
}
|
|
1369
|
-
function AwardIcon(props) {
|
|
1370
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1371
|
-
/* @__PURE__ */ jsx("path", { d: "m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526" }),
|
|
1372
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
|
|
1373
|
-
] });
|
|
1374
|
-
}
|
|
1375
|
-
function InfoIcon(props) {
|
|
1376
|
-
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1377
|
-
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1378
|
-
/* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
|
|
1379
|
-
/* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
|
|
1380
|
-
] });
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// src/theme/tokens/semantic.ts
|
|
1384
|
-
var semanticColorsLight = {
|
|
1385
|
-
// Brand colors (brand-*)
|
|
1386
|
-
"brand-primary": primitiveRamps.orange[400],
|
|
1387
|
-
"brand-primary-light": primitiveRamps.orange[300],
|
|
1388
|
-
"brand-primary-dark": primitiveRamps.orange[500],
|
|
1389
|
-
"brand-primary-subtle": "rgba(255, 121, 0, 0.08)",
|
|
1390
|
-
"brand-primary-hover": primitiveRamps.orange[500],
|
|
1391
|
-
"brand-primary-active": primitiveRamps.orange[600],
|
|
1392
|
-
"brand-secondary": primitiveRamps.cyan[600],
|
|
1393
|
-
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
1394
|
-
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
1395
|
-
"brand-secondary-subtle": "rgba(48, 123, 155, 0.08)",
|
|
1396
|
-
"brand-secondary-hover": primitiveRamps.cyan[700],
|
|
1397
|
-
"brand-secondary-active": primitiveRamps.cyan[800],
|
|
1398
|
-
// Text on brand backgrounds (on-*)
|
|
1399
|
-
"on-brand-primary": primitiveColors.white,
|
|
1400
|
-
"on-brand-secondary": primitiveColors.white,
|
|
1401
|
-
// Status colors (status-*)
|
|
1402
|
-
"status-success": primitiveRamps.green[300],
|
|
1403
|
-
"status-success-light": primitiveRamps.green[200],
|
|
1404
|
-
"status-success-dark": primitiveRamps.green[600],
|
|
1405
|
-
"status-success-subtle": primitiveRamps.green[50],
|
|
1406
|
-
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1407
|
-
"status-live": primitiveRamps.green[300],
|
|
1408
|
-
"status-live-muted": primitiveRamps.green[500],
|
|
1409
|
-
"status-error": primitiveRamps.red[600],
|
|
1410
|
-
"status-error-light": primitiveRamps.red[500],
|
|
1411
|
-
"status-error-dark": primitiveRamps.red[700],
|
|
1412
|
-
"status-error-subtle": primitiveRamps.red[50],
|
|
1413
|
-
"status-error-vivid": primitiveColors.redVivid[500],
|
|
1414
|
-
// vivid alert red
|
|
1415
|
-
"status-error-vivid-light": primitiveColors.redVivid[400],
|
|
1416
|
-
"status-error-vivid-dark": primitiveColors.redVivid[700],
|
|
1417
|
-
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
1418
|
-
"status-warning": primitiveRamps.amber[300],
|
|
1419
|
-
"status-warning-light": primitiveRamps.amber[200],
|
|
1420
|
-
"status-warning-dark": primitiveRamps.amber[500],
|
|
1421
|
-
"status-warning-subtle": primitiveRamps.amber[50],
|
|
1422
|
-
"status-info": primitiveRamps.blue[500],
|
|
1423
|
-
"status-info-light": primitiveRamps.blue[300],
|
|
1424
|
-
"status-info-dark": primitiveRamps.blue[600],
|
|
1425
|
-
"status-info-subtle": primitiveRamps.blue[50],
|
|
1426
|
-
// Text on status backgrounds (on-status-*)
|
|
1427
|
-
"on-status-success": primitiveColors.white,
|
|
1428
|
-
"on-status-error": primitiveColors.white,
|
|
1429
|
-
"on-status-warning": primitiveColors.white,
|
|
1430
|
-
"on-status-info": primitiveColors.white,
|
|
1431
|
-
// Result/outcome indicators (result-*)
|
|
1432
|
-
"result-improve": "#4caf50",
|
|
1433
|
-
// Green - positive outcome
|
|
1434
|
-
"result-improve-light": "rgba(76, 175, 80, 0.12)",
|
|
1435
|
-
"result-improve-dark": "#248a24",
|
|
1436
|
-
"result-degrade": "#ef5350",
|
|
1437
|
-
// Red - negative outcome
|
|
1438
|
-
"result-degrade-light": "rgba(239, 83, 80, 0.12)",
|
|
1439
|
-
"result-degrade-dark": "#b30000",
|
|
1440
|
-
"result-inconclusive": "#9E9A97",
|
|
1441
|
-
// Gray - no clear result
|
|
1442
|
-
"result-inconclusive-light": "rgba(158, 154, 151, 0.12)",
|
|
1443
|
-
"result-neutral": primitiveColors.neutral[500],
|
|
1444
|
-
// Neutral baseline
|
|
1445
|
-
// Text on result backgrounds (on-result-*)
|
|
1446
|
-
"on-result-improve": primitiveColors.white,
|
|
1447
|
-
"on-result-degrade": primitiveColors.white,
|
|
1448
|
-
"on-result-inconclusive": primitiveColors.white,
|
|
1449
|
-
// Data visualization colors (data-*)
|
|
1450
|
-
// First 10 colors from discrete rainbow optimized for charts
|
|
1451
|
-
"data-1": discreteRainbow[9],
|
|
1452
|
-
// Blue
|
|
1453
|
-
"data-2": discreteRainbow[14],
|
|
1454
|
-
// Green
|
|
1455
|
-
"data-3": discreteRainbow[17],
|
|
1456
|
-
// Yellow
|
|
1457
|
-
"data-4": discreteRainbow[25],
|
|
1458
|
-
// Red
|
|
1459
|
-
"data-5": discreteRainbow[8],
|
|
1460
|
-
// Purple
|
|
1461
|
-
"data-6": discreteRainbow[20],
|
|
1462
|
-
// Orange
|
|
1463
|
-
"data-7": discreteRainbow[13],
|
|
1464
|
-
// Light Blue
|
|
1465
|
-
"data-8": discreteRainbow[15],
|
|
1466
|
-
// Light Green
|
|
1467
|
-
"data-9": discreteRainbow[3],
|
|
1468
|
-
// Lavender
|
|
1469
|
-
"data-10": discreteRainbow[22],
|
|
1470
|
-
// Dark Orange
|
|
1471
|
-
// Text colors (text-*)
|
|
1472
|
-
"text-primary": "#121828",
|
|
1473
|
-
"text-secondary": "#65748B",
|
|
1474
|
-
"text-tertiary": primitiveColors.neutral[400],
|
|
1475
|
-
"text-disabled": "rgba(55, 65, 81, 0.48)",
|
|
1476
|
-
"text-inverse": primitiveColors.white,
|
|
1477
|
-
"text-link": primitiveColors.blue[600],
|
|
1478
|
-
"text-link-hover": primitiveColors.blue[700],
|
|
1479
|
-
// Surface colors (surface-*) - for elevated containers
|
|
1480
|
-
"surface-base": primitiveColors.white,
|
|
1481
|
-
"surface-elevated": primitiveColors.neutral[50],
|
|
1482
|
-
// slightly off-white for elevated cards
|
|
1483
|
-
"surface-raised": primitiveColors.neutral[100],
|
|
1484
|
-
// light gray for raised cards
|
|
1485
|
-
"surface-overlay": primitiveColors.white,
|
|
1486
|
-
"surface-input": primitiveColors.neutral[50],
|
|
1487
|
-
// Input field background (filled variant)
|
|
1488
|
-
// Deepest pressed pit (TD-surface-tokens, S-3) — light-mode counterpart of
|
|
1489
|
-
// the dark ramp's `inset` plane; a placeholder pairing, not part of the
|
|
1490
|
-
// dark-ramp investigation (surface.contract.test.ts is dark-mode only).
|
|
1491
|
-
"surface-inset": primitiveColors.neutral[300],
|
|
1492
|
-
// Background colors (background-*)
|
|
1493
|
-
"background-base": "#EBEBEB",
|
|
1494
|
-
"background-default": primitiveColors.white,
|
|
1495
|
-
"background-subtle": primitiveColors.neutral[50],
|
|
1496
|
-
// Frame/bezel chrome (TD-surface-tokens, S-3) — top bar + side nav shell,
|
|
1497
|
-
// one step below `background-base` (darker still than `surface-inset`
|
|
1498
|
-
// above, mirroring the dark-mode ordering). Placeholder pairing for light
|
|
1499
|
-
// mode (see dark-mode note on `surface-inset` above).
|
|
1500
|
-
"background-frame": primitiveColors.neutral[400],
|
|
1501
|
-
// Border colors (border-*)
|
|
1502
|
-
"border-default": "#E8E9EB",
|
|
1503
|
-
"border-subtle": primitiveColors.neutral[100],
|
|
1504
|
-
"border-strong": primitiveColors.neutral[300],
|
|
1505
|
-
"border-prominent": primitiveColors.neutral[400],
|
|
1506
|
-
// high-visibility divider
|
|
1507
|
-
"border-focus": primitiveColors.blue[600],
|
|
1508
|
-
"border-input": primitiveColors.neutral[300],
|
|
1509
|
-
// Input field border
|
|
1510
|
-
"border-input-hover": primitiveColors.neutral[400],
|
|
1511
|
-
// Input field border on hover
|
|
1512
|
-
"border-input-focus": primitiveColors.blue[600],
|
|
1513
|
-
// Input field border on focus
|
|
1514
|
-
"border-input-error": primitiveColors.red[600],
|
|
1515
|
-
// Input field border on error
|
|
1516
|
-
// Alpha hairline separators (surface-independent — composite toward black on
|
|
1517
|
-
// light surfaces, mirroring the dark-mode white-alpha family). See §4/S-2.
|
|
1518
|
-
"hairline-subtle": "rgba(0, 0, 0, 0.06)",
|
|
1519
|
-
"hairline-default": "rgba(0, 0, 0, 0.09)",
|
|
1520
|
-
"hairline-strong": "rgba(0, 0, 0, 0.14)",
|
|
1521
|
-
// Interactive states (interactive-*)
|
|
1522
|
-
"interactive-hover": "rgba(55, 65, 81, 0.04)",
|
|
1523
|
-
"interactive-focus": "rgba(55, 65, 81, 0.12)",
|
|
1524
|
-
"interactive-active": "rgba(55, 65, 81, 0.16)",
|
|
1525
|
-
"interactive-selected": "rgba(55, 65, 81, 0.08)",
|
|
1526
|
-
"interactive-disabled": "rgba(55, 65, 81, 0.12)",
|
|
1527
|
-
"interactive-disabled-text": "rgba(55, 65, 81, 0.26)",
|
|
1528
|
-
// Divider
|
|
1529
|
-
"divider": "#E8E9EB",
|
|
1530
|
-
// Avatar default
|
|
1531
|
-
"avatar-background": primitiveColors.neutral[500],
|
|
1532
|
-
"avatar-text": primitiveColors.white
|
|
1533
|
-
};
|
|
1534
|
-
var semanticColorsDark = {
|
|
1535
|
-
// Brand colors stay the same in dark mode
|
|
1536
|
-
"brand-primary": primitiveRamps.orange[400],
|
|
1537
|
-
"brand-primary-light": primitiveRamps.orange[300],
|
|
1538
|
-
"brand-primary-dark": primitiveRamps.orange[500],
|
|
1539
|
-
"brand-primary-subtle": "rgba(255, 121, 0, 0.12)",
|
|
1540
|
-
"brand-primary-hover": primitiveRamps.orange[300],
|
|
1541
|
-
"brand-primary-active": primitiveRamps.orange[200],
|
|
1542
|
-
"brand-secondary": primitiveRamps.cyan[600],
|
|
1543
|
-
"brand-secondary-light": primitiveRamps.cyan[500],
|
|
1544
|
-
"brand-secondary-dark": primitiveRamps.cyan[700],
|
|
1545
|
-
"brand-secondary-subtle": "rgba(48, 123, 155, 0.12)",
|
|
1546
|
-
"brand-secondary-hover": primitiveRamps.cyan[500],
|
|
1547
|
-
"brand-secondary-active": primitiveRamps.cyan[400],
|
|
1548
|
-
// Text on brand backgrounds
|
|
1549
|
-
"on-brand-primary": primitiveColors.white,
|
|
1550
|
-
"on-brand-secondary": primitiveColors.white,
|
|
1551
|
-
// Status colors
|
|
1552
|
-
"status-success": primitiveRamps.green[300],
|
|
1553
|
-
"status-success-light": primitiveRamps.green[200],
|
|
1554
|
-
"status-success-dark": primitiveRamps.green[600],
|
|
1555
|
-
"status-success-subtle": "rgba(46, 213, 115, 0.12)",
|
|
1556
|
-
// Live-session accent — its OWN role, decoupled from success so the two can diverge
|
|
1557
|
-
"status-live": primitiveRamps.green[300],
|
|
1558
|
-
"status-live-muted": primitiveRamps.green[500],
|
|
1559
|
-
"status-error": primitiveRamps.red[600],
|
|
1560
|
-
"status-error-light": primitiveRamps.red[500],
|
|
1561
|
-
"status-error-dark": primitiveRamps.red[700],
|
|
1562
|
-
"status-error-subtle": "rgba(209, 67, 67, 0.12)",
|
|
1563
|
-
"status-error-vivid": primitiveColors.redVivid[500],
|
|
1564
|
-
"status-error-vivid-light": primitiveColors.redVivid[400],
|
|
1565
|
-
"status-error-vivid-dark": primitiveColors.redVivid[700],
|
|
1566
|
-
"status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
|
|
1567
|
-
"status-warning": primitiveRamps.amber[300],
|
|
1568
|
-
"status-warning-light": primitiveRamps.amber[200],
|
|
1569
|
-
"status-warning-dark": primitiveRamps.amber[500],
|
|
1570
|
-
"status-warning-subtle": "rgba(249, 180, 21, 0.12)",
|
|
1571
|
-
"status-info": primitiveRamps.blue[500],
|
|
1572
|
-
"status-info-light": primitiveRamps.blue[300],
|
|
1573
|
-
"status-info-dark": primitiveRamps.blue[600],
|
|
1574
|
-
"status-info-subtle": "rgba(33, 150, 243, 0.12)",
|
|
1575
|
-
// Text on status backgrounds
|
|
1576
|
-
"on-status-success": primitiveColors.white,
|
|
1577
|
-
"on-status-error": primitiveColors.white,
|
|
1578
|
-
"on-status-warning": primitiveColors.white,
|
|
1579
|
-
"on-status-info": primitiveColors.white,
|
|
1580
|
-
// Result/outcome indicators (result-*)
|
|
1581
|
-
"result-improve": "#4caf50",
|
|
1582
|
-
"result-improve-light": "rgba(76, 175, 80, 0.16)",
|
|
1583
|
-
"result-improve-dark": "#248a24",
|
|
1584
|
-
"result-degrade": "#ef5350",
|
|
1585
|
-
"result-degrade-light": "rgba(239, 83, 80, 0.16)",
|
|
1586
|
-
"result-degrade-dark": "#b30000",
|
|
1587
|
-
"result-inconclusive": "#9E9A97",
|
|
1588
|
-
"result-inconclusive-light": "rgba(158, 154, 151, 0.16)",
|
|
1589
|
-
"result-neutral": primitiveColors.neutral[400],
|
|
1590
|
-
// Text on result backgrounds
|
|
1591
|
-
"on-result-improve": primitiveColors.white,
|
|
1592
|
-
"on-result-degrade": primitiveColors.white,
|
|
1593
|
-
"on-result-inconclusive": primitiveColors.white,
|
|
1594
|
-
// Data visualization colors (same in dark mode for consistency)
|
|
1595
|
-
"data-1": discreteRainbow[9],
|
|
1596
|
-
"data-2": discreteRainbow[14],
|
|
1597
|
-
"data-3": discreteRainbow[17],
|
|
1598
|
-
"data-4": discreteRainbow[25],
|
|
1599
|
-
"data-5": discreteRainbow[8],
|
|
1600
|
-
"data-6": discreteRainbow[20],
|
|
1601
|
-
"data-7": discreteRainbow[13],
|
|
1602
|
-
"data-8": discreteRainbow[15],
|
|
1603
|
-
"data-9": discreteRainbow[3],
|
|
1604
|
-
"data-10": discreteRainbow[22],
|
|
1605
|
-
// Text colors - inverted for dark mode
|
|
1606
|
-
"text-primary": primitiveColors.neutral[100],
|
|
1607
|
-
"text-secondary": primitiveColors.neutral[400],
|
|
1608
|
-
"text-tertiary": primitiveColors.neutral[500],
|
|
1609
|
-
"text-disabled": "rgba(255, 255, 255, 0.38)",
|
|
1610
|
-
"text-inverse": primitiveColors.neutral[900],
|
|
1611
|
-
"text-link": "#828DF8",
|
|
1612
|
-
"text-link-hover": primitiveColors.blue[400],
|
|
1613
|
-
// Surface colors - dark backgrounds — warm-tapered DERIVED ramp (TD-surface-tokens,
|
|
1614
|
-
// S-1, re-spaced S-3). Shipped verbatim from `deriveSurfaceRamp()` — see
|
|
1615
|
-
// `surfaceRampDark` in primitives.ts for the full derivation note. `surface-overlay`
|
|
1616
|
-
// and `surface-elevated` are distinct (were both #191919 pre-S-1); `surface-input`
|
|
1617
|
-
// tracks one plane above `surface-base`, same relative position as before the remap.
|
|
1618
|
-
// `background-base` backs `Surface level="background"` (SurfaceContext.SURFACE_LEVEL_TOKEN),
|
|
1619
|
-
// so it takes the ramp's `background` (shell/frame) role, NOT `inset`. The ramp's
|
|
1620
|
-
// deepest `inset` plane is now promoted to `surface-inset` below (S-3) — still no
|
|
1621
|
-
// `SurfaceLevel` member for it (follow-up, alongside the elevation.ts -1/-2 pressed
|
|
1622
|
-
// levels — parallel workstream, not this branch).
|
|
1623
|
-
"surface-base": surfaceRampDark.base,
|
|
1624
|
-
// main surface (#252321, L*13.5)
|
|
1625
|
-
"surface-elevated": surfaceRampDark.elevated,
|
|
1626
|
-
// elevated surface (#2C2A28, L*17 — nav/rail)
|
|
1627
|
-
"surface-raised": surfaceRampDark.raised,
|
|
1628
|
-
// raised surface (#31302F, L*20 — cards)
|
|
1629
|
-
"surface-overlay": surfaceRampDark.overlay,
|
|
1630
|
-
// overlay surface (#373635, L*22.5 — hero/popover)
|
|
1631
|
-
"surface-input": surfaceRampDark.elevated,
|
|
1632
|
-
// input surface (#2C2A28 — one plane above base)
|
|
1633
|
-
"surface-inset": surfaceRampDark.inset,
|
|
1634
|
-
// deepest pressed pit (#13100D, L*4.5 — sub-shell well)
|
|
1635
|
-
// Background colors — same ramp, the frame/shell end of it.
|
|
1636
|
-
"background-base": surfaceRampDark.background,
|
|
1637
|
-
// shell / frame (#1C1916, L*9 — Surface level="background")
|
|
1638
|
-
"background-default": surfaceRampDark.base,
|
|
1639
|
-
// main background (#252321, L*13.5 — matches surface-base)
|
|
1640
|
-
"background-subtle": surfaceRampDark.elevated,
|
|
1641
|
-
// subtle background (#2C2A28, L*17 — matches surface-elevated)
|
|
1642
|
-
// Frame/bezel chrome (S-3) — the top bar + side nav shell; one step BELOW
|
|
1643
|
-
// `background-base`, darker even than `surface-inset` — it exits the content
|
|
1644
|
-
// ramp entirely rather than sitting in it. See `backgroundFrameDark` in
|
|
1645
|
-
// primitives.ts.
|
|
1646
|
-
"background-frame": backgroundFrameDark,
|
|
1647
|
-
// frame / bezel (#100D0A, L*3.79)
|
|
1648
|
-
// Border colors — `border-subtle` (#1C1C1C) previously collided with
|
|
1649
|
-
// `surface-raised` (also #1C1C1C, an invisible border on raised cards). The
|
|
1650
|
-
// re-spaced ramp above moved `surface-raised` to #2D2C2B, so this value is now
|
|
1651
|
-
// distinct from every surface hex without needing to change it — see
|
|
1652
|
-
// `surface.contract.test.ts` R2/R4.
|
|
1653
|
-
"border-default": primitiveColors.charcoal[400],
|
|
1654
|
-
// default border
|
|
1655
|
-
"border-subtle": primitiveColors.charcoal[500],
|
|
1656
|
-
// subtle border
|
|
1657
|
-
"border-strong": primitiveColors.charcoal[300],
|
|
1658
|
-
// strong border
|
|
1659
|
-
"border-prominent": primitiveColors.charcoal[200],
|
|
1660
|
-
// high-visibility divider
|
|
1661
|
-
"border-focus": "#828DF8",
|
|
1662
|
-
"border-input": primitiveColors.neutral[600],
|
|
1663
|
-
"border-input-hover": primitiveColors.neutral[500],
|
|
1664
|
-
"border-input-focus": "#828DF8",
|
|
1665
|
-
"border-input-error": primitiveColors.red[500],
|
|
1666
|
-
// Alpha hairline separators — the primary separation cue (§4/S-2). Self-
|
|
1667
|
-
// normalizing: composites toward white by ~the same amount on ANY plane, so
|
|
1668
|
-
// one family works at every elevation instead of per-surface border tokens.
|
|
1669
|
-
// Shadows are demoted to floating-overlay use only (not shipped as a fill
|
|
1670
|
-
// separator here — see elevation.ts, follow-up S-4).
|
|
1671
|
-
"hairline-subtle": "rgba(255, 255, 255, 0.06)",
|
|
1672
|
-
"hairline-default": "rgba(255, 255, 255, 0.09)",
|
|
1673
|
-
"hairline-strong": "rgba(255, 255, 255, 0.14)",
|
|
1674
|
-
// Interactive states
|
|
1675
|
-
"interactive-hover": "rgba(255, 255, 255, 0.04)",
|
|
1676
|
-
"interactive-focus": "rgba(255, 255, 255, 0.12)",
|
|
1677
|
-
"interactive-active": "rgba(255, 255, 255, 0.16)",
|
|
1678
|
-
"interactive-selected": "rgba(255, 255, 255, 0.08)",
|
|
1679
|
-
"interactive-disabled": "rgba(255, 255, 255, 0.12)",
|
|
1680
|
-
"interactive-disabled-text": "rgba(255, 255, 255, 0.26)",
|
|
1681
|
-
// Divider
|
|
1682
|
-
"divider": primitiveColors.charcoal[400],
|
|
1683
|
-
// divider color
|
|
1684
|
-
// Avatar default
|
|
1685
|
-
"avatar-background": primitiveColors.neutral[600],
|
|
1686
|
-
"avatar-text": primitiveColors.white
|
|
1687
|
-
};
|
|
1688
|
-
function getSemanticColors(mode) {
|
|
1689
|
-
return mode === "dark" ? semanticColorsDark : semanticColorsLight;
|
|
1695
|
+
...props
|
|
1696
|
+
}) {
|
|
1697
|
+
return /* @__PURE__ */ jsx(
|
|
1698
|
+
reactNative.View,
|
|
1699
|
+
{
|
|
1700
|
+
className: cn("flex-row", className),
|
|
1701
|
+
style: [
|
|
1702
|
+
{ height: 3, gap: 2, opacity: 0.5 },
|
|
1703
|
+
width != null ? { width } : { flex: 1 }
|
|
1704
|
+
],
|
|
1705
|
+
accessibilityRole: "image",
|
|
1706
|
+
accessibilityLabel: "Planned set, not yet completed",
|
|
1707
|
+
testID: "placeholder-strip",
|
|
1708
|
+
...props,
|
|
1709
|
+
children: Array.from({ length: segments }, (_, i) => /* @__PURE__ */ jsx(
|
|
1710
|
+
reactNative.View,
|
|
1711
|
+
{
|
|
1712
|
+
style: {
|
|
1713
|
+
flex: 1,
|
|
1714
|
+
height: 3,
|
|
1715
|
+
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
1716
|
+
borderRadius: 1,
|
|
1717
|
+
minWidth: 4
|
|
1718
|
+
},
|
|
1719
|
+
accessibilityElementsHidden: true,
|
|
1720
|
+
testID: "placeholder-segment"
|
|
1721
|
+
},
|
|
1722
|
+
i
|
|
1723
|
+
))
|
|
1724
|
+
}
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
function PlaceholderStrip({
|
|
1728
|
+
mode = "single",
|
|
1729
|
+
segments,
|
|
1730
|
+
...props
|
|
1731
|
+
}) {
|
|
1732
|
+
if (mode === "segmented") {
|
|
1733
|
+
return /* @__PURE__ */ jsx(SegmentedStrip, { segments, ...props });
|
|
1734
|
+
}
|
|
1735
|
+
return /* @__PURE__ */ jsx(SingleStrip, { ...props });
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/components/icons/SvgIcon.tsx
|
|
1739
|
+
function SvgIcon({
|
|
1740
|
+
size = 24,
|
|
1741
|
+
color = "currentColor",
|
|
1742
|
+
strokeWidth = 2,
|
|
1743
|
+
fill = "none",
|
|
1744
|
+
title,
|
|
1745
|
+
children
|
|
1746
|
+
}) {
|
|
1747
|
+
return /* @__PURE__ */ jsxs(
|
|
1748
|
+
"svg",
|
|
1749
|
+
{
|
|
1750
|
+
width: size,
|
|
1751
|
+
height: size,
|
|
1752
|
+
viewBox: "0 0 24 24",
|
|
1753
|
+
fill,
|
|
1754
|
+
stroke: color,
|
|
1755
|
+
strokeWidth,
|
|
1756
|
+
strokeLinecap: "round",
|
|
1757
|
+
strokeLinejoin: "round",
|
|
1758
|
+
...title ? { role: "img", "aria-label": title } : { "aria-hidden": true },
|
|
1759
|
+
children: [
|
|
1760
|
+
title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
|
|
1761
|
+
children
|
|
1762
|
+
]
|
|
1763
|
+
}
|
|
1764
|
+
);
|
|
1690
1765
|
}
|
|
1691
1766
|
|
|
1692
|
-
// src/components/
|
|
1767
|
+
// src/components/icons/icons.tsx
|
|
1768
|
+
function StarIcon(props) {
|
|
1769
|
+
return /* @__PURE__ */ jsx(SvgIcon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" }) });
|
|
1770
|
+
}
|
|
1771
|
+
function ScaleIcon(props) {
|
|
1772
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1773
|
+
/* @__PURE__ */ jsx("path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1774
|
+
/* @__PURE__ */ jsx("path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }),
|
|
1775
|
+
/* @__PURE__ */ jsx("path", { d: "M7 21h10" }),
|
|
1776
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3v18" }),
|
|
1777
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" })
|
|
1778
|
+
] });
|
|
1779
|
+
}
|
|
1780
|
+
function AlertTriangleIcon(props) {
|
|
1781
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1782
|
+
/* @__PURE__ */ jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }),
|
|
1783
|
+
/* @__PURE__ */ jsx("path", { d: "M12 9v4" }),
|
|
1784
|
+
/* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
|
|
1785
|
+
] });
|
|
1786
|
+
}
|
|
1787
|
+
function TrendingDownIcon(props) {
|
|
1788
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1789
|
+
/* @__PURE__ */ jsx("path", { d: "M16 17h6v-6" }),
|
|
1790
|
+
/* @__PURE__ */ jsx("path", { d: "m22 17-8.5-8.5-5 5L2 7" })
|
|
1791
|
+
] });
|
|
1792
|
+
}
|
|
1793
|
+
function CircleSlashIcon(props) {
|
|
1794
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1795
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1796
|
+
/* @__PURE__ */ jsx("path", { d: "m9 15 6-6" })
|
|
1797
|
+
] });
|
|
1798
|
+
}
|
|
1799
|
+
function AwardIcon(props) {
|
|
1800
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1801
|
+
/* @__PURE__ */ jsx("path", { d: "m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526" }),
|
|
1802
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "6" })
|
|
1803
|
+
] });
|
|
1804
|
+
}
|
|
1805
|
+
function InfoIcon(props) {
|
|
1806
|
+
return /* @__PURE__ */ jsxs(SvgIcon, { ...props, children: [
|
|
1807
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1808
|
+
/* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
|
|
1809
|
+
/* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
|
|
1810
|
+
] });
|
|
1811
|
+
}
|
|
1693
1812
|
var t = getSemanticColors("dark");
|
|
1694
1813
|
var baseBadgeSizeConfig = {
|
|
1695
1814
|
sm: { fontSize: 9, paddingH: 6, paddingV: 2, iconSize: 10 },
|
|
@@ -1697,7 +1816,7 @@ var baseBadgeSizeConfig = {
|
|
|
1697
1816
|
lg: { fontSize: 12, paddingH: 10, paddingV: 4, iconSize: 14 }
|
|
1698
1817
|
};
|
|
1699
1818
|
var variantColors = {
|
|
1700
|
-
plain: { backgroundColor: t["surface-raised"], borderColor: "
|
|
1819
|
+
plain: { backgroundColor: t["surface-raised"], borderColor: t["hairline-default"] },
|
|
1701
1820
|
pr: {
|
|
1702
1821
|
backgroundColor: "rgba(255, 121, 0, 0.12)",
|
|
1703
1822
|
borderColor: "rgba(255, 121, 0, 0.3)"
|
|
@@ -1904,6 +2023,7 @@ function RowStrip({ set }) {
|
|
|
1904
2023
|
showInfo: false,
|
|
1905
2024
|
height: 24,
|
|
1906
2025
|
scale: "fixed",
|
|
2026
|
+
liveRepIndex: set.velocities.length - 1,
|
|
1907
2027
|
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
1908
2028
|
zones
|
|
1909
2029
|
}
|
|
@@ -1913,7 +2033,9 @@ function RowStrip({ set }) {
|
|
|
1913
2033
|
return /* @__PURE__ */ jsx(
|
|
1914
2034
|
VelocityStrip,
|
|
1915
2035
|
{
|
|
1916
|
-
variant: "
|
|
2036
|
+
variant: "compact",
|
|
2037
|
+
height: 8,
|
|
2038
|
+
hideBaseline: true,
|
|
1917
2039
|
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
1918
2040
|
zones
|
|
1919
2041
|
}
|
|
@@ -2048,321 +2170,10 @@ function SetsRepsLoad({
|
|
|
2048
2170
|
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${AT} ` }),
|
|
2049
2171
|
/* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: load }),
|
|
2050
2172
|
/* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${unit}` })
|
|
2051
|
-
]
|
|
2052
|
-
}
|
|
2053
|
-
);
|
|
2054
|
-
}
|
|
2055
|
-
function hexToRgb(hex) {
|
|
2056
|
-
const cleaned = hex.replace("#", "");
|
|
2057
|
-
let r, g, b;
|
|
2058
|
-
if (cleaned.length === 3) {
|
|
2059
|
-
r = parseInt(cleaned[0] + cleaned[0], 16);
|
|
2060
|
-
g = parseInt(cleaned[1] + cleaned[1], 16);
|
|
2061
|
-
b = parseInt(cleaned[2] + cleaned[2], 16);
|
|
2062
|
-
} else if (cleaned.length === 6) {
|
|
2063
|
-
r = parseInt(cleaned.slice(0, 2), 16);
|
|
2064
|
-
g = parseInt(cleaned.slice(2, 4), 16);
|
|
2065
|
-
b = parseInt(cleaned.slice(4, 6), 16);
|
|
2066
|
-
} else {
|
|
2067
|
-
return null;
|
|
2068
|
-
}
|
|
2069
|
-
return { r, g, b };
|
|
2070
|
-
}
|
|
2071
|
-
function rgbToHsv(r, g, b) {
|
|
2072
|
-
const rNorm = r / 255;
|
|
2073
|
-
const gNorm = g / 255;
|
|
2074
|
-
const bNorm = b / 255;
|
|
2075
|
-
const max = Math.max(rNorm, gNorm, bNorm);
|
|
2076
|
-
const min = Math.min(rNorm, gNorm, bNorm);
|
|
2077
|
-
const delta = max - min;
|
|
2078
|
-
const v = max;
|
|
2079
|
-
const s = max === 0 ? 0 : delta / max;
|
|
2080
|
-
let h = 0;
|
|
2081
|
-
if (delta !== 0) {
|
|
2082
|
-
if (max === rNorm) {
|
|
2083
|
-
h = 60 * ((gNorm - bNorm) / delta % 6);
|
|
2084
|
-
} else if (max === gNorm) {
|
|
2085
|
-
h = 60 * ((bNorm - rNorm) / delta + 2);
|
|
2086
|
-
} else {
|
|
2087
|
-
h = 60 * ((rNorm - gNorm) / delta + 4);
|
|
2088
|
-
}
|
|
2089
|
-
}
|
|
2090
|
-
if (h < 0) h += 360;
|
|
2091
|
-
return { h, s, v };
|
|
2092
|
-
}
|
|
2093
|
-
function hsvToRgb(h, s, v) {
|
|
2094
|
-
const c = v * s;
|
|
2095
|
-
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
2096
|
-
const m = v - c;
|
|
2097
|
-
let rPrime, gPrime, bPrime;
|
|
2098
|
-
if (h >= 0 && h < 60) {
|
|
2099
|
-
rPrime = c;
|
|
2100
|
-
gPrime = x;
|
|
2101
|
-
bPrime = 0;
|
|
2102
|
-
} else if (h >= 60 && h < 120) {
|
|
2103
|
-
rPrime = x;
|
|
2104
|
-
gPrime = c;
|
|
2105
|
-
bPrime = 0;
|
|
2106
|
-
} else if (h >= 120 && h < 180) {
|
|
2107
|
-
rPrime = 0;
|
|
2108
|
-
gPrime = c;
|
|
2109
|
-
bPrime = x;
|
|
2110
|
-
} else if (h >= 180 && h < 240) {
|
|
2111
|
-
rPrime = 0;
|
|
2112
|
-
gPrime = x;
|
|
2113
|
-
bPrime = c;
|
|
2114
|
-
} else if (h >= 240 && h < 300) {
|
|
2115
|
-
rPrime = x;
|
|
2116
|
-
gPrime = 0;
|
|
2117
|
-
bPrime = c;
|
|
2118
|
-
} else {
|
|
2119
|
-
rPrime = c;
|
|
2120
|
-
gPrime = 0;
|
|
2121
|
-
bPrime = x;
|
|
2122
|
-
}
|
|
2123
|
-
return {
|
|
2124
|
-
r: Math.round((rPrime + m) * 255),
|
|
2125
|
-
g: Math.round((gPrime + m) * 255),
|
|
2126
|
-
b: Math.round((bPrime + m) * 255)
|
|
2127
|
-
};
|
|
2128
|
-
}
|
|
2129
|
-
function rgbToHex(r, g, b) {
|
|
2130
|
-
const toHex = (n) => {
|
|
2131
|
-
const clamped = Math.max(0, Math.min(255, Math.round(n)));
|
|
2132
|
-
return clamped.toString(16).padStart(2, "0");
|
|
2133
|
-
};
|
|
2134
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
2135
|
-
}
|
|
2136
|
-
function lighten(hex, amount = 0.1) {
|
|
2137
|
-
const rgb = hexToRgb(hex);
|
|
2138
|
-
if (!rgb) return hex;
|
|
2139
|
-
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
2140
|
-
const newV = Math.min(1, hsv.v + amount);
|
|
2141
|
-
const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
|
|
2142
|
-
return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
|
|
2143
|
-
}
|
|
2144
|
-
var shadowColors = {
|
|
2145
|
-
// For charcoal surfaces (~#2C2C2C to #3C3C3C)
|
|
2146
|
-
charcoal: {
|
|
2147
|
-
dark: "rgba(0, 0, 0, 0.5)",
|
|
2148
|
-
light: "rgba(255, 255, 255, 0.12)",
|
|
2149
|
-
// brighter for crisp rim light
|
|
2150
|
-
darker: "rgba(0, 0, 0, 0.6)"
|
|
2151
|
-
},
|
|
2152
|
-
// For neutral dark surfaces (~#1F2937)
|
|
2153
|
-
neutralDark: {
|
|
2154
|
-
dark: "rgba(0, 0, 0, 0.4)",
|
|
2155
|
-
light: "rgba(255, 255, 255, 0.08)",
|
|
2156
|
-
darker: "rgba(0, 0, 0, 0.5)"
|
|
2157
|
-
},
|
|
2158
|
-
// For light surfaces (white/light gray backgrounds)
|
|
2159
|
-
neutralLight: {
|
|
2160
|
-
dark: "rgba(0, 0, 0, 0.15)",
|
|
2161
|
-
// Visible dark shadow for depth on light backgrounds
|
|
2162
|
-
light: "rgba(255, 255, 255, 1)",
|
|
2163
|
-
// Bright highlight for raised effect (visible on light gray)
|
|
2164
|
-
darker: "rgba(0, 0, 0, 0.2)"
|
|
2165
|
-
// Deeper shadow for pressed states
|
|
2166
|
-
}
|
|
2167
|
-
};
|
|
2168
|
-
var intensityConfig = {
|
|
2169
|
-
subtle: { offset: 1, blur: 2 },
|
|
2170
|
-
// very light, barely visible
|
|
2171
|
-
medium: { offset: 2, blur: 4 },
|
|
2172
|
-
// noticeable but restrained
|
|
2173
|
-
strong: { offset: 2, blur: 5 }
|
|
2174
|
-
// slightly deeper blur, same offset as medium
|
|
2175
|
-
};
|
|
2176
|
-
function createRaisedShadow(surface = "charcoal", intensity = "medium") {
|
|
2177
|
-
const colors = shadowColors[surface];
|
|
2178
|
-
const { offset, blur } = intensityConfig[intensity];
|
|
2179
|
-
const rimBlur = 1;
|
|
2180
|
-
return reactNative.Platform.select({
|
|
2181
|
-
web: {
|
|
2182
|
-
boxShadow: `${offset}px ${offset}px ${blur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
2183
|
-
},
|
|
2184
|
-
default: {
|
|
2185
|
-
// React Native shadow approximation (limited compared to web)
|
|
2186
|
-
shadowColor: "#000",
|
|
2187
|
-
shadowOffset: { width: offset, height: offset },
|
|
2188
|
-
shadowOpacity: 0.3,
|
|
2189
|
-
shadowRadius: blur / 2,
|
|
2190
|
-
elevation: offset
|
|
2191
|
-
}
|
|
2192
|
-
});
|
|
2193
|
-
}
|
|
2194
|
-
function createPressedShadow(surface = "charcoal", intensity = "medium") {
|
|
2195
|
-
const colors = shadowColors[surface];
|
|
2196
|
-
const { offset, blur } = intensityConfig[intensity];
|
|
2197
|
-
const rimOffset = 1;
|
|
2198
|
-
const rimBlur = 1;
|
|
2199
|
-
return reactNative.Platform.select({
|
|
2200
|
-
web: {
|
|
2201
|
-
boxShadow: `inset ${offset}px ${offset}px ${blur}px ${colors.darker}, inset -${rimOffset}px -${rimOffset}px ${rimBlur}px ${colors.light}`
|
|
2202
|
-
},
|
|
2203
|
-
default: {
|
|
2204
|
-
// React Native doesn't support inset shadows, approximate with darker bg
|
|
2205
|
-
shadowOpacity: 0,
|
|
2206
|
-
elevation: 0
|
|
2207
|
-
}
|
|
2208
|
-
});
|
|
2209
|
-
}
|
|
2210
|
-
function createFlatShadow() {
|
|
2211
|
-
return reactNative.Platform.select({
|
|
2212
|
-
web: {
|
|
2213
|
-
boxShadow: "none"
|
|
2214
|
-
},
|
|
2215
|
-
default: {
|
|
2216
|
-
shadowOpacity: 0,
|
|
2217
|
-
elevation: 0
|
|
2218
|
-
}
|
|
2219
|
-
});
|
|
2220
|
-
}
|
|
2221
|
-
function createRaisedHoverShadow(surface = "charcoal", intensity = "medium") {
|
|
2222
|
-
const colors = shadowColors[surface];
|
|
2223
|
-
const { offset, blur } = intensityConfig[intensity];
|
|
2224
|
-
const hoverOffset = Math.max(1, Math.round(offset / 2));
|
|
2225
|
-
const hoverBlur = Math.max(1, Math.round(blur / 2));
|
|
2226
|
-
const rimBlur = 1;
|
|
2227
|
-
return reactNative.Platform.select({
|
|
2228
|
-
web: {
|
|
2229
|
-
// Keep same offsets as normal state for rim to prevent "shrinking" effect
|
|
2230
|
-
boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
|
|
2231
|
-
},
|
|
2232
|
-
default: {
|
|
2233
|
-
shadowColor: "#000",
|
|
2234
|
-
shadowOffset: { width: hoverOffset, height: hoverOffset },
|
|
2235
|
-
shadowOpacity: 0.2,
|
|
2236
|
-
shadowRadius: hoverBlur / 2,
|
|
2237
|
-
elevation: hoverOffset
|
|
2238
|
-
}
|
|
2239
|
-
});
|
|
2240
|
-
}
|
|
2241
|
-
function createPressedHoverShadow(surface = "charcoal", intensity = "medium") {
|
|
2242
|
-
const colors = shadowColors[surface];
|
|
2243
|
-
const { offset, blur } = intensityConfig[intensity];
|
|
2244
|
-
const hoverOffset = Math.max(1, Math.round(offset / 2));
|
|
2245
|
-
const hoverBlur = Math.max(1, Math.round(blur / 2));
|
|
2246
|
-
const rimBlur = 1;
|
|
2247
|
-
return reactNative.Platform.select({
|
|
2248
|
-
web: {
|
|
2249
|
-
// Keep rim offset at 1px (same as pressed normal) to prevent "shrinking" effect
|
|
2250
|
-
boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.darker}, inset -1px -1px ${rimBlur}px ${colors.light}`
|
|
2251
|
-
},
|
|
2252
|
-
default: {
|
|
2253
|
-
shadowOpacity: 0,
|
|
2254
|
-
elevation: 0
|
|
2255
|
-
}
|
|
2256
|
-
});
|
|
2257
|
-
}
|
|
2258
|
-
({
|
|
2259
|
-
// Charcoal surface shadows (for dark toolbars, cards)
|
|
2260
|
-
charcoal: {
|
|
2261
|
-
raised: {
|
|
2262
|
-
subtle: createRaisedShadow("charcoal", "subtle"),
|
|
2263
|
-
medium: createRaisedShadow("charcoal", "medium"),
|
|
2264
|
-
strong: createRaisedShadow("charcoal", "strong")
|
|
2265
|
-
},
|
|
2266
|
-
raisedHover: {
|
|
2267
|
-
subtle: createRaisedHoverShadow("charcoal", "subtle"),
|
|
2268
|
-
medium: createRaisedHoverShadow("charcoal", "medium"),
|
|
2269
|
-
strong: createRaisedHoverShadow("charcoal", "strong")
|
|
2270
|
-
},
|
|
2271
|
-
pressed: {
|
|
2272
|
-
subtle: createPressedShadow("charcoal", "subtle"),
|
|
2273
|
-
medium: createPressedShadow("charcoal", "medium"),
|
|
2274
|
-
strong: createPressedShadow("charcoal", "strong")
|
|
2275
|
-
},
|
|
2276
|
-
pressedHover: {
|
|
2277
|
-
subtle: createPressedHoverShadow("charcoal", "subtle"),
|
|
2278
|
-
medium: createPressedHoverShadow("charcoal", "medium"),
|
|
2279
|
-
strong: createPressedHoverShadow("charcoal", "strong")
|
|
2280
|
-
},
|
|
2281
|
-
flat: createFlatShadow()
|
|
2282
|
-
},
|
|
2283
|
-
// Neutral dark surface shadows
|
|
2284
|
-
neutralDark: {
|
|
2285
|
-
raised: {
|
|
2286
|
-
subtle: createRaisedShadow("neutralDark", "subtle"),
|
|
2287
|
-
medium: createRaisedShadow("neutralDark", "medium"),
|
|
2288
|
-
strong: createRaisedShadow("neutralDark", "strong")
|
|
2289
|
-
},
|
|
2290
|
-
raisedHover: {
|
|
2291
|
-
subtle: createRaisedHoverShadow("neutralDark", "subtle"),
|
|
2292
|
-
medium: createRaisedHoverShadow("neutralDark", "medium"),
|
|
2293
|
-
strong: createRaisedHoverShadow("neutralDark", "strong")
|
|
2294
|
-
},
|
|
2295
|
-
pressed: {
|
|
2296
|
-
subtle: createPressedShadow("neutralDark", "subtle"),
|
|
2297
|
-
medium: createPressedShadow("neutralDark", "medium"),
|
|
2298
|
-
strong: createPressedShadow("neutralDark", "strong")
|
|
2299
|
-
},
|
|
2300
|
-
pressedHover: {
|
|
2301
|
-
subtle: createPressedHoverShadow("neutralDark", "subtle"),
|
|
2302
|
-
medium: createPressedHoverShadow("neutralDark", "medium"),
|
|
2303
|
-
strong: createPressedHoverShadow("neutralDark", "strong")
|
|
2304
|
-
},
|
|
2305
|
-
flat: createFlatShadow()
|
|
2306
|
-
},
|
|
2307
|
-
// Neutral light surface shadows
|
|
2308
|
-
neutralLight: {
|
|
2309
|
-
raised: {
|
|
2310
|
-
subtle: createRaisedShadow("neutralLight", "subtle"),
|
|
2311
|
-
medium: createRaisedShadow("neutralLight", "medium"),
|
|
2312
|
-
strong: createRaisedShadow("neutralLight", "strong")
|
|
2313
|
-
},
|
|
2314
|
-
raisedHover: {
|
|
2315
|
-
subtle: createRaisedHoverShadow("neutralLight", "subtle"),
|
|
2316
|
-
medium: createRaisedHoverShadow("neutralLight", "medium"),
|
|
2317
|
-
strong: createRaisedHoverShadow("neutralLight", "strong")
|
|
2318
|
-
},
|
|
2319
|
-
pressed: {
|
|
2320
|
-
subtle: createPressedShadow("neutralLight", "subtle"),
|
|
2321
|
-
medium: createPressedShadow("neutralLight", "medium"),
|
|
2322
|
-
strong: createPressedShadow("neutralLight", "strong")
|
|
2323
|
-
},
|
|
2324
|
-
pressedHover: {
|
|
2325
|
-
subtle: createPressedHoverShadow("neutralLight", "subtle"),
|
|
2326
|
-
medium: createPressedHoverShadow("neutralLight", "medium"),
|
|
2327
|
-
strong: createPressedHoverShadow("neutralLight", "strong")
|
|
2328
|
-
},
|
|
2329
|
-
flat: createFlatShadow()
|
|
2330
|
-
}
|
|
2331
|
-
});
|
|
2332
|
-
|
|
2333
|
-
// src/utils/colors.ts
|
|
2334
|
-
function alpha(color, opacity) {
|
|
2335
|
-
const clampedOpacity = Math.max(0, Math.min(1, opacity));
|
|
2336
|
-
if (color.startsWith("#")) {
|
|
2337
|
-
const hex = color.slice(1);
|
|
2338
|
-
let r, g, b;
|
|
2339
|
-
if (hex.length === 3) {
|
|
2340
|
-
r = parseInt(hex[0] + hex[0], 16);
|
|
2341
|
-
g = parseInt(hex[1] + hex[1], 16);
|
|
2342
|
-
b = parseInt(hex[2] + hex[2], 16);
|
|
2343
|
-
} else if (hex.length === 6) {
|
|
2344
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
2345
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
2346
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
2347
|
-
} else {
|
|
2348
|
-
return color;
|
|
2173
|
+
]
|
|
2349
2174
|
}
|
|
2350
|
-
|
|
2351
|
-
}
|
|
2352
|
-
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
2353
|
-
if (rgbMatch) {
|
|
2354
|
-
const [, r, g, b] = rgbMatch;
|
|
2355
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
2356
|
-
}
|
|
2357
|
-
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
2358
|
-
if (rgbaMatch) {
|
|
2359
|
-
const [, r, g, b] = rgbaMatch;
|
|
2360
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
2361
|
-
}
|
|
2362
|
-
return color;
|
|
2175
|
+
);
|
|
2363
2176
|
}
|
|
2364
|
-
|
|
2365
|
-
// src/components/custom/Workout/TempoDisplay.tsx
|
|
2366
2177
|
var t3 = getSemanticColors("dark");
|
|
2367
2178
|
var INTER = "Inter, sans-serif";
|
|
2368
2179
|
var TEXT_TERTIARY = "#6B7280";
|
|
@@ -2444,7 +2255,7 @@ function LiveTempoCell({
|
|
|
2444
2255
|
alignItems: "center",
|
|
2445
2256
|
justifyContent: "center"
|
|
2446
2257
|
};
|
|
2447
|
-
const num = (
|
|
2258
|
+
const num = (c2, text) => /* @__PURE__ */ jsx(reactNative.Text, { style: { fontFamily: MONO, fontSize, color: c2, fontWeight: "700" }, children: text });
|
|
2448
2259
|
if (status === "upcoming") {
|
|
2449
2260
|
const targetText = targetMs != null ? (targetMs / 1e3).toFixed(1) : String(value);
|
|
2450
2261
|
return /* @__PURE__ */ jsx(reactNative.View, { style: wrap, children: num(color, targetText) });
|
|
@@ -2746,6 +2557,243 @@ function ExerciseHeading({
|
|
|
2746
2557
|
)
|
|
2747
2558
|
] });
|
|
2748
2559
|
}
|
|
2560
|
+
var PULSE_HALF_MS = 950;
|
|
2561
|
+
var PULSE_MIN_OPACITY = 0.45;
|
|
2562
|
+
var SEGMENTED_BAR_GAP = 5;
|
|
2563
|
+
function usePulse(active) {
|
|
2564
|
+
const [value] = react.useState(() => new reactNative.Animated.Value(0));
|
|
2565
|
+
react.useEffect(() => {
|
|
2566
|
+
if (!active) return;
|
|
2567
|
+
const loop = reactNative.Animated.loop(
|
|
2568
|
+
reactNative.Animated.sequence([
|
|
2569
|
+
reactNative.Animated.timing(value, {
|
|
2570
|
+
toValue: 1,
|
|
2571
|
+
duration: PULSE_HALF_MS,
|
|
2572
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
2573
|
+
useNativeDriver: false
|
|
2574
|
+
}),
|
|
2575
|
+
reactNative.Animated.timing(value, {
|
|
2576
|
+
toValue: 0,
|
|
2577
|
+
duration: PULSE_HALF_MS,
|
|
2578
|
+
easing: reactNative.Easing.inOut(reactNative.Easing.ease),
|
|
2579
|
+
useNativeDriver: false
|
|
2580
|
+
})
|
|
2581
|
+
])
|
|
2582
|
+
);
|
|
2583
|
+
loop.start();
|
|
2584
|
+
return () => loop.stop();
|
|
2585
|
+
}, [active, value]);
|
|
2586
|
+
return value;
|
|
2587
|
+
}
|
|
2588
|
+
function SegmentedBar({
|
|
2589
|
+
segments,
|
|
2590
|
+
height = 8,
|
|
2591
|
+
gap = SEGMENTED_BAR_GAP,
|
|
2592
|
+
radius = 2,
|
|
2593
|
+
marker = null,
|
|
2594
|
+
segmentTestID,
|
|
2595
|
+
style,
|
|
2596
|
+
...props
|
|
2597
|
+
}) {
|
|
2598
|
+
const pulse = usePulse(segments.some((s) => s.pulse));
|
|
2599
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
2600
|
+
segments.map((seg, i) => {
|
|
2601
|
+
const fillStyle = {
|
|
2602
|
+
width: `${(seg.fill ?? 1) * 100}%`,
|
|
2603
|
+
height: "100%",
|
|
2604
|
+
backgroundColor: seg.color,
|
|
2605
|
+
opacity: seg.opacity
|
|
2606
|
+
};
|
|
2607
|
+
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
2608
|
+
return /* @__PURE__ */ jsx(
|
|
2609
|
+
reactNative.View,
|
|
2610
|
+
{
|
|
2611
|
+
style: {
|
|
2612
|
+
flex: seg.weight ?? 1,
|
|
2613
|
+
minWidth: 0,
|
|
2614
|
+
height: "100%",
|
|
2615
|
+
marginLeft: seg.leadingGap,
|
|
2616
|
+
borderRadius: radius,
|
|
2617
|
+
overflow: "hidden"
|
|
2618
|
+
},
|
|
2619
|
+
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
2620
|
+
reactNative.Animated.View,
|
|
2621
|
+
{
|
|
2622
|
+
style: {
|
|
2623
|
+
...fillStyle,
|
|
2624
|
+
...seg.pulseColor ? {
|
|
2625
|
+
backgroundColor: pulse.interpolate({
|
|
2626
|
+
inputRange: [0, 1],
|
|
2627
|
+
outputRange: seg.pulseColor
|
|
2628
|
+
})
|
|
2629
|
+
} : {
|
|
2630
|
+
opacity: pulse.interpolate({
|
|
2631
|
+
inputRange: [0, 1],
|
|
2632
|
+
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
2633
|
+
})
|
|
2634
|
+
}
|
|
2635
|
+
},
|
|
2636
|
+
accessibilityElementsHidden: true,
|
|
2637
|
+
testID
|
|
2638
|
+
}
|
|
2639
|
+
) : /* @__PURE__ */ jsx(reactNative.View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
2640
|
+
},
|
|
2641
|
+
i
|
|
2642
|
+
);
|
|
2643
|
+
}),
|
|
2644
|
+
marker ? /* @__PURE__ */ jsx(
|
|
2645
|
+
reactNative.View,
|
|
2646
|
+
{
|
|
2647
|
+
style: {
|
|
2648
|
+
position: "absolute",
|
|
2649
|
+
left: `${marker.position * 100}%`,
|
|
2650
|
+
top: 0,
|
|
2651
|
+
bottom: 0,
|
|
2652
|
+
width: 2,
|
|
2653
|
+
backgroundColor: marker.color
|
|
2654
|
+
},
|
|
2655
|
+
accessibilityElementsHidden: true,
|
|
2656
|
+
testID: "segmented-bar-marker"
|
|
2657
|
+
}
|
|
2658
|
+
) : null
|
|
2659
|
+
] });
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
// src/components/custom/Workout/SetBar.tsx
|
|
2663
|
+
var SET_STRIP_ZONES = {
|
|
2664
|
+
slow: primitiveRamps.red[600],
|
|
2665
|
+
moderate: primitiveRamps.orange[400],
|
|
2666
|
+
fast: primitiveRamps.amber[300],
|
|
2667
|
+
fastest: primitiveRamps.green[300]
|
|
2668
|
+
};
|
|
2669
|
+
var TODO_COLOR = greyRamp[900];
|
|
2670
|
+
var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
|
|
2671
|
+
var NOTCH_GAP = 2;
|
|
2672
|
+
var CLUSTER_GAP = 3;
|
|
2673
|
+
var MYO_UPCOMING_CLUSTERS = 3;
|
|
2674
|
+
var MYO_UPCOMING_CLUSTER_LEN = 5;
|
|
2675
|
+
var MYO_UPCOMING_FADE = 0.6;
|
|
2676
|
+
var PULSE_RANGE = {
|
|
2677
|
+
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
2678
|
+
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
2679
|
+
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
2680
|
+
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
2681
|
+
};
|
|
2682
|
+
function velocityZoneColor(v) {
|
|
2683
|
+
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
2684
|
+
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
2685
|
+
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
2686
|
+
return SET_STRIP_ZONES.fastest;
|
|
2687
|
+
}
|
|
2688
|
+
function chunkedSegments(chunks, gap) {
|
|
2689
|
+
return chunks.flatMap(
|
|
2690
|
+
(vels, ci) => vels.map((v, ri) => ({
|
|
2691
|
+
color: velocityZoneColor(v),
|
|
2692
|
+
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
2693
|
+
}))
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
function rangeSegments(floor, max, doneVels) {
|
|
2697
|
+
return Array.from({ length: max }, (_, i) => {
|
|
2698
|
+
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
2699
|
+
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
2703
|
+
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
2704
|
+
const trail = Array.from(
|
|
2705
|
+
{ length: clusterCount },
|
|
2706
|
+
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
2707
|
+
color: SET_STRIP_VARIABLE_COLOR,
|
|
2708
|
+
opacity: MYO_UPCOMING_FADE ** ci,
|
|
2709
|
+
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
2710
|
+
}))
|
|
2711
|
+
).flat();
|
|
2712
|
+
return [...activation, ...trail];
|
|
2713
|
+
}
|
|
2714
|
+
function setSegments(set) {
|
|
2715
|
+
if (set.status === "todo") {
|
|
2716
|
+
return [{ color: TODO_COLOR }];
|
|
2717
|
+
}
|
|
2718
|
+
if (set.status === "done") {
|
|
2719
|
+
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
2720
|
+
}
|
|
2721
|
+
if (set.status === "range") {
|
|
2722
|
+
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
2723
|
+
}
|
|
2724
|
+
if (set.status === "drop") {
|
|
2725
|
+
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
2726
|
+
}
|
|
2727
|
+
if (set.status === "myo") {
|
|
2728
|
+
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
2729
|
+
}
|
|
2730
|
+
if (set.status === "myo-upcoming") {
|
|
2731
|
+
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
2732
|
+
}
|
|
2733
|
+
const performed = set.velocities.map((v) => {
|
|
2734
|
+
const color = velocityZoneColor(v);
|
|
2735
|
+
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
2736
|
+
});
|
|
2737
|
+
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
2738
|
+
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
2739
|
+
}
|
|
2740
|
+
function setBarSegmentTestID(seg) {
|
|
2741
|
+
if (seg.pulse) return "set-strip-pulse";
|
|
2742
|
+
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
2743
|
+
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
2744
|
+
}
|
|
2745
|
+
function SetBar({ set, height = 8 }) {
|
|
2746
|
+
return /* @__PURE__ */ jsx(
|
|
2747
|
+
SegmentedBar,
|
|
2748
|
+
{
|
|
2749
|
+
segments: setSegments(set),
|
|
2750
|
+
height,
|
|
2751
|
+
gap: 0,
|
|
2752
|
+
radius: 0,
|
|
2753
|
+
segmentTestID: setBarSegmentTestID,
|
|
2754
|
+
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
2755
|
+
testID: "set-strip-set"
|
|
2756
|
+
}
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// src/components/custom/Workout/SetStrip.tsx
|
|
2761
|
+
var SET_STRIP_GAP = 5;
|
|
2762
|
+
function setCategory(set) {
|
|
2763
|
+
switch (set.status) {
|
|
2764
|
+
case "done":
|
|
2765
|
+
case "drop":
|
|
2766
|
+
case "myo":
|
|
2767
|
+
return "done";
|
|
2768
|
+
case "active":
|
|
2769
|
+
return "active";
|
|
2770
|
+
case "range":
|
|
2771
|
+
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
2772
|
+
case "todo":
|
|
2773
|
+
case "myo-upcoming":
|
|
2774
|
+
return "upcoming";
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
function describeSets(sets) {
|
|
2778
|
+
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
2779
|
+
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
2780
|
+
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
2781
|
+
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
2782
|
+
}
|
|
2783
|
+
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
2784
|
+
return /* @__PURE__ */ jsx(
|
|
2785
|
+
reactNative.View,
|
|
2786
|
+
{
|
|
2787
|
+
className,
|
|
2788
|
+
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
2789
|
+
accessibilityRole: "image",
|
|
2790
|
+
accessibilityLabel: describeSets(sets),
|
|
2791
|
+
testID: "set-strip",
|
|
2792
|
+
...props,
|
|
2793
|
+
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
2794
|
+
}
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2749
2797
|
|
|
2750
2798
|
// src/components/custom/Workout/ExerciseCardHeading.tsx
|
|
2751
2799
|
function ExerciseCardHeading({
|
|
@@ -2888,7 +2936,9 @@ function CollapsedCard({
|
|
|
2888
2936
|
{
|
|
2889
2937
|
velocities,
|
|
2890
2938
|
zones: velocityZones,
|
|
2891
|
-
variant: "
|
|
2939
|
+
variant: "compact",
|
|
2940
|
+
height: 8,
|
|
2941
|
+
hideBaseline: true,
|
|
2892
2942
|
testID: `exercise-card-velocity-strip-${i}`
|
|
2893
2943
|
},
|
|
2894
2944
|
i
|
|
@@ -2908,7 +2958,7 @@ function CollapsedCard({
|
|
|
2908
2958
|
);
|
|
2909
2959
|
}
|
|
2910
2960
|
var DARK2 = getSemanticColors("dark");
|
|
2911
|
-
var BODY_DIVIDER = DARK2["
|
|
2961
|
+
var BODY_DIVIDER = DARK2["hairline-subtle"];
|
|
2912
2962
|
function deriveHeaderSetStates(sets) {
|
|
2913
2963
|
return sets.map((set) => {
|
|
2914
2964
|
switch (set.state) {
|
|
@@ -2942,7 +2992,7 @@ function ExpandedCard({
|
|
|
2942
2992
|
return /* @__PURE__ */ jsxs(
|
|
2943
2993
|
reactNative.View,
|
|
2944
2994
|
{
|
|
2945
|
-
className: "bg-surface-elevated border-
|
|
2995
|
+
className: "bg-surface-elevated border-hairline",
|
|
2946
2996
|
style: {
|
|
2947
2997
|
borderWidth: 1,
|
|
2948
2998
|
...borderRadius,
|
|
@@ -3075,7 +3125,7 @@ function resolveColor(token, mode = "dark") {
|
|
|
3075
3125
|
|
|
3076
3126
|
// src/components/custom/Workout/InputBar.tsx
|
|
3077
3127
|
var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
|
|
3078
|
-
var INPUT_CLASSNAME = "bg-surface-raised border-
|
|
3128
|
+
var INPUT_CLASSNAME = "bg-surface-raised border-hairline-strong text-text-primary";
|
|
3079
3129
|
var inputStyle = {
|
|
3080
3130
|
fontSize: 14,
|
|
3081
3131
|
fontWeight: "600",
|
|
@@ -3108,7 +3158,7 @@ function InputBar({
|
|
|
3108
3158
|
style: {
|
|
3109
3159
|
width: "100%",
|
|
3110
3160
|
borderTopWidth: 1,
|
|
3111
|
-
borderTopColor: resolveColor("
|
|
3161
|
+
borderTopColor: resolveColor("hairline-default"),
|
|
3112
3162
|
paddingTop: 10,
|
|
3113
3163
|
paddingHorizontal: 16,
|
|
3114
3164
|
paddingBottom: 12,
|
|
@@ -3323,7 +3373,7 @@ function CircularProgress({
|
|
|
3323
3373
|
cy: center,
|
|
3324
3374
|
r: radius,
|
|
3325
3375
|
fill: "none",
|
|
3326
|
-
stroke: "var(--color-
|
|
3376
|
+
stroke: "var(--color-hairline-default)",
|
|
3327
3377
|
strokeWidth
|
|
3328
3378
|
}
|
|
3329
3379
|
),
|
|
@@ -3578,7 +3628,7 @@ function RestTimer({
|
|
|
3578
3628
|
style: {
|
|
3579
3629
|
width: "100%",
|
|
3580
3630
|
borderTopWidth: 1,
|
|
3581
|
-
borderTopColor: resolveColor("
|
|
3631
|
+
borderTopColor: resolveColor("hairline-default"),
|
|
3582
3632
|
paddingVertical: 12,
|
|
3583
3633
|
paddingHorizontal: 16
|
|
3584
3634
|
},
|
|
@@ -3647,7 +3697,7 @@ function RestTimer({
|
|
|
3647
3697
|
/* @__PURE__ */ jsx(
|
|
3648
3698
|
reactNative.View,
|
|
3649
3699
|
{
|
|
3650
|
-
className: "bg-
|
|
3700
|
+
className: "bg-hairline",
|
|
3651
3701
|
style: {
|
|
3652
3702
|
height: 3,
|
|
3653
3703
|
borderRadius: 2,
|
|
@@ -3867,7 +3917,7 @@ function WorkoutHeader({
|
|
|
3867
3917
|
/* @__PURE__ */ jsx(
|
|
3868
3918
|
reactNative.View,
|
|
3869
3919
|
{
|
|
3870
|
-
className: "bg-
|
|
3920
|
+
className: "bg-hairline",
|
|
3871
3921
|
style: { height: 4, borderRadius: 2 },
|
|
3872
3922
|
testID: "active-workout-page-progress-track",
|
|
3873
3923
|
children: /* @__PURE__ */ jsx(
|
|
@@ -3892,7 +3942,7 @@ function ExerciseList({ groups, focusedId, onToggle }) {
|
|
|
3892
3942
|
return /* @__PURE__ */ jsx(
|
|
3893
3943
|
reactNative.View,
|
|
3894
3944
|
{
|
|
3895
|
-
className: "bg-surface-elevated border-
|
|
3945
|
+
className: "bg-surface-elevated border-hairline",
|
|
3896
3946
|
style: {
|
|
3897
3947
|
borderWidth: 1,
|
|
3898
3948
|
borderRadius: 12
|
|
@@ -3974,7 +4024,7 @@ function ActiveWorkoutPage({
|
|
|
3974
4024
|
(showRest || showInput) && /* @__PURE__ */ jsx(
|
|
3975
4025
|
reactNative.View,
|
|
3976
4026
|
{
|
|
3977
|
-
style: { borderTopWidth: 1, borderTopColor: resolveColor("
|
|
4027
|
+
style: { borderTopWidth: 1, borderTopColor: resolveColor("hairline-default") },
|
|
3978
4028
|
testID: "active-workout-page-bottom-bar",
|
|
3979
4029
|
children: showRest && rest != null ? /* @__PURE__ */ jsx(
|
|
3980
4030
|
RestTimer,
|
|
@@ -4038,7 +4088,13 @@ var elevationSystem = {
|
|
|
4038
4088
|
// Level 1: Subtle elevation (outline cards, subtle containers)
|
|
4039
4089
|
[1]: {
|
|
4040
4090
|
name: "subtle",
|
|
4041
|
-
|
|
4091
|
+
// The whole ladder was re-spaced in TD-07.16. It used to run
|
|
4092
|
+
// 0.01/0.02/0.04/0.06/0.08, where the first step was ~2 RGB values — under
|
|
4093
|
+
// the perceptual floor, so level 1 existed in the type system and nowhere on
|
|
4094
|
+
// screen. With drop-shadow now gated off below FLOATING_ELEVATION_MIN, TONE
|
|
4095
|
+
// is the only thing separating 0-3, so every step has to actually land.
|
|
4096
|
+
// Even 0.025 spacing, verified in elevation.test.ts as ΔL* > 1 per step.
|
|
4097
|
+
colorAdjustment: 0.025,
|
|
4042
4098
|
shadowIntensity: "subtle",
|
|
4043
4099
|
shadowStyle: "raised",
|
|
4044
4100
|
description: "Subtle elevation (outline cards, subtle containers)"
|
|
@@ -4046,7 +4102,7 @@ var elevationSystem = {
|
|
|
4046
4102
|
// Level 2: Standard elevation (default cards, buttons)
|
|
4047
4103
|
[2]: {
|
|
4048
4104
|
name: "standard",
|
|
4049
|
-
colorAdjustment: 0.
|
|
4105
|
+
colorAdjustment: 0.05,
|
|
4050
4106
|
shadowIntensity: "medium",
|
|
4051
4107
|
shadowStyle: "raised",
|
|
4052
4108
|
description: "Standard elevation (default cards, raised buttons)"
|
|
@@ -4054,7 +4110,7 @@ var elevationSystem = {
|
|
|
4054
4110
|
// Level 3: Prominent elevation (important cards, modals)
|
|
4055
4111
|
[3]: {
|
|
4056
4112
|
name: "prominent",
|
|
4057
|
-
colorAdjustment: 0.
|
|
4113
|
+
colorAdjustment: 0.075,
|
|
4058
4114
|
shadowIntensity: "strong",
|
|
4059
4115
|
shadowStyle: "raised",
|
|
4060
4116
|
description: "Prominent elevation (important cards, modals, dropdowns)"
|
|
@@ -4062,7 +4118,7 @@ var elevationSystem = {
|
|
|
4062
4118
|
// Level 4: High elevation (overlays, popovers)
|
|
4063
4119
|
[4]: {
|
|
4064
4120
|
name: "high",
|
|
4065
|
-
colorAdjustment: 0.
|
|
4121
|
+
colorAdjustment: 0.1,
|
|
4066
4122
|
shadowIntensity: "strong",
|
|
4067
4123
|
shadowStyle: "raised",
|
|
4068
4124
|
description: "High elevation (overlays, popovers, tooltips)"
|
|
@@ -4070,7 +4126,7 @@ var elevationSystem = {
|
|
|
4070
4126
|
// Level 5: Floating (toasts, floating action buttons)
|
|
4071
4127
|
[5]: {
|
|
4072
4128
|
name: "floating",
|
|
4073
|
-
colorAdjustment: 0.
|
|
4129
|
+
colorAdjustment: 0.13,
|
|
4074
4130
|
shadowIntensity: "strong",
|
|
4075
4131
|
shadowStyle: "raised",
|
|
4076
4132
|
description: "Floating elevation (toasts, floating action buttons, highest z-index)"
|
|
@@ -4186,12 +4242,12 @@ function calculateElevationShadow(baseColor, level, theme, isHovered = false) {
|
|
|
4186
4242
|
const elevatedSurfaceColor = calculateElevationSurface(baseColor, level);
|
|
4187
4243
|
const rimLightColor = calculateRimLightColor(baseColor, level);
|
|
4188
4244
|
const darkShadowColors = calculateDarkShadowColors(elevatedSurfaceColor);
|
|
4189
|
-
const
|
|
4245
|
+
const intensityConfig = {
|
|
4190
4246
|
subtle: { offset: 1, blur: 2 },
|
|
4191
4247
|
medium: { offset: 2, blur: 4 },
|
|
4192
4248
|
strong: { offset: 2, blur: 5 }
|
|
4193
4249
|
};
|
|
4194
|
-
const { offset, blur } =
|
|
4250
|
+
const { offset, blur } = intensityConfig[config.shadowIntensity];
|
|
4195
4251
|
const rimConfig = getRimLightConfig();
|
|
4196
4252
|
if (config.shadowStyle === "pressed") {
|
|
4197
4253
|
if (isHovered) {
|
|
@@ -4247,12 +4303,13 @@ function calculateElevationShadow(baseColor, level, theme, isHovered = false) {
|
|
|
4247
4303
|
});
|
|
4248
4304
|
}
|
|
4249
4305
|
}
|
|
4306
|
+
var FLOATING_ELEVATION_MIN = 4;
|
|
4250
4307
|
function getElevationShadow(baseColor, level, theme, isHovered = false) {
|
|
4251
4308
|
const cacheKey = getShadowCacheKey(baseColor, level, theme, isHovered);
|
|
4252
4309
|
if (elevationShadowCache.has(cacheKey)) {
|
|
4253
4310
|
return elevationShadowCache.get(cacheKey);
|
|
4254
4311
|
}
|
|
4255
|
-
const result = calculateElevationShadow(baseColor, level, theme, isHovered);
|
|
4312
|
+
const result = level > 0 && level < FLOATING_ELEVATION_MIN ? {} : calculateElevationShadow(baseColor, level, theme, isHovered);
|
|
4256
4313
|
if (elevationShadowCache.size >= MAX_SHADOW_CACHE_SIZE) {
|
|
4257
4314
|
const firstKey = elevationShadowCache.keys().next().value;
|
|
4258
4315
|
elevationShadowCache.delete(firstKey);
|
|
@@ -4338,12 +4395,12 @@ function useTheme() {
|
|
|
4338
4395
|
var variantStyles = {
|
|
4339
4396
|
elevated: "",
|
|
4340
4397
|
// Will be set dynamically via elevation system
|
|
4341
|
-
outline: "border-2 border-
|
|
4398
|
+
outline: "border-2 border-hairline-strong",
|
|
4342
4399
|
// Thicker border with stronger contrast
|
|
4343
4400
|
filled: "",
|
|
4344
4401
|
// Will be set dynamically via elevation system
|
|
4345
|
-
accent: "border border-
|
|
4346
|
-
subtle: "border border-
|
|
4402
|
+
accent: "border border-hairline",
|
|
4403
|
+
subtle: "border border-hairline-subtle"
|
|
4347
4404
|
};
|
|
4348
4405
|
function Card({
|
|
4349
4406
|
variant = "elevated",
|
|
@@ -4463,7 +4520,7 @@ var solidVariantColors = {
|
|
|
4463
4520
|
success: t5["status-success"],
|
|
4464
4521
|
warning: t5["status-warning"],
|
|
4465
4522
|
error: t5["status-error"],
|
|
4466
|
-
neutral:
|
|
4523
|
+
neutral: greyRamp[500]
|
|
4467
4524
|
};
|
|
4468
4525
|
var ringVariantStyles = {
|
|
4469
4526
|
"on-track": {
|
|
@@ -4501,10 +4558,10 @@ var iconColors = {
|
|
|
4501
4558
|
success: "#0A5C52",
|
|
4502
4559
|
warning: "#6B4000",
|
|
4503
4560
|
error: "#5C1A1A",
|
|
4504
|
-
neutral:
|
|
4561
|
+
neutral: greyRamp[200],
|
|
4505
4562
|
"on-track": t5["status-success"],
|
|
4506
4563
|
deviation: t5["status-warning"],
|
|
4507
|
-
future:
|
|
4564
|
+
future: greyRamp[500]
|
|
4508
4565
|
};
|
|
4509
4566
|
var sizeDimensions = {
|
|
4510
4567
|
sm: { size: 8, showIcon: false },
|
|
@@ -4573,7 +4630,7 @@ function StatusDot({
|
|
|
4573
4630
|
/* @__PURE__ */ jsx(
|
|
4574
4631
|
reactNative.Text,
|
|
4575
4632
|
{
|
|
4576
|
-
style: { fontSize: 12, lineHeight: 16, color:
|
|
4633
|
+
style: { fontSize: 12, lineHeight: 16, color: greyRamp[400] },
|
|
4577
4634
|
accessibilityElementsHidden: true,
|
|
4578
4635
|
children: label
|
|
4579
4636
|
}
|
|
@@ -5046,11 +5103,11 @@ function ChartLine({
|
|
|
5046
5103
|
dashed,
|
|
5047
5104
|
testID
|
|
5048
5105
|
}) {
|
|
5049
|
-
return /* @__PURE__ */ jsx(jsxRuntime.Fragment, { children: coords.map((
|
|
5106
|
+
return /* @__PURE__ */ jsx(jsxRuntime.Fragment, { children: coords.map((c2, i) => {
|
|
5050
5107
|
if (i === 0) return null;
|
|
5051
5108
|
const prev = coords[i - 1];
|
|
5052
|
-
const dx =
|
|
5053
|
-
const dy =
|
|
5109
|
+
const dx = c2.x - prev.x;
|
|
5110
|
+
const dy = c2.y - prev.y;
|
|
5054
5111
|
const length = Math.sqrt(dx * dx + dy * dy);
|
|
5055
5112
|
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
5056
5113
|
return /* @__PURE__ */ jsx(
|
|
@@ -5253,21 +5310,21 @@ function StrengthTrendChart({
|
|
|
5253
5310
|
}
|
|
5254
5311
|
),
|
|
5255
5312
|
geometry.actual.map(
|
|
5256
|
-
(
|
|
5313
|
+
(c2) => c2.point.isPR ? /* @__PURE__ */ jsx(
|
|
5257
5314
|
reactNative.Text,
|
|
5258
5315
|
{
|
|
5259
5316
|
accessibilityElementsHidden: true,
|
|
5260
5317
|
testID: "strength-trend-chart-pr-star",
|
|
5261
5318
|
style: {
|
|
5262
5319
|
position: "absolute",
|
|
5263
|
-
left:
|
|
5264
|
-
top:
|
|
5320
|
+
left: c2.x - 7,
|
|
5321
|
+
top: c2.y - 20,
|
|
5265
5322
|
fontSize: 12,
|
|
5266
5323
|
color: STATUS_WARNING2
|
|
5267
5324
|
},
|
|
5268
5325
|
children: "\u2605"
|
|
5269
5326
|
},
|
|
5270
|
-
`star-${
|
|
5327
|
+
`star-${c2.index}`
|
|
5271
5328
|
) : null
|
|
5272
5329
|
)
|
|
5273
5330
|
]
|
|
@@ -5278,29 +5335,29 @@ function StrengthTrendChart({
|
|
|
5278
5335
|
{
|
|
5279
5336
|
style: { position: "absolute", top: 0, left: 0, width, height },
|
|
5280
5337
|
testID: "strength-trend-chart-points",
|
|
5281
|
-
children: geometry.actual.map((
|
|
5282
|
-
const label =
|
|
5338
|
+
children: geometry.actual.map((c2) => {
|
|
5339
|
+
const label = c2.point.sessionLabel ?? c2.point.date;
|
|
5283
5340
|
return /* @__PURE__ */ jsx(
|
|
5284
5341
|
reactNative.Pressable,
|
|
5285
5342
|
{
|
|
5286
5343
|
testID: "strength-trend-chart-point",
|
|
5287
5344
|
accessibilityRole: "button",
|
|
5288
|
-
accessibilityLabel: `${label}: ${formatValue(
|
|
5345
|
+
accessibilityLabel: `${label}: ${formatValue(c2.point.e1rm)} ${unit}${c2.point.isPR ? ", personal record" : ""}`,
|
|
5289
5346
|
onPress: () => {
|
|
5290
|
-
setSelected(
|
|
5291
|
-
onPointPress?.(
|
|
5347
|
+
setSelected(c2.index);
|
|
5348
|
+
onPointPress?.(c2.point);
|
|
5292
5349
|
},
|
|
5293
5350
|
style: {
|
|
5294
5351
|
position: "absolute",
|
|
5295
|
-
left:
|
|
5296
|
-
top:
|
|
5352
|
+
left: c2.x - 4,
|
|
5353
|
+
top: c2.y - 4,
|
|
5297
5354
|
width: 8,
|
|
5298
5355
|
height: 8,
|
|
5299
5356
|
borderRadius: 1,
|
|
5300
5357
|
backgroundColor: BRAND_PRIMARY6
|
|
5301
5358
|
}
|
|
5302
5359
|
},
|
|
5303
|
-
`point-${
|
|
5360
|
+
`point-${c2.index}`
|
|
5304
5361
|
);
|
|
5305
5362
|
})
|
|
5306
5363
|
}
|
|
@@ -5310,7 +5367,7 @@ function StrengthTrendChart({
|
|
|
5310
5367
|
{
|
|
5311
5368
|
testID: "strength-trend-chart-tooltip",
|
|
5312
5369
|
accessibilityElementsHidden: true,
|
|
5313
|
-
className: "bg-surface-elevated border-
|
|
5370
|
+
className: "bg-surface-elevated border-hairline-strong",
|
|
5314
5371
|
style: {
|
|
5315
5372
|
position: "absolute",
|
|
5316
5373
|
left: Math.max(
|
|
@@ -5881,7 +5938,7 @@ function TabBar({ active, onSelect }) {
|
|
|
5881
5938
|
className: "flex-row",
|
|
5882
5939
|
style: {
|
|
5883
5940
|
borderBottomWidth: 1,
|
|
5884
|
-
borderBottomColor: resolveColor("
|
|
5941
|
+
borderBottomColor: resolveColor("hairline-default")
|
|
5885
5942
|
},
|
|
5886
5943
|
accessibilityRole: "tablist",
|
|
5887
5944
|
testID: "exercise-detail-page-tabs",
|
|
@@ -5932,7 +5989,7 @@ function StatStrip({ stats, unit }) {
|
|
|
5932
5989
|
return /* @__PURE__ */ jsxs(
|
|
5933
5990
|
reactNative.View,
|
|
5934
5991
|
{
|
|
5935
|
-
className: "bg-surface-elevated border-
|
|
5992
|
+
className: "bg-surface-elevated border-hairline",
|
|
5936
5993
|
style: {
|
|
5937
5994
|
flex: 1,
|
|
5938
5995
|
padding: 12,
|
|
@@ -5982,7 +6039,7 @@ function SectionCard({
|
|
|
5982
6039
|
return /* @__PURE__ */ jsxs(
|
|
5983
6040
|
reactNative.View,
|
|
5984
6041
|
{
|
|
5985
|
-
className: "bg-surface-elevated border-
|
|
6042
|
+
className: "bg-surface-elevated border-hairline",
|
|
5986
6043
|
style: {
|
|
5987
6044
|
borderWidth: 1,
|
|
5988
6045
|
borderRadius: 12,
|
|
@@ -6057,7 +6114,16 @@ function VbtBreakdown({
|
|
|
6057
6114
|
children: set.label
|
|
6058
6115
|
}
|
|
6059
6116
|
),
|
|
6060
|
-
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(
|
|
6117
|
+
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(
|
|
6118
|
+
VelocityStrip,
|
|
6119
|
+
{
|
|
6120
|
+
velocities: set.velocities,
|
|
6121
|
+
zones,
|
|
6122
|
+
variant: "compact",
|
|
6123
|
+
height: 8,
|
|
6124
|
+
hideBaseline: true
|
|
6125
|
+
}
|
|
6126
|
+
) }),
|
|
6061
6127
|
/* @__PURE__ */ jsx(
|
|
6062
6128
|
reactNative.Text,
|
|
6063
6129
|
{
|
|
@@ -6084,7 +6150,7 @@ function VbtSummaryRow({ summary }) {
|
|
|
6084
6150
|
return /* @__PURE__ */ jsx(reactNative.View, { className: "flex-row", style: { gap: 8 }, testID: "exercise-detail-page-vbt-summary", children: cells.map((cell) => /* @__PURE__ */ jsxs(
|
|
6085
6151
|
reactNative.View,
|
|
6086
6152
|
{
|
|
6087
|
-
className: "bg-surface-elevated border-
|
|
6153
|
+
className: "bg-surface-elevated border-hairline",
|
|
6088
6154
|
style: {
|
|
6089
6155
|
flex: 1,
|
|
6090
6156
|
padding: 12,
|
|
@@ -6476,7 +6542,7 @@ function Indicator({
|
|
|
6476
6542
|
// src/components/ui/badge/Badge.tsx
|
|
6477
6543
|
var variantColorStyles = {
|
|
6478
6544
|
solid: {
|
|
6479
|
-
default: "bg-
|
|
6545
|
+
default: "bg-hairline-strong text-text-inverse",
|
|
6480
6546
|
primary: "bg-brand-primary text-text-inverse",
|
|
6481
6547
|
secondary: "bg-brand-secondary text-text-inverse",
|
|
6482
6548
|
success: "bg-status-success text-text-inverse",
|
|
@@ -6494,7 +6560,7 @@ var variantColorStyles = {
|
|
|
6494
6560
|
info: "bg-status-info-subtle text-status-info"
|
|
6495
6561
|
},
|
|
6496
6562
|
outline: {
|
|
6497
|
-
default: "border border-
|
|
6563
|
+
default: "border border-hairline-strong text-text-secondary",
|
|
6498
6564
|
primary: "border border-brand-primary text-brand-primary",
|
|
6499
6565
|
secondary: "border border-brand-secondary text-brand-secondary",
|
|
6500
6566
|
success: "border border-status-success text-status-success",
|
|
@@ -6542,6 +6608,7 @@ function Badge({
|
|
|
6542
6608
|
}
|
|
6543
6609
|
);
|
|
6544
6610
|
}
|
|
6611
|
+
var HAIRLINE_DEFAULT = getSemanticColors("dark")["hairline-default"];
|
|
6545
6612
|
var t8 = getSemanticColors("dark");
|
|
6546
6613
|
var statusBgColors = {
|
|
6547
6614
|
completed: "rgba(46,213,115,0.15)",
|
|
@@ -6555,7 +6622,7 @@ var statusBorderStyles = {
|
|
|
6555
6622
|
completed: { borderWidth: 1, borderColor: "rgba(46,213,115,0.3)" },
|
|
6556
6623
|
current: { borderWidth: 1, borderColor: "rgba(255,121,0,0.3)" },
|
|
6557
6624
|
next: { borderWidth: 1, borderColor: "rgba(255,121,0,0.4)" },
|
|
6558
|
-
upcoming: { borderWidth: 1, borderColor:
|
|
6625
|
+
upcoming: { borderWidth: 1, borderColor: HAIRLINE_DEFAULT },
|
|
6559
6626
|
missed: { borderWidth: 1, borderColor: "rgba(209,67,67,0.25)" },
|
|
6560
6627
|
deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
|
|
6561
6628
|
};
|
|
@@ -6563,7 +6630,7 @@ var textColors = {
|
|
|
6563
6630
|
completed: t8["status-success"],
|
|
6564
6631
|
current: t8["brand-primary"],
|
|
6565
6632
|
next: t8["brand-primary"],
|
|
6566
|
-
upcoming:
|
|
6633
|
+
upcoming: greyRamp[500],
|
|
6567
6634
|
missed: "rgba(209,67,67,0.7)",
|
|
6568
6635
|
deload: WORKOUT_PILL_DELOAD
|
|
6569
6636
|
};
|
|
@@ -6974,10 +7041,11 @@ function WeekRow({
|
|
|
6974
7041
|
}
|
|
6975
7042
|
|
|
6976
7043
|
// src/components/custom/Workout/MesoCard.tsx
|
|
7044
|
+
var HAIRLINE_DEFAULT2 = getSemanticColors("dark")["hairline-default"];
|
|
6977
7045
|
var BRAND_PRIMARY10 = getSemanticColors("dark")["brand-primary"];
|
|
6978
7046
|
var BRAND_PRIMARY_DARK2 = MESO_ACCENT_GRADIENT_DARK;
|
|
6979
7047
|
var BRAND_PRIMARY_LIGHT2 = MESO_ACCENT_GRADIENT_LIGHT;
|
|
6980
|
-
var BORDER_DEFAULT =
|
|
7048
|
+
var BORDER_DEFAULT = HAIRLINE_DEFAULT2;
|
|
6981
7049
|
var ACCENT_STOPS2 = [BRAND_PRIMARY_DARK2, BRAND_PRIMARY10, BRAND_PRIMARY_LIGHT2];
|
|
6982
7050
|
function heatmapColor(percentage) {
|
|
6983
7051
|
const clamped = Math.max(0, Math.min(100, percentage));
|
|
@@ -7132,7 +7200,7 @@ function MesoCard({
|
|
|
7132
7200
|
}
|
|
7133
7201
|
var t10 = getSemanticColors("dark");
|
|
7134
7202
|
var dotColorMap = {
|
|
7135
|
-
untrained:
|
|
7203
|
+
untrained: greyRamp[900],
|
|
7136
7204
|
behind: t10["brand-secondary"],
|
|
7137
7205
|
ontrack: t10["status-success"],
|
|
7138
7206
|
target: t10["brand-primary"],
|
|
@@ -7145,12 +7213,12 @@ function MuscleGroupChip({
|
|
|
7145
7213
|
className,
|
|
7146
7214
|
...props
|
|
7147
7215
|
}) {
|
|
7148
|
-
const dotColor = volumeStatus ? dotColorMap[volumeStatus] :
|
|
7216
|
+
const dotColor = volumeStatus ? dotColorMap[volumeStatus] : greyRamp[900];
|
|
7149
7217
|
const statusLabel = volumeStatus ?? "no status";
|
|
7150
7218
|
const content = /* @__PURE__ */ jsxs(
|
|
7151
7219
|
reactNative.View,
|
|
7152
7220
|
{
|
|
7153
|
-
className: ["bg-surface-raised border-
|
|
7221
|
+
className: ["bg-surface-raised border-hairline", className].filter(Boolean).join(" "),
|
|
7154
7222
|
style: {
|
|
7155
7223
|
flexDirection: "row",
|
|
7156
7224
|
alignItems: "center",
|
|
@@ -7210,11 +7278,12 @@ function MuscleGroupChip({
|
|
|
7210
7278
|
}
|
|
7211
7279
|
|
|
7212
7280
|
// src/components/custom/Workout/WorkoutCard.tsx
|
|
7281
|
+
var HAIRLINE_DEFAULT3 = getSemanticColors("dark")["hairline-default"];
|
|
7213
7282
|
var t11 = getSemanticColors("dark");
|
|
7214
7283
|
var COLORS = {
|
|
7215
7284
|
statusSuccess: t11["status-success"],
|
|
7216
7285
|
brandPrimary: t11["brand-primary"],
|
|
7217
|
-
borderDefault:
|
|
7286
|
+
borderDefault: HAIRLINE_DEFAULT3,
|
|
7218
7287
|
brandPrimarySubtle: "rgba(255,121,0,0.06)"
|
|
7219
7288
|
};
|
|
7220
7289
|
var statusBorderColors = {
|
|
@@ -7593,7 +7662,7 @@ function ProgramPlanningPage({
|
|
|
7593
7662
|
/* @__PURE__ */ jsxs(
|
|
7594
7663
|
reactNative.View,
|
|
7595
7664
|
{
|
|
7596
|
-
className: "bg-surface-elevated border-
|
|
7665
|
+
className: "bg-surface-elevated border-hairline",
|
|
7597
7666
|
style: {
|
|
7598
7667
|
borderWidth: 1,
|
|
7599
7668
|
borderRadius: 12,
|