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