@titan-design/react-ui 0.4.0 → 0.5.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/MesoStatusCard-CQk71hSi.d.mts +77 -0
- package/dist/MesoStatusCard-CQk71hSi.d.ts +77 -0
- package/dist/bodymap.d.mts +246 -3
- package/dist/bodymap.d.ts +246 -3
- package/dist/bodymap.mjs +1911 -6
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +31 -738
- package/dist/index.d.ts +31 -738
- package/dist/index.js +894 -1692
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10287 -8203
- package/dist/index.mjs.map +1 -1
- package/dist/pages-D7Jlssvp.d.ts +791 -0
- package/dist/pages-MO0JCySL.d.mts +791 -0
- package/dist/pages.d.mts +4 -0
- package/dist/pages.d.ts +4 -0
- package/dist/pages.js +6646 -0
- package/dist/pages.js.map +1 -0
- package/dist/pages.mjs +6626 -0
- package/dist/pages.mjs.map +1 -0
- package/dist/theme/index.d.mts +12 -12
- package/dist/theme/index.d.ts +12 -12
- package/dist/theme/index.js +30 -30
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1880 -2
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.mjs +573 -1
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +9 -7
- package/src/components/custom/Workout/BaseBadge.stories.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +1 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +17 -4
- package/src/components/custom/Workout/IntensityBar.test.tsx +112 -60
- package/src/components/custom/Workout/IntensityBar.tsx +109 -58
- package/src/components/custom/Workout/PlaceholderStrip.tsx +5 -9
- package/src/components/custom/Workout/PrBadge.tsx +3 -3
- package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
- package/src/components/custom/Workout/README.md +50 -0
- package/src/components/custom/Workout/RestTimer.test.tsx +41 -10
- package/src/components/custom/Workout/RestTimer.tsx +48 -43
- package/src/components/custom/Workout/SetRow.tsx +10 -1
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +69 -2
- package/src/components/custom/Workout/TempoDisplay.test.tsx +35 -0
- package/src/components/custom/Workout/TempoDisplay.tsx +104 -1
- package/src/components/custom/Workout/VelocityStrip.test.tsx +89 -2
- package/src/components/custom/Workout/VelocityStrip.tsx +203 -38
- package/src/components/custom/Workout/WeekRow.test.tsx +2 -2
- package/src/components/custom/Workout/WeightBadge.test.tsx +2 -2
- package/src/components/custom/Workout/WeightBadge.tsx +2 -2
- package/src/components/custom/Workout/icons.tsx +68 -0
- package/src/components/custom/Workout/index.ts +38 -47
- package/src/index.ts +3 -2
- package/src/pages.ts +61 -0
- package/src/test/stories-smoke.test.tsx +50 -0
- package/src/theme/barrel.ts +53 -0
- package/src/theme/index.ts +8 -43
- package/src/theme/native-theming-guard.test.ts +117 -0
- package/dist/bodymap-BhG55xHM.d.mts +0 -318
- package/dist/bodymap-BhG55xHM.d.ts +0 -318
- package/dist/chunk-2SISKTWM.mjs +0 -1027
- package/dist/chunk-2SISKTWM.mjs.map +0 -1
- package/dist/chunk-7XPB4NAO.mjs +0 -1013
- package/dist/chunk-7XPB4NAO.mjs.map +0 -1
- package/dist/chunk-SNVKL5WZ.mjs +0 -883
- package/dist/chunk-SNVKL5WZ.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,6 @@ var clsx = require('clsx');
|
|
|
5
5
|
var tailwindMerge = require('tailwind-merge');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
var React24 = require('react');
|
|
8
|
-
var nativewind = require('nativewind');
|
|
9
|
-
var lucideReact = require('lucide-react');
|
|
10
8
|
|
|
11
9
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
10
|
|
|
@@ -2161,18 +2159,6 @@ function validateCssPropertyManifest(candidate) {
|
|
|
2161
2159
|
function isCssPropertyManifest(candidate) {
|
|
2162
2160
|
return validateCssPropertyManifest(candidate).valid;
|
|
2163
2161
|
}
|
|
2164
|
-
var MODE_VARS = {
|
|
2165
|
-
dark: nativewind.vars(darkThemeCSSVars),
|
|
2166
|
-
light: nativewind.vars(lightThemeCSSVars)
|
|
2167
|
-
};
|
|
2168
|
-
function ThemeProvider({
|
|
2169
|
-
mode = "dark",
|
|
2170
|
-
style,
|
|
2171
|
-
children,
|
|
2172
|
-
...props
|
|
2173
|
-
}) {
|
|
2174
|
-
return /* @__PURE__ */ jsx(reactNative.View, { style: [MODE_VARS[mode], { flex: 1 }, style], ...props, children });
|
|
2175
|
-
}
|
|
2176
2162
|
function resolveColor(token, mode = "dark") {
|
|
2177
2163
|
return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
|
|
2178
2164
|
}
|
|
@@ -6768,7 +6754,7 @@ var variantColors = {
|
|
|
6768
6754
|
plain: { backgroundColor: "#1C1C1C", borderColor: "#1F1F1F" },
|
|
6769
6755
|
pr: {
|
|
6770
6756
|
backgroundColor: "rgba(255, 121, 0, 0.12)",
|
|
6771
|
-
borderColor: "rgba(255, 121, 0, 0.
|
|
6757
|
+
borderColor: "rgba(255, 121, 0, 0.3)"
|
|
6772
6758
|
}
|
|
6773
6759
|
};
|
|
6774
6760
|
function BaseBadge({
|
|
@@ -6836,6 +6822,58 @@ function BaseBadge({
|
|
|
6836
6822
|
}
|
|
6837
6823
|
return badge;
|
|
6838
6824
|
}
|
|
6825
|
+
|
|
6826
|
+
// src/components/custom/Workout/icons.tsx
|
|
6827
|
+
function DumbbellIcon({
|
|
6828
|
+
size = 24,
|
|
6829
|
+
color = "currentColor",
|
|
6830
|
+
fill = "none",
|
|
6831
|
+
strokeWidth = 2
|
|
6832
|
+
}) {
|
|
6833
|
+
return /* @__PURE__ */ jsxs(
|
|
6834
|
+
"svg",
|
|
6835
|
+
{
|
|
6836
|
+
width: size,
|
|
6837
|
+
height: size,
|
|
6838
|
+
viewBox: "0 0 24 24",
|
|
6839
|
+
fill,
|
|
6840
|
+
stroke: color,
|
|
6841
|
+
strokeWidth,
|
|
6842
|
+
strokeLinecap: "round",
|
|
6843
|
+
strokeLinejoin: "round",
|
|
6844
|
+
children: [
|
|
6845
|
+
/* @__PURE__ */ jsx("path", { d: "M14.4 14.4 9.6 9.6" }),
|
|
6846
|
+
/* @__PURE__ */ jsx("path", { d: "M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z" }),
|
|
6847
|
+
/* @__PURE__ */ jsx("path", { d: "m21.5 21.5-1.4-1.4" }),
|
|
6848
|
+
/* @__PURE__ */ jsx("path", { d: "M3.9 3.9 2.5 2.5" }),
|
|
6849
|
+
/* @__PURE__ */ jsx("path", { d: "M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z" })
|
|
6850
|
+
]
|
|
6851
|
+
}
|
|
6852
|
+
);
|
|
6853
|
+
}
|
|
6854
|
+
function StarIcon({
|
|
6855
|
+
size = 24,
|
|
6856
|
+
color = "currentColor",
|
|
6857
|
+
fill = "none",
|
|
6858
|
+
strokeWidth = 2
|
|
6859
|
+
}) {
|
|
6860
|
+
return /* @__PURE__ */ jsx(
|
|
6861
|
+
"svg",
|
|
6862
|
+
{
|
|
6863
|
+
width: size,
|
|
6864
|
+
height: size,
|
|
6865
|
+
viewBox: "0 0 24 24",
|
|
6866
|
+
fill,
|
|
6867
|
+
stroke: color,
|
|
6868
|
+
strokeWidth,
|
|
6869
|
+
strokeLinecap: "round",
|
|
6870
|
+
strokeLinejoin: "round",
|
|
6871
|
+
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" })
|
|
6872
|
+
}
|
|
6873
|
+
);
|
|
6874
|
+
}
|
|
6875
|
+
|
|
6876
|
+
// src/components/custom/Workout/WeightBadge.tsx
|
|
6839
6877
|
function WeightBadge({
|
|
6840
6878
|
value,
|
|
6841
6879
|
unit = "lbs",
|
|
@@ -6861,7 +6899,7 @@ function WeightBadge({
|
|
|
6861
6899
|
size,
|
|
6862
6900
|
onPress,
|
|
6863
6901
|
className,
|
|
6864
|
-
icon: showIcon ? /* @__PURE__ */ jsx(
|
|
6902
|
+
icon: showIcon ? /* @__PURE__ */ jsx(DumbbellIcon, { size: config.iconSize, color: iconColor, strokeWidth: 2 }) : void 0,
|
|
6865
6903
|
accessibilityLabel: fullLabel,
|
|
6866
6904
|
testID: "weight-badge",
|
|
6867
6905
|
...props,
|
|
@@ -6965,7 +7003,7 @@ function PrBadge({
|
|
|
6965
7003
|
accessibilityLabel: `Personal record: ${resolvedLabel}`,
|
|
6966
7004
|
testID: "pr-badge-star",
|
|
6967
7005
|
...props,
|
|
6968
|
-
children: /* @__PURE__ */ jsx(
|
|
7006
|
+
children: /* @__PURE__ */ jsx(StarIcon, { size: 14, color: "#FF7900", fill: "#FF7900", strokeWidth: 2 })
|
|
6969
7007
|
}
|
|
6970
7008
|
) : /* @__PURE__ */ jsx(
|
|
6971
7009
|
BaseBadge,
|
|
@@ -6978,7 +7016,7 @@ function PrBadge({
|
|
|
6978
7016
|
reactNative.Text,
|
|
6979
7017
|
{
|
|
6980
7018
|
style: {
|
|
6981
|
-
fontSize:
|
|
7019
|
+
fontSize: 12,
|
|
6982
7020
|
color: "#FF7900",
|
|
6983
7021
|
fontWeight: "700",
|
|
6984
7022
|
fontFamily: "Inter, sans-serif"
|
|
@@ -7129,6 +7167,29 @@ function StatusDot({
|
|
|
7129
7167
|
}
|
|
7130
7168
|
return /* @__PURE__ */ jsx(reactNative.View, { className, ...props, children: dot });
|
|
7131
7169
|
}
|
|
7170
|
+
|
|
7171
|
+
// src/theme/workout-tokens.ts
|
|
7172
|
+
var WORKOUT_TOKENS = {
|
|
7173
|
+
// Canonical 4-band performance scale — the single source for BOTH the
|
|
7174
|
+
// VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
|
|
7175
|
+
// is intentionally inverted between the two consumers: for velocity, green =
|
|
7176
|
+
// fastest/best; for RPE, green = easiest. Each component maps its own
|
|
7177
|
+
// thresholds onto these four colors. Kept as vivid data-viz values (distinct
|
|
7178
|
+
// from the muted status tokens) and theme-independent, so — like the heatmap
|
|
7179
|
+
// scale below — they are consumed inline as a JS import rather than CSS vars.
|
|
7180
|
+
// A JS import also resolves on native RN, unlike var(--…) strings.
|
|
7181
|
+
scale: {
|
|
7182
|
+
green: "#2ed573",
|
|
7183
|
+
yellow: "#ffd43b",
|
|
7184
|
+
orange: "#ffa502",
|
|
7185
|
+
red: "#ff4757"
|
|
7186
|
+
},
|
|
7187
|
+
// Placeholder strip
|
|
7188
|
+
placeholder: {
|
|
7189
|
+
fill: "#3A3A3A"
|
|
7190
|
+
}};
|
|
7191
|
+
|
|
7192
|
+
// src/components/custom/Workout/PlaceholderStrip.tsx
|
|
7132
7193
|
function SingleStrip({
|
|
7133
7194
|
width,
|
|
7134
7195
|
className,
|
|
@@ -7137,14 +7198,12 @@ function SingleStrip({
|
|
|
7137
7198
|
return /* @__PURE__ */ jsx(
|
|
7138
7199
|
reactNative.View,
|
|
7139
7200
|
{
|
|
7140
|
-
className
|
|
7201
|
+
className,
|
|
7141
7202
|
style: [
|
|
7142
7203
|
{
|
|
7143
7204
|
height: 3,
|
|
7144
|
-
backgroundColor:
|
|
7145
|
-
|
|
7146
|
-
borderStyle: "dashed",
|
|
7147
|
-
borderRadius: 1,
|
|
7205
|
+
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
7206
|
+
borderRadius: 2,
|
|
7148
7207
|
opacity: 0.5
|
|
7149
7208
|
},
|
|
7150
7209
|
width != null ? { width } : { flex: 1 }
|
|
@@ -7177,13 +7236,10 @@ function SegmentedStrip({
|
|
|
7177
7236
|
children: Array.from({ length: segments }, (_, i) => /* @__PURE__ */ jsx(
|
|
7178
7237
|
reactNative.View,
|
|
7179
7238
|
{
|
|
7180
|
-
className: "border-border-strong",
|
|
7181
7239
|
style: {
|
|
7182
7240
|
flex: 1,
|
|
7183
7241
|
height: 3,
|
|
7184
|
-
backgroundColor:
|
|
7185
|
-
borderWidth: 1,
|
|
7186
|
-
borderStyle: "dashed",
|
|
7242
|
+
backgroundColor: WORKOUT_TOKENS.placeholder.fill,
|
|
7187
7243
|
borderRadius: 1,
|
|
7188
7244
|
minWidth: 4
|
|
7189
7245
|
},
|
|
@@ -7206,23 +7262,6 @@ function PlaceholderStrip({
|
|
|
7206
7262
|
return /* @__PURE__ */ jsx(SingleStrip, { ...props });
|
|
7207
7263
|
}
|
|
7208
7264
|
|
|
7209
|
-
// src/theme/workout-tokens.ts
|
|
7210
|
-
var WORKOUT_TOKENS = {
|
|
7211
|
-
// Canonical 4-band performance scale — the single source for BOTH the
|
|
7212
|
-
// VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
|
|
7213
|
-
// is intentionally inverted between the two consumers: for velocity, green =
|
|
7214
|
-
// fastest/best; for RPE, green = easiest. Each component maps its own
|
|
7215
|
-
// thresholds onto these four colors. Kept as vivid data-viz values (distinct
|
|
7216
|
-
// from the muted status tokens) and theme-independent, so — like the heatmap
|
|
7217
|
-
// scale below — they are consumed inline as a JS import rather than CSS vars.
|
|
7218
|
-
// A JS import also resolves on native RN, unlike var(--…) strings.
|
|
7219
|
-
scale: {
|
|
7220
|
-
green: "#2ed573",
|
|
7221
|
-
yellow: "#ffd43b",
|
|
7222
|
-
orange: "#ffa502",
|
|
7223
|
-
red: "#ff4757"
|
|
7224
|
-
}};
|
|
7225
|
-
|
|
7226
7265
|
// src/utils/workout-format.ts
|
|
7227
7266
|
function roundRpe(rpe) {
|
|
7228
7267
|
return Math.round(rpe * 2) / 2;
|
|
@@ -7261,302 +7300,90 @@ function formatPrescription(p) {
|
|
|
7261
7300
|
return full.length > 0 ? full : null;
|
|
7262
7301
|
}
|
|
7263
7302
|
|
|
7264
|
-
// src/
|
|
7265
|
-
|
|
7266
|
-
var
|
|
7267
|
-
var phaseColors = {
|
|
7268
|
-
eccentric: "#FFB020",
|
|
7269
|
-
// status-warning (amber)
|
|
7270
|
-
pauseBottom: TEXT_TERTIARY,
|
|
7271
|
-
concentric: "#14B8A6",
|
|
7272
|
-
// status-success (teal)
|
|
7273
|
-
pauseTop: TEXT_TERTIARY,
|
|
7274
|
-
dash: TEXT_TERTIARY
|
|
7275
|
-
};
|
|
7276
|
-
function TempoValue({
|
|
7277
|
-
value,
|
|
7278
|
-
color,
|
|
7279
|
-
fontSize
|
|
7280
|
-
}) {
|
|
7281
|
-
return /* @__PURE__ */ jsx(
|
|
7282
|
-
reactNative.Text,
|
|
7283
|
-
{
|
|
7284
|
-
style: {
|
|
7285
|
-
fontFamily: INTER,
|
|
7286
|
-
fontSize,
|
|
7287
|
-
color,
|
|
7288
|
-
fontWeight: "600",
|
|
7289
|
-
letterSpacing: 1
|
|
7290
|
-
},
|
|
7291
|
-
children: value
|
|
7292
|
-
}
|
|
7293
|
-
);
|
|
7303
|
+
// src/utils/colors.ts
|
|
7304
|
+
function getStatusColor(status) {
|
|
7305
|
+
return `var(--color-status-${status})`;
|
|
7294
7306
|
}
|
|
7295
|
-
function
|
|
7296
|
-
return
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
+
function getResultColor(result) {
|
|
7308
|
+
return `var(--color-result-${result})`;
|
|
7309
|
+
}
|
|
7310
|
+
function alpha(color, opacity) {
|
|
7311
|
+
const clampedOpacity = Math.max(0, Math.min(1, opacity));
|
|
7312
|
+
if (color.startsWith("#")) {
|
|
7313
|
+
const hex = color.slice(1);
|
|
7314
|
+
let r, g, b;
|
|
7315
|
+
if (hex.length === 3) {
|
|
7316
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
7317
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
7318
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
7319
|
+
} else if (hex.length === 6) {
|
|
7320
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
7321
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
7322
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
7323
|
+
} else {
|
|
7324
|
+
return color;
|
|
7307
7325
|
}
|
|
7308
|
-
|
|
7326
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
7327
|
+
}
|
|
7328
|
+
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
7329
|
+
if (rgbMatch) {
|
|
7330
|
+
const [, r, g, b] = rgbMatch;
|
|
7331
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
7332
|
+
}
|
|
7333
|
+
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
7334
|
+
if (rgbaMatch) {
|
|
7335
|
+
const [, r, g, b] = rgbaMatch;
|
|
7336
|
+
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
7337
|
+
}
|
|
7338
|
+
return color;
|
|
7309
7339
|
}
|
|
7310
|
-
function
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
}) {
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
},
|
|
7358
|
-
children: "TEMPO"
|
|
7359
|
-
}
|
|
7360
|
-
),
|
|
7361
|
-
/* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row" }, testID: "tempo-value", children: colored ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
7362
|
-
/* @__PURE__ */ jsx(TempoValue, { value: eccentric, color: phaseColors.eccentric, fontSize }),
|
|
7363
|
-
/* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
|
|
7364
|
-
/* @__PURE__ */ jsx(TempoValue, { value: pauseBottom, color: phaseColors.pauseBottom, fontSize }),
|
|
7365
|
-
/* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
|
|
7366
|
-
/* @__PURE__ */ jsx(TempoValue, { value: concentric, color: phaseColors.concentric, fontSize }),
|
|
7367
|
-
/* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
|
|
7368
|
-
/* @__PURE__ */ jsx(TempoValue, { value: pauseTop, color: phaseColors.pauseTop, fontSize })
|
|
7369
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
7370
|
-
reactNative.Text,
|
|
7371
|
-
{
|
|
7372
|
-
style: {
|
|
7373
|
-
fontFamily: INTER,
|
|
7374
|
-
fontSize,
|
|
7375
|
-
color: monoColor,
|
|
7376
|
-
fontWeight: "600",
|
|
7377
|
-
letterSpacing: 1
|
|
7378
|
-
},
|
|
7379
|
-
children: [
|
|
7380
|
-
eccentric,
|
|
7381
|
-
"-",
|
|
7382
|
-
pauseBottom,
|
|
7383
|
-
"-",
|
|
7384
|
-
concentric,
|
|
7385
|
-
"-",
|
|
7386
|
-
pauseTop
|
|
7387
|
-
]
|
|
7388
|
-
}
|
|
7389
|
-
) })
|
|
7390
|
-
]
|
|
7391
|
-
}
|
|
7392
|
-
);
|
|
7393
|
-
return /* @__PURE__ */ jsxs(
|
|
7394
|
-
reactNative.Pressable,
|
|
7395
|
-
{
|
|
7396
|
-
accessibilityLabel: `Tempo: ${eccentric} second eccentric, ${pauseBottom} second pause, ${concentric} second concentric, ${pauseTop} second pause`,
|
|
7397
|
-
accessibilityRole: "button",
|
|
7398
|
-
onPress: handlePress,
|
|
7399
|
-
testID: "tempo-display",
|
|
7400
|
-
children: [
|
|
7401
|
-
content,
|
|
7402
|
-
showTooltip && /* @__PURE__ */ jsxs(
|
|
7403
|
-
reactNative.View,
|
|
7404
|
-
{
|
|
7405
|
-
style: {
|
|
7406
|
-
position: "absolute",
|
|
7407
|
-
bottom: "100%",
|
|
7408
|
-
left: "50%",
|
|
7409
|
-
transform: [{ translateX: "-50%" }],
|
|
7410
|
-
marginBottom: 8,
|
|
7411
|
-
zIndex: 20,
|
|
7412
|
-
alignItems: "center"
|
|
7413
|
-
},
|
|
7414
|
-
testID: "tempo-tooltip",
|
|
7415
|
-
children: [
|
|
7416
|
-
/* @__PURE__ */ jsxs(
|
|
7417
|
-
reactNative.View,
|
|
7418
|
-
{
|
|
7419
|
-
style: {
|
|
7420
|
-
backgroundColor: "#191919",
|
|
7421
|
-
borderRadius: 6,
|
|
7422
|
-
paddingVertical: 8,
|
|
7423
|
-
paddingHorizontal: 12,
|
|
7424
|
-
borderWidth: 1,
|
|
7425
|
-
borderColor: "#2C2C2C"
|
|
7426
|
-
},
|
|
7427
|
-
children: [
|
|
7428
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7429
|
-
"Eccentric: ",
|
|
7430
|
-
eccentric,
|
|
7431
|
-
"s"
|
|
7432
|
-
] }),
|
|
7433
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7434
|
-
"Pause (bottom): ",
|
|
7435
|
-
pauseBottom,
|
|
7436
|
-
"s"
|
|
7437
|
-
] }),
|
|
7438
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7439
|
-
"Concentric: ",
|
|
7440
|
-
concentric,
|
|
7441
|
-
"s"
|
|
7442
|
-
] }),
|
|
7443
|
-
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7444
|
-
"Pause (top): ",
|
|
7445
|
-
pauseTop,
|
|
7446
|
-
"s"
|
|
7447
|
-
] })
|
|
7448
|
-
]
|
|
7449
|
-
}
|
|
7450
|
-
),
|
|
7451
|
-
/* @__PURE__ */ jsx(
|
|
7452
|
-
reactNative.View,
|
|
7453
|
-
{
|
|
7454
|
-
style: {
|
|
7455
|
-
width: 0,
|
|
7456
|
-
height: 0,
|
|
7457
|
-
borderLeftWidth: 5,
|
|
7458
|
-
borderRightWidth: 5,
|
|
7459
|
-
borderTopWidth: 5,
|
|
7460
|
-
borderLeftColor: "transparent",
|
|
7461
|
-
borderRightColor: "transparent",
|
|
7462
|
-
borderTopColor: "#2C2C2C"
|
|
7463
|
-
}
|
|
7464
|
-
}
|
|
7465
|
-
)
|
|
7466
|
-
]
|
|
7467
|
-
}
|
|
7468
|
-
)
|
|
7469
|
-
]
|
|
7470
|
-
}
|
|
7471
|
-
);
|
|
7472
|
-
}
|
|
7473
|
-
|
|
7474
|
-
// src/utils/colors.ts
|
|
7475
|
-
function getStatusColor(status) {
|
|
7476
|
-
return `var(--color-status-${status})`;
|
|
7477
|
-
}
|
|
7478
|
-
function getResultColor(result) {
|
|
7479
|
-
return `var(--color-result-${result})`;
|
|
7480
|
-
}
|
|
7481
|
-
function alpha(color, opacity) {
|
|
7482
|
-
const clampedOpacity = Math.max(0, Math.min(1, opacity));
|
|
7483
|
-
if (color.startsWith("#")) {
|
|
7484
|
-
const hex = color.slice(1);
|
|
7485
|
-
let r, g, b;
|
|
7486
|
-
if (hex.length === 3) {
|
|
7487
|
-
r = parseInt(hex[0] + hex[0], 16);
|
|
7488
|
-
g = parseInt(hex[1] + hex[1], 16);
|
|
7489
|
-
b = parseInt(hex[2] + hex[2], 16);
|
|
7490
|
-
} else if (hex.length === 6) {
|
|
7491
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
7492
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
7493
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
7494
|
-
} else {
|
|
7495
|
-
return color;
|
|
7496
|
-
}
|
|
7497
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
7498
|
-
}
|
|
7499
|
-
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
7500
|
-
if (rgbMatch) {
|
|
7501
|
-
const [, r, g, b] = rgbMatch;
|
|
7502
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
7503
|
-
}
|
|
7504
|
-
const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
|
|
7505
|
-
if (rgbaMatch) {
|
|
7506
|
-
const [, r, g, b] = rgbaMatch;
|
|
7507
|
-
return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
|
|
7508
|
-
}
|
|
7509
|
-
return color;
|
|
7510
|
-
}
|
|
7511
|
-
function getLuminance(color) {
|
|
7512
|
-
if (!color.startsWith("#")) return "dark";
|
|
7513
|
-
const hex = color.slice(1);
|
|
7514
|
-
let r, g, b;
|
|
7515
|
-
if (hex.length === 3) {
|
|
7516
|
-
r = parseInt(hex[0] + hex[0], 16);
|
|
7517
|
-
g = parseInt(hex[1] + hex[1], 16);
|
|
7518
|
-
b = parseInt(hex[2] + hex[2], 16);
|
|
7519
|
-
} else if (hex.length === 6) {
|
|
7520
|
-
r = parseInt(hex.slice(0, 2), 16);
|
|
7521
|
-
g = parseInt(hex.slice(2, 4), 16);
|
|
7522
|
-
b = parseInt(hex.slice(4, 6), 16);
|
|
7523
|
-
} else {
|
|
7524
|
-
return "dark";
|
|
7525
|
-
}
|
|
7526
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
7527
|
-
return luminance > 0.5 ? "light" : "dark";
|
|
7528
|
-
}
|
|
7529
|
-
function getContrastText(backgroundColor) {
|
|
7530
|
-
return getLuminance(backgroundColor) === "light" ? "#000000" : "#FFFFFF";
|
|
7531
|
-
}
|
|
7532
|
-
|
|
7533
|
-
// src/components/custom/Workout/TempoBar.tsx
|
|
7534
|
-
var t = getSemanticColors("dark");
|
|
7535
|
-
var TEMPO_PACING = {
|
|
7536
|
-
behindThresholdPct: 0.15,
|
|
7537
|
-
minPhaseDurationMs: 500
|
|
7538
|
-
};
|
|
7539
|
-
function getTempoPacingState(elapsedMs, targetMs) {
|
|
7540
|
-
if (!targetMs || targetMs < TEMPO_PACING.minPhaseDurationMs) return "none";
|
|
7541
|
-
const ratio = elapsedMs / targetMs;
|
|
7542
|
-
if (ratio > 1 + TEMPO_PACING.behindThresholdPct) return "behind";
|
|
7543
|
-
return "on-pace";
|
|
7544
|
-
}
|
|
7545
|
-
function getTempoFillPct(elapsedMs, targetMs) {
|
|
7546
|
-
if (!targetMs) return 100;
|
|
7547
|
-
return Math.min(100, elapsedMs / targetMs * 100);
|
|
7548
|
-
}
|
|
7549
|
-
var PHASE_ORDER = ["concentric", "hold", "eccentric"];
|
|
7550
|
-
var PHASE_CONFIG = {
|
|
7551
|
-
concentric: { label: "Con", color: t["status-success"], flex: 2 },
|
|
7552
|
-
hold: { label: "Hold", color: t["brand-primary"], flex: 1 },
|
|
7553
|
-
eccentric: { label: "Ecc", color: t["status-warning"], flex: 3 }
|
|
7554
|
-
};
|
|
7555
|
-
function TempoBar({
|
|
7556
|
-
activePhase,
|
|
7557
|
-
phaseElapsedMs,
|
|
7558
|
-
completed,
|
|
7559
|
-
target,
|
|
7340
|
+
function getLuminance(color) {
|
|
7341
|
+
if (!color.startsWith("#")) return "dark";
|
|
7342
|
+
const hex = color.slice(1);
|
|
7343
|
+
let r, g, b;
|
|
7344
|
+
if (hex.length === 3) {
|
|
7345
|
+
r = parseInt(hex[0] + hex[0], 16);
|
|
7346
|
+
g = parseInt(hex[1] + hex[1], 16);
|
|
7347
|
+
b = parseInt(hex[2] + hex[2], 16);
|
|
7348
|
+
} else if (hex.length === 6) {
|
|
7349
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
7350
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
7351
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
7352
|
+
} else {
|
|
7353
|
+
return "dark";
|
|
7354
|
+
}
|
|
7355
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
7356
|
+
return luminance > 0.5 ? "light" : "dark";
|
|
7357
|
+
}
|
|
7358
|
+
function getContrastText(backgroundColor) {
|
|
7359
|
+
return getLuminance(backgroundColor) === "light" ? "#000000" : "#FFFFFF";
|
|
7360
|
+
}
|
|
7361
|
+
var t = getSemanticColors("dark");
|
|
7362
|
+
var TEMPO_PACING = {
|
|
7363
|
+
behindThresholdPct: 0.15,
|
|
7364
|
+
minPhaseDurationMs: 500
|
|
7365
|
+
};
|
|
7366
|
+
function getTempoPacingState(elapsedMs, targetMs) {
|
|
7367
|
+
if (!targetMs || targetMs < TEMPO_PACING.minPhaseDurationMs) return "none";
|
|
7368
|
+
const ratio = elapsedMs / targetMs;
|
|
7369
|
+
if (ratio > 1 + TEMPO_PACING.behindThresholdPct) return "behind";
|
|
7370
|
+
return "on-pace";
|
|
7371
|
+
}
|
|
7372
|
+
function getTempoFillPct(elapsedMs, targetMs) {
|
|
7373
|
+
if (!targetMs) return 100;
|
|
7374
|
+
return Math.min(100, elapsedMs / targetMs * 100);
|
|
7375
|
+
}
|
|
7376
|
+
var PHASE_ORDER = ["concentric", "hold", "eccentric"];
|
|
7377
|
+
var PHASE_CONFIG = {
|
|
7378
|
+
concentric: { label: "Con", color: t["status-success"], flex: 2 },
|
|
7379
|
+
hold: { label: "Hold", color: t["brand-primary"], flex: 1 },
|
|
7380
|
+
eccentric: { label: "Ecc", color: t["status-warning"], flex: 3 }
|
|
7381
|
+
};
|
|
7382
|
+
function TempoBar({
|
|
7383
|
+
activePhase,
|
|
7384
|
+
phaseElapsedMs,
|
|
7385
|
+
completed,
|
|
7386
|
+
target,
|
|
7560
7387
|
className,
|
|
7561
7388
|
...props
|
|
7562
7389
|
}) {
|
|
@@ -7662,31 +7489,306 @@ function formatDuration(ms) {
|
|
|
7662
7489
|
const s = ms / 1e3;
|
|
7663
7490
|
return s < 10 ? s.toFixed(1) : Math.round(s).toString();
|
|
7664
7491
|
}
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7492
|
+
|
|
7493
|
+
// src/components/custom/Workout/TempoDisplay.tsx
|
|
7494
|
+
var INTER = "Inter, sans-serif";
|
|
7495
|
+
var TEXT_TERTIARY = "#6B7280";
|
|
7496
|
+
var STATUS_ERROR = "#D14343";
|
|
7497
|
+
var phaseColors = {
|
|
7498
|
+
eccentric: "#FFB020",
|
|
7499
|
+
// status-warning (amber)
|
|
7500
|
+
pauseBottom: TEXT_TERTIARY,
|
|
7501
|
+
concentric: "#14B8A6",
|
|
7502
|
+
// status-success (teal)
|
|
7503
|
+
pauseTop: TEXT_TERTIARY,
|
|
7504
|
+
dash: TEXT_TERTIARY
|
|
7505
|
+
};
|
|
7506
|
+
function TempoValue({
|
|
7507
|
+
value,
|
|
7508
|
+
color,
|
|
7509
|
+
fontSize
|
|
7510
|
+
}) {
|
|
7511
|
+
return /* @__PURE__ */ jsx(
|
|
7512
|
+
reactNative.Text,
|
|
7513
|
+
{
|
|
7514
|
+
style: {
|
|
7515
|
+
fontFamily: INTER,
|
|
7516
|
+
fontSize,
|
|
7517
|
+
color,
|
|
7518
|
+
fontWeight: "600",
|
|
7519
|
+
letterSpacing: 1
|
|
7520
|
+
},
|
|
7521
|
+
children: value
|
|
7522
|
+
}
|
|
7523
|
+
);
|
|
7671
7524
|
}
|
|
7672
|
-
function
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7525
|
+
function TempoSeparator({ color, fontSize }) {
|
|
7526
|
+
return /* @__PURE__ */ jsx(
|
|
7527
|
+
reactNative.Text,
|
|
7528
|
+
{
|
|
7529
|
+
style: {
|
|
7530
|
+
fontFamily: INTER,
|
|
7531
|
+
fontSize,
|
|
7532
|
+
color,
|
|
7533
|
+
fontWeight: "600",
|
|
7534
|
+
letterSpacing: 1
|
|
7535
|
+
},
|
|
7536
|
+
children: "-"
|
|
7537
|
+
}
|
|
7538
|
+
);
|
|
7676
7539
|
}
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7540
|
+
var LIVE_PHASES = [
|
|
7541
|
+
{ key: "eccentric", color: phaseColors.eccentric },
|
|
7542
|
+
{ key: "pauseBottom", color: phaseColors.pauseBottom },
|
|
7543
|
+
{ key: "concentric", color: phaseColors.concentric },
|
|
7544
|
+
{ key: "pauseTop", color: phaseColors.pauseTop }
|
|
7545
|
+
];
|
|
7546
|
+
function LiveTempoCell({
|
|
7547
|
+
value,
|
|
7548
|
+
color,
|
|
7549
|
+
isActive,
|
|
7550
|
+
phaseElapsedMs,
|
|
7551
|
+
fontSize
|
|
7682
7552
|
}) {
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
const
|
|
7687
|
-
const
|
|
7688
|
-
const
|
|
7689
|
-
|
|
7553
|
+
if (!isActive) {
|
|
7554
|
+
return /* @__PURE__ */ jsx(TempoValue, { value, color: alpha(color, 0.35), fontSize });
|
|
7555
|
+
}
|
|
7556
|
+
const targetMs = value > 0 ? value * 1e3 : null;
|
|
7557
|
+
const barColor = getTempoPacingState(phaseElapsedMs, targetMs) === "behind" ? STATUS_ERROR : color;
|
|
7558
|
+
const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
|
|
7559
|
+
return /* @__PURE__ */ jsxs(reactNative.View, { style: { position: "relative" }, testID: "tempo-live-active", children: [
|
|
7560
|
+
/* @__PURE__ */ jsx(
|
|
7561
|
+
reactNative.View,
|
|
7562
|
+
{
|
|
7563
|
+
style: {
|
|
7564
|
+
position: "absolute",
|
|
7565
|
+
left: 0,
|
|
7566
|
+
top: 0,
|
|
7567
|
+
bottom: 0,
|
|
7568
|
+
width: `${fillPct}%`,
|
|
7569
|
+
backgroundColor: alpha(barColor, 0.3),
|
|
7570
|
+
borderRadius: 2
|
|
7571
|
+
}
|
|
7572
|
+
}
|
|
7573
|
+
),
|
|
7574
|
+
/* @__PURE__ */ jsx(TempoValue, { value, color: barColor, fontSize })
|
|
7575
|
+
] });
|
|
7576
|
+
}
|
|
7577
|
+
function LiveTempoRow({
|
|
7578
|
+
values,
|
|
7579
|
+
live,
|
|
7580
|
+
fontSize
|
|
7581
|
+
}) {
|
|
7582
|
+
return /* @__PURE__ */ jsx(jsxRuntime.Fragment, { children: LIVE_PHASES.map((phase, i) => /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row" }, children: [
|
|
7583
|
+
i > 0 && /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
|
|
7584
|
+
/* @__PURE__ */ jsx(
|
|
7585
|
+
LiveTempoCell,
|
|
7586
|
+
{
|
|
7587
|
+
value: values[i],
|
|
7588
|
+
color: phase.color,
|
|
7589
|
+
isActive: live.activePhase === phase.key,
|
|
7590
|
+
phaseElapsedMs: live.phaseElapsedMs,
|
|
7591
|
+
fontSize
|
|
7592
|
+
}
|
|
7593
|
+
)
|
|
7594
|
+
] }, phase.key)) });
|
|
7595
|
+
}
|
|
7596
|
+
function TempoDisplay({
|
|
7597
|
+
tempo,
|
|
7598
|
+
size = "md",
|
|
7599
|
+
colored = true,
|
|
7600
|
+
showInfo = true,
|
|
7601
|
+
live,
|
|
7602
|
+
onPress,
|
|
7603
|
+
className,
|
|
7604
|
+
...props
|
|
7605
|
+
}) {
|
|
7606
|
+
const [showTooltip, setShowTooltip] = React24.useState(false);
|
|
7607
|
+
const [eccentric, pauseBottom, concentric, pauseTop] = roundTempo(tempo);
|
|
7608
|
+
const isSm = size === "sm";
|
|
7609
|
+
const fontSize = isSm ? 9 : 11;
|
|
7610
|
+
const monoColor = TEXT_TERTIARY;
|
|
7611
|
+
const handlePress = React24.useCallback(() => {
|
|
7612
|
+
if (onPress) {
|
|
7613
|
+
onPress();
|
|
7614
|
+
}
|
|
7615
|
+
if (showInfo) {
|
|
7616
|
+
setShowTooltip((prev) => !prev);
|
|
7617
|
+
}
|
|
7618
|
+
}, [onPress, showInfo]);
|
|
7619
|
+
const content = /* @__PURE__ */ jsxs(
|
|
7620
|
+
reactNative.View,
|
|
7621
|
+
{
|
|
7622
|
+
className,
|
|
7623
|
+
style: {
|
|
7624
|
+
flexDirection: "row",
|
|
7625
|
+
alignItems: "center",
|
|
7626
|
+
backgroundColor: "#1C1C1C",
|
|
7627
|
+
paddingHorizontal: isSm ? 6 : 8,
|
|
7628
|
+
paddingVertical: 3,
|
|
7629
|
+
borderRadius: 4
|
|
7630
|
+
},
|
|
7631
|
+
...props,
|
|
7632
|
+
children: [
|
|
7633
|
+
/* @__PURE__ */ jsx(
|
|
7634
|
+
reactNative.Text,
|
|
7635
|
+
{
|
|
7636
|
+
style: {
|
|
7637
|
+
fontFamily: INTER,
|
|
7638
|
+
fontSize: 9,
|
|
7639
|
+
fontWeight: "500",
|
|
7640
|
+
color: TEXT_TERTIARY,
|
|
7641
|
+
letterSpacing: 0.5,
|
|
7642
|
+
textTransform: "uppercase",
|
|
7643
|
+
marginRight: 6
|
|
7644
|
+
},
|
|
7645
|
+
children: "TEMPO"
|
|
7646
|
+
}
|
|
7647
|
+
),
|
|
7648
|
+
/* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row" }, testID: "tempo-value", children: live ? /* @__PURE__ */ jsx(
|
|
7649
|
+
LiveTempoRow,
|
|
7650
|
+
{
|
|
7651
|
+
values: [eccentric, pauseBottom, concentric, pauseTop],
|
|
7652
|
+
live,
|
|
7653
|
+
fontSize
|
|
7654
|
+
}
|
|
7655
|
+
) : colored ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
|
|
7656
|
+
/* @__PURE__ */ jsx(TempoValue, { value: eccentric, color: phaseColors.eccentric, fontSize }),
|
|
7657
|
+
/* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
|
|
7658
|
+
/* @__PURE__ */ jsx(TempoValue, { value: pauseBottom, color: phaseColors.pauseBottom, fontSize }),
|
|
7659
|
+
/* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
|
|
7660
|
+
/* @__PURE__ */ jsx(TempoValue, { value: concentric, color: phaseColors.concentric, fontSize }),
|
|
7661
|
+
/* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
|
|
7662
|
+
/* @__PURE__ */ jsx(TempoValue, { value: pauseTop, color: phaseColors.pauseTop, fontSize })
|
|
7663
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
7664
|
+
reactNative.Text,
|
|
7665
|
+
{
|
|
7666
|
+
style: {
|
|
7667
|
+
fontFamily: INTER,
|
|
7668
|
+
fontSize,
|
|
7669
|
+
color: monoColor,
|
|
7670
|
+
fontWeight: "600",
|
|
7671
|
+
letterSpacing: 1
|
|
7672
|
+
},
|
|
7673
|
+
children: [
|
|
7674
|
+
eccentric,
|
|
7675
|
+
"-",
|
|
7676
|
+
pauseBottom,
|
|
7677
|
+
"-",
|
|
7678
|
+
concentric,
|
|
7679
|
+
"-",
|
|
7680
|
+
pauseTop
|
|
7681
|
+
]
|
|
7682
|
+
}
|
|
7683
|
+
) })
|
|
7684
|
+
]
|
|
7685
|
+
}
|
|
7686
|
+
);
|
|
7687
|
+
return /* @__PURE__ */ jsxs(
|
|
7688
|
+
reactNative.Pressable,
|
|
7689
|
+
{
|
|
7690
|
+
accessibilityLabel: `Tempo: ${eccentric} second eccentric, ${pauseBottom} second pause, ${concentric} second concentric, ${pauseTop} second pause`,
|
|
7691
|
+
accessibilityRole: "button",
|
|
7692
|
+
onPress: handlePress,
|
|
7693
|
+
testID: "tempo-display",
|
|
7694
|
+
children: [
|
|
7695
|
+
content,
|
|
7696
|
+
showTooltip && /* @__PURE__ */ jsxs(
|
|
7697
|
+
reactNative.View,
|
|
7698
|
+
{
|
|
7699
|
+
style: {
|
|
7700
|
+
position: "absolute",
|
|
7701
|
+
bottom: "100%",
|
|
7702
|
+
left: "50%",
|
|
7703
|
+
transform: [{ translateX: "-50%" }],
|
|
7704
|
+
marginBottom: 8,
|
|
7705
|
+
zIndex: 20,
|
|
7706
|
+
alignItems: "center"
|
|
7707
|
+
},
|
|
7708
|
+
testID: "tempo-tooltip",
|
|
7709
|
+
children: [
|
|
7710
|
+
/* @__PURE__ */ jsxs(
|
|
7711
|
+
reactNative.View,
|
|
7712
|
+
{
|
|
7713
|
+
style: {
|
|
7714
|
+
backgroundColor: "#191919",
|
|
7715
|
+
borderRadius: 6,
|
|
7716
|
+
paddingVertical: 8,
|
|
7717
|
+
paddingHorizontal: 12,
|
|
7718
|
+
borderWidth: 1,
|
|
7719
|
+
borderColor: "#2C2C2C"
|
|
7720
|
+
},
|
|
7721
|
+
children: [
|
|
7722
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7723
|
+
"Eccentric: ",
|
|
7724
|
+
eccentric,
|
|
7725
|
+
"s"
|
|
7726
|
+
] }),
|
|
7727
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7728
|
+
"Pause (bottom): ",
|
|
7729
|
+
pauseBottom,
|
|
7730
|
+
"s"
|
|
7731
|
+
] }),
|
|
7732
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7733
|
+
"Concentric: ",
|
|
7734
|
+
concentric,
|
|
7735
|
+
"s"
|
|
7736
|
+
] }),
|
|
7737
|
+
/* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 10, lineHeight: 16, color: "#9CA3AF", fontFamily: INTER }, children: [
|
|
7738
|
+
"Pause (top): ",
|
|
7739
|
+
pauseTop,
|
|
7740
|
+
"s"
|
|
7741
|
+
] })
|
|
7742
|
+
]
|
|
7743
|
+
}
|
|
7744
|
+
),
|
|
7745
|
+
/* @__PURE__ */ jsx(
|
|
7746
|
+
reactNative.View,
|
|
7747
|
+
{
|
|
7748
|
+
style: {
|
|
7749
|
+
width: 0,
|
|
7750
|
+
height: 0,
|
|
7751
|
+
borderLeftWidth: 5,
|
|
7752
|
+
borderRightWidth: 5,
|
|
7753
|
+
borderTopWidth: 5,
|
|
7754
|
+
borderLeftColor: "transparent",
|
|
7755
|
+
borderRightColor: "transparent",
|
|
7756
|
+
borderTopColor: "#2C2C2C"
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
7759
|
+
)
|
|
7760
|
+
]
|
|
7761
|
+
}
|
|
7762
|
+
)
|
|
7763
|
+
]
|
|
7764
|
+
}
|
|
7765
|
+
);
|
|
7766
|
+
}
|
|
7767
|
+
function getDotColor(deviation) {
|
|
7768
|
+
const abs = Math.abs(deviation);
|
|
7769
|
+
if (deviation < -0.3) return "#14B8A6";
|
|
7770
|
+
if (abs <= 0.3) return "#6B7280";
|
|
7771
|
+
if (abs <= 0.7) return "#FFB020";
|
|
7772
|
+
return "#D14343";
|
|
7773
|
+
}
|
|
7774
|
+
function getDeviationDescription(deviation) {
|
|
7775
|
+
if (deviation < -0.3) return "lighter than planned";
|
|
7776
|
+
if (deviation > 0.3) return "harder than planned";
|
|
7777
|
+
return "on plan";
|
|
7778
|
+
}
|
|
7779
|
+
function DeviationBar({
|
|
7780
|
+
deviation,
|
|
7781
|
+
width,
|
|
7782
|
+
className,
|
|
7783
|
+
...props
|
|
7784
|
+
}) {
|
|
7785
|
+
const clamped = Math.max(-1, Math.min(1, deviation));
|
|
7786
|
+
const resolvedWidth = width ?? 40;
|
|
7787
|
+
const dotPosition = (clamped + 1) / 2 * resolvedWidth;
|
|
7788
|
+
const dotSize = 8;
|
|
7789
|
+
const trackHeight = 6;
|
|
7790
|
+
const containerHeight = 10;
|
|
7791
|
+
const valueNow = Math.round(clamped * 100);
|
|
7690
7792
|
return /* @__PURE__ */ jsxs(
|
|
7691
7793
|
reactNative.View,
|
|
7692
7794
|
{
|
|
@@ -7735,19 +7837,42 @@ function DeviationBar({
|
|
|
7735
7837
|
}
|
|
7736
7838
|
);
|
|
7737
7839
|
}
|
|
7738
|
-
var TRACK_BG = "
|
|
7739
|
-
var THRESHOLD_COLOR = "#FFFFFF";
|
|
7840
|
+
var TRACK_BG = "#333333";
|
|
7740
7841
|
var LABEL_COLOR = "#6B7280";
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7842
|
+
var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
|
|
7843
|
+
var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
|
|
7844
|
+
var ZONE = {
|
|
7845
|
+
building: "#14B8A6",
|
|
7846
|
+
// teal — below target ramp-up
|
|
7847
|
+
approaching: "#FFB020",
|
|
7848
|
+
// amber — nearing target
|
|
7849
|
+
target: "#FF7900",
|
|
7850
|
+
// brand — exactly at target
|
|
7851
|
+
over1: "#D14343",
|
|
7852
|
+
// red — mild over-target
|
|
7853
|
+
over2: "#A62626",
|
|
7854
|
+
// deep red — moderate over-target
|
|
7855
|
+
over3: "#7A1C1C"
|
|
7856
|
+
// darkest red — severe over-target
|
|
7857
|
+
};
|
|
7858
|
+
var BULGE_SIZE = { 1: 8, 2: 10, 3: 11 };
|
|
7859
|
+
function zoneForPct(pct) {
|
|
7860
|
+
if (pct >= 125) return { color: ZONE.over3, over: 3 };
|
|
7861
|
+
if (pct >= 115) return { color: ZONE.over2, over: 2 };
|
|
7862
|
+
if (pct > 100) return { color: ZONE.over1, over: 1 };
|
|
7863
|
+
if (pct === 100) return { color: ZONE.target, over: 0 };
|
|
7864
|
+
if (pct >= 75) return { color: ZONE.approaching, over: 0 };
|
|
7865
|
+
return { color: ZONE.building, over: 0 };
|
|
7866
|
+
}
|
|
7867
|
+
function clampFill(value) {
|
|
7747
7868
|
if (value < 0) return 0;
|
|
7748
7869
|
if (value > 1) return 1;
|
|
7749
7870
|
return value;
|
|
7750
7871
|
}
|
|
7872
|
+
function isAtTarget(pct, threshold) {
|
|
7873
|
+
if (threshold == null) return false;
|
|
7874
|
+
return Math.abs(pct - Math.round(threshold * 100)) <= 5;
|
|
7875
|
+
}
|
|
7751
7876
|
function IntensityBar({
|
|
7752
7877
|
level,
|
|
7753
7878
|
threshold,
|
|
@@ -7759,62 +7884,62 @@ function IntensityBar({
|
|
|
7759
7884
|
...props
|
|
7760
7885
|
}) {
|
|
7761
7886
|
const isVertical = orientation === "vertical";
|
|
7762
|
-
const
|
|
7763
|
-
const
|
|
7764
|
-
const
|
|
7765
|
-
const
|
|
7887
|
+
const fillLevel = clampFill(level);
|
|
7888
|
+
const pct = Math.round(Math.max(0, level) * 100);
|
|
7889
|
+
const zone = zoneForPct(pct);
|
|
7890
|
+
const atTarget = isAtTarget(pct, threshold);
|
|
7891
|
+
const [fillAnim] = React24.useState(() => new reactNative.Animated.Value(fillLevel));
|
|
7766
7892
|
React24.useEffect(() => {
|
|
7767
7893
|
const animation = reactNative.Animated.timing(fillAnim, {
|
|
7768
|
-
toValue:
|
|
7894
|
+
toValue: fillLevel,
|
|
7769
7895
|
duration: 250,
|
|
7770
7896
|
useNativeDriver: false
|
|
7771
7897
|
});
|
|
7772
7898
|
animation.start();
|
|
7773
7899
|
return () => animation.stop();
|
|
7774
|
-
}, [
|
|
7900
|
+
}, [fillLevel, fillAnim]);
|
|
7775
7901
|
const fillSizePercent = fillAnim.interpolate({
|
|
7776
7902
|
inputRange: [0, 1],
|
|
7777
7903
|
outputRange: ["0%", "100%"]
|
|
7778
7904
|
});
|
|
7779
7905
|
const trackStyle = isVertical ? { width: thickness, height: size } : { width: size, height: thickness };
|
|
7780
|
-
const fillStyle = isVertical ? {
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
left: 0,
|
|
7784
|
-
right: 0,
|
|
7785
|
-
height: fillSizePercent
|
|
7786
|
-
} : {
|
|
7906
|
+
const fillStyle = isVertical ? { position: "absolute", bottom: 0, left: 0, right: 0, height: fillSizePercent } : { position: "absolute", top: 0, bottom: 0, left: 0, width: fillSizePercent };
|
|
7907
|
+
const bulgeSize = zone.over === 0 ? 0 : BULGE_SIZE[zone.over];
|
|
7908
|
+
const bulgeStyle = {
|
|
7787
7909
|
position: "absolute",
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7910
|
+
width: bulgeSize,
|
|
7911
|
+
height: bulgeSize,
|
|
7912
|
+
borderRadius: bulgeSize / 2,
|
|
7913
|
+
backgroundColor: zone.color,
|
|
7914
|
+
zIndex: 1,
|
|
7915
|
+
...isVertical ? { top: 0, left: "50%", transform: [{ translateX: -bulgeSize / 2 }] } : { right: 0, top: "50%", transform: [{ translateY: -bulgeSize / 2 }] }
|
|
7792
7916
|
};
|
|
7793
|
-
const
|
|
7917
|
+
const targetPct = `${clampFill(threshold ?? 0) * 100}%`;
|
|
7918
|
+
const targetLineStyle = isVertical ? {
|
|
7794
7919
|
position: "absolute",
|
|
7795
|
-
bottom:
|
|
7796
|
-
left: -
|
|
7797
|
-
right: -
|
|
7798
|
-
height: 1,
|
|
7799
|
-
backgroundColor:
|
|
7920
|
+
bottom: targetPct,
|
|
7921
|
+
left: -3,
|
|
7922
|
+
right: -3,
|
|
7923
|
+
height: 1.5,
|
|
7924
|
+
backgroundColor: TARGET_LINE_COLOR,
|
|
7800
7925
|
zIndex: 4
|
|
7801
7926
|
} : {
|
|
7802
7927
|
position: "absolute",
|
|
7803
|
-
left:
|
|
7804
|
-
top: -
|
|
7805
|
-
bottom: -
|
|
7806
|
-
width: 1,
|
|
7807
|
-
backgroundColor:
|
|
7928
|
+
left: targetPct,
|
|
7929
|
+
top: -3,
|
|
7930
|
+
bottom: -3,
|
|
7931
|
+
width: 1.5,
|
|
7932
|
+
backgroundColor: TARGET_LINE_COLOR,
|
|
7808
7933
|
zIndex: 4
|
|
7809
|
-
}
|
|
7934
|
+
};
|
|
7810
7935
|
return /* @__PURE__ */ jsxs(
|
|
7811
7936
|
reactNative.View,
|
|
7812
7937
|
{
|
|
7813
7938
|
className,
|
|
7814
7939
|
style: { alignItems: "center" },
|
|
7815
7940
|
accessibilityRole: "progressbar",
|
|
7816
|
-
accessibilityValue: { min: 0, max: 100, now:
|
|
7817
|
-
accessibilityLabel: `Intensity level: ${
|
|
7941
|
+
accessibilityValue: { min: 0, max: 100, now: pct },
|
|
7942
|
+
accessibilityLabel: `Intensity level: ${pct}%`,
|
|
7818
7943
|
testID: "intensity-bar",
|
|
7819
7944
|
...props,
|
|
7820
7945
|
children: [
|
|
@@ -7836,13 +7961,32 @@ function IntensityBar({
|
|
|
7836
7961
|
style: {
|
|
7837
7962
|
...fillStyle,
|
|
7838
7963
|
borderRadius: 3,
|
|
7839
|
-
backgroundColor:
|
|
7840
|
-
zIndex: 2
|
|
7964
|
+
backgroundColor: zone.color,
|
|
7965
|
+
zIndex: 2,
|
|
7966
|
+
...atTarget ? { boxShadow: AT_TARGET_GLOW } : null
|
|
7841
7967
|
},
|
|
7842
7968
|
testID: "intensity-fill"
|
|
7843
7969
|
}
|
|
7844
7970
|
),
|
|
7845
|
-
|
|
7971
|
+
zone.over !== 0 && /* @__PURE__ */ jsx(reactNative.View, { style: bulgeStyle, testID: "intensity-bulge" }),
|
|
7972
|
+
threshold != null && /* @__PURE__ */ jsx(reactNative.View, { style: targetLineStyle, testID: "intensity-target" })
|
|
7973
|
+
]
|
|
7974
|
+
}
|
|
7975
|
+
),
|
|
7976
|
+
/* @__PURE__ */ jsxs(
|
|
7977
|
+
reactNative.Text,
|
|
7978
|
+
{
|
|
7979
|
+
style: {
|
|
7980
|
+
fontSize: 8,
|
|
7981
|
+
color: LABEL_COLOR,
|
|
7982
|
+
fontFamily: '"Nunito Sans", sans-serif',
|
|
7983
|
+
marginTop: 6
|
|
7984
|
+
},
|
|
7985
|
+
accessibilityElementsHidden: true,
|
|
7986
|
+
testID: "intensity-label",
|
|
7987
|
+
children: [
|
|
7988
|
+
pct,
|
|
7989
|
+
"%"
|
|
7846
7990
|
]
|
|
7847
7991
|
}
|
|
7848
7992
|
),
|
|
@@ -7854,7 +7998,7 @@ function IntensityBar({
|
|
|
7854
7998
|
fontWeight: "500",
|
|
7855
7999
|
color: LABEL_COLOR,
|
|
7856
8000
|
fontFamily: "Inter, sans-serif",
|
|
7857
|
-
marginTop:
|
|
8001
|
+
marginTop: 2
|
|
7858
8002
|
},
|
|
7859
8003
|
accessibilityElementsHidden: true,
|
|
7860
8004
|
testID: "intensity-threshold-label",
|
|
@@ -8018,6 +8162,13 @@ function WorkoutPill({
|
|
|
8018
8162
|
return pill;
|
|
8019
8163
|
}
|
|
8020
8164
|
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
8165
|
+
var zoneIdToScaleToken = {
|
|
8166
|
+
speed: "green",
|
|
8167
|
+
power: "yellow",
|
|
8168
|
+
strengthSpeed: "orange",
|
|
8169
|
+
maximalStrength: "red",
|
|
8170
|
+
grinding: "red"
|
|
8171
|
+
};
|
|
8021
8172
|
function getVelocityZoneColor(velocity) {
|
|
8022
8173
|
if (velocity >= 1) return "vel-green";
|
|
8023
8174
|
if (velocity >= 0.75) return "vel-yellow";
|
|
@@ -8030,47 +8181,62 @@ function getVelocityZoneName(velocity) {
|
|
|
8030
8181
|
if (velocity >= 0.5) return "Strength-Speed";
|
|
8031
8182
|
return "Strength";
|
|
8032
8183
|
}
|
|
8184
|
+
var zoneHexMap = {
|
|
8185
|
+
"vel-green": VEL_COLORS.green,
|
|
8186
|
+
"vel-yellow": VEL_COLORS.yellow,
|
|
8187
|
+
"vel-orange": VEL_COLORS.orange,
|
|
8188
|
+
"vel-red": VEL_COLORS.red
|
|
8189
|
+
};
|
|
8033
8190
|
function calculateVelocityLoss(velocities) {
|
|
8034
8191
|
if (velocities.length < 2) return 0;
|
|
8035
|
-
const
|
|
8192
|
+
const best = Math.max(...velocities);
|
|
8193
|
+
if (best <= 0) return 0;
|
|
8036
8194
|
const last = velocities[velocities.length - 1];
|
|
8037
|
-
|
|
8038
|
-
return Math.round((first - last) / first * 100);
|
|
8195
|
+
return Math.max(0, Math.round((best - last) / best * 100));
|
|
8039
8196
|
}
|
|
8040
8197
|
function calculateMeanVelocity(velocities) {
|
|
8041
8198
|
if (velocities.length === 0) return 0;
|
|
8042
8199
|
const sum = velocities.reduce((acc, v) => acc + v, 0);
|
|
8043
8200
|
return sum / velocities.length;
|
|
8044
8201
|
}
|
|
8045
|
-
function
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
return VEL_COLORS.green;
|
|
8049
|
-
case "vel-yellow":
|
|
8050
|
-
return VEL_COLORS.yellow;
|
|
8051
|
-
case "vel-orange":
|
|
8052
|
-
return VEL_COLORS.orange;
|
|
8053
|
-
case "vel-red":
|
|
8054
|
-
return VEL_COLORS.red;
|
|
8055
|
-
default:
|
|
8056
|
-
return VEL_COLORS.green;
|
|
8202
|
+
function classifyBand(velocity, bands) {
|
|
8203
|
+
for (const band of bands) {
|
|
8204
|
+
if (band.max === null || velocity < band.max) return band;
|
|
8057
8205
|
}
|
|
8206
|
+
return bands[bands.length - 1];
|
|
8207
|
+
}
|
|
8208
|
+
function bandColor(band) {
|
|
8209
|
+
const token = zoneIdToScaleToken[band.id];
|
|
8210
|
+
return token ? VEL_COLORS[token] : VEL_COLORS.green;
|
|
8058
8211
|
}
|
|
8059
|
-
var zoneHexMap = {
|
|
8060
|
-
"vel-green": VEL_COLORS.green,
|
|
8061
|
-
"vel-yellow": VEL_COLORS.yellow,
|
|
8062
|
-
"vel-orange": VEL_COLORS.orange,
|
|
8063
|
-
"vel-red": VEL_COLORS.red
|
|
8064
|
-
};
|
|
8065
8212
|
function getLossStyle(loss) {
|
|
8066
8213
|
if (loss > 25) return { color: VEL_COLORS.red };
|
|
8067
8214
|
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
8068
8215
|
return null;
|
|
8069
8216
|
}
|
|
8217
|
+
function getReducedMotionPreference() {
|
|
8218
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
8219
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
8220
|
+
}
|
|
8221
|
+
function usePrefersReducedMotion() {
|
|
8222
|
+
const [reduced, setReduced] = React24.useState(getReducedMotionPreference);
|
|
8223
|
+
React24.useEffect(() => {
|
|
8224
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
8225
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
8226
|
+
const handler = () => setReduced(mq.matches);
|
|
8227
|
+
handler();
|
|
8228
|
+
mq.addEventListener?.("change", handler);
|
|
8229
|
+
return () => mq.removeEventListener?.("change", handler);
|
|
8230
|
+
}, []);
|
|
8231
|
+
return reduced;
|
|
8232
|
+
}
|
|
8070
8233
|
var ANIMATION_DURATION = 400;
|
|
8071
8234
|
var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
|
|
8235
|
+
var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
|
|
8072
8236
|
function VelocityStrip({
|
|
8073
8237
|
velocities,
|
|
8238
|
+
zones,
|
|
8239
|
+
liveRepIndex,
|
|
8074
8240
|
expanded = false,
|
|
8075
8241
|
onToggle,
|
|
8076
8242
|
onRepPress,
|
|
@@ -8082,10 +8248,48 @@ function VelocityStrip({
|
|
|
8082
8248
|
const maxVelocity = Math.max(...velocities, 0);
|
|
8083
8249
|
const meanVelocity = calculateMeanVelocity(velocities);
|
|
8084
8250
|
const loss = calculateVelocityLoss(velocities);
|
|
8085
|
-
const
|
|
8251
|
+
const hasZones = zones != null && zones.length > 0;
|
|
8252
|
+
const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
8253
|
+
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
8254
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
8086
8255
|
const [heightAnim] = React24.useState(() => new reactNative.Animated.Value(expanded ? 60 : 3));
|
|
8087
8256
|
const [labelOpacity] = React24.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
8088
8257
|
const [infoOpacity] = React24.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
|
|
8258
|
+
const [liveScale] = React24.useState(() => new reactNative.Animated.Value(1));
|
|
8259
|
+
const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
|
|
8260
|
+
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
8261
|
+
React24.useEffect(() => {
|
|
8262
|
+
if (variant === "mini" || liveRepIndex == null || liveVelocity == null) return;
|
|
8263
|
+
if (prefersReducedMotion) {
|
|
8264
|
+
liveScale.setValue(1);
|
|
8265
|
+
return;
|
|
8266
|
+
}
|
|
8267
|
+
if (isNewPeak) {
|
|
8268
|
+
liveScale.setValue(1);
|
|
8269
|
+
reactNative.Animated.sequence([
|
|
8270
|
+
reactNative.Animated.timing(liveScale, {
|
|
8271
|
+
toValue: 1.25,
|
|
8272
|
+
duration: 150,
|
|
8273
|
+
easing: reactNative.Easing.out(reactNative.Easing.quad),
|
|
8274
|
+
useNativeDriver: true
|
|
8275
|
+
}),
|
|
8276
|
+
reactNative.Animated.spring(liveScale, {
|
|
8277
|
+
toValue: 1,
|
|
8278
|
+
friction: 3,
|
|
8279
|
+
tension: 140,
|
|
8280
|
+
useNativeDriver: true
|
|
8281
|
+
})
|
|
8282
|
+
]).start();
|
|
8283
|
+
} else {
|
|
8284
|
+
liveScale.setValue(0.8);
|
|
8285
|
+
reactNative.Animated.timing(liveScale, {
|
|
8286
|
+
toValue: 1,
|
|
8287
|
+
duration: 300,
|
|
8288
|
+
easing: POP_EASING,
|
|
8289
|
+
useNativeDriver: true
|
|
8290
|
+
}).start();
|
|
8291
|
+
}
|
|
8292
|
+
}, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
|
|
8089
8293
|
React24.useEffect(() => {
|
|
8090
8294
|
if (variant === "mini") return;
|
|
8091
8295
|
reactNative.Animated.parallel([
|
|
@@ -8125,7 +8329,7 @@ function VelocityStrip({
|
|
|
8125
8329
|
{
|
|
8126
8330
|
style: {
|
|
8127
8331
|
flex: 1,
|
|
8128
|
-
backgroundColor:
|
|
8332
|
+
backgroundColor: barColorFor(v),
|
|
8129
8333
|
borderRadius: 1,
|
|
8130
8334
|
minWidth: 4,
|
|
8131
8335
|
height: "100%"
|
|
@@ -8168,8 +8372,35 @@ function VelocityStrip({
|
|
|
8168
8372
|
{
|
|
8169
8373
|
style: { flexDirection: "row", flex: 1, gap: 2, alignItems: "flex-end" },
|
|
8170
8374
|
children: velocities.map((v, i) => {
|
|
8171
|
-
const
|
|
8375
|
+
const barBackground = barColorFor(v);
|
|
8172
8376
|
const barHeightPct = maxVelocity > 0 ? Math.round(v / (maxVelocity * 1.15) * 100) : 0;
|
|
8377
|
+
const isLive = liveRepIndex === i;
|
|
8378
|
+
const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
|
|
8379
|
+
const barStyle = expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: barBackground } : {
|
|
8380
|
+
minHeight: "100%",
|
|
8381
|
+
borderTopLeftRadius: 2,
|
|
8382
|
+
borderTopRightRadius: 2,
|
|
8383
|
+
borderBottomLeftRadius: 0,
|
|
8384
|
+
borderBottomRightRadius: 0,
|
|
8385
|
+
backgroundColor: barBackground
|
|
8386
|
+
};
|
|
8387
|
+
const barInner = isLive ? /* @__PURE__ */ jsx(
|
|
8388
|
+
reactNative.Animated.View,
|
|
8389
|
+
{
|
|
8390
|
+
style: [barStyle, { transform: [{ scale: liveScale }] }],
|
|
8391
|
+
testID: `velocity-bar-${i}`,
|
|
8392
|
+
accessibilityRole: "image",
|
|
8393
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
|
|
8394
|
+
}
|
|
8395
|
+
) : /* @__PURE__ */ jsx(
|
|
8396
|
+
reactNative.View,
|
|
8397
|
+
{
|
|
8398
|
+
style: barStyle,
|
|
8399
|
+
testID: `velocity-bar-${i}`,
|
|
8400
|
+
accessibilityRole: "image",
|
|
8401
|
+
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
8402
|
+
}
|
|
8403
|
+
);
|
|
8173
8404
|
const bar = /* @__PURE__ */ jsxs(
|
|
8174
8405
|
reactNative.View,
|
|
8175
8406
|
{
|
|
@@ -8184,22 +8415,7 @@ function VelocityStrip({
|
|
|
8184
8415
|
children: formatVelocity(v)
|
|
8185
8416
|
}
|
|
8186
8417
|
) }),
|
|
8187
|
-
|
|
8188
|
-
reactNative.View,
|
|
8189
|
-
{
|
|
8190
|
-
style: expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: zoneHexMap[zone] } : {
|
|
8191
|
-
minHeight: "100%",
|
|
8192
|
-
borderTopLeftRadius: 2,
|
|
8193
|
-
borderTopRightRadius: 2,
|
|
8194
|
-
borderBottomLeftRadius: 0,
|
|
8195
|
-
borderBottomRightRadius: 0,
|
|
8196
|
-
backgroundColor: zoneHexMap[zone]
|
|
8197
|
-
},
|
|
8198
|
-
testID: `velocity-bar-${i}`,
|
|
8199
|
-
accessibilityRole: "image",
|
|
8200
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
8201
|
-
}
|
|
8202
|
-
)
|
|
8418
|
+
barInner
|
|
8203
8419
|
]
|
|
8204
8420
|
},
|
|
8205
8421
|
i
|
|
@@ -8532,6 +8748,8 @@ function SetRow({
|
|
|
8532
8748
|
rpe,
|
|
8533
8749
|
unit,
|
|
8534
8750
|
velocities,
|
|
8751
|
+
velocityZones,
|
|
8752
|
+
velocityLiveRepIndex,
|
|
8535
8753
|
velocityExpanded,
|
|
8536
8754
|
onVelocityToggle,
|
|
8537
8755
|
setType,
|
|
@@ -8734,6 +8952,8 @@ function SetRow({
|
|
|
8734
8952
|
VelocityStrip,
|
|
8735
8953
|
{
|
|
8736
8954
|
velocities,
|
|
8955
|
+
zones: velocityZones,
|
|
8956
|
+
liveRepIndex: velocityLiveRepIndex,
|
|
8737
8957
|
expanded: velocityExpanded,
|
|
8738
8958
|
onToggle: onVelocityToggle,
|
|
8739
8959
|
variant: onVelocityToggle ? "full" : "mini"
|
|
@@ -8908,7 +9128,8 @@ function RestTimer({
|
|
|
8908
9128
|
onSkip,
|
|
8909
9129
|
onAddTime,
|
|
8910
9130
|
nextSetInfo,
|
|
8911
|
-
visible
|
|
9131
|
+
visible,
|
|
9132
|
+
displayOnly = false
|
|
8912
9133
|
}) {
|
|
8913
9134
|
if (!visible) return null;
|
|
8914
9135
|
const remainingMs = Math.max(0, totalSeconds * 1e3 - elapsedMs);
|
|
@@ -9015,7 +9236,7 @@ function RestTimer({
|
|
|
9015
9236
|
)
|
|
9016
9237
|
}
|
|
9017
9238
|
),
|
|
9018
|
-
/* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
|
|
9239
|
+
!displayOnly && /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
|
|
9019
9240
|
/* @__PURE__ */ jsx(
|
|
9020
9241
|
reactNative.Pressable,
|
|
9021
9242
|
{
|
|
@@ -9132,6 +9353,7 @@ function CollapsedCard({
|
|
|
9132
9353
|
e1rm,
|
|
9133
9354
|
isPR,
|
|
9134
9355
|
setVelocities,
|
|
9356
|
+
velocityZones,
|
|
9135
9357
|
totalPlannedSets,
|
|
9136
9358
|
supersetPosition
|
|
9137
9359
|
}) {
|
|
@@ -9219,6 +9441,7 @@ function CollapsedCard({
|
|
|
9219
9441
|
VelocityStrip,
|
|
9220
9442
|
{
|
|
9221
9443
|
velocities,
|
|
9444
|
+
zones: velocityZones,
|
|
9222
9445
|
variant: "mini",
|
|
9223
9446
|
testID: `exercise-card-velocity-strip-${i}`
|
|
9224
9447
|
},
|
|
@@ -10056,7 +10279,7 @@ function PrRecordRow({ record, index }) {
|
|
|
10056
10279
|
},
|
|
10057
10280
|
testID: `pr-history-modal-record-${index}`,
|
|
10058
10281
|
children: [
|
|
10059
|
-
/* @__PURE__ */ jsx(
|
|
10282
|
+
/* @__PURE__ */ jsx(StarIcon, { size: 16, color: BRAND_PRIMARY7, fill: BRAND_PRIMARY7, strokeWidth: 2 }),
|
|
10060
10283
|
/* @__PURE__ */ jsx(
|
|
10061
10284
|
reactNative.Text,
|
|
10062
10285
|
{
|
|
@@ -10204,7 +10427,7 @@ var BRAND_PRIMARY8 = "#FF7900";
|
|
|
10204
10427
|
var BRAND_PRIMARY_SUBTLE = "rgba(255,121,0,0.12)";
|
|
10205
10428
|
var STATUS_SUCCESS = "#14B8A6";
|
|
10206
10429
|
var STATUS_WARNING = "#FFB020";
|
|
10207
|
-
var
|
|
10430
|
+
var STATUS_ERROR2 = "#D14343";
|
|
10208
10431
|
var EMOJI_SETS = {
|
|
10209
10432
|
sleep: ["\u{1F634}", "\u{1F610}", "\u{1F642}", "\u{1F60A}", "\u{1F929}"],
|
|
10210
10433
|
soreness: ["\u{1F635}", "\u{1F623}", "\u{1F610}", "\u{1F642}", "\u{1F4AA}"],
|
|
@@ -10213,7 +10436,7 @@ var EMOJI_SETS = {
|
|
|
10213
10436
|
};
|
|
10214
10437
|
var DEFAULT_EMOJI_SET = ["\u{1F61E}", "\u{1F615}", "\u{1F610}", "\u{1F642}", "\u{1F604}"];
|
|
10215
10438
|
function scoreColor(score) {
|
|
10216
|
-
if (score < 40) return
|
|
10439
|
+
if (score < 40) return STATUS_ERROR2;
|
|
10217
10440
|
if (score < 70) return STATUS_WARNING;
|
|
10218
10441
|
return STATUS_SUCCESS;
|
|
10219
10442
|
}
|
|
@@ -10408,7 +10631,7 @@ var STATUS_VARIANTS = {
|
|
|
10408
10631
|
warning: { bg: "rgba(255,176,32,0.15)", border: "rgba(255,176,32,0.3)", text: WARNING },
|
|
10409
10632
|
error: { bg: "rgba(209,67,67,0.15)", border: "rgba(209,67,67,0.25)", text: ERROR }
|
|
10410
10633
|
};
|
|
10411
|
-
function
|
|
10634
|
+
function clamp01(value) {
|
|
10412
10635
|
if (!Number.isFinite(value)) return 0;
|
|
10413
10636
|
if (value < 0) return 0;
|
|
10414
10637
|
if (value > 1) return 1;
|
|
@@ -10503,7 +10726,7 @@ function MetricCell({ metric }) {
|
|
|
10503
10726
|
] });
|
|
10504
10727
|
}
|
|
10505
10728
|
function Gauge({ gauge }) {
|
|
10506
|
-
const level =
|
|
10729
|
+
const level = clamp01(gauge.level);
|
|
10507
10730
|
const percentage = Math.round(level * 100);
|
|
10508
10731
|
const markerColor = getGaugeZoneColor(level);
|
|
10509
10732
|
return /* @__PURE__ */ jsxs(
|
|
@@ -10759,7 +10982,7 @@ function MesoStatusCard({
|
|
|
10759
10982
|
var BRAND_PRIMARY10 = "#FF7900";
|
|
10760
10983
|
var PROJECTION_LINE_COLOR = resolveColor("text-tertiary");
|
|
10761
10984
|
var STATUS_SUCCESS2 = "#14B8A6";
|
|
10762
|
-
var
|
|
10985
|
+
var STATUS_ERROR3 = "#D14343";
|
|
10763
10986
|
var STATUS_WARNING2 = "#FFB020";
|
|
10764
10987
|
var GRID_LINE = "rgba(255,255,255,0.06)";
|
|
10765
10988
|
var SUCCESS_PILL_BG = "rgba(20,184,166,0.10)";
|
|
@@ -11159,7 +11382,7 @@ function StrengthTrendChart({
|
|
|
11159
11382
|
marginTop: 1,
|
|
11160
11383
|
fontSize: 10,
|
|
11161
11384
|
fontFamily: "Inter, sans-serif",
|
|
11162
|
-
color: selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? STATUS_SUCCESS2 :
|
|
11385
|
+
color: selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? STATUS_SUCCESS2 : STATUS_ERROR3
|
|
11163
11386
|
},
|
|
11164
11387
|
children: `${selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? "+" : "-"}${Math.abs(
|
|
11165
11388
|
selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm
|
|
@@ -11215,7 +11438,7 @@ function StrengthTrendChart({
|
|
|
11215
11438
|
fontSize: 11,
|
|
11216
11439
|
fontFamily: "Inter, sans-serif",
|
|
11217
11440
|
fontWeight: "600",
|
|
11218
|
-
color: trend.positive ? STATUS_SUCCESS2 :
|
|
11441
|
+
color: trend.positive ? STATUS_SUCCESS2 : STATUS_ERROR3
|
|
11219
11442
|
},
|
|
11220
11443
|
children: trendText
|
|
11221
11444
|
}
|
|
@@ -11407,1231 +11630,230 @@ function CapacityBandChart({
|
|
|
11407
11630
|
const toY2 = (value) => PADDING_TOP + (1 - (value - domainMin) / vRange) * plotHeight;
|
|
11408
11631
|
return { toX: toX2, toY: toY2 };
|
|
11409
11632
|
}, [band, workouts, projection, plotWidth, plotHeight]);
|
|
11410
|
-
if (band.length === 0) {
|
|
11411
|
-
return /* @__PURE__ */ jsx(
|
|
11412
|
-
reactNative.View,
|
|
11413
|
-
{
|
|
11414
|
-
style: { width, height },
|
|
11415
|
-
className,
|
|
11416
|
-
accessibilityRole: "image",
|
|
11417
|
-
accessibilityLabel: "Training capacity band chart, no data",
|
|
11418
|
-
testID: "capacity-band-chart-empty",
|
|
11419
|
-
...props
|
|
11420
|
-
}
|
|
11421
|
-
);
|
|
11422
|
-
}
|
|
11423
|
-
const { toX, toY } = scale;
|
|
11424
|
-
const bandPixels = toPixels(band, toX, toY);
|
|
11425
|
-
const columns = buildColumns(bandPixels, COLUMN_STEP);
|
|
11426
|
-
const topEdge = buildEdges(bandPixels, "yHigh");
|
|
11427
|
-
const bottomEdge = buildEdges(bandPixels, "yLow");
|
|
11428
|
-
const lastBandPoint = band[band.length - 1];
|
|
11429
|
-
const trainingPixels = projection ? toPixels([lastBandPoint, ...projection.withTraining], toX, toY) : [];
|
|
11430
|
-
const restPixels = projection ? toPixels([lastBandPoint, ...projection.withRest], toX, toY) : [];
|
|
11431
|
-
const labelStride = Math.max(1, Math.ceil(band.length / 5));
|
|
11432
|
-
const revealWidth = reveal.interpolate({ inputRange: [0, 1], outputRange: [0, width] });
|
|
11433
|
-
const renderColumns = (cols, color, key) => cols.map((col, i) => /* @__PURE__ */ jsx(
|
|
11434
|
-
reactNative.View,
|
|
11435
|
-
{
|
|
11436
|
-
style: {
|
|
11437
|
-
position: "absolute",
|
|
11438
|
-
left: col.x,
|
|
11439
|
-
top: col.top,
|
|
11440
|
-
width: COLUMN_STEP + 0.5,
|
|
11441
|
-
height: col.height,
|
|
11442
|
-
backgroundColor: color
|
|
11443
|
-
},
|
|
11444
|
-
accessibilityElementsHidden: true,
|
|
11445
|
-
importantForAccessibility: "no-hide-descendants",
|
|
11446
|
-
testID: key
|
|
11447
|
-
},
|
|
11448
|
-
`${key}-${i}`
|
|
11449
|
-
));
|
|
11450
|
-
const renderEdges = (segments, color, dashed, key) => segments.map((seg, i) => /* @__PURE__ */ jsx(
|
|
11451
|
-
reactNative.View,
|
|
11452
|
-
{
|
|
11453
|
-
style: {
|
|
11454
|
-
position: "absolute",
|
|
11455
|
-
left: seg.left,
|
|
11456
|
-
top: seg.top,
|
|
11457
|
-
width: seg.length,
|
|
11458
|
-
height: dashed ? 0 : 1.5,
|
|
11459
|
-
borderTopWidth: dashed ? 1 : 0,
|
|
11460
|
-
borderTopColor: dashed ? color : void 0,
|
|
11461
|
-
borderStyle: dashed ? "dashed" : "solid",
|
|
11462
|
-
backgroundColor: dashed ? void 0 : color,
|
|
11463
|
-
opacity: dashed ? 0.7 : 1,
|
|
11464
|
-
transformOrigin: "0 0",
|
|
11465
|
-
transform: [{ rotate: `${seg.angle}deg` }]
|
|
11466
|
-
},
|
|
11467
|
-
accessibilityElementsHidden: true,
|
|
11468
|
-
importantForAccessibility: "no-hide-descendants",
|
|
11469
|
-
testID: key
|
|
11470
|
-
},
|
|
11471
|
-
`${key}-${i}`
|
|
11472
|
-
));
|
|
11473
|
-
return /* @__PURE__ */ jsxs(
|
|
11474
|
-
reactNative.View,
|
|
11475
|
-
{
|
|
11476
|
-
style: { width, height, position: "relative" },
|
|
11477
|
-
className,
|
|
11478
|
-
testID: "capacity-band-chart-root",
|
|
11479
|
-
...props,
|
|
11480
|
-
children: [
|
|
11481
|
-
/* @__PURE__ */ jsxs(
|
|
11482
|
-
reactNative.View,
|
|
11483
|
-
{
|
|
11484
|
-
style: { position: "absolute", top: 0, left: 0, width, height },
|
|
11485
|
-
accessibilityRole: "image",
|
|
11486
|
-
accessibilityLabel: `Training capacity band chart. Current capacity: ${currentStatus(
|
|
11487
|
-
workouts
|
|
11488
|
-
)}. ${workouts.length} workout${workouts.length === 1 ? "" : "s"} shown.`,
|
|
11489
|
-
testID: "capacity-band-chart",
|
|
11490
|
-
children: [
|
|
11491
|
-
/* @__PURE__ */ jsx(
|
|
11492
|
-
reactNative.Animated.View,
|
|
11493
|
-
{
|
|
11494
|
-
style: { position: "absolute", top: 0, left: 0, height, width: revealWidth, overflow: "hidden" },
|
|
11495
|
-
accessibilityElementsHidden: true,
|
|
11496
|
-
importantForAccessibility: "no-hide-descendants",
|
|
11497
|
-
testID: "capacity-band-chart-reveal",
|
|
11498
|
-
children: /* @__PURE__ */ jsxs(reactNative.View, { style: { width, height, position: "relative" }, children: [
|
|
11499
|
-
projection && /* @__PURE__ */ jsxs(reactNative.View, { testID: "capacity-band-chart-projection", children: [
|
|
11500
|
-
renderColumns(buildColumns(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
11501
|
-
renderColumns(buildColumns(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
11502
|
-
renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
|
|
11503
|
-
renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
|
|
11504
|
-
renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
|
|
11505
|
-
renderEdges(buildEdges(restPixels, "yLow"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
|
|
11506
|
-
/* @__PURE__ */ jsx(
|
|
11507
|
-
reactNative.Text,
|
|
11508
|
-
{
|
|
11509
|
-
style: {
|
|
11510
|
-
position: "absolute",
|
|
11511
|
-
left: trainingPixels[trainingPixels.length - 1]?.x + 2,
|
|
11512
|
-
top: trainingPixels[trainingPixels.length - 1]?.yHigh - 12,
|
|
11513
|
-
fontSize: 9,
|
|
11514
|
-
fontFamily: "Inter, sans-serif",
|
|
11515
|
-
color: STATUS_SUCCESS3
|
|
11516
|
-
},
|
|
11517
|
-
testID: "capacity-band-chart-projection-training-label",
|
|
11518
|
-
children: "Training"
|
|
11519
|
-
}
|
|
11520
|
-
),
|
|
11521
|
-
/* @__PURE__ */ jsx(
|
|
11522
|
-
reactNative.Text,
|
|
11523
|
-
{
|
|
11524
|
-
style: {
|
|
11525
|
-
position: "absolute",
|
|
11526
|
-
left: restPixels[restPixels.length - 1]?.x + 2,
|
|
11527
|
-
top: restPixels[restPixels.length - 1]?.yLow + 2,
|
|
11528
|
-
fontSize: 9,
|
|
11529
|
-
fontFamily: "Inter, sans-serif",
|
|
11530
|
-
color: STATUS_INFO
|
|
11531
|
-
},
|
|
11532
|
-
testID: "capacity-band-chart-projection-rest-label",
|
|
11533
|
-
children: "Rest"
|
|
11534
|
-
}
|
|
11535
|
-
)
|
|
11536
|
-
] }),
|
|
11537
|
-
/* @__PURE__ */ jsx(reactNative.View, { testID: "capacity-band-chart-band", children: renderColumns(columns, BAND_FILL, "capacity-band-chart-band-cell") }),
|
|
11538
|
-
renderEdges(topEdge, BAND_EDGE, false, "capacity-band-chart-edge-top"),
|
|
11539
|
-
renderEdges(bottomEdge, BAND_EDGE, false, "capacity-band-chart-edge-bottom")
|
|
11540
|
-
] })
|
|
11541
|
-
}
|
|
11542
|
-
),
|
|
11543
|
-
/* @__PURE__ */ jsx(
|
|
11544
|
-
reactNative.Text,
|
|
11545
|
-
{
|
|
11546
|
-
className: "text-text-tertiary",
|
|
11547
|
-
style: {
|
|
11548
|
-
position: "absolute",
|
|
11549
|
-
left: PADDING_LEFT / 2 - 14,
|
|
11550
|
-
top: PADDING_TOP + plotHeight / 2 - 7,
|
|
11551
|
-
width: 32,
|
|
11552
|
-
textAlign: "center",
|
|
11553
|
-
fontSize: 9,
|
|
11554
|
-
fontFamily: "Inter, sans-serif",
|
|
11555
|
-
transform: [{ rotate: "-90deg" }]
|
|
11556
|
-
},
|
|
11557
|
-
accessibilityElementsHidden: true,
|
|
11558
|
-
importantForAccessibility: "no-hide-descendants",
|
|
11559
|
-
testID: "capacity-band-chart-y-axis-label",
|
|
11560
|
-
children: "Load"
|
|
11561
|
-
}
|
|
11562
|
-
),
|
|
11563
|
-
band.map((point, i) => {
|
|
11564
|
-
if (i % labelStride !== 0 && i !== band.length - 1) return null;
|
|
11565
|
-
return /* @__PURE__ */ jsx(
|
|
11566
|
-
reactNative.Text,
|
|
11567
|
-
{
|
|
11568
|
-
className: "text-text-tertiary",
|
|
11569
|
-
style: {
|
|
11570
|
-
position: "absolute",
|
|
11571
|
-
left: toX(point.date) - 14,
|
|
11572
|
-
top: height - PADDING_BOTTOM + 2,
|
|
11573
|
-
width: 28,
|
|
11574
|
-
textAlign: "center",
|
|
11575
|
-
fontSize: 10,
|
|
11576
|
-
fontFamily: "Inter, sans-serif"
|
|
11577
|
-
},
|
|
11578
|
-
accessibilityElementsHidden: true,
|
|
11579
|
-
importantForAccessibility: "no-hide-descendants",
|
|
11580
|
-
testID: "capacity-band-chart-x-label",
|
|
11581
|
-
children: formatDate2(point.date)
|
|
11582
|
-
},
|
|
11583
|
-
`x-${point.date}`
|
|
11584
|
-
);
|
|
11585
|
-
})
|
|
11586
|
-
]
|
|
11587
|
-
}
|
|
11588
|
-
),
|
|
11589
|
-
workouts.map((workout, i) => {
|
|
11590
|
-
const cx = toX(workout.date);
|
|
11591
|
-
const cy = toY(workout.load);
|
|
11592
|
-
const color = DOT_COLORS[workout.status];
|
|
11593
|
-
const dotStyle = {
|
|
11594
|
-
width: DOT_SIZE,
|
|
11595
|
-
height: DOT_SIZE,
|
|
11596
|
-
borderRadius: DOT_SIZE / 2,
|
|
11597
|
-
backgroundColor: color,
|
|
11598
|
-
borderWidth: 2,
|
|
11599
|
-
borderColor: DOT_BORDER
|
|
11600
|
-
};
|
|
11601
|
-
const label = `Workout on ${formatDate2(workout.date)}, load ${workout.load}, ${STATUS_PHRASES[workout.status]}`;
|
|
11602
|
-
return /* @__PURE__ */ jsx(
|
|
11603
|
-
reactNative.Animated.View,
|
|
11604
|
-
{
|
|
11605
|
-
style: {
|
|
11606
|
-
position: "absolute",
|
|
11607
|
-
left: cx - DOT_SIZE / 2,
|
|
11608
|
-
top: cy - DOT_SIZE / 2,
|
|
11609
|
-
transform: [{ scale: dotAnims[i] ?? 1 }]
|
|
11610
|
-
},
|
|
11611
|
-
testID: "capacity-band-chart-dot-wrapper",
|
|
11612
|
-
children: onWorkoutPress ? /* @__PURE__ */ jsx(
|
|
11613
|
-
reactNative.Pressable,
|
|
11614
|
-
{
|
|
11615
|
-
onPress: () => onWorkoutPress(workout),
|
|
11616
|
-
accessibilityRole: "button",
|
|
11617
|
-
accessibilityLabel: label,
|
|
11618
|
-
style: dotStyle,
|
|
11619
|
-
testID: "capacity-band-chart-dot"
|
|
11620
|
-
}
|
|
11621
|
-
) : /* @__PURE__ */ jsx(
|
|
11622
|
-
reactNative.View,
|
|
11623
|
-
{
|
|
11624
|
-
accessibilityElementsHidden: true,
|
|
11625
|
-
importantForAccessibility: "no-hide-descendants",
|
|
11626
|
-
style: dotStyle,
|
|
11627
|
-
testID: "capacity-band-chart-dot"
|
|
11628
|
-
}
|
|
11629
|
-
)
|
|
11630
|
-
},
|
|
11631
|
-
`dot-${workout.date}-${i}`
|
|
11632
|
-
);
|
|
11633
|
-
})
|
|
11634
|
-
]
|
|
11635
|
-
}
|
|
11636
|
-
);
|
|
11637
|
-
}
|
|
11638
|
-
var BRAND_PRIMARY11 = "#FF7900";
|
|
11639
|
-
var WORKOUT_PILL_STATUS = {
|
|
11640
|
-
completed: "completed",
|
|
11641
|
-
today: "current",
|
|
11642
|
-
upcoming: "upcoming"
|
|
11643
|
-
};
|
|
11644
|
-
function deriveNavLevel(selection) {
|
|
11645
|
-
if (selection.workoutId != null) return "workout";
|
|
11646
|
-
if (selection.weekNumber != null) return "week";
|
|
11647
|
-
return "meso";
|
|
11648
|
-
}
|
|
11649
|
-
function toProgressBarMesos(mesos) {
|
|
11650
|
-
return mesos.map(({ id, name, weekCount, status, currentWeek }) => ({
|
|
11651
|
-
id,
|
|
11652
|
-
name,
|
|
11653
|
-
weekCount,
|
|
11654
|
-
status,
|
|
11655
|
-
currentWeek
|
|
11656
|
-
}));
|
|
11657
|
-
}
|
|
11658
|
-
function findMeso(mesos, id) {
|
|
11659
|
-
if (id == null) return null;
|
|
11660
|
-
return mesos.find((meso) => meso.id === id) ?? null;
|
|
11661
|
-
}
|
|
11662
|
-
function findWeek(meso, weekNumber) {
|
|
11663
|
-
if (meso == null || weekNumber == null) return null;
|
|
11664
|
-
return meso.weeks.find((week) => week.weekNumber === weekNumber) ?? null;
|
|
11665
|
-
}
|
|
11666
|
-
function findWorkout(week, workoutId) {
|
|
11667
|
-
if (week == null || workoutId == null) return null;
|
|
11668
|
-
return week.workouts.find((workout) => workout.id === workoutId) ?? null;
|
|
11669
|
-
}
|
|
11670
|
-
function buildBreadcrumbs(meso, weekNumber, workout) {
|
|
11671
|
-
const crumbs = [{ key: "meso", label: meso.name, level: "meso" }];
|
|
11672
|
-
if (weekNumber != null) {
|
|
11673
|
-
crumbs.push({ key: "week", label: `Week ${weekNumber}`, level: "week" });
|
|
11674
|
-
}
|
|
11675
|
-
if (workout != null) {
|
|
11676
|
-
crumbs.push({ key: "workout", label: workout.name, level: "workout" });
|
|
11677
|
-
}
|
|
11678
|
-
return crumbs;
|
|
11679
|
-
}
|
|
11680
|
-
function Breadcrumbs2({ crumbs, onNavigate }) {
|
|
11681
|
-
return /* @__PURE__ */ jsx(
|
|
11682
|
-
reactNative.View,
|
|
11683
|
-
{
|
|
11684
|
-
className: "flex-row items-center flex-wrap",
|
|
11685
|
-
style: { gap: 4 },
|
|
11686
|
-
testID: "program-planning-page-breadcrumbs",
|
|
11687
|
-
children: crumbs.map((crumb, index) => {
|
|
11688
|
-
const isLast = index === crumbs.length - 1;
|
|
11689
|
-
return /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { gap: 4 }, children: [
|
|
11690
|
-
index > 0 && /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-tertiary", style: { fontSize: 12 }, children: "\u203A" }),
|
|
11691
|
-
/* @__PURE__ */ jsx(
|
|
11692
|
-
reactNative.Pressable,
|
|
11693
|
-
{
|
|
11694
|
-
onPress: () => onNavigate(crumb.level),
|
|
11695
|
-
disabled: isLast,
|
|
11696
|
-
accessibilityRole: "button",
|
|
11697
|
-
testID: `program-planning-page-crumb-${crumb.key}`,
|
|
11698
|
-
children: /* @__PURE__ */ jsx(
|
|
11699
|
-
reactNative.Text,
|
|
11700
|
-
{
|
|
11701
|
-
className: isLast ? "text-text-primary" : void 0,
|
|
11702
|
-
style: {
|
|
11703
|
-
fontSize: 12,
|
|
11704
|
-
fontFamily: "Inter, sans-serif",
|
|
11705
|
-
fontWeight: isLast ? "700" : "500",
|
|
11706
|
-
color: isLast ? void 0 : BRAND_PRIMARY11
|
|
11707
|
-
},
|
|
11708
|
-
children: crumb.label
|
|
11709
|
-
}
|
|
11710
|
-
)
|
|
11711
|
-
}
|
|
11712
|
-
)
|
|
11713
|
-
] }, crumb.key);
|
|
11714
|
-
})
|
|
11715
|
-
}
|
|
11716
|
-
);
|
|
11717
|
-
}
|
|
11718
|
-
function toWeekRow(week, meso, onSelectWeek) {
|
|
11719
|
-
const workouts = week.workouts.map((workout) => ({
|
|
11720
|
-
name: workout.name,
|
|
11721
|
-
status: WORKOUT_PILL_STATUS[workout.status],
|
|
11722
|
-
onPress: () => onSelectWeek(week.weekNumber)
|
|
11723
|
-
}));
|
|
11724
|
-
return {
|
|
11725
|
-
weekNumber: week.weekNumber,
|
|
11726
|
-
totalWeeks: meso.weekCount,
|
|
11727
|
-
workouts,
|
|
11728
|
-
intensityLevel: week.intensityLevel,
|
|
11729
|
-
intensityThreshold: week.intensityThreshold,
|
|
11730
|
-
isCurrent: week.isCurrent ?? week.weekNumber === meso.currentWeek,
|
|
11731
|
-
isDeload: week.isDeload
|
|
11732
|
-
};
|
|
11733
|
-
}
|
|
11734
|
-
function MesoLevel({ mesos, activeMesoId, onSelectMeso, onSelectWeek }) {
|
|
11735
|
-
return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 12 }, testID: "program-planning-page-meso-level", children: mesos.map((meso) => {
|
|
11736
|
-
const expanded = meso.id === activeMesoId;
|
|
11737
|
-
return /* @__PURE__ */ jsx(
|
|
11738
|
-
MesoCard,
|
|
11739
|
-
{
|
|
11740
|
-
name: meso.name,
|
|
11741
|
-
goal: meso.goal,
|
|
11742
|
-
split: meso.split,
|
|
11743
|
-
weekRange: meso.weekRange,
|
|
11744
|
-
currentWeek: meso.currentWeek,
|
|
11745
|
-
totalWeeks: meso.weekCount,
|
|
11746
|
-
weeks: expanded ? meso.weeks.map((week) => toWeekRow(week, meso, onSelectWeek)) : [],
|
|
11747
|
-
volumeHeatmap: meso.volumeHeatmap,
|
|
11748
|
-
expanded,
|
|
11749
|
-
onToggle: () => onSelectMeso(meso.id),
|
|
11750
|
-
highlighted: expanded
|
|
11751
|
-
},
|
|
11752
|
-
meso.id
|
|
11753
|
-
);
|
|
11754
|
-
}) });
|
|
11755
|
-
}
|
|
11756
|
-
function WeekLevel({ week, onSelectWorkout }) {
|
|
11757
|
-
return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 10 }, testID: "program-planning-page-week-level", children: week.workouts.map((workout) => /* @__PURE__ */ jsx(
|
|
11758
|
-
WorkoutCard,
|
|
11759
|
-
{
|
|
11760
|
-
name: workout.name,
|
|
11761
|
-
date: workout.date,
|
|
11762
|
-
duration: workout.duration,
|
|
11763
|
-
status: workout.status,
|
|
11764
|
-
muscleGroups: workout.muscleGroups,
|
|
11765
|
-
totalSets: workout.totalSets,
|
|
11766
|
-
totalVolume: workout.totalVolume,
|
|
11767
|
-
unit: workout.unit,
|
|
11768
|
-
exercises: workout.exercises,
|
|
11769
|
-
expanded: false,
|
|
11770
|
-
onToggle: () => onSelectWorkout(workout.id)
|
|
11771
|
-
},
|
|
11772
|
-
workout.id
|
|
11773
|
-
)) });
|
|
11774
|
-
}
|
|
11775
|
-
function WorkoutLevel({ workout, expandedExercise, onToggleExercise }) {
|
|
11776
|
-
const exercises = workout.exercises.map((exercise, index) => ({
|
|
11777
|
-
...exercise,
|
|
11778
|
-
state: exercise.state === "upcoming" ? exercise.state : index === expandedExercise ? "expanded" : "collapsed",
|
|
11779
|
-
onToggle: () => onToggleExercise(index)
|
|
11780
|
-
}));
|
|
11781
|
-
return /* @__PURE__ */ jsx(reactNative.View, { testID: "program-planning-page-workout-level", children: /* @__PURE__ */ jsx(
|
|
11782
|
-
WorkoutCard,
|
|
11783
|
-
{
|
|
11784
|
-
name: workout.name,
|
|
11785
|
-
date: workout.date,
|
|
11786
|
-
duration: workout.duration,
|
|
11787
|
-
status: workout.status,
|
|
11788
|
-
muscleGroups: workout.muscleGroups,
|
|
11789
|
-
totalSets: workout.totalSets,
|
|
11790
|
-
totalVolume: workout.totalVolume,
|
|
11791
|
-
unit: workout.unit,
|
|
11792
|
-
exercises,
|
|
11793
|
-
expanded: true,
|
|
11794
|
-
onToggle: () => void 0
|
|
11795
|
-
}
|
|
11796
|
-
) });
|
|
11797
|
-
}
|
|
11798
|
-
function ProgramPlanningPage({
|
|
11799
|
-
title = "Program Plan",
|
|
11800
|
-
mesos,
|
|
11801
|
-
className,
|
|
11802
|
-
...props
|
|
11803
|
-
}) {
|
|
11804
|
-
const [activeMesoId, setActiveMesoId] = React24.useState(mesos[0]?.id ?? null);
|
|
11805
|
-
const [selectedWeek, setSelectedWeek] = React24.useState(null);
|
|
11806
|
-
const [selectedWorkoutId, setSelectedWorkoutId] = React24.useState(null);
|
|
11807
|
-
const [expandedExercise, setExpandedExercise] = React24.useState(null);
|
|
11808
|
-
const progressMesos = React24.useMemo(() => toProgressBarMesos(mesos), [mesos]);
|
|
11809
|
-
const activeMeso = findMeso(mesos, activeMesoId);
|
|
11810
|
-
const level = deriveNavLevel({ weekNumber: selectedWeek, workoutId: selectedWorkoutId });
|
|
11811
|
-
const activeWeek = findWeek(activeMeso, selectedWeek);
|
|
11812
|
-
const activeWorkout = findWorkout(activeWeek, selectedWorkoutId);
|
|
11813
|
-
const selectMeso = (id) => {
|
|
11814
|
-
setActiveMesoId(id);
|
|
11815
|
-
setSelectedWeek(null);
|
|
11816
|
-
setSelectedWorkoutId(null);
|
|
11817
|
-
};
|
|
11818
|
-
const selectWeek = (weekNumber) => {
|
|
11819
|
-
setSelectedWeek(weekNumber);
|
|
11820
|
-
setSelectedWorkoutId(null);
|
|
11821
|
-
setExpandedExercise(null);
|
|
11822
|
-
};
|
|
11823
|
-
const selectWorkout = (id) => {
|
|
11824
|
-
setSelectedWorkoutId(id);
|
|
11825
|
-
setExpandedExercise(null);
|
|
11826
|
-
};
|
|
11827
|
-
const toggleExercise = (index) => setExpandedExercise((current) => current === index ? null : index);
|
|
11828
|
-
const navigateTo = (target) => {
|
|
11829
|
-
if (target === "meso") selectMeso(activeMesoId ?? mesos[0]?.id);
|
|
11830
|
-
else if (target === "week" && selectedWeek != null) selectWeek(selectedWeek);
|
|
11831
|
-
};
|
|
11832
|
-
return /* @__PURE__ */ jsx(
|
|
11833
|
-
reactNative.View,
|
|
11834
|
-
{
|
|
11835
|
-
className: cn(className, "bg-background-base"),
|
|
11836
|
-
style: { width: 390 },
|
|
11837
|
-
accessibilityRole: "main",
|
|
11838
|
-
"aria-label": title,
|
|
11839
|
-
testID: "program-planning-page",
|
|
11840
|
-
...props,
|
|
11841
|
-
children: /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 16, gap: 14 }, testID: "program-planning-page-content", children: [
|
|
11842
|
-
/* @__PURE__ */ jsx(
|
|
11843
|
-
reactNative.Text,
|
|
11844
|
-
{
|
|
11845
|
-
accessibilityRole: "header",
|
|
11846
|
-
className: "text-text-primary",
|
|
11847
|
-
style: {
|
|
11848
|
-
fontSize: 20,
|
|
11849
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
11850
|
-
fontWeight: "700"
|
|
11851
|
-
},
|
|
11852
|
-
testID: "program-planning-page-title",
|
|
11853
|
-
children: title
|
|
11854
|
-
}
|
|
11855
|
-
),
|
|
11856
|
-
/* @__PURE__ */ jsx(reactNative.View, { style: { marginHorizontal: -16 }, children: /* @__PURE__ */ jsx(
|
|
11857
|
-
MesoProgressBar,
|
|
11858
|
-
{
|
|
11859
|
-
mesos: progressMesos,
|
|
11860
|
-
activeMesoId,
|
|
11861
|
-
onMesoPress: selectMeso
|
|
11862
|
-
}
|
|
11863
|
-
) }),
|
|
11864
|
-
activeMeso != null && /* @__PURE__ */ jsx(
|
|
11865
|
-
Breadcrumbs2,
|
|
11866
|
-
{
|
|
11867
|
-
crumbs: buildBreadcrumbs(activeMeso, selectedWeek, activeWorkout),
|
|
11868
|
-
onNavigate: navigateTo
|
|
11869
|
-
}
|
|
11870
|
-
),
|
|
11871
|
-
/* @__PURE__ */ jsxs(
|
|
11872
|
-
reactNative.View,
|
|
11873
|
-
{
|
|
11874
|
-
className: "bg-surface-elevated border-border",
|
|
11875
|
-
style: {
|
|
11876
|
-
borderWidth: 1,
|
|
11877
|
-
borderRadius: 12,
|
|
11878
|
-
padding: 12
|
|
11879
|
-
},
|
|
11880
|
-
testID: "program-planning-page-body",
|
|
11881
|
-
children: [
|
|
11882
|
-
level === "meso" && /* @__PURE__ */ jsx(
|
|
11883
|
-
MesoLevel,
|
|
11884
|
-
{
|
|
11885
|
-
mesos,
|
|
11886
|
-
activeMesoId,
|
|
11887
|
-
onSelectMeso: selectMeso,
|
|
11888
|
-
onSelectWeek: selectWeek
|
|
11889
|
-
}
|
|
11890
|
-
),
|
|
11891
|
-
level === "week" && activeWeek != null && /* @__PURE__ */ jsx(WeekLevel, { week: activeWeek, onSelectWorkout: selectWorkout }),
|
|
11892
|
-
level === "workout" && activeWorkout != null && /* @__PURE__ */ jsx(
|
|
11893
|
-
WorkoutLevel,
|
|
11894
|
-
{
|
|
11895
|
-
workout: activeWorkout,
|
|
11896
|
-
expandedExercise,
|
|
11897
|
-
onToggleExercise: toggleExercise
|
|
11898
|
-
}
|
|
11899
|
-
)
|
|
11900
|
-
]
|
|
11901
|
-
}
|
|
11902
|
-
)
|
|
11903
|
-
] })
|
|
11904
|
-
}
|
|
11905
|
-
);
|
|
11906
|
-
}
|
|
11907
|
-
var BRAND_PRIMARY12 = "#FF7900";
|
|
11908
|
-
var CHART_WIDTH = 326;
|
|
11909
|
-
var CHART_HEIGHT = 150;
|
|
11910
|
-
var EXERCISE_DETAIL_TABS = [
|
|
11911
|
-
{ key: "progress", label: "Progress" },
|
|
11912
|
-
{ key: "history", label: "History" },
|
|
11913
|
-
{ key: "advanced", label: "Advanced" }
|
|
11914
|
-
];
|
|
11915
|
-
function deriveExerciseStats(entries) {
|
|
11916
|
-
let bestE1rm = null;
|
|
11917
|
-
let prCount = 0;
|
|
11918
|
-
for (const entry of entries) {
|
|
11919
|
-
if (entry.e1rm != null && (bestE1rm == null || entry.e1rm.value > bestE1rm)) {
|
|
11920
|
-
bestE1rm = entry.e1rm.value;
|
|
11921
|
-
}
|
|
11922
|
-
if (entry.isPR) prCount += 1;
|
|
11923
|
-
}
|
|
11924
|
-
return { sessions: entries.length, bestE1rm, prCount };
|
|
11925
|
-
}
|
|
11926
|
-
function toExerciseCardProps(entry, expanded, onToggle) {
|
|
11927
|
-
return {
|
|
11928
|
-
name: entry.title,
|
|
11929
|
-
state: expanded ? "expanded" : "collapsed",
|
|
11930
|
-
onToggle,
|
|
11931
|
-
summary: entry.summary,
|
|
11932
|
-
e1rm: entry.e1rm,
|
|
11933
|
-
isPR: entry.isPR,
|
|
11934
|
-
tempo: entry.tempo,
|
|
11935
|
-
sets: expanded ? entry.sets : void 0
|
|
11936
|
-
};
|
|
11937
|
-
}
|
|
11938
|
-
function summarizeVbt(sets) {
|
|
11939
|
-
const allReps = sets.flatMap((set) => set.velocities);
|
|
11940
|
-
const meanVelocity = calculateMeanVelocity(allReps);
|
|
11941
|
-
let velocityLoss = 0;
|
|
11942
|
-
for (const set of sets) {
|
|
11943
|
-
velocityLoss = Math.max(velocityLoss, calculateVelocityLoss(set.velocities));
|
|
11944
|
-
}
|
|
11945
|
-
return { meanVelocity, velocityLoss };
|
|
11946
|
-
}
|
|
11947
|
-
function TabBar({ active, onSelect }) {
|
|
11948
|
-
return /* @__PURE__ */ jsx(
|
|
11949
|
-
reactNative.View,
|
|
11950
|
-
{
|
|
11951
|
-
className: "flex-row",
|
|
11952
|
-
style: {
|
|
11953
|
-
borderBottomWidth: 1,
|
|
11954
|
-
borderBottomColor: resolveColor("border-default")
|
|
11955
|
-
},
|
|
11956
|
-
accessibilityRole: "tablist",
|
|
11957
|
-
testID: "exercise-detail-page-tabs",
|
|
11958
|
-
children: EXERCISE_DETAIL_TABS.map(({ key, label }) => {
|
|
11959
|
-
const isActive = key === active;
|
|
11960
|
-
return /* @__PURE__ */ jsx(
|
|
11961
|
-
reactNative.Pressable,
|
|
11962
|
-
{
|
|
11963
|
-
onPress: () => onSelect(key),
|
|
11964
|
-
accessibilityRole: "tab",
|
|
11965
|
-
accessibilityState: { selected: isActive },
|
|
11966
|
-
style: {
|
|
11967
|
-
flex: 1,
|
|
11968
|
-
alignItems: "center",
|
|
11969
|
-
paddingVertical: 10,
|
|
11970
|
-
borderBottomWidth: 2,
|
|
11971
|
-
borderBottomColor: isActive ? BRAND_PRIMARY12 : "transparent"
|
|
11972
|
-
},
|
|
11973
|
-
testID: `exercise-detail-page-tab-${key}`,
|
|
11974
|
-
children: /* @__PURE__ */ jsx(
|
|
11975
|
-
reactNative.Text,
|
|
11976
|
-
{
|
|
11977
|
-
className: isActive ? "text-text-primary" : "text-text-tertiary",
|
|
11978
|
-
style: {
|
|
11979
|
-
fontSize: 13,
|
|
11980
|
-
fontFamily: "Inter, sans-serif",
|
|
11981
|
-
fontWeight: isActive ? "700" : "500"
|
|
11982
|
-
},
|
|
11983
|
-
children: label
|
|
11984
|
-
}
|
|
11985
|
-
)
|
|
11986
|
-
},
|
|
11987
|
-
key
|
|
11988
|
-
);
|
|
11989
|
-
})
|
|
11990
|
-
}
|
|
11991
|
-
);
|
|
11992
|
-
}
|
|
11993
|
-
var STAT_CARDS = [
|
|
11994
|
-
{ key: "sessions", label: "Sessions" },
|
|
11995
|
-
{ key: "bestE1rm", label: "Best e1RM" },
|
|
11996
|
-
{ key: "prCount", label: "PRs" }
|
|
11997
|
-
];
|
|
11998
|
-
function StatStrip({ stats, unit }) {
|
|
11999
|
-
return /* @__PURE__ */ jsx(reactNative.View, { className: "flex-row", style: { gap: 8 }, testID: "exercise-detail-page-stats", children: STAT_CARDS.map(({ key, label }) => {
|
|
12000
|
-
const raw = stats[key];
|
|
12001
|
-
const value = key === "bestE1rm" ? raw == null ? "\u2014" : `${raw} ${unit}` : `${raw}`;
|
|
12002
|
-
return /* @__PURE__ */ jsxs(
|
|
12003
|
-
reactNative.View,
|
|
12004
|
-
{
|
|
12005
|
-
className: "bg-surface-elevated border-border",
|
|
12006
|
-
style: {
|
|
12007
|
-
flex: 1,
|
|
12008
|
-
padding: 12,
|
|
12009
|
-
borderRadius: 10,
|
|
12010
|
-
borderWidth: 1
|
|
12011
|
-
},
|
|
12012
|
-
testID: `exercise-detail-page-stat-${key}`,
|
|
12013
|
-
children: [
|
|
12014
|
-
/* @__PURE__ */ jsx(
|
|
12015
|
-
reactNative.Text,
|
|
12016
|
-
{
|
|
12017
|
-
className: "text-text-primary",
|
|
12018
|
-
style: {
|
|
12019
|
-
fontSize: 18,
|
|
12020
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
12021
|
-
fontWeight: "700"
|
|
12022
|
-
},
|
|
12023
|
-
children: value
|
|
12024
|
-
}
|
|
12025
|
-
),
|
|
12026
|
-
/* @__PURE__ */ jsx(
|
|
12027
|
-
reactNative.Text,
|
|
12028
|
-
{
|
|
12029
|
-
className: "text-text-tertiary",
|
|
12030
|
-
style: {
|
|
12031
|
-
marginTop: 2,
|
|
12032
|
-
fontSize: 10,
|
|
12033
|
-
fontFamily: "Inter, sans-serif",
|
|
12034
|
-
fontWeight: "600",
|
|
12035
|
-
letterSpacing: 0.4,
|
|
12036
|
-
textTransform: "uppercase"
|
|
12037
|
-
},
|
|
12038
|
-
children: label
|
|
12039
|
-
}
|
|
12040
|
-
)
|
|
12041
|
-
]
|
|
12042
|
-
},
|
|
12043
|
-
key
|
|
12044
|
-
);
|
|
12045
|
-
}) });
|
|
12046
|
-
}
|
|
12047
|
-
function SectionCard({
|
|
12048
|
-
title,
|
|
12049
|
-
children,
|
|
12050
|
-
testID
|
|
12051
|
-
}) {
|
|
12052
|
-
return /* @__PURE__ */ jsxs(
|
|
12053
|
-
reactNative.View,
|
|
12054
|
-
{
|
|
12055
|
-
className: "bg-surface-elevated border-border",
|
|
12056
|
-
style: {
|
|
12057
|
-
borderWidth: 1,
|
|
12058
|
-
borderRadius: 12,
|
|
12059
|
-
padding: 12,
|
|
12060
|
-
gap: 10
|
|
12061
|
-
},
|
|
12062
|
-
testID,
|
|
12063
|
-
children: [
|
|
12064
|
-
/* @__PURE__ */ jsx(
|
|
12065
|
-
reactNative.Text,
|
|
12066
|
-
{
|
|
12067
|
-
className: "text-text-tertiary",
|
|
12068
|
-
style: {
|
|
12069
|
-
fontSize: 11,
|
|
12070
|
-
fontFamily: "Inter, sans-serif",
|
|
12071
|
-
fontWeight: "600",
|
|
12072
|
-
letterSpacing: 0.6,
|
|
12073
|
-
textTransform: "uppercase"
|
|
12074
|
-
},
|
|
12075
|
-
children: title
|
|
12076
|
-
}
|
|
12077
|
-
),
|
|
12078
|
-
children
|
|
12079
|
-
]
|
|
12080
|
-
}
|
|
12081
|
-
);
|
|
12082
|
-
}
|
|
12083
|
-
function EntryList({ entries, expandedId, onToggle, emptyLabel, testID }) {
|
|
12084
|
-
if (entries.length === 0) {
|
|
12085
|
-
return /* @__PURE__ */ jsx(
|
|
12086
|
-
reactNative.Text,
|
|
12087
|
-
{
|
|
12088
|
-
className: "text-text-tertiary",
|
|
12089
|
-
style: { fontSize: 12, fontFamily: "Inter, sans-serif" },
|
|
12090
|
-
testID: `${testID}-empty`,
|
|
12091
|
-
children: emptyLabel
|
|
12092
|
-
}
|
|
12093
|
-
);
|
|
12094
|
-
}
|
|
12095
|
-
return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 8 }, testID, children: entries.map((entry) => /* @__PURE__ */ jsx(
|
|
12096
|
-
ExerciseCard,
|
|
12097
|
-
{
|
|
12098
|
-
...toExerciseCardProps(entry, entry.id === expandedId, () => onToggle(entry.id))
|
|
12099
|
-
},
|
|
12100
|
-
entry.id
|
|
12101
|
-
)) });
|
|
12102
|
-
}
|
|
12103
|
-
function VbtBreakdown({ sets }) {
|
|
12104
|
-
return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 10 }, testID: "exercise-detail-page-vbt-breakdown", children: sets.map((set) => {
|
|
12105
|
-
const mean = calculateMeanVelocity(set.velocities);
|
|
12106
|
-
const loss = calculateVelocityLoss(set.velocities);
|
|
12107
|
-
return /* @__PURE__ */ jsxs(
|
|
12108
|
-
reactNative.View,
|
|
12109
|
-
{
|
|
12110
|
-
className: "flex-row items-center",
|
|
12111
|
-
style: { gap: 10 },
|
|
12112
|
-
testID: "exercise-detail-page-vbt-set",
|
|
12113
|
-
children: [
|
|
12114
|
-
/* @__PURE__ */ jsx(
|
|
12115
|
-
reactNative.Text,
|
|
12116
|
-
{
|
|
12117
|
-
className: "text-text-secondary",
|
|
12118
|
-
style: {
|
|
12119
|
-
width: 44,
|
|
12120
|
-
fontSize: 11,
|
|
12121
|
-
fontFamily: "Inter, sans-serif",
|
|
12122
|
-
fontWeight: "600"
|
|
12123
|
-
},
|
|
12124
|
-
children: set.label
|
|
12125
|
-
}
|
|
12126
|
-
),
|
|
12127
|
-
/* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(VelocityStrip, { velocities: set.velocities, variant: "mini" }) }),
|
|
12128
|
-
/* @__PURE__ */ jsx(
|
|
12129
|
-
reactNative.Text,
|
|
12130
|
-
{
|
|
12131
|
-
className: "text-text-tertiary",
|
|
12132
|
-
style: {
|
|
12133
|
-
fontSize: 11,
|
|
12134
|
-
fontFamily: "Inter, sans-serif",
|
|
12135
|
-
fontVariant: ["tabular-nums"]
|
|
12136
|
-
},
|
|
12137
|
-
children: `${mean.toFixed(2)} \xB7 -${loss}%`
|
|
12138
|
-
}
|
|
12139
|
-
)
|
|
12140
|
-
]
|
|
12141
|
-
},
|
|
12142
|
-
set.label
|
|
12143
|
-
);
|
|
12144
|
-
}) });
|
|
12145
|
-
}
|
|
12146
|
-
function VbtSummaryRow({ summary }) {
|
|
12147
|
-
const cells = [
|
|
12148
|
-
{ label: "Mean Velocity", value: `${summary.meanVelocity.toFixed(2)} m/s` },
|
|
12149
|
-
{ label: "Peak Vel. Loss", value: `-${summary.velocityLoss}%` }
|
|
12150
|
-
];
|
|
12151
|
-
return /* @__PURE__ */ jsx(reactNative.View, { className: "flex-row", style: { gap: 8 }, testID: "exercise-detail-page-vbt-summary", children: cells.map((cell) => /* @__PURE__ */ jsxs(
|
|
12152
|
-
reactNative.View,
|
|
12153
|
-
{
|
|
12154
|
-
className: "bg-surface-elevated border-border",
|
|
12155
|
-
style: {
|
|
12156
|
-
flex: 1,
|
|
12157
|
-
padding: 12,
|
|
12158
|
-
borderRadius: 10,
|
|
12159
|
-
borderWidth: 1
|
|
12160
|
-
},
|
|
12161
|
-
children: [
|
|
12162
|
-
/* @__PURE__ */ jsx(
|
|
12163
|
-
reactNative.Text,
|
|
12164
|
-
{
|
|
12165
|
-
className: "text-text-primary",
|
|
12166
|
-
style: {
|
|
12167
|
-
fontSize: 16,
|
|
12168
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
12169
|
-
fontWeight: "700"
|
|
12170
|
-
},
|
|
12171
|
-
children: cell.value
|
|
12172
|
-
}
|
|
12173
|
-
),
|
|
12174
|
-
/* @__PURE__ */ jsx(
|
|
12175
|
-
reactNative.Text,
|
|
12176
|
-
{
|
|
12177
|
-
className: "text-text-tertiary",
|
|
12178
|
-
style: {
|
|
12179
|
-
marginTop: 2,
|
|
12180
|
-
fontSize: 10,
|
|
12181
|
-
fontFamily: "Inter, sans-serif",
|
|
12182
|
-
fontWeight: "600",
|
|
12183
|
-
letterSpacing: 0.4,
|
|
12184
|
-
textTransform: "uppercase"
|
|
12185
|
-
},
|
|
12186
|
-
children: cell.label
|
|
12187
|
-
}
|
|
12188
|
-
)
|
|
12189
|
-
]
|
|
12190
|
-
},
|
|
12191
|
-
cell.label
|
|
12192
|
-
)) });
|
|
12193
|
-
}
|
|
12194
|
-
function ExerciseDetailPage({
|
|
12195
|
-
exercise,
|
|
12196
|
-
meso,
|
|
12197
|
-
trend,
|
|
12198
|
-
progression,
|
|
12199
|
-
history,
|
|
12200
|
-
vbt,
|
|
12201
|
-
className,
|
|
12202
|
-
...props
|
|
12203
|
-
}) {
|
|
12204
|
-
const [tab, setTab] = React24.useState("progress");
|
|
12205
|
-
const [expandedProgressId, setExpandedProgressId] = React24.useState(null);
|
|
12206
|
-
const [expandedHistoryId, setExpandedHistoryId] = React24.useState(null);
|
|
12207
|
-
const stats = React24.useMemo(() => deriveExerciseStats(history), [history]);
|
|
12208
|
-
const vbtSummary = React24.useMemo(() => summarizeVbt(vbt.sets), [vbt.sets]);
|
|
12209
|
-
const toggle = (setId) => (id) => setId((current) => current === id ? null : id);
|
|
12210
|
-
return /* @__PURE__ */ jsx(
|
|
12211
|
-
reactNative.View,
|
|
12212
|
-
{
|
|
12213
|
-
className: cn("bg-background-base", className),
|
|
12214
|
-
style: { width: 390 },
|
|
12215
|
-
accessibilityRole: "main",
|
|
12216
|
-
"aria-label": exercise.name,
|
|
12217
|
-
testID: "exercise-detail-page",
|
|
12218
|
-
...props,
|
|
12219
|
-
children: /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 16, gap: 14 }, testID: "exercise-detail-page-content", children: [
|
|
12220
|
-
/* @__PURE__ */ jsxs(
|
|
12221
|
-
reactNative.View,
|
|
12222
|
-
{
|
|
12223
|
-
className: "flex-row items-start justify-between",
|
|
12224
|
-
style: { gap: 8 },
|
|
12225
|
-
testID: "exercise-detail-page-header",
|
|
12226
|
-
children: [
|
|
12227
|
-
/* @__PURE__ */ jsxs(reactNative.View, { style: { flexShrink: 1 }, children: [
|
|
12228
|
-
/* @__PURE__ */ jsx(
|
|
12229
|
-
reactNative.Text,
|
|
12230
|
-
{
|
|
12231
|
-
accessibilityRole: "header",
|
|
12232
|
-
className: "text-text-primary",
|
|
12233
|
-
style: {
|
|
12234
|
-
fontSize: 20,
|
|
12235
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
12236
|
-
fontWeight: "700"
|
|
12237
|
-
},
|
|
12238
|
-
testID: "exercise-detail-page-title",
|
|
12239
|
-
children: exercise.name
|
|
12240
|
-
}
|
|
12241
|
-
),
|
|
12242
|
-
/* @__PURE__ */ jsx(
|
|
12243
|
-
reactNative.Text,
|
|
12244
|
-
{
|
|
12245
|
-
className: "text-text-secondary",
|
|
12246
|
-
style: {
|
|
12247
|
-
marginTop: 2,
|
|
12248
|
-
fontSize: 12,
|
|
12249
|
-
fontFamily: "Inter, sans-serif"
|
|
12250
|
-
},
|
|
12251
|
-
testID: "exercise-detail-page-subtitle",
|
|
12252
|
-
children: exercise.subtitle
|
|
12253
|
-
}
|
|
12254
|
-
)
|
|
12255
|
-
] }),
|
|
12256
|
-
exercise.currentE1rm != null && /* @__PURE__ */ jsxs(
|
|
12257
|
-
reactNative.View,
|
|
12258
|
-
{
|
|
12259
|
-
style: {
|
|
12260
|
-
paddingVertical: 4,
|
|
12261
|
-
paddingHorizontal: 10,
|
|
12262
|
-
borderRadius: 6,
|
|
12263
|
-
backgroundColor: "rgba(255,121,0,0.10)",
|
|
12264
|
-
borderWidth: 1,
|
|
12265
|
-
borderColor: "rgba(255,121,0,0.25)"
|
|
12266
|
-
},
|
|
12267
|
-
testID: "exercise-detail-page-e1rm",
|
|
12268
|
-
children: [
|
|
12269
|
-
/* @__PURE__ */ jsx(
|
|
12270
|
-
reactNative.Text,
|
|
12271
|
-
{
|
|
12272
|
-
className: "text-text-tertiary",
|
|
12273
|
-
style: {
|
|
12274
|
-
fontSize: 9,
|
|
12275
|
-
fontFamily: "Inter, sans-serif",
|
|
12276
|
-
fontWeight: "600",
|
|
12277
|
-
letterSpacing: 0.4,
|
|
12278
|
-
textTransform: "uppercase"
|
|
12279
|
-
},
|
|
12280
|
-
children: "e1RM"
|
|
12281
|
-
}
|
|
12282
|
-
),
|
|
12283
|
-
/* @__PURE__ */ jsx(
|
|
12284
|
-
reactNative.Text,
|
|
12285
|
-
{
|
|
12286
|
-
style: {
|
|
12287
|
-
fontSize: 15,
|
|
12288
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
12289
|
-
fontWeight: "700",
|
|
12290
|
-
color: BRAND_PRIMARY12
|
|
12291
|
-
},
|
|
12292
|
-
children: `${exercise.currentE1rm} ${exercise.unit}`
|
|
12293
|
-
}
|
|
12294
|
-
)
|
|
12295
|
-
]
|
|
12296
|
-
}
|
|
12297
|
-
)
|
|
12298
|
-
]
|
|
12299
|
-
}
|
|
12300
|
-
),
|
|
12301
|
-
/* @__PURE__ */ jsx(TabBar, { active: tab, onSelect: setTab }),
|
|
12302
|
-
tab === "progress" && /* @__PURE__ */ jsxs(reactNative.View, { style: { gap: 14 }, testID: "exercise-detail-page-panel-progress", children: [
|
|
12303
|
-
/* @__PURE__ */ jsx(MesoStatusCard, { ...meso }),
|
|
12304
|
-
/* @__PURE__ */ jsx(StatStrip, { stats, unit: exercise.unit }),
|
|
12305
|
-
/* @__PURE__ */ jsx(SectionCard, { title: "Strength Trend", testID: "exercise-detail-page-trend", children: /* @__PURE__ */ jsx(
|
|
12306
|
-
StrengthTrendChart,
|
|
12307
|
-
{
|
|
12308
|
-
data: trend.data,
|
|
12309
|
-
projection: trend.projection,
|
|
12310
|
-
mesoBoundaries: trend.mesoBoundaries,
|
|
12311
|
-
width: CHART_WIDTH,
|
|
12312
|
-
height: CHART_HEIGHT,
|
|
12313
|
-
unit: exercise.unit,
|
|
12314
|
-
animateOnMount: false
|
|
12315
|
-
}
|
|
12316
|
-
) }),
|
|
12317
|
-
/* @__PURE__ */ jsx(SectionCard, { title: "Week-by-Week", testID: "exercise-detail-page-progression", children: /* @__PURE__ */ jsx(
|
|
12318
|
-
EntryList,
|
|
12319
|
-
{
|
|
12320
|
-
entries: progression,
|
|
12321
|
-
expandedId: expandedProgressId,
|
|
12322
|
-
onToggle: toggle(setExpandedProgressId),
|
|
12323
|
-
emptyLabel: "No progression logged yet",
|
|
12324
|
-
testID: "exercise-detail-page-progression-list"
|
|
12325
|
-
}
|
|
12326
|
-
) })
|
|
12327
|
-
] }),
|
|
12328
|
-
tab === "history" && /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 14 }, testID: "exercise-detail-page-panel-history", children: /* @__PURE__ */ jsx(SectionCard, { title: "All Sessions", testID: "exercise-detail-page-history", children: /* @__PURE__ */ jsx(
|
|
12329
|
-
EntryList,
|
|
12330
|
-
{
|
|
12331
|
-
entries: history,
|
|
12332
|
-
expandedId: expandedHistoryId,
|
|
12333
|
-
onToggle: toggle(setExpandedHistoryId),
|
|
12334
|
-
emptyLabel: "No sessions logged yet",
|
|
12335
|
-
testID: "exercise-detail-page-history-list"
|
|
12336
|
-
}
|
|
12337
|
-
) }) }),
|
|
12338
|
-
tab === "advanced" && /* @__PURE__ */ jsxs(reactNative.View, { style: { gap: 14 }, testID: "exercise-detail-page-panel-advanced", children: [
|
|
12339
|
-
/* @__PURE__ */ jsx(SectionCard, { title: "Capacity Band", testID: "exercise-detail-page-capacity", children: /* @__PURE__ */ jsx(
|
|
12340
|
-
CapacityBandChart,
|
|
12341
|
-
{
|
|
12342
|
-
band: vbt.band,
|
|
12343
|
-
workouts: vbt.workouts,
|
|
12344
|
-
projection: vbt.projection,
|
|
12345
|
-
width: CHART_WIDTH,
|
|
12346
|
-
height: CHART_HEIGHT
|
|
12347
|
-
}
|
|
12348
|
-
) }),
|
|
12349
|
-
/* @__PURE__ */ jsx(VbtSummaryRow, { summary: vbtSummary }),
|
|
12350
|
-
/* @__PURE__ */ jsx(SectionCard, { title: "Velocity Breakdown", testID: "exercise-detail-page-velocity", children: /* @__PURE__ */ jsx(VbtBreakdown, { sets: vbt.sets }) })
|
|
12351
|
-
] })
|
|
12352
|
-
] })
|
|
12353
|
-
}
|
|
12354
|
-
);
|
|
12355
|
-
}
|
|
12356
|
-
var BRAND_PRIMARY13 = "#FF7900";
|
|
12357
|
-
function countCompletedSets(exercise) {
|
|
12358
|
-
if (exercise.setVelocities != null) return exercise.setVelocities.length;
|
|
12359
|
-
if (exercise.status === "completed") return exercise.totalPlannedSets;
|
|
12360
|
-
return 0;
|
|
12361
|
-
}
|
|
12362
|
-
function deriveWorkoutProgress(exercises) {
|
|
12363
|
-
let completedExercises = 0;
|
|
12364
|
-
let completedSets = 0;
|
|
12365
|
-
let totalSets = 0;
|
|
12366
|
-
for (const exercise of exercises) {
|
|
12367
|
-
if (exercise.status === "completed") completedExercises += 1;
|
|
12368
|
-
completedSets += countCompletedSets(exercise);
|
|
12369
|
-
totalSets += exercise.totalPlannedSets;
|
|
12370
|
-
}
|
|
12371
|
-
return {
|
|
12372
|
-
completedExercises,
|
|
12373
|
-
totalExercises: exercises.length,
|
|
12374
|
-
completedSets,
|
|
12375
|
-
totalSets,
|
|
12376
|
-
fraction: totalSets === 0 ? 0 : completedSets / totalSets
|
|
12377
|
-
};
|
|
12378
|
-
}
|
|
12379
|
-
function findActiveExercise(exercises) {
|
|
12380
|
-
return exercises.find((exercise) => exercise.status === "active") ?? null;
|
|
12381
|
-
}
|
|
12382
|
-
function statusToCardState(status, focused) {
|
|
12383
|
-
if (status === "upcoming") return "upcoming";
|
|
12384
|
-
return focused ? "expanded" : "collapsed";
|
|
12385
|
-
}
|
|
12386
|
-
function toActiveCardProps(exercise, focused, onToggle, supersetPosition = null, supersetColor) {
|
|
12387
|
-
const state = statusToCardState(exercise.status, focused);
|
|
12388
|
-
return {
|
|
12389
|
-
name: exercise.name,
|
|
12390
|
-
state,
|
|
12391
|
-
onToggle,
|
|
12392
|
-
summary: exercise.summary,
|
|
12393
|
-
e1rm: exercise.e1rm,
|
|
12394
|
-
isPR: exercise.isPR,
|
|
12395
|
-
tempo: exercise.tempo,
|
|
12396
|
-
setVelocities: exercise.setVelocities,
|
|
12397
|
-
totalPlannedSets: exercise.totalPlannedSets,
|
|
12398
|
-
sets: state === "expanded" ? exercise.sets : void 0,
|
|
12399
|
-
prescription: exercise.prescription,
|
|
12400
|
-
previousBest: exercise.previousBest,
|
|
12401
|
-
supersetPosition,
|
|
12402
|
-
supersetColor
|
|
12403
|
-
};
|
|
12404
|
-
}
|
|
12405
|
-
function groupExercises(exercises, supersets) {
|
|
12406
|
-
const byId = new Map(supersets.map((superset) => [superset.id, superset]));
|
|
12407
|
-
const groups = [];
|
|
12408
|
-
for (const exercise of exercises) {
|
|
12409
|
-
const superset = exercise.supersetId != null ? byId.get(exercise.supersetId) : void 0;
|
|
12410
|
-
const last = groups[groups.length - 1];
|
|
12411
|
-
if (superset != null && last?.type === "superset" && last.superset.id === superset.id) {
|
|
12412
|
-
last.exercises.push(exercise);
|
|
12413
|
-
} else if (superset != null) {
|
|
12414
|
-
groups.push({ type: "superset", superset, exercises: [exercise] });
|
|
12415
|
-
} else {
|
|
12416
|
-
groups.push({ type: "single", exercise });
|
|
12417
|
-
}
|
|
12418
|
-
}
|
|
12419
|
-
return groups;
|
|
12420
|
-
}
|
|
12421
|
-
function supersetPositionAt(index, length) {
|
|
12422
|
-
if (length === 1) return null;
|
|
12423
|
-
if (index === 0) return "first";
|
|
12424
|
-
if (index === length - 1) return "last";
|
|
12425
|
-
return "middle";
|
|
12426
|
-
}
|
|
12427
|
-
function WorkoutHeader({
|
|
12428
|
-
title,
|
|
12429
|
-
subtitle,
|
|
12430
|
-
progress
|
|
12431
|
-
}) {
|
|
12432
|
-
return /* @__PURE__ */ jsxs(reactNative.View, { style: { gap: 10 }, testID: "active-workout-page-header", children: [
|
|
12433
|
-
/* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-start justify-between", style: { gap: 8 }, children: [
|
|
12434
|
-
/* @__PURE__ */ jsxs(reactNative.View, { style: { flexShrink: 1 }, children: [
|
|
12435
|
-
/* @__PURE__ */ jsx(
|
|
12436
|
-
reactNative.Text,
|
|
12437
|
-
{
|
|
12438
|
-
accessibilityRole: "header",
|
|
12439
|
-
className: "text-text-primary",
|
|
12440
|
-
style: {
|
|
12441
|
-
fontSize: 20,
|
|
12442
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
12443
|
-
fontWeight: "700"
|
|
12444
|
-
},
|
|
12445
|
-
testID: "active-workout-page-title",
|
|
12446
|
-
children: title
|
|
12447
|
-
}
|
|
12448
|
-
),
|
|
12449
|
-
subtitle != null && /* @__PURE__ */ jsx(
|
|
12450
|
-
reactNative.Text,
|
|
12451
|
-
{
|
|
12452
|
-
className: "text-text-secondary",
|
|
12453
|
-
style: {
|
|
12454
|
-
marginTop: 2,
|
|
12455
|
-
fontSize: 12,
|
|
12456
|
-
fontFamily: "Inter, sans-serif"
|
|
12457
|
-
},
|
|
12458
|
-
testID: "active-workout-page-subtitle",
|
|
12459
|
-
children: subtitle
|
|
12460
|
-
}
|
|
12461
|
-
)
|
|
12462
|
-
] }),
|
|
12463
|
-
/* @__PURE__ */ jsxs(reactNative.View, { style: { alignItems: "flex-end" }, testID: "active-workout-page-progress-count", children: [
|
|
12464
|
-
/* @__PURE__ */ jsx(
|
|
12465
|
-
reactNative.Text,
|
|
12466
|
-
{
|
|
12467
|
-
style: {
|
|
12468
|
-
fontSize: 15,
|
|
12469
|
-
fontFamily: '"Space Grotesk", sans-serif',
|
|
12470
|
-
fontWeight: "700",
|
|
12471
|
-
color: BRAND_PRIMARY13,
|
|
12472
|
-
fontVariant: ["tabular-nums"]
|
|
12473
|
-
},
|
|
12474
|
-
children: `${progress.completedExercises}/${progress.totalExercises}`
|
|
12475
|
-
}
|
|
12476
|
-
),
|
|
12477
|
-
/* @__PURE__ */ jsx(
|
|
12478
|
-
reactNative.Text,
|
|
12479
|
-
{
|
|
12480
|
-
className: "text-text-tertiary",
|
|
12481
|
-
style: {
|
|
12482
|
-
fontSize: 10,
|
|
12483
|
-
fontFamily: "Inter, sans-serif",
|
|
12484
|
-
fontWeight: "600",
|
|
12485
|
-
letterSpacing: 0.4,
|
|
12486
|
-
textTransform: "uppercase"
|
|
12487
|
-
},
|
|
12488
|
-
children: `${progress.completedSets}/${progress.totalSets} sets`
|
|
12489
|
-
}
|
|
12490
|
-
)
|
|
12491
|
-
] })
|
|
12492
|
-
] }),
|
|
12493
|
-
/* @__PURE__ */ jsx(
|
|
11633
|
+
if (band.length === 0) {
|
|
11634
|
+
return /* @__PURE__ */ jsx(
|
|
12494
11635
|
reactNative.View,
|
|
12495
11636
|
{
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
style: {
|
|
12503
|
-
height: "100%",
|
|
12504
|
-
width: `${Math.round(progress.fraction * 100)}%`,
|
|
12505
|
-
backgroundColor: BRAND_PRIMARY13,
|
|
12506
|
-
borderRadius: 2
|
|
12507
|
-
},
|
|
12508
|
-
testID: "active-workout-page-progress-fill"
|
|
12509
|
-
}
|
|
12510
|
-
)
|
|
11637
|
+
style: { width, height },
|
|
11638
|
+
className,
|
|
11639
|
+
accessibilityRole: "image",
|
|
11640
|
+
accessibilityLabel: "Training capacity band chart, no data",
|
|
11641
|
+
testID: "capacity-band-chart-empty",
|
|
11642
|
+
...props
|
|
12511
11643
|
}
|
|
12512
|
-
)
|
|
12513
|
-
] });
|
|
12514
|
-
}
|
|
12515
|
-
function ExerciseList({ groups, focusedId, onToggle }) {
|
|
12516
|
-
return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 10 }, testID: "active-workout-page-exercises", children: groups.map((group) => {
|
|
12517
|
-
if (group.type === "single") {
|
|
12518
|
-
return /* @__PURE__ */ jsx(
|
|
12519
|
-
reactNative.View,
|
|
12520
|
-
{
|
|
12521
|
-
className: "bg-surface-elevated border-border",
|
|
12522
|
-
style: {
|
|
12523
|
-
borderWidth: 1,
|
|
12524
|
-
borderRadius: 12
|
|
12525
|
-
},
|
|
12526
|
-
children: /* @__PURE__ */ jsx(
|
|
12527
|
-
ExerciseCard,
|
|
12528
|
-
{
|
|
12529
|
-
...toActiveCardProps(
|
|
12530
|
-
group.exercise,
|
|
12531
|
-
group.exercise.id === focusedId,
|
|
12532
|
-
() => onToggle(group.exercise.id)
|
|
12533
|
-
)
|
|
12534
|
-
}
|
|
12535
|
-
)
|
|
12536
|
-
},
|
|
12537
|
-
group.exercise.id
|
|
12538
|
-
);
|
|
12539
|
-
}
|
|
12540
|
-
return /* @__PURE__ */ jsx(
|
|
12541
|
-
SupersetWrapper,
|
|
12542
|
-
{
|
|
12543
|
-
label: group.superset.label,
|
|
12544
|
-
color: group.superset.color,
|
|
12545
|
-
children: group.exercises.map((exercise, index) => /* @__PURE__ */ jsx(
|
|
12546
|
-
ExerciseCard,
|
|
12547
|
-
{
|
|
12548
|
-
...toActiveCardProps(
|
|
12549
|
-
exercise,
|
|
12550
|
-
exercise.id === focusedId,
|
|
12551
|
-
() => onToggle(exercise.id),
|
|
12552
|
-
supersetPositionAt(index, group.exercises.length),
|
|
12553
|
-
group.superset.color
|
|
12554
|
-
)
|
|
12555
|
-
},
|
|
12556
|
-
exercise.id
|
|
12557
|
-
))
|
|
12558
|
-
},
|
|
12559
|
-
group.superset.id
|
|
12560
11644
|
);
|
|
12561
|
-
}
|
|
12562
|
-
}
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
11645
|
+
}
|
|
11646
|
+
const { toX, toY } = scale;
|
|
11647
|
+
const bandPixels = toPixels(band, toX, toY);
|
|
11648
|
+
const columns = buildColumns(bandPixels, COLUMN_STEP);
|
|
11649
|
+
const topEdge = buildEdges(bandPixels, "yHigh");
|
|
11650
|
+
const bottomEdge = buildEdges(bandPixels, "yLow");
|
|
11651
|
+
const lastBandPoint = band[band.length - 1];
|
|
11652
|
+
const trainingPixels = projection ? toPixels([lastBandPoint, ...projection.withTraining], toX, toY) : [];
|
|
11653
|
+
const restPixels = projection ? toPixels([lastBandPoint, ...projection.withRest], toX, toY) : [];
|
|
11654
|
+
const labelStride = Math.max(1, Math.ceil(band.length / 5));
|
|
11655
|
+
const revealWidth = reveal.interpolate({ inputRange: [0, 1], outputRange: [0, width] });
|
|
11656
|
+
const renderColumns = (cols, color, key) => cols.map((col, i) => /* @__PURE__ */ jsx(
|
|
11657
|
+
reactNative.View,
|
|
11658
|
+
{
|
|
11659
|
+
style: {
|
|
11660
|
+
position: "absolute",
|
|
11661
|
+
left: col.x,
|
|
11662
|
+
top: col.top,
|
|
11663
|
+
width: COLUMN_STEP + 0.5,
|
|
11664
|
+
height: col.height,
|
|
11665
|
+
backgroundColor: color
|
|
11666
|
+
},
|
|
11667
|
+
accessibilityElementsHidden: true,
|
|
11668
|
+
importantForAccessibility: "no-hide-descendants",
|
|
11669
|
+
testID: key
|
|
11670
|
+
},
|
|
11671
|
+
`${key}-${i}`
|
|
11672
|
+
));
|
|
11673
|
+
const renderEdges = (segments, color, dashed, key) => segments.map((seg, i) => /* @__PURE__ */ jsx(
|
|
11674
|
+
reactNative.View,
|
|
11675
|
+
{
|
|
11676
|
+
style: {
|
|
11677
|
+
position: "absolute",
|
|
11678
|
+
left: seg.left,
|
|
11679
|
+
top: seg.top,
|
|
11680
|
+
width: seg.length,
|
|
11681
|
+
height: dashed ? 0 : 1.5,
|
|
11682
|
+
borderTopWidth: dashed ? 1 : 0,
|
|
11683
|
+
borderTopColor: dashed ? color : void 0,
|
|
11684
|
+
borderStyle: dashed ? "dashed" : "solid",
|
|
11685
|
+
backgroundColor: dashed ? void 0 : color,
|
|
11686
|
+
opacity: dashed ? 0.7 : 1,
|
|
11687
|
+
transformOrigin: "0 0",
|
|
11688
|
+
transform: [{ rotate: `${seg.angle}deg` }]
|
|
11689
|
+
},
|
|
11690
|
+
accessibilityElementsHidden: true,
|
|
11691
|
+
importantForAccessibility: "no-hide-descendants",
|
|
11692
|
+
testID: key
|
|
11693
|
+
},
|
|
11694
|
+
`${key}-${i}`
|
|
11695
|
+
));
|
|
12586
11696
|
return /* @__PURE__ */ jsxs(
|
|
12587
11697
|
reactNative.View,
|
|
12588
11698
|
{
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
|
|
12592
|
-
"aria-label": title,
|
|
12593
|
-
testID: "active-workout-page",
|
|
11699
|
+
style: { width, height, position: "relative" },
|
|
11700
|
+
className,
|
|
11701
|
+
testID: "capacity-band-chart-root",
|
|
12594
11702
|
...props,
|
|
12595
11703
|
children: [
|
|
12596
|
-
/* @__PURE__ */ jsxs(
|
|
12597
|
-
/* @__PURE__ */ jsx(WorkoutHeader, { title, subtitle, progress }),
|
|
12598
|
-
/* @__PURE__ */ jsx(ExerciseList, { groups, focusedId, onToggle: toggleFocus })
|
|
12599
|
-
] }),
|
|
12600
|
-
(showRest || showInput) && /* @__PURE__ */ jsx(
|
|
11704
|
+
/* @__PURE__ */ jsxs(
|
|
12601
11705
|
reactNative.View,
|
|
12602
11706
|
{
|
|
12603
|
-
style: {
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12622
|
-
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
11707
|
+
style: { position: "absolute", top: 0, left: 0, width, height },
|
|
11708
|
+
accessibilityRole: "image",
|
|
11709
|
+
accessibilityLabel: `Training capacity band chart. Current capacity: ${currentStatus(
|
|
11710
|
+
workouts
|
|
11711
|
+
)}. ${workouts.length} workout${workouts.length === 1 ? "" : "s"} shown.`,
|
|
11712
|
+
testID: "capacity-band-chart",
|
|
11713
|
+
children: [
|
|
11714
|
+
/* @__PURE__ */ jsx(
|
|
11715
|
+
reactNative.Animated.View,
|
|
11716
|
+
{
|
|
11717
|
+
style: { position: "absolute", top: 0, left: 0, height, width: revealWidth, overflow: "hidden" },
|
|
11718
|
+
accessibilityElementsHidden: true,
|
|
11719
|
+
importantForAccessibility: "no-hide-descendants",
|
|
11720
|
+
testID: "capacity-band-chart-reveal",
|
|
11721
|
+
children: /* @__PURE__ */ jsxs(reactNative.View, { style: { width, height, position: "relative" }, children: [
|
|
11722
|
+
projection && /* @__PURE__ */ jsxs(reactNative.View, { testID: "capacity-band-chart-projection", children: [
|
|
11723
|
+
renderColumns(buildColumns(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
11724
|
+
renderColumns(buildColumns(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
|
|
11725
|
+
renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
|
|
11726
|
+
renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
|
|
11727
|
+
renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
|
|
11728
|
+
renderEdges(buildEdges(restPixels, "yLow"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
|
|
11729
|
+
/* @__PURE__ */ jsx(
|
|
11730
|
+
reactNative.Text,
|
|
11731
|
+
{
|
|
11732
|
+
style: {
|
|
11733
|
+
position: "absolute",
|
|
11734
|
+
left: trainingPixels[trainingPixels.length - 1]?.x + 2,
|
|
11735
|
+
top: trainingPixels[trainingPixels.length - 1]?.yHigh - 12,
|
|
11736
|
+
fontSize: 9,
|
|
11737
|
+
fontFamily: "Inter, sans-serif",
|
|
11738
|
+
color: STATUS_SUCCESS3
|
|
11739
|
+
},
|
|
11740
|
+
testID: "capacity-band-chart-projection-training-label",
|
|
11741
|
+
children: "Training"
|
|
11742
|
+
}
|
|
11743
|
+
),
|
|
11744
|
+
/* @__PURE__ */ jsx(
|
|
11745
|
+
reactNative.Text,
|
|
11746
|
+
{
|
|
11747
|
+
style: {
|
|
11748
|
+
position: "absolute",
|
|
11749
|
+
left: restPixels[restPixels.length - 1]?.x + 2,
|
|
11750
|
+
top: restPixels[restPixels.length - 1]?.yLow + 2,
|
|
11751
|
+
fontSize: 9,
|
|
11752
|
+
fontFamily: "Inter, sans-serif",
|
|
11753
|
+
color: STATUS_INFO
|
|
11754
|
+
},
|
|
11755
|
+
testID: "capacity-band-chart-projection-rest-label",
|
|
11756
|
+
children: "Rest"
|
|
11757
|
+
}
|
|
11758
|
+
)
|
|
11759
|
+
] }),
|
|
11760
|
+
/* @__PURE__ */ jsx(reactNative.View, { testID: "capacity-band-chart-band", children: renderColumns(columns, BAND_FILL, "capacity-band-chart-band-cell") }),
|
|
11761
|
+
renderEdges(topEdge, BAND_EDGE, false, "capacity-band-chart-edge-top"),
|
|
11762
|
+
renderEdges(bottomEdge, BAND_EDGE, false, "capacity-band-chart-edge-bottom")
|
|
11763
|
+
] })
|
|
11764
|
+
}
|
|
11765
|
+
),
|
|
11766
|
+
/* @__PURE__ */ jsx(
|
|
11767
|
+
reactNative.Text,
|
|
11768
|
+
{
|
|
11769
|
+
className: "text-text-tertiary",
|
|
11770
|
+
style: {
|
|
11771
|
+
position: "absolute",
|
|
11772
|
+
left: PADDING_LEFT / 2 - 14,
|
|
11773
|
+
top: PADDING_TOP + plotHeight / 2 - 7,
|
|
11774
|
+
width: 32,
|
|
11775
|
+
textAlign: "center",
|
|
11776
|
+
fontSize: 9,
|
|
11777
|
+
fontFamily: "Inter, sans-serif",
|
|
11778
|
+
transform: [{ rotate: "-90deg" }]
|
|
11779
|
+
},
|
|
11780
|
+
accessibilityElementsHidden: true,
|
|
11781
|
+
importantForAccessibility: "no-hide-descendants",
|
|
11782
|
+
testID: "capacity-band-chart-y-axis-label",
|
|
11783
|
+
children: "Load"
|
|
11784
|
+
}
|
|
11785
|
+
),
|
|
11786
|
+
band.map((point, i) => {
|
|
11787
|
+
if (i % labelStride !== 0 && i !== band.length - 1) return null;
|
|
11788
|
+
return /* @__PURE__ */ jsx(
|
|
11789
|
+
reactNative.Text,
|
|
11790
|
+
{
|
|
11791
|
+
className: "text-text-tertiary",
|
|
11792
|
+
style: {
|
|
11793
|
+
position: "absolute",
|
|
11794
|
+
left: toX(point.date) - 14,
|
|
11795
|
+
top: height - PADDING_BOTTOM + 2,
|
|
11796
|
+
width: 28,
|
|
11797
|
+
textAlign: "center",
|
|
11798
|
+
fontSize: 10,
|
|
11799
|
+
fontFamily: "Inter, sans-serif"
|
|
11800
|
+
},
|
|
11801
|
+
accessibilityElementsHidden: true,
|
|
11802
|
+
importantForAccessibility: "no-hide-descendants",
|
|
11803
|
+
testID: "capacity-band-chart-x-label",
|
|
11804
|
+
children: formatDate2(point.date)
|
|
11805
|
+
},
|
|
11806
|
+
`x-${point.date}`
|
|
11807
|
+
);
|
|
11808
|
+
})
|
|
11809
|
+
]
|
|
12633
11810
|
}
|
|
12634
|
-
)
|
|
11811
|
+
),
|
|
11812
|
+
workouts.map((workout, i) => {
|
|
11813
|
+
const cx = toX(workout.date);
|
|
11814
|
+
const cy = toY(workout.load);
|
|
11815
|
+
const color = DOT_COLORS[workout.status];
|
|
11816
|
+
const dotStyle = {
|
|
11817
|
+
width: DOT_SIZE,
|
|
11818
|
+
height: DOT_SIZE,
|
|
11819
|
+
borderRadius: DOT_SIZE / 2,
|
|
11820
|
+
backgroundColor: color,
|
|
11821
|
+
borderWidth: 2,
|
|
11822
|
+
borderColor: DOT_BORDER
|
|
11823
|
+
};
|
|
11824
|
+
const label = `Workout on ${formatDate2(workout.date)}, load ${workout.load}, ${STATUS_PHRASES[workout.status]}`;
|
|
11825
|
+
return /* @__PURE__ */ jsx(
|
|
11826
|
+
reactNative.Animated.View,
|
|
11827
|
+
{
|
|
11828
|
+
style: {
|
|
11829
|
+
position: "absolute",
|
|
11830
|
+
left: cx - DOT_SIZE / 2,
|
|
11831
|
+
top: cy - DOT_SIZE / 2,
|
|
11832
|
+
transform: [{ scale: dotAnims[i] ?? 1 }]
|
|
11833
|
+
},
|
|
11834
|
+
testID: "capacity-band-chart-dot-wrapper",
|
|
11835
|
+
children: onWorkoutPress ? /* @__PURE__ */ jsx(
|
|
11836
|
+
reactNative.Pressable,
|
|
11837
|
+
{
|
|
11838
|
+
onPress: () => onWorkoutPress(workout),
|
|
11839
|
+
accessibilityRole: "button",
|
|
11840
|
+
accessibilityLabel: label,
|
|
11841
|
+
style: dotStyle,
|
|
11842
|
+
testID: "capacity-band-chart-dot"
|
|
11843
|
+
}
|
|
11844
|
+
) : /* @__PURE__ */ jsx(
|
|
11845
|
+
reactNative.View,
|
|
11846
|
+
{
|
|
11847
|
+
accessibilityElementsHidden: true,
|
|
11848
|
+
importantForAccessibility: "no-hide-descendants",
|
|
11849
|
+
style: dotStyle,
|
|
11850
|
+
testID: "capacity-band-chart-dot"
|
|
11851
|
+
}
|
|
11852
|
+
)
|
|
11853
|
+
},
|
|
11854
|
+
`dot-${workout.date}-${i}`
|
|
11855
|
+
);
|
|
11856
|
+
})
|
|
12635
11857
|
]
|
|
12636
11858
|
}
|
|
12637
11859
|
);
|
|
@@ -12943,24 +12165,24 @@ function Scatter({
|
|
|
12943
12165
|
}
|
|
12944
12166
|
var STATUS_SUCCESS4 = "#14B8A6";
|
|
12945
12167
|
var STATUS_WARNING4 = "#FFB020";
|
|
12946
|
-
var
|
|
12168
|
+
var STATUS_ERROR4 = "#D14343";
|
|
12947
12169
|
var TRACK = "rgba(255,255,255,0.08)";
|
|
12948
12170
|
var DEFAULT_THRESHOLDS = [
|
|
12949
|
-
{ value: 0, color:
|
|
12171
|
+
{ value: 0, color: STATUS_ERROR4 },
|
|
12950
12172
|
{ value: 60, color: STATUS_WARNING4 },
|
|
12951
12173
|
{ value: 80, color: STATUS_SUCCESS4 }
|
|
12952
12174
|
];
|
|
12953
12175
|
var ARC_START = 135;
|
|
12954
12176
|
var ARC_SWEEP = 270;
|
|
12955
12177
|
var SEGMENTS = 40;
|
|
12956
|
-
function
|
|
12178
|
+
function clamp012(n) {
|
|
12957
12179
|
if (Number.isNaN(n)) return 0;
|
|
12958
12180
|
return Math.max(0, Math.min(1, n));
|
|
12959
12181
|
}
|
|
12960
12182
|
function formatValue2(v) {
|
|
12961
12183
|
return Number.isInteger(v) ? `${v}` : v.toFixed(1);
|
|
12962
12184
|
}
|
|
12963
|
-
function
|
|
12185
|
+
function bandColor2(fraction, bands, min, max) {
|
|
12964
12186
|
const value = min + fraction * (max - min);
|
|
12965
12187
|
const sorted = [...bands].sort((a, b) => a.value - b.value);
|
|
12966
12188
|
let color = sorted[0]?.color ?? STATUS_SUCCESS4;
|
|
@@ -12998,10 +12220,10 @@ function Gauge2({
|
|
|
12998
12220
|
...props
|
|
12999
12221
|
}) {
|
|
13000
12222
|
const span = max - min || 1;
|
|
13001
|
-
const fill =
|
|
13002
|
-
const activeColor = (f) => color ??
|
|
12223
|
+
const fill = clamp012((value - min) / span);
|
|
12224
|
+
const activeColor = (f) => color ?? bandColor2(f, thresholds, min, max);
|
|
13003
12225
|
const ticks = buildTicks(fill, size, activeColor);
|
|
13004
|
-
const displayColor = color ??
|
|
12226
|
+
const displayColor = color ?? bandColor2(fill, thresholds, min, max);
|
|
13005
12227
|
const tickLength = size * 0.11;
|
|
13006
12228
|
const tickThickness = Math.max(2, size * 0.9 / SEGMENTS);
|
|
13007
12229
|
const ariaLabel = `${label ? `${label}: ` : ""}${formatValue2(value)}${unit ?? ""} of ${formatValue2(max)}`;
|
|
@@ -13137,7 +12359,6 @@ exports.Accordion = Accordion;
|
|
|
13137
12359
|
exports.AccordionButton = AccordionButton;
|
|
13138
12360
|
exports.AccordionItem = AccordionItem;
|
|
13139
12361
|
exports.AccordionPanel = AccordionPanel;
|
|
13140
|
-
exports.ActiveWorkoutPage = ActiveWorkoutPage;
|
|
13141
12362
|
exports.Alert = Alert;
|
|
13142
12363
|
exports.AlertDescription = AlertDescription;
|
|
13143
12364
|
exports.AlertTitle = AlertTitle;
|
|
@@ -13178,10 +12399,8 @@ exports.Drawer = Drawer;
|
|
|
13178
12399
|
exports.DrawerBody = DrawerBody;
|
|
13179
12400
|
exports.DrawerFooter = DrawerFooter;
|
|
13180
12401
|
exports.DrawerHeader = DrawerHeader;
|
|
13181
|
-
exports.EXERCISE_DETAIL_TABS = EXERCISE_DETAIL_TABS;
|
|
13182
12402
|
exports.EmptyState = EmptyState;
|
|
13183
12403
|
exports.ExerciseCard = ExerciseCard;
|
|
13184
|
-
exports.ExerciseDetailPage = ExerciseDetailPage;
|
|
13185
12404
|
exports.FormActions = FormActions;
|
|
13186
12405
|
exports.FormField = FormField;
|
|
13187
12406
|
exports.FormRow = FormRow;
|
|
@@ -13234,7 +12453,6 @@ exports.PopoverContent = PopoverContent;
|
|
|
13234
12453
|
exports.PopoverTrigger = PopoverTrigger;
|
|
13235
12454
|
exports.PrBadge = PrBadge;
|
|
13236
12455
|
exports.PrHistoryModal = PrHistoryModal;
|
|
13237
|
-
exports.ProgramPlanningPage = ProgramPlanningPage;
|
|
13238
12456
|
exports.Progress = Progress;
|
|
13239
12457
|
exports.ProgressSteps = ProgressSteps;
|
|
13240
12458
|
exports.Radio = Radio;
|
|
@@ -13287,7 +12505,6 @@ exports.TableRow = TableRow;
|
|
|
13287
12505
|
exports.Tabs = Tabs;
|
|
13288
12506
|
exports.TempoBar = TempoBar;
|
|
13289
12507
|
exports.TempoDisplay = TempoDisplay;
|
|
13290
|
-
exports.ThemeProvider = ThemeProvider;
|
|
13291
12508
|
exports.Toast = Toast;
|
|
13292
12509
|
exports.ToastProvider = ToastProvider;
|
|
13293
12510
|
exports.ToolbarButton = ToolbarButton;
|
|
@@ -13304,14 +12521,12 @@ exports.WorkoutPill = WorkoutPill;
|
|
|
13304
12521
|
exports.alpha = alpha;
|
|
13305
12522
|
exports.applyThemePreset = applyThemePreset;
|
|
13306
12523
|
exports.audiobookPreset = audiobookPreset;
|
|
13307
|
-
exports.buildBreadcrumbs = buildBreadcrumbs;
|
|
13308
12524
|
exports.buttonSizes = buttonSizes;
|
|
13309
12525
|
exports.calculateMeanVelocity = calculateMeanVelocity;
|
|
13310
12526
|
exports.calculateVelocityLoss = calculateVelocityLoss;
|
|
13311
12527
|
exports.cn = cn;
|
|
13312
12528
|
exports.componentElevationRanges = componentElevationRanges;
|
|
13313
12529
|
exports.composeValidators = composeValidators;
|
|
13314
|
-
exports.countCompletedSets = countCompletedSets;
|
|
13315
12530
|
exports.createFieldId = createFieldId;
|
|
13316
12531
|
exports.createFlatShadow = createFlatShadow;
|
|
13317
12532
|
exports.createPressedHoverShadow = createPressedHoverShadow;
|
|
@@ -13321,15 +12536,8 @@ exports.createRaisedShadow = createRaisedShadow;
|
|
|
13321
12536
|
exports.darkThemeCSSVars = darkThemeCSSVars;
|
|
13322
12537
|
exports.darken = darken;
|
|
13323
12538
|
exports.defaultPreset = defaultPreset;
|
|
13324
|
-
exports.deriveExerciseStats = deriveExerciseStats;
|
|
13325
|
-
exports.deriveNavLevel = deriveNavLevel;
|
|
13326
|
-
exports.deriveWorkoutProgress = deriveWorkoutProgress;
|
|
13327
12539
|
exports.discreteRainbow = discreteRainbow;
|
|
13328
12540
|
exports.elevationSystem = elevationSystem;
|
|
13329
|
-
exports.findActiveExercise = findActiveExercise;
|
|
13330
|
-
exports.findMeso = findMeso;
|
|
13331
|
-
exports.findWeek = findWeek;
|
|
13332
|
-
exports.findWorkout = findWorkout;
|
|
13333
12541
|
exports.formatDateTime = formatDateTime;
|
|
13334
12542
|
exports.formatPrescription = formatPrescription;
|
|
13335
12543
|
exports.formatSignedPct = formatSignedPct;
|
|
@@ -13355,7 +12563,6 @@ exports.getValidatedElevation = getValidatedElevation;
|
|
|
13355
12563
|
exports.getVelocityZoneColor = getVelocityZoneColor;
|
|
13356
12564
|
exports.getVelocityZoneName = getVelocityZoneName;
|
|
13357
12565
|
exports.gluestackConfig = gluestackConfig;
|
|
13358
|
-
exports.groupExercises = groupExercises;
|
|
13359
12566
|
exports.hasMaxLength = hasMaxLength;
|
|
13360
12567
|
exports.hasMinLength = hasMinLength;
|
|
13361
12568
|
exports.hexToRgb = hexToRgb;
|
|
@@ -13385,11 +12592,6 @@ exports.semanticColorsDark = semanticColorsDark;
|
|
|
13385
12592
|
exports.semanticColorsLight = semanticColorsLight;
|
|
13386
12593
|
exports.semanticTypography = semanticTypography;
|
|
13387
12594
|
exports.shadowColors = shadowColors;
|
|
13388
|
-
exports.statusToCardState = statusToCardState;
|
|
13389
|
-
exports.summarizeVbt = summarizeVbt;
|
|
13390
|
-
exports.toActiveCardProps = toActiveCardProps;
|
|
13391
|
-
exports.toExerciseCardProps = toExerciseCardProps;
|
|
13392
|
-
exports.toProgressBarMesos = toProgressBarMesos;
|
|
13393
12595
|
exports.useTable = useTable;
|
|
13394
12596
|
exports.useToast = useToast;
|
|
13395
12597
|
exports.useToolbarButton = useToolbarButton;
|