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