@titan-design/react-ui 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +399 -4
- package/dist/index.d.ts +399 -4
- package/dist/index.js +2295 -895
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2279 -905
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1974 -1740
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1975 -1741
- package/dist/pages.mjs.map +1 -1
- package/package.json +8 -3
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +50 -13
- package/src/components/custom/Workout/SetRow.test.tsx +6 -6
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +196 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +871 -0
- package/src/theme/ColorPrimitives.stories.tsx +300 -0
- package/src/theme/bar-paper.ts +59 -0
- package/src/theme/color-stories.coverage.test.ts +130 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -508
- package/src/theme/Colors.stories.tsx +0 -323
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as jsx$1, jsxs as jsxs$1, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import { Pressable, ActivityIndicator, Text, View, TextInput, Platform, StyleSheet, Easing, Image, Modal as Modal$1, ScrollView, Animated } from 'react-native';
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
import React25, { forwardRef, useState, createContext, useContext, useMemo, useRef, useEffect, useCallback, useSyncExternalStore, Fragment as Fragment$1 } from 'react';
|
|
5
|
+
import React25, { forwardRef, useState, createContext, useContext, useMemo, useRef, useEffect, useCallback, useId, useSyncExternalStore, Fragment as Fragment$1 } from 'react';
|
|
6
6
|
|
|
7
7
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
8
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -5373,7 +5373,7 @@ function ToastProvider({
|
|
|
5373
5373
|
return id;
|
|
5374
5374
|
}, [defaultDuration, maxToasts]);
|
|
5375
5375
|
const removeToast = useCallback((id) => {
|
|
5376
|
-
setToasts((prev) => prev.filter((
|
|
5376
|
+
setToasts((prev) => prev.filter((t26) => t26.id !== id));
|
|
5377
5377
|
}, []);
|
|
5378
5378
|
const removeAllToasts = useCallback(() => {
|
|
5379
5379
|
setToasts([]);
|
|
@@ -8661,257 +8661,412 @@ function WorkoutPill({
|
|
|
8661
8661
|
}
|
|
8662
8662
|
return pill;
|
|
8663
8663
|
}
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
const
|
|
8664
|
+
|
|
8665
|
+
// src/theme/bar-paper.ts
|
|
8666
|
+
function grainOpacityForColor(hex) {
|
|
8667
|
+
const h = hex.replace("#", "");
|
|
8668
|
+
const full = h.length === 3 ? h.split("").map((ch) => ch + ch).join("") : h;
|
|
8669
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
8670
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
8671
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
8672
|
+
if ([r, g, b].some(Number.isNaN)) return 0.14;
|
|
8673
|
+
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
8674
|
+
return Math.min(0.24, Math.max(0.04, 0.02 + 0.31 * lum));
|
|
8675
|
+
}
|
|
8676
|
+
function barGrain(color) {
|
|
8677
|
+
const op = grainOpacityForColor(color).toFixed(3);
|
|
8678
|
+
return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='${op}'/%3E%3C/svg%3E")`;
|
|
8679
|
+
}
|
|
8680
|
+
function barPaper(color, flip = false) {
|
|
8681
|
+
return {
|
|
8682
|
+
backgroundImage: barGrain(color),
|
|
8683
|
+
boxShadow: flip ? "0 -6px 16px rgba(0,0,0,0.45)" : "inset 0 1.5px 0 rgba(255,255,255,0.22), 0 6px 16px rgba(0,0,0,0.45)"
|
|
8684
|
+
};
|
|
8685
|
+
}
|
|
8686
|
+
var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
|
|
8687
|
+
var PEAK_OVERSHOOT = 1.12;
|
|
8688
|
+
function getReducedMotionPreference() {
|
|
8689
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
8690
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
8691
|
+
}
|
|
8692
|
+
function usePrefersReducedMotion() {
|
|
8693
|
+
const [reduced, setReduced] = useState(getReducedMotionPreference);
|
|
8669
8694
|
useEffect(() => {
|
|
8670
|
-
if (
|
|
8671
|
-
const
|
|
8695
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
8696
|
+
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
8697
|
+
const handler = () => setReduced(mq.matches);
|
|
8698
|
+
handler();
|
|
8699
|
+
mq.addEventListener?.("change", handler);
|
|
8700
|
+
return () => mq.removeEventListener?.("change", handler);
|
|
8701
|
+
}, []);
|
|
8702
|
+
return reduced;
|
|
8703
|
+
}
|
|
8704
|
+
function useLiveRepGrowth(active, liveRepIndex, liveVelocity, isNewPeak) {
|
|
8705
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
8706
|
+
const [liveGrowth] = useState(() => new Animated.Value(1));
|
|
8707
|
+
useEffect(() => {
|
|
8708
|
+
if (liveRepIndex == null || liveVelocity == null) return;
|
|
8709
|
+
if (prefersReducedMotion) {
|
|
8710
|
+
liveGrowth.setValue(1);
|
|
8711
|
+
return;
|
|
8712
|
+
}
|
|
8713
|
+
liveGrowth.setValue(0);
|
|
8714
|
+
if (isNewPeak) {
|
|
8672
8715
|
Animated.sequence([
|
|
8673
|
-
Animated.timing(
|
|
8674
|
-
toValue:
|
|
8675
|
-
duration:
|
|
8676
|
-
easing: Easing.
|
|
8716
|
+
Animated.timing(liveGrowth, {
|
|
8717
|
+
toValue: PEAK_OVERSHOOT,
|
|
8718
|
+
duration: 220,
|
|
8719
|
+
easing: Easing.out(Easing.quad),
|
|
8677
8720
|
useNativeDriver: false
|
|
8678
8721
|
}),
|
|
8679
|
-
Animated.
|
|
8680
|
-
toValue:
|
|
8681
|
-
|
|
8682
|
-
|
|
8722
|
+
Animated.spring(liveGrowth, {
|
|
8723
|
+
toValue: 1,
|
|
8724
|
+
friction: 5,
|
|
8725
|
+
tension: 140,
|
|
8683
8726
|
useNativeDriver: false
|
|
8684
8727
|
})
|
|
8685
|
-
])
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8728
|
+
]).start();
|
|
8729
|
+
} else {
|
|
8730
|
+
Animated.timing(liveGrowth, {
|
|
8731
|
+
toValue: 1,
|
|
8732
|
+
duration: 300,
|
|
8733
|
+
easing: ANIMATION_EASING,
|
|
8734
|
+
useNativeDriver: false
|
|
8735
|
+
}).start();
|
|
8736
|
+
}
|
|
8737
|
+
}, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveGrowth]);
|
|
8738
|
+
return liveGrowth;
|
|
8691
8739
|
}
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8740
|
+
|
|
8741
|
+
// src/components/custom/charts/SetBarChart.tsx
|
|
8742
|
+
function mixHex(a, b, t26) {
|
|
8743
|
+
const parse = (h) => {
|
|
8744
|
+
const s = h.replace("#", "");
|
|
8745
|
+
return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
|
|
8746
|
+
};
|
|
8747
|
+
const [ar, ag, ab] = parse(a);
|
|
8748
|
+
const [br, bg, bb] = parse(b);
|
|
8749
|
+
const ch = (x, y) => Math.round(x + (y - x) * t26).toString(16).padStart(2, "0");
|
|
8750
|
+
return `#${ch(ar, br)}${ch(ag, bg)}${ch(ab, bb)}`;
|
|
8751
|
+
}
|
|
8752
|
+
var VARIABLE_FILL = primitiveRamps.cyan[900];
|
|
8753
|
+
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
8754
|
+
var FIXED_MAX_VALUE = 1.15;
|
|
8755
|
+
var PEAK_HEADROOM = 1.03;
|
|
8756
|
+
function scaleDenominator(scale, maxValue) {
|
|
8757
|
+
return scale === "fixed" ? FIXED_MAX_VALUE : maxValue * PEAK_HEADROOM;
|
|
8758
|
+
}
|
|
8759
|
+
function valueLabelFontSize(height) {
|
|
8760
|
+
return Math.round(Math.max(8, Math.min(12, height * 0.11)));
|
|
8761
|
+
}
|
|
8762
|
+
var SET_BAR_DEFAULT_HEIGHT = 220;
|
|
8763
|
+
var LABEL_ROW_HEIGHT = 16;
|
|
8764
|
+
var LABEL_GAP = 3;
|
|
8765
|
+
var GAP_RATIO = 0.08;
|
|
8766
|
+
var MIN_BAR_GAP = 2;
|
|
8767
|
+
var CHUNK_NOTCH_RATIO = 0.25;
|
|
8768
|
+
var CHUNK_NOTCH_MIN_PX = 10;
|
|
8769
|
+
var BAR_MAX_WIDTH = 120;
|
|
8770
|
+
var LABEL_MIN_BAR_WIDTH = 30;
|
|
8771
|
+
var DEFAULT_BAR_RADIUS = 5;
|
|
8772
|
+
var MIN_BAR_HEIGHT = 4;
|
|
8773
|
+
var FLAT_BAR_FRACTION = 0.5;
|
|
8774
|
+
var STUB_HEIGHT = 9;
|
|
8775
|
+
var REP_GAP = 2;
|
|
8776
|
+
function computeBarLayout(plotWidth, count, gapRatio = GAP_RATIO) {
|
|
8777
|
+
const rawBarWidth = plotWidth > 0 && count > 0 ? plotWidth / (count + (count - 1) * gapRatio) : BAR_MAX_WIDTH;
|
|
8778
|
+
const barWidth = Math.min(BAR_MAX_WIDTH, rawBarWidth);
|
|
8779
|
+
const gap = plotWidth === 0 ? MIN_BAR_GAP : Math.max(MIN_BAR_GAP, gapRatio * barWidth);
|
|
8780
|
+
return { barWidth, gap };
|
|
8781
|
+
}
|
|
8782
|
+
function isRep(slot) {
|
|
8783
|
+
return slot.kind === "rep";
|
|
8784
|
+
}
|
|
8785
|
+
function repIndexByCell(cells) {
|
|
8786
|
+
let count = 0;
|
|
8787
|
+
return cells.map((slot) => isRep(slot) ? count++ : -1);
|
|
8788
|
+
}
|
|
8789
|
+
function SetBarChart({
|
|
8790
|
+
slots,
|
|
8791
|
+
colorFor,
|
|
8792
|
+
height,
|
|
8793
|
+
scale = "peak",
|
|
8794
|
+
scaleMax,
|
|
8795
|
+
orientation = "up",
|
|
8796
|
+
liveRepIndex,
|
|
8797
|
+
isNewPeak = false,
|
|
8798
|
+
targetReps,
|
|
8799
|
+
gapRatio = GAP_RATIO,
|
|
8800
|
+
barRadius = DEFAULT_BAR_RADIUS,
|
|
8801
|
+
cornerStyle = "top",
|
|
8802
|
+
flat = false,
|
|
8803
|
+
showValueLabels: showValueLabelsProp = false,
|
|
8804
|
+
formatValue: formatValue3 = String,
|
|
8805
|
+
todoVariant = "solid",
|
|
8806
|
+
minColumns,
|
|
8807
|
+
renderReference,
|
|
8808
|
+
renderBarOverlay,
|
|
8809
|
+
expandProgress,
|
|
8810
|
+
hideBaseline = false,
|
|
8811
|
+
testID,
|
|
8812
|
+
testIDPrefix = "setbar",
|
|
8813
|
+
accessibilityLabel: accessibilityLabel2,
|
|
8814
|
+
label,
|
|
8815
|
+
className,
|
|
8816
|
+
viewProps = {}
|
|
8817
|
+
}) {
|
|
8818
|
+
const targetPadded = Math.max(slots.length, targetReps ?? 0, minColumns ?? 0);
|
|
8819
|
+
const cells = targetPadded > slots.length ? [
|
|
8820
|
+
...slots,
|
|
8821
|
+
...Array.from({ length: targetPadded - slots.length }, () => ({ kind: "todo" }))
|
|
8822
|
+
] : slots;
|
|
8823
|
+
const repValues = cells.filter(isRep).map((s) => s.value ?? 0);
|
|
8824
|
+
const best = repValues.length > 0 ? Math.max(...repValues) : 0;
|
|
8825
|
+
const flip = orientation === "down";
|
|
8826
|
+
const flipStyle = flip ? { transform: [{ scaleY: -1 }] } : null;
|
|
8827
|
+
const placeholderColor = useOnSurfaceColor("tertiary");
|
|
8828
|
+
const surface = useSurface();
|
|
8829
|
+
const surfaceBg = surfaceBackground(surface.level, surface.mode);
|
|
8830
|
+
const solidTodoColor = mixHex(surfaceBg, placeholderColor, 0.55);
|
|
8831
|
+
const emptyColor = mixHex(surfaceBg, placeholderColor, 0.28);
|
|
8832
|
+
const barCorners = cornerStyle === "all" ? { borderRadius: barRadius } : { borderTopLeftRadius: barRadius, borderTopRightRadius: barRadius };
|
|
8833
|
+
const scaleDenom = scaleMax != null && scaleMax > 0 ? scaleMax * PEAK_HEADROOM : scaleDenominator(scale, best);
|
|
8834
|
+
const showValueLabels = showValueLabelsProp && !flat;
|
|
8835
|
+
const plotHeight = Math.max(0, height - (showValueLabels ? LABEL_ROW_HEIGHT + LABEL_GAP : 0));
|
|
8836
|
+
const yOf = (value) => scaleDenom > 0 ? value / scaleDenom * plotHeight : 0;
|
|
8837
|
+
const flatBarHeight = Math.max(MIN_BAR_HEIGHT, FLAT_BAR_FRACTION * plotHeight);
|
|
8838
|
+
const compactBarHeight = Math.max(MIN_BAR_HEIGHT, plotHeight);
|
|
8839
|
+
const valueBarHeight = (value) => scaleDenom > 0 ? Math.max(MIN_BAR_HEIGHT, Math.min(1, value / scaleDenom) * plotHeight) : MIN_BAR_HEIGHT;
|
|
8840
|
+
const barHeight = (value) => flat ? compactBarHeight : valueBarHeight(value);
|
|
8841
|
+
const liveValue = liveRepIndex != null && liveRepIndex < repValues.length ? repValues[liveRepIndex] : void 0;
|
|
8842
|
+
const liveGrowth = useLiveRepGrowth(true, liveRepIndex, liveValue, isNewPeak);
|
|
8843
|
+
const [plotW, setPlotW] = useState(0);
|
|
8844
|
+
const onPlotLayout = (e) => setPlotW(e.nativeEvent.layout.width);
|
|
8845
|
+
const totalCells = cells.length;
|
|
8846
|
+
const { barWidth, gap } = computeBarLayout(plotW, totalCells, gapRatio);
|
|
8847
|
+
const labelsCrowded = plotW > 0 && barWidth < LABEL_MIN_BAR_WIDTH;
|
|
8848
|
+
const peakRepIndex = repValues.reduce((b, v, i) => v > repValues[b] ? i : b, 0);
|
|
8849
|
+
const showBarLabel = (repIndex) => !labelsCrowded || repIndex === peakRepIndex || repIndex === liveRepIndex;
|
|
8850
|
+
const geometry = { scaleDenom, plotHeight, yOf, best, flip };
|
|
8851
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
8852
|
+
const a11y = accessibilityLabel2 != null ? { accessibilityRole: "image", accessibilityLabel: accessibilityLabel2 } : {};
|
|
8853
|
+
const repIndices = repIndexByCell(cells);
|
|
8854
|
+
return /* @__PURE__ */ jsxs(
|
|
8855
|
+
View,
|
|
8856
|
+
{
|
|
8857
|
+
className,
|
|
8858
|
+
style: [{ height, flexDirection: "row" }, externalStyle],
|
|
8859
|
+
testID,
|
|
8860
|
+
...a11y,
|
|
8861
|
+
...restProps,
|
|
8862
|
+
children: [
|
|
8863
|
+
label ? /* @__PURE__ */ jsx(
|
|
8864
|
+
ChartSideRail,
|
|
8865
|
+
{
|
|
8866
|
+
sections: [{ label, testID: `${testIDPrefix}-side-label` }],
|
|
8867
|
+
sectionExtent: height
|
|
8868
|
+
}
|
|
8869
|
+
) : null,
|
|
8870
|
+
/* @__PURE__ */ jsxs(
|
|
8871
|
+
View,
|
|
8872
|
+
{
|
|
8873
|
+
onLayout: onPlotLayout,
|
|
8874
|
+
style: {
|
|
8875
|
+
flex: 1,
|
|
8876
|
+
flexDirection: "row",
|
|
8877
|
+
alignItems: "flex-end",
|
|
8878
|
+
gap,
|
|
8879
|
+
position: "relative",
|
|
8880
|
+
borderBottomWidth: hideBaseline ? 0 : 2,
|
|
8881
|
+
borderBottomColor: placeholderColor,
|
|
8882
|
+
...flipStyle
|
|
8883
|
+
},
|
|
8884
|
+
children: [
|
|
8885
|
+
renderReference?.(geometry),
|
|
8886
|
+
cells.map((slot, i) => {
|
|
8887
|
+
const isChunkBoundary = (slot.leadingGap ?? REP_GAP) > REP_GAP;
|
|
8888
|
+
const extraGap = isChunkBoundary ? Math.max(CHUNK_NOTCH_MIN_PX, CHUNK_NOTCH_RATIO * barWidth) : 0;
|
|
8889
|
+
const column = {
|
|
8890
|
+
flex: 1,
|
|
8891
|
+
maxWidth: BAR_MAX_WIDTH,
|
|
8892
|
+
height: "100%",
|
|
8893
|
+
alignItems: "center",
|
|
8894
|
+
justifyContent: "flex-end",
|
|
8895
|
+
marginLeft: extraGap
|
|
8896
|
+
};
|
|
8897
|
+
if (!isRep(slot)) {
|
|
8898
|
+
return /* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, style: column, children: renderStub(
|
|
8899
|
+
slot.kind,
|
|
8900
|
+
barCorners,
|
|
8901
|
+
// Flat (compact) stubs fill the plot like the compact bars; else the fixed short stub.
|
|
8902
|
+
flat ? compactBarHeight : STUB_HEIGHT,
|
|
8903
|
+
placeholderColor,
|
|
8904
|
+
testIDPrefix,
|
|
8905
|
+
todoVariant,
|
|
8906
|
+
solidTodoColor,
|
|
8907
|
+
emptyColor
|
|
8908
|
+
) }, i);
|
|
8909
|
+
}
|
|
8910
|
+
const repIndex = repIndices[i];
|
|
8911
|
+
const value = slot.value ?? 0;
|
|
8912
|
+
const isLive = liveRepIndex === repIndex;
|
|
8913
|
+
const color = colorFor(value);
|
|
8914
|
+
const barHeightStyle = expandProgress ? expandProgress.interpolate({
|
|
8915
|
+
inputRange: [0, 1],
|
|
8916
|
+
outputRange: [flatBarHeight, valueBarHeight(value)]
|
|
8917
|
+
}) : isLive ? liveGrowth.interpolate({ inputRange: [0, 1], outputRange: [0, barHeight(value)] }) : barHeight(value);
|
|
8918
|
+
const ownLabel = showValueLabels && !renderBarOverlay && showBarLabel(repIndex);
|
|
8919
|
+
const labelBottom = (expandProgress ? valueBarHeight(best) : barHeight(best)) + LABEL_GAP;
|
|
8920
|
+
return /* @__PURE__ */ jsxs(View, { accessibilityElementsHidden: renderBarOverlay == null, style: column, children: [
|
|
8921
|
+
ownLabel && // All labels pin to one aligned row JUST ABOVE THE PEAK BAR (not the container top),
|
|
8922
|
+
// in the muted on-surface-secondary tone — so the row hugs the bars, no floating void.
|
|
8923
|
+
/* @__PURE__ */ jsx(
|
|
8924
|
+
Animated.View,
|
|
8925
|
+
{
|
|
8926
|
+
style: {
|
|
8927
|
+
position: "absolute",
|
|
8928
|
+
bottom: labelBottom,
|
|
8929
|
+
left: 0,
|
|
8930
|
+
right: 0,
|
|
8931
|
+
alignItems: "center",
|
|
8932
|
+
...expandProgress ? { opacity: expandProgress } : null
|
|
8933
|
+
},
|
|
8934
|
+
pointerEvents: "none",
|
|
8935
|
+
children: /* @__PURE__ */ jsx(
|
|
8936
|
+
Text,
|
|
8937
|
+
{
|
|
8938
|
+
className: "text-text-secondary",
|
|
8939
|
+
style: [{ fontSize: valueLabelFontSize(height), fontWeight: "700" }, flipStyle],
|
|
8940
|
+
testID: `${testIDPrefix}-label-${repIndex}`,
|
|
8941
|
+
children: formatValue3(value)
|
|
8942
|
+
}
|
|
8943
|
+
)
|
|
8944
|
+
}
|
|
8945
|
+
),
|
|
8946
|
+
/* @__PURE__ */ jsx(
|
|
8947
|
+
Animated.View,
|
|
8948
|
+
{
|
|
8949
|
+
style: [
|
|
8950
|
+
{ width: "100%", height: barHeightStyle, backgroundColor: color },
|
|
8951
|
+
barCorners,
|
|
8952
|
+
barPaper(color, flip)
|
|
8953
|
+
],
|
|
8954
|
+
testID: `${testIDPrefix}-bar-${repIndex}`
|
|
8955
|
+
}
|
|
8956
|
+
),
|
|
8957
|
+
renderBarOverlay?.(repIndex, value)
|
|
8958
|
+
] }, i);
|
|
8959
|
+
})
|
|
8960
|
+
]
|
|
8961
|
+
}
|
|
8962
|
+
)
|
|
8963
|
+
]
|
|
8964
|
+
}
|
|
8965
|
+
);
|
|
8966
|
+
}
|
|
8967
|
+
function renderStub(kind, barCorners, stubHeight, placeholderColor, testIDPrefix, todoVariant, solidTodoColor, emptyColor) {
|
|
8968
|
+
const base = {
|
|
8969
|
+
width: "100%",
|
|
8970
|
+
height: stubHeight,
|
|
8971
|
+
...barCorners
|
|
8972
|
+
};
|
|
8973
|
+
if (kind === "variable") {
|
|
8974
|
+
return /* @__PURE__ */ jsx(
|
|
8975
|
+
View,
|
|
8976
|
+
{
|
|
8977
|
+
style: { ...base, backgroundColor: VARIABLE_FILL },
|
|
8978
|
+
testID: `${testIDPrefix}-slot-variable`
|
|
8979
|
+
}
|
|
8980
|
+
);
|
|
8981
|
+
}
|
|
8982
|
+
if (kind === "continue") {
|
|
8983
|
+
return /* @__PURE__ */ jsx(
|
|
8984
|
+
View,
|
|
8985
|
+
{
|
|
8986
|
+
style: { ...base, borderWidth: 1, borderColor: CONTINUE_OUTLINE },
|
|
8987
|
+
testID: `${testIDPrefix}-slot-continue`
|
|
8988
|
+
}
|
|
8989
|
+
);
|
|
8990
|
+
}
|
|
8991
|
+
if (kind === "empty") {
|
|
8992
|
+
return /* @__PURE__ */ jsx(
|
|
8993
|
+
View,
|
|
8994
|
+
{
|
|
8995
|
+
style: { ...base, backgroundColor: emptyColor },
|
|
8996
|
+
testID: `${testIDPrefix}-slot-empty`
|
|
8997
|
+
}
|
|
8998
|
+
);
|
|
8999
|
+
}
|
|
9000
|
+
const todoStyle = todoVariant === "solid" ? { ...base, backgroundColor: solidTodoColor } : { ...base, borderWidth: 1, borderStyle: "dashed", borderColor: placeholderColor };
|
|
9001
|
+
return /* @__PURE__ */ jsx(View, { style: todoStyle, testID: `${testIDPrefix}-slot-todo` });
|
|
9002
|
+
}
|
|
9003
|
+
function sideLabelFontSize(height) {
|
|
9004
|
+
return Math.round(Math.max(7, Math.min(13, height * 0.06)));
|
|
9005
|
+
}
|
|
9006
|
+
var SIDE_LABEL_INITIALS_BELOW = 70;
|
|
9007
|
+
function sideLabelText(label, extent) {
|
|
9008
|
+
if (extent >= SIDE_LABEL_INITIALS_BELOW) return label;
|
|
9009
|
+
return label.trim().split(/\s+/).map((word) => word.charAt(0)).join(" ").toUpperCase();
|
|
9010
|
+
}
|
|
9011
|
+
function ChartSideRail({
|
|
9012
|
+
sections,
|
|
9013
|
+
sectionExtent,
|
|
9014
|
+
variant = "hero"
|
|
9015
|
+
}) {
|
|
9016
|
+
if (!sections.some((s) => s.label)) return null;
|
|
9017
|
+
const isRail = variant === "rail";
|
|
9018
|
+
const labelLength = Math.max(0, sectionExtent - (isRail ? 0 : 6));
|
|
9019
|
+
const fontSize = isRail ? 7 : sideLabelFontSize(sectionExtent);
|
|
9020
|
+
const textStyle = {
|
|
9021
|
+
maxWidth: "100%",
|
|
9022
|
+
textAlign: "center",
|
|
9023
|
+
fontSize,
|
|
9024
|
+
fontWeight: "700",
|
|
9025
|
+
letterSpacing: isRail || fontSize < 10 ? 0 : 2,
|
|
9026
|
+
textTransform: "uppercase"
|
|
9027
|
+
};
|
|
9028
|
+
return /* @__PURE__ */ jsx(View, { style: { width: isRail ? 18 : 34 }, accessibilityElementsHidden: true, children: sections.map((s) => /* @__PURE__ */ jsx(
|
|
9029
|
+
View,
|
|
9030
|
+
{
|
|
9031
|
+
style: { height: sectionExtent, alignItems: "center", justifyContent: "center" },
|
|
9032
|
+
children: s.label ? /* @__PURE__ */ jsx(
|
|
8713
9033
|
View,
|
|
8714
9034
|
{
|
|
8715
9035
|
style: {
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
marginLeft: seg.leadingGap,
|
|
8720
|
-
borderRadius: radius,
|
|
8721
|
-
overflow: "hidden"
|
|
9036
|
+
width: labelLength,
|
|
9037
|
+
alignItems: "center",
|
|
9038
|
+
transform: [{ rotate: "-90deg" }]
|
|
8722
9039
|
},
|
|
8723
|
-
children:
|
|
8724
|
-
|
|
9040
|
+
children: /* @__PURE__ */ jsx(
|
|
9041
|
+
Text,
|
|
8725
9042
|
{
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
outputRange: seg.pulseColor
|
|
8732
|
-
})
|
|
8733
|
-
} : {
|
|
8734
|
-
opacity: pulse.interpolate({
|
|
8735
|
-
inputRange: [0, 1],
|
|
8736
|
-
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
8737
|
-
})
|
|
8738
|
-
}
|
|
8739
|
-
},
|
|
8740
|
-
accessibilityElementsHidden: true,
|
|
8741
|
-
testID
|
|
9043
|
+
className: "text-text-tertiary",
|
|
9044
|
+
style: textStyle,
|
|
9045
|
+
numberOfLines: 1,
|
|
9046
|
+
testID: s.testID,
|
|
9047
|
+
children: isRail ? s.label : sideLabelText(s.label, sectionExtent)
|
|
8742
9048
|
}
|
|
8743
|
-
)
|
|
8744
|
-
}
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
View,
|
|
8750
|
-
{
|
|
8751
|
-
style: {
|
|
8752
|
-
position: "absolute",
|
|
8753
|
-
left: `${marker.position * 100}%`,
|
|
8754
|
-
top: 0,
|
|
8755
|
-
bottom: 0,
|
|
8756
|
-
width: 2,
|
|
8757
|
-
backgroundColor: marker.color
|
|
8758
|
-
},
|
|
8759
|
-
accessibilityElementsHidden: true,
|
|
8760
|
-
testID: "segmented-bar-marker"
|
|
8761
|
-
}
|
|
8762
|
-
) : null
|
|
8763
|
-
] });
|
|
9049
|
+
)
|
|
9050
|
+
}
|
|
9051
|
+
) : null
|
|
9052
|
+
},
|
|
9053
|
+
s.testID
|
|
9054
|
+
)) });
|
|
8764
9055
|
}
|
|
8765
9056
|
|
|
8766
|
-
// src/components/custom/Workout/
|
|
8767
|
-
var
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
var MYO_UPCOMING_FADE = 0.6;
|
|
8780
|
-
var PULSE_RANGE = {
|
|
8781
|
-
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
8782
|
-
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
8783
|
-
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
8784
|
-
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
8785
|
-
};
|
|
8786
|
-
function velocityZoneColor(v) {
|
|
8787
|
-
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
8788
|
-
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
8789
|
-
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
8790
|
-
return SET_STRIP_ZONES.fastest;
|
|
8791
|
-
}
|
|
8792
|
-
function chunkedSegments(chunks, gap) {
|
|
8793
|
-
return chunks.flatMap(
|
|
8794
|
-
(vels, ci) => vels.map((v, ri) => ({
|
|
8795
|
-
color: velocityZoneColor(v),
|
|
8796
|
-
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
8797
|
-
}))
|
|
8798
|
-
);
|
|
8799
|
-
}
|
|
8800
|
-
function rangeSegments(floor, max, doneVels) {
|
|
8801
|
-
return Array.from({ length: max }, (_, i) => {
|
|
8802
|
-
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
8803
|
-
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
8804
|
-
});
|
|
8805
|
-
}
|
|
8806
|
-
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
8807
|
-
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
8808
|
-
const trail = Array.from(
|
|
8809
|
-
{ length: clusterCount },
|
|
8810
|
-
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
8811
|
-
color: SET_STRIP_VARIABLE_COLOR,
|
|
8812
|
-
opacity: MYO_UPCOMING_FADE ** ci,
|
|
8813
|
-
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
8814
|
-
}))
|
|
8815
|
-
).flat();
|
|
8816
|
-
return [...activation, ...trail];
|
|
8817
|
-
}
|
|
8818
|
-
function setSegments(set) {
|
|
8819
|
-
if (set.status === "todo") {
|
|
8820
|
-
return [{ color: TODO_COLOR }];
|
|
8821
|
-
}
|
|
8822
|
-
if (set.status === "done") {
|
|
8823
|
-
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
8824
|
-
}
|
|
8825
|
-
if (set.status === "range") {
|
|
8826
|
-
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
8827
|
-
}
|
|
8828
|
-
if (set.status === "drop") {
|
|
8829
|
-
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
8830
|
-
}
|
|
8831
|
-
if (set.status === "myo") {
|
|
8832
|
-
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
8833
|
-
}
|
|
8834
|
-
if (set.status === "myo-upcoming") {
|
|
8835
|
-
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
8836
|
-
}
|
|
8837
|
-
const performed = set.velocities.map((v) => {
|
|
8838
|
-
const color = velocityZoneColor(v);
|
|
8839
|
-
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
8840
|
-
});
|
|
8841
|
-
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
8842
|
-
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
8843
|
-
}
|
|
8844
|
-
function setBarSegmentTestID(seg) {
|
|
8845
|
-
if (seg.pulse) return "set-strip-pulse";
|
|
8846
|
-
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
8847
|
-
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
8848
|
-
}
|
|
8849
|
-
function SetBar({ set, height = 8 }) {
|
|
8850
|
-
return /* @__PURE__ */ jsx(
|
|
8851
|
-
SegmentedBar,
|
|
8852
|
-
{
|
|
8853
|
-
segments: setSegments(set),
|
|
8854
|
-
height,
|
|
8855
|
-
gap: 0,
|
|
8856
|
-
radius: 0,
|
|
8857
|
-
segmentTestID: setBarSegmentTestID,
|
|
8858
|
-
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
8859
|
-
testID: "set-strip-set"
|
|
8860
|
-
}
|
|
8861
|
-
);
|
|
8862
|
-
}
|
|
8863
|
-
|
|
8864
|
-
// src/components/custom/Workout/SetStrip.tsx
|
|
8865
|
-
var SET_STRIP_GAP = 5;
|
|
8866
|
-
function setCategory(set) {
|
|
8867
|
-
switch (set.status) {
|
|
8868
|
-
case "done":
|
|
8869
|
-
case "drop":
|
|
8870
|
-
case "myo":
|
|
8871
|
-
return "done";
|
|
8872
|
-
case "active":
|
|
8873
|
-
return "active";
|
|
8874
|
-
case "range":
|
|
8875
|
-
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
8876
|
-
case "todo":
|
|
8877
|
-
case "myo-upcoming":
|
|
8878
|
-
return "upcoming";
|
|
8879
|
-
}
|
|
8880
|
-
}
|
|
8881
|
-
function describeSets(sets) {
|
|
8882
|
-
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
8883
|
-
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
8884
|
-
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
8885
|
-
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
8886
|
-
}
|
|
8887
|
-
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
8888
|
-
return /* @__PURE__ */ jsx(
|
|
8889
|
-
View,
|
|
8890
|
-
{
|
|
8891
|
-
className,
|
|
8892
|
-
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
8893
|
-
accessibilityRole: "image",
|
|
8894
|
-
accessibilityLabel: describeSets(sets),
|
|
8895
|
-
testID: "set-strip",
|
|
8896
|
-
...props,
|
|
8897
|
-
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
8898
|
-
}
|
|
8899
|
-
);
|
|
8900
|
-
}
|
|
8901
|
-
|
|
8902
|
-
// src/components/custom/Workout/VelocityStrip.tsx
|
|
8903
|
-
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
8904
|
-
var bandIdToEffortColor = {
|
|
8905
|
-
speed: sequentialEffort[0],
|
|
8906
|
-
// green-300
|
|
8907
|
-
power: sequentialEffort[2],
|
|
8908
|
-
// amber-300 (gold)
|
|
8909
|
-
strengthSpeed: sequentialEffort[3],
|
|
8910
|
-
// orange-400
|
|
8911
|
-
maximalStrength: sequentialEffort[4],
|
|
8912
|
-
// red-600
|
|
8913
|
-
grinding: sequentialEffort[5]
|
|
8914
|
-
// red-700 — the distinct 5th band
|
|
9057
|
+
// src/components/custom/Workout/VelocityStrip.tsx
|
|
9058
|
+
var VEL_COLORS = WORKOUT_TOKENS.scale;
|
|
9059
|
+
var bandIdToEffortColor = {
|
|
9060
|
+
speed: sequentialEffort[0],
|
|
9061
|
+
// green-300
|
|
9062
|
+
power: sequentialEffort[2],
|
|
9063
|
+
// amber-300 (gold)
|
|
9064
|
+
strengthSpeed: sequentialEffort[3],
|
|
9065
|
+
// orange-400
|
|
9066
|
+
maximalStrength: sequentialEffort[4],
|
|
9067
|
+
// red-600
|
|
9068
|
+
grinding: sequentialEffort[5]
|
|
9069
|
+
// red-700 — the distinct 5th band
|
|
8915
9070
|
};
|
|
8916
9071
|
function getVelocityZoneColor(velocity) {
|
|
8917
9072
|
if (velocity >= 1) return "vel-green";
|
|
@@ -8943,6 +9098,18 @@ function calculateMeanVelocity(velocities) {
|
|
|
8943
9098
|
const sum = velocities.reduce((acc, v) => acc + v, 0);
|
|
8944
9099
|
return sum / velocities.length;
|
|
8945
9100
|
}
|
|
9101
|
+
var VL_LOSS_THRESHOLDS = [10, 20, 30];
|
|
9102
|
+
function getVelocityLossColor(lossPct) {
|
|
9103
|
+
const [t1, t26, t32] = VL_LOSS_THRESHOLDS;
|
|
9104
|
+
if (lossPct < t1) return VEL_COLORS.green;
|
|
9105
|
+
if (lossPct < t26) return VEL_COLORS.yellow;
|
|
9106
|
+
if (lossPct < t32) return VEL_COLORS.orange;
|
|
9107
|
+
return VEL_COLORS.red;
|
|
9108
|
+
}
|
|
9109
|
+
function velocityLossForRep(velocity, best) {
|
|
9110
|
+
if (best <= 0) return 0;
|
|
9111
|
+
return Math.max(0, Math.round((best - velocity) / best * 100));
|
|
9112
|
+
}
|
|
8946
9113
|
function classifyBand(velocity, bands) {
|
|
8947
9114
|
for (const band of bands) {
|
|
8948
9115
|
if (band.max === null || velocity < band.max) return band;
|
|
@@ -8952,15 +9119,17 @@ function classifyBand(velocity, bands) {
|
|
|
8952
9119
|
function bandColor(band) {
|
|
8953
9120
|
return bandIdToEffortColor[band.id] ?? VEL_COLORS.green;
|
|
8954
9121
|
}
|
|
9122
|
+
function makeBarColorFor(zones) {
|
|
9123
|
+
const hasZones = zones != null && zones.length > 0;
|
|
9124
|
+
return (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
|
|
9125
|
+
}
|
|
8955
9126
|
function getLossStyle(loss) {
|
|
8956
9127
|
if (loss > 25) return { color: VEL_COLORS.red };
|
|
8957
9128
|
if (loss > 20) return { color: VEL_COLORS.orange };
|
|
8958
9129
|
return null;
|
|
8959
9130
|
}
|
|
8960
|
-
var
|
|
9131
|
+
var REP_GAP2 = 2;
|
|
8961
9132
|
var WIDE_GAP = 8;
|
|
8962
|
-
var TODO_COLOR2 = primitiveColors.charcoal[300];
|
|
8963
|
-
var CONTINUE_OUTLINE = primitiveRamps.cyan[800];
|
|
8964
9133
|
function deriveDoneVelocities(set) {
|
|
8965
9134
|
switch (set.type) {
|
|
8966
9135
|
case "drop":
|
|
@@ -8976,7 +9145,7 @@ function chunkedRepSlots(chunks) {
|
|
|
8976
9145
|
chunks.forEach((chunk) => {
|
|
8977
9146
|
chunk.forEach((velocity, ri) => {
|
|
8978
9147
|
const first = slots.length === 0;
|
|
8979
|
-
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP :
|
|
9148
|
+
slots.push({ kind: "rep", velocity, leadingGap: first ? 0 : ri === 0 ? WIDE_GAP : REP_GAP2 });
|
|
8980
9149
|
});
|
|
8981
9150
|
});
|
|
8982
9151
|
return slots;
|
|
@@ -8990,7 +9159,7 @@ function buildSlots(set) {
|
|
|
8990
9159
|
return {
|
|
8991
9160
|
kind: done ? "rep" : "todo",
|
|
8992
9161
|
velocity: done ? set.velocities[i] : void 0,
|
|
8993
|
-
leadingGap: i === 0 ? 0 :
|
|
9162
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
8994
9163
|
};
|
|
8995
9164
|
});
|
|
8996
9165
|
}
|
|
@@ -9001,7 +9170,7 @@ function buildSlots(set) {
|
|
|
9001
9170
|
return {
|
|
9002
9171
|
kind,
|
|
9003
9172
|
velocity: kind === "rep" ? set.velocities[i] : void 0,
|
|
9004
|
-
leadingGap: i === 0 ? 0 :
|
|
9173
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
9005
9174
|
};
|
|
9006
9175
|
});
|
|
9007
9176
|
}
|
|
@@ -9009,16 +9178,16 @@ function buildSlots(set) {
|
|
|
9009
9178
|
const reps = set.velocities.map((velocity, i) => ({
|
|
9010
9179
|
kind: "rep",
|
|
9011
9180
|
velocity,
|
|
9012
|
-
leadingGap: i === 0 ? 0 :
|
|
9181
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
9013
9182
|
}));
|
|
9014
|
-
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 :
|
|
9183
|
+
reps.push({ kind: "continue", leadingGap: reps.length === 0 ? 0 : REP_GAP2 });
|
|
9015
9184
|
return reps;
|
|
9016
9185
|
}
|
|
9017
9186
|
case "drop":
|
|
9018
9187
|
return chunkedRepSlots(set.subloads);
|
|
9019
9188
|
case "myo": {
|
|
9020
9189
|
const slots = chunkedRepSlots([set.activation, ...set.clusters]);
|
|
9021
|
-
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 :
|
|
9190
|
+
if (set.open) slots.push({ kind: "continue", leadingGap: slots.length === 0 ? 0 : REP_GAP2 });
|
|
9022
9191
|
return slots;
|
|
9023
9192
|
}
|
|
9024
9193
|
case "cluster": {
|
|
@@ -9029,7 +9198,7 @@ function buildSlots(set) {
|
|
|
9029
9198
|
return {
|
|
9030
9199
|
kind: done ? "rep" : "todo",
|
|
9031
9200
|
velocity: done ? set.velocities[i] : void 0,
|
|
9032
|
-
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP :
|
|
9201
|
+
leadingGap: i === 0 ? 0 : boundary ? WIDE_GAP : REP_GAP2
|
|
9033
9202
|
};
|
|
9034
9203
|
});
|
|
9035
9204
|
}
|
|
@@ -9051,253 +9220,406 @@ function setAccessibilityLabel(set, repCount) {
|
|
|
9051
9220
|
return `Velocity strip, cluster set, groups of ${set.groupSize}, ${repCount} reps`;
|
|
9052
9221
|
}
|
|
9053
9222
|
}
|
|
9054
|
-
function slotAccessibilityLabel(slot, index) {
|
|
9055
|
-
switch (slot.kind) {
|
|
9056
|
-
case "rep":
|
|
9057
|
-
return `Rep ${index + 1}: ${formatVelocity(slot.velocity ?? 0)} meters per second`;
|
|
9058
|
-
case "todo":
|
|
9059
|
-
return `Rep ${index + 1}: planned`;
|
|
9060
|
-
case "variable":
|
|
9061
|
-
return `Rep ${index + 1}: variable`;
|
|
9062
|
-
case "continue":
|
|
9063
|
-
return "Keep going";
|
|
9064
|
-
}
|
|
9065
|
-
}
|
|
9066
|
-
var EXPANDED_TODO_STUB_PCT = 16;
|
|
9067
|
-
var EXPANDED_ENCODED_PCT = 45;
|
|
9068
9223
|
var EXPANDED_HEIGHT = 60;
|
|
9069
|
-
var
|
|
9070
|
-
var BARE_STUB_HEIGHT = 3;
|
|
9071
|
-
var HERO_HEIGHT = 220;
|
|
9072
|
-
var HERO_LABEL_HEADROOM = 20;
|
|
9073
|
-
var HERO_BAR_GAP = 8;
|
|
9074
|
-
var HERO_BAR_MAX_WIDTH = 120;
|
|
9075
|
-
var HERO_LABEL_MIN_BAR_WIDTH = 30;
|
|
9076
|
-
var HERO_BAR_RADIUS = 5;
|
|
9077
|
-
var HERO_MIN_BAR_HEIGHT = 4;
|
|
9078
|
-
var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
|
|
9224
|
+
var COMPACT_HEIGHT = 8;
|
|
9079
9225
|
var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
return reduced;
|
|
9226
|
+
function DashedReferenceLine({
|
|
9227
|
+
anchor,
|
|
9228
|
+
offset,
|
|
9229
|
+
testID
|
|
9230
|
+
}) {
|
|
9231
|
+
const edge = anchor === "top" ? { top: offset } : { bottom: offset };
|
|
9232
|
+
return /* @__PURE__ */ jsx(
|
|
9233
|
+
View,
|
|
9234
|
+
{
|
|
9235
|
+
accessibilityElementsHidden: true,
|
|
9236
|
+
style: {
|
|
9237
|
+
position: "absolute",
|
|
9238
|
+
left: 0,
|
|
9239
|
+
right: 0,
|
|
9240
|
+
borderTopWidth: 1,
|
|
9241
|
+
borderStyle: "dashed",
|
|
9242
|
+
borderColor: HERO_REFERENCE_COLOR,
|
|
9243
|
+
pointerEvents: "none",
|
|
9244
|
+
...edge
|
|
9245
|
+
},
|
|
9246
|
+
testID
|
|
9247
|
+
}
|
|
9248
|
+
);
|
|
9104
9249
|
}
|
|
9105
|
-
var
|
|
9106
|
-
var
|
|
9107
|
-
var
|
|
9108
|
-
function
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9250
|
+
var BAND_LABEL_FONT = "monospace";
|
|
9251
|
+
var VL_LABEL_MIN_PLOT = 65;
|
|
9252
|
+
var VL_SEMANTIC = getSemanticColors("dark");
|
|
9253
|
+
function VelocityLossBands({
|
|
9254
|
+
best,
|
|
9255
|
+
scaleDenom,
|
|
9256
|
+
plotHeight,
|
|
9257
|
+
flip = false
|
|
9258
|
+
}) {
|
|
9259
|
+
if (best <= 0 || scaleDenom <= 0 || plotHeight <= 0) return null;
|
|
9260
|
+
const yOf = (v) => v / scaleDenom * plotHeight;
|
|
9261
|
+
const vl20 = best * 0.8;
|
|
9262
|
+
const vl30 = best * 0.7;
|
|
9263
|
+
const vlFont = Math.round(Math.max(7, Math.min(9, plotHeight * 0.05)));
|
|
9264
|
+
const showLabels = plotHeight >= VL_LABEL_MIN_PLOT;
|
|
9265
|
+
const band = (loV, hiV, color) => /* @__PURE__ */ jsx(
|
|
9266
|
+
View,
|
|
9267
|
+
{
|
|
9268
|
+
style: {
|
|
9269
|
+
position: "absolute",
|
|
9270
|
+
left: 0,
|
|
9271
|
+
right: 0,
|
|
9272
|
+
bottom: yOf(loV),
|
|
9273
|
+
height: Math.max(0, yOf(hiV) - yOf(loV)),
|
|
9274
|
+
backgroundColor: color
|
|
9275
|
+
}
|
|
9116
9276
|
}
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9277
|
+
);
|
|
9278
|
+
const threshold = (v, color, label) => /* @__PURE__ */ jsxs(
|
|
9279
|
+
View,
|
|
9280
|
+
{
|
|
9281
|
+
style: {
|
|
9282
|
+
position: "absolute",
|
|
9283
|
+
left: 0,
|
|
9284
|
+
right: 0,
|
|
9285
|
+
bottom: yOf(v),
|
|
9286
|
+
// Collapse to zero height so the dashed border lands EXACTLY on `yOf(v)` — the
|
|
9287
|
+
// band edge — instead of floating up half a text-row (the label then centres on
|
|
9288
|
+
// the line, breaking it). Matches DashedReferenceLine's bare-border anchoring.
|
|
9289
|
+
height: 0,
|
|
9290
|
+
flexDirection: "row",
|
|
9291
|
+
alignItems: "center"
|
|
9292
|
+
},
|
|
9293
|
+
children: [
|
|
9294
|
+
/* @__PURE__ */ jsx(View, { style: { flex: 9, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } }),
|
|
9295
|
+
showLabels ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9296
|
+
/* @__PURE__ */ jsx(
|
|
9297
|
+
Text,
|
|
9298
|
+
{
|
|
9299
|
+
style: {
|
|
9300
|
+
marginHorizontal: 6,
|
|
9301
|
+
fontSize: vlFont,
|
|
9302
|
+
fontWeight: "800",
|
|
9303
|
+
fontFamily: BAND_LABEL_FONT,
|
|
9304
|
+
color,
|
|
9305
|
+
...flip ? { transform: [{ scaleY: -1 }] } : null
|
|
9306
|
+
},
|
|
9307
|
+
children: label
|
|
9308
|
+
}
|
|
9309
|
+
),
|
|
9310
|
+
/* @__PURE__ */ jsx(View, { style: { flex: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: color } })
|
|
9311
|
+
] }) : null
|
|
9312
|
+
]
|
|
9313
|
+
}
|
|
9314
|
+
);
|
|
9315
|
+
return /* @__PURE__ */ jsxs(
|
|
9316
|
+
View,
|
|
9317
|
+
{
|
|
9318
|
+
accessibilityElementsHidden: true,
|
|
9319
|
+
pointerEvents: "none",
|
|
9320
|
+
style: { position: "absolute", left: 0, right: 0, bottom: 0, top: 0 },
|
|
9321
|
+
testID: "velocity-loss-bands",
|
|
9322
|
+
children: [
|
|
9323
|
+
band(0, vl30, alpha(VL_SEMANTIC["status-error"], 0.09)),
|
|
9324
|
+
band(vl30, vl20, alpha(VL_SEMANTIC["status-warning"], 0.08)),
|
|
9325
|
+
threshold(vl20, alpha(VL_SEMANTIC["status-warning"], 0.75), "VL 20%"),
|
|
9326
|
+
threshold(vl30, alpha(VL_SEMANTIC["status-error"], 0.75), "VL 30%")
|
|
9327
|
+
]
|
|
9141
9328
|
}
|
|
9142
|
-
|
|
9143
|
-
return liveScale;
|
|
9329
|
+
);
|
|
9144
9330
|
}
|
|
9145
|
-
function
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9331
|
+
function velocityReferenceOverlay(g, showLossBands) {
|
|
9332
|
+
const referencePx = g.best > 0 && g.scaleDenom > 0 ? Math.min(g.plotHeight, g.yOf(g.best)) : 0;
|
|
9333
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9334
|
+
showLossBands && /* @__PURE__ */ jsx(
|
|
9335
|
+
VelocityLossBands,
|
|
9336
|
+
{
|
|
9337
|
+
best: g.best,
|
|
9338
|
+
scaleDenom: g.scaleDenom,
|
|
9339
|
+
plotHeight: g.plotHeight,
|
|
9340
|
+
flip: g.flip
|
|
9341
|
+
}
|
|
9342
|
+
),
|
|
9343
|
+
referencePx > 0 && /* @__PURE__ */ jsx(
|
|
9344
|
+
DashedReferenceLine,
|
|
9345
|
+
{
|
|
9346
|
+
anchor: "bottom",
|
|
9347
|
+
offset: referencePx,
|
|
9348
|
+
testID: "velocity-hero-reference"
|
|
9349
|
+
}
|
|
9350
|
+
)
|
|
9351
|
+
] });
|
|
9352
|
+
}
|
|
9353
|
+
var ANIMATION_DURATION = 400;
|
|
9354
|
+
var DUAL_HERO_HEIGHT = SET_BAR_DEFAULT_HEIGHT;
|
|
9355
|
+
var DUAL_RAIL_HEIGHT = 96;
|
|
9356
|
+
var DUAL_COMPACT_HEIGHT = 11.5;
|
|
9357
|
+
var DUAL_WING_GAP = 2;
|
|
9358
|
+
var COMPACT_FOLD_GAP = 1.5;
|
|
9359
|
+
var COMPACT_FOLD_HALF = 5;
|
|
9360
|
+
function streamDone(stream) {
|
|
9361
|
+
return stream.set ? deriveDoneVelocities(stream.set) : stream.velocities ?? [];
|
|
9362
|
+
}
|
|
9363
|
+
function streamNaturalSlots(stream, targetReps) {
|
|
9364
|
+
if (stream.set) {
|
|
9365
|
+
return buildSlots(stream.set).map((s) => ({
|
|
9366
|
+
kind: s.kind,
|
|
9367
|
+
value: s.velocity,
|
|
9368
|
+
leadingGap: s.leadingGap
|
|
9369
|
+
}));
|
|
9370
|
+
}
|
|
9371
|
+
const reps = (stream.velocities ?? []).map((v, i) => ({
|
|
9372
|
+
kind: "rep",
|
|
9373
|
+
value: v,
|
|
9374
|
+
leadingGap: i === 0 ? 0 : REP_GAP2
|
|
9375
|
+
}));
|
|
9376
|
+
const pad = Math.max(0, (targetReps ?? 0) - reps.length);
|
|
9377
|
+
return [...reps, ...Array.from({ length: pad }, () => ({ kind: "todo" }))];
|
|
9378
|
+
}
|
|
9379
|
+
function mergeColumnKind(l, r) {
|
|
9380
|
+
const kinds = [l?.kind, r?.kind];
|
|
9381
|
+
if (kinds.includes("variable")) return "variable";
|
|
9382
|
+
if (kinds.includes("continue")) return "continue";
|
|
9383
|
+
if (kinds.includes("rep")) return "rep";
|
|
9384
|
+
return "todo";
|
|
9385
|
+
}
|
|
9386
|
+
function sideColumnCell(slot, kind, leadingGap) {
|
|
9387
|
+
if (kind !== "rep") return { kind, leadingGap };
|
|
9388
|
+
return slot?.kind === "rep" ? { kind: "rep", value: slot.value, leadingGap } : { kind: "empty", leadingGap };
|
|
9389
|
+
}
|
|
9390
|
+
function alignDualSlots(left, right) {
|
|
9391
|
+
const n = Math.max(left.length, right.length);
|
|
9392
|
+
const L = [];
|
|
9393
|
+
const R = [];
|
|
9394
|
+
for (let i = 0; i < n; i++) {
|
|
9395
|
+
const lt = left[i];
|
|
9396
|
+
const rt = right[i];
|
|
9397
|
+
const kind = mergeColumnKind(lt, rt);
|
|
9398
|
+
const gap = Math.max(lt?.leadingGap ?? REP_GAP2, rt?.leadingGap ?? REP_GAP2);
|
|
9399
|
+
L.push(sideColumnCell(lt, kind, gap));
|
|
9400
|
+
R.push(sideColumnCell(rt, kind, gap));
|
|
9401
|
+
}
|
|
9402
|
+
return { left: L, right: R };
|
|
9403
|
+
}
|
|
9404
|
+
function DualVelocityHero({
|
|
9405
|
+
leftDone,
|
|
9406
|
+
rightDone,
|
|
9407
|
+
leftStream,
|
|
9408
|
+
rightStream,
|
|
9409
|
+
leftLabel,
|
|
9410
|
+
rightLabel,
|
|
9411
|
+
zones,
|
|
9412
|
+
barColor: barColor2,
|
|
9413
|
+
scale,
|
|
9152
9414
|
targetReps,
|
|
9415
|
+
liveRepIndex,
|
|
9153
9416
|
height,
|
|
9154
9417
|
className,
|
|
9418
|
+
label,
|
|
9155
9419
|
viewProps
|
|
9156
9420
|
}) {
|
|
9157
|
-
const
|
|
9158
|
-
const
|
|
9159
|
-
const
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
|
|
9164
|
-
const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
|
|
9165
|
-
const totalSlots = doneVelocities.length + pendingCount;
|
|
9166
|
-
const barWidth = plotW > 0 && totalSlots > 0 ? Math.min(HERO_BAR_MAX_WIDTH, (plotW - HERO_BAR_GAP * (totalSlots - 1)) / totalSlots) : HERO_BAR_MAX_WIDTH;
|
|
9167
|
-
const labelsCrowded = plotW > 0 && barWidth < HERO_LABEL_MIN_BAR_WIDTH;
|
|
9168
|
-
const heroGap = plotW === 0 ? HERO_BAR_GAP : barWidth < 16 ? 2 : barWidth < 28 ? 4 : HERO_BAR_GAP;
|
|
9169
|
-
const peakIndex = doneVelocities.reduce(
|
|
9170
|
-
(best, v, i) => v > doneVelocities[best] ? i : best,
|
|
9171
|
-
0
|
|
9172
|
-
);
|
|
9173
|
-
const showBarLabel = (i) => !labelsCrowded || i === peakIndex || i === liveRepIndex;
|
|
9174
|
-
const repCount = doneVelocities.length;
|
|
9175
|
-
const total = Math.max(repCount, targetReps ?? repCount);
|
|
9176
|
-
const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
9421
|
+
const plotHalf = (height - DUAL_WING_GAP) / 2;
|
|
9422
|
+
const sharedMax = Math.max(...leftDone, ...rightDone, 0);
|
|
9423
|
+
const aligned = alignDualSlots(
|
|
9424
|
+
leftStream ? streamNaturalSlots(leftStream, targetReps) : [],
|
|
9425
|
+
rightStream ? streamNaturalSlots(rightStream, targetReps) : []
|
|
9426
|
+
);
|
|
9177
9427
|
const { style: externalStyle, ...restProps } = viewProps;
|
|
9178
|
-
|
|
9428
|
+
const wing = (orientation, columns, done) => /* @__PURE__ */ jsx(
|
|
9429
|
+
VelocityStrip,
|
|
9430
|
+
{
|
|
9431
|
+
variant: "hero",
|
|
9432
|
+
orientation,
|
|
9433
|
+
velocities: done,
|
|
9434
|
+
columnSlots: columns,
|
|
9435
|
+
scale,
|
|
9436
|
+
scaleMax: scale === "fixed" ? void 0 : sharedMax,
|
|
9437
|
+
barColor: barColor2,
|
|
9438
|
+
zones,
|
|
9439
|
+
liveRepIndex,
|
|
9440
|
+
height: plotHalf,
|
|
9441
|
+
hideBaseline: true
|
|
9442
|
+
}
|
|
9443
|
+
);
|
|
9444
|
+
return /* @__PURE__ */ jsxs(
|
|
9179
9445
|
View,
|
|
9180
9446
|
{
|
|
9181
9447
|
className,
|
|
9182
|
-
style: [{ height }, externalStyle],
|
|
9448
|
+
style: [{ height, flexDirection: "row" }, externalStyle],
|
|
9183
9449
|
accessibilityRole: "image",
|
|
9184
9450
|
accessibilityLabel: label,
|
|
9185
|
-
testID: "velocity-strip
|
|
9451
|
+
testID: "dual-velocity-strip",
|
|
9186
9452
|
...restProps,
|
|
9187
|
-
children:
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
children:
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9453
|
+
children: [
|
|
9454
|
+
/* @__PURE__ */ jsx(
|
|
9455
|
+
ChartSideRail,
|
|
9456
|
+
{
|
|
9457
|
+
sectionExtent: plotHalf,
|
|
9458
|
+
variant: "hero",
|
|
9459
|
+
sections: [
|
|
9460
|
+
{ label: leftLabel, testID: "dual-velocity-side-label-L" },
|
|
9461
|
+
{ label: rightLabel, testID: "dual-velocity-side-label-R" }
|
|
9462
|
+
]
|
|
9463
|
+
}
|
|
9464
|
+
),
|
|
9465
|
+
/* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: DUAL_WING_GAP }, children: [
|
|
9466
|
+
/* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, testID: "dual-velocity-wing-up", children: wing("up", aligned.left, leftDone) }),
|
|
9467
|
+
/* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, testID: "dual-velocity-wing-down", children: wing("down", aligned.right, rightDone) })
|
|
9468
|
+
] })
|
|
9469
|
+
]
|
|
9470
|
+
}
|
|
9471
|
+
);
|
|
9472
|
+
}
|
|
9473
|
+
function DualVelocityRail({
|
|
9474
|
+
leftDone,
|
|
9475
|
+
rightDone,
|
|
9476
|
+
leftStream,
|
|
9477
|
+
rightStream,
|
|
9478
|
+
zones,
|
|
9479
|
+
barColor: barColor2,
|
|
9480
|
+
scale,
|
|
9481
|
+
targetReps,
|
|
9482
|
+
liveRepIndex,
|
|
9483
|
+
height,
|
|
9484
|
+
className,
|
|
9485
|
+
label,
|
|
9486
|
+
viewProps
|
|
9487
|
+
}) {
|
|
9488
|
+
const plotHalf = (height - DUAL_WING_GAP) / 2;
|
|
9489
|
+
const sharedMax = Math.max(...leftDone, ...rightDone, 0);
|
|
9490
|
+
const aligned = alignDualSlots(
|
|
9491
|
+
leftStream ? streamNaturalSlots(leftStream, targetReps) : [],
|
|
9492
|
+
rightStream ? streamNaturalSlots(rightStream, targetReps) : []
|
|
9493
|
+
);
|
|
9494
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
9495
|
+
const wing = (orientation, columns, done) => /* @__PURE__ */ jsx(
|
|
9496
|
+
VelocityStrip,
|
|
9497
|
+
{
|
|
9498
|
+
variant: "expanded",
|
|
9499
|
+
showNumbers: false,
|
|
9500
|
+
showInfo: false,
|
|
9501
|
+
orientation,
|
|
9502
|
+
velocities: done,
|
|
9503
|
+
columnSlots: columns,
|
|
9504
|
+
scale,
|
|
9505
|
+
scaleMax: scale === "fixed" ? void 0 : sharedMax,
|
|
9506
|
+
barColor: barColor2,
|
|
9507
|
+
zones,
|
|
9508
|
+
liveRepIndex,
|
|
9509
|
+
height: plotHalf
|
|
9510
|
+
}
|
|
9511
|
+
);
|
|
9512
|
+
return /* @__PURE__ */ jsxs(
|
|
9513
|
+
View,
|
|
9514
|
+
{
|
|
9515
|
+
className,
|
|
9516
|
+
style: [{ height, flexDirection: "column", gap: DUAL_WING_GAP }, externalStyle],
|
|
9517
|
+
accessibilityRole: "image",
|
|
9518
|
+
accessibilityLabel: label,
|
|
9519
|
+
testID: "dual-velocity-strip",
|
|
9520
|
+
...restProps,
|
|
9521
|
+
children: [
|
|
9522
|
+
/* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, testID: "dual-velocity-wing-up", children: wing("up", aligned.left, leftDone) }),
|
|
9523
|
+
/* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, testID: "dual-velocity-wing-down", children: wing("down", aligned.right, rightDone) })
|
|
9524
|
+
]
|
|
9525
|
+
}
|
|
9526
|
+
);
|
|
9527
|
+
}
|
|
9528
|
+
function DualVelocityCompactStrip({
|
|
9529
|
+
leftDone,
|
|
9530
|
+
rightDone,
|
|
9531
|
+
leftStream,
|
|
9532
|
+
rightStream,
|
|
9533
|
+
zones,
|
|
9534
|
+
barColor: barColor2,
|
|
9535
|
+
targetReps,
|
|
9536
|
+
height,
|
|
9537
|
+
className,
|
|
9538
|
+
label,
|
|
9539
|
+
viewProps
|
|
9540
|
+
}) {
|
|
9541
|
+
const half = height === DUAL_COMPACT_HEIGHT ? COMPACT_FOLD_HALF : (height - COMPACT_FOLD_GAP) / 2;
|
|
9542
|
+
const aligned = alignDualSlots(
|
|
9543
|
+
leftStream ? streamNaturalSlots(leftStream, targetReps) : [],
|
|
9544
|
+
rightStream ? streamNaturalSlots(rightStream, targetReps) : []
|
|
9545
|
+
);
|
|
9546
|
+
const { style: externalStyle, ...restProps } = viewProps;
|
|
9547
|
+
const wing = (orientation, columns, done) => /* @__PURE__ */ jsx(
|
|
9548
|
+
VelocityStrip,
|
|
9549
|
+
{
|
|
9550
|
+
variant: "compact",
|
|
9551
|
+
orientation,
|
|
9552
|
+
velocities: done,
|
|
9553
|
+
columnSlots: columns,
|
|
9554
|
+
barColor: barColor2,
|
|
9555
|
+
zones,
|
|
9556
|
+
height: half
|
|
9557
|
+
}
|
|
9558
|
+
);
|
|
9559
|
+
return /* @__PURE__ */ jsxs(
|
|
9560
|
+
View,
|
|
9561
|
+
{
|
|
9562
|
+
className,
|
|
9563
|
+
style: [{ height, flexDirection: "column", gap: COMPACT_FOLD_GAP }, externalStyle],
|
|
9564
|
+
accessibilityRole: "image",
|
|
9565
|
+
accessibilityLabel: label,
|
|
9566
|
+
testID: "dual-velocity-strip",
|
|
9567
|
+
...restProps,
|
|
9568
|
+
children: [
|
|
9569
|
+
/* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, testID: "dual-velocity-wing-up", children: wing("up", aligned.left, leftDone) }),
|
|
9570
|
+
/* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, testID: "dual-velocity-wing-down", children: wing("down", aligned.right, rightDone) })
|
|
9571
|
+
]
|
|
9294
9572
|
}
|
|
9295
9573
|
);
|
|
9296
9574
|
}
|
|
9575
|
+
function DualVelocityStrip({
|
|
9576
|
+
left,
|
|
9577
|
+
right,
|
|
9578
|
+
zones,
|
|
9579
|
+
barColor: barColor2 = "loss",
|
|
9580
|
+
targetReps,
|
|
9581
|
+
liveRepIndex,
|
|
9582
|
+
variant = "hero",
|
|
9583
|
+
scale = "peak",
|
|
9584
|
+
height,
|
|
9585
|
+
className,
|
|
9586
|
+
...props
|
|
9587
|
+
}) {
|
|
9588
|
+
const resolvedHeight = height ?? (variant === "hero" ? DUAL_HERO_HEIGHT : variant === "compact" ? DUAL_COMPACT_HEIGHT : DUAL_RAIL_HEIGHT);
|
|
9589
|
+
const leftDone = streamDone(left);
|
|
9590
|
+
const rightDone = streamDone(right);
|
|
9591
|
+
const label = `Dual velocity chart, left ${leftDone.length} of ${Math.max(leftDone.length, targetReps ?? leftDone.length)} reps, right ${rightDone.length} of ${Math.max(rightDone.length, targetReps ?? rightDone.length)} reps`;
|
|
9592
|
+
const shared = {
|
|
9593
|
+
leftDone,
|
|
9594
|
+
rightDone,
|
|
9595
|
+
leftStream: left,
|
|
9596
|
+
rightStream: right,
|
|
9597
|
+
leftLabel: left.label,
|
|
9598
|
+
rightLabel: right.label,
|
|
9599
|
+
zones,
|
|
9600
|
+
barColor: barColor2,
|
|
9601
|
+
scale,
|
|
9602
|
+
targetReps,
|
|
9603
|
+
height: resolvedHeight,
|
|
9604
|
+
className,
|
|
9605
|
+
label,
|
|
9606
|
+
viewProps: props
|
|
9607
|
+
};
|
|
9608
|
+
if (variant === "rail") return /* @__PURE__ */ jsx(DualVelocityRail, { ...shared, liveRepIndex });
|
|
9609
|
+
if (variant === "compact") return /* @__PURE__ */ jsx(DualVelocityCompactStrip, { ...shared });
|
|
9610
|
+
return /* @__PURE__ */ jsx(DualVelocityHero, { ...shared, liveRepIndex });
|
|
9611
|
+
}
|
|
9297
9612
|
function VelocityStrip({
|
|
9298
9613
|
velocities,
|
|
9299
9614
|
set,
|
|
9300
9615
|
zones,
|
|
9616
|
+
barColor: barColor2 = "loss",
|
|
9617
|
+
showLossBands,
|
|
9618
|
+
orientation = "up",
|
|
9619
|
+
scaleMax,
|
|
9620
|
+
hideBaseline,
|
|
9621
|
+
columnSlots,
|
|
9622
|
+
label,
|
|
9301
9623
|
liveRepIndex,
|
|
9302
9624
|
expanded = true,
|
|
9303
9625
|
onToggle,
|
|
@@ -9311,51 +9633,29 @@ function VelocityStrip({
|
|
|
9311
9633
|
className,
|
|
9312
9634
|
...props
|
|
9313
9635
|
}) {
|
|
9636
|
+
const lossBandsOn = showLossBands ?? barColor2 === "loss";
|
|
9314
9637
|
const doneVelocities = set ? deriveDoneVelocities(set) : velocities ?? [];
|
|
9315
|
-
const slots = set ? buildSlots(set) : doneVelocities.map((v, i) => ({
|
|
9316
|
-
kind: "rep",
|
|
9317
|
-
velocity: v,
|
|
9318
|
-
leadingGap: i === 0 ? 0 : REP_GAP
|
|
9319
|
-
}));
|
|
9320
9638
|
const maxVelocity = Math.max(...doneVelocities, 0);
|
|
9321
9639
|
const meanVelocity = calculateMeanVelocity(doneVelocities);
|
|
9322
9640
|
const loss = calculateVelocityLoss(doneVelocities);
|
|
9323
9641
|
const framed = showNumbers || showInfo;
|
|
9324
|
-
const scaleDenom = scaleDenominator(scale, maxVelocity);
|
|
9325
9642
|
const hasZones = zones != null && zones.length > 0;
|
|
9326
|
-
const
|
|
9327
|
-
const
|
|
9328
|
-
if (slot.kind === "rep") return barColorFor(slot.velocity ?? 0);
|
|
9329
|
-
if (slot.kind === "todo") return TODO_COLOR2;
|
|
9330
|
-
return SET_STRIP_VARIABLE_COLOR;
|
|
9331
|
-
};
|
|
9643
|
+
const zoneColorFor = makeBarColorFor(zones);
|
|
9644
|
+
const barColorFor = (v) => barColor2 === "loss" ? getVelocityLossColor(velocityLossForRep(v, maxVelocity)) : zoneColorFor(v);
|
|
9332
9645
|
const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
|
|
9333
|
-
const [
|
|
9334
|
-
const [labelOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
|
|
9646
|
+
const [expandProgress] = useState(() => new Animated.Value(expanded ? 1 : 0));
|
|
9335
9647
|
const [infoOpacity] = useState(() => new Animated.Value(expanded ? 1 : 0));
|
|
9336
9648
|
const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
|
|
9337
9649
|
const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
|
|
9338
|
-
const liveScale = useLiveRepPop(
|
|
9339
|
-
variant === "expanded" && framed,
|
|
9340
|
-
liveRepIndex,
|
|
9341
|
-
liveVelocity,
|
|
9342
|
-
isNewPeak
|
|
9343
|
-
);
|
|
9344
9650
|
useEffect(() => {
|
|
9345
9651
|
if (variant !== "expanded" || !framed) return;
|
|
9346
9652
|
Animated.parallel([
|
|
9347
|
-
Animated.timing(
|
|
9348
|
-
toValue: expanded ?
|
|
9653
|
+
Animated.timing(expandProgress, {
|
|
9654
|
+
toValue: expanded ? 1 : 0,
|
|
9349
9655
|
duration: ANIMATION_DURATION,
|
|
9350
9656
|
easing: ANIMATION_EASING,
|
|
9351
9657
|
useNativeDriver: false
|
|
9352
9658
|
}),
|
|
9353
|
-
Animated.timing(labelOpacity, {
|
|
9354
|
-
toValue: expanded ? 1 : 0,
|
|
9355
|
-
duration: 300,
|
|
9356
|
-
delay: expanded ? 150 : 0,
|
|
9357
|
-
useNativeDriver: false
|
|
9358
|
-
}),
|
|
9359
9659
|
Animated.timing(infoOpacity, {
|
|
9360
9660
|
toValue: expanded ? 1 : 0,
|
|
9361
9661
|
duration: 300,
|
|
@@ -9363,250 +9663,176 @@ function VelocityStrip({
|
|
|
9363
9663
|
useNativeDriver: false
|
|
9364
9664
|
})
|
|
9365
9665
|
]).start();
|
|
9366
|
-
}, [expanded, variant, framed,
|
|
9666
|
+
}, [expanded, variant, framed, expandProgress, infoOpacity]);
|
|
9367
9667
|
if (set == null && velocities == null) return null;
|
|
9368
9668
|
const repCount = doneVelocities.length;
|
|
9369
9669
|
const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
|
|
9370
9670
|
if (variant === "hero") {
|
|
9371
|
-
const heroHeight = height === EXPANDED_HEIGHT ?
|
|
9671
|
+
const heroHeight = height === EXPANDED_HEIGHT && columnSlots == null ? SET_BAR_DEFAULT_HEIGHT : height;
|
|
9672
|
+
const heroSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
9673
|
+
kind: s.kind,
|
|
9674
|
+
value: s.velocity,
|
|
9675
|
+
leadingGap: s.leadingGap
|
|
9676
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
9677
|
+
const total = Math.max(repCount, targetReps ?? repCount);
|
|
9678
|
+
const heroLabel = maxVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(maxVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
|
|
9372
9679
|
return /* @__PURE__ */ jsx(
|
|
9373
|
-
|
|
9680
|
+
SetBarChart,
|
|
9374
9681
|
{
|
|
9375
|
-
|
|
9376
|
-
barColorFor,
|
|
9377
|
-
|
|
9378
|
-
|
|
9682
|
+
slots: heroSlots,
|
|
9683
|
+
colorFor: barColorFor,
|
|
9684
|
+
height: heroHeight,
|
|
9685
|
+
scale,
|
|
9686
|
+
scaleMax,
|
|
9687
|
+
orientation,
|
|
9379
9688
|
liveRepIndex,
|
|
9380
9689
|
isNewPeak,
|
|
9381
|
-
targetReps,
|
|
9382
|
-
|
|
9690
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
9691
|
+
label,
|
|
9692
|
+
showValueLabels: true,
|
|
9693
|
+
formatValue: formatVelocity,
|
|
9694
|
+
renderReference: (g) => velocityReferenceOverlay(g, lossBandsOn),
|
|
9695
|
+
hideBaseline: true,
|
|
9696
|
+
testID: "velocity-strip-hero",
|
|
9697
|
+
testIDPrefix: "velocity",
|
|
9698
|
+
accessibilityLabel: heroLabel,
|
|
9383
9699
|
className,
|
|
9384
9700
|
viewProps: props
|
|
9385
9701
|
}
|
|
9386
9702
|
);
|
|
9387
9703
|
}
|
|
9388
|
-
if (variant === "
|
|
9389
|
-
const
|
|
9704
|
+
if (variant === "compact") {
|
|
9705
|
+
const compactSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
9706
|
+
kind: s.kind,
|
|
9707
|
+
value: s.velocity,
|
|
9708
|
+
leadingGap: s.leadingGap
|
|
9709
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
9710
|
+
const compactHeight = height === EXPANDED_HEIGHT ? COMPACT_HEIGHT : height;
|
|
9390
9711
|
return /* @__PURE__ */ jsx(
|
|
9391
|
-
|
|
9712
|
+
SetBarChart,
|
|
9392
9713
|
{
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9714
|
+
slots: compactSlots,
|
|
9715
|
+
colorFor: barColorFor,
|
|
9716
|
+
height: compactHeight,
|
|
9717
|
+
scale,
|
|
9718
|
+
scaleMax,
|
|
9719
|
+
orientation,
|
|
9720
|
+
flat: true,
|
|
9721
|
+
barRadius: 2,
|
|
9722
|
+
cornerStyle: "all",
|
|
9723
|
+
targetReps: set || columnSlots ? void 0 : targetReps,
|
|
9724
|
+
label,
|
|
9725
|
+
hideBaseline: true,
|
|
9726
|
+
testID: "velocity-strip-compact",
|
|
9727
|
+
testIDPrefix: "velocity",
|
|
9399
9728
|
accessibilityLabel: miniLabel,
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
9403
|
-
View,
|
|
9404
|
-
{
|
|
9405
|
-
style: {
|
|
9406
|
-
flex: 1,
|
|
9407
|
-
backgroundColor: slotColor(slot),
|
|
9408
|
-
borderRadius: 1,
|
|
9409
|
-
minWidth: 4,
|
|
9410
|
-
height: "100%",
|
|
9411
|
-
// The container's uniform 2px gap covers rep spacing; a wide slot adds
|
|
9412
|
-
// only the EXTRA (WIDE_GAP − REP_GAP) so the notch totals WIDE_GAP.
|
|
9413
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
9414
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
9415
|
-
},
|
|
9416
|
-
accessibilityElementsHidden: true,
|
|
9417
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
9418
|
-
},
|
|
9419
|
-
i
|
|
9420
|
-
))
|
|
9729
|
+
className,
|
|
9730
|
+
viewProps: props
|
|
9421
9731
|
}
|
|
9422
9732
|
);
|
|
9423
9733
|
}
|
|
9424
9734
|
if (!framed) {
|
|
9425
|
-
const
|
|
9735
|
+
const spotlightSlots = columnSlots ?? (set ? buildSlots(set).map((s) => ({
|
|
9736
|
+
kind: s.kind,
|
|
9737
|
+
value: s.velocity,
|
|
9738
|
+
leadingGap: s.leadingGap
|
|
9739
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v })));
|
|
9426
9740
|
return /* @__PURE__ */ jsx(
|
|
9427
|
-
|
|
9741
|
+
SetBarChart,
|
|
9428
9742
|
{
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9743
|
+
slots: spotlightSlots,
|
|
9744
|
+
colorFor: barColorFor,
|
|
9745
|
+
height,
|
|
9746
|
+
scale,
|
|
9747
|
+
scaleMax,
|
|
9748
|
+
orientation,
|
|
9749
|
+
liveRepIndex,
|
|
9750
|
+
isNewPeak,
|
|
9751
|
+
barRadius: 2,
|
|
9752
|
+
cornerStyle: "top",
|
|
9753
|
+
targetReps: set ? void 0 : targetReps,
|
|
9754
|
+
label,
|
|
9755
|
+
hideBaseline: true,
|
|
9756
|
+
testID: "velocity-strip-spotlight",
|
|
9757
|
+
testIDPrefix: "velocity",
|
|
9435
9758
|
accessibilityLabel: miniLabel,
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
children: slots.map((slot, i) => /* @__PURE__ */ jsx(
|
|
9439
|
-
View,
|
|
9440
|
-
{
|
|
9441
|
-
style: {
|
|
9442
|
-
flex: 1,
|
|
9443
|
-
minWidth: 4,
|
|
9444
|
-
height: bareSlotHeight(slot, height, scaleDenom),
|
|
9445
|
-
// Top-rounded to match the framed chart's bars (the "rounded tops from
|
|
9446
|
-
// the numbers one"); square bottoms since bars sit on the baseline.
|
|
9447
|
-
borderTopLeftRadius: 2,
|
|
9448
|
-
borderTopRightRadius: 2,
|
|
9449
|
-
backgroundColor: slotColor(slot),
|
|
9450
|
-
marginLeft: Math.max(0, slot.leadingGap - REP_GAP),
|
|
9451
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
9452
|
-
},
|
|
9453
|
-
accessibilityElementsHidden: true,
|
|
9454
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`
|
|
9455
|
-
},
|
|
9456
|
-
i
|
|
9457
|
-
))
|
|
9759
|
+
className,
|
|
9760
|
+
viewProps: props
|
|
9458
9761
|
}
|
|
9459
9762
|
);
|
|
9460
9763
|
}
|
|
9461
9764
|
const stripLabel = set ? `${setAccessibilityLabel(set, repCount)}, tap to ${expanded ? "collapse" : "expand"}` : `Velocity chart for set, ${repCount} reps, tap to ${expanded ? "collapse" : "expand"}`;
|
|
9462
9765
|
const hasInteractiveContainer = onToggle != null;
|
|
9463
9766
|
const hasInteractiveReps = onRepPress != null && expanded;
|
|
9464
|
-
const
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9767
|
+
const framedSlots = set ? buildSlots(set).map((sl) => ({
|
|
9768
|
+
kind: sl.kind,
|
|
9769
|
+
value: sl.velocity,
|
|
9770
|
+
leadingGap: sl.leadingGap
|
|
9771
|
+
})) : doneVelocities.map((v) => ({ kind: "rep", value: v }));
|
|
9772
|
+
const needsBarOverlay = showNumbers || onRepPress != null;
|
|
9773
|
+
const renderFramedBarOverlay = needsBarOverlay ? (repIndex, value) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9774
|
+
showNumbers && /* @__PURE__ */ jsx(
|
|
9775
|
+
Animated.View,
|
|
9469
9776
|
{
|
|
9470
9777
|
style: {
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9778
|
+
opacity: expandProgress,
|
|
9779
|
+
position: "absolute",
|
|
9780
|
+
top: -13,
|
|
9781
|
+
left: 0,
|
|
9782
|
+
right: 0,
|
|
9783
|
+
alignItems: "center"
|
|
9475
9784
|
},
|
|
9785
|
+
accessibilityElementsHidden: true,
|
|
9786
|
+
pointerEvents: "none",
|
|
9476
9787
|
children: /* @__PURE__ */ jsx(
|
|
9477
|
-
|
|
9788
|
+
Text,
|
|
9478
9789
|
{
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
borderTopRightRadius: 2,
|
|
9484
|
-
backgroundColor: slotColor(slot),
|
|
9485
|
-
...slot.kind === "continue" ? { borderWidth: 1, borderColor: CONTINUE_OUTLINE } : {}
|
|
9486
|
-
},
|
|
9487
|
-
testID: slot.kind === "rep" ? `velocity-bar-${i}` : `velocity-slot-${slot.kind}`,
|
|
9488
|
-
accessibilityRole: "image",
|
|
9489
|
-
accessibilityLabel: slotAccessibilityLabel(slot, i)
|
|
9790
|
+
className: "text-text-secondary",
|
|
9791
|
+
style: { fontSize: 8, fontWeight: "600" },
|
|
9792
|
+
testID: `velocity-label-${repIndex}`,
|
|
9793
|
+
children: formatVelocity(value)
|
|
9490
9794
|
}
|
|
9491
9795
|
)
|
|
9492
|
-
}
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9796
|
+
}
|
|
9797
|
+
),
|
|
9798
|
+
onRepPress && expanded && /* @__PURE__ */ jsx(
|
|
9799
|
+
Pressable,
|
|
9800
|
+
{
|
|
9801
|
+
style: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0 },
|
|
9802
|
+
onPress: () => onRepPress(repIndex, value),
|
|
9803
|
+
accessibilityRole: "button",
|
|
9804
|
+
accessibilityLabel: `Rep ${repIndex + 1}: ${formatVelocity(value)} meters per second, tap for details`,
|
|
9805
|
+
testID: `velocity-bar-pressable-${repIndex}`
|
|
9806
|
+
}
|
|
9807
|
+
)
|
|
9808
|
+
] }) : void 0;
|
|
9496
9809
|
const stripContent = /* @__PURE__ */ jsxs(
|
|
9497
9810
|
Animated.View,
|
|
9498
9811
|
{
|
|
9499
|
-
className: [className,
|
|
9500
|
-
style:
|
|
9501
|
-
{
|
|
9502
|
-
height: heightAnim,
|
|
9503
|
-
width: "100%",
|
|
9504
|
-
gap: 2,
|
|
9505
|
-
borderRadius: expanded ? 6 : 2,
|
|
9506
|
-
paddingTop: expanded ? 16 : 0,
|
|
9507
|
-
paddingBottom: expanded ? 24 : 0,
|
|
9508
|
-
// No horizontal inset — the framed chart's bars span full width, matching
|
|
9509
|
-
// the bare spotlight and mini. Numbers/info sit in the vertical padding.
|
|
9510
|
-
paddingHorizontal: 0,
|
|
9511
|
-
paddingVertical: expanded ? void 0 : 8,
|
|
9512
|
-
overflow: expanded ? "visible" : "hidden"
|
|
9513
|
-
}
|
|
9514
|
-
],
|
|
9812
|
+
className: [className, "bg-surface-raised"].filter(Boolean).join(" "),
|
|
9813
|
+
style: { width: "100%", borderRadius: 6, paddingTop: 16, paddingBottom: showInfo ? 8 : 4 },
|
|
9515
9814
|
accessibilityRole: hasInteractiveContainer || hasInteractiveReps ? "none" : "button",
|
|
9516
9815
|
accessibilityLabel: hasInteractiveContainer || hasInteractiveReps ? void 0 : stripLabel,
|
|
9517
9816
|
testID: "velocity-strip",
|
|
9518
9817
|
...props,
|
|
9519
9818
|
children: [
|
|
9520
|
-
/* @__PURE__ */ jsx(
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
borderBottomLeftRadius: 0,
|
|
9536
|
-
borderBottomRightRadius: 0,
|
|
9537
|
-
backgroundColor: barBackground
|
|
9538
|
-
};
|
|
9539
|
-
const barInner = isLive ? /* @__PURE__ */ jsx(
|
|
9540
|
-
Animated.View,
|
|
9541
|
-
{
|
|
9542
|
-
style: [barStyle, { transform: [{ scale: liveScale }] }],
|
|
9543
|
-
testID: `velocity-bar-${i}`,
|
|
9544
|
-
accessibilityRole: "image",
|
|
9545
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
|
|
9546
|
-
}
|
|
9547
|
-
) : /* @__PURE__ */ jsx(
|
|
9548
|
-
View,
|
|
9549
|
-
{
|
|
9550
|
-
style: barStyle,
|
|
9551
|
-
testID: `velocity-bar-${i}`,
|
|
9552
|
-
accessibilityRole: "image",
|
|
9553
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
|
|
9554
|
-
}
|
|
9555
|
-
);
|
|
9556
|
-
const bar = /* @__PURE__ */ jsxs(
|
|
9557
|
-
View,
|
|
9558
|
-
{
|
|
9559
|
-
style: {
|
|
9560
|
-
flex: 1,
|
|
9561
|
-
height: "100%",
|
|
9562
|
-
justifyContent: "flex-end",
|
|
9563
|
-
position: "relative"
|
|
9564
|
-
},
|
|
9565
|
-
children: [
|
|
9566
|
-
expanded && showNumbers && /* @__PURE__ */ jsx(
|
|
9567
|
-
Animated.View,
|
|
9568
|
-
{
|
|
9569
|
-
style: {
|
|
9570
|
-
opacity: labelOpacity,
|
|
9571
|
-
alignItems: "center",
|
|
9572
|
-
position: "absolute",
|
|
9573
|
-
top: -13,
|
|
9574
|
-
left: 0,
|
|
9575
|
-
right: 0
|
|
9576
|
-
},
|
|
9577
|
-
accessibilityElementsHidden: true,
|
|
9578
|
-
children: /* @__PURE__ */ jsx(
|
|
9579
|
-
Text,
|
|
9580
|
-
{
|
|
9581
|
-
className: "text-text-secondary",
|
|
9582
|
-
style: { fontSize: 8, fontWeight: "600" },
|
|
9583
|
-
testID: `velocity-label-${i}`,
|
|
9584
|
-
children: formatVelocity(v)
|
|
9585
|
-
}
|
|
9586
|
-
)
|
|
9587
|
-
}
|
|
9588
|
-
),
|
|
9589
|
-
barInner
|
|
9590
|
-
]
|
|
9591
|
-
},
|
|
9592
|
-
i
|
|
9593
|
-
);
|
|
9594
|
-
if (onRepPress && expanded) {
|
|
9595
|
-
return /* @__PURE__ */ jsx(
|
|
9596
|
-
Pressable,
|
|
9597
|
-
{
|
|
9598
|
-
style: { flex: 1, height: "100%" },
|
|
9599
|
-
onPress: () => onRepPress(i, v),
|
|
9600
|
-
accessibilityRole: "button",
|
|
9601
|
-
accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second, tap for details`,
|
|
9602
|
-
testID: `velocity-bar-pressable-${i}`,
|
|
9603
|
-
children: bar
|
|
9604
|
-
},
|
|
9605
|
-
i
|
|
9606
|
-
);
|
|
9819
|
+
/* @__PURE__ */ jsx(
|
|
9820
|
+
SetBarChart,
|
|
9821
|
+
{
|
|
9822
|
+
slots: framedSlots,
|
|
9823
|
+
colorFor: barColorFor,
|
|
9824
|
+
height,
|
|
9825
|
+
scale,
|
|
9826
|
+
scaleMax,
|
|
9827
|
+
expandProgress,
|
|
9828
|
+
renderBarOverlay: renderFramedBarOverlay,
|
|
9829
|
+
targetReps: set ? void 0 : targetReps,
|
|
9830
|
+
barRadius: 2,
|
|
9831
|
+
cornerStyle: "top",
|
|
9832
|
+
hideBaseline: true,
|
|
9833
|
+
testIDPrefix: "velocity"
|
|
9607
9834
|
}
|
|
9608
|
-
|
|
9609
|
-
}) }),
|
|
9835
|
+
),
|
|
9610
9836
|
expanded && showInfo && /* @__PURE__ */ jsxs(
|
|
9611
9837
|
Animated.View,
|
|
9612
9838
|
{
|
|
@@ -9614,10 +9840,8 @@ function VelocityStrip({
|
|
|
9614
9840
|
flexDirection: "row",
|
|
9615
9841
|
justifyContent: "space-between",
|
|
9616
9842
|
opacity: infoOpacity,
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
left: 6,
|
|
9620
|
-
right: 6
|
|
9843
|
+
marginTop: 6,
|
|
9844
|
+
paddingHorizontal: 6
|
|
9621
9845
|
},
|
|
9622
9846
|
testID: "velocity-info-row",
|
|
9623
9847
|
children: [
|
|
@@ -9625,10 +9849,7 @@ function VelocityStrip({
|
|
|
9625
9849
|
Text,
|
|
9626
9850
|
{
|
|
9627
9851
|
className: "text-text-secondary",
|
|
9628
|
-
style: {
|
|
9629
|
-
fontSize: 10,
|
|
9630
|
-
fontFamily: "Inter, sans-serif"
|
|
9631
|
-
},
|
|
9852
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif" },
|
|
9632
9853
|
children: [
|
|
9633
9854
|
meanZone,
|
|
9634
9855
|
" ",
|
|
@@ -9643,11 +9864,7 @@ function VelocityStrip({
|
|
|
9643
9864
|
Text,
|
|
9644
9865
|
{
|
|
9645
9866
|
className: "text-text-secondary",
|
|
9646
|
-
style: {
|
|
9647
|
-
fontSize: 10,
|
|
9648
|
-
fontFamily: "Inter, sans-serif",
|
|
9649
|
-
...getLossStyle(loss)
|
|
9650
|
-
},
|
|
9867
|
+
style: { fontSize: 10, fontFamily: "Inter, sans-serif", ...getLossStyle(loss) },
|
|
9651
9868
|
children: [
|
|
9652
9869
|
"Loss: ",
|
|
9653
9870
|
loss,
|
|
@@ -9955,6 +10172,7 @@ function RowStrip({ set }) {
|
|
|
9955
10172
|
showInfo: false,
|
|
9956
10173
|
height: 24,
|
|
9957
10174
|
scale: "fixed",
|
|
10175
|
+
liveRepIndex: set.velocities.length - 1,
|
|
9958
10176
|
set: { type: "straight", velocities: set.velocities, planned: set.target.reps },
|
|
9959
10177
|
zones
|
|
9960
10178
|
}
|
|
@@ -9964,7 +10182,9 @@ function RowStrip({ set }) {
|
|
|
9964
10182
|
return /* @__PURE__ */ jsx(
|
|
9965
10183
|
VelocityStrip,
|
|
9966
10184
|
{
|
|
9967
|
-
variant: "
|
|
10185
|
+
variant: "compact",
|
|
10186
|
+
height: 8,
|
|
10187
|
+
hideBaseline: true,
|
|
9968
10188
|
set: { type: "straight", velocities, planned: plannedReps(set) },
|
|
9969
10189
|
zones
|
|
9970
10190
|
}
|
|
@@ -10417,11 +10637,112 @@ function SetsRepsLoad({
|
|
|
10417
10637
|
}
|
|
10418
10638
|
);
|
|
10419
10639
|
}
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10640
|
+
var PULSE_HALF_MS = 950;
|
|
10641
|
+
var PULSE_MIN_OPACITY = 0.45;
|
|
10642
|
+
var SEGMENTED_BAR_GAP = 5;
|
|
10643
|
+
function usePulse2(active) {
|
|
10644
|
+
const [value] = useState(() => new Animated.Value(0));
|
|
10645
|
+
useEffect(() => {
|
|
10646
|
+
if (!active) return;
|
|
10647
|
+
const loop = Animated.loop(
|
|
10648
|
+
Animated.sequence([
|
|
10649
|
+
Animated.timing(value, {
|
|
10650
|
+
toValue: 1,
|
|
10651
|
+
duration: PULSE_HALF_MS,
|
|
10652
|
+
easing: Easing.inOut(Easing.ease),
|
|
10653
|
+
useNativeDriver: false
|
|
10654
|
+
}),
|
|
10655
|
+
Animated.timing(value, {
|
|
10656
|
+
toValue: 0,
|
|
10657
|
+
duration: PULSE_HALF_MS,
|
|
10658
|
+
easing: Easing.inOut(Easing.ease),
|
|
10659
|
+
useNativeDriver: false
|
|
10660
|
+
})
|
|
10661
|
+
])
|
|
10662
|
+
);
|
|
10663
|
+
loop.start();
|
|
10664
|
+
return () => loop.stop();
|
|
10665
|
+
}, [active, value]);
|
|
10666
|
+
return value;
|
|
10667
|
+
}
|
|
10668
|
+
function SegmentedBar({
|
|
10669
|
+
segments,
|
|
10670
|
+
height = 8,
|
|
10671
|
+
gap = SEGMENTED_BAR_GAP,
|
|
10672
|
+
radius = 2,
|
|
10673
|
+
marker = null,
|
|
10674
|
+
segmentTestID,
|
|
10675
|
+
style,
|
|
10676
|
+
...props
|
|
10677
|
+
}) {
|
|
10678
|
+
const pulse = usePulse2(segments.some((s) => s.pulse));
|
|
10679
|
+
return /* @__PURE__ */ jsxs(View, { style: [{ flexDirection: "row", height, gap, position: "relative" }, style], ...props, children: [
|
|
10680
|
+
segments.map((seg, i) => {
|
|
10681
|
+
const fillStyle = {
|
|
10682
|
+
width: `${(seg.fill ?? 1) * 100}%`,
|
|
10683
|
+
height: "100%",
|
|
10684
|
+
backgroundColor: seg.color,
|
|
10685
|
+
opacity: seg.opacity
|
|
10686
|
+
};
|
|
10687
|
+
const testID = segmentTestID?.(seg, i) ?? "segmented-bar-segment";
|
|
10688
|
+
return /* @__PURE__ */ jsx(
|
|
10689
|
+
View,
|
|
10690
|
+
{
|
|
10691
|
+
style: {
|
|
10692
|
+
flex: seg.weight ?? 1,
|
|
10693
|
+
minWidth: 0,
|
|
10694
|
+
height: "100%",
|
|
10695
|
+
marginLeft: seg.leadingGap,
|
|
10696
|
+
borderRadius: radius,
|
|
10697
|
+
overflow: "hidden"
|
|
10698
|
+
},
|
|
10699
|
+
children: seg.pulse ? /* @__PURE__ */ jsx(
|
|
10700
|
+
Animated.View,
|
|
10701
|
+
{
|
|
10702
|
+
style: {
|
|
10703
|
+
...fillStyle,
|
|
10704
|
+
...seg.pulseColor ? {
|
|
10705
|
+
backgroundColor: pulse.interpolate({
|
|
10706
|
+
inputRange: [0, 1],
|
|
10707
|
+
outputRange: seg.pulseColor
|
|
10708
|
+
})
|
|
10709
|
+
} : {
|
|
10710
|
+
opacity: pulse.interpolate({
|
|
10711
|
+
inputRange: [0, 1],
|
|
10712
|
+
outputRange: [PULSE_MIN_OPACITY, 1]
|
|
10713
|
+
})
|
|
10714
|
+
}
|
|
10715
|
+
},
|
|
10716
|
+
accessibilityElementsHidden: true,
|
|
10717
|
+
testID
|
|
10718
|
+
}
|
|
10719
|
+
) : /* @__PURE__ */ jsx(View, { style: fillStyle, accessibilityElementsHidden: true, testID })
|
|
10720
|
+
},
|
|
10721
|
+
i
|
|
10722
|
+
);
|
|
10723
|
+
}),
|
|
10724
|
+
marker ? /* @__PURE__ */ jsx(
|
|
10725
|
+
View,
|
|
10726
|
+
{
|
|
10727
|
+
style: {
|
|
10728
|
+
position: "absolute",
|
|
10729
|
+
left: `${marker.position * 100}%`,
|
|
10730
|
+
top: 0,
|
|
10731
|
+
bottom: 0,
|
|
10732
|
+
width: 2,
|
|
10733
|
+
backgroundColor: marker.color
|
|
10734
|
+
},
|
|
10735
|
+
accessibilityElementsHidden: true,
|
|
10736
|
+
testID: "segmented-bar-marker"
|
|
10737
|
+
}
|
|
10738
|
+
) : null
|
|
10739
|
+
] });
|
|
10740
|
+
}
|
|
10741
|
+
|
|
10742
|
+
// src/components/custom/Workout/paceTone.ts
|
|
10743
|
+
function paceTone(progress, target) {
|
|
10744
|
+
if (target === void 0) return "neutral";
|
|
10745
|
+
return progress >= target ? "ahead" : "behind";
|
|
10425
10746
|
}
|
|
10426
10747
|
var PACE_TONE_COLORS = {
|
|
10427
10748
|
ahead: getSemanticColors("dark")["status-success"],
|
|
@@ -10510,6 +10831,140 @@ function ScheduleTiles({ when, now, gap = 1, ...props }) {
|
|
|
10510
10831
|
)
|
|
10511
10832
|
] });
|
|
10512
10833
|
}
|
|
10834
|
+
|
|
10835
|
+
// src/components/custom/Workout/SetBar.tsx
|
|
10836
|
+
var SET_STRIP_ZONES = {
|
|
10837
|
+
slow: primitiveRamps.red[600],
|
|
10838
|
+
moderate: primitiveRamps.orange[400],
|
|
10839
|
+
fast: primitiveRamps.amber[300],
|
|
10840
|
+
fastest: primitiveRamps.green[300]
|
|
10841
|
+
};
|
|
10842
|
+
var TODO_COLOR = primitiveColors.charcoal[300];
|
|
10843
|
+
var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
|
|
10844
|
+
var NOTCH_GAP = 2;
|
|
10845
|
+
var CLUSTER_GAP = 3;
|
|
10846
|
+
var MYO_UPCOMING_CLUSTERS = 3;
|
|
10847
|
+
var MYO_UPCOMING_CLUSTER_LEN = 5;
|
|
10848
|
+
var MYO_UPCOMING_FADE = 0.6;
|
|
10849
|
+
var PULSE_RANGE = {
|
|
10850
|
+
[SET_STRIP_ZONES.slow]: [primitiveRamps.red[500], primitiveRamps.red[700]],
|
|
10851
|
+
[SET_STRIP_ZONES.moderate]: [primitiveRamps.orange[300], primitiveRamps.orange[500]],
|
|
10852
|
+
[SET_STRIP_ZONES.fast]: [primitiveRamps.amber[200], primitiveRamps.amber[400]],
|
|
10853
|
+
[SET_STRIP_ZONES.fastest]: [primitiveRamps.green[200], primitiveRamps.green[400]]
|
|
10854
|
+
};
|
|
10855
|
+
function velocityZoneColor(v) {
|
|
10856
|
+
if (v < 0.5) return SET_STRIP_ZONES.slow;
|
|
10857
|
+
if (v < 0.75) return SET_STRIP_ZONES.moderate;
|
|
10858
|
+
if (v < 1) return SET_STRIP_ZONES.fast;
|
|
10859
|
+
return SET_STRIP_ZONES.fastest;
|
|
10860
|
+
}
|
|
10861
|
+
function chunkedSegments(chunks, gap) {
|
|
10862
|
+
return chunks.flatMap(
|
|
10863
|
+
(vels, ci) => vels.map((v, ri) => ({
|
|
10864
|
+
color: velocityZoneColor(v),
|
|
10865
|
+
...ci > 0 && ri === 0 ? { leadingGap: gap } : {}
|
|
10866
|
+
}))
|
|
10867
|
+
);
|
|
10868
|
+
}
|
|
10869
|
+
function rangeSegments(floor, max, doneVels) {
|
|
10870
|
+
return Array.from({ length: max }, (_, i) => {
|
|
10871
|
+
if (i < doneVels.length) return { color: velocityZoneColor(doneVels[i]) };
|
|
10872
|
+
return { color: i < floor ? TODO_COLOR : SET_STRIP_VARIABLE_COLOR };
|
|
10873
|
+
});
|
|
10874
|
+
}
|
|
10875
|
+
function myoUpcomingSegments(activationLen, clusterCount) {
|
|
10876
|
+
const activation = Array.from({ length: activationLen }, () => ({ color: TODO_COLOR }));
|
|
10877
|
+
const trail = Array.from(
|
|
10878
|
+
{ length: clusterCount },
|
|
10879
|
+
(_, ci) => Array.from({ length: MYO_UPCOMING_CLUSTER_LEN }, (_2, ri) => ({
|
|
10880
|
+
color: SET_STRIP_VARIABLE_COLOR,
|
|
10881
|
+
opacity: MYO_UPCOMING_FADE ** ci,
|
|
10882
|
+
...ri === 0 ? { leadingGap: CLUSTER_GAP } : {}
|
|
10883
|
+
}))
|
|
10884
|
+
).flat();
|
|
10885
|
+
return [...activation, ...trail];
|
|
10886
|
+
}
|
|
10887
|
+
function setSegments(set) {
|
|
10888
|
+
if (set.status === "todo") {
|
|
10889
|
+
return [{ color: TODO_COLOR }];
|
|
10890
|
+
}
|
|
10891
|
+
if (set.status === "done") {
|
|
10892
|
+
return set.velocities.map((v) => ({ color: velocityZoneColor(v) }));
|
|
10893
|
+
}
|
|
10894
|
+
if (set.status === "range") {
|
|
10895
|
+
return rangeSegments(set.floor, set.max, set.doneVels);
|
|
10896
|
+
}
|
|
10897
|
+
if (set.status === "drop") {
|
|
10898
|
+
return chunkedSegments(set.subloads, NOTCH_GAP);
|
|
10899
|
+
}
|
|
10900
|
+
if (set.status === "myo") {
|
|
10901
|
+
return chunkedSegments([set.activation, ...set.clusters], CLUSTER_GAP);
|
|
10902
|
+
}
|
|
10903
|
+
if (set.status === "myo-upcoming") {
|
|
10904
|
+
return myoUpcomingSegments(set.activationLen, set.clusterCount ?? MYO_UPCOMING_CLUSTERS);
|
|
10905
|
+
}
|
|
10906
|
+
const performed = set.velocities.map((v) => {
|
|
10907
|
+
const color = velocityZoneColor(v);
|
|
10908
|
+
return { color, pulse: true, pulseColor: PULSE_RANGE[color] };
|
|
10909
|
+
});
|
|
10910
|
+
const remaining = Math.max(0, set.planned - set.velocities.length);
|
|
10911
|
+
return [...performed, ...Array.from({ length: remaining }, () => ({ color: TODO_COLOR }))];
|
|
10912
|
+
}
|
|
10913
|
+
function setBarSegmentTestID(seg) {
|
|
10914
|
+
if (seg.pulse) return "set-strip-pulse";
|
|
10915
|
+
if (seg.color === SET_STRIP_VARIABLE_COLOR) return "set-strip-variable";
|
|
10916
|
+
return seg.color === TODO_COLOR ? "set-strip-empty" : "set-strip-fill";
|
|
10917
|
+
}
|
|
10918
|
+
function SetBar({ set, height = 8 }) {
|
|
10919
|
+
return /* @__PURE__ */ jsx(
|
|
10920
|
+
SegmentedBar,
|
|
10921
|
+
{
|
|
10922
|
+
segments: setSegments(set),
|
|
10923
|
+
height,
|
|
10924
|
+
gap: 0,
|
|
10925
|
+
radius: 0,
|
|
10926
|
+
segmentTestID: setBarSegmentTestID,
|
|
10927
|
+
style: { flex: 1, minWidth: 0, borderRadius: 2, overflow: "hidden" },
|
|
10928
|
+
testID: "set-strip-set"
|
|
10929
|
+
}
|
|
10930
|
+
);
|
|
10931
|
+
}
|
|
10932
|
+
var SET_STRIP_GAP = 5;
|
|
10933
|
+
function setCategory(set) {
|
|
10934
|
+
switch (set.status) {
|
|
10935
|
+
case "done":
|
|
10936
|
+
case "drop":
|
|
10937
|
+
case "myo":
|
|
10938
|
+
return "done";
|
|
10939
|
+
case "active":
|
|
10940
|
+
return "active";
|
|
10941
|
+
case "range":
|
|
10942
|
+
return set.doneVels.length > 0 ? "active" : "upcoming";
|
|
10943
|
+
case "todo":
|
|
10944
|
+
case "myo-upcoming":
|
|
10945
|
+
return "upcoming";
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
function describeSets(sets) {
|
|
10949
|
+
const done = sets.filter((s) => setCategory(s) === "done").length;
|
|
10950
|
+
const active = sets.filter((s) => setCategory(s) === "active").length;
|
|
10951
|
+
const upcoming = sets.filter((s) => setCategory(s) === "upcoming").length;
|
|
10952
|
+
return `Set progress: ${done} done, ${active} in progress, ${upcoming} upcoming`;
|
|
10953
|
+
}
|
|
10954
|
+
function SetStrip({ sets, height = 8, className, ...props }) {
|
|
10955
|
+
return /* @__PURE__ */ jsx(
|
|
10956
|
+
View,
|
|
10957
|
+
{
|
|
10958
|
+
className,
|
|
10959
|
+
style: { flexDirection: "row", width: "100%", height, gap: SET_STRIP_GAP },
|
|
10960
|
+
accessibilityRole: "image",
|
|
10961
|
+
accessibilityLabel: describeSets(sets),
|
|
10962
|
+
testID: "set-strip",
|
|
10963
|
+
...props,
|
|
10964
|
+
children: sets.map((set, i) => /* @__PURE__ */ jsx(SetBar, { set, height }, i))
|
|
10965
|
+
}
|
|
10966
|
+
);
|
|
10967
|
+
}
|
|
10513
10968
|
var t9 = getSemanticColors("dark");
|
|
10514
10969
|
var INDICATOR_CONFIG = {
|
|
10515
10970
|
imbalance: { Icon: ScaleIcon, color: t9["status-error"], label: "Left/right imbalance" },
|
|
@@ -10812,7 +11267,9 @@ function CollapsedCard({
|
|
|
10812
11267
|
{
|
|
10813
11268
|
velocities,
|
|
10814
11269
|
zones: velocityZones,
|
|
10815
|
-
variant: "
|
|
11270
|
+
variant: "compact",
|
|
11271
|
+
height: 8,
|
|
11272
|
+
hideBaseline: true,
|
|
10816
11273
|
testID: `exercise-card-velocity-strip-${i}`
|
|
10817
11274
|
},
|
|
10818
11275
|
i
|
|
@@ -11111,6 +11568,7 @@ function SessionRail({
|
|
|
11111
11568
|
next,
|
|
11112
11569
|
stripHeight = 8,
|
|
11113
11570
|
width = DEFAULT_WIDTH,
|
|
11571
|
+
expandedIndex,
|
|
11114
11572
|
onExercisePress,
|
|
11115
11573
|
className,
|
|
11116
11574
|
style,
|
|
@@ -11151,7 +11609,24 @@ function SessionRail({
|
|
|
11151
11609
|
borderBottomWidth: i < exercises.length - 1 ? 1 : 0,
|
|
11152
11610
|
borderBottomColor: DIVIDER
|
|
11153
11611
|
},
|
|
11154
|
-
children: /* @__PURE__ */ jsx(
|
|
11612
|
+
children: i === expandedIndex && ex.sets ? /* @__PURE__ */ jsx(
|
|
11613
|
+
ExerciseCard,
|
|
11614
|
+
{
|
|
11615
|
+
name: ex.name,
|
|
11616
|
+
expanded: true,
|
|
11617
|
+
summary: {
|
|
11618
|
+
sets: ex.summary.sets,
|
|
11619
|
+
reps: ex.summary.reps,
|
|
11620
|
+
// ExerciseCard's summary takes a numeric load; a string load is
|
|
11621
|
+
// an unset/discovery weight, which has no expanded form yet.
|
|
11622
|
+
weight: typeof ex.summary.weight === "number" ? ex.summary.weight : 0,
|
|
11623
|
+
unit: ex.summary.unit
|
|
11624
|
+
},
|
|
11625
|
+
tempo: ex.tempo,
|
|
11626
|
+
sets: ex.sets,
|
|
11627
|
+
onExpandedChange: () => onExercisePress?.(ex, i)
|
|
11628
|
+
}
|
|
11629
|
+
) : /* @__PURE__ */ jsx(
|
|
11155
11630
|
ExerciseCardHeading,
|
|
11156
11631
|
{
|
|
11157
11632
|
name: ex.name,
|
|
@@ -12497,8 +12972,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
|
|
|
12497
12972
|
var PLOT_LEFT = 26;
|
|
12498
12973
|
var TOOLTIP_WIDTH = 124;
|
|
12499
12974
|
function timeOf(dateStr) {
|
|
12500
|
-
const
|
|
12501
|
-
return Number.isNaN(
|
|
12975
|
+
const t26 = Date.parse(dateStr);
|
|
12976
|
+
return Number.isNaN(t26) ? 0 : t26;
|
|
12502
12977
|
}
|
|
12503
12978
|
function formatValue(value) {
|
|
12504
12979
|
return `${Math.round(value)}`;
|
|
@@ -13033,8 +13508,8 @@ function interpEdge(pixels, x, key) {
|
|
|
13033
13508
|
const a = pixels[i];
|
|
13034
13509
|
const b = pixels[i + 1];
|
|
13035
13510
|
if (x >= a.x && x <= b.x) {
|
|
13036
|
-
const
|
|
13037
|
-
return a[key] +
|
|
13511
|
+
const t26 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
|
|
13512
|
+
return a[key] + t26 * (b[key] - a[key]);
|
|
13038
13513
|
}
|
|
13039
13514
|
}
|
|
13040
13515
|
return pixels[pixels.length - 1][key];
|
|
@@ -13640,14 +14115,14 @@ function FatigueMeter({
|
|
|
13640
14115
|
className,
|
|
13641
14116
|
...props
|
|
13642
14117
|
}) {
|
|
13643
|
-
const [t1,
|
|
14118
|
+
const [t1, t26, t32] = thresholds;
|
|
13644
14119
|
const zones = [
|
|
13645
14120
|
{ upTo: t1, color: zoneColors[0] },
|
|
13646
|
-
{ upTo:
|
|
14121
|
+
{ upTo: t26, color: zoneColors[1] },
|
|
13647
14122
|
{ upTo: t32, color: zoneColors[2] },
|
|
13648
14123
|
{ upTo: max, color: zoneColors[3] }
|
|
13649
14124
|
];
|
|
13650
|
-
const tickValues = [0, t1,
|
|
14125
|
+
const tickValues = [0, t1, t26, t32, max];
|
|
13651
14126
|
const ticks = tickValues.map((v, i) => ({ value: v, label: labels[i] }));
|
|
13652
14127
|
return /* @__PURE__ */ jsx(
|
|
13653
14128
|
ZoneTrack,
|
|
@@ -13695,196 +14170,1095 @@ function zoneForSets(sets, { mev, mav, mrv }) {
|
|
|
13695
14170
|
const midpoint = (mav + mrv) / 2;
|
|
13696
14171
|
return sets < midpoint ? "productive" : "approaching";
|
|
13697
14172
|
}
|
|
13698
|
-
function VolumeLandmarkBar({
|
|
13699
|
-
muscle,
|
|
13700
|
-
currentSets,
|
|
13701
|
-
landmarks,
|
|
13702
|
-
width = 220,
|
|
13703
|
-
trackHeight = 10,
|
|
13704
|
-
scaleMax,
|
|
13705
|
-
className,
|
|
13706
|
-
style,
|
|
13707
|
-
...props
|
|
13708
|
-
}) {
|
|
13709
|
-
const { mev, mav, mrv } = landmarks;
|
|
13710
|
-
const max = scaleMax ?? mrv * 1.2;
|
|
13711
|
-
const zone = zoneForSets(currentSets, landmarks);
|
|
13712
|
-
const fillColor = HEAT_BY_ZONE[zone];
|
|
13713
|
-
const pct2 = mav > 0 ? Math.round(currentSets / mav * 100) : 0;
|
|
14173
|
+
function VolumeLandmarkBar({
|
|
14174
|
+
muscle,
|
|
14175
|
+
currentSets,
|
|
14176
|
+
landmarks,
|
|
14177
|
+
width = 220,
|
|
14178
|
+
trackHeight = 10,
|
|
14179
|
+
scaleMax,
|
|
14180
|
+
className,
|
|
14181
|
+
style,
|
|
14182
|
+
...props
|
|
14183
|
+
}) {
|
|
14184
|
+
const { mev, mav, mrv } = landmarks;
|
|
14185
|
+
const max = scaleMax ?? mrv * 1.2;
|
|
14186
|
+
const zone = zoneForSets(currentSets, landmarks);
|
|
14187
|
+
const fillColor = HEAT_BY_ZONE[zone];
|
|
14188
|
+
const pct2 = mav > 0 ? Math.round(currentSets / mav * 100) : 0;
|
|
14189
|
+
return /* @__PURE__ */ jsxs(
|
|
14190
|
+
View,
|
|
14191
|
+
{
|
|
14192
|
+
className,
|
|
14193
|
+
style: [{ width, gap: 3 }, style],
|
|
14194
|
+
testID: "volume-landmark-bar",
|
|
14195
|
+
...props,
|
|
14196
|
+
children: [
|
|
14197
|
+
/* @__PURE__ */ jsx(
|
|
14198
|
+
DataRow,
|
|
14199
|
+
{
|
|
14200
|
+
label: muscle,
|
|
14201
|
+
value: /* @__PURE__ */ jsxs(
|
|
14202
|
+
Text,
|
|
14203
|
+
{
|
|
14204
|
+
testID: "volume-landmark-pct",
|
|
14205
|
+
style: { fontSize: 14, fontWeight: "700", fontFamily: MONO2, color: fillColor },
|
|
14206
|
+
children: [
|
|
14207
|
+
pct2,
|
|
14208
|
+
"%"
|
|
14209
|
+
]
|
|
14210
|
+
}
|
|
14211
|
+
),
|
|
14212
|
+
className: "py-0",
|
|
14213
|
+
testID: "volume-landmark-header"
|
|
14214
|
+
}
|
|
14215
|
+
),
|
|
14216
|
+
/* @__PURE__ */ jsx(
|
|
14217
|
+
ZoneTrack,
|
|
14218
|
+
{
|
|
14219
|
+
zones: [{ upTo: max, color: NEUTRAL_TRACK }],
|
|
14220
|
+
max,
|
|
14221
|
+
marker: {
|
|
14222
|
+
type: "fill",
|
|
14223
|
+
value: currentSets,
|
|
14224
|
+
color: fillColor,
|
|
14225
|
+
// Glow when in the optimal productive band — the "sweet spot" cue.
|
|
14226
|
+
glow: zone === "productive"
|
|
14227
|
+
},
|
|
14228
|
+
trackColor: NEUTRAL_TRACK,
|
|
14229
|
+
trackHeight,
|
|
14230
|
+
ticks: [
|
|
14231
|
+
{ value: mev, label: "MEV", tooltip: `${LANDMARK_NAME.MEV} \xB7 ${mev} sets/wk` },
|
|
14232
|
+
{
|
|
14233
|
+
value: mav,
|
|
14234
|
+
label: "MAV",
|
|
14235
|
+
emphasized: true,
|
|
14236
|
+
tooltip: `${LANDMARK_NAME.MAV} (target) \xB7 ${mav} sets/wk`
|
|
14237
|
+
},
|
|
14238
|
+
{ value: mrv, label: "MRV", tooltip: `${LANDMARK_NAME.MRV} \xB7 ${mrv} sets/wk` }
|
|
14239
|
+
],
|
|
14240
|
+
accessibilityLabel: `${muscle} weekly volume: ${currentSets} sets, ${pct2}% of MAV target, ${ZONE_DESCRIPTION[zone]}`,
|
|
14241
|
+
testID: "volume-landmark-track"
|
|
14242
|
+
}
|
|
14243
|
+
)
|
|
14244
|
+
]
|
|
14245
|
+
}
|
|
14246
|
+
);
|
|
14247
|
+
}
|
|
14248
|
+
var t15 = getSemanticColors("dark");
|
|
14249
|
+
var statusToken = {
|
|
14250
|
+
productive: "status-success",
|
|
14251
|
+
threshold: "status-warning",
|
|
14252
|
+
stop: "status-error"
|
|
14253
|
+
};
|
|
14254
|
+
var statusDotVariant = {
|
|
14255
|
+
productive: "success",
|
|
14256
|
+
threshold: "warning",
|
|
14257
|
+
stop: "error"
|
|
14258
|
+
};
|
|
14259
|
+
var defaultLabel = {
|
|
14260
|
+
productive: "Productive",
|
|
14261
|
+
threshold: "Threshold",
|
|
14262
|
+
stop: "Stop"
|
|
14263
|
+
};
|
|
14264
|
+
function statusPillColor(status) {
|
|
14265
|
+
return t15[statusToken[status]];
|
|
14266
|
+
}
|
|
14267
|
+
function StatusPill2({ status, label, className, style, ...props }) {
|
|
14268
|
+
const color = statusPillColor(status);
|
|
14269
|
+
const text = label ?? defaultLabel[status];
|
|
14270
|
+
return /* @__PURE__ */ jsxs(
|
|
14271
|
+
View,
|
|
14272
|
+
{
|
|
14273
|
+
testID: "status-pill",
|
|
14274
|
+
className,
|
|
14275
|
+
style: [
|
|
14276
|
+
{
|
|
14277
|
+
flexDirection: "row",
|
|
14278
|
+
alignItems: "center",
|
|
14279
|
+
gap: 7,
|
|
14280
|
+
alignSelf: "flex-start",
|
|
14281
|
+
paddingVertical: 7,
|
|
14282
|
+
paddingHorizontal: 13,
|
|
14283
|
+
borderRadius: 9,
|
|
14284
|
+
backgroundColor: alpha(color, 0.14),
|
|
14285
|
+
borderWidth: 1,
|
|
14286
|
+
borderColor: alpha(color, 0.4)
|
|
14287
|
+
},
|
|
14288
|
+
style
|
|
14289
|
+
],
|
|
14290
|
+
...props,
|
|
14291
|
+
children: [
|
|
14292
|
+
/* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(StatusDot, { variant: statusDotVariant[status], glow: true }) }),
|
|
14293
|
+
/* @__PURE__ */ jsx(Text, { testID: "status-pill-label", style: { fontSize: 13, fontWeight: "800", color }, children: text })
|
|
14294
|
+
]
|
|
14295
|
+
}
|
|
14296
|
+
);
|
|
14297
|
+
}
|
|
14298
|
+
var t16 = getSemanticColors("dark");
|
|
14299
|
+
var categoryToken = {
|
|
14300
|
+
productive: null,
|
|
14301
|
+
threshold: "status-warning",
|
|
14302
|
+
stop: "status-error"
|
|
14303
|
+
};
|
|
14304
|
+
var floodOpacity = {
|
|
14305
|
+
productive: 0,
|
|
14306
|
+
threshold: 0.12,
|
|
14307
|
+
stop: 0.18
|
|
14308
|
+
};
|
|
14309
|
+
function liveAuraColor(category) {
|
|
14310
|
+
const token = categoryToken[category];
|
|
14311
|
+
return token ? t16[token] : null;
|
|
14312
|
+
}
|
|
14313
|
+
function LiveAuraFrame({
|
|
14314
|
+
category,
|
|
14315
|
+
pulse = true,
|
|
14316
|
+
className,
|
|
14317
|
+
children,
|
|
14318
|
+
style,
|
|
14319
|
+
...props
|
|
14320
|
+
}) {
|
|
14321
|
+
const color = liveAuraColor(category);
|
|
14322
|
+
const tint = color ? alpha(color, floodOpacity[category]) : "transparent";
|
|
14323
|
+
const floodStyle = {
|
|
14324
|
+
...StyleSheet.absoluteFillObject,
|
|
14325
|
+
...Platform.OS === "web" ? (
|
|
14326
|
+
// backgroundImage is a runtime RNW style, not in RN ViewStyle types.
|
|
14327
|
+
{ backgroundImage: `radial-gradient(130% 95% at 50% 0%, ${tint}, transparent 62%)` }
|
|
14328
|
+
) : { backgroundColor: tint }
|
|
14329
|
+
};
|
|
14330
|
+
return /* @__PURE__ */ jsxs(
|
|
14331
|
+
View,
|
|
14332
|
+
{
|
|
14333
|
+
testID: "live-aura-frame",
|
|
14334
|
+
className,
|
|
14335
|
+
style: [
|
|
14336
|
+
{
|
|
14337
|
+
position: "relative",
|
|
14338
|
+
overflow: "hidden",
|
|
14339
|
+
borderRadius: 10,
|
|
14340
|
+
backgroundColor: t16["background-base"],
|
|
14341
|
+
borderWidth: 1,
|
|
14342
|
+
borderColor: t16["border-default"]
|
|
14343
|
+
},
|
|
14344
|
+
style
|
|
14345
|
+
],
|
|
14346
|
+
...props,
|
|
14347
|
+
children: [
|
|
14348
|
+
color && /* @__PURE__ */ jsx(
|
|
14349
|
+
View,
|
|
14350
|
+
{
|
|
14351
|
+
testID: "live-aura-flood",
|
|
14352
|
+
accessibilityElementsHidden: true,
|
|
14353
|
+
pointerEvents: "none",
|
|
14354
|
+
className: category === "stop" && pulse ? "animate-pulse" : void 0,
|
|
14355
|
+
style: floodStyle
|
|
14356
|
+
}
|
|
14357
|
+
),
|
|
14358
|
+
children
|
|
14359
|
+
]
|
|
14360
|
+
}
|
|
14361
|
+
);
|
|
14362
|
+
}
|
|
14363
|
+
function VelocityHero({
|
|
14364
|
+
velocities,
|
|
14365
|
+
targetReps,
|
|
14366
|
+
liveRepIndex,
|
|
14367
|
+
width,
|
|
14368
|
+
height = 300
|
|
14369
|
+
}) {
|
|
14370
|
+
return /* @__PURE__ */ jsx(View, { testID: "velocity-hero", style: width != null ? { width, height } : { flex: 1, height }, children: /* @__PURE__ */ jsx(
|
|
14371
|
+
VelocityStrip,
|
|
14372
|
+
{
|
|
14373
|
+
variant: "hero",
|
|
14374
|
+
velocities,
|
|
14375
|
+
liveRepIndex: liveRepIndex ?? velocities.length - 1,
|
|
14376
|
+
targetReps,
|
|
14377
|
+
height,
|
|
14378
|
+
scale: "peak"
|
|
14379
|
+
}
|
|
14380
|
+
) });
|
|
14381
|
+
}
|
|
14382
|
+
|
|
14383
|
+
// src/components/custom/Fatigue/fatigue-tokens.ts
|
|
14384
|
+
var t17 = getSemanticColors("dark");
|
|
14385
|
+
var FONT_HEAD = '"Space Grotesk", sans-serif';
|
|
14386
|
+
var FONT_UI = '"Nunito Sans", sans-serif';
|
|
14387
|
+
var FONT_MONO = "monospace";
|
|
14388
|
+
var TONE_COLOR = {
|
|
14389
|
+
ok: t17["status-success"],
|
|
14390
|
+
warn: t17["status-warning"],
|
|
14391
|
+
alarm: t17["status-error"]
|
|
14392
|
+
};
|
|
14393
|
+
var STATE_LABEL = {
|
|
14394
|
+
good: "Good",
|
|
14395
|
+
slowing: "Slowing",
|
|
14396
|
+
grinding: "Grinding",
|
|
14397
|
+
"form-breakdown": "Form breaking down"
|
|
14398
|
+
};
|
|
14399
|
+
var PHASE_AXIS_COLOR = {
|
|
14400
|
+
eccentric: primitiveRamps.magenta[800],
|
|
14401
|
+
concentric: primitiveRamps.cyan[800],
|
|
14402
|
+
hold: primitiveColors.charcoal[100],
|
|
14403
|
+
idle: primitiveColors.charcoal[200]
|
|
14404
|
+
};
|
|
14405
|
+
var PHASE_AXIS_BASE_COLOR = {
|
|
14406
|
+
eccentric: primitiveRamps.magenta[950],
|
|
14407
|
+
concentric: primitiveRamps.cyan[950],
|
|
14408
|
+
hold: primitiveColors.charcoal[300],
|
|
14409
|
+
idle: primitiveColors.charcoal[200]
|
|
14410
|
+
};
|
|
14411
|
+
var PACING_TONE = {
|
|
14412
|
+
ahead: primitiveRamps.amber[300],
|
|
14413
|
+
onPace: primitiveRamps.green[300],
|
|
14414
|
+
over: primitiveRamps.red[300]
|
|
14415
|
+
};
|
|
14416
|
+
var SILVER = primitiveColors.neutral[300];
|
|
14417
|
+
var RED_LIGHT = primitiveRamps.red[400];
|
|
14418
|
+
var RED_MID = primitiveRamps.red[600];
|
|
14419
|
+
var RED_DEEP = primitiveRamps.red[800];
|
|
14420
|
+
var GRIND_THRESHOLD = 0.35;
|
|
14421
|
+
var DRIFT_GREY = primitiveColors.neutral[600];
|
|
14422
|
+
function clamp012(v) {
|
|
14423
|
+
return Math.min(1, Math.max(0, v));
|
|
14424
|
+
}
|
|
14425
|
+
function hexToRgb2(hex) {
|
|
14426
|
+
const h = hex.replace("#", "");
|
|
14427
|
+
const f = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
14428
|
+
return [parseInt(f.slice(0, 2), 16), parseInt(f.slice(2, 4), 16), parseInt(f.slice(4, 6), 16)];
|
|
14429
|
+
}
|
|
14430
|
+
function mixHex2(a, b, ratio) {
|
|
14431
|
+
const [ar, ag, ab] = hexToRgb2(a);
|
|
14432
|
+
const [br, bg, bb] = hexToRgb2(b);
|
|
14433
|
+
const m = (x, y) => Math.round(x + (y - x) * clamp012(ratio));
|
|
14434
|
+
return `#${[m(ar, br), m(ag, bg), m(ab, bb)].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
14435
|
+
}
|
|
14436
|
+
function ghostLineColor(tempoDeviation, grindSignature) {
|
|
14437
|
+
if (grindSignature >= GRIND_THRESHOLD) {
|
|
14438
|
+
const severity = clamp012((grindSignature - GRIND_THRESHOLD) / (1 - GRIND_THRESHOLD));
|
|
14439
|
+
return severity < 0.5 ? mixHex2(RED_LIGHT, RED_MID, severity * 2) : mixHex2(RED_MID, RED_DEEP, (severity - 0.5) * 2);
|
|
14440
|
+
}
|
|
14441
|
+
return mixHex2(SILVER, DRIFT_GREY, (tempoDeviation ?? 0) * 0.55);
|
|
14442
|
+
}
|
|
14443
|
+
function auraForVerdict(state) {
|
|
14444
|
+
if (state == null || state === "good") return "productive";
|
|
14445
|
+
if (state === "form-breakdown") return "stop";
|
|
14446
|
+
return "threshold";
|
|
14447
|
+
}
|
|
14448
|
+
|
|
14449
|
+
// src/components/custom/Fatigue/VerdictHero.tsx
|
|
14450
|
+
var t18 = getSemanticColors("dark");
|
|
14451
|
+
function formatRpe(rpe) {
|
|
14452
|
+
if (rpe == null) return "\u2014";
|
|
14453
|
+
return (Math.round(rpe * 2) / 2).toFixed(1);
|
|
14454
|
+
}
|
|
14455
|
+
function VerdictHero({ rpe, verdict }) {
|
|
14456
|
+
const tone = verdict ? TONE_COLOR[verdict.tone] : t18["text-tertiary"];
|
|
14457
|
+
const word = verdict ? STATE_LABEL[verdict.state] : "Warming up";
|
|
14458
|
+
return /* @__PURE__ */ jsxs(View, { style: { gap: 6 }, testID: "verdict-hero", children: [
|
|
14459
|
+
/* @__PURE__ */ jsx(
|
|
14460
|
+
Text,
|
|
14461
|
+
{
|
|
14462
|
+
style: {
|
|
14463
|
+
fontSize: 9,
|
|
14464
|
+
letterSpacing: 1.5,
|
|
14465
|
+
fontFamily: FONT_MONO,
|
|
14466
|
+
color: t18["text-tertiary"]
|
|
14467
|
+
},
|
|
14468
|
+
children: "FATIGUE"
|
|
14469
|
+
}
|
|
14470
|
+
),
|
|
14471
|
+
/* @__PURE__ */ jsxs(View, { style: { gap: 2 }, children: [
|
|
14472
|
+
/* @__PURE__ */ jsxs(View, { style: { flexDirection: "row", alignItems: "flex-end", gap: 7 }, children: [
|
|
14473
|
+
/* @__PURE__ */ jsx(
|
|
14474
|
+
Text,
|
|
14475
|
+
{
|
|
14476
|
+
style: {
|
|
14477
|
+
fontSize: 62,
|
|
14478
|
+
fontWeight: "900",
|
|
14479
|
+
fontFamily: FONT_HEAD,
|
|
14480
|
+
color: tone,
|
|
14481
|
+
lineHeight: 60
|
|
14482
|
+
},
|
|
14483
|
+
accessibilityLabel: `RPE ${formatRpe(rpe)}`,
|
|
14484
|
+
children: formatRpe(rpe)
|
|
14485
|
+
}
|
|
14486
|
+
),
|
|
14487
|
+
/* @__PURE__ */ jsx(
|
|
14488
|
+
Text,
|
|
14489
|
+
{
|
|
14490
|
+
style: {
|
|
14491
|
+
fontSize: 18,
|
|
14492
|
+
fontWeight: "800",
|
|
14493
|
+
color: tone,
|
|
14494
|
+
marginBottom: 9,
|
|
14495
|
+
fontFamily: FONT_HEAD
|
|
14496
|
+
},
|
|
14497
|
+
children: "RPE"
|
|
14498
|
+
}
|
|
14499
|
+
)
|
|
14500
|
+
] }),
|
|
14501
|
+
/* @__PURE__ */ jsx(Text, { style: { fontSize: 17, fontWeight: "800", fontFamily: FONT_HEAD, color: tone }, children: word })
|
|
14502
|
+
] })
|
|
14503
|
+
] });
|
|
14504
|
+
}
|
|
14505
|
+
var t19 = getSemanticColors("dark");
|
|
14506
|
+
var TONE_VARIANT = {
|
|
14507
|
+
ok: "success",
|
|
14508
|
+
warn: "warning",
|
|
14509
|
+
alarm: "error"
|
|
14510
|
+
};
|
|
14511
|
+
var TONE_WORD = { ok: "ok", warn: "watch", alarm: "alarm" };
|
|
14512
|
+
function Light({
|
|
14513
|
+
label,
|
|
14514
|
+
tone,
|
|
14515
|
+
detail
|
|
14516
|
+
}) {
|
|
14517
|
+
const variant = tone ? TONE_VARIANT[tone] : "neutral";
|
|
14518
|
+
const word = tone ? TONE_WORD[tone] : "warming up";
|
|
14519
|
+
return /* @__PURE__ */ jsx(Tooltip, { label: `${detail} \xB7 ${word}`, placement: "bottom", children: /* @__PURE__ */ jsxs(
|
|
14520
|
+
View,
|
|
14521
|
+
{
|
|
14522
|
+
accessibilityLabel: `${detail}, ${word}`,
|
|
14523
|
+
style: { flexDirection: "row", alignItems: "center", gap: 5 },
|
|
14524
|
+
children: [
|
|
14525
|
+
/* @__PURE__ */ jsx(StatusDot, { variant, size: "sm", glow: true }),
|
|
14526
|
+
/* @__PURE__ */ jsx(
|
|
14527
|
+
Text,
|
|
14528
|
+
{
|
|
14529
|
+
style: {
|
|
14530
|
+
fontSize: 9,
|
|
14531
|
+
letterSpacing: 0.6,
|
|
14532
|
+
fontFamily: FONT_MONO,
|
|
14533
|
+
color: t19["text-secondary"]
|
|
14534
|
+
},
|
|
14535
|
+
children: label
|
|
14536
|
+
}
|
|
14537
|
+
)
|
|
14538
|
+
]
|
|
14539
|
+
}
|
|
14540
|
+
) });
|
|
14541
|
+
}
|
|
14542
|
+
function FatigueLights({ dimensions, spread = false }) {
|
|
14543
|
+
return /* @__PURE__ */ jsxs(
|
|
14544
|
+
View,
|
|
14545
|
+
{
|
|
14546
|
+
testID: "fatigue-lights",
|
|
14547
|
+
style: {
|
|
14548
|
+
flexDirection: "row",
|
|
14549
|
+
gap: spread ? 0 : 16,
|
|
14550
|
+
alignItems: "center",
|
|
14551
|
+
justifyContent: spread ? "space-between" : "flex-start"
|
|
14552
|
+
},
|
|
14553
|
+
children: [
|
|
14554
|
+
/* @__PURE__ */ jsx(Light, { label: "VEL", tone: dimensions?.velocityLoss ?? null, detail: "Velocity loss" }),
|
|
14555
|
+
/* @__PURE__ */ jsx(Light, { label: "ROM", tone: dimensions?.rom ?? null, detail: "ROM depth" }),
|
|
14556
|
+
/* @__PURE__ */ jsx(Light, { label: "TEMPO", tone: dimensions?.tempo ?? null, detail: "Tempo" })
|
|
14557
|
+
]
|
|
14558
|
+
}
|
|
14559
|
+
);
|
|
14560
|
+
}
|
|
14561
|
+
function barColor(romM, working, short) {
|
|
14562
|
+
if (short != null && romM < short) return RED_DEEP;
|
|
14563
|
+
if (working != null && romM < working) return RED_LIGHT;
|
|
14564
|
+
return SILVER;
|
|
14565
|
+
}
|
|
14566
|
+
function romReferenceOverlay(g, workingStandardM, shortThresholdM) {
|
|
14567
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
14568
|
+
shortThresholdM != null && /* @__PURE__ */ jsx(
|
|
14569
|
+
View,
|
|
14570
|
+
{
|
|
14571
|
+
pointerEvents: "none",
|
|
14572
|
+
style: {
|
|
14573
|
+
position: "absolute",
|
|
14574
|
+
left: 0,
|
|
14575
|
+
right: 0,
|
|
14576
|
+
bottom: 0,
|
|
14577
|
+
height: g.yOf(shortThresholdM),
|
|
14578
|
+
backgroundColor: alpha(RED_DEEP, 0.06)
|
|
14579
|
+
}
|
|
14580
|
+
}
|
|
14581
|
+
),
|
|
14582
|
+
workingStandardM != null && /* @__PURE__ */ jsx(
|
|
14583
|
+
View,
|
|
14584
|
+
{
|
|
14585
|
+
pointerEvents: "none",
|
|
14586
|
+
style: {
|
|
14587
|
+
position: "absolute",
|
|
14588
|
+
left: 0,
|
|
14589
|
+
right: 0,
|
|
14590
|
+
bottom: g.yOf(workingStandardM),
|
|
14591
|
+
borderTopWidth: 1,
|
|
14592
|
+
borderStyle: "dashed",
|
|
14593
|
+
borderColor: alpha(SILVER, 0.4)
|
|
14594
|
+
}
|
|
14595
|
+
}
|
|
14596
|
+
),
|
|
14597
|
+
shortThresholdM != null && /* @__PURE__ */ jsx(
|
|
14598
|
+
View,
|
|
14599
|
+
{
|
|
14600
|
+
pointerEvents: "none",
|
|
14601
|
+
style: {
|
|
14602
|
+
position: "absolute",
|
|
14603
|
+
left: 0,
|
|
14604
|
+
right: 0,
|
|
14605
|
+
bottom: g.yOf(shortThresholdM),
|
|
14606
|
+
borderTopWidth: 1,
|
|
14607
|
+
borderStyle: "dashed",
|
|
14608
|
+
borderColor: alpha(RED_DEEP, 0.4)
|
|
14609
|
+
}
|
|
14610
|
+
}
|
|
14611
|
+
)
|
|
14612
|
+
] });
|
|
14613
|
+
}
|
|
14614
|
+
function RomProgressionChart({
|
|
14615
|
+
points,
|
|
14616
|
+
workingStandardM,
|
|
14617
|
+
shortThresholdM,
|
|
14618
|
+
barHeight = 44,
|
|
14619
|
+
maxReps = 12,
|
|
14620
|
+
plannedReps: plannedReps2
|
|
14621
|
+
}) {
|
|
14622
|
+
const shown = points.slice(-maxReps);
|
|
14623
|
+
const slots = shown.map((p) => ({ kind: "rep", value: p.romM }));
|
|
14624
|
+
const scaleMax = Math.max(...shown.map((p) => p.romM), workingStandardM ?? 0, shortThresholdM ?? 0) || void 0;
|
|
14625
|
+
return /* @__PURE__ */ jsx(View, { testID: "rom-progression", children: /* @__PURE__ */ jsx(
|
|
14626
|
+
SetBarChart,
|
|
14627
|
+
{
|
|
14628
|
+
slots,
|
|
14629
|
+
colorFor: (romM) => barColor(romM, workingStandardM, shortThresholdM),
|
|
14630
|
+
height: barHeight,
|
|
14631
|
+
scaleMax,
|
|
14632
|
+
barRadius: 2,
|
|
14633
|
+
hideBaseline: true,
|
|
14634
|
+
targetReps: plannedReps2,
|
|
14635
|
+
renderReference: (g) => romReferenceOverlay(g, workingStandardM, shortThresholdM),
|
|
14636
|
+
testIDPrefix: "rom"
|
|
14637
|
+
}
|
|
14638
|
+
) });
|
|
14639
|
+
}
|
|
14640
|
+
|
|
14641
|
+
// src/components/custom/Fatigue/tempo-pacing.ts
|
|
14642
|
+
var t20 = getSemanticColors("dark");
|
|
14643
|
+
var ON_TARGET_MS2 = 100;
|
|
14644
|
+
function phaseFillFraction(elapsedMs, targetMs) {
|
|
14645
|
+
if (targetMs == null || targetMs <= 0) return 1;
|
|
14646
|
+
return Math.min(1, Math.max(0, elapsedMs / targetMs));
|
|
14647
|
+
}
|
|
14648
|
+
function pacingTone(elapsedMs, targetMs) {
|
|
14649
|
+
if (targetMs == null || targetMs <= 0) return t20["text-primary"];
|
|
14650
|
+
const remainingMs = targetMs - elapsedMs;
|
|
14651
|
+
if (remainingMs > ON_TARGET_MS2) return PACING_TONE.ahead;
|
|
14652
|
+
if (remainingMs >= -ON_TARGET_MS2) return PACING_TONE.onPace;
|
|
14653
|
+
return PACING_TONE.over;
|
|
14654
|
+
}
|
|
14655
|
+
function prescribedSegments(tempo) {
|
|
14656
|
+
if (tempo == null) return [];
|
|
14657
|
+
const [ecc, pauseBottom, con, pauseTop] = tempo;
|
|
14658
|
+
const order = [
|
|
14659
|
+
["eccentric", ecc],
|
|
14660
|
+
["hold", pauseBottom],
|
|
14661
|
+
["concentric", con],
|
|
14662
|
+
["hold", pauseTop]
|
|
14663
|
+
];
|
|
14664
|
+
const out = [];
|
|
14665
|
+
let t26 = 0;
|
|
14666
|
+
for (const [phase, seconds] of order) {
|
|
14667
|
+
const ms = seconds * 1e3;
|
|
14668
|
+
if (ms > 0) out.push({ phase, startMs: t26, endMs: t26 + ms });
|
|
14669
|
+
t26 += ms;
|
|
14670
|
+
}
|
|
14671
|
+
return out;
|
|
14672
|
+
}
|
|
14673
|
+
function phaseTargetsMs(phases, tempo) {
|
|
14674
|
+
if (tempo == null) return phases.map(() => null);
|
|
14675
|
+
const [eccS, pauseBottomS, conS, pauseTopS] = tempo;
|
|
14676
|
+
let seenConcentric = false;
|
|
14677
|
+
return phases.map((phase) => {
|
|
14678
|
+
switch (phase) {
|
|
14679
|
+
case "eccentric":
|
|
14680
|
+
return eccS * 1e3;
|
|
14681
|
+
case "concentric":
|
|
14682
|
+
seenConcentric = true;
|
|
14683
|
+
return conS * 1e3;
|
|
14684
|
+
case "hold":
|
|
14685
|
+
return (seenConcentric ? pauseTopS : pauseBottomS) * 1e3;
|
|
14686
|
+
default:
|
|
14687
|
+
return null;
|
|
14688
|
+
}
|
|
14689
|
+
});
|
|
14690
|
+
}
|
|
14691
|
+
|
|
14692
|
+
// src/components/custom/Fatigue/GhostBand.tsx
|
|
14693
|
+
var t21 = getSemanticColors("dark");
|
|
14694
|
+
var BAND_H = 16;
|
|
14695
|
+
var BAND_GAP = 4;
|
|
14696
|
+
var PHASE_LABEL = {
|
|
14697
|
+
eccentric: "ECC",
|
|
14698
|
+
concentric: "CON"
|
|
14699
|
+
};
|
|
14700
|
+
var LABEL_CH_PX = 7;
|
|
14701
|
+
var LABEL_PAD_PX = 6;
|
|
14702
|
+
var WELL_SHADE = primitiveColors.black;
|
|
14703
|
+
var WELL_FLOOR_LIGHT = primitiveColors.white;
|
|
14704
|
+
function labelFits(label, segW) {
|
|
14705
|
+
return segW >= label.length * LABEL_CH_PX + LABEL_PAD_PX;
|
|
14706
|
+
}
|
|
14707
|
+
function GhostBand({
|
|
14708
|
+
segments,
|
|
14709
|
+
x,
|
|
14710
|
+
top,
|
|
14711
|
+
height = BAND_H,
|
|
14712
|
+
showLabels = false,
|
|
14713
|
+
labelColor = t21["text-primary"],
|
|
14714
|
+
targetTempoSeconds = null,
|
|
14715
|
+
prescribed = false
|
|
14716
|
+
}) {
|
|
14717
|
+
const rawId = useId();
|
|
14718
|
+
const safeId = rawId.replace(/[^a-zA-Z0-9]/g, "");
|
|
14719
|
+
const clipId = `ghost-band-${safeId}`;
|
|
14720
|
+
const wellId = `ghost-band-well-${safeId}`;
|
|
14721
|
+
const drawn = segments.filter((seg) => x(seg.endMs) - x(seg.startMs) > 0);
|
|
14722
|
+
if (drawn.length === 0) return null;
|
|
14723
|
+
const bandLeft = x(drawn[0].startMs);
|
|
14724
|
+
const bandRight = x(drawn[drawn.length - 1].endMs);
|
|
14725
|
+
const bandW = bandRight - bandLeft;
|
|
14726
|
+
if (bandW <= 0) return null;
|
|
14727
|
+
const pacing = targetTempoSeconds != null && !prescribed;
|
|
14728
|
+
const targetsMs = phaseTargetsMs(
|
|
14729
|
+
drawn.map((seg) => seg.phase),
|
|
14730
|
+
targetTempoSeconds
|
|
14731
|
+
);
|
|
14732
|
+
const runs = drawn.map((seg, i) => {
|
|
14733
|
+
const left = x(seg.startMs);
|
|
14734
|
+
const right = i === drawn.length - 1 ? bandRight : x(drawn[i + 1].startMs);
|
|
14735
|
+
const width = Math.max(0, right - left);
|
|
14736
|
+
const targetMs = targetsMs[i];
|
|
14737
|
+
const elapsedMs = seg.endMs - seg.startMs;
|
|
14738
|
+
return {
|
|
14739
|
+
phase: seg.phase,
|
|
14740
|
+
left,
|
|
14741
|
+
width,
|
|
14742
|
+
// Prescribed → nothing performed, so nothing filled. No target at all → the run reads
|
|
14743
|
+
// complete rather than perpetually empty.
|
|
14744
|
+
fillWidth: prescribed ? 0 : pacing ? width * phaseFillFraction(elapsedMs, targetMs) : width,
|
|
14745
|
+
labelTone: pacing ? pacingTone(elapsedMs, targetMs) : labelColor
|
|
14746
|
+
};
|
|
14747
|
+
});
|
|
14748
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
14749
|
+
/* @__PURE__ */ jsxs("defs", { children: [
|
|
14750
|
+
/* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx("rect", { x: bandLeft, y: top, width: bandW, height, rx: 2 }) }),
|
|
14751
|
+
/* @__PURE__ */ jsxs("linearGradient", { id: wellId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
14752
|
+
/* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: WELL_SHADE, stopOpacity: 0.6 }),
|
|
14753
|
+
/* @__PURE__ */ jsx("stop", { offset: "42%", stopColor: WELL_SHADE, stopOpacity: 0.12 }),
|
|
14754
|
+
/* @__PURE__ */ jsx("stop", { offset: "88%", stopColor: WELL_SHADE, stopOpacity: 0.02 }),
|
|
14755
|
+
/* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: WELL_FLOOR_LIGHT, stopOpacity: 0.07 })
|
|
14756
|
+
] })
|
|
14757
|
+
] }),
|
|
14758
|
+
/* @__PURE__ */ jsxs("g", { clipPath: `url(#${clipId})`, children: [
|
|
14759
|
+
/* @__PURE__ */ jsx(
|
|
14760
|
+
"rect",
|
|
14761
|
+
{
|
|
14762
|
+
x: bandLeft,
|
|
14763
|
+
y: top,
|
|
14764
|
+
width: bandW,
|
|
14765
|
+
height,
|
|
14766
|
+
fill: PHASE_AXIS_COLOR.idle,
|
|
14767
|
+
"data-testid": "ghost-band-floor"
|
|
14768
|
+
}
|
|
14769
|
+
),
|
|
14770
|
+
runs.map((run, i) => /* @__PURE__ */ jsxs("g", { children: [
|
|
14771
|
+
/* @__PURE__ */ jsx(
|
|
14772
|
+
"rect",
|
|
14773
|
+
{
|
|
14774
|
+
x: run.left,
|
|
14775
|
+
y: top,
|
|
14776
|
+
width: run.width,
|
|
14777
|
+
height,
|
|
14778
|
+
fill: PHASE_AXIS_BASE_COLOR[run.phase],
|
|
14779
|
+
"data-testid": "ghost-band-base"
|
|
14780
|
+
}
|
|
14781
|
+
),
|
|
14782
|
+
/* @__PURE__ */ jsx(
|
|
14783
|
+
"rect",
|
|
14784
|
+
{
|
|
14785
|
+
x: run.left,
|
|
14786
|
+
y: top,
|
|
14787
|
+
width: run.width,
|
|
14788
|
+
height,
|
|
14789
|
+
fill: `url(#${wellId})`,
|
|
14790
|
+
"data-testid": "ghost-band-well"
|
|
14791
|
+
}
|
|
14792
|
+
),
|
|
14793
|
+
/* @__PURE__ */ jsx(
|
|
14794
|
+
"rect",
|
|
14795
|
+
{
|
|
14796
|
+
x: run.left,
|
|
14797
|
+
y: top,
|
|
14798
|
+
width: run.fillWidth,
|
|
14799
|
+
height,
|
|
14800
|
+
fill: PHASE_AXIS_COLOR[run.phase],
|
|
14801
|
+
"data-testid": "ghost-band-fill"
|
|
14802
|
+
}
|
|
14803
|
+
)
|
|
14804
|
+
] }, i)),
|
|
14805
|
+
showLabels && runs.map((run, i) => {
|
|
14806
|
+
const label = PHASE_LABEL[run.phase];
|
|
14807
|
+
if (!label || !labelFits(label, run.width)) return null;
|
|
14808
|
+
return /* @__PURE__ */ jsx(
|
|
14809
|
+
"text",
|
|
14810
|
+
{
|
|
14811
|
+
x: run.left + run.width / 2,
|
|
14812
|
+
y: top + height / 2,
|
|
14813
|
+
textAnchor: "middle",
|
|
14814
|
+
dominantBaseline: "central",
|
|
14815
|
+
fill: run.labelTone,
|
|
14816
|
+
fontSize: 8,
|
|
14817
|
+
fontWeight: 800,
|
|
14818
|
+
letterSpacing: 1,
|
|
14819
|
+
fontFamily: FONT_UI,
|
|
14820
|
+
children: label
|
|
14821
|
+
},
|
|
14822
|
+
i
|
|
14823
|
+
);
|
|
14824
|
+
})
|
|
14825
|
+
] })
|
|
14826
|
+
] });
|
|
14827
|
+
}
|
|
14828
|
+
var t22 = getSemanticColors("dark");
|
|
14829
|
+
var PARCH = t22["text-primary"];
|
|
14830
|
+
function smoothPath(pts) {
|
|
14831
|
+
if (pts.length < 2) return pts.length ? `M ${pts[0][0]} ${pts[0][1]}` : "";
|
|
14832
|
+
let d = `M ${pts[0][0].toFixed(1)} ${pts[0][1].toFixed(1)}`;
|
|
14833
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
14834
|
+
const p0 = pts[i - 1] ?? pts[i];
|
|
14835
|
+
const p1 = pts[i];
|
|
14836
|
+
const p2 = pts[i + 1];
|
|
14837
|
+
const p3 = pts[i + 2] ?? p2;
|
|
14838
|
+
const c1x = p1[0] + (p2[0] - p0[0]) / 6;
|
|
14839
|
+
const c1y = p1[1] + (p2[1] - p0[1]) / 6;
|
|
14840
|
+
const c2x = p2[0] - (p3[0] - p1[0]) / 6;
|
|
14841
|
+
const c2y = p2[1] - (p3[1] - p1[1]) / 6;
|
|
14842
|
+
d += ` C ${c1x.toFixed(1)} ${c1y.toFixed(1)}, ${c2x.toFixed(1)} ${c2y.toFixed(1)}, ${p2[0].toFixed(1)} ${p2[1].toFixed(1)}`;
|
|
14843
|
+
}
|
|
14844
|
+
return d;
|
|
14845
|
+
}
|
|
14846
|
+
var BLUR_STD_DEV = 2.6;
|
|
14847
|
+
function GhostBloom({
|
|
14848
|
+
current,
|
|
14849
|
+
ghosts = [],
|
|
14850
|
+
tint,
|
|
14851
|
+
baseline,
|
|
14852
|
+
orientation = "up",
|
|
14853
|
+
ghostStroke = (i) => alpha(PARCH, 0.1 + i * 0.015),
|
|
14854
|
+
lineWidth = 3.5
|
|
14855
|
+
}) {
|
|
14856
|
+
const rawId = useId();
|
|
14857
|
+
const blurId = `bloom-blur-${rawId.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
14858
|
+
const toY = (mag) => orientation === "down" ? baseline + mag : baseline - mag;
|
|
14859
|
+
const toAbs = (pts) => pts.map(([px, mag]) => [px, toY(mag)]);
|
|
14860
|
+
const curD = smoothPath(toAbs(current));
|
|
14861
|
+
const ghostDs = ghosts.map((g) => smoothPath(toAbs(g)));
|
|
14862
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
14863
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("filter", { id: blurId, x: "-50%", y: "-50%", width: "200%", height: "200%", children: /* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: BLUR_STD_DEV }) }) }),
|
|
14864
|
+
ghostDs.map((d, i) => /* @__PURE__ */ jsx(
|
|
14865
|
+
"path",
|
|
14866
|
+
{
|
|
14867
|
+
d,
|
|
14868
|
+
fill: "none",
|
|
14869
|
+
stroke: ghostStroke(i),
|
|
14870
|
+
strokeWidth: 1.5,
|
|
14871
|
+
strokeLinejoin: "round"
|
|
14872
|
+
},
|
|
14873
|
+
i
|
|
14874
|
+
)),
|
|
14875
|
+
/* @__PURE__ */ jsx(
|
|
14876
|
+
"path",
|
|
14877
|
+
{
|
|
14878
|
+
d: curD,
|
|
14879
|
+
fill: "none",
|
|
14880
|
+
stroke: alpha(primitiveColors.black, 0.3),
|
|
14881
|
+
strokeWidth: lineWidth + 3,
|
|
14882
|
+
strokeLinejoin: "round",
|
|
14883
|
+
strokeLinecap: "round",
|
|
14884
|
+
filter: `url(#${blurId})`
|
|
14885
|
+
}
|
|
14886
|
+
),
|
|
14887
|
+
/* @__PURE__ */ jsx(
|
|
14888
|
+
"path",
|
|
14889
|
+
{
|
|
14890
|
+
d: curD,
|
|
14891
|
+
fill: "none",
|
|
14892
|
+
stroke: tint,
|
|
14893
|
+
strokeWidth: lineWidth,
|
|
14894
|
+
strokeLinejoin: "round",
|
|
14895
|
+
strokeLinecap: "round"
|
|
14896
|
+
}
|
|
14897
|
+
)
|
|
14898
|
+
] });
|
|
14899
|
+
}
|
|
14900
|
+
|
|
14901
|
+
// src/components/custom/Fatigue/GhostSpark.tsx
|
|
14902
|
+
function GhostSpark({
|
|
14903
|
+
curves,
|
|
14904
|
+
width,
|
|
14905
|
+
height = 172,
|
|
14906
|
+
targetTempoSeconds = null
|
|
14907
|
+
}) {
|
|
14908
|
+
const w = width;
|
|
14909
|
+
const h = height;
|
|
14910
|
+
const padL = 12;
|
|
14911
|
+
const padR = 8;
|
|
14912
|
+
const padTop = 10;
|
|
14913
|
+
const padBot = 6;
|
|
14914
|
+
if (curves.length === 0) {
|
|
14915
|
+
const prescribed = prescribedSegments(targetTempoSeconds);
|
|
14916
|
+
if (prescribed.length === 0) {
|
|
14917
|
+
return /* @__PURE__ */ jsx(View, { testID: "ghost-spark", style: { width: w, height: h } });
|
|
14918
|
+
}
|
|
14919
|
+
const totalMs = prescribed[prescribed.length - 1].endMs;
|
|
14920
|
+
const bandTopEmpty = h - padBot - BAND_H;
|
|
14921
|
+
const xEmpty = (ms) => padL + ms / (totalMs * 1.04) * (w - padL - padR);
|
|
14922
|
+
return /* @__PURE__ */ jsx(View, { testID: "ghost-spark", style: { paddingHorizontal: 4 }, children: /* @__PURE__ */ jsx("svg", { width: w, height: h, children: /* @__PURE__ */ jsx(
|
|
14923
|
+
GhostBand,
|
|
14924
|
+
{
|
|
14925
|
+
segments: prescribed,
|
|
14926
|
+
x: xEmpty,
|
|
14927
|
+
top: bandTopEmpty,
|
|
14928
|
+
showLabels: true,
|
|
14929
|
+
targetTempoSeconds,
|
|
14930
|
+
prescribed: true
|
|
14931
|
+
}
|
|
14932
|
+
) }) });
|
|
14933
|
+
}
|
|
14934
|
+
const cur = curves[curves.length - 1];
|
|
14935
|
+
const allSamples = curves.flatMap((c) => c.samples);
|
|
14936
|
+
const vmax = Math.max(0.01, ...allSamples.map((s) => s.velocityMps)) * 1.06;
|
|
14937
|
+
const axisMaxT = Math.max(1, ...curves.map((c) => c.samples[c.samples.length - 1]?.tMs ?? 0)) * 1.04;
|
|
14938
|
+
const bandBottom = h - padBot;
|
|
14939
|
+
const bandTop = bandBottom - BAND_H;
|
|
14940
|
+
const baseline = bandTop - BAND_GAP;
|
|
14941
|
+
const plotH = Math.max(1, baseline - padTop);
|
|
14942
|
+
const x = (ms) => padL + ms / axisMaxT * (w - padL - padR);
|
|
14943
|
+
const mag = (v) => clamp012(v / vmax) * plotH;
|
|
14944
|
+
const lineTint = ghostLineColor(cur.tempoDeviation, cur.grindSignature);
|
|
14945
|
+
const curPts = cur.samples.map((s) => [x(s.tMs), mag(s.velocityMps)]);
|
|
14946
|
+
const ghostPts = curves.slice(0, -1).map((c) => c.samples.map((s) => [x(s.tMs), mag(s.velocityMps)]));
|
|
14947
|
+
return /* @__PURE__ */ jsx(View, { testID: "ghost-spark", style: { paddingHorizontal: 4 }, children: /* @__PURE__ */ jsxs("svg", { width: w, height: h, children: [
|
|
14948
|
+
/* @__PURE__ */ jsx(
|
|
14949
|
+
GhostBloom,
|
|
14950
|
+
{
|
|
14951
|
+
current: curPts,
|
|
14952
|
+
ghosts: ghostPts,
|
|
14953
|
+
tint: lineTint,
|
|
14954
|
+
baseline,
|
|
14955
|
+
orientation: "up"
|
|
14956
|
+
}
|
|
14957
|
+
),
|
|
14958
|
+
/* @__PURE__ */ jsx(
|
|
14959
|
+
GhostBand,
|
|
14960
|
+
{
|
|
14961
|
+
segments: cur.phaseSegments,
|
|
14962
|
+
x,
|
|
14963
|
+
top: bandTop,
|
|
14964
|
+
showLabels: true,
|
|
14965
|
+
targetTempoSeconds
|
|
14966
|
+
}
|
|
14967
|
+
)
|
|
14968
|
+
] }) });
|
|
14969
|
+
}
|
|
14970
|
+
|
|
14971
|
+
// src/components/custom/Fatigue/LiveFatigueCard.tsx
|
|
14972
|
+
var t23 = getSemanticColors("dark");
|
|
14973
|
+
var PAD = 18;
|
|
14974
|
+
var GHOST_GUTTER = 4;
|
|
14975
|
+
function LiveFatigueCard({ model, width = 318, height }) {
|
|
14976
|
+
const chartW = width - PAD * 2 - GHOST_GUTTER * 2;
|
|
14977
|
+
const chartH = height != null ? Math.round(Math.min(240, Math.max(168, height * 0.4))) : 172;
|
|
13714
14978
|
return /* @__PURE__ */ jsxs(
|
|
13715
|
-
|
|
14979
|
+
Surface,
|
|
13716
14980
|
{
|
|
13717
|
-
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
|
-
|
|
14981
|
+
level: "base",
|
|
14982
|
+
testID: "live-fatigue-card",
|
|
14983
|
+
style: {
|
|
14984
|
+
width,
|
|
14985
|
+
height,
|
|
14986
|
+
borderRadius: 14,
|
|
14987
|
+
padding: PAD,
|
|
14988
|
+
borderWidth: 1,
|
|
14989
|
+
borderColor: t23["hairline-default"],
|
|
14990
|
+
...barPaper(t23["surface-base"])
|
|
14991
|
+
},
|
|
13721
14992
|
children: [
|
|
14993
|
+
/* @__PURE__ */ jsxs(View, { style: { gap: 12 }, children: [
|
|
14994
|
+
/* @__PURE__ */ jsx(VerdictHero, { rpe: model.rpe, verdict: model.verdict }),
|
|
14995
|
+
/* @__PURE__ */ jsx(FatigueLights, { dimensions: model.verdict?.dimensions ?? null })
|
|
14996
|
+
] }),
|
|
14997
|
+
/* @__PURE__ */ jsx(View, { style: { flex: 1, minHeight: 16 } }),
|
|
13722
14998
|
/* @__PURE__ */ jsx(
|
|
13723
|
-
|
|
14999
|
+
RomProgressionChart,
|
|
13724
15000
|
{
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13728
|
-
|
|
13729
|
-
testID: "volume-landmark-pct",
|
|
13730
|
-
style: { fontSize: 14, fontWeight: "700", fontFamily: MONO2, color: fillColor },
|
|
13731
|
-
children: [
|
|
13732
|
-
pct2,
|
|
13733
|
-
"%"
|
|
13734
|
-
]
|
|
13735
|
-
}
|
|
13736
|
-
),
|
|
13737
|
-
className: "py-0",
|
|
13738
|
-
testID: "volume-landmark-header"
|
|
15001
|
+
points: model.romProgression,
|
|
15002
|
+
workingStandardM: model.romWorkingStandardM,
|
|
15003
|
+
shortThresholdM: model.romShortThresholdM,
|
|
15004
|
+
plannedReps: model.plannedReps
|
|
13739
15005
|
}
|
|
13740
15006
|
),
|
|
15007
|
+
/* @__PURE__ */ jsx(View, { style: { flex: 1, minHeight: 16 } }),
|
|
13741
15008
|
/* @__PURE__ */ jsx(
|
|
13742
|
-
|
|
15009
|
+
GhostSpark,
|
|
13743
15010
|
{
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
value: currentSets,
|
|
13749
|
-
color: fillColor,
|
|
13750
|
-
// Glow when in the optimal productive band — the "sweet spot" cue.
|
|
13751
|
-
glow: zone === "productive"
|
|
13752
|
-
},
|
|
13753
|
-
trackColor: NEUTRAL_TRACK,
|
|
13754
|
-
trackHeight,
|
|
13755
|
-
ticks: [
|
|
13756
|
-
{ value: mev, label: "MEV", tooltip: `${LANDMARK_NAME.MEV} \xB7 ${mev} sets/wk` },
|
|
13757
|
-
{
|
|
13758
|
-
value: mav,
|
|
13759
|
-
label: "MAV",
|
|
13760
|
-
emphasized: true,
|
|
13761
|
-
tooltip: `${LANDMARK_NAME.MAV} (target) \xB7 ${mav} sets/wk`
|
|
13762
|
-
},
|
|
13763
|
-
{ value: mrv, label: "MRV", tooltip: `${LANDMARK_NAME.MRV} \xB7 ${mrv} sets/wk` }
|
|
13764
|
-
],
|
|
13765
|
-
accessibilityLabel: `${muscle} weekly volume: ${currentSets} sets, ${pct2}% of MAV target, ${ZONE_DESCRIPTION[zone]}`,
|
|
13766
|
-
testID: "volume-landmark-track"
|
|
15011
|
+
curves: model.velocityCurves,
|
|
15012
|
+
width: chartW,
|
|
15013
|
+
height: chartH,
|
|
15014
|
+
targetTempoSeconds: model.targetTempoSeconds
|
|
13767
15015
|
}
|
|
13768
15016
|
)
|
|
13769
15017
|
]
|
|
13770
15018
|
}
|
|
13771
15019
|
);
|
|
13772
15020
|
}
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
stop: "status-error"
|
|
13778
|
-
};
|
|
13779
|
-
var statusDotVariant = {
|
|
13780
|
-
productive: "success",
|
|
13781
|
-
threshold: "warning",
|
|
13782
|
-
stop: "error"
|
|
13783
|
-
};
|
|
13784
|
-
var defaultLabel = {
|
|
13785
|
-
productive: "Productive",
|
|
13786
|
-
threshold: "Threshold",
|
|
13787
|
-
stop: "Stop"
|
|
13788
|
-
};
|
|
13789
|
-
function statusPillColor(status) {
|
|
13790
|
-
return t15[statusToken[status]];
|
|
13791
|
-
}
|
|
13792
|
-
function StatusPill2({ status, label, className, style, ...props }) {
|
|
13793
|
-
const color = statusPillColor(status);
|
|
13794
|
-
const text = label ?? defaultLabel[status];
|
|
15021
|
+
|
|
15022
|
+
// src/components/custom/Fatigue/LiveFatiguePanel.tsx
|
|
15023
|
+
var t24 = getSemanticColors("dark");
|
|
15024
|
+
function ExerciseHeaderLite({ header }) {
|
|
13795
15025
|
return /* @__PURE__ */ jsxs(
|
|
13796
15026
|
View,
|
|
13797
15027
|
{
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
borderRadius: 9,
|
|
13809
|
-
backgroundColor: alpha(color, 0.14),
|
|
13810
|
-
borderWidth: 1,
|
|
13811
|
-
borderColor: alpha(color, 0.4)
|
|
13812
|
-
},
|
|
13813
|
-
style
|
|
13814
|
-
],
|
|
13815
|
-
...props,
|
|
15028
|
+
style: {
|
|
15029
|
+
flexDirection: "row",
|
|
15030
|
+
alignItems: "flex-end",
|
|
15031
|
+
justifyContent: "space-between",
|
|
15032
|
+
paddingHorizontal: 24,
|
|
15033
|
+
paddingTop: 18,
|
|
15034
|
+
paddingBottom: 14,
|
|
15035
|
+
borderBottomWidth: 1,
|
|
15036
|
+
borderColor: alpha(t24["text-primary"], 0.08)
|
|
15037
|
+
},
|
|
13816
15038
|
children: [
|
|
13817
|
-
/* @__PURE__ */
|
|
13818
|
-
|
|
15039
|
+
/* @__PURE__ */ jsxs(View, { style: { gap: 2 }, children: [
|
|
15040
|
+
/* @__PURE__ */ jsx(
|
|
15041
|
+
Text,
|
|
15042
|
+
{
|
|
15043
|
+
style: {
|
|
15044
|
+
fontSize: 28,
|
|
15045
|
+
fontWeight: "700",
|
|
15046
|
+
fontFamily: FONT_HEAD,
|
|
15047
|
+
color: t24["text-primary"]
|
|
15048
|
+
},
|
|
15049
|
+
children: header.title
|
|
15050
|
+
}
|
|
15051
|
+
),
|
|
15052
|
+
header.subtitle && /* @__PURE__ */ jsx(
|
|
15053
|
+
Text,
|
|
15054
|
+
{
|
|
15055
|
+
style: {
|
|
15056
|
+
fontSize: 13,
|
|
15057
|
+
fontWeight: "600",
|
|
15058
|
+
fontFamily: FONT_UI,
|
|
15059
|
+
color: t24["text-secondary"]
|
|
15060
|
+
},
|
|
15061
|
+
children: header.subtitle
|
|
15062
|
+
}
|
|
15063
|
+
)
|
|
15064
|
+
] }),
|
|
15065
|
+
header.meta && /* @__PURE__ */ jsx(
|
|
15066
|
+
Text,
|
|
15067
|
+
{
|
|
15068
|
+
style: {
|
|
15069
|
+
fontSize: 13,
|
|
15070
|
+
fontWeight: "800",
|
|
15071
|
+
fontFamily: FONT_MONO,
|
|
15072
|
+
color: t24["text-tertiary"]
|
|
15073
|
+
},
|
|
15074
|
+
children: header.meta
|
|
15075
|
+
}
|
|
15076
|
+
)
|
|
13819
15077
|
]
|
|
13820
15078
|
}
|
|
13821
15079
|
);
|
|
13822
15080
|
}
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
productive: 0,
|
|
13831
|
-
threshold: 0.12,
|
|
13832
|
-
stop: 0.18
|
|
13833
|
-
};
|
|
13834
|
-
function liveAuraColor(category) {
|
|
13835
|
-
const token = categoryToken[category];
|
|
13836
|
-
return token ? t16[token] : null;
|
|
13837
|
-
}
|
|
13838
|
-
function LiveAuraFrame({
|
|
13839
|
-
category,
|
|
13840
|
-
pulse = true,
|
|
13841
|
-
className,
|
|
13842
|
-
children,
|
|
13843
|
-
style,
|
|
13844
|
-
...props
|
|
15081
|
+
function LiveFatiguePanel({
|
|
15082
|
+
model,
|
|
15083
|
+
velocity,
|
|
15084
|
+
header,
|
|
15085
|
+
aura,
|
|
15086
|
+
bodyHeight = 508,
|
|
15087
|
+
cardWidth = 318
|
|
13845
15088
|
}) {
|
|
13846
|
-
const
|
|
13847
|
-
const
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
{
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13863
|
-
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
children
|
|
13884
|
-
]
|
|
15089
|
+
const category = aura ?? auraForVerdict(model.verdict?.state ?? null);
|
|
15090
|
+
const heroH = bodyHeight - 26;
|
|
15091
|
+
return /* @__PURE__ */ jsx(
|
|
15092
|
+
LiveAuraFrame,
|
|
15093
|
+
{
|
|
15094
|
+
category,
|
|
15095
|
+
style: { borderRadius: 0, borderWidth: 0 },
|
|
15096
|
+
testID: "live-fatigue-panel",
|
|
15097
|
+
children: /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
|
|
15098
|
+
header && /* @__PURE__ */ jsx(ExerciseHeaderLite, { header }),
|
|
15099
|
+
/* @__PURE__ */ jsxs(View, { style: { padding: 24, flexDirection: "row", gap: 18, alignItems: "stretch" }, children: [
|
|
15100
|
+
/* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: 8 }, children: [
|
|
15101
|
+
/* @__PURE__ */ jsx(
|
|
15102
|
+
Text,
|
|
15103
|
+
{
|
|
15104
|
+
style: {
|
|
15105
|
+
fontSize: 9,
|
|
15106
|
+
letterSpacing: 1.2,
|
|
15107
|
+
fontFamily: FONT_MONO,
|
|
15108
|
+
color: t24["text-tertiary"]
|
|
15109
|
+
},
|
|
15110
|
+
children: "VELOCITY \xB7 this set"
|
|
15111
|
+
}
|
|
15112
|
+
),
|
|
15113
|
+
/* @__PURE__ */ jsx(
|
|
15114
|
+
VelocityHero,
|
|
15115
|
+
{
|
|
15116
|
+
velocities: velocity.velocities,
|
|
15117
|
+
targetReps: velocity.targetReps,
|
|
15118
|
+
liveRepIndex: velocity.liveRepIndex,
|
|
15119
|
+
height: heroH
|
|
15120
|
+
}
|
|
15121
|
+
)
|
|
15122
|
+
] }),
|
|
15123
|
+
/* @__PURE__ */ jsx(LiveFatigueCard, { model, width: cardWidth, height: bodyHeight })
|
|
15124
|
+
] })
|
|
15125
|
+
] })
|
|
13885
15126
|
}
|
|
13886
15127
|
);
|
|
13887
15128
|
}
|
|
15129
|
+
var t25 = getSemanticColors("dark");
|
|
15130
|
+
function phaseAt(segments, ms) {
|
|
15131
|
+
const hit = segments.find((s) => ms >= s.startMs && ms < s.endMs);
|
|
15132
|
+
return hit ? hit.phase : null;
|
|
15133
|
+
}
|
|
15134
|
+
function mergePhaseSegments(a, b) {
|
|
15135
|
+
if (a.length === 0) return b;
|
|
15136
|
+
if (b.length === 0) return a;
|
|
15137
|
+
const bounds = Array.from(new Set([...a, ...b].flatMap((s) => [s.startMs, s.endMs]))).sort(
|
|
15138
|
+
(p, q) => p - q
|
|
15139
|
+
);
|
|
15140
|
+
const out = [];
|
|
15141
|
+
for (let i = 0; i < bounds.length - 1; i++) {
|
|
15142
|
+
const startMs = bounds[i];
|
|
15143
|
+
const endMs = bounds[i + 1];
|
|
15144
|
+
if (endMs <= startMs) continue;
|
|
15145
|
+
const mid = (startMs + endMs) / 2;
|
|
15146
|
+
const pa = phaseAt(a, mid);
|
|
15147
|
+
const pb = phaseAt(b, mid);
|
|
15148
|
+
const phase = pa == null ? pb ?? "idle" : pb == null ? pa : pa === pb ? pa : "idle";
|
|
15149
|
+
const last = out[out.length - 1];
|
|
15150
|
+
if (last && last.phase === phase && last.endMs === startMs) last.endMs = endMs;
|
|
15151
|
+
else out.push({ phase, startMs, endMs });
|
|
15152
|
+
}
|
|
15153
|
+
return out;
|
|
15154
|
+
}
|
|
15155
|
+
function lastTMs(curves) {
|
|
15156
|
+
return Math.max(0, ...curves.map((c) => c.samples[c.samples.length - 1]?.tMs ?? 0));
|
|
15157
|
+
}
|
|
15158
|
+
function DualGhostSpark({
|
|
15159
|
+
left,
|
|
15160
|
+
right,
|
|
15161
|
+
width,
|
|
15162
|
+
height = 232,
|
|
15163
|
+
leftLabel = "LEFT",
|
|
15164
|
+
rightLabel = "RIGHT",
|
|
15165
|
+
showDeviceLabels = true,
|
|
15166
|
+
targetTempoSeconds = null
|
|
15167
|
+
}) {
|
|
15168
|
+
const w = width;
|
|
15169
|
+
const h = height;
|
|
15170
|
+
const padL = 14;
|
|
15171
|
+
const padR = 14;
|
|
15172
|
+
const padTop = 22;
|
|
15173
|
+
const padBot = 22;
|
|
15174
|
+
if (left.length === 0 && right.length === 0) {
|
|
15175
|
+
return /* @__PURE__ */ jsx(View, { testID: "dual-ghost-spark", style: { width: w, height: h } });
|
|
15176
|
+
}
|
|
15177
|
+
const mid = padTop + (h - padTop - padBot) / 2;
|
|
15178
|
+
const bandTop = mid - BAND_H / 2;
|
|
15179
|
+
const baseUp = bandTop - BAND_GAP;
|
|
15180
|
+
const baseDown = mid + BAND_H / 2 + BAND_GAP;
|
|
15181
|
+
const wingH = Math.max(1, Math.min(baseUp - padTop, h - padBot - baseDown));
|
|
15182
|
+
const allSamples = [...left, ...right].flatMap((c) => c.samples);
|
|
15183
|
+
const vmax = Math.max(0.01, ...allSamples.map((s) => s.velocityMps)) * 1.06;
|
|
15184
|
+
const axisMaxT = Math.max(1, lastTMs(left), lastTMs(right)) * 1.04;
|
|
15185
|
+
const x = (ms) => padL + ms / axisMaxT * (w - padL - padR);
|
|
15186
|
+
const mag = (v) => clamp012(v / vmax) * wingH;
|
|
15187
|
+
const toPts = (c) => c.samples.map((s) => [x(s.tMs), mag(s.velocityMps)]);
|
|
15188
|
+
const wing = (curves) => {
|
|
15189
|
+
const cur = curves[curves.length - 1];
|
|
15190
|
+
return {
|
|
15191
|
+
cur,
|
|
15192
|
+
current: cur ? toPts(cur) : [],
|
|
15193
|
+
ghosts: curves.slice(0, -1).map(toPts),
|
|
15194
|
+
tint: cur ? ghostLineColor(cur.tempoDeviation, cur.grindSignature) : t25["text-tertiary"]
|
|
15195
|
+
};
|
|
15196
|
+
};
|
|
15197
|
+
const up = wing(left);
|
|
15198
|
+
const down = wing(right);
|
|
15199
|
+
const segments = mergePhaseSegments(up.cur?.phaseSegments ?? [], down.cur?.phaseSegments ?? []);
|
|
15200
|
+
return /* @__PURE__ */ jsx(View, { testID: "dual-ghost-spark", style: { paddingHorizontal: 4 }, children: /* @__PURE__ */ jsxs("svg", { width: w, height: h, children: [
|
|
15201
|
+
up.current.length > 0 && /* @__PURE__ */ jsx("g", { "data-testid": "dual-ghost-bloom-left", children: /* @__PURE__ */ jsx(
|
|
15202
|
+
GhostBloom,
|
|
15203
|
+
{
|
|
15204
|
+
current: up.current,
|
|
15205
|
+
ghosts: up.ghosts,
|
|
15206
|
+
tint: up.tint,
|
|
15207
|
+
baseline: baseUp,
|
|
15208
|
+
orientation: "up"
|
|
15209
|
+
}
|
|
15210
|
+
) }),
|
|
15211
|
+
down.current.length > 0 && /* @__PURE__ */ jsx("g", { "data-testid": "dual-ghost-bloom-right", children: /* @__PURE__ */ jsx(
|
|
15212
|
+
GhostBloom,
|
|
15213
|
+
{
|
|
15214
|
+
current: down.current,
|
|
15215
|
+
ghosts: down.ghosts,
|
|
15216
|
+
tint: down.tint,
|
|
15217
|
+
baseline: baseDown,
|
|
15218
|
+
orientation: "down"
|
|
15219
|
+
}
|
|
15220
|
+
) }),
|
|
15221
|
+
/* @__PURE__ */ jsx(
|
|
15222
|
+
GhostBand,
|
|
15223
|
+
{
|
|
15224
|
+
segments,
|
|
15225
|
+
x,
|
|
15226
|
+
top: bandTop,
|
|
15227
|
+
showLabels: true,
|
|
15228
|
+
labelColor: alpha(t25["text-primary"], 0.92),
|
|
15229
|
+
targetTempoSeconds
|
|
15230
|
+
}
|
|
15231
|
+
),
|
|
15232
|
+
showDeviceLabels && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15233
|
+
/* @__PURE__ */ jsx(
|
|
15234
|
+
"text",
|
|
15235
|
+
{
|
|
15236
|
+
x: padL,
|
|
15237
|
+
y: padTop - 9,
|
|
15238
|
+
fontSize: 9,
|
|
15239
|
+
fontWeight: 800,
|
|
15240
|
+
letterSpacing: 1,
|
|
15241
|
+
fontFamily: FONT_UI,
|
|
15242
|
+
fill: t25["text-tertiary"],
|
|
15243
|
+
children: leftLabel
|
|
15244
|
+
}
|
|
15245
|
+
),
|
|
15246
|
+
/* @__PURE__ */ jsx(
|
|
15247
|
+
"text",
|
|
15248
|
+
{
|
|
15249
|
+
x: padL,
|
|
15250
|
+
y: h - padBot + 15,
|
|
15251
|
+
fontSize: 9,
|
|
15252
|
+
fontWeight: 800,
|
|
15253
|
+
letterSpacing: 1,
|
|
15254
|
+
fontFamily: FONT_UI,
|
|
15255
|
+
fill: t25["text-tertiary"],
|
|
15256
|
+
children: rightLabel
|
|
15257
|
+
}
|
|
15258
|
+
)
|
|
15259
|
+
] })
|
|
15260
|
+
] }) });
|
|
15261
|
+
}
|
|
13888
15262
|
var PALETTE = DATAVIZ_CATEGORICAL_PALETTE;
|
|
13889
15263
|
function scaleValue(v, scale) {
|
|
13890
15264
|
if (v <= 0) return 0;
|
|
@@ -13973,7 +15347,7 @@ function Treemap({
|
|
|
13973
15347
|
const rects = squarify(weighted, width, height);
|
|
13974
15348
|
return /* @__PURE__ */ jsx(View, { className: cn("relative", className), style: { width, height }, ...props, children: rects.map((r) => {
|
|
13975
15349
|
const selected = r.datum.id === selectedId;
|
|
13976
|
-
const
|
|
15350
|
+
const labelFits2 = r.w > 54 && r.h > 22;
|
|
13977
15351
|
const pressable = r.datum.id !== "__more__";
|
|
13978
15352
|
return /* @__PURE__ */ jsx(
|
|
13979
15353
|
Pressable,
|
|
@@ -13995,7 +15369,7 @@ function Treemap({
|
|
|
13995
15369
|
padding: 4,
|
|
13996
15370
|
overflow: "hidden"
|
|
13997
15371
|
},
|
|
13998
|
-
children:
|
|
15372
|
+
children: labelFits2 && /* @__PURE__ */ jsx(Text, { numberOfLines: 2, className: "text-[10px] font-semibold text-[#0b0b0b]", children: r.datum.label ?? r.datum.id })
|
|
13999
15373
|
},
|
|
14000
15374
|
r.datum.id
|
|
14001
15375
|
);
|
|
@@ -14091,28 +15465,28 @@ function Scatter({
|
|
|
14091
15465
|
testID: "scatter-canvas",
|
|
14092
15466
|
style: { position: "absolute", top: 0, left: 0, width, height, overflow: "hidden" },
|
|
14093
15467
|
children: [
|
|
14094
|
-
ticksOf(yd, TICK_COUNT).map((
|
|
14095
|
-
const y = toY(
|
|
15468
|
+
ticksOf(yd, TICK_COUNT).map((t26, i) => {
|
|
15469
|
+
const y = toY(t26);
|
|
14096
15470
|
return /* @__PURE__ */ jsx(
|
|
14097
15471
|
View,
|
|
14098
15472
|
{
|
|
14099
15473
|
accessibilityElementsHidden: true,
|
|
14100
15474
|
testID: "scatter-gridline-y",
|
|
14101
15475
|
style: { position: "absolute", left: PLOT_LEFT2, width: innerW, top: y, height: 1, backgroundColor: GRID_LINE2 },
|
|
14102
|
-
children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", left: -PLOT_LEFT2, top: -6, width: PLOT_LEFT2 - 6, textAlign: "right" }, children: formatTick(
|
|
15476
|
+
children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", left: -PLOT_LEFT2, top: -6, width: PLOT_LEFT2 - 6, textAlign: "right" }, children: formatTick(t26) })
|
|
14103
15477
|
},
|
|
14104
15478
|
`y-${i}`
|
|
14105
15479
|
);
|
|
14106
15480
|
}),
|
|
14107
|
-
ticksOf(xd, TICK_COUNT).map((
|
|
14108
|
-
const x = toX(
|
|
15481
|
+
ticksOf(xd, TICK_COUNT).map((t26, i) => {
|
|
15482
|
+
const x = toX(t26);
|
|
14109
15483
|
return /* @__PURE__ */ jsx(
|
|
14110
15484
|
View,
|
|
14111
15485
|
{
|
|
14112
15486
|
accessibilityElementsHidden: true,
|
|
14113
15487
|
testID: "scatter-gridline-x",
|
|
14114
15488
|
style: { position: "absolute", top: PLOT_TOP, height: innerH, left: x, width: 1, backgroundColor: GRID_LINE2 },
|
|
14115
|
-
children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", top: innerH + 4, left: -16, width: 32, textAlign: "center" }, children: formatTick(
|
|
15489
|
+
children: /* @__PURE__ */ jsx(Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", top: innerH + 4, left: -16, width: 32, textAlign: "center" }, children: formatTick(t26) })
|
|
14116
15490
|
},
|
|
14117
15491
|
`x-${i}`
|
|
14118
15492
|
);
|
|
@@ -14185,7 +15559,7 @@ var DEFAULT_THRESHOLDS2 = [
|
|
|
14185
15559
|
var ARC_START = 135;
|
|
14186
15560
|
var ARC_SWEEP = 270;
|
|
14187
15561
|
var SEGMENTS = 40;
|
|
14188
|
-
function
|
|
15562
|
+
function clamp013(n) {
|
|
14189
15563
|
if (Number.isNaN(n)) return 0;
|
|
14190
15564
|
return Math.max(0, Math.min(1, n));
|
|
14191
15565
|
}
|
|
@@ -14230,7 +15604,7 @@ function Gauge2({
|
|
|
14230
15604
|
...props
|
|
14231
15605
|
}) {
|
|
14232
15606
|
const span = max - min || 1;
|
|
14233
|
-
const fill =
|
|
15607
|
+
const fill = clamp013((value - min) / span);
|
|
14234
15608
|
const activeColor = (f) => color ?? bandColor2(f, thresholds, min, max);
|
|
14235
15609
|
const ticks = buildTicks(fill, size, activeColor);
|
|
14236
15610
|
const displayColor = color ?? bandColor2(fill, thresholds, min, max);
|
|
@@ -14247,23 +15621,23 @@ function Gauge2({
|
|
|
14247
15621
|
testID: "gauge",
|
|
14248
15622
|
...props,
|
|
14249
15623
|
children: [
|
|
14250
|
-
ticks.map((
|
|
15624
|
+
ticks.map((t26) => /* @__PURE__ */ jsx(
|
|
14251
15625
|
View,
|
|
14252
15626
|
{
|
|
14253
15627
|
accessibilityElementsHidden: true,
|
|
14254
15628
|
testID: "gauge-segment",
|
|
14255
15629
|
style: {
|
|
14256
15630
|
position: "absolute",
|
|
14257
|
-
left:
|
|
14258
|
-
top:
|
|
15631
|
+
left: t26.left - tickThickness / 2,
|
|
15632
|
+
top: t26.top - tickLength / 2,
|
|
14259
15633
|
width: tickThickness,
|
|
14260
15634
|
height: tickLength,
|
|
14261
15635
|
borderRadius: tickThickness / 2,
|
|
14262
|
-
backgroundColor:
|
|
14263
|
-
transform: [{ rotate:
|
|
15636
|
+
backgroundColor: t26.color,
|
|
15637
|
+
transform: [{ rotate: t26.rotate }]
|
|
14264
15638
|
}
|
|
14265
15639
|
},
|
|
14266
|
-
|
|
15640
|
+
t26.key
|
|
14267
15641
|
)),
|
|
14268
15642
|
/* @__PURE__ */ jsxs(View, { style: { position: "absolute", top: 0, left: 0, width: size, height: size }, className: "items-center justify-center", children: [
|
|
14269
15643
|
/* @__PURE__ */ jsxs(View, { className: "flex-row items-baseline gap-0.5", children: [
|
|
@@ -14683,6 +16057,6 @@ function composeValidators(...validators) {
|
|
|
14683
16057
|
};
|
|
14684
16058
|
}
|
|
14685
16059
|
|
|
14686
|
-
export { Accordion, AccordionButton, AccordionItem, AccordionPanel, ActivityIcon, Alert, AlertDescription, AlertTitle, AlertTriangleIcon, Autocomplete, Avatar, AvatarBadge, AvatarGroup, AwardIcon, Badge, BadgeText, BaseBadge, BluetoothIcon, BrandLockup, BreadcrumbItem, Breadcrumbs, Button, ButtonIcon, ButtonSpinner, ButtonText, CATEGORICAL_CVD_SAFE_MAX, CapacityBandChart, Caption, Card, CardContent, CardDescription, CardFooter, CardHeader, CardSkeleton, CardTitle, Checkbox, CheckboxGroup, Chip, CircleSlashIcon, CircularProgress, CircularTimer, Collapse, CollapseButton, CollapseContent, DashboardShell, DataRow, DateTime, DeviationBar, DeviceIndicator, DeviceMenu, DeviceRow, Divider, Drawer, DrawerBody, DrawerFooter, DrawerHeader, DumbbellIcon, EmptyState, ExerciseCard, ExerciseCardHeading, ExerciseHeading, ExerciseIndicator, FatigueMeter, FormActions, FormField, FormRow, FormSection, Gauge2 as Gauge, HStack, Heading, HelpTip, HistoryIcon, INDICATOR_PRECEDENCE, IconBox, Indicator, InfoIcon, Input, InputBar, InputGroup, IntensityBar, Label, LabelWithHelp, LayersIcon, Link, ListItem, ListItemContent, ListItemDivider, ListItemIcon, ListItemTrailing, LiveAuraFrame, METRIC_FONT, Menu, MenuDivider, MenuGroup, MenuItem, MenuList, MenuTrigger, MesoCard, MesoProgressBar, MesoStatusCard, Metric, MetricCell, MetricGroup, MetricTiles, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalTitle, MuscleGroupChip, NavItem, Overline, PRESSED_ELEVATION_LEVEL, Paragraph, PasswordInput, PersonStandingIcon, Pill, PlaceholderStrip, Popover, PopoverCloseButton, PopoverContent, PopoverTrigger, PrBadge, PrHistoryModal, Progress, ProgressSteps, Radio, RadioGroup, ReadinessCheck, RestTimer, SET_STRIP_VARIABLE_COLOR, SET_STRIP_ZONES, SURFACE_LEVEL_TOKEN, ScaleIcon, Scatter, ScheduleTiles, Section, SectionContent, SectionHeader, SegmentedBar, SegmentedProgressBar, Select, SessionHeader, SessionRail, SessionStatePill, SetBar, SetRow, SetStrip, SetTableHeader, SetsRepsLoad, SideNav, Sidebar, SidebarContent, SidebarDivider, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, Skeleton, SkeletonCard, SkeletonCircle, SkeletonListItem, SkeletonText, Sparkline, Spinner, Stack, StarIcon, StatusDot, StatusPill2 as StatusPill, StepContent, StepIndicator, StepLabel, Stepper, Step as StepperStep, StrengthTrendChart, SupersetWrapper, Surface, SurfaceContext, SvgIcon, Switch, Tab, TabList, TabPanel, TabPanels, Table, TableBody, TableCell, TableHeader, TableHeaderCell, TablePagination, TableRow, Tabs, TempoDisplay, Tile, TimerReadout, Toast, ToastProvider, ToolbarButton, ToolbarButtonGroup, Tooltip, TopBar, Treemap, TrendingDownIcon, Typography, VStack, VelocityStrip, VoltrasMark, VolumeLandmarkBar, WeekRow, WeightBadge, WorkoutCard, WorkoutPill, ZoneTrack, alpha, applyThemePreset, audiobookPreset, backgroundFrameDark, bestTextColor, buttonSizes, calculateMeanVelocity, calculateVelocityLoss, categoricalPalette, cn, componentElevationRanges, composeValidators, createFieldId, createFlatShadow, createPressedHoverShadow, createPressedShadow, createRaisedHoverShadow, createRaisedShadow, darkThemeCSSVars, darken, defaultNavItems, defaultPreset, discreteRainbow, divergingScale, elevationSystem, formatDateTime, formatDuration, formatPrescription, formatSignedPct, formatVelocity, getBaseSurfaceColor, getCategoricalColor, getContrastText, getDiscreteRainbowColor, getElevationConfig, getElevationShadow, getElevationSurface, getFieldAriaProps, getFieldValidationProps, getGlowShadow, getHoverColors, getLuminance, getPressedRecessShadow, getResultColor, getSemanticColors, getStatusColor, getThemeCSSVars, getValidatedElevation, getVelocityZoneColor, getVelocityZoneName, gluestackConfig, hasMaxLength, hasMinLength, hexToRgb, hsvToRgb, isCssPropertyManifest, isDark, isEmpty, isValidEmail, lightThemeCSSVars, lighten, linearGradient, liveAuraColor, neumorphicShadows, onSurfaceColors, paceTone, paceToneColor, pressedLevel, primitiveBorderRadius, primitiveBreakpoints, primitiveColors, primitiveRamps, primitiveShadows, primitiveSpacing, primitiveTypography, primitiveZIndex, resolveColor, resolveIndicator, rgbToHex, rgbToHsv, roundRpe, roundTempo, roundWeight, rpeColor, semanticColorsDark, semanticColorsLight, semanticTypography, sequentialEffort, shadowColors, statusPillColor, surfaceBackground, surfaceGradient, surfaceRampDark, useOnSurfaceColor, useSurface, useSurfaceMode, useTable, useTimer, useToast, useToolbarButton, validateCssPropertyManifest, validationRules, velocityZoneColor };
|
|
16060
|
+
export { Accordion, AccordionButton, AccordionItem, AccordionPanel, ActivityIcon, Alert, AlertDescription, AlertTitle, AlertTriangleIcon, Autocomplete, Avatar, AvatarBadge, AvatarGroup, AwardIcon, BAND_GAP, BAND_H, Badge, BadgeText, BaseBadge, BluetoothIcon, BrandLockup, BreadcrumbItem, Breadcrumbs, Button, ButtonIcon, ButtonSpinner, ButtonText, CATEGORICAL_CVD_SAFE_MAX, CapacityBandChart, Caption, Card, CardContent, CardDescription, CardFooter, CardHeader, CardSkeleton, CardTitle, Checkbox, CheckboxGroup, Chip, CircleSlashIcon, CircularProgress, CircularTimer, Collapse, CollapseButton, CollapseContent, DRIFT_GREY, DashboardShell, DataRow, DateTime, DeviationBar, DeviceIndicator, DeviceMenu, DeviceRow, Divider, Drawer, DrawerBody, DrawerFooter, DrawerHeader, DualGhostSpark, DualVelocityStrip, DumbbellIcon, EmptyState, ExerciseCard, ExerciseCardHeading, ExerciseHeading, ExerciseIndicator, FatigueLights, FatigueMeter, FormActions, FormField, FormRow, FormSection, GRIND_THRESHOLD, Gauge2 as Gauge, GhostBand, GhostBloom, GhostSpark, HStack, Heading, HelpTip, HistoryIcon, INDICATOR_PRECEDENCE, IconBox, Indicator, InfoIcon, Input, InputBar, InputGroup, IntensityBar, Label, LabelWithHelp, LayersIcon, Link, ListItem, ListItemContent, ListItemDivider, ListItemIcon, ListItemTrailing, LiveAuraFrame, LiveFatigueCard, LiveFatiguePanel, METRIC_FONT, Menu, MenuDivider, MenuGroup, MenuItem, MenuList, MenuTrigger, MesoCard, MesoProgressBar, MesoStatusCard, Metric, MetricCell, MetricGroup, MetricTiles, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalTitle, MuscleGroupChip, NavItem, Overline, PHASE_AXIS_COLOR, PRESSED_ELEVATION_LEVEL, Paragraph, PasswordInput, PersonStandingIcon, Pill, PlaceholderStrip, Popover, PopoverCloseButton, PopoverContent, PopoverTrigger, PrBadge, PrHistoryModal, Progress, ProgressSteps, RED_DEEP, RED_LIGHT, RED_MID, Radio, RadioGroup, ReadinessCheck, RestTimer, RomProgressionChart, SET_STRIP_VARIABLE_COLOR, SET_STRIP_ZONES, SILVER, STATE_LABEL, SURFACE_LEVEL_TOKEN, ScaleIcon, Scatter, ScheduleTiles, Section, SectionContent, SectionHeader, SegmentedBar, SegmentedProgressBar, Select, SessionHeader, SessionRail, SessionStatePill, SetBar, SetRow, SetStrip, SetTableHeader, SetsRepsLoad, SideNav, Sidebar, SidebarContent, SidebarDivider, SidebarFooter, SidebarHeader, SidebarItem, SidebarSection, Skeleton, SkeletonCard, SkeletonCircle, SkeletonListItem, SkeletonText, Sparkline, Spinner, Stack, StarIcon, StatusDot, StatusPill2 as StatusPill, StepContent, StepIndicator, StepLabel, Stepper, Step as StepperStep, StrengthTrendChart, SupersetWrapper, Surface, SurfaceContext, SvgIcon, Switch, TONE_COLOR, Tab, TabList, TabPanel, TabPanels, Table, TableBody, TableCell, TableHeader, TableHeaderCell, TablePagination, TableRow, Tabs, TempoDisplay, Tile, TimerReadout, Toast, ToastProvider, ToolbarButton, ToolbarButtonGroup, Tooltip, TopBar, Treemap, TrendingDownIcon, Typography, VStack, VelocityHero, VelocityStrip, VerdictHero, VoltrasMark, VolumeLandmarkBar, WeekRow, WeightBadge, WorkoutCard, WorkoutPill, ZoneTrack, alpha, applyThemePreset, audiobookPreset, auraForVerdict, backgroundFrameDark, bestTextColor, buttonSizes, calculateMeanVelocity, calculateVelocityLoss, categoricalPalette, cn, componentElevationRanges, composeValidators, createFieldId, createFlatShadow, createPressedHoverShadow, createPressedShadow, createRaisedHoverShadow, createRaisedShadow, darkThemeCSSVars, darken, defaultNavItems, defaultPreset, discreteRainbow, divergingScale, elevationSystem, formatDateTime, formatDuration, formatPrescription, formatSignedPct, formatVelocity, getBaseSurfaceColor, getCategoricalColor, getContrastText, getDiscreteRainbowColor, getElevationConfig, getElevationShadow, getElevationSurface, getFieldAriaProps, getFieldValidationProps, getGlowShadow, getHoverColors, getLuminance, getPressedRecessShadow, getResultColor, getSemanticColors, getStatusColor, getThemeCSSVars, getValidatedElevation, getVelocityZoneColor, getVelocityZoneName, ghostLineColor, gluestackConfig, hasMaxLength, hasMinLength, hexToRgb, hsvToRgb, isCssPropertyManifest, isDark, isEmpty, isValidEmail, lightThemeCSSVars, lighten, linearGradient, liveAuraColor, mergePhaseSegments, neumorphicShadows, onSurfaceColors, paceTone, paceToneColor, pressedLevel, primitiveBorderRadius, primitiveBreakpoints, primitiveColors, primitiveRamps, primitiveShadows, primitiveSpacing, primitiveTypography, primitiveZIndex, resolveColor, resolveIndicator, rgbToHex, rgbToHsv, roundRpe, roundTempo, roundWeight, rpeColor, semanticColorsDark, semanticColorsLight, semanticTypography, sequentialEffort, shadowColors, smoothPath, statusPillColor, surfaceBackground, surfaceGradient, surfaceRampDark, useOnSurfaceColor, useSurface, useSurfaceMode, useTable, useTimer, useToast, useToolbarButton, validateCssPropertyManifest, validationRules, velocityZoneColor };
|
|
14687
16061
|
//# sourceMappingURL=index.mjs.map
|
|
14688
16062
|
//# sourceMappingURL=index.mjs.map
|