@titan-design/react-ui 0.7.0 → 0.9.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.
Files changed (52) hide show
  1. package/dist/index.d.mts +112 -55
  2. package/dist/index.d.ts +112 -55
  3. package/dist/index.js +795 -501
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +795 -498
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/{pages-8u_4WbL-.d.mts → pages-D7hOD4Vj.d.mts} +17 -8
  8. package/dist/{pages-DaWiBOGa.d.ts → pages-DyEx-lBf.d.ts} +17 -8
  9. package/dist/pages.d.mts +1 -1
  10. package/dist/pages.d.ts +1 -1
  11. package/dist/pages.js +691 -250
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +692 -251
  14. package/dist/pages.mjs.map +1 -1
  15. package/package.json +1 -1
  16. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
  17. package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
  18. package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
  19. package/src/components/custom/CircularTimer/index.ts +1 -0
  20. package/src/components/custom/Workout/ExerciseIndicator.tsx +1 -1
  21. package/src/components/custom/Workout/FatigueMeter.stories.tsx +2 -2
  22. package/src/components/custom/Workout/FatigueMeter.test.tsx +12 -0
  23. package/src/components/custom/Workout/FatigueMeter.tsx +7 -3
  24. package/src/components/custom/Workout/README.md +70 -3
  25. package/src/components/custom/Workout/RestTimer.stories.tsx +132 -2
  26. package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
  27. package/src/components/custom/Workout/RestTimer.tsx +141 -50
  28. package/src/components/custom/Workout/SetsRepsLoad.test.tsx +6 -0
  29. package/src/components/custom/Workout/SetsRepsLoad.tsx +15 -6
  30. package/src/components/custom/Workout/TempoDisplay.stories.tsx +240 -65
  31. package/src/components/custom/Workout/TempoDisplay.test.tsx +61 -16
  32. package/src/components/custom/Workout/TempoDisplay.tsx +173 -60
  33. package/src/components/custom/Workout/VelocityStrip.stories.tsx +152 -9
  34. package/src/components/custom/Workout/VelocityStrip.test.tsx +67 -3
  35. package/src/components/custom/Workout/VelocityStrip.tsx +323 -42
  36. package/src/components/custom/Workout/ZoneTrack.stories.tsx +2 -3
  37. package/src/components/custom/Workout/ZoneTrack.test.tsx +34 -0
  38. package/src/components/custom/Workout/ZoneTrack.tsx +91 -19
  39. package/src/components/custom/Workout/index.ts +1 -9
  40. package/src/components/custom/index.ts +1 -0
  41. package/src/components/ui/alert/Alert.stories.tsx +85 -2
  42. package/src/components/ui/alert/Alert.test.tsx +52 -0
  43. package/src/components/ui/alert/Alert.tsx +54 -20
  44. package/src/components/ui/progress/Progress.tsx +47 -22
  45. package/src/lab/north-star/LivePage.tsx +101 -0
  46. package/src/lab/north-star/LiveView.tsx +409 -0
  47. package/src/lab/north-star/LiveWallDashboard.stories.tsx +129 -0
  48. package/src/lab/north-star/RestView.tsx +140 -0
  49. package/src/lab/north-star/fixtures.ts +224 -0
  50. package/src/components/custom/Workout/TempoBar.stories.tsx +0 -95
  51. package/src/components/custom/Workout/TempoBar.test.tsx +0 -103
  52. package/src/components/custom/Workout/TempoBar.tsx +0 -215
package/dist/pages.js CHANGED
@@ -35,6 +35,8 @@ var primitiveColors = {
35
35
  900: "#111827"},
36
36
  // Charcoal scale (dark backgrounds)
37
37
  charcoal: {
38
+ 0: "#6E6E6E",
39
+ 100: "#4C4C4C",
38
40
  200: "#3C3C3C",
39
41
  300: "#2C2C2C",
40
42
  400: "#1F1F1F",
@@ -76,6 +78,7 @@ var primitiveRamps = {
76
78
  500: "#22A444",
77
79
  600: "#298732"},
78
80
  cyan: {
81
+ 300: "#22D3EE",
79
82
  // pin
80
83
  400: "#01B5D1",
81
84
  500: "#2697B7",
@@ -90,6 +93,7 @@ var primitiveRamps = {
90
93
  // pin
91
94
  600: "#1072CB"},
92
95
  magenta: {
96
+ 400: "#ED69C3",
93
97
  600: "#BA2996"}
94
98
  };
95
99
  var sequentialEffort = [
@@ -606,8 +610,18 @@ var EXPANDED_ENCODED_PCT = 45;
606
610
  var EXPANDED_HEIGHT = 60;
607
611
  var FIXED_MAX_VELOCITY = 1.15;
608
612
  var BARE_STUB_HEIGHT = 3;
613
+ var HERO_HEIGHT = 220;
614
+ var HERO_LABEL_HEADROOM = 20;
615
+ var HERO_BAR_GAP = 8;
616
+ var HERO_BAR_MAX_WIDTH = 120;
617
+ var HERO_LABEL_MIN_BAR_WIDTH = 30;
618
+ var HERO_BAR_RADIUS = 5;
619
+ var HERO_MIN_BAR_HEIGHT = 4;
620
+ var HERO_PENDING_COLOR = primitiveColors.charcoal[100];
621
+ var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
622
+ var PEAK_HEADROOM = 1.06;
609
623
  function scaleDenominator(scale, maxVelocity) {
610
- return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * 1.15;
624
+ return scale === "fixed" ? FIXED_MAX_VELOCITY : maxVelocity * PEAK_HEADROOM;
611
625
  }
612
626
  function bareSlotHeight(slot, height, denom) {
613
627
  if (slot.kind !== "rep" || denom <= 0) return BARE_STUB_HEIGHT;
@@ -633,6 +647,195 @@ function usePrefersReducedMotion() {
633
647
  var ANIMATION_DURATION = 400;
634
648
  var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
635
649
  var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
650
+ function useLiveRepPop(active, liveRepIndex, liveVelocity, isNewPeak) {
651
+ const prefersReducedMotion = usePrefersReducedMotion();
652
+ const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
653
+ react.useEffect(() => {
654
+ if (!active || liveRepIndex == null || liveVelocity == null) return;
655
+ if (prefersReducedMotion) {
656
+ liveScale.setValue(1);
657
+ return;
658
+ }
659
+ if (isNewPeak) {
660
+ liveScale.setValue(1);
661
+ reactNative.Animated.sequence([
662
+ reactNative.Animated.timing(liveScale, {
663
+ toValue: 1.25,
664
+ duration: 150,
665
+ easing: reactNative.Easing.out(reactNative.Easing.quad),
666
+ useNativeDriver: true
667
+ }),
668
+ reactNative.Animated.spring(liveScale, {
669
+ toValue: 1,
670
+ friction: 3,
671
+ tension: 140,
672
+ useNativeDriver: true
673
+ })
674
+ ]).start();
675
+ } else {
676
+ liveScale.setValue(0.8);
677
+ reactNative.Animated.timing(liveScale, {
678
+ toValue: 1,
679
+ duration: 300,
680
+ easing: POP_EASING,
681
+ useNativeDriver: true
682
+ }).start();
683
+ }
684
+ }, [active, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
685
+ return liveScale;
686
+ }
687
+ function HeroVelocityChart({
688
+ doneVelocities,
689
+ barColorFor,
690
+ scaleDenom,
691
+ referenceVelocity,
692
+ liveRepIndex,
693
+ isNewPeak,
694
+ targetReps,
695
+ height,
696
+ className,
697
+ viewProps
698
+ }) {
699
+ const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
700
+ const liveScale = useLiveRepPop(true, liveRepIndex, liveVelocity, isNewPeak);
701
+ const [plotW, setPlotW] = react.useState(0);
702
+ const onPlotLayout = (e) => setPlotW(e.nativeEvent.layout.width);
703
+ const plotHeight = Math.max(0, height - HERO_LABEL_HEADROOM);
704
+ const barHeight = (velocity) => scaleDenom > 0 ? Math.max(HERO_MIN_BAR_HEIGHT, Math.min(1, velocity / scaleDenom) * plotHeight) : HERO_MIN_BAR_HEIGHT;
705
+ const pendingCount = Math.max(0, (targetReps ?? 0) - doneVelocities.length);
706
+ const referencePx = referenceVelocity > 0 && scaleDenom > 0 ? Math.min(plotHeight, referenceVelocity / scaleDenom * plotHeight) : 0;
707
+ const totalSlots = doneVelocities.length + pendingCount;
708
+ const barWidth = plotW > 0 && totalSlots > 0 ? Math.min(HERO_BAR_MAX_WIDTH, (plotW - HERO_BAR_GAP * (totalSlots - 1)) / totalSlots) : HERO_BAR_MAX_WIDTH;
709
+ const labelsCrowded = plotW > 0 && barWidth < HERO_LABEL_MIN_BAR_WIDTH;
710
+ const heroGap = plotW === 0 ? HERO_BAR_GAP : barWidth < 16 ? 2 : barWidth < 28 ? 4 : HERO_BAR_GAP;
711
+ const peakIndex = doneVelocities.reduce(
712
+ (best, v, i) => v > doneVelocities[best] ? i : best,
713
+ 0
714
+ );
715
+ const showBarLabel = (i) => !labelsCrowded || i === peakIndex || i === liveRepIndex;
716
+ const repCount = doneVelocities.length;
717
+ const total = Math.max(repCount, targetReps ?? repCount);
718
+ const label = referenceVelocity > 0 ? `Velocity chart, ${repCount} of ${total} reps, best ${formatVelocity(referenceVelocity)} meters per second` : `Velocity chart, ${repCount} of ${total} reps`;
719
+ const { style: externalStyle, ...restProps } = viewProps;
720
+ return /* @__PURE__ */ jsx(
721
+ reactNative.View,
722
+ {
723
+ className,
724
+ style: [{ height }, externalStyle],
725
+ accessibilityRole: "image",
726
+ accessibilityLabel: label,
727
+ testID: "velocity-strip-hero",
728
+ ...restProps,
729
+ children: /* @__PURE__ */ jsxs(
730
+ reactNative.View,
731
+ {
732
+ onLayout: onPlotLayout,
733
+ style: {
734
+ flex: 1,
735
+ flexDirection: "row",
736
+ alignItems: "flex-end",
737
+ gap: heroGap,
738
+ position: "relative",
739
+ borderBottomWidth: 2,
740
+ borderBottomColor: HERO_PENDING_COLOR
741
+ },
742
+ children: [
743
+ referencePx > 0 && /* @__PURE__ */ jsx(
744
+ reactNative.View,
745
+ {
746
+ accessibilityElementsHidden: true,
747
+ style: {
748
+ position: "absolute",
749
+ left: 0,
750
+ right: 0,
751
+ bottom: referencePx,
752
+ borderTopWidth: 1,
753
+ borderStyle: "dashed",
754
+ borderColor: HERO_REFERENCE_COLOR,
755
+ pointerEvents: "none"
756
+ },
757
+ testID: "velocity-hero-reference"
758
+ }
759
+ ),
760
+ doneVelocities.map((velocity, i) => {
761
+ const isLive = liveRepIndex === i;
762
+ return /* @__PURE__ */ jsxs(
763
+ reactNative.View,
764
+ {
765
+ accessibilityElementsHidden: true,
766
+ style: {
767
+ flex: 1,
768
+ maxWidth: HERO_BAR_MAX_WIDTH,
769
+ height: "100%",
770
+ alignItems: "center",
771
+ justifyContent: "flex-end"
772
+ },
773
+ children: [
774
+ showBarLabel(i) && /* @__PURE__ */ jsx(
775
+ reactNative.Text,
776
+ {
777
+ className: "text-text-primary",
778
+ style: { fontSize: 12, fontWeight: "800", marginBottom: 4 },
779
+ testID: `velocity-label-${i}`,
780
+ children: formatVelocity(velocity)
781
+ }
782
+ ),
783
+ /* @__PURE__ */ jsx(
784
+ reactNative.Animated.View,
785
+ {
786
+ style: [
787
+ {
788
+ width: "100%",
789
+ height: barHeight(velocity),
790
+ borderTopLeftRadius: HERO_BAR_RADIUS,
791
+ borderTopRightRadius: HERO_BAR_RADIUS,
792
+ backgroundColor: barColorFor(velocity)
793
+ },
794
+ isLive ? { transform: [{ scale: liveScale }] } : null
795
+ ],
796
+ testID: `velocity-bar-${i}`
797
+ }
798
+ )
799
+ ]
800
+ },
801
+ i
802
+ );
803
+ }),
804
+ Array.from({ length: pendingCount }, (_, i) => /* @__PURE__ */ jsx(
805
+ reactNative.View,
806
+ {
807
+ accessibilityElementsHidden: true,
808
+ style: {
809
+ flex: 1,
810
+ maxWidth: HERO_BAR_MAX_WIDTH,
811
+ height: "100%",
812
+ alignItems: "center",
813
+ justifyContent: "flex-end"
814
+ },
815
+ children: /* @__PURE__ */ jsx(
816
+ reactNative.View,
817
+ {
818
+ style: {
819
+ width: "100%",
820
+ height: HERO_MIN_BAR_HEIGHT * 3,
821
+ borderWidth: 1,
822
+ borderStyle: "dashed",
823
+ borderColor: HERO_PENDING_COLOR,
824
+ borderTopLeftRadius: HERO_BAR_RADIUS,
825
+ borderTopRightRadius: HERO_BAR_RADIUS
826
+ },
827
+ testID: "velocity-slot-todo"
828
+ }
829
+ )
830
+ },
831
+ `pending-${i}`
832
+ ))
833
+ ]
834
+ }
835
+ )
836
+ }
837
+ );
838
+ }
636
839
  function VelocityStrip({
637
840
  velocities,
638
841
  set,
@@ -643,6 +846,7 @@ function VelocityStrip({
643
846
  onRepPress,
644
847
  variant = "expanded",
645
848
  height = EXPANDED_HEIGHT,
849
+ targetReps,
646
850
  scale = "peak",
647
851
  showNumbers = true,
648
852
  showInfo = true,
@@ -668,45 +872,17 @@ function VelocityStrip({
668
872
  return SET_STRIP_VARIABLE_COLOR;
669
873
  };
670
874
  const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
671
- const prefersReducedMotion = usePrefersReducedMotion();
672
875
  const [heightAnim] = react.useState(() => new reactNative.Animated.Value(expanded ? height : 3));
673
876
  const [labelOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
674
877
  const [infoOpacity] = react.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
675
- const [liveScale] = react.useState(() => new reactNative.Animated.Value(1));
676
878
  const liveVelocity = liveRepIndex != null ? doneVelocities[liveRepIndex] : void 0;
677
879
  const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
678
- react.useEffect(() => {
679
- if (variant !== "expanded" || !framed || liveRepIndex == null || liveVelocity == null) return;
680
- if (prefersReducedMotion) {
681
- liveScale.setValue(1);
682
- return;
683
- }
684
- if (isNewPeak) {
685
- liveScale.setValue(1);
686
- reactNative.Animated.sequence([
687
- reactNative.Animated.timing(liveScale, {
688
- toValue: 1.25,
689
- duration: 150,
690
- easing: reactNative.Easing.out(reactNative.Easing.quad),
691
- useNativeDriver: true
692
- }),
693
- reactNative.Animated.spring(liveScale, {
694
- toValue: 1,
695
- friction: 3,
696
- tension: 140,
697
- useNativeDriver: true
698
- })
699
- ]).start();
700
- } else {
701
- liveScale.setValue(0.8);
702
- reactNative.Animated.timing(liveScale, {
703
- toValue: 1,
704
- duration: 300,
705
- easing: POP_EASING,
706
- useNativeDriver: true
707
- }).start();
708
- }
709
- }, [variant, framed, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
880
+ const liveScale = useLiveRepPop(
881
+ variant === "expanded" && framed,
882
+ liveRepIndex,
883
+ liveVelocity,
884
+ isNewPeak
885
+ );
710
886
  react.useEffect(() => {
711
887
  if (variant !== "expanded" || !framed) return;
712
888
  reactNative.Animated.parallel([
@@ -733,6 +909,24 @@ function VelocityStrip({
733
909
  if (set == null && velocities == null) return null;
734
910
  const repCount = doneVelocities.length;
735
911
  const miniLabel = set ? setAccessibilityLabel(set, repCount) : `Velocity strip, ${repCount} reps`;
912
+ if (variant === "hero") {
913
+ const heroHeight = height === EXPANDED_HEIGHT ? HERO_HEIGHT : height;
914
+ return /* @__PURE__ */ jsx(
915
+ HeroVelocityChart,
916
+ {
917
+ doneVelocities,
918
+ barColorFor,
919
+ scaleDenom,
920
+ referenceVelocity: maxVelocity,
921
+ liveRepIndex,
922
+ isNewPeak,
923
+ targetReps,
924
+ height: heroHeight,
925
+ className,
926
+ viewProps: props
927
+ }
928
+ );
929
+ }
736
930
  if (variant === "mini") {
737
931
  const { style: externalStyle, ...restProps } = props;
738
932
  return /* @__PURE__ */ jsx(
@@ -1776,6 +1970,7 @@ function SetsRepsLoad({
1776
1970
  reps,
1777
1971
  load,
1778
1972
  unit = "lb",
1973
+ fontSize,
1779
1974
  className,
1780
1975
  ...props
1781
1976
  }) {
@@ -1790,12 +1985,12 @@ function SetsRepsLoad({
1790
1985
  testID: "sets-reps-load",
1791
1986
  ...props,
1792
1987
  children: [
1793
- /* @__PURE__ */ jsx(MetricCell, { color: value, children: sets }),
1794
- /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${TIMES} ` }),
1795
- /* @__PURE__ */ jsx(MetricCell, { color: value, children: reps }),
1796
- /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${AT} ` }),
1797
- /* @__PURE__ */ jsx(MetricCell, { color: value, children: load }),
1798
- /* @__PURE__ */ jsx(MetricCell, { color: sep, children: ` ${unit}` })
1988
+ /* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: sets }),
1989
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${TIMES} ` }),
1990
+ /* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: reps }),
1991
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${AT} ` }),
1992
+ /* @__PURE__ */ jsx(MetricCell, { color: value, fontSize, children: load }),
1993
+ /* @__PURE__ */ jsx(MetricCell, { color: sep, fontSize, children: ` ${unit}` })
1799
1994
  ]
1800
1995
  }
1801
1996
  );
@@ -2109,36 +2304,34 @@ function alpha(color, opacity) {
2109
2304
  }
2110
2305
  return color;
2111
2306
  }
2307
+
2308
+ // src/components/custom/Workout/TempoDisplay.tsx
2112
2309
  var t2 = getSemanticColors("dark");
2113
- var TEMPO_PACING = {
2114
- behindThresholdPct: 0.15,
2115
- minPhaseDurationMs: 500
2116
- };
2117
- function getTempoPacingState(elapsedMs, targetMs) {
2118
- if (!targetMs || targetMs < TEMPO_PACING.minPhaseDurationMs) return "none";
2119
- const ratio = elapsedMs / targetMs;
2120
- if (ratio > 1 + TEMPO_PACING.behindThresholdPct) return "behind";
2121
- return "on-pace";
2122
- }
2310
+ var INTER = "Inter, sans-serif";
2311
+ var TEXT_TERTIARY = "#6B7280";
2312
+ var STATUS_ERROR = t2["status-error"];
2313
+ var STATUS_SUCCESS = t2["status-success"];
2314
+ var STATUS_WARNING = t2["status-warning"];
2315
+ var ON_TARGET_MS = 100;
2123
2316
  function getTempoFillPct(elapsedMs, targetMs) {
2124
2317
  if (!targetMs) return 100;
2125
2318
  return Math.min(100, elapsedMs / targetMs * 100);
2126
2319
  }
2127
- ({
2128
- concentric: { color: t2["status-success"]},
2129
- hold: { color: t2["brand-primary"]},
2130
- eccentric: { color: t2["status-warning"]}
2131
- });
2132
-
2133
- // src/components/custom/Workout/TempoDisplay.tsx
2134
- var t3 = getSemanticColors("dark");
2135
- var INTER = "Inter, sans-serif";
2136
- var TEXT_TERTIARY = "#6B7280";
2137
- var STATUS_ERROR = t3["status-error"];
2320
+ function activeNumberTone(elapsedMs, targetMs) {
2321
+ if (targetMs == null) return t2["text-primary"];
2322
+ const remainingMs = targetMs - elapsedMs;
2323
+ if (remainingMs > ON_TARGET_MS) return STATUS_WARNING;
2324
+ if (remainingMs >= -ON_TARGET_MS) return STATUS_SUCCESS;
2325
+ return STATUS_ERROR;
2326
+ }
2327
+ function liveReadoutText(elapsedMs, targetMs, readout) {
2328
+ const seconds = readout === "countup" ? elapsedMs / 1e3 : (targetMs - elapsedMs) / 1e3;
2329
+ return seconds.toFixed(1);
2330
+ }
2138
2331
  var phaseColors = {
2139
- eccentric: t3["status-warning"],
2332
+ eccentric: primitiveRamps.magenta[400],
2140
2333
  pauseBottom: TEXT_TERTIARY,
2141
- concentric: t3["status-success"],
2334
+ concentric: primitiveRamps.cyan[300],
2142
2335
  pauseTop: TEXT_TERTIARY,
2143
2336
  dash: TEXT_TERTIARY
2144
2337
  };
@@ -2158,63 +2351,98 @@ var LIVE_PHASES = [
2158
2351
  { key: "concentric", color: phaseColors.concentric },
2159
2352
  { key: "pauseTop", color: phaseColors.pauseTop }
2160
2353
  ];
2354
+ function CellFill({ color, pct }) {
2355
+ return /* @__PURE__ */ jsx(
2356
+ reactNative.View,
2357
+ {
2358
+ style: {
2359
+ position: "absolute",
2360
+ left: 2,
2361
+ right: 2,
2362
+ bottom: 0,
2363
+ height: `${pct}%`,
2364
+ backgroundColor: alpha(color, 0.28),
2365
+ borderRadius: 3
2366
+ }
2367
+ }
2368
+ );
2369
+ }
2370
+ var MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
2371
+ var READOUT_MAX_CHARS = 5;
2161
2372
  function LiveTempoCell({
2162
2373
  value,
2163
2374
  color,
2164
- isActive,
2375
+ status,
2165
2376
  phaseElapsedMs,
2166
- fontSize
2377
+ completedMs,
2378
+ fontSize,
2379
+ readout
2167
2380
  }) {
2168
- if (!isActive) {
2169
- return /* @__PURE__ */ jsx(TempoValue, { value, color: alpha(color, 0.35), fontSize });
2170
- }
2171
2381
  const targetMs = value > 0 ? value * 1e3 : null;
2172
- const barColor = getTempoPacingState(phaseElapsedMs, targetMs) === "behind" ? STATUS_ERROR : color;
2382
+ const cellW = Math.ceil(fontSize * 0.62 * READOUT_MAX_CHARS);
2383
+ const wrap = {
2384
+ position: "relative",
2385
+ width: cellW,
2386
+ minHeight: Math.round(fontSize * 1.25),
2387
+ alignItems: "center",
2388
+ justifyContent: "center"
2389
+ };
2390
+ const num = (c, text) => /* @__PURE__ */ jsx(reactNative.Text, { style: { fontFamily: MONO, fontSize, color: c, fontWeight: "700" }, children: text });
2391
+ if (status === "upcoming") {
2392
+ const targetText = targetMs != null ? (targetMs / 1e3).toFixed(1) : String(value);
2393
+ return /* @__PURE__ */ jsx(reactNative.View, { style: wrap, children: num(color, targetText) });
2394
+ }
2395
+ if (status === "done") {
2396
+ const finalMs = completedMs ?? targetMs ?? 0;
2397
+ const finalText = targetMs != null ? liveReadoutText(finalMs, targetMs, readout) : String(value);
2398
+ const finalTone = targetMs != null ? activeNumberTone(finalMs, targetMs) : color;
2399
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: wrap, testID: "tempo-live-done", children: [
2400
+ /* @__PURE__ */ jsx(CellFill, { color, pct: 100 }),
2401
+ num(finalTone, finalText)
2402
+ ] });
2403
+ }
2404
+ const numberTone = activeNumberTone(phaseElapsedMs, targetMs);
2173
2405
  const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
2174
- return /* @__PURE__ */ jsxs(reactNative.View, { style: { position: "relative" }, testID: "tempo-live-active", children: [
2175
- /* @__PURE__ */ jsx(
2176
- reactNative.View,
2177
- {
2178
- style: {
2179
- position: "absolute",
2180
- left: 0,
2181
- top: 0,
2182
- bottom: 0,
2183
- width: `${fillPct}%`,
2184
- backgroundColor: alpha(barColor, 0.3),
2185
- borderRadius: 2
2186
- }
2187
- }
2188
- ),
2189
- /* @__PURE__ */ jsx(TempoValue, { value, color: barColor, fontSize })
2406
+ const activeText = targetMs != null ? liveReadoutText(phaseElapsedMs, targetMs, readout) : String(value);
2407
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: wrap, testID: "tempo-live-active", children: [
2408
+ /* @__PURE__ */ jsx(CellFill, { color, pct: fillPct }),
2409
+ num(numberTone, activeText)
2190
2410
  ] });
2191
2411
  }
2192
2412
  function LiveTempoRow({
2193
2413
  values,
2194
2414
  live,
2195
- fontSize
2415
+ fontSize,
2416
+ readout
2196
2417
  }) {
2197
- return /* @__PURE__ */ jsx(jsxRuntime.Fragment, { children: LIVE_PHASES.map((phase, i) => /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row" }, children: [
2198
- i > 0 && /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2199
- /* @__PURE__ */ jsx(
2200
- LiveTempoCell,
2201
- {
2202
- value: values[i],
2203
- color: phase.color,
2204
- isActive: live.activePhase === phase.key,
2205
- phaseElapsedMs: live.phaseElapsedMs,
2206
- fontSize
2207
- }
2208
- )
2209
- ] }, phase.key)) });
2418
+ const activeIndex = live.activePhase ? LIVE_PHASES.findIndex((p) => p.key === live.activePhase) : -1;
2419
+ return /* @__PURE__ */ jsx(jsxRuntime.Fragment, { children: LIVE_PHASES.map((phase, i) => {
2420
+ const status = activeIndex < 0 || i > activeIndex ? "upcoming" : i < activeIndex ? "done" : "active";
2421
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center" }, children: [
2422
+ i > 0 && /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2423
+ /* @__PURE__ */ jsx(
2424
+ LiveTempoCell,
2425
+ {
2426
+ value: values[i],
2427
+ color: phase.color,
2428
+ status,
2429
+ phaseElapsedMs: live.phaseElapsedMs,
2430
+ completedMs: live.completed?.[phase.key],
2431
+ fontSize,
2432
+ readout
2433
+ }
2434
+ )
2435
+ ] }, phase.key);
2436
+ }) });
2210
2437
  }
2211
2438
  function TempoDisplay({
2212
2439
  tempo,
2213
2440
  size = "md",
2214
- colored = true,
2441
+ fontSize: fontSizeProp,
2215
2442
  showLabel = true,
2216
2443
  showInfo = true,
2217
2444
  live,
2445
+ liveReadout = "countdown",
2218
2446
  onPress,
2219
2447
  className,
2220
2448
  ...props
@@ -2222,8 +2450,11 @@ function TempoDisplay({
2222
2450
  const [showTooltip, setShowTooltip] = react.useState(false);
2223
2451
  const [eccentric, pauseBottom, concentric, pauseTop] = roundTempo(tempo);
2224
2452
  const isSm = size === "sm";
2225
- const fontSize = isSm ? 9 : 11;
2226
- const monoColor = TEXT_TERTIARY;
2453
+ const fontSize = fontSizeProp ?? (isSm ? 9 : 11);
2454
+ const chromePadX = Math.round(fontSize * 0.6);
2455
+ const chromePadY = Math.round(fontSize * 0.3);
2456
+ const chromeRadius = Math.round(fontSize * 0.4);
2457
+ const labelFont = Math.max(9, Math.round(fontSize * 0.5));
2227
2458
  const handlePress = react.useCallback(() => {
2228
2459
  if (onPress) {
2229
2460
  onPress();
@@ -2240,9 +2471,9 @@ function TempoDisplay({
2240
2471
  flexDirection: "row",
2241
2472
  alignItems: "center",
2242
2473
  backgroundColor: "#1C1C1C",
2243
- paddingHorizontal: isSm ? 6 : 8,
2244
- paddingVertical: 3,
2245
- borderRadius: 4
2474
+ paddingHorizontal: chromePadX,
2475
+ paddingVertical: chromePadY,
2476
+ borderRadius: chromeRadius
2246
2477
  },
2247
2478
  ...props,
2248
2479
  children: [
@@ -2251,12 +2482,13 @@ function TempoDisplay({
2251
2482
  {
2252
2483
  style: {
2253
2484
  fontFamily: INTER,
2254
- fontSize: 9,
2485
+ fontSize: labelFont,
2255
2486
  fontWeight: "500",
2256
- color: TEXT_TERTIARY,
2487
+ // Live-muted green while a rep is running, tertiary at rest.
2488
+ color: live ? t2["status-live-muted"] : TEXT_TERTIARY,
2257
2489
  letterSpacing: 0.5,
2258
2490
  textTransform: "uppercase",
2259
- marginRight: 6
2491
+ marginRight: Math.round(fontSize * 0.5)
2260
2492
  },
2261
2493
  children: "TEMPO"
2262
2494
  }
@@ -2266,36 +2498,20 @@ function TempoDisplay({
2266
2498
  {
2267
2499
  values: [eccentric, pauseBottom, concentric, pauseTop],
2268
2500
  live,
2269
- fontSize
2270
- }
2271
- ) : colored ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
2272
- /* @__PURE__ */ jsx(TempoValue, { value: eccentric, color: phaseColors.eccentric, fontSize }),
2273
- /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2274
- /* @__PURE__ */ jsx(TempoValue, { value: pauseBottom, color: phaseColors.pauseBottom, fontSize }),
2275
- /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2276
- /* @__PURE__ */ jsx(TempoValue, { value: concentric, color: phaseColors.concentric, fontSize }),
2277
- /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2278
- /* @__PURE__ */ jsx(TempoValue, { value: pauseTop, color: phaseColors.pauseTop, fontSize })
2279
- ] }) : /* @__PURE__ */ jsxs(
2280
- reactNative.Text,
2281
- {
2282
- style: {
2283
- fontFamily: INTER,
2284
- fontSize,
2285
- color: monoColor,
2286
- fontWeight: "600",
2287
- letterSpacing: 1
2288
- },
2289
- children: [
2290
- eccentric,
2291
- "-",
2292
- pauseBottom,
2293
- "-",
2294
- concentric,
2295
- "-",
2296
- pauseTop
2297
- ]
2501
+ fontSize,
2502
+ readout: liveReadout
2298
2503
  }
2504
+ ) : (
2505
+ // Static prescription: the phase-coloured lockup (the same colours the live row rests at).
2506
+ /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
2507
+ /* @__PURE__ */ jsx(TempoValue, { value: eccentric, color: phaseColors.eccentric, fontSize }),
2508
+ /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2509
+ /* @__PURE__ */ jsx(TempoValue, { value: pauseBottom, color: phaseColors.pauseBottom, fontSize }),
2510
+ /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2511
+ /* @__PURE__ */ jsx(TempoValue, { value: concentric, color: phaseColors.concentric, fontSize }),
2512
+ /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
2513
+ /* @__PURE__ */ jsx(TempoValue, { value: pauseTop, color: phaseColors.pauseTop, fontSize })
2514
+ ] })
2299
2515
  ) })
2300
2516
  ]
2301
2517
  }
@@ -2380,14 +2596,14 @@ function TempoDisplay({
2380
2596
  }
2381
2597
  );
2382
2598
  }
2383
- var t4 = getSemanticColors("dark");
2599
+ var t3 = getSemanticColors("dark");
2384
2600
  var INDICATOR_CONFIG = {
2385
- imbalance: { Icon: ScaleIcon, color: t4["status-error"], label: "Left/right imbalance" },
2386
- overshoot: { Icon: AlertTriangleIcon, color: t4["status-error"], label: "Load overshoot" },
2387
- "velocity-loss": { Icon: TrendingDownIcon, color: t4["status-warning"], label: "Velocity loss" },
2388
- "missed-reps": { Icon: CircleSlashIcon, color: t4["status-warning"], label: "Missed reps" },
2389
- pr: { Icon: AwardIcon, color: t4["status-success"], label: "Personal record" },
2390
- info: { Icon: InfoIcon, color: t4["status-info"], label: "More info" }
2601
+ imbalance: { Icon: ScaleIcon, color: t3["status-error"], label: "Left/right imbalance" },
2602
+ overshoot: { Icon: AlertTriangleIcon, color: t3["status-error"], label: "Load overshoot" },
2603
+ "velocity-loss": { Icon: TrendingDownIcon, color: t3["status-warning"], label: "Velocity loss" },
2604
+ "missed-reps": { Icon: CircleSlashIcon, color: t3["status-warning"], label: "Missed reps" },
2605
+ pr: { Icon: AwardIcon, color: t3["status-success"], label: "Personal record" },
2606
+ info: { Icon: InfoIcon, color: t3["status-info"], label: "More info" }
2391
2607
  };
2392
2608
  function ExerciseIndicator({ kind, onPress, className, ...props }) {
2393
2609
  const { Icon, color, label } = INDICATOR_CONFIG[kind];
@@ -2993,9 +3209,270 @@ function useTimer(options = {}) {
2993
3209
  const done = durationMs != null && elapsedMs >= durationMs;
2994
3210
  return { elapsedMs, remainingMs, progress, label, done };
2995
3211
  }
3212
+ var colorVarMap = {
3213
+ primary: "var(--color-brand-primary)",
3214
+ secondary: "var(--color-brand-secondary)",
3215
+ success: "var(--color-status-success)",
3216
+ error: "var(--color-status-error)",
3217
+ warning: "var(--color-status-warning)",
3218
+ info: "var(--color-status-info)"
3219
+ };
3220
+ function CircularProgress({
3221
+ value = 0,
3222
+ max = 100,
3223
+ isIndeterminate = false,
3224
+ size = 48,
3225
+ strokeWidth = 4,
3226
+ color = "primary",
3227
+ showValue = false,
3228
+ formatValue: formatValue2 = (v, m) => `${Math.round(v / m * 100)}%`,
3229
+ children,
3230
+ fill = false,
3231
+ className,
3232
+ ...props
3233
+ }) {
3234
+ const percentage = Math.min(Math.max(value / max * 100, 0), 100);
3235
+ const radius = (size - strokeWidth) / 2;
3236
+ const circumference = 2 * Math.PI * radius;
3237
+ const strokeDashoffset = circumference - percentage / 100 * circumference;
3238
+ const center = size / 2;
3239
+ const boxStyle = fill ? { width: "100%", aspectRatio: 1 } : { width: size, height: size };
3240
+ return /* @__PURE__ */ jsxs(
3241
+ reactNative.View,
3242
+ {
3243
+ className: cn("items-center justify-center", className),
3244
+ style: boxStyle,
3245
+ accessibilityRole: "progressbar",
3246
+ accessibilityValue: {
3247
+ min: 0,
3248
+ max,
3249
+ now: isIndeterminate ? void 0 : value
3250
+ },
3251
+ ...props,
3252
+ children: [
3253
+ /* @__PURE__ */ jsx(reactNative.View, { className: cn(isIndeterminate && "animate-spin"), style: boxStyle, children: /* @__PURE__ */ jsxs(
3254
+ "svg",
3255
+ {
3256
+ width: "100%",
3257
+ height: "100%",
3258
+ viewBox: `0 0 ${size} ${size}`,
3259
+ style: { position: "absolute" },
3260
+ children: [
3261
+ /* @__PURE__ */ jsx(
3262
+ "circle",
3263
+ {
3264
+ cx: center,
3265
+ cy: center,
3266
+ r: radius,
3267
+ fill: "none",
3268
+ stroke: "var(--color-border-default)",
3269
+ strokeWidth
3270
+ }
3271
+ ),
3272
+ /* @__PURE__ */ jsx(
3273
+ "circle",
3274
+ {
3275
+ cx: center,
3276
+ cy: center,
3277
+ r: radius,
3278
+ fill: "none",
3279
+ stroke: colorVarMap[color],
3280
+ strokeWidth,
3281
+ strokeLinecap: "round",
3282
+ strokeDasharray: circumference,
3283
+ strokeDashoffset: isIndeterminate ? circumference * 0.75 : strokeDashoffset,
3284
+ transform: `rotate(-90 ${center} ${center})`,
3285
+ style: { transition: "stroke-dashoffset 300ms cubic-bezier(0.22, 1, 0.36, 1)" }
3286
+ }
3287
+ )
3288
+ ]
3289
+ }
3290
+ ) }),
3291
+ children != null ? /* @__PURE__ */ jsx(
3292
+ reactNative.View,
3293
+ {
3294
+ style: {
3295
+ position: "absolute",
3296
+ alignItems: "center",
3297
+ justifyContent: "center",
3298
+ pointerEvents: "none"
3299
+ },
3300
+ children
3301
+ }
3302
+ ) : showValue && !isIndeterminate && /* @__PURE__ */ jsx(
3303
+ reactNative.Text,
3304
+ {
3305
+ className: "text-xs font-semibold text-text-primary absolute",
3306
+ style: { fontSize: Math.max(size * 0.22, 10) },
3307
+ children: formatValue2(value, max)
3308
+ }
3309
+ )
3310
+ ]
3311
+ }
3312
+ );
3313
+ }
3314
+
3315
+ // src/components/custom/CircularTimer/CircularTimer.tsx
3316
+ function CircularTimer({
3317
+ durationMs,
3318
+ elapsedMs,
3319
+ mode = "down",
3320
+ size = 160,
3321
+ strokeWidth = 8,
3322
+ fill = false,
3323
+ color = "primary",
3324
+ doneLabel,
3325
+ doneColor = "success",
3326
+ controls,
3327
+ accessibilityLabel: accessibilityLabel2,
3328
+ testID
3329
+ }) {
3330
+ const { remainingMs, progress, label, done } = useTimer({ mode, durationMs, elapsedMs });
3331
+ const isDone = done && mode === "down";
3332
+ const showDone = isDone && doneLabel != null;
3333
+ const remainingFrac = durationMs > 0 ? Math.max(0, Math.min(1, remainingMs / durationMs)) : 0;
3334
+ const arc = mode === "down" ? showDone ? 1 : remainingFrac : progress;
3335
+ const ringColor = showDone ? doneColor : color;
3336
+ const remainingSec = Math.ceil(Math.max(0, remainingMs) / 1e3);
3337
+ const a11y = accessibilityLabel2 ?? (isDone ? "Timer complete" : `${remainingSec} seconds remaining`);
3338
+ return /* @__PURE__ */ jsxs(
3339
+ reactNative.View,
3340
+ {
3341
+ style: { alignItems: "center", gap: 16 },
3342
+ accessibilityRole: "timer",
3343
+ accessibilityLabel: a11y,
3344
+ testID: testID ?? "circular-timer",
3345
+ children: [
3346
+ /* @__PURE__ */ jsx(
3347
+ CircularProgress,
3348
+ {
3349
+ value: arc,
3350
+ max: 1,
3351
+ size,
3352
+ strokeWidth,
3353
+ fill,
3354
+ color: ringColor,
3355
+ accessibilityLabel: "Timer ring",
3356
+ testID: "circular-timer-ring",
3357
+ children: /* @__PURE__ */ jsx(
3358
+ reactNative.Text,
3359
+ {
3360
+ className: showDone ? void 0 : "text-text-primary",
3361
+ style: {
3362
+ fontSize: Math.round(size * (showDone ? 0.28 : 0.24)),
3363
+ fontFamily: '"Space Grotesk", sans-serif',
3364
+ fontWeight: "800",
3365
+ fontVariant: ["tabular-nums"],
3366
+ letterSpacing: showDone ? 1 : -1,
3367
+ ...showDone ? { color: colorVarMap[doneColor] } : null
3368
+ },
3369
+ testID: "circular-timer-label",
3370
+ children: showDone ? doneLabel : label
3371
+ }
3372
+ )
3373
+ }
3374
+ ),
3375
+ controls
3376
+ ]
3377
+ }
3378
+ );
3379
+ }
2996
3380
 
2997
3381
  // src/components/custom/Workout/RestTimer.tsx
2998
3382
  var BRAND_PRIMARY3 = getSemanticColors("dark")["brand-primary"];
3383
+ var RING_DEFAULT_SIZE = 180;
3384
+ function RestActions({ onAddTime, onSkip }) {
3385
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
3386
+ /* @__PURE__ */ jsx(
3387
+ reactNative.Pressable,
3388
+ {
3389
+ onPress: onAddTime,
3390
+ style: {
3391
+ backgroundColor: "rgba(255,255,255,0.06)",
3392
+ paddingVertical: 8,
3393
+ paddingHorizontal: 20,
3394
+ borderRadius: 8
3395
+ },
3396
+ accessibilityRole: "button",
3397
+ accessibilityLabel: "Add 30 seconds",
3398
+ testID: "rest-timer-add-time",
3399
+ children: /* @__PURE__ */ jsx(
3400
+ reactNative.Text,
3401
+ {
3402
+ className: "text-text-secondary",
3403
+ style: { fontSize: 11, fontFamily: "Inter, sans-serif", fontWeight: "600" },
3404
+ children: "+30s"
3405
+ }
3406
+ )
3407
+ }
3408
+ ),
3409
+ /* @__PURE__ */ jsx(
3410
+ reactNative.Pressable,
3411
+ {
3412
+ onPress: onSkip,
3413
+ style: {
3414
+ backgroundColor: "rgba(255,121,0,0.12)",
3415
+ paddingVertical: 8,
3416
+ paddingHorizontal: 20,
3417
+ borderRadius: 8
3418
+ },
3419
+ accessibilityRole: "button",
3420
+ accessibilityLabel: "Skip rest",
3421
+ testID: "rest-timer-skip",
3422
+ children: /* @__PURE__ */ jsx(
3423
+ reactNative.Text,
3424
+ {
3425
+ style: {
3426
+ fontSize: 11,
3427
+ fontFamily: "Inter, sans-serif",
3428
+ fontWeight: "600",
3429
+ color: BRAND_PRIMARY3
3430
+ },
3431
+ children: "Skip"
3432
+ }
3433
+ )
3434
+ }
3435
+ )
3436
+ ] });
3437
+ }
3438
+ function RestTimerRing({
3439
+ totalSeconds,
3440
+ elapsedMs,
3441
+ remainingSec,
3442
+ done,
3443
+ size,
3444
+ nextSetInfo,
3445
+ displayOnly,
3446
+ onSkip,
3447
+ onAddTime
3448
+ }) {
3449
+ const a11yLabel = done ? "Rest complete, next set ready" : `Rest timer, ${Math.max(0, remainingSec)} seconds remaining`;
3450
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { alignItems: "center", gap: 16 }, testID: "rest-timer", children: [
3451
+ /* @__PURE__ */ jsx(
3452
+ CircularTimer,
3453
+ {
3454
+ durationMs: totalSeconds * 1e3,
3455
+ elapsedMs,
3456
+ mode: "down",
3457
+ size,
3458
+ doneLabel: "GO",
3459
+ doneColor: "success",
3460
+ accessibilityLabel: a11yLabel,
3461
+ controls: !displayOnly ? /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip }) : void 0,
3462
+ testID: "rest-timer-ring"
3463
+ }
3464
+ ),
3465
+ nextSetInfo != null && /* @__PURE__ */ jsx(
3466
+ reactNative.Text,
3467
+ {
3468
+ className: "text-text-tertiary",
3469
+ style: { fontSize: 13, fontFamily: "Inter, sans-serif" },
3470
+ testID: "rest-timer-next-set",
3471
+ children: nextSetInfo
3472
+ }
3473
+ )
3474
+ ] });
3475
+ }
2999
3476
  function RestTimer({
3000
3477
  totalSeconds,
3001
3478
  elapsedMs,
@@ -3003,12 +3480,15 @@ function RestTimer({
3003
3480
  onAddTime,
3004
3481
  nextSetInfo,
3005
3482
  visible,
3006
- displayOnly = false
3483
+ displayOnly = false,
3484
+ variant = "bar",
3485
+ size = RING_DEFAULT_SIZE
3007
3486
  }) {
3008
3487
  const {
3009
3488
  remainingMs,
3010
3489
  progress,
3011
- label: timeDisplay
3490
+ label: timeDisplay,
3491
+ done
3012
3492
  } = useTimer({
3013
3493
  mode: "down",
3014
3494
  durationMs: totalSeconds * 1e3,
@@ -3017,6 +3497,22 @@ function RestTimer({
3017
3497
  const remainingSec = Math.ceil(remainingMs / 1e3);
3018
3498
  const progressPct = progress * 100;
3019
3499
  if (!visible) return null;
3500
+ if (variant === "ring") {
3501
+ return /* @__PURE__ */ jsx(
3502
+ RestTimerRing,
3503
+ {
3504
+ totalSeconds,
3505
+ elapsedMs,
3506
+ remainingSec,
3507
+ done,
3508
+ size,
3509
+ nextSetInfo,
3510
+ displayOnly,
3511
+ onSkip,
3512
+ onAddTime
3513
+ }
3514
+ );
3515
+ }
3020
3516
  return /* @__PURE__ */ jsxs(
3021
3517
  reactNative.View,
3022
3518
  {
@@ -3114,62 +3610,7 @@ function RestTimer({
3114
3610
  )
3115
3611
  }
3116
3612
  ),
3117
- !displayOnly && /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
3118
- /* @__PURE__ */ jsx(
3119
- reactNative.Pressable,
3120
- {
3121
- onPress: onAddTime,
3122
- style: {
3123
- backgroundColor: "rgba(255,255,255,0.06)",
3124
- paddingVertical: 8,
3125
- paddingHorizontal: 20,
3126
- borderRadius: 8
3127
- },
3128
- accessibilityRole: "button",
3129
- accessibilityLabel: "Add 30 seconds",
3130
- testID: "rest-timer-add-time",
3131
- children: /* @__PURE__ */ jsx(
3132
- reactNative.Text,
3133
- {
3134
- className: "text-text-secondary",
3135
- style: {
3136
- fontSize: 11,
3137
- fontFamily: "Inter, sans-serif",
3138
- fontWeight: "600"
3139
- },
3140
- children: "+30s"
3141
- }
3142
- )
3143
- }
3144
- ),
3145
- /* @__PURE__ */ jsx(
3146
- reactNative.Pressable,
3147
- {
3148
- onPress: onSkip,
3149
- style: {
3150
- backgroundColor: "rgba(255,121,0,0.12)",
3151
- paddingVertical: 8,
3152
- paddingHorizontal: 20,
3153
- borderRadius: 8
3154
- },
3155
- accessibilityRole: "button",
3156
- accessibilityLabel: "Skip rest",
3157
- testID: "rest-timer-skip",
3158
- children: /* @__PURE__ */ jsx(
3159
- reactNative.Text,
3160
- {
3161
- style: {
3162
- fontSize: 11,
3163
- fontFamily: "Inter, sans-serif",
3164
- fontWeight: "600",
3165
- color: BRAND_PRIMARY3
3166
- },
3167
- children: "Skip"
3168
- }
3169
- )
3170
- }
3171
- )
3172
- ] })
3613
+ !displayOnly && /* @__PURE__ */ jsx(RestActions, { onAddTime, onSkip })
3173
3614
  ]
3174
3615
  }
3175
3616
  );
@@ -3959,11 +4400,11 @@ function CardContent({ children, className }) {
3959
4400
  function CardFooter({ children, className }) {
3960
4401
  return /* @__PURE__ */ jsx(reactNative.View, { className: cn("px-6 py-4 flex-row items-center gap-2", className), children });
3961
4402
  }
3962
- var t5 = getSemanticColors("dark");
4403
+ var t4 = getSemanticColors("dark");
3963
4404
  var solidVariantColors = {
3964
- success: t5["status-success"],
3965
- warning: t5["status-warning"],
3966
- error: t5["status-error"],
4405
+ success: t4["status-success"],
4406
+ warning: t4["status-warning"],
4407
+ error: t4["status-error"],
3967
4408
  neutral: "#6B7280"
3968
4409
  };
3969
4410
  var ringVariantStyles = {
@@ -4003,8 +4444,8 @@ var iconColors = {
4003
4444
  warning: "#6B4000",
4004
4445
  error: "#5C1A1A",
4005
4446
  neutral: "#D1D5DB",
4006
- "on-track": t5["status-success"],
4007
- deviation: t5["status-warning"],
4447
+ "on-track": t4["status-success"],
4448
+ deviation: t4["status-warning"],
4008
4449
  future: "#6B7280"
4009
4450
  };
4010
4451
  var sizeDimensions = {
@@ -4090,13 +4531,13 @@ var MESO_ACCENT_GRADIENT_LIGHT = primitiveRamps.orange[300];
4090
4531
  var WORKOUT_PILL_DELOAD = primitiveRamps.magenta[600];
4091
4532
 
4092
4533
  // src/components/custom/Workout/MesoStatusCard.tsx
4093
- var t6 = getSemanticColors("dark");
4094
- var BRAND_PRIMARY5 = t6["brand-primary"];
4534
+ var t5 = getSemanticColors("dark");
4535
+ var BRAND_PRIMARY5 = t5["brand-primary"];
4095
4536
  var BRAND_PRIMARY_DARK = MESO_ACCENT_GRADIENT_DARK;
4096
4537
  var BRAND_PRIMARY_LIGHT = MESO_ACCENT_GRADIENT_LIGHT;
4097
- var SUCCESS = t6["status-success"];
4098
- var WARNING = t6["status-warning"];
4099
- var ERROR = t6["status-error"];
4538
+ var SUCCESS = t5["status-success"];
4539
+ var WARNING = t5["status-warning"];
4540
+ var ERROR = t5["status-error"];
4100
4541
  var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY5, BRAND_PRIMARY_LIGHT];
4101
4542
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
4102
4543
  var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)";
@@ -4456,9 +4897,9 @@ function MesoStatusCard({
4456
4897
  var sem = getSemanticColors("dark");
4457
4898
  var BRAND_PRIMARY6 = sem["brand-primary"];
4458
4899
  var PROJECTION_LINE_COLOR = resolveColor("text-tertiary");
4459
- var STATUS_SUCCESS = sem["status-success"];
4900
+ var STATUS_SUCCESS2 = sem["status-success"];
4460
4901
  var STATUS_ERROR2 = sem["status-error"];
4461
- var STATUS_WARNING = sem["status-warning"];
4902
+ var STATUS_WARNING2 = sem["status-warning"];
4462
4903
  var GRID_LINE = "rgba(255,255,255,0.06)";
4463
4904
  var SUCCESS_PILL_BG = "rgba(46,213,115,0.10)";
4464
4905
  var SUCCESS_PILL_BORDER = "rgba(46,213,115,0.20)";
@@ -4467,8 +4908,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
4467
4908
  var PLOT_LEFT = 26;
4468
4909
  var TOOLTIP_WIDTH = 124;
4469
4910
  function timeOf(dateStr) {
4470
- const t12 = Date.parse(dateStr);
4471
- return Number.isNaN(t12) ? 0 : t12;
4911
+ const t11 = Date.parse(dateStr);
4912
+ return Number.isNaN(t11) ? 0 : t11;
4472
4913
  }
4473
4914
  function formatValue(value) {
4474
4915
  return `${Math.round(value)}`;
@@ -4764,7 +5205,7 @@ function StrengthTrendChart({
4764
5205
  left: c.x - 7,
4765
5206
  top: c.y - 20,
4766
5207
  fontSize: 12,
4767
- color: STATUS_WARNING
5208
+ color: STATUS_WARNING2
4768
5209
  },
4769
5210
  children: "\u2605"
4770
5211
  },
@@ -4857,7 +5298,7 @@ function StrengthTrendChart({
4857
5298
  marginTop: 1,
4858
5299
  fontSize: 10,
4859
5300
  fontFamily: "Inter, sans-serif",
4860
- color: selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? STATUS_SUCCESS : STATUS_ERROR2
5301
+ color: selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? STATUS_SUCCESS2 : STATUS_ERROR2
4861
5302
  },
4862
5303
  children: `${selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? "+" : "-"}${Math.abs(
4863
5304
  selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm
@@ -4913,7 +5354,7 @@ function StrengthTrendChart({
4913
5354
  fontSize: 11,
4914
5355
  fontFamily: "Inter, sans-serif",
4915
5356
  fontWeight: "600",
4916
- color: trend.positive ? STATUS_SUCCESS : STATUS_ERROR2
5357
+ color: trend.positive ? STATUS_SUCCESS2 : STATUS_ERROR2
4917
5358
  },
4918
5359
  children: trendText
4919
5360
  }
@@ -4947,7 +5388,7 @@ function StrengthTrendChart({
4947
5388
  /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-secondary", style: { fontSize: 10, fontFamily: "Inter, sans-serif" }, children: "Projected" })
4948
5389
  ] }),
4949
5390
  /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { gap: 5 }, children: [
4950
- /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 12, color: STATUS_WARNING }, children: "\u2605" }),
5391
+ /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 12, color: STATUS_WARNING2 }, children: "\u2605" }),
4951
5392
  /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-secondary", style: { fontSize: 10, fontFamily: "Inter, sans-serif" }, children: "PR" })
4952
5393
  ] })
4953
5394
  ]
@@ -4957,10 +5398,10 @@ function StrengthTrendChart({
4957
5398
  }
4958
5399
  );
4959
5400
  }
4960
- var t7 = getSemanticColors("dark");
4961
- var STATUS_SUCCESS2 = t7["status-success"];
4962
- var STATUS_WARNING2 = t7["status-warning"];
4963
- var STATUS_INFO = t7["status-info"];
5401
+ var t6 = getSemanticColors("dark");
5402
+ var STATUS_SUCCESS3 = t6["status-success"];
5403
+ var STATUS_WARNING3 = t6["status-warning"];
5404
+ var STATUS_INFO = t6["status-info"];
4964
5405
  var DOT_BORDER = "#F3F4F6";
4965
5406
  var BAND_FILL = "rgba(46,213,115,0.1)";
4966
5407
  var BAND_EDGE = "rgba(46,213,115,0.45)";
@@ -4972,8 +5413,8 @@ var PADDING_BOTTOM = 16;
4972
5413
  var COLUMN_STEP = 4;
4973
5414
  var DOT_SIZE = 8;
4974
5415
  var DOT_COLORS = {
4975
- within: STATUS_SUCCESS2,
4976
- above: STATUS_WARNING2,
5416
+ within: STATUS_SUCCESS3,
5417
+ above: STATUS_WARNING3,
4977
5418
  below: STATUS_INFO
4978
5419
  };
4979
5420
  var STATUS_PHRASES = {
@@ -5003,8 +5444,8 @@ function interpEdge(pixels, x, key) {
5003
5444
  const a = pixels[i];
5004
5445
  const b = pixels[i + 1];
5005
5446
  if (x >= a.x && x <= b.x) {
5006
- const t12 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
5007
- return a[key] + t12 * (b[key] - a[key]);
5447
+ const t11 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
5448
+ return a[key] + t11 * (b[key] - a[key]);
5008
5449
  }
5009
5450
  }
5010
5451
  return pixels[pixels.length - 1][key];
@@ -5198,8 +5639,8 @@ function CapacityBandChart({
5198
5639
  projection && /* @__PURE__ */ jsxs(reactNative.View, { testID: "capacity-band-chart-projection", children: [
5199
5640
  renderColumns(buildColumns2(trainingPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5200
5641
  renderColumns(buildColumns2(restPixels, COLUMN_STEP), PROJECTION_FILL, "capacity-band-chart-projection-fill"),
5201
- renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
5202
- renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS2, true, "capacity-band-chart-projection-training"),
5642
+ renderEdges(buildEdges(trainingPixels, "yHigh"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
5643
+ renderEdges(buildEdges(trainingPixels, "yLow"), STATUS_SUCCESS3, true, "capacity-band-chart-projection-training"),
5203
5644
  renderEdges(buildEdges(restPixels, "yHigh"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
5204
5645
  renderEdges(buildEdges(restPixels, "yLow"), STATUS_INFO, true, "capacity-band-chart-projection-rest"),
5205
5646
  /* @__PURE__ */ jsx(
@@ -5211,7 +5652,7 @@ function CapacityBandChart({
5211
5652
  top: trainingPixels[trainingPixels.length - 1]?.yHigh - 12,
5212
5653
  fontSize: 9,
5213
5654
  fontFamily: "Inter, sans-serif",
5214
- color: STATUS_SUCCESS2
5655
+ color: STATUS_SUCCESS3
5215
5656
  },
5216
5657
  testID: "capacity-band-chart-projection-training-label",
5217
5658
  children: "Training"
@@ -6043,7 +6484,7 @@ function Badge({
6043
6484
  }
6044
6485
  );
6045
6486
  }
6046
- var t8 = getSemanticColors("dark");
6487
+ var t7 = getSemanticColors("dark");
6047
6488
  var statusBgColors = {
6048
6489
  completed: "rgba(46,213,115,0.15)",
6049
6490
  current: "rgba(255,121,0,0.15)",
@@ -6061,9 +6502,9 @@ var statusBorderStyles = {
6061
6502
  deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
6062
6503
  };
6063
6504
  var textColors = {
6064
- completed: t8["status-success"],
6065
- current: t8["brand-primary"],
6066
- next: t8["brand-primary"],
6505
+ completed: t7["status-success"],
6506
+ current: t7["brand-primary"],
6507
+ next: t7["brand-primary"],
6067
6508
  upcoming: "#6B7280",
6068
6509
  missed: "rgba(209,67,67,0.7)",
6069
6510
  deload: WORKOUT_PILL_DELOAD
@@ -6196,19 +6637,19 @@ function WorkoutPill({
6196
6637
  }
6197
6638
  return pill;
6198
6639
  }
6199
- var t9 = getSemanticColors("dark");
6640
+ var t8 = getSemanticColors("dark");
6200
6641
  var TRACK_BG = "#333333";
6201
6642
  var LABEL_COLOR = "#6B7280";
6202
6643
  var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
6203
6644
  var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
6204
6645
  var ZONE = {
6205
- building: t9["status-success"],
6646
+ building: t8["status-success"],
6206
6647
  // below target ramp-up
6207
- approaching: t9["status-warning"],
6648
+ approaching: t8["status-warning"],
6208
6649
  // nearing target
6209
- target: t9["brand-primary"],
6650
+ target: t8["brand-primary"],
6210
6651
  // exactly at target
6211
- over1: t9["status-error"],
6652
+ over1: t8["status-error"],
6212
6653
  // mild over-target
6213
6654
  over2: WORKOUT_TOKENS.intensity.over2,
6214
6655
  // moderate over-target
@@ -6631,13 +7072,13 @@ function MesoCard({
6631
7072
  }
6632
7073
  ) });
6633
7074
  }
6634
- var t10 = getSemanticColors("dark");
7075
+ var t9 = getSemanticColors("dark");
6635
7076
  var dotColorMap = {
6636
7077
  untrained: "#2C2C2C",
6637
- behind: t10["brand-secondary"],
6638
- ontrack: t10["status-success"],
6639
- target: t10["brand-primary"],
6640
- over: t10["status-error"]
7078
+ behind: t9["brand-secondary"],
7079
+ ontrack: t9["status-success"],
7080
+ target: t9["brand-primary"],
7081
+ over: t9["status-error"]
6641
7082
  };
6642
7083
  function MuscleGroupChip({
6643
7084
  name,
@@ -6711,10 +7152,10 @@ function MuscleGroupChip({
6711
7152
  }
6712
7153
 
6713
7154
  // src/components/custom/Workout/WorkoutCard.tsx
6714
- var t11 = getSemanticColors("dark");
7155
+ var t10 = getSemanticColors("dark");
6715
7156
  var COLORS = {
6716
- statusSuccess: t11["status-success"],
6717
- brandPrimary: t11["brand-primary"],
7157
+ statusSuccess: t10["status-success"],
7158
+ brandPrimary: t10["brand-primary"],
6718
7159
  borderDefault: "#1F1F1F",
6719
7160
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
6720
7161
  };