@titan-design/react-ui 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/MesoStatusCard-CQk71hSi.d.mts +77 -0
  2. package/dist/MesoStatusCard-CQk71hSi.d.ts +77 -0
  3. package/dist/bodymap.d.mts +246 -3
  4. package/dist/bodymap.d.ts +246 -3
  5. package/dist/bodymap.js +441 -97
  6. package/dist/bodymap.js.map +1 -1
  7. package/dist/bodymap.mjs +1958 -66
  8. package/dist/bodymap.mjs.map +1 -1
  9. package/dist/index.d.mts +71 -738
  10. package/dist/index.d.ts +71 -738
  11. package/dist/index.js +888 -1583
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +4515 -2348
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/pages-D7Jlssvp.d.ts +791 -0
  16. package/dist/pages-MO0JCySL.d.mts +791 -0
  17. package/dist/pages.d.mts +4 -0
  18. package/dist/pages.d.ts +4 -0
  19. package/dist/pages.js +6646 -0
  20. package/dist/pages.js.map +1 -0
  21. package/dist/pages.mjs +6626 -0
  22. package/dist/pages.mjs.map +1 -0
  23. package/dist/theme/index.d.mts +28 -2
  24. package/dist/theme/index.d.ts +28 -2
  25. package/dist/theme/index.js +37 -0
  26. package/dist/theme/index.js.map +1 -1
  27. package/dist/theme/index.mjs +1880 -2
  28. package/dist/theme/index.mjs.map +1 -1
  29. package/dist/theme/tokens-css.mjs +573 -1
  30. package/dist/theme/tokens-css.mjs.map +1 -1
  31. package/package.json +9 -7
  32. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +10 -15
  33. package/src/components/custom/Workout/BaseBadge.stories.tsx +3 -3
  34. package/src/components/custom/Workout/BaseBadge.test.tsx +1 -1
  35. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  36. package/src/components/custom/Workout/BodyMap.tsx +7 -8
  37. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +25 -28
  38. package/src/components/custom/Workout/CapacityBandChart.tsx +2 -3
  39. package/src/components/custom/Workout/ExerciseCard.tsx +17 -28
  40. package/src/components/custom/Workout/ExerciseDetailPage.tsx +38 -31
  41. package/src/components/custom/Workout/InputBar.tsx +10 -11
  42. package/src/components/custom/Workout/IntensityBar.test.tsx +112 -60
  43. package/src/components/custom/Workout/IntensityBar.tsx +109 -58
  44. package/src/components/custom/Workout/MesoCard.tsx +2 -6
  45. package/src/components/custom/Workout/MesoStatusCard.tsx +17 -15
  46. package/src/components/custom/Workout/MuscleGroupChip.tsx +2 -4
  47. package/src/components/custom/Workout/PlaceholderStrip.tsx +5 -10
  48. package/src/components/custom/Workout/PrBadge.tsx +3 -3
  49. package/src/components/custom/Workout/PrHistoryModal.tsx +14 -16
  50. package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -12
  51. package/src/components/custom/Workout/README.md +50 -0
  52. package/src/components/custom/Workout/ReadinessCheck.tsx +13 -11
  53. package/src/components/custom/Workout/RestTimer.test.tsx +41 -10
  54. package/src/components/custom/Workout/RestTimer.tsx +55 -49
  55. package/src/components/custom/Workout/SetRow.tsx +20 -8
  56. package/src/components/custom/Workout/StrengthTrendChart.tsx +15 -17
  57. package/src/components/custom/Workout/TempoBar.stories.tsx +95 -0
  58. package/src/components/custom/Workout/TempoBar.test.tsx +103 -0
  59. package/src/components/custom/Workout/TempoBar.tsx +215 -0
  60. package/src/components/custom/Workout/TempoDisplay.stories.tsx +69 -2
  61. package/src/components/custom/Workout/TempoDisplay.test.tsx +35 -0
  62. package/src/components/custom/Workout/TempoDisplay.tsx +104 -1
  63. package/src/components/custom/Workout/TrainingStatusPage.tsx +8 -17
  64. package/src/components/custom/Workout/VelocityStrip.test.tsx +89 -2
  65. package/src/components/custom/Workout/VelocityStrip.tsx +210 -43
  66. package/src/components/custom/Workout/WeekRow.test.tsx +2 -2
  67. package/src/components/custom/Workout/WeekRow.tsx +2 -1
  68. package/src/components/custom/Workout/WeightBadge.test.tsx +2 -2
  69. package/src/components/custom/Workout/WeightBadge.tsx +2 -2
  70. package/src/components/custom/Workout/WorkoutCard.tsx +3 -8
  71. package/src/components/custom/Workout/icons.tsx +68 -0
  72. package/src/components/custom/Workout/index.ts +47 -47
  73. package/src/index.ts +3 -2
  74. package/src/pages.ts +61 -0
  75. package/src/test/nativewind-stub.ts +13 -0
  76. package/src/test/stories-smoke.test.tsx +50 -0
  77. package/src/theme/ThemeProvider.test.tsx +24 -0
  78. package/src/theme/ThemeProvider.tsx +50 -0
  79. package/src/theme/barrel.ts +53 -0
  80. package/src/theme/index.ts +8 -41
  81. package/src/theme/native-theming-guard.test.ts +117 -0
  82. package/src/theme/resolve-color.ts +19 -0
  83. package/dist/bodymap-BhG55xHM.d.mts +0 -318
  84. package/dist/bodymap-BhG55xHM.d.ts +0 -318
  85. package/dist/chunk-7XPB4NAO.mjs +0 -1013
  86. package/dist/chunk-7XPB4NAO.mjs.map +0 -1
  87. package/dist/chunk-P7TSQUN6.mjs +0 -846
  88. package/dist/chunk-P7TSQUN6.mjs.map +0 -1
  89. package/dist/chunk-TOD2WQBG.mjs +0 -1048
  90. package/dist/chunk-TOD2WQBG.mjs.map +0 -1
package/dist/index.js CHANGED
@@ -5,7 +5,6 @@ var clsx = require('clsx');
5
5
  var tailwindMerge = require('tailwind-merge');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var React24 = require('react');
8
- var lucideReact = require('lucide-react');
9
8
 
10
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
10
 
@@ -2160,6 +2159,9 @@ function validateCssPropertyManifest(candidate) {
2160
2159
  function isCssPropertyManifest(candidate) {
2161
2160
  return validateCssPropertyManifest(candidate).valid;
2162
2161
  }
2162
+ function resolveColor(token, mode = "dark") {
2163
+ return reactNative.Platform.OS === "web" ? `var(--color-${token})` : getSemanticColors(mode)[token];
2164
+ }
2163
2165
 
2164
2166
  // src/theme/presets/apply.ts
2165
2167
  function colorKeyToVar(key) {
@@ -4985,7 +4987,7 @@ function ToastProvider({
4985
4987
  return id;
4986
4988
  }, [defaultDuration, maxToasts]);
4987
4989
  const removeToast = React24.useCallback((id) => {
4988
- setToasts((prev) => prev.filter((t) => t.id !== id));
4990
+ setToasts((prev) => prev.filter((t2) => t2.id !== id));
4989
4991
  }, []);
4990
4992
  const removeAllToasts = React24.useCallback(() => {
4991
4993
  setToasts([]);
@@ -6752,7 +6754,7 @@ var variantColors = {
6752
6754
  plain: { backgroundColor: "#1C1C1C", borderColor: "#1F1F1F" },
6753
6755
  pr: {
6754
6756
  backgroundColor: "rgba(255, 121, 0, 0.12)",
6755
- borderColor: "rgba(255, 121, 0, 0.12)"
6757
+ borderColor: "rgba(255, 121, 0, 0.3)"
6756
6758
  }
6757
6759
  };
6758
6760
  function BaseBadge({
@@ -6820,6 +6822,58 @@ function BaseBadge({
6820
6822
  }
6821
6823
  return badge;
6822
6824
  }
6825
+
6826
+ // src/components/custom/Workout/icons.tsx
6827
+ function DumbbellIcon({
6828
+ size = 24,
6829
+ color = "currentColor",
6830
+ fill = "none",
6831
+ strokeWidth = 2
6832
+ }) {
6833
+ return /* @__PURE__ */ jsxs(
6834
+ "svg",
6835
+ {
6836
+ width: size,
6837
+ height: size,
6838
+ viewBox: "0 0 24 24",
6839
+ fill,
6840
+ stroke: color,
6841
+ strokeWidth,
6842
+ strokeLinecap: "round",
6843
+ strokeLinejoin: "round",
6844
+ children: [
6845
+ /* @__PURE__ */ jsx("path", { d: "M14.4 14.4 9.6 9.6" }),
6846
+ /* @__PURE__ */ jsx("path", { d: "M18.657 21.485a2 2 0 1 1-2.829-2.828l-1.767 1.768a2 2 0 1 1-2.829-2.829l6.364-6.364a2 2 0 1 1 2.829 2.829l-1.768 1.767a2 2 0 1 1 2.828 2.829z" }),
6847
+ /* @__PURE__ */ jsx("path", { d: "m21.5 21.5-1.4-1.4" }),
6848
+ /* @__PURE__ */ jsx("path", { d: "M3.9 3.9 2.5 2.5" }),
6849
+ /* @__PURE__ */ jsx("path", { d: "M6.404 12.768a2 2 0 1 1-2.829-2.829l1.768-1.767a2 2 0 1 1-2.828-2.829l2.828-2.828a2 2 0 1 1 2.829 2.828l1.767-1.768a2 2 0 1 1 2.829 2.829z" })
6850
+ ]
6851
+ }
6852
+ );
6853
+ }
6854
+ function StarIcon({
6855
+ size = 24,
6856
+ color = "currentColor",
6857
+ fill = "none",
6858
+ strokeWidth = 2
6859
+ }) {
6860
+ return /* @__PURE__ */ jsx(
6861
+ "svg",
6862
+ {
6863
+ width: size,
6864
+ height: size,
6865
+ viewBox: "0 0 24 24",
6866
+ fill,
6867
+ stroke: color,
6868
+ strokeWidth,
6869
+ strokeLinecap: "round",
6870
+ strokeLinejoin: "round",
6871
+ children: /* @__PURE__ */ jsx("path", { d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" })
6872
+ }
6873
+ );
6874
+ }
6875
+
6876
+ // src/components/custom/Workout/WeightBadge.tsx
6823
6877
  function WeightBadge({
6824
6878
  value,
6825
6879
  unit = "lbs",
@@ -6845,7 +6899,7 @@ function WeightBadge({
6845
6899
  size,
6846
6900
  onPress,
6847
6901
  className,
6848
- icon: showIcon ? /* @__PURE__ */ jsx(lucideReact.Dumbbell, { size: config.iconSize, color: iconColor, strokeWidth: 2 }) : void 0,
6902
+ icon: showIcon ? /* @__PURE__ */ jsx(DumbbellIcon, { size: config.iconSize, color: iconColor, strokeWidth: 2 }) : void 0,
6849
6903
  accessibilityLabel: fullLabel,
6850
6904
  testID: "weight-badge",
6851
6905
  ...props,
@@ -6949,7 +7003,7 @@ function PrBadge({
6949
7003
  accessibilityLabel: `Personal record: ${resolvedLabel}`,
6950
7004
  testID: "pr-badge-star",
6951
7005
  ...props,
6952
- children: /* @__PURE__ */ jsx(lucideReact.Star, { size: 14, color: "#FF7900", fill: "#FF7900", strokeWidth: 2 })
7006
+ children: /* @__PURE__ */ jsx(StarIcon, { size: 14, color: "#FF7900", fill: "#FF7900", strokeWidth: 2 })
6953
7007
  }
6954
7008
  ) : /* @__PURE__ */ jsx(
6955
7009
  BaseBadge,
@@ -6962,7 +7016,7 @@ function PrBadge({
6962
7016
  reactNative.Text,
6963
7017
  {
6964
7018
  style: {
6965
- fontSize: 10,
7019
+ fontSize: 12,
6966
7020
  color: "#FF7900",
6967
7021
  fontWeight: "700",
6968
7022
  fontFamily: "Inter, sans-serif"
@@ -7113,6 +7167,29 @@ function StatusDot({
7113
7167
  }
7114
7168
  return /* @__PURE__ */ jsx(reactNative.View, { className, ...props, children: dot });
7115
7169
  }
7170
+
7171
+ // src/theme/workout-tokens.ts
7172
+ var WORKOUT_TOKENS = {
7173
+ // Canonical 4-band performance scale — the single source for BOTH the
7174
+ // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
7175
+ // is intentionally inverted between the two consumers: for velocity, green =
7176
+ // fastest/best; for RPE, green = easiest. Each component maps its own
7177
+ // thresholds onto these four colors. Kept as vivid data-viz values (distinct
7178
+ // from the muted status tokens) and theme-independent, so — like the heatmap
7179
+ // scale below — they are consumed inline as a JS import rather than CSS vars.
7180
+ // A JS import also resolves on native RN, unlike var(--…) strings.
7181
+ scale: {
7182
+ green: "#2ed573",
7183
+ yellow: "#ffd43b",
7184
+ orange: "#ffa502",
7185
+ red: "#ff4757"
7186
+ },
7187
+ // Placeholder strip
7188
+ placeholder: {
7189
+ fill: "#3A3A3A"
7190
+ }};
7191
+
7192
+ // src/components/custom/Workout/PlaceholderStrip.tsx
7116
7193
  function SingleStrip({
7117
7194
  width,
7118
7195
  className,
@@ -7121,15 +7198,12 @@ function SingleStrip({
7121
7198
  return /* @__PURE__ */ jsx(
7122
7199
  reactNative.View,
7123
7200
  {
7124
- className: cn(className),
7201
+ className,
7125
7202
  style: [
7126
7203
  {
7127
7204
  height: 3,
7128
- backgroundColor: "transparent",
7129
- borderWidth: 1,
7130
- borderStyle: "dashed",
7131
- borderColor: "var(--color-border-strong)",
7132
- borderRadius: 1,
7205
+ backgroundColor: WORKOUT_TOKENS.placeholder.fill,
7206
+ borderRadius: 2,
7133
7207
  opacity: 0.5
7134
7208
  },
7135
7209
  width != null ? { width } : { flex: 1 }
@@ -7165,10 +7239,7 @@ function SegmentedStrip({
7165
7239
  style: {
7166
7240
  flex: 1,
7167
7241
  height: 3,
7168
- backgroundColor: "transparent",
7169
- borderWidth: 1,
7170
- borderStyle: "dashed",
7171
- borderColor: "var(--color-border-strong)",
7242
+ backgroundColor: WORKOUT_TOKENS.placeholder.fill,
7172
7243
  borderRadius: 1,
7173
7244
  minWidth: 4
7174
7245
  },
@@ -7191,23 +7262,6 @@ function PlaceholderStrip({
7191
7262
  return /* @__PURE__ */ jsx(SingleStrip, { ...props });
7192
7263
  }
7193
7264
 
7194
- // src/theme/workout-tokens.ts
7195
- var WORKOUT_TOKENS = {
7196
- // Canonical 4-band performance scale — the single source for BOTH the
7197
- // VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
7198
- // is intentionally inverted between the two consumers: for velocity, green =
7199
- // fastest/best; for RPE, green = easiest. Each component maps its own
7200
- // thresholds onto these four colors. Kept as vivid data-viz values (distinct
7201
- // from the muted status tokens) and theme-independent, so — like the heatmap
7202
- // scale below — they are consumed inline as a JS import rather than CSS vars.
7203
- // A JS import also resolves on native RN, unlike var(--…) strings.
7204
- scale: {
7205
- green: "#2ed573",
7206
- yellow: "#ffd43b",
7207
- orange: "#ffa502",
7208
- red: "#ff4757"
7209
- }};
7210
-
7211
7265
  // src/utils/workout-format.ts
7212
7266
  function roundRpe(rpe) {
7213
7267
  return Math.round(rpe * 2) / 2;
@@ -7246,9 +7300,200 @@ function formatPrescription(p) {
7246
7300
  return full.length > 0 ? full : null;
7247
7301
  }
7248
7302
 
7303
+ // src/utils/colors.ts
7304
+ function getStatusColor(status) {
7305
+ return `var(--color-status-${status})`;
7306
+ }
7307
+ function getResultColor(result) {
7308
+ return `var(--color-result-${result})`;
7309
+ }
7310
+ function alpha(color, opacity) {
7311
+ const clampedOpacity = Math.max(0, Math.min(1, opacity));
7312
+ if (color.startsWith("#")) {
7313
+ const hex = color.slice(1);
7314
+ let r, g, b;
7315
+ if (hex.length === 3) {
7316
+ r = parseInt(hex[0] + hex[0], 16);
7317
+ g = parseInt(hex[1] + hex[1], 16);
7318
+ b = parseInt(hex[2] + hex[2], 16);
7319
+ } else if (hex.length === 6) {
7320
+ r = parseInt(hex.slice(0, 2), 16);
7321
+ g = parseInt(hex.slice(2, 4), 16);
7322
+ b = parseInt(hex.slice(4, 6), 16);
7323
+ } else {
7324
+ return color;
7325
+ }
7326
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
7327
+ }
7328
+ const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
7329
+ if (rgbMatch) {
7330
+ const [, r, g, b] = rgbMatch;
7331
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
7332
+ }
7333
+ const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
7334
+ if (rgbaMatch) {
7335
+ const [, r, g, b] = rgbaMatch;
7336
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
7337
+ }
7338
+ return color;
7339
+ }
7340
+ function getLuminance(color) {
7341
+ if (!color.startsWith("#")) return "dark";
7342
+ const hex = color.slice(1);
7343
+ let r, g, b;
7344
+ if (hex.length === 3) {
7345
+ r = parseInt(hex[0] + hex[0], 16);
7346
+ g = parseInt(hex[1] + hex[1], 16);
7347
+ b = parseInt(hex[2] + hex[2], 16);
7348
+ } else if (hex.length === 6) {
7349
+ r = parseInt(hex.slice(0, 2), 16);
7350
+ g = parseInt(hex.slice(2, 4), 16);
7351
+ b = parseInt(hex.slice(4, 6), 16);
7352
+ } else {
7353
+ return "dark";
7354
+ }
7355
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
7356
+ return luminance > 0.5 ? "light" : "dark";
7357
+ }
7358
+ function getContrastText(backgroundColor) {
7359
+ return getLuminance(backgroundColor) === "light" ? "#000000" : "#FFFFFF";
7360
+ }
7361
+ var t = getSemanticColors("dark");
7362
+ var TEMPO_PACING = {
7363
+ behindThresholdPct: 0.15,
7364
+ minPhaseDurationMs: 500
7365
+ };
7366
+ function getTempoPacingState(elapsedMs, targetMs) {
7367
+ if (!targetMs || targetMs < TEMPO_PACING.minPhaseDurationMs) return "none";
7368
+ const ratio = elapsedMs / targetMs;
7369
+ if (ratio > 1 + TEMPO_PACING.behindThresholdPct) return "behind";
7370
+ return "on-pace";
7371
+ }
7372
+ function getTempoFillPct(elapsedMs, targetMs) {
7373
+ if (!targetMs) return 100;
7374
+ return Math.min(100, elapsedMs / targetMs * 100);
7375
+ }
7376
+ var PHASE_ORDER = ["concentric", "hold", "eccentric"];
7377
+ var PHASE_CONFIG = {
7378
+ concentric: { label: "Con", color: t["status-success"], flex: 2 },
7379
+ hold: { label: "Hold", color: t["brand-primary"], flex: 1 },
7380
+ eccentric: { label: "Ecc", color: t["status-warning"], flex: 3 }
7381
+ };
7382
+ function TempoBar({
7383
+ activePhase,
7384
+ phaseElapsedMs,
7385
+ completed,
7386
+ target,
7387
+ className,
7388
+ ...props
7389
+ }) {
7390
+ return /* @__PURE__ */ jsxs(reactNative.View, { className, testID: "tempo-bar", ...props, children: [
7391
+ /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", marginBottom: 4 }, children: PHASE_ORDER.map((phase) => {
7392
+ const config = PHASE_CONFIG[phase];
7393
+ return /* @__PURE__ */ jsx(reactNative.View, { style: { flex: config.flex, alignItems: "center" }, children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 10, color: t["text-disabled"] }, children: config.label }) }, phase);
7394
+ }) }),
7395
+ /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row", gap: 2, height: 20 }, children: PHASE_ORDER.map((phase) => {
7396
+ const config = PHASE_CONFIG[phase];
7397
+ const isActive = activePhase === phase;
7398
+ const completedMs = completed?.[phase];
7399
+ const targetSec = target?.[phase];
7400
+ const targetMs = targetSec != null ? targetSec * 1e3 : null;
7401
+ return /* @__PURE__ */ jsx(
7402
+ reactNative.View,
7403
+ {
7404
+ style: {
7405
+ flex: config.flex,
7406
+ backgroundColor: alpha("#ffffff", 0.06),
7407
+ borderRadius: 4,
7408
+ overflow: "hidden"
7409
+ },
7410
+ children: isActive ? /* @__PURE__ */ jsx(
7411
+ ActiveSegment,
7412
+ {
7413
+ config,
7414
+ phaseElapsedMs,
7415
+ targetMs
7416
+ }
7417
+ ) : completedMs != null ? /* @__PURE__ */ jsx(CompletedSegment, { config, completedMs, targetMs }) : null
7418
+ },
7419
+ phase
7420
+ );
7421
+ }) })
7422
+ ] });
7423
+ }
7424
+ function ActiveSegment({
7425
+ config,
7426
+ phaseElapsedMs,
7427
+ targetMs
7428
+ }) {
7429
+ const pacing = getTempoPacingState(phaseElapsedMs, targetMs);
7430
+ const barColor = pacing === "behind" ? t["status-error"] : config.color;
7431
+ const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
7432
+ const label = targetMs ? `${formatDuration(phaseElapsedMs)} / ${formatDuration(targetMs)}` : formatDuration(phaseElapsedMs);
7433
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { height: "100%", position: "relative" }, testID: `tempo-segment-active-${config.label}`, children: [
7434
+ /* @__PURE__ */ jsx(
7435
+ reactNative.View,
7436
+ {
7437
+ style: {
7438
+ position: "absolute",
7439
+ left: 0,
7440
+ top: 0,
7441
+ bottom: 0,
7442
+ width: `${fillPct}%`,
7443
+ backgroundColor: alpha(barColor, 0.3),
7444
+ borderRadius: 4
7445
+ }
7446
+ }
7447
+ ),
7448
+ /* @__PURE__ */ jsx(
7449
+ reactNative.View,
7450
+ {
7451
+ style: {
7452
+ height: "100%",
7453
+ flexDirection: "row",
7454
+ alignItems: "center",
7455
+ justifyContent: "center"
7456
+ },
7457
+ children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 12, fontWeight: "700", color: barColor }, children: label })
7458
+ }
7459
+ )
7460
+ ] });
7461
+ }
7462
+ function CompletedSegment({
7463
+ config,
7464
+ completedMs,
7465
+ targetMs
7466
+ }) {
7467
+ const pacing = getTempoPacingState(completedMs, targetMs);
7468
+ const hitTarget = pacing !== "behind";
7469
+ const indicator = targetMs && targetMs >= TEMPO_PACING.minPhaseDurationMs ? hitTarget ? " \u2713" : " \u2717" : "";
7470
+ return /* @__PURE__ */ jsx(
7471
+ reactNative.View,
7472
+ {
7473
+ style: {
7474
+ height: "100%",
7475
+ flexDirection: "row",
7476
+ alignItems: "center",
7477
+ justifyContent: "center",
7478
+ backgroundColor: alpha(config.color, 0.15)
7479
+ },
7480
+ testID: `tempo-segment-completed-${config.label}`,
7481
+ children: /* @__PURE__ */ jsxs(reactNative.Text, { style: { fontSize: 12, fontWeight: "500", color: alpha(config.color, 0.7) }, children: [
7482
+ formatDuration(completedMs),
7483
+ indicator
7484
+ ] })
7485
+ }
7486
+ );
7487
+ }
7488
+ function formatDuration(ms) {
7489
+ const s = ms / 1e3;
7490
+ return s < 10 ? s.toFixed(1) : Math.round(s).toString();
7491
+ }
7492
+
7249
7493
  // src/components/custom/Workout/TempoDisplay.tsx
7250
7494
  var INTER = "Inter, sans-serif";
7251
7495
  var TEXT_TERTIARY = "#6B7280";
7496
+ var STATUS_ERROR = "#D14343";
7252
7497
  var phaseColors = {
7253
7498
  eccentric: "#FFB020",
7254
7499
  // status-warning (amber)
@@ -7292,11 +7537,68 @@ function TempoSeparator({ color, fontSize }) {
7292
7537
  }
7293
7538
  );
7294
7539
  }
7540
+ var LIVE_PHASES = [
7541
+ { key: "eccentric", color: phaseColors.eccentric },
7542
+ { key: "pauseBottom", color: phaseColors.pauseBottom },
7543
+ { key: "concentric", color: phaseColors.concentric },
7544
+ { key: "pauseTop", color: phaseColors.pauseTop }
7545
+ ];
7546
+ function LiveTempoCell({
7547
+ value,
7548
+ color,
7549
+ isActive,
7550
+ phaseElapsedMs,
7551
+ fontSize
7552
+ }) {
7553
+ if (!isActive) {
7554
+ return /* @__PURE__ */ jsx(TempoValue, { value, color: alpha(color, 0.35), fontSize });
7555
+ }
7556
+ const targetMs = value > 0 ? value * 1e3 : null;
7557
+ const barColor = getTempoPacingState(phaseElapsedMs, targetMs) === "behind" ? STATUS_ERROR : color;
7558
+ const fillPct = getTempoFillPct(phaseElapsedMs, targetMs);
7559
+ return /* @__PURE__ */ jsxs(reactNative.View, { style: { position: "relative" }, testID: "tempo-live-active", children: [
7560
+ /* @__PURE__ */ jsx(
7561
+ reactNative.View,
7562
+ {
7563
+ style: {
7564
+ position: "absolute",
7565
+ left: 0,
7566
+ top: 0,
7567
+ bottom: 0,
7568
+ width: `${fillPct}%`,
7569
+ backgroundColor: alpha(barColor, 0.3),
7570
+ borderRadius: 2
7571
+ }
7572
+ }
7573
+ ),
7574
+ /* @__PURE__ */ jsx(TempoValue, { value, color: barColor, fontSize })
7575
+ ] });
7576
+ }
7577
+ function LiveTempoRow({
7578
+ values,
7579
+ live,
7580
+ fontSize
7581
+ }) {
7582
+ return /* @__PURE__ */ jsx(jsxRuntime.Fragment, { children: LIVE_PHASES.map((phase, i) => /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row" }, children: [
7583
+ i > 0 && /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
7584
+ /* @__PURE__ */ jsx(
7585
+ LiveTempoCell,
7586
+ {
7587
+ value: values[i],
7588
+ color: phase.color,
7589
+ isActive: live.activePhase === phase.key,
7590
+ phaseElapsedMs: live.phaseElapsedMs,
7591
+ fontSize
7592
+ }
7593
+ )
7594
+ ] }, phase.key)) });
7595
+ }
7295
7596
  function TempoDisplay({
7296
7597
  tempo,
7297
7598
  size = "md",
7298
7599
  colored = true,
7299
7600
  showInfo = true,
7601
+ live,
7300
7602
  onPress,
7301
7603
  className,
7302
7604
  ...props
@@ -7343,7 +7645,14 @@ function TempoDisplay({
7343
7645
  children: "TEMPO"
7344
7646
  }
7345
7647
  ),
7346
- /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row" }, testID: "tempo-value", children: colored ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
7648
+ /* @__PURE__ */ jsx(reactNative.View, { style: { flexDirection: "row" }, testID: "tempo-value", children: live ? /* @__PURE__ */ jsx(
7649
+ LiveTempoRow,
7650
+ {
7651
+ values: [eccentric, pauseBottom, concentric, pauseTop],
7652
+ live,
7653
+ fontSize
7654
+ }
7655
+ ) : colored ? /* @__PURE__ */ jsxs(jsxRuntime.Fragment, { children: [
7347
7656
  /* @__PURE__ */ jsx(TempoValue, { value: eccentric, color: phaseColors.eccentric, fontSize }),
7348
7657
  /* @__PURE__ */ jsx(TempoSeparator, { color: phaseColors.dash, fontSize }),
7349
7658
  /* @__PURE__ */ jsx(TempoValue, { value: pauseBottom, color: phaseColors.pauseBottom, fontSize }),
@@ -7528,19 +7837,42 @@ function DeviationBar({
7528
7837
  }
7529
7838
  );
7530
7839
  }
7531
- var TRACK_BG = "rgba(255,255,255,0.06)";
7532
- var THRESHOLD_COLOR = "#FFFFFF";
7840
+ var TRACK_BG = "#333333";
7533
7841
  var LABEL_COLOR = "#6B7280";
7534
- function getZoneColor(level) {
7535
- if (level >= 0.7) return "#D14343";
7536
- if (level >= 0.4) return "#FFB020";
7537
- return "#14B8A6";
7538
- }
7539
- function clamp01(value) {
7842
+ var TARGET_LINE_COLOR = "rgba(33, 150, 243, 0.5)";
7843
+ var AT_TARGET_GLOW = "0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)";
7844
+ var ZONE = {
7845
+ building: "#14B8A6",
7846
+ // teal — below target ramp-up
7847
+ approaching: "#FFB020",
7848
+ // amber — nearing target
7849
+ target: "#FF7900",
7850
+ // brand — exactly at target
7851
+ over1: "#D14343",
7852
+ // red — mild over-target
7853
+ over2: "#A62626",
7854
+ // deep red — moderate over-target
7855
+ over3: "#7A1C1C"
7856
+ // darkest red — severe over-target
7857
+ };
7858
+ var BULGE_SIZE = { 1: 8, 2: 10, 3: 11 };
7859
+ function zoneForPct(pct) {
7860
+ if (pct >= 125) return { color: ZONE.over3, over: 3 };
7861
+ if (pct >= 115) return { color: ZONE.over2, over: 2 };
7862
+ if (pct > 100) return { color: ZONE.over1, over: 1 };
7863
+ if (pct === 100) return { color: ZONE.target, over: 0 };
7864
+ if (pct >= 75) return { color: ZONE.approaching, over: 0 };
7865
+ return { color: ZONE.building, over: 0 };
7866
+ }
7867
+ function clampFill(value) {
7540
7868
  if (value < 0) return 0;
7541
7869
  if (value > 1) return 1;
7542
7870
  return value;
7543
7871
  }
7872
+ function isAtTarget(pct, threshold) {
7873
+ if (threshold == null) return false;
7874
+ return Math.abs(pct - Math.round(threshold * 100)) <= 5;
7875
+ }
7544
7876
  function IntensityBar({
7545
7877
  level,
7546
7878
  threshold,
@@ -7552,62 +7884,62 @@ function IntensityBar({
7552
7884
  ...props
7553
7885
  }) {
7554
7886
  const isVertical = orientation === "vertical";
7555
- const clampedLevel = clamp01(level);
7556
- const percentage = Math.round(clampedLevel * 100);
7557
- const zoneColor = getZoneColor(clampedLevel);
7558
- const [fillAnim] = React24.useState(() => new reactNative.Animated.Value(clampedLevel));
7887
+ const fillLevel = clampFill(level);
7888
+ const pct = Math.round(Math.max(0, level) * 100);
7889
+ const zone = zoneForPct(pct);
7890
+ const atTarget = isAtTarget(pct, threshold);
7891
+ const [fillAnim] = React24.useState(() => new reactNative.Animated.Value(fillLevel));
7559
7892
  React24.useEffect(() => {
7560
7893
  const animation = reactNative.Animated.timing(fillAnim, {
7561
- toValue: clampedLevel,
7894
+ toValue: fillLevel,
7562
7895
  duration: 250,
7563
7896
  useNativeDriver: false
7564
7897
  });
7565
7898
  animation.start();
7566
7899
  return () => animation.stop();
7567
- }, [clampedLevel, fillAnim]);
7900
+ }, [fillLevel, fillAnim]);
7568
7901
  const fillSizePercent = fillAnim.interpolate({
7569
7902
  inputRange: [0, 1],
7570
7903
  outputRange: ["0%", "100%"]
7571
7904
  });
7572
7905
  const trackStyle = isVertical ? { width: thickness, height: size } : { width: size, height: thickness };
7573
- const fillStyle = isVertical ? {
7574
- position: "absolute",
7575
- bottom: 0,
7576
- left: 0,
7577
- right: 0,
7578
- height: fillSizePercent
7579
- } : {
7906
+ const fillStyle = isVertical ? { position: "absolute", bottom: 0, left: 0, right: 0, height: fillSizePercent } : { position: "absolute", top: 0, bottom: 0, left: 0, width: fillSizePercent };
7907
+ const bulgeSize = zone.over === 0 ? 0 : BULGE_SIZE[zone.over];
7908
+ const bulgeStyle = {
7580
7909
  position: "absolute",
7581
- top: 0,
7582
- bottom: 0,
7583
- left: 0,
7584
- width: fillSizePercent
7910
+ width: bulgeSize,
7911
+ height: bulgeSize,
7912
+ borderRadius: bulgeSize / 2,
7913
+ backgroundColor: zone.color,
7914
+ zIndex: 1,
7915
+ ...isVertical ? { top: 0, left: "50%", transform: [{ translateX: -bulgeSize / 2 }] } : { right: 0, top: "50%", transform: [{ translateY: -bulgeSize / 2 }] }
7585
7916
  };
7586
- const thresholdLineStyle = threshold != null ? isVertical ? {
7917
+ const targetPct = `${clampFill(threshold ?? 0) * 100}%`;
7918
+ const targetLineStyle = isVertical ? {
7587
7919
  position: "absolute",
7588
- bottom: `${clamp01(threshold) * 100}%`,
7589
- left: -2,
7590
- right: -2,
7591
- height: 1,
7592
- backgroundColor: THRESHOLD_COLOR,
7920
+ bottom: targetPct,
7921
+ left: -3,
7922
+ right: -3,
7923
+ height: 1.5,
7924
+ backgroundColor: TARGET_LINE_COLOR,
7593
7925
  zIndex: 4
7594
7926
  } : {
7595
7927
  position: "absolute",
7596
- left: `${clamp01(threshold) * 100}%`,
7597
- top: -2,
7598
- bottom: -2,
7599
- width: 1,
7600
- backgroundColor: THRESHOLD_COLOR,
7928
+ left: targetPct,
7929
+ top: -3,
7930
+ bottom: -3,
7931
+ width: 1.5,
7932
+ backgroundColor: TARGET_LINE_COLOR,
7601
7933
  zIndex: 4
7602
- } : void 0;
7934
+ };
7603
7935
  return /* @__PURE__ */ jsxs(
7604
7936
  reactNative.View,
7605
7937
  {
7606
7938
  className,
7607
7939
  style: { alignItems: "center" },
7608
7940
  accessibilityRole: "progressbar",
7609
- accessibilityValue: { min: 0, max: 100, now: percentage },
7610
- accessibilityLabel: `Intensity level: ${percentage}%`,
7941
+ accessibilityValue: { min: 0, max: 100, now: pct },
7942
+ accessibilityLabel: `Intensity level: ${pct}%`,
7611
7943
  testID: "intensity-bar",
7612
7944
  ...props,
7613
7945
  children: [
@@ -7629,13 +7961,32 @@ function IntensityBar({
7629
7961
  style: {
7630
7962
  ...fillStyle,
7631
7963
  borderRadius: 3,
7632
- backgroundColor: zoneColor,
7633
- zIndex: 2
7964
+ backgroundColor: zone.color,
7965
+ zIndex: 2,
7966
+ ...atTarget ? { boxShadow: AT_TARGET_GLOW } : null
7634
7967
  },
7635
7968
  testID: "intensity-fill"
7636
7969
  }
7637
7970
  ),
7638
- threshold != null && /* @__PURE__ */ jsx(reactNative.View, { style: thresholdLineStyle, testID: "intensity-threshold" })
7971
+ zone.over !== 0 && /* @__PURE__ */ jsx(reactNative.View, { style: bulgeStyle, testID: "intensity-bulge" }),
7972
+ threshold != null && /* @__PURE__ */ jsx(reactNative.View, { style: targetLineStyle, testID: "intensity-target" })
7973
+ ]
7974
+ }
7975
+ ),
7976
+ /* @__PURE__ */ jsxs(
7977
+ reactNative.Text,
7978
+ {
7979
+ style: {
7980
+ fontSize: 8,
7981
+ color: LABEL_COLOR,
7982
+ fontFamily: '"Nunito Sans", sans-serif',
7983
+ marginTop: 6
7984
+ },
7985
+ accessibilityElementsHidden: true,
7986
+ testID: "intensity-label",
7987
+ children: [
7988
+ pct,
7989
+ "%"
7639
7990
  ]
7640
7991
  }
7641
7992
  ),
@@ -7647,7 +7998,7 @@ function IntensityBar({
7647
7998
  fontWeight: "500",
7648
7999
  color: LABEL_COLOR,
7649
8000
  fontFamily: "Inter, sans-serif",
7650
- marginTop: 6
8001
+ marginTop: 2
7651
8002
  },
7652
8003
  accessibilityElementsHidden: true,
7653
8004
  testID: "intensity-threshold-label",
@@ -7811,6 +8162,13 @@ function WorkoutPill({
7811
8162
  return pill;
7812
8163
  }
7813
8164
  var VEL_COLORS = WORKOUT_TOKENS.scale;
8165
+ var zoneIdToScaleToken = {
8166
+ speed: "green",
8167
+ power: "yellow",
8168
+ strengthSpeed: "orange",
8169
+ maximalStrength: "red",
8170
+ grinding: "red"
8171
+ };
7814
8172
  function getVelocityZoneColor(velocity) {
7815
8173
  if (velocity >= 1) return "vel-green";
7816
8174
  if (velocity >= 0.75) return "vel-yellow";
@@ -7823,47 +8181,62 @@ function getVelocityZoneName(velocity) {
7823
8181
  if (velocity >= 0.5) return "Strength-Speed";
7824
8182
  return "Strength";
7825
8183
  }
8184
+ var zoneHexMap = {
8185
+ "vel-green": VEL_COLORS.green,
8186
+ "vel-yellow": VEL_COLORS.yellow,
8187
+ "vel-orange": VEL_COLORS.orange,
8188
+ "vel-red": VEL_COLORS.red
8189
+ };
7826
8190
  function calculateVelocityLoss(velocities) {
7827
8191
  if (velocities.length < 2) return 0;
7828
- const first = velocities[0];
8192
+ const best = Math.max(...velocities);
8193
+ if (best <= 0) return 0;
7829
8194
  const last = velocities[velocities.length - 1];
7830
- if (first === 0) return 0;
7831
- return Math.round((first - last) / first * 100);
8195
+ return Math.max(0, Math.round((best - last) / best * 100));
7832
8196
  }
7833
8197
  function calculateMeanVelocity(velocities) {
7834
8198
  if (velocities.length === 0) return 0;
7835
8199
  const sum = velocities.reduce((acc, v) => acc + v, 0);
7836
8200
  return sum / velocities.length;
7837
8201
  }
7838
- function getBarColor(zone) {
7839
- switch (zone) {
7840
- case "vel-green":
7841
- return VEL_COLORS.green;
7842
- case "vel-yellow":
7843
- return VEL_COLORS.yellow;
7844
- case "vel-orange":
7845
- return VEL_COLORS.orange;
7846
- case "vel-red":
7847
- return VEL_COLORS.red;
7848
- default:
7849
- return VEL_COLORS.green;
8202
+ function classifyBand(velocity, bands) {
8203
+ for (const band of bands) {
8204
+ if (band.max === null || velocity < band.max) return band;
7850
8205
  }
8206
+ return bands[bands.length - 1];
8207
+ }
8208
+ function bandColor(band) {
8209
+ const token = zoneIdToScaleToken[band.id];
8210
+ return token ? VEL_COLORS[token] : VEL_COLORS.green;
7851
8211
  }
7852
- var zoneHexMap = {
7853
- "vel-green": VEL_COLORS.green,
7854
- "vel-yellow": VEL_COLORS.yellow,
7855
- "vel-orange": VEL_COLORS.orange,
7856
- "vel-red": VEL_COLORS.red
7857
- };
7858
8212
  function getLossStyle(loss) {
7859
8213
  if (loss > 25) return { color: VEL_COLORS.red };
7860
8214
  if (loss > 20) return { color: VEL_COLORS.orange };
7861
8215
  return null;
7862
8216
  }
8217
+ function getReducedMotionPreference() {
8218
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
8219
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
8220
+ }
8221
+ function usePrefersReducedMotion() {
8222
+ const [reduced, setReduced] = React24.useState(getReducedMotionPreference);
8223
+ React24.useEffect(() => {
8224
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
8225
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
8226
+ const handler = () => setReduced(mq.matches);
8227
+ handler();
8228
+ mq.addEventListener?.("change", handler);
8229
+ return () => mq.removeEventListener?.("change", handler);
8230
+ }, []);
8231
+ return reduced;
8232
+ }
7863
8233
  var ANIMATION_DURATION = 400;
7864
8234
  var ANIMATION_EASING = reactNative.Easing.bezier(0.22, 1, 0.36, 1);
8235
+ var POP_EASING = reactNative.Easing.bezier(0.34, 1.56, 0.64, 1);
7865
8236
  function VelocityStrip({
7866
8237
  velocities,
8238
+ zones,
8239
+ liveRepIndex,
7867
8240
  expanded = false,
7868
8241
  onToggle,
7869
8242
  onRepPress,
@@ -7875,10 +8248,48 @@ function VelocityStrip({
7875
8248
  const maxVelocity = Math.max(...velocities, 0);
7876
8249
  const meanVelocity = calculateMeanVelocity(velocities);
7877
8250
  const loss = calculateVelocityLoss(velocities);
7878
- const meanZone = getVelocityZoneName(meanVelocity);
8251
+ const hasZones = zones != null && zones.length > 0;
8252
+ const barColorFor = (v) => hasZones ? bandColor(classifyBand(v, zones)) : zoneHexMap[getVelocityZoneColor(v)];
8253
+ const meanZone = hasZones ? classifyBand(meanVelocity, zones)?.label ?? "" : getVelocityZoneName(meanVelocity);
8254
+ const prefersReducedMotion = usePrefersReducedMotion();
7879
8255
  const [heightAnim] = React24.useState(() => new reactNative.Animated.Value(expanded ? 60 : 3));
7880
8256
  const [labelOpacity] = React24.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
7881
8257
  const [infoOpacity] = React24.useState(() => new reactNative.Animated.Value(expanded ? 1 : 0));
8258
+ const [liveScale] = React24.useState(() => new reactNative.Animated.Value(1));
8259
+ const liveVelocity = liveRepIndex != null ? velocities[liveRepIndex] : void 0;
8260
+ const isNewPeak = liveVelocity != null && maxVelocity > 0 && liveVelocity === maxVelocity;
8261
+ React24.useEffect(() => {
8262
+ if (variant === "mini" || liveRepIndex == null || liveVelocity == null) return;
8263
+ if (prefersReducedMotion) {
8264
+ liveScale.setValue(1);
8265
+ return;
8266
+ }
8267
+ if (isNewPeak) {
8268
+ liveScale.setValue(1);
8269
+ reactNative.Animated.sequence([
8270
+ reactNative.Animated.timing(liveScale, {
8271
+ toValue: 1.25,
8272
+ duration: 150,
8273
+ easing: reactNative.Easing.out(reactNative.Easing.quad),
8274
+ useNativeDriver: true
8275
+ }),
8276
+ reactNative.Animated.spring(liveScale, {
8277
+ toValue: 1,
8278
+ friction: 3,
8279
+ tension: 140,
8280
+ useNativeDriver: true
8281
+ })
8282
+ ]).start();
8283
+ } else {
8284
+ liveScale.setValue(0.8);
8285
+ reactNative.Animated.timing(liveScale, {
8286
+ toValue: 1,
8287
+ duration: 300,
8288
+ easing: POP_EASING,
8289
+ useNativeDriver: true
8290
+ }).start();
8291
+ }
8292
+ }, [variant, liveRepIndex, liveVelocity, isNewPeak, prefersReducedMotion, liveScale]);
7882
8293
  React24.useEffect(() => {
7883
8294
  if (variant === "mini") return;
7884
8295
  reactNative.Animated.parallel([
@@ -7918,7 +8329,7 @@ function VelocityStrip({
7918
8329
  {
7919
8330
  style: {
7920
8331
  flex: 1,
7921
- backgroundColor: getBarColor(getVelocityZoneColor(v)),
8332
+ backgroundColor: barColorFor(v),
7922
8333
  borderRadius: 1,
7923
8334
  minWidth: 4,
7924
8335
  height: "100%"
@@ -7937,7 +8348,7 @@ function VelocityStrip({
7937
8348
  const stripContent = /* @__PURE__ */ jsxs(
7938
8349
  reactNative.Animated.View,
7939
8350
  {
7940
- className,
8351
+ className: [className, expanded ? "bg-surface-raised" : "bg-transparent"].filter(Boolean).join(" "),
7941
8352
  style: [
7942
8353
  {
7943
8354
  height: heightAnim,
@@ -7948,7 +8359,6 @@ function VelocityStrip({
7948
8359
  paddingBottom: expanded ? 24 : 0,
7949
8360
  paddingHorizontal: expanded ? 6 : 0,
7950
8361
  paddingVertical: expanded ? void 0 : 8,
7951
- backgroundColor: expanded ? "var(--color-surface-raised)" : "transparent",
7952
8362
  overflow: expanded ? "visible" : "hidden"
7953
8363
  }
7954
8364
  ],
@@ -7962,9 +8372,36 @@ function VelocityStrip({
7962
8372
  {
7963
8373
  style: { flexDirection: "row", flex: 1, gap: 2, alignItems: "flex-end" },
7964
8374
  children: velocities.map((v, i) => {
7965
- const zone = getVelocityZoneColor(v);
8375
+ const barBackground = barColorFor(v);
7966
8376
  const barHeightPct = maxVelocity > 0 ? Math.round(v / (maxVelocity * 1.15) * 100) : 0;
7967
- const bar = /* @__PURE__ */ jsxs(
8377
+ const isLive = liveRepIndex === i;
8378
+ const liveLabelSuffix = isLive ? isNewPeak ? ", latest rep, new set peak" : ", latest rep" : "";
8379
+ const barStyle = expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: barBackground } : {
8380
+ minHeight: "100%",
8381
+ borderTopLeftRadius: 2,
8382
+ borderTopRightRadius: 2,
8383
+ borderBottomLeftRadius: 0,
8384
+ borderBottomRightRadius: 0,
8385
+ backgroundColor: barBackground
8386
+ };
8387
+ const barInner = isLive ? /* @__PURE__ */ jsx(
8388
+ reactNative.Animated.View,
8389
+ {
8390
+ style: [barStyle, { transform: [{ scale: liveScale }] }],
8391
+ testID: `velocity-bar-${i}`,
8392
+ accessibilityRole: "image",
8393
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second${liveLabelSuffix}`
8394
+ }
8395
+ ) : /* @__PURE__ */ jsx(
8396
+ reactNative.View,
8397
+ {
8398
+ style: barStyle,
8399
+ testID: `velocity-bar-${i}`,
8400
+ accessibilityRole: "image",
8401
+ accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
8402
+ }
8403
+ );
8404
+ const bar = /* @__PURE__ */ jsxs(
7968
8405
  reactNative.View,
7969
8406
  {
7970
8407
  style: { flex: 1, height: "100%", justifyContent: "flex-end", position: "relative" },
@@ -7972,27 +8409,13 @@ function VelocityStrip({
7972
8409
  expanded && /* @__PURE__ */ jsx(reactNative.Animated.View, { style: { opacity: labelOpacity, alignItems: "center", position: "absolute", top: -13, left: 0, right: 0 }, accessibilityElementsHidden: true, children: /* @__PURE__ */ jsx(
7973
8410
  reactNative.Text,
7974
8411
  {
7975
- style: { fontSize: 8, fontWeight: "600", color: "var(--color-text-secondary)" },
8412
+ className: "text-text-secondary",
8413
+ style: { fontSize: 8, fontWeight: "600" },
7976
8414
  testID: `velocity-label-${i}`,
7977
8415
  children: formatVelocity(v)
7978
8416
  }
7979
8417
  ) }),
7980
- /* @__PURE__ */ jsx(
7981
- reactNative.View,
7982
- {
7983
- style: expanded ? { height: `${barHeightPct}%`, minHeight: 2, borderTopLeftRadius: 2, borderTopRightRadius: 2, backgroundColor: zoneHexMap[zone] } : {
7984
- minHeight: "100%",
7985
- borderTopLeftRadius: 2,
7986
- borderTopRightRadius: 2,
7987
- borderBottomLeftRadius: 0,
7988
- borderBottomRightRadius: 0,
7989
- backgroundColor: zoneHexMap[zone]
7990
- },
7991
- testID: `velocity-bar-${i}`,
7992
- accessibilityRole: "image",
7993
- accessibilityLabel: `Rep ${i + 1}: ${formatVelocity(v)} meters per second`
7994
- }
7995
- )
8418
+ barInner
7996
8419
  ]
7997
8420
  },
7998
8421
  i
@@ -8032,9 +8455,9 @@ function VelocityStrip({
8032
8455
  /* @__PURE__ */ jsxs(
8033
8456
  reactNative.Text,
8034
8457
  {
8458
+ className: "text-text-secondary",
8035
8459
  style: {
8036
8460
  fontSize: 10,
8037
- color: "var(--color-text-secondary)",
8038
8461
  fontFamily: "Inter, sans-serif"
8039
8462
  },
8040
8463
  children: [
@@ -8050,9 +8473,9 @@ function VelocityStrip({
8050
8473
  /* @__PURE__ */ jsxs(
8051
8474
  reactNative.Text,
8052
8475
  {
8476
+ className: "text-text-secondary",
8053
8477
  style: {
8054
8478
  fontSize: 10,
8055
- color: "var(--color-text-secondary)",
8056
8479
  fontFamily: "Inter, sans-serif",
8057
8480
  ...getLossStyle(loss)
8058
8481
  },
@@ -8102,16 +8525,14 @@ function MuscleGroupChip({
8102
8525
  const content = /* @__PURE__ */ jsxs(
8103
8526
  reactNative.View,
8104
8527
  {
8105
- className,
8528
+ className: ["bg-surface-raised border-border", className].filter(Boolean).join(" "),
8106
8529
  style: {
8107
8530
  flexDirection: "row",
8108
8531
  alignItems: "center",
8109
8532
  borderRadius: 9999,
8110
8533
  paddingHorizontal: 9,
8111
8534
  paddingVertical: 3,
8112
- backgroundColor: "var(--color-surface-raised)",
8113
- borderWidth: 1,
8114
- borderColor: "var(--color-border-default)"
8535
+ borderWidth: 1
8115
8536
  },
8116
8537
  accessibilityLabel: onPress ? void 0 : `${name}, volume status: ${statusLabel}`,
8117
8538
  testID: "muscle-group-chip",
@@ -8135,11 +8556,11 @@ function MuscleGroupChip({
8135
8556
  /* @__PURE__ */ jsx(
8136
8557
  reactNative.Text,
8137
8558
  {
8559
+ className: "text-text-secondary",
8138
8560
  style: {
8139
8561
  fontFamily: "Inter, sans-serif",
8140
8562
  fontWeight: "500",
8141
- fontSize: 11,
8142
- color: "var(--color-text-secondary)"
8563
+ fontSize: 11
8143
8564
  },
8144
8565
  accessibilityElementsHidden: true,
8145
8566
  children: name
@@ -8327,6 +8748,8 @@ function SetRow({
8327
8748
  rpe,
8328
8749
  unit,
8329
8750
  velocities,
8751
+ velocityZones,
8752
+ velocityLiveRepIndex,
8330
8753
  velocityExpanded,
8331
8754
  onVelocityToggle,
8332
8755
  setType,
@@ -8389,11 +8812,11 @@ function SetRow({
8389
8812
  ) : /* @__PURE__ */ jsx(
8390
8813
  reactNative.Text,
8391
8814
  {
8815
+ className: "text-text-secondary",
8392
8816
  style: {
8393
8817
  fontSize: 13,
8394
8818
  fontWeight: "600",
8395
- fontFamily: "Inter, sans-serif",
8396
- color: "var(--color-text-secondary)"
8819
+ fontFamily: "Inter, sans-serif"
8397
8820
  },
8398
8821
  children: setNumber
8399
8822
  }
@@ -8408,10 +8831,10 @@ function SetRow({
8408
8831
  children: /* @__PURE__ */ jsx(
8409
8832
  reactNative.Text,
8410
8833
  {
8834
+ className: "text-text-secondary",
8411
8835
  style: {
8412
8836
  fontSize: 12,
8413
- fontFamily: "Inter, sans-serif",
8414
- color: "var(--color-text-secondary)"
8837
+ fontFamily: "Inter, sans-serif"
8415
8838
  },
8416
8839
  children: previous ? `${previous.reps} x ${roundWeight(previous.weight)}` : "\u2014"
8417
8840
  }
@@ -8426,11 +8849,11 @@ function SetRow({
8426
8849
  children: isActive && reps == null && targets ? /* @__PURE__ */ jsx(
8427
8850
  reactNative.Text,
8428
8851
  {
8852
+ className: "text-text-tertiary",
8429
8853
  style: {
8430
8854
  fontSize: 13,
8431
8855
  fontStyle: "italic",
8432
- fontFamily: "Inter, sans-serif",
8433
- color: "var(--color-text-tertiary)"
8856
+ fontFamily: "Inter, sans-serif"
8434
8857
  },
8435
8858
  testID: "set-row-target-reps",
8436
8859
  children: targets.reps
@@ -8438,11 +8861,11 @@ function SetRow({
8438
8861
  ) : /* @__PURE__ */ jsx(
8439
8862
  reactNative.Text,
8440
8863
  {
8864
+ className: reps != null ? "text-text-primary" : "text-text-tertiary",
8441
8865
  style: {
8442
8866
  fontSize: 14,
8443
8867
  fontWeight: "600",
8444
- fontFamily: "Inter, sans-serif",
8445
- color: reps != null ? "var(--color-text-primary)" : "var(--color-text-tertiary)"
8868
+ fontFamily: "Inter, sans-serif"
8446
8869
  },
8447
8870
  children: reps != null ? reps : "\u2014"
8448
8871
  }
@@ -8457,11 +8880,11 @@ function SetRow({
8457
8880
  children: isActive && weight == null && targets ? /* @__PURE__ */ jsx(
8458
8881
  reactNative.Text,
8459
8882
  {
8883
+ className: "text-text-tertiary",
8460
8884
  style: {
8461
8885
  fontSize: 13,
8462
8886
  fontStyle: "italic",
8463
- fontFamily: "Inter, sans-serif",
8464
- color: "var(--color-text-tertiary)"
8887
+ fontFamily: "Inter, sans-serif"
8465
8888
  },
8466
8889
  testID: "set-row-target-weight",
8467
8890
  children: roundWeight(targets.weight)
@@ -8469,11 +8892,11 @@ function SetRow({
8469
8892
  ) : /* @__PURE__ */ jsx(
8470
8893
  reactNative.Text,
8471
8894
  {
8895
+ className: weight != null ? "text-text-primary" : "text-text-tertiary",
8472
8896
  style: {
8473
8897
  fontSize: 14,
8474
8898
  fontWeight: "600",
8475
- fontFamily: "Inter, sans-serif",
8476
- color: weight != null ? "var(--color-text-primary)" : "var(--color-text-tertiary)"
8899
+ fontFamily: "Inter, sans-serif"
8477
8900
  },
8478
8901
  children: weight != null ? roundWeight(weight) : "\u2014"
8479
8902
  }
@@ -8488,11 +8911,14 @@ function SetRow({
8488
8911
  children: /* @__PURE__ */ jsx(
8489
8912
  reactNative.Text,
8490
8913
  {
8914
+ className: rpe != null ? void 0 : "text-text-tertiary",
8491
8915
  style: {
8492
8916
  fontSize: 13,
8493
8917
  fontWeight: "600",
8494
8918
  fontFamily: "Inter, sans-serif",
8495
- color: rpe != null ? rpeColor(rpe) : "var(--color-text-tertiary)"
8919
+ // rpeColor returns a concrete hex (native-safe); only the tertiary
8920
+ // fallback needs the className token.
8921
+ color: rpe != null ? rpeColor(rpe) : void 0
8496
8922
  },
8497
8923
  children: rpe != null ? roundRpe(rpe) : "\u2014"
8498
8924
  }
@@ -8526,6 +8952,8 @@ function SetRow({
8526
8952
  VelocityStrip,
8527
8953
  {
8528
8954
  velocities,
8955
+ zones: velocityZones,
8956
+ liveRepIndex: velocityLiveRepIndex,
8529
8957
  expanded: velocityExpanded,
8530
8958
  onToggle: onVelocityToggle,
8531
8959
  variant: onVelocityToggle ? "full" : "mini"
@@ -8538,18 +8966,14 @@ function SetRow({
8538
8966
  );
8539
8967
  }
8540
8968
  var BRAND_PRIMARY2 = "#FF7900";
8541
- var TEXT_PRIMARY = "var(--color-text-primary)";
8542
- var TEXT_TERTIARY2 = "var(--color-text-tertiary)";
8969
+ var INPUT_CLASSNAME = "bg-surface-raised border-border-strong text-text-primary";
8543
8970
  var inputStyle = {
8544
8971
  fontSize: 14,
8545
8972
  fontWeight: "600",
8546
8973
  fontFamily: "Inter, sans-serif",
8547
- backgroundColor: "var(--color-surface-raised)",
8548
8974
  borderWidth: 1,
8549
- borderColor: "var(--color-border-strong)",
8550
8975
  borderRadius: 6,
8551
8976
  textAlign: "center",
8552
- color: TEXT_PRIMARY,
8553
8977
  paddingVertical: 5,
8554
8978
  paddingHorizontal: 2
8555
8979
  };
@@ -8571,11 +8995,11 @@ function InputBar({
8571
8995
  return /* @__PURE__ */ jsxs(
8572
8996
  reactNative.View,
8573
8997
  {
8998
+ className: "bg-surface-elevated",
8574
8999
  style: {
8575
9000
  width: "100%",
8576
- backgroundColor: "var(--color-surface-elevated)",
8577
9001
  borderTopWidth: 1,
8578
- borderTopColor: "var(--color-border-default)",
9002
+ borderTopColor: resolveColor("border-default"),
8579
9003
  paddingTop: 10,
8580
9004
  paddingHorizontal: 16,
8581
9005
  paddingBottom: 12,
@@ -8590,11 +9014,11 @@ function InputBar({
8590
9014
  /* @__PURE__ */ jsx(
8591
9015
  reactNative.Text,
8592
9016
  {
9017
+ className: "text-text-primary",
8593
9018
  style: {
8594
9019
  fontSize: 12,
8595
9020
  fontWeight: "700",
8596
- fontFamily: '"Space Grotesk", sans-serif',
8597
- color: TEXT_PRIMARY
9021
+ fontFamily: '"Space Grotesk", sans-serif'
8598
9022
  },
8599
9023
  testID: "input-bar-exercise-name",
8600
9024
  numberOfLines: 1,
@@ -8604,10 +9028,10 @@ function InputBar({
8604
9028
  /* @__PURE__ */ jsx(
8605
9029
  reactNative.Text,
8606
9030
  {
9031
+ className: "text-text-tertiary",
8607
9032
  style: {
8608
9033
  fontSize: 10,
8609
- fontFamily: "Inter, sans-serif",
8610
- color: TEXT_TERTIARY2
9034
+ fontFamily: "Inter, sans-serif"
8611
9035
  },
8612
9036
  testID: "input-bar-set-info",
8613
9037
  children: setLabel
@@ -8620,6 +9044,7 @@ function InputBar({
8620
9044
  {
8621
9045
  value: reps,
8622
9046
  onChangeText: onRepsChange,
9047
+ className: INPUT_CLASSNAME,
8623
9048
  style: { ...inputStyle, width: 36 },
8624
9049
  accessibilityLabel: "Reps",
8625
9050
  testID: "input-bar-reps",
@@ -8629,10 +9054,10 @@ function InputBar({
8629
9054
  /* @__PURE__ */ jsx(
8630
9055
  reactNative.Text,
8631
9056
  {
9057
+ className: "text-text-tertiary",
8632
9058
  style: {
8633
9059
  fontSize: 12,
8634
- fontFamily: "Inter, sans-serif",
8635
- color: TEXT_TERTIARY2
9060
+ fontFamily: "Inter, sans-serif"
8636
9061
  },
8637
9062
  children: "\xD7"
8638
9063
  }
@@ -8642,6 +9067,7 @@ function InputBar({
8642
9067
  {
8643
9068
  value: weight,
8644
9069
  onChangeText: onWeightChange,
9070
+ className: INPUT_CLASSNAME,
8645
9071
  style: { ...inputStyle, width: 52 },
8646
9072
  accessibilityLabel: `Weight in ${unit}`,
8647
9073
  testID: "input-bar-weight",
@@ -8651,11 +9077,11 @@ function InputBar({
8651
9077
  /* @__PURE__ */ jsx(
8652
9078
  reactNative.Text,
8653
9079
  {
9080
+ className: "text-text-tertiary",
8654
9081
  style: {
8655
9082
  fontSize: 12,
8656
9083
  fontWeight: "500",
8657
- fontFamily: "Inter, sans-serif",
8658
- color: TEXT_TERTIARY2
9084
+ fontFamily: "Inter, sans-serif"
8659
9085
  },
8660
9086
  testID: "input-bar-unit",
8661
9087
  children: unit
@@ -8702,7 +9128,8 @@ function RestTimer({
8702
9128
  onSkip,
8703
9129
  onAddTime,
8704
9130
  nextSetInfo,
8705
- visible
9131
+ visible,
9132
+ displayOnly = false
8706
9133
  }) {
8707
9134
  if (!visible) return null;
8708
9135
  const remainingMs = Math.max(0, totalSeconds * 1e3 - elapsedMs);
@@ -8715,11 +9142,11 @@ function RestTimer({
8715
9142
  return /* @__PURE__ */ jsxs(
8716
9143
  reactNative.View,
8717
9144
  {
9145
+ className: "bg-surface-raised",
8718
9146
  style: {
8719
9147
  width: "100%",
8720
- backgroundColor: "var(--color-surface-raised)",
8721
9148
  borderTopWidth: 1,
8722
- borderTopColor: "var(--color-border-default)",
9149
+ borderTopColor: resolveColor("border-default"),
8723
9150
  paddingVertical: 12,
8724
9151
  paddingHorizontal: 16
8725
9152
  },
@@ -8741,13 +9168,13 @@ function RestTimer({
8741
9168
  /* @__PURE__ */ jsx(
8742
9169
  reactNative.Text,
8743
9170
  {
9171
+ className: "text-text-secondary",
8744
9172
  style: {
8745
9173
  fontSize: 11,
8746
9174
  fontFamily: "Inter, sans-serif",
8747
9175
  fontWeight: "600",
8748
9176
  textTransform: "uppercase",
8749
- letterSpacing: 1,
8750
- color: "var(--color-text-secondary)"
9177
+ letterSpacing: 1
8751
9178
  },
8752
9179
  testID: "rest-timer-label",
8753
9180
  children: "REST"
@@ -8756,10 +9183,10 @@ function RestTimer({
8756
9183
  nextSetInfo != null && /* @__PURE__ */ jsx(
8757
9184
  reactNative.Text,
8758
9185
  {
9186
+ className: "text-text-tertiary",
8759
9187
  style: {
8760
9188
  fontSize: 11,
8761
9189
  fontFamily: "Inter, sans-serif",
8762
- color: "var(--color-text-tertiary)",
8763
9190
  marginTop: 2
8764
9191
  },
8765
9192
  testID: "rest-timer-next-set",
@@ -8770,11 +9197,11 @@ function RestTimer({
8770
9197
  /* @__PURE__ */ jsx(
8771
9198
  reactNative.Text,
8772
9199
  {
9200
+ className: "text-text-primary",
8773
9201
  style: {
8774
9202
  fontSize: 28,
8775
9203
  fontFamily: '"Space Grotesk", sans-serif',
8776
9204
  fontWeight: "700",
8777
- color: "var(--color-text-primary)",
8778
9205
  fontVariant: ["tabular-nums"],
8779
9206
  letterSpacing: -0.5
8780
9207
  },
@@ -8788,9 +9215,9 @@ function RestTimer({
8788
9215
  /* @__PURE__ */ jsx(
8789
9216
  reactNative.View,
8790
9217
  {
9218
+ className: "bg-border",
8791
9219
  style: {
8792
9220
  height: 3,
8793
- backgroundColor: "var(--color-border-default)",
8794
9221
  borderRadius: 2,
8795
9222
  marginBottom: 12
8796
9223
  },
@@ -8809,7 +9236,7 @@ function RestTimer({
8809
9236
  )
8810
9237
  }
8811
9238
  ),
8812
- /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
9239
+ !displayOnly && /* @__PURE__ */ jsxs(reactNative.View, { style: { flexDirection: "row", gap: 8 }, children: [
8813
9240
  /* @__PURE__ */ jsx(
8814
9241
  reactNative.Pressable,
8815
9242
  {
@@ -8826,11 +9253,11 @@ function RestTimer({
8826
9253
  children: /* @__PURE__ */ jsx(
8827
9254
  reactNative.Text,
8828
9255
  {
9256
+ className: "text-text-secondary",
8829
9257
  style: {
8830
9258
  fontSize: 11,
8831
9259
  fontFamily: "Inter, sans-serif",
8832
- fontWeight: "600",
8833
- color: "var(--color-text-secondary)"
9260
+ fontWeight: "600"
8834
9261
  },
8835
9262
  children: "+30s"
8836
9263
  }
@@ -8869,19 +9296,6 @@ function RestTimer({
8869
9296
  }
8870
9297
  );
8871
9298
  }
8872
- var COLORS = {
8873
- // Theme-aware tokens: text foregrounds and this card's own surfaces/borders
8874
- // must flip together so text stays readable in both light and dark. The
8875
- // expanded/pressed surfaces are self-owned here (not from a themed Card), so
8876
- // hardcoding dark values would leave dark-on-dark once the text flips.
8877
- // react-native-web passes the CSS var through to the inline style.
8878
- textPrimary: "var(--color-text-primary)",
8879
- textSecondary: "var(--color-text-secondary)",
8880
- textTertiary: "var(--color-text-tertiary)",
8881
- surfaceRaised: "var(--color-surface-raised)",
8882
- surfaceElevated: "var(--color-surface-elevated)",
8883
- borderDefault: "var(--color-border-default)"
8884
- };
8885
9299
  function buildColumnHeaders(unit) {
8886
9300
  return ["SET", "PREV", "REPS", unit.toUpperCase(), "RPE"];
8887
9301
  }
@@ -8939,6 +9353,7 @@ function CollapsedCard({
8939
9353
  e1rm,
8940
9354
  isPR,
8941
9355
  setVelocities,
9356
+ velocityZones,
8942
9357
  totalPlannedSets,
8943
9358
  supersetPosition
8944
9359
  }) {
@@ -8959,11 +9374,11 @@ function CollapsedCard({
8959
9374
  children: /* @__PURE__ */ jsxs(
8960
9375
  reactNative.View,
8961
9376
  {
9377
+ className: pressed ? "bg-surface-raised" : void 0,
8962
9378
  style: {
8963
9379
  padding: 12,
8964
9380
  paddingHorizontal: 14,
8965
9381
  cursor: "pointer",
8966
- backgroundColor: pressed ? COLORS.surfaceRaised : "transparent",
8967
9382
  ...borderRadius,
8968
9383
  ...supersetMargin
8969
9384
  },
@@ -8977,11 +9392,11 @@ function CollapsedCard({
8977
9392
  /* @__PURE__ */ jsx(
8978
9393
  reactNative.Text,
8979
9394
  {
9395
+ className: "text-text-primary",
8980
9396
  style: {
8981
9397
  fontSize: 14,
8982
9398
  fontFamily: '"Space Grotesk", sans-serif',
8983
- fontWeight: "700",
8984
- color: COLORS.textPrimary
9399
+ fontWeight: "700"
8985
9400
  },
8986
9401
  testID: "exercise-card-name",
8987
9402
  children: name
@@ -8991,10 +9406,10 @@ function CollapsedCard({
8991
9406
  summary && /* @__PURE__ */ jsx(
8992
9407
  reactNative.Text,
8993
9408
  {
9409
+ className: "text-text-secondary",
8994
9410
  style: {
8995
9411
  fontSize: 12,
8996
9412
  fontFamily: "Inter, sans-serif",
8997
- color: COLORS.textSecondary,
8998
9413
  marginRight: 8
8999
9414
  },
9000
9415
  testID: "exercise-card-summary",
@@ -9026,6 +9441,7 @@ function CollapsedCard({
9026
9441
  VelocityStrip,
9027
9442
  {
9028
9443
  velocities,
9444
+ zones: velocityZones,
9029
9445
  variant: "mini",
9030
9446
  testID: `exercise-card-velocity-strip-${i}`
9031
9447
  },
@@ -9064,10 +9480,9 @@ function ExpandedCard({
9064
9480
  return /* @__PURE__ */ jsxs(
9065
9481
  reactNative.View,
9066
9482
  {
9483
+ className: "bg-surface-elevated border-border",
9067
9484
  style: {
9068
- backgroundColor: COLORS.surfaceElevated,
9069
9485
  borderWidth: 1,
9070
- borderColor: COLORS.borderDefault,
9071
9486
  ...borderRadius,
9072
9487
  ...supersetMargin
9073
9488
  },
@@ -9090,17 +9505,17 @@ function ExpandedCard({
9090
9505
  paddingHorizontal: 14,
9091
9506
  paddingBottom: 10,
9092
9507
  borderBottomWidth: 1,
9093
- borderBottomColor: COLORS.borderDefault
9508
+ borderBottomColor: resolveColor("border-default")
9094
9509
  },
9095
9510
  children: [
9096
9511
  /* @__PURE__ */ jsx(
9097
9512
  reactNative.Text,
9098
9513
  {
9514
+ className: "text-text-primary",
9099
9515
  style: {
9100
9516
  fontSize: 14,
9101
9517
  fontFamily: '"Space Grotesk", sans-serif',
9102
- fontWeight: "700",
9103
- color: COLORS.textPrimary
9518
+ fontWeight: "700"
9104
9519
  },
9105
9520
  testID: "exercise-card-name",
9106
9521
  children: name
@@ -9110,10 +9525,10 @@ function ExpandedCard({
9110
9525
  summary && /* @__PURE__ */ jsx(
9111
9526
  reactNative.Text,
9112
9527
  {
9528
+ className: "text-text-secondary",
9113
9529
  style: {
9114
9530
  fontSize: 12,
9115
9531
  fontFamily: "Inter, sans-serif",
9116
- color: COLORS.textSecondary,
9117
9532
  marginRight: 8
9118
9533
  },
9119
9534
  testID: "exercise-card-summary",
@@ -9164,13 +9579,13 @@ function ExpandedCard({
9164
9579
  children: /* @__PURE__ */ jsx(
9165
9580
  reactNative.Text,
9166
9581
  {
9582
+ className: "text-text-tertiary",
9167
9583
  style: {
9168
9584
  fontSize: 10,
9169
9585
  fontWeight: "600",
9170
9586
  fontFamily: "Inter, sans-serif",
9171
9587
  textTransform: "uppercase",
9172
- letterSpacing: 0.8,
9173
- color: COLORS.textTertiary
9588
+ letterSpacing: 0.8
9174
9589
  },
9175
9590
  children: header
9176
9591
  }
@@ -9210,11 +9625,11 @@ function UpcomingCard({
9210
9625
  /* @__PURE__ */ jsx(
9211
9626
  reactNative.Text,
9212
9627
  {
9628
+ className: "text-text-primary",
9213
9629
  style: {
9214
9630
  fontSize: 14,
9215
9631
  fontFamily: '"Space Grotesk", sans-serif',
9216
- fontWeight: "700",
9217
- color: COLORS.textPrimary
9632
+ fontWeight: "700"
9218
9633
  },
9219
9634
  testID: "exercise-card-name",
9220
9635
  children: name
@@ -9223,10 +9638,10 @@ function UpcomingCard({
9223
9638
  prescription && /* @__PURE__ */ jsx(
9224
9639
  reactNative.Text,
9225
9640
  {
9641
+ className: "text-text-secondary",
9226
9642
  style: {
9227
9643
  fontSize: 12,
9228
9644
  fontFamily: "Inter, sans-serif",
9229
- color: COLORS.textSecondary,
9230
9645
  marginLeft: 8
9231
9646
  },
9232
9647
  testID: "exercise-card-prescription",
@@ -9237,10 +9652,10 @@ function UpcomingCard({
9237
9652
  previousBest && /* @__PURE__ */ jsx(
9238
9653
  reactNative.Text,
9239
9654
  {
9655
+ className: "text-text-tertiary",
9240
9656
  style: {
9241
9657
  fontSize: 11,
9242
- fontFamily: "Inter, sans-serif",
9243
- color: COLORS.textTertiary
9658
+ fontFamily: "Inter, sans-serif"
9244
9659
  },
9245
9660
  testID: "exercise-card-previous-best",
9246
9661
  children: previousBest
@@ -9478,11 +9893,12 @@ function WeekRow({
9478
9893
  /* @__PURE__ */ jsx(
9479
9894
  reactNative.Text,
9480
9895
  {
9896
+ className: isCurrent ? void 0 : "text-text-primary",
9481
9897
  style: {
9482
9898
  fontSize: 13,
9483
9899
  fontWeight: "700",
9484
9900
  fontFamily: "Inter, sans-serif",
9485
- color: isCurrent ? BRAND_PRIMARY5 : "var(--color-text-primary)"
9901
+ color: isCurrent ? BRAND_PRIMARY5 : void 0
9486
9902
  },
9487
9903
  accessibilityElementsHidden: true,
9488
9904
  children: `W${weekNumber}`
@@ -9521,21 +9937,16 @@ function WeekRow({
9521
9937
  }
9522
9938
  );
9523
9939
  }
9524
- var COLORS2 = {
9525
- // Theme-aware foregrounds: resolve to a readable color in both light and dark
9526
- // (react-native-web passes the CSS var through to the inline style).
9527
- textPrimary: "var(--color-text-primary)",
9528
- textSecondary: "var(--color-text-secondary)",
9529
- textTertiary: "var(--color-text-tertiary)",
9940
+ var COLORS = {
9530
9941
  statusSuccess: "#14B8A6",
9531
9942
  brandPrimary: "#FF7900",
9532
9943
  borderDefault: "#1F1F1F",
9533
9944
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
9534
9945
  };
9535
9946
  var statusBorderColors = {
9536
- completed: COLORS2.statusSuccess,
9537
- today: COLORS2.brandPrimary,
9538
- upcoming: COLORS2.borderDefault
9947
+ completed: COLORS.statusSuccess,
9948
+ today: COLORS.brandPrimary,
9949
+ upcoming: COLORS.borderDefault
9539
9950
  };
9540
9951
  var volumeStatusMap = {
9541
9952
  under: "behind",
@@ -9572,11 +9983,11 @@ function WorkoutCard({
9572
9983
  /* @__PURE__ */ jsx(
9573
9984
  reactNative.Text,
9574
9985
  {
9986
+ className: "text-text-primary",
9575
9987
  style: {
9576
9988
  fontSize: 15,
9577
9989
  fontFamily: '"Space Grotesk", sans-serif',
9578
- fontWeight: "700",
9579
- color: COLORS2.textPrimary
9990
+ fontWeight: "700"
9580
9991
  },
9581
9992
  testID: "workout-card-name",
9582
9993
  children: name
@@ -9586,10 +9997,10 @@ function WorkoutCard({
9586
9997
  /* @__PURE__ */ jsx(
9587
9998
  reactNative.Text,
9588
9999
  {
10000
+ className: "text-text-tertiary",
9589
10001
  style: {
9590
10002
  fontSize: 11,
9591
- fontFamily: "Inter, sans-serif",
9592
- color: COLORS2.textTertiary
10003
+ fontFamily: "Inter, sans-serif"
9593
10004
  },
9594
10005
  testID: "workout-card-date",
9595
10006
  children: date
@@ -9599,11 +10010,11 @@ function WorkoutCard({
9599
10010
  /* @__PURE__ */ jsx(
9600
10011
  reactNative.Text,
9601
10012
  {
10013
+ className: "text-text-secondary",
9602
10014
  style: {
9603
10015
  marginTop: 4,
9604
10016
  fontSize: 12,
9605
- fontFamily: "Inter, sans-serif",
9606
- color: COLORS2.textSecondary
10017
+ fontFamily: "Inter, sans-serif"
9607
10018
  },
9608
10019
  testID: "workout-card-stats",
9609
10020
  children: formatStats(totalSets, totalVolume, unit, duration)
@@ -9634,7 +10045,7 @@ function WorkoutCard({
9634
10045
  variant: "accent",
9635
10046
  elevation: 1,
9636
10047
  accentColor: statusBorderColors[status],
9637
- bgColor: isToday ? COLORS2.brandPrimarySubtle : void 0,
10048
+ bgColor: isToday ? COLORS.brandPrimarySubtle : void 0,
9638
10049
  className,
9639
10050
  style: isUpcoming ? { opacity: 0.6 } : void 0,
9640
10051
  testID: "workout-card",
@@ -9674,8 +10085,6 @@ var BRAND_PRIMARY6 = "#FF7900";
9674
10085
  var BRAND_PRIMARY_DARK = "#C45E00";
9675
10086
  var BRAND_PRIMARY_LIGHT = "#FFB066";
9676
10087
  var BORDER_DEFAULT = "#1F1F1F";
9677
- var TEXT_PRIMARY2 = "var(--color-text-primary)";
9678
- var TEXT_SECONDARY = "var(--color-text-secondary)";
9679
10088
  var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY6, BRAND_PRIMARY_LIGHT];
9680
10089
  function heatmapColor(percentage) {
9681
10090
  const clamped = Math.max(0, Math.min(100, percentage));
@@ -9718,11 +10127,11 @@ function MesoCard({
9718
10127
  /* @__PURE__ */ jsx(
9719
10128
  reactNative.Text,
9720
10129
  {
10130
+ className: "text-text-primary",
9721
10131
  style: {
9722
10132
  fontSize: 15,
9723
10133
  fontFamily: '"Space Grotesk", sans-serif',
9724
- fontWeight: "700",
9725
- color: TEXT_PRIMARY2
10134
+ fontWeight: "700"
9726
10135
  },
9727
10136
  testID: "meso-card-name",
9728
10137
  children: name
@@ -9733,11 +10142,11 @@ function MesoCard({
9733
10142
  /* @__PURE__ */ jsx(
9734
10143
  reactNative.Text,
9735
10144
  {
10145
+ className: "text-text-secondary",
9736
10146
  style: {
9737
10147
  marginTop: 4,
9738
10148
  fontSize: 12,
9739
- fontFamily: "Inter, sans-serif",
9740
- color: TEXT_SECONDARY
10149
+ fontFamily: "Inter, sans-serif"
9741
10150
  },
9742
10151
  testID: "meso-card-subheader",
9743
10152
  children: `${split} \xB7 ${weekRange}`
@@ -9831,11 +10240,7 @@ function MesoCard({
9831
10240
  var BRAND_PRIMARY7 = "#FF7900";
9832
10241
  var RECENT_BORDER = "rgba(255,176,32,0.3)";
9833
10242
  var RECENT_GLOW = "0 0 12px rgba(255,176,32,0.06)";
9834
- var SURFACE_RAISED = "var(--color-surface-raised)";
9835
- var BORDER_DEFAULT2 = "var(--color-border-default)";
9836
- var TEXT_PRIMARY3 = "var(--color-text-primary)";
9837
- var TEXT_SECONDARY2 = "var(--color-text-secondary)";
9838
- var TEXT_TERTIARY3 = "var(--color-text-tertiary)";
10243
+ var BORDER_DEFAULT2 = resolveColor("border-default");
9839
10244
  var TYPE_LABELS = {
9840
10245
  e1rm: "e1RM",
9841
10246
  weight: "Weight",
@@ -9861,30 +10266,29 @@ function PrRecordRow({ record, index }) {
9861
10266
  {
9862
10267
  accessibilityRole: "text",
9863
10268
  accessibilityLabel: a11yLabel,
9864
- className: "flex-row items-center",
10269
+ className: "flex-row items-center bg-surface-raised",
9865
10270
  style: {
9866
10271
  gap: 10,
9867
10272
  paddingVertical: 10,
9868
10273
  paddingHorizontal: 12,
9869
10274
  marginBottom: 8,
9870
10275
  borderRadius: 8,
9871
- backgroundColor: SURFACE_RAISED,
9872
10276
  borderWidth: 1,
9873
10277
  borderColor: record.isRecent ? RECENT_BORDER : BORDER_DEFAULT2,
9874
10278
  ...record.isRecent ? { boxShadow: RECENT_GLOW } : {}
9875
10279
  },
9876
10280
  testID: `pr-history-modal-record-${index}`,
9877
10281
  children: [
9878
- /* @__PURE__ */ jsx(lucideReact.Star, { size: 16, color: BRAND_PRIMARY7, fill: BRAND_PRIMARY7, strokeWidth: 2 }),
10282
+ /* @__PURE__ */ jsx(StarIcon, { size: 16, color: BRAND_PRIMARY7, fill: BRAND_PRIMARY7, strokeWidth: 2 }),
9879
10283
  /* @__PURE__ */ jsx(
9880
10284
  reactNative.Text,
9881
10285
  {
10286
+ className: "text-text-secondary",
9882
10287
  style: {
9883
10288
  flex: 1,
9884
10289
  fontSize: 13,
9885
10290
  fontFamily: "Inter, sans-serif",
9886
- fontWeight: "600",
9887
- color: TEXT_SECONDARY2
10291
+ fontWeight: "600"
9888
10292
  },
9889
10293
  testID: `pr-history-modal-record-${index}-type`,
9890
10294
  children: label
@@ -9893,11 +10297,11 @@ function PrRecordRow({ record, index }) {
9893
10297
  /* @__PURE__ */ jsx(
9894
10298
  reactNative.Text,
9895
10299
  {
10300
+ className: "text-text-primary",
9896
10301
  style: {
9897
10302
  fontSize: 14,
9898
10303
  fontFamily: '"Space Grotesk", sans-serif',
9899
- fontWeight: "700",
9900
- color: TEXT_PRIMARY3
10304
+ fontWeight: "700"
9901
10305
  },
9902
10306
  testID: `pr-history-modal-record-${index}-value`,
9903
10307
  children: valueText
@@ -9906,12 +10310,12 @@ function PrRecordRow({ record, index }) {
9906
10310
  /* @__PURE__ */ jsx(
9907
10311
  reactNative.Text,
9908
10312
  {
10313
+ className: "text-text-tertiary",
9909
10314
  style: {
9910
10315
  minWidth: 64,
9911
10316
  textAlign: "right",
9912
10317
  fontSize: 11,
9913
- fontFamily: "Inter, sans-serif",
9914
- color: TEXT_TERTIARY3
10318
+ fontFamily: "Inter, sans-serif"
9915
10319
  },
9916
10320
  testID: `pr-history-modal-record-${index}-date`,
9917
10321
  children: record.date
@@ -9949,13 +10353,13 @@ function PrHistoryModal({
9949
10353
  reactNative.View,
9950
10354
  {
9951
10355
  accessibilityElementsHidden: true,
10356
+ className: "bg-border",
9952
10357
  style: {
9953
10358
  alignSelf: "center",
9954
10359
  width: 40,
9955
10360
  height: 4,
9956
10361
  borderRadius: 2,
9957
- marginTop: 8,
9958
- backgroundColor: BORDER_DEFAULT2
10362
+ marginTop: 8
9959
10363
  },
9960
10364
  testID: "pr-history-modal-handle"
9961
10365
  }
@@ -9971,11 +10375,11 @@ function PrHistoryModal({
9971
10375
  reactNative.Text,
9972
10376
  {
9973
10377
  accessibilityRole: "header",
10378
+ className: "text-text-primary",
9974
10379
  style: {
9975
10380
  fontSize: 16,
9976
10381
  fontFamily: '"Space Grotesk", sans-serif',
9977
- fontWeight: "700",
9978
- color: TEXT_PRIMARY3
10382
+ fontWeight: "700"
9979
10383
  },
9980
10384
  testID: "pr-history-modal-title",
9981
10385
  children: dialogLabel
@@ -9990,7 +10394,7 @@ function PrHistoryModal({
9990
10394
  hitSlop: 8,
9991
10395
  style: { padding: 4, margin: -4 },
9992
10396
  testID: "pr-history-modal-close",
9993
- children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 22, lineHeight: 22, color: TEXT_SECONDARY2 }, children: "\xD7" })
10397
+ children: /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-secondary", style: { fontSize: 22, lineHeight: 22 }, children: "\xD7" })
9994
10398
  }
9995
10399
  )
9996
10400
  ]
@@ -9999,11 +10403,11 @@ function PrHistoryModal({
9999
10403
  /* @__PURE__ */ jsx(DrawerBody, { className: "px-4 pt-0 pb-4", children: records.length === 0 ? /* @__PURE__ */ jsx(reactNative.View, { style: { paddingVertical: 24 }, testID: "pr-history-modal-empty", children: /* @__PURE__ */ jsx(
10000
10404
  reactNative.Text,
10001
10405
  {
10406
+ className: "text-text-tertiary",
10002
10407
  style: {
10003
10408
  textAlign: "center",
10004
10409
  fontSize: 13,
10005
- fontFamily: "Inter, sans-serif",
10006
- color: TEXT_TERTIARY3
10410
+ fontFamily: "Inter, sans-serif"
10007
10411
  },
10008
10412
  children: "No personal records yet"
10009
10413
  }
@@ -10023,9 +10427,7 @@ var BRAND_PRIMARY8 = "#FF7900";
10023
10427
  var BRAND_PRIMARY_SUBTLE = "rgba(255,121,0,0.12)";
10024
10428
  var STATUS_SUCCESS = "#14B8A6";
10025
10429
  var STATUS_WARNING = "#FFB020";
10026
- var STATUS_ERROR = "#D14343";
10027
- var TEXT_PRIMARY4 = "var(--color-text-primary)";
10028
- var TEXT_SECONDARY3 = "var(--color-text-secondary)";
10430
+ var STATUS_ERROR2 = "#D14343";
10029
10431
  var EMOJI_SETS = {
10030
10432
  sleep: ["\u{1F634}", "\u{1F610}", "\u{1F642}", "\u{1F60A}", "\u{1F929}"],
10031
10433
  soreness: ["\u{1F635}", "\u{1F623}", "\u{1F610}", "\u{1F642}", "\u{1F4AA}"],
@@ -10034,7 +10436,7 @@ var EMOJI_SETS = {
10034
10436
  };
10035
10437
  var DEFAULT_EMOJI_SET = ["\u{1F61E}", "\u{1F615}", "\u{1F610}", "\u{1F642}", "\u{1F604}"];
10036
10438
  function scoreColor(score) {
10037
- if (score < 40) return STATUS_ERROR;
10439
+ if (score < 40) return STATUS_ERROR2;
10038
10440
  if (score < 70) return STATUS_WARNING;
10039
10441
  return STATUS_SUCCESS;
10040
10442
  }
@@ -10057,7 +10459,8 @@ function EmojiSlider({ factor }) {
10057
10459
  /* @__PURE__ */ jsx(
10058
10460
  reactNative.Text,
10059
10461
  {
10060
- style: { fontSize: 12, fontWeight: "500", fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY3 },
10462
+ className: "text-text-secondary",
10463
+ style: { fontSize: 12, fontWeight: "500", fontFamily: "Inter, sans-serif" },
10061
10464
  testID: "readiness-check-factor-label",
10062
10465
  children: factor.label
10063
10466
  }
@@ -10081,14 +10484,15 @@ function EmojiSlider({ factor }) {
10081
10484
  accessibilityLabel: `${factor.label} level ${level} of 5`,
10082
10485
  "aria-checked": selected,
10083
10486
  testID: "readiness-check-emoji",
10487
+ className: selected ? void 0 : "border-border bg-surface-raised",
10084
10488
  style: ({ pressed }) => ({
10085
10489
  flex: 1,
10086
10490
  alignItems: "center",
10087
10491
  paddingVertical: 8,
10088
10492
  borderRadius: 8,
10089
10493
  borderWidth: 1,
10090
- borderColor: selected ? BRAND_PRIMARY8 : "var(--color-border-default)",
10091
- backgroundColor: selected ? BRAND_PRIMARY_SUBTLE : "var(--color-surface-raised)",
10494
+ borderColor: selected ? BRAND_PRIMARY8 : void 0,
10495
+ backgroundColor: selected ? BRAND_PRIMARY_SUBTLE : void 0,
10092
10496
  opacity: selected ? 1 : pressed ? 0.8 : 0.55,
10093
10497
  transform: [{ scale: selected ? 1.06 : 1 }]
10094
10498
  }),
@@ -10109,13 +10513,13 @@ function ScoreGauge({ score }) {
10109
10513
  accessibilityRole: "image",
10110
10514
  accessibilityLabel: `Readiness score: ${score} out of 100`,
10111
10515
  testID: "readiness-check-score",
10516
+ className: "bg-surface-raised",
10112
10517
  style: {
10113
10518
  width: 60,
10114
10519
  height: 60,
10115
10520
  borderRadius: 30,
10116
10521
  borderWidth: 4,
10117
10522
  borderColor: color,
10118
- backgroundColor: "var(--color-surface-raised)",
10119
10523
  alignItems: "center",
10120
10524
  justifyContent: "center"
10121
10525
  },
@@ -10135,13 +10539,12 @@ function WarmUpCard({ validation }) {
10135
10539
  return /* @__PURE__ */ jsxs(
10136
10540
  reactNative.View,
10137
10541
  {
10542
+ className: "border-border bg-surface-raised",
10138
10543
  style: {
10139
10544
  marginTop: 16,
10140
10545
  padding: 12,
10141
10546
  borderRadius: 8,
10142
- borderWidth: 1,
10143
- borderColor: "var(--color-border-default)",
10144
- backgroundColor: "var(--color-surface-raised)"
10547
+ borderWidth: 1
10145
10548
  },
10146
10549
  testID: "readiness-check-warmup",
10147
10550
  children: [
@@ -10149,7 +10552,8 @@ function WarmUpCard({ validation }) {
10149
10552
  /* @__PURE__ */ jsx(
10150
10553
  reactNative.Text,
10151
10554
  {
10152
- style: { fontSize: 12, fontWeight: "600", fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY3 },
10555
+ className: "text-text-secondary",
10556
+ style: { fontSize: 12, fontWeight: "600", fontFamily: "Inter, sans-serif" },
10153
10557
  testID: "readiness-check-warmup-deficit",
10154
10558
  children: velocityDeficitText(validation.velocityDeficit)
10155
10559
  }
@@ -10159,7 +10563,8 @@ function WarmUpCard({ validation }) {
10159
10563
  /* @__PURE__ */ jsx(
10160
10564
  reactNative.Text,
10161
10565
  {
10162
- style: { marginTop: 6, fontSize: 13, fontFamily: "Inter, sans-serif", color: TEXT_PRIMARY4 },
10566
+ className: "text-text-primary",
10567
+ style: { marginTop: 6, fontSize: 13, fontFamily: "Inter, sans-serif" },
10163
10568
  testID: "readiness-check-warmup-recommendation",
10164
10569
  children: validation.recommendation
10165
10570
  }
@@ -10181,7 +10586,8 @@ function ReadinessCheck({
10181
10586
  /* @__PURE__ */ jsx(
10182
10587
  reactNative.Text,
10183
10588
  {
10184
- style: { fontSize: 18, fontWeight: "700", fontFamily: '"Space Grotesk", sans-serif', color: TEXT_PRIMARY4 },
10589
+ className: "text-text-primary",
10590
+ style: { fontSize: 18, fontWeight: "700", fontFamily: '"Space Grotesk", sans-serif' },
10185
10591
  testID: "readiness-check-title",
10186
10592
  children: "How are you feeling?"
10187
10593
  }
@@ -10214,21 +10620,18 @@ function ReadinessCheck({
10214
10620
  var BRAND_PRIMARY9 = "#FF7900";
10215
10621
  var BRAND_PRIMARY_DARK2 = "#C45E00";
10216
10622
  var BRAND_PRIMARY_LIGHT2 = "#FFB066";
10217
- var TEXT_PRIMARY5 = "var(--color-text-primary)";
10218
- var TEXT_SECONDARY4 = "var(--color-text-secondary)";
10219
- var TEXT_TERTIARY4 = "var(--color-text-tertiary)";
10220
10623
  var SUCCESS = "#14B8A6";
10221
10624
  var WARNING = "#FFB020";
10222
10625
  var ERROR = "#D14343";
10223
10626
  var ACCENT_STOPS2 = [BRAND_PRIMARY_DARK2, BRAND_PRIMARY9, BRAND_PRIMARY_LIGHT2];
10224
- var CARD_GRADIENT = "linear-gradient(135deg, var(--color-surface-elevated) 0%, var(--color-surface-raised) 100%)";
10627
+ var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
10225
10628
  var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(20,184,166,0.25) 0%, rgba(255,176,32,0.25) 50%, rgba(209,67,67,0.25) 100%)";
10226
10629
  var STATUS_VARIANTS = {
10227
10630
  success: { bg: "rgba(20,184,166,0.15)", border: "rgba(20,184,166,0.3)", text: SUCCESS },
10228
10631
  warning: { bg: "rgba(255,176,32,0.15)", border: "rgba(255,176,32,0.3)", text: WARNING },
10229
10632
  error: { bg: "rgba(209,67,67,0.15)", border: "rgba(209,67,67,0.25)", text: ERROR }
10230
10633
  };
10231
- function clamp012(value) {
10634
+ function clamp01(value) {
10232
10635
  if (!Number.isFinite(value)) return 0;
10233
10636
  if (value < 0) return 0;
10234
10637
  if (value > 1) return 1;
@@ -10248,7 +10651,8 @@ function renderCoachingText(text, highlights) {
10248
10651
  (part, index) => highlights.includes(part) ? /* @__PURE__ */ jsx(
10249
10652
  reactNative.Text,
10250
10653
  {
10251
- style: { fontWeight: "700", color: TEXT_PRIMARY5 },
10654
+ className: "text-text-primary",
10655
+ style: { fontWeight: "700" },
10252
10656
  children: part
10253
10657
  },
10254
10658
  `${part}-${index}`
@@ -10295,13 +10699,13 @@ function MetricCell({ metric }) {
10295
10699
  /* @__PURE__ */ jsx(
10296
10700
  reactNative.Text,
10297
10701
  {
10702
+ className: "text-text-tertiary",
10298
10703
  style: {
10299
10704
  fontSize: 10,
10300
10705
  fontFamily: "Inter, sans-serif",
10301
10706
  fontWeight: "600",
10302
10707
  letterSpacing: 0.5,
10303
- textTransform: "uppercase",
10304
- color: TEXT_TERTIARY4
10708
+ textTransform: "uppercase"
10305
10709
  },
10306
10710
  children: metric.label
10307
10711
  }
@@ -10309,12 +10713,12 @@ function MetricCell({ metric }) {
10309
10713
  /* @__PURE__ */ jsx(
10310
10714
  reactNative.Text,
10311
10715
  {
10716
+ className: "text-text-primary",
10312
10717
  style: {
10313
10718
  marginTop: 2,
10314
10719
  fontSize: 13,
10315
10720
  fontFamily: "Inter, sans-serif",
10316
- fontWeight: "600",
10317
- color: TEXT_PRIMARY5
10721
+ fontWeight: "600"
10318
10722
  },
10319
10723
  children: metric.value
10320
10724
  }
@@ -10322,7 +10726,7 @@ function MetricCell({ metric }) {
10322
10726
  ] });
10323
10727
  }
10324
10728
  function Gauge({ gauge }) {
10325
- const level = clamp012(gauge.level);
10729
+ const level = clamp01(gauge.level);
10326
10730
  const percentage = Math.round(level * 100);
10327
10731
  const markerColor = getGaugeZoneColor(level);
10328
10732
  return /* @__PURE__ */ jsxs(
@@ -10346,13 +10750,13 @@ function Gauge({ gauge }) {
10346
10750
  /* @__PURE__ */ jsx(
10347
10751
  reactNative.Text,
10348
10752
  {
10753
+ className: "text-text-secondary",
10349
10754
  style: {
10350
10755
  fontSize: 10,
10351
10756
  fontFamily: "Inter, sans-serif",
10352
10757
  fontWeight: "500",
10353
10758
  letterSpacing: 0.5,
10354
- textTransform: "uppercase",
10355
- color: TEXT_SECONDARY4
10759
+ textTransform: "uppercase"
10356
10760
  },
10357
10761
  children: gauge.label
10358
10762
  }
@@ -10360,11 +10764,11 @@ function Gauge({ gauge }) {
10360
10764
  gauge.sublabel != null && /* @__PURE__ */ jsx(
10361
10765
  reactNative.Text,
10362
10766
  {
10767
+ className: "text-text-tertiary",
10363
10768
  style: {
10364
10769
  fontSize: 10,
10365
10770
  fontFamily: "Inter, sans-serif",
10366
- fontWeight: "500",
10367
- color: TEXT_TERTIARY4
10771
+ fontWeight: "500"
10368
10772
  },
10369
10773
  children: gauge.sublabel
10370
10774
  }
@@ -10401,6 +10805,7 @@ function Gauge({ gauge }) {
10401
10805
  /* @__PURE__ */ jsx(
10402
10806
  reactNative.View,
10403
10807
  {
10808
+ className: "border-text-primary",
10404
10809
  style: {
10405
10810
  position: "absolute",
10406
10811
  left: `${percentage}%`,
@@ -10410,7 +10815,6 @@ function Gauge({ gauge }) {
10410
10815
  height: 14,
10411
10816
  borderRadius: 9999,
10412
10817
  borderWidth: 2,
10413
- borderColor: TEXT_PRIMARY5,
10414
10818
  backgroundColor: markerColor,
10415
10819
  boxShadow: "0 0 4px rgba(0,0,0,0.5)"
10416
10820
  },
@@ -10473,12 +10877,12 @@ function MesoStatusCard({
10473
10877
  /* @__PURE__ */ jsx(
10474
10878
  reactNative.Text,
10475
10879
  {
10880
+ className: "text-text-primary",
10476
10881
  style: {
10477
10882
  flexShrink: 1,
10478
10883
  fontSize: 15,
10479
10884
  fontFamily: '"Space Grotesk", sans-serif',
10480
- fontWeight: "700",
10481
- color: TEXT_PRIMARY5
10885
+ fontWeight: "700"
10482
10886
  },
10483
10887
  testID: "meso-status-card-name",
10484
10888
  children: mesoName
@@ -10491,11 +10895,11 @@ function MesoStatusCard({
10491
10895
  /* @__PURE__ */ jsx(
10492
10896
  reactNative.Text,
10493
10897
  {
10898
+ className: "text-text-secondary",
10494
10899
  style: {
10495
10900
  marginTop: 4,
10496
10901
  fontSize: 12,
10497
- fontFamily: "Inter, sans-serif",
10498
- color: TEXT_SECONDARY4
10902
+ fontFamily: "Inter, sans-serif"
10499
10903
  },
10500
10904
  testID: "meso-status-card-subtitle",
10501
10905
  children: mesoSubtitle
@@ -10527,11 +10931,11 @@ function MesoStatusCard({
10527
10931
  children: /* @__PURE__ */ jsx(
10528
10932
  reactNative.Text,
10529
10933
  {
10934
+ className: "text-text-secondary",
10530
10935
  style: {
10531
10936
  fontSize: 12,
10532
10937
  lineHeight: 17,
10533
- fontFamily: "Inter, sans-serif",
10534
- color: TEXT_SECONDARY4
10938
+ fontFamily: "Inter, sans-serif"
10535
10939
  },
10536
10940
  children: renderCoachingText(coaching.text, coaching.highlights)
10537
10941
  }
@@ -10576,15 +10980,11 @@ function MesoStatusCard({
10576
10980
  );
10577
10981
  }
10578
10982
  var BRAND_PRIMARY10 = "#FF7900";
10579
- var TEXT_PRIMARY6 = "var(--color-text-primary)";
10580
- var TEXT_SECONDARY5 = "var(--color-text-secondary)";
10581
- var TEXT_TERTIARY5 = "var(--color-text-tertiary)";
10582
- var BORDER_STRONG = "var(--color-border-strong)";
10983
+ var PROJECTION_LINE_COLOR = resolveColor("text-tertiary");
10583
10984
  var STATUS_SUCCESS2 = "#14B8A6";
10584
- var STATUS_ERROR2 = "#D14343";
10985
+ var STATUS_ERROR3 = "#D14343";
10585
10986
  var STATUS_WARNING2 = "#FFB020";
10586
10987
  var GRID_LINE = "rgba(255,255,255,0.06)";
10587
- var TOOLTIP_BG = "var(--color-surface-elevated)";
10588
10988
  var SUCCESS_PILL_BG = "rgba(20,184,166,0.10)";
10589
10989
  var SUCCESS_PILL_BORDER = "rgba(20,184,166,0.20)";
10590
10990
  var ERROR_PILL_BG = "rgba(209,67,67,0.10)";
@@ -10592,8 +10992,8 @@ var ERROR_PILL_BORDER = "rgba(209,67,67,0.20)";
10592
10992
  var PLOT_LEFT = 26;
10593
10993
  var TOOLTIP_WIDTH = 124;
10594
10994
  function timeOf(dateStr) {
10595
- const t = Date.parse(dateStr);
10596
- return Number.isNaN(t) ? 0 : t;
10995
+ const t2 = Date.parse(dateStr);
10996
+ return Number.isNaN(t2) ? 0 : t2;
10597
10997
  }
10598
10998
  function formatValue(value) {
10599
10999
  return `${Math.round(value)}`;
@@ -10765,7 +11165,7 @@ function StrengthTrendChart({
10765
11165
  alignItems: "center",
10766
11166
  justifyContent: "center"
10767
11167
  },
10768
- children: /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 12, fontFamily: "Inter, sans-serif", color: TEXT_TERTIARY5 }, children: "No strength data yet" })
11168
+ children: /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-tertiary", style: { fontSize: 12, fontFamily: "Inter, sans-serif" }, children: "No strength data yet" })
10769
11169
  }
10770
11170
  )
10771
11171
  }
@@ -10809,6 +11209,7 @@ function StrengthTrendChart({
10809
11209
  children: /* @__PURE__ */ jsx(
10810
11210
  reactNative.Text,
10811
11211
  {
11212
+ className: "text-text-tertiary",
10812
11213
  style: {
10813
11214
  position: "absolute",
10814
11215
  left: -PLOT_LEFT,
@@ -10816,8 +11217,7 @@ function StrengthTrendChart({
10816
11217
  width: PLOT_LEFT - 4,
10817
11218
  textAlign: "right",
10818
11219
  fontSize: 9,
10819
- fontFamily: "Inter, sans-serif",
10820
- color: TEXT_TERTIARY5
11220
+ fontFamily: "Inter, sans-serif"
10821
11221
  },
10822
11222
  children: line.label
10823
11223
  }
@@ -10860,7 +11260,7 @@ function StrengthTrendChart({
10860
11260
  ChartLine,
10861
11261
  {
10862
11262
  coords: geometry.projection,
10863
- color: TEXT_TERTIARY5,
11263
+ color: PROJECTION_LINE_COLOR,
10864
11264
  strokeWidth: 2,
10865
11265
  dashed: true,
10866
11266
  testID: "strength-trend-chart-projection-segment"
@@ -10936,6 +11336,7 @@ function StrengthTrendChart({
10936
11336
  {
10937
11337
  testID: "strength-trend-chart-tooltip",
10938
11338
  accessibilityElementsHidden: true,
11339
+ className: "bg-surface-elevated border-border-strong",
10939
11340
  style: {
10940
11341
  position: "absolute",
10941
11342
  left: Math.max(
@@ -10944,9 +11345,7 @@ function StrengthTrendChart({
10944
11345
  ),
10945
11346
  top: Math.max(0, selectedCoord.y - 52),
10946
11347
  maxWidth: TOOLTIP_WIDTH,
10947
- backgroundColor: TOOLTIP_BG,
10948
11348
  borderWidth: 1,
10949
- borderColor: BORDER_STRONG,
10950
11349
  borderRadius: 8,
10951
11350
  paddingVertical: 8,
10952
11351
  paddingHorizontal: 10
@@ -10955,10 +11354,10 @@ function StrengthTrendChart({
10955
11354
  /* @__PURE__ */ jsx(
10956
11355
  reactNative.Text,
10957
11356
  {
11357
+ className: "text-text-tertiary",
10958
11358
  style: {
10959
11359
  fontSize: 10,
10960
- fontFamily: "Inter, sans-serif",
10961
- color: TEXT_TERTIARY5
11360
+ fontFamily: "Inter, sans-serif"
10962
11361
  },
10963
11362
  children: selectedCoord.point.sessionLabel ?? selectedCoord.point.date
10964
11363
  }
@@ -10966,12 +11365,12 @@ function StrengthTrendChart({
10966
11365
  /* @__PURE__ */ jsx(
10967
11366
  reactNative.Text,
10968
11367
  {
11368
+ className: "text-text-primary",
10969
11369
  style: {
10970
11370
  marginTop: 2,
10971
11371
  fontSize: 13,
10972
11372
  fontFamily: "Inter, sans-serif",
10973
- fontWeight: "700",
10974
- color: TEXT_PRIMARY6
11373
+ fontWeight: "700"
10975
11374
  },
10976
11375
  children: `${formatValue(selectedCoord.point.e1rm)} ${unit}`
10977
11376
  }
@@ -10983,7 +11382,7 @@ function StrengthTrendChart({
10983
11382
  marginTop: 1,
10984
11383
  fontSize: 10,
10985
11384
  fontFamily: "Inter, sans-serif",
10986
- color: selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? STATUS_SUCCESS2 : STATUS_ERROR2
11385
+ color: selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? STATUS_SUCCESS2 : STATUS_ERROR3
10987
11386
  },
10988
11387
  children: `${selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm >= 0 ? "+" : "-"}${Math.abs(
10989
11388
  selectedCoord.point.e1rm - geometry.actual[selected - 1].point.e1rm
@@ -11004,14 +11403,14 @@ function StrengthTrendChart({
11004
11403
  reactNative.Text,
11005
11404
  {
11006
11405
  testID: "strength-trend-chart-axis-label",
11406
+ className: "text-text-tertiary",
11007
11407
  style: {
11008
11408
  position: "absolute",
11009
11409
  left: boundary.x - 24,
11010
11410
  width: 48,
11011
11411
  textAlign: "center",
11012
11412
  fontSize: 10,
11013
- fontFamily: "Inter, sans-serif",
11014
- color: TEXT_TERTIARY5
11413
+ fontFamily: "Inter, sans-serif"
11015
11414
  },
11016
11415
  children: boundary.label
11017
11416
  },
@@ -11039,7 +11438,7 @@ function StrengthTrendChart({
11039
11438
  fontSize: 11,
11040
11439
  fontFamily: "Inter, sans-serif",
11041
11440
  fontWeight: "600",
11042
- color: trend.positive ? STATUS_SUCCESS2 : STATUS_ERROR2
11441
+ color: trend.positive ? STATUS_SUCCESS2 : STATUS_ERROR3
11043
11442
  },
11044
11443
  children: trendText
11045
11444
  }
@@ -11055,7 +11454,7 @@ function StrengthTrendChart({
11055
11454
  children: [
11056
11455
  /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { gap: 5 }, children: [
11057
11456
  /* @__PURE__ */ jsx(reactNative.View, { style: { width: 14, height: 2.5, borderRadius: 1.5, backgroundColor: BRAND_PRIMARY10 } }),
11058
- /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 10, fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY5 }, children: "Actual" })
11457
+ /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-secondary", style: { fontSize: 10, fontFamily: "Inter, sans-serif" }, children: "Actual" })
11059
11458
  ] }),
11060
11459
  /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { gap: 5 }, children: [
11061
11460
  /* @__PURE__ */ jsx(
@@ -11066,15 +11465,15 @@ function StrengthTrendChart({
11066
11465
  height: 0,
11067
11466
  borderTopWidth: 2,
11068
11467
  borderStyle: "dashed",
11069
- borderTopColor: TEXT_TERTIARY5
11468
+ borderTopColor: PROJECTION_LINE_COLOR
11070
11469
  }
11071
11470
  }
11072
11471
  ),
11073
- /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 10, fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY5 }, children: "Projected" })
11472
+ /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-secondary", style: { fontSize: 10, fontFamily: "Inter, sans-serif" }, children: "Projected" })
11074
11473
  ] }),
11075
11474
  /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { gap: 5 }, children: [
11076
11475
  /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 12, color: STATUS_WARNING2 }, children: "\u2605" }),
11077
- /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 10, fontFamily: "Inter, sans-serif", color: TEXT_SECONDARY5 }, children: "PR" })
11476
+ /* @__PURE__ */ jsx(reactNative.Text, { className: "text-text-secondary", style: { fontSize: 10, fontFamily: "Inter, sans-serif" }, children: "PR" })
11078
11477
  ] })
11079
11478
  ]
11080
11479
  }
@@ -11087,7 +11486,6 @@ var STATUS_SUCCESS3 = "#14B8A6";
11087
11486
  var STATUS_WARNING3 = "#FFB020";
11088
11487
  var STATUS_INFO = "#2196F3";
11089
11488
  var DOT_BORDER = "#F3F4F6";
11090
- var TEXT_TERTIARY6 = "var(--color-text-tertiary)";
11091
11489
  var BAND_FILL = "rgba(20,184,166,0.1)";
11092
11490
  var BAND_EDGE = "rgba(20,184,166,0.45)";
11093
11491
  var PROJECTION_FILL = "rgba(20,184,166,0.05)";
@@ -11129,8 +11527,8 @@ function interpEdge(pixels, x, key) {
11129
11527
  const a = pixels[i];
11130
11528
  const b = pixels[i + 1];
11131
11529
  if (x >= a.x && x <= b.x) {
11132
- const t = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
11133
- return a[key] + t * (b[key] - a[key]);
11530
+ const t2 = b.x === a.x ? 0 : (x - a.x) / (b.x - a.x);
11531
+ return a[key] + t2 * (b[key] - a[key]);
11134
11532
  }
11135
11533
  }
11136
11534
  return pixels[pixels.length - 1][key];
@@ -11368,6 +11766,7 @@ function CapacityBandChart({
11368
11766
  /* @__PURE__ */ jsx(
11369
11767
  reactNative.Text,
11370
11768
  {
11769
+ className: "text-text-tertiary",
11371
11770
  style: {
11372
11771
  position: "absolute",
11373
11772
  left: PADDING_LEFT / 2 - 14,
@@ -11376,7 +11775,6 @@ function CapacityBandChart({
11376
11775
  textAlign: "center",
11377
11776
  fontSize: 9,
11378
11777
  fontFamily: "Inter, sans-serif",
11379
- color: TEXT_TERTIARY6,
11380
11778
  transform: [{ rotate: "-90deg" }]
11381
11779
  },
11382
11780
  accessibilityElementsHidden: true,
@@ -11390,6 +11788,7 @@ function CapacityBandChart({
11390
11788
  return /* @__PURE__ */ jsx(
11391
11789
  reactNative.Text,
11392
11790
  {
11791
+ className: "text-text-tertiary",
11393
11792
  style: {
11394
11793
  position: "absolute",
11395
11794
  left: toX(point.date) - 14,
@@ -11397,8 +11796,7 @@ function CapacityBandChart({
11397
11796
  width: 28,
11398
11797
  textAlign: "center",
11399
11798
  fontSize: 10,
11400
- fontFamily: "Inter, sans-serif",
11401
- color: TEXT_TERTIARY6
11799
+ fontFamily: "Inter, sans-serif"
11402
11800
  },
11403
11801
  accessibilityElementsHidden: true,
11404
11802
  importantForAccessibility: "no-hide-descendants",
@@ -11460,1196 +11858,176 @@ function CapacityBandChart({
11460
11858
  }
11461
11859
  );
11462
11860
  }
11463
- var SURFACE_ELEVATED = "var(--color-surface-elevated)";
11464
- var BORDER_DEFAULT3 = "var(--color-border-default)";
11465
- var BRAND_PRIMARY11 = "#FF7900";
11466
- var TEXT_PRIMARY7 = "var(--color-text-primary)";
11467
- var TEXT_TERTIARY7 = "var(--color-text-tertiary)";
11468
- var PAGE_BG = "var(--color-background-base)";
11469
- var WORKOUT_PILL_STATUS = {
11470
- completed: "completed",
11471
- today: "current",
11472
- upcoming: "upcoming"
11473
- };
11474
- function deriveNavLevel(selection) {
11475
- if (selection.workoutId != null) return "workout";
11476
- if (selection.weekNumber != null) return "week";
11477
- return "meso";
11478
- }
11479
- function toProgressBarMesos(mesos) {
11480
- return mesos.map(({ id, name, weekCount, status, currentWeek }) => ({
11481
- id,
11482
- name,
11483
- weekCount,
11484
- status,
11485
- currentWeek
11486
- }));
11487
- }
11488
- function findMeso(mesos, id) {
11489
- if (id == null) return null;
11490
- return mesos.find((meso) => meso.id === id) ?? null;
11491
- }
11492
- function findWeek(meso, weekNumber) {
11493
- if (meso == null || weekNumber == null) return null;
11494
- return meso.weeks.find((week) => week.weekNumber === weekNumber) ?? null;
11495
- }
11496
- function findWorkout(week, workoutId) {
11497
- if (week == null || workoutId == null) return null;
11498
- return week.workouts.find((workout) => workout.id === workoutId) ?? null;
11861
+ var PALETTE = [
11862
+ "#5B9BD5",
11863
+ "#14B8A6",
11864
+ "#F4A736",
11865
+ "#F83030",
11866
+ "#823CA0",
11867
+ "#D4A520",
11868
+ "#406D87",
11869
+ "#43C6B7"
11870
+ ];
11871
+ function scaleValue(v, scale) {
11872
+ if (v <= 0) return 0;
11873
+ if (scale === "sqrt") return Math.sqrt(v);
11874
+ if (scale === "log") return Math.log10(v + 1);
11875
+ return v;
11499
11876
  }
11500
- function buildBreadcrumbs(meso, weekNumber, workout) {
11501
- const crumbs = [{ key: "meso", label: meso.name, level: "meso" }];
11502
- if (weekNumber != null) {
11503
- crumbs.push({ key: "week", label: `Week ${weekNumber}`, level: "week" });
11504
- }
11505
- if (workout != null) {
11506
- crumbs.push({ key: "workout", label: workout.name, level: "workout" });
11877
+ function worstRatio(row, side) {
11878
+ const sum = row.reduce((s, r) => s + r.area, 0);
11879
+ const thickness = sum / side;
11880
+ let worst = 0;
11881
+ for (const r of row) {
11882
+ const len = r.area / thickness;
11883
+ const ratio = Math.max(thickness / len, len / thickness);
11884
+ if (ratio > worst) worst = ratio;
11507
11885
  }
11508
- return crumbs;
11886
+ return worst;
11509
11887
  }
11510
- function Breadcrumbs2({ crumbs, onNavigate }) {
11511
- return /* @__PURE__ */ jsx(
11512
- reactNative.View,
11513
- {
11514
- className: "flex-row items-center flex-wrap",
11515
- style: { gap: 4 },
11516
- testID: "program-planning-page-breadcrumbs",
11517
- children: crumbs.map((crumb, index) => {
11518
- const isLast = index === crumbs.length - 1;
11519
- return /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-center", style: { gap: 4 }, children: [
11520
- index > 0 && /* @__PURE__ */ jsx(reactNative.Text, { style: { fontSize: 12, color: TEXT_TERTIARY7 }, children: "\u203A" }),
11521
- /* @__PURE__ */ jsx(
11522
- reactNative.Pressable,
11523
- {
11524
- onPress: () => onNavigate(crumb.level),
11525
- disabled: isLast,
11526
- accessibilityRole: "button",
11527
- testID: `program-planning-page-crumb-${crumb.key}`,
11528
- children: /* @__PURE__ */ jsx(
11529
- reactNative.Text,
11530
- {
11531
- style: {
11532
- fontSize: 12,
11533
- fontFamily: "Inter, sans-serif",
11534
- fontWeight: isLast ? "700" : "500",
11535
- color: isLast ? TEXT_PRIMARY7 : BRAND_PRIMARY11
11536
- },
11537
- children: crumb.label
11538
- }
11539
- )
11540
- }
11541
- )
11542
- ] }, crumb.key);
11543
- })
11888
+ function squarify(data, w, h) {
11889
+ const total = data.reduce((s, d) => s + d.value, 0);
11890
+ if (total <= 0 || w <= 0 || h <= 0) return [];
11891
+ const scaled = data.map((d) => ({ datum: d, area: d.value / total * w * h }));
11892
+ const out = [];
11893
+ let rx = 0;
11894
+ let ry = 0;
11895
+ let rw = w;
11896
+ let rh = h;
11897
+ let i = 0;
11898
+ while (i < scaled.length) {
11899
+ const vertical = rw >= rh;
11900
+ const side = vertical ? rh : rw;
11901
+ let row = [];
11902
+ let bestWorst = Infinity;
11903
+ let j = i;
11904
+ while (j < scaled.length) {
11905
+ const trial = [...row, scaled[j]];
11906
+ const worst = worstRatio(trial, side);
11907
+ if (row.length > 0 && worst > bestWorst) break;
11908
+ row = trial;
11909
+ bestWorst = worst;
11910
+ j++;
11544
11911
  }
11545
- );
11546
- }
11547
- function toWeekRow(week, meso, onSelectWeek) {
11548
- const workouts = week.workouts.map((workout) => ({
11549
- name: workout.name,
11550
- status: WORKOUT_PILL_STATUS[workout.status],
11551
- onPress: () => onSelectWeek(week.weekNumber)
11552
- }));
11553
- return {
11554
- weekNumber: week.weekNumber,
11555
- totalWeeks: meso.weekCount,
11556
- workouts,
11557
- intensityLevel: week.intensityLevel,
11558
- intensityThreshold: week.intensityThreshold,
11559
- isCurrent: week.isCurrent ?? week.weekNumber === meso.currentWeek,
11560
- isDeload: week.isDeload
11561
- };
11912
+ const rowArea = row.reduce((s, r) => s + r.area, 0);
11913
+ const thickness = rowArea / side;
11914
+ let along = vertical ? ry : rx;
11915
+ for (const r of row) {
11916
+ const len = r.area / thickness;
11917
+ out.push(
11918
+ vertical ? { x: rx, y: along, w: thickness, h: len, datum: r.datum } : { x: along, y: ry, w: len, h: thickness, datum: r.datum }
11919
+ );
11920
+ along += len;
11921
+ }
11922
+ if (vertical) {
11923
+ rx += thickness;
11924
+ rw -= thickness;
11925
+ } else {
11926
+ ry += thickness;
11927
+ rh -= thickness;
11928
+ }
11929
+ i = j;
11930
+ }
11931
+ return out;
11562
11932
  }
11563
- function MesoLevel({ mesos, activeMesoId, onSelectMeso, onSelectWeek }) {
11564
- return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 12 }, testID: "program-planning-page-meso-level", children: mesos.map((meso) => {
11565
- const expanded = meso.id === activeMesoId;
11933
+ function Treemap({
11934
+ data,
11935
+ width,
11936
+ height,
11937
+ scale = "sqrt",
11938
+ maxTiles = 60,
11939
+ onPress,
11940
+ selectedId,
11941
+ className,
11942
+ ...props
11943
+ }) {
11944
+ const clean = data.filter((d) => d.value > 0).sort((a, b) => b.value - a.value).map((d, i) => ({ ...d, color: d.color ?? PALETTE[i % PALETTE.length] }));
11945
+ let tiles = clean;
11946
+ if (clean.length > maxTiles) {
11947
+ const head = clean.slice(0, maxTiles - 1);
11948
+ const restValue = clean.slice(maxTiles - 1).reduce((s, d) => s + d.value, 0);
11949
+ tiles = [
11950
+ ...head,
11951
+ { id: "__more__", value: restValue, color: "#3a3a3a", label: `+${clean.length - (maxTiles - 1)} more` }
11952
+ ];
11953
+ }
11954
+ const weighted = tiles.map((d) => ({ ...d, value: scaleValue(d.value, scale) }));
11955
+ const rects = squarify(weighted, width, height);
11956
+ return /* @__PURE__ */ jsx(reactNative.View, { className: cn("relative", className), style: { width, height }, ...props, children: rects.map((r) => {
11957
+ const selected = r.datum.id === selectedId;
11958
+ const labelFits = r.w > 54 && r.h > 22;
11959
+ const pressable = r.datum.id !== "__more__";
11566
11960
  return /* @__PURE__ */ jsx(
11567
- MesoCard,
11961
+ reactNative.Pressable,
11568
11962
  {
11569
- name: meso.name,
11570
- goal: meso.goal,
11571
- split: meso.split,
11572
- weekRange: meso.weekRange,
11573
- currentWeek: meso.currentWeek,
11574
- totalWeeks: meso.weekCount,
11575
- weeks: expanded ? meso.weeks.map((week) => toWeekRow(week, meso, onSelectWeek)) : [],
11576
- volumeHeatmap: meso.volumeHeatmap,
11577
- expanded,
11578
- onToggle: () => onSelectMeso(meso.id),
11579
- highlighted: expanded
11963
+ testID: `treemap-tile-${r.datum.id}`,
11964
+ accessibilityLabel: r.datum.label ?? r.datum.id,
11965
+ onPress: pressable ? () => onPress?.(r.datum.id) : void 0,
11966
+ style: {
11967
+ position: "absolute",
11968
+ left: r.x,
11969
+ top: r.y,
11970
+ width: Math.max(0, r.w - 2),
11971
+ height: Math.max(0, r.h - 2),
11972
+ backgroundColor: r.datum.color,
11973
+ borderRadius: 3,
11974
+ opacity: selected ? 1 : 0.9,
11975
+ borderWidth: selected ? 2 : 0,
11976
+ borderColor: "#ffffff",
11977
+ padding: 4,
11978
+ overflow: "hidden"
11979
+ },
11980
+ children: labelFits && /* @__PURE__ */ jsx(reactNative.Text, { numberOfLines: 2, className: "text-[10px] font-semibold text-[#0b0b0b]", children: r.datum.label ?? r.datum.id })
11580
11981
  },
11581
- meso.id
11982
+ r.datum.id
11582
11983
  );
11583
11984
  }) });
11584
11985
  }
11585
- function WeekLevel({ week, onSelectWorkout }) {
11586
- return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 10 }, testID: "program-planning-page-week-level", children: week.workouts.map((workout) => /* @__PURE__ */ jsx(
11587
- WorkoutCard,
11588
- {
11589
- name: workout.name,
11590
- date: workout.date,
11591
- duration: workout.duration,
11592
- status: workout.status,
11593
- muscleGroups: workout.muscleGroups,
11594
- totalSets: workout.totalSets,
11595
- totalVolume: workout.totalVolume,
11596
- unit: workout.unit,
11597
- exercises: workout.exercises,
11598
- expanded: false,
11599
- onToggle: () => onSelectWorkout(workout.id)
11600
- },
11601
- workout.id
11602
- )) });
11986
+ var PALETTE2 = [
11987
+ "#5B9BD5",
11988
+ "#14B8A6",
11989
+ "#F4A736",
11990
+ "#F83030",
11991
+ "#823CA0",
11992
+ "#D4A520",
11993
+ "#406D87",
11994
+ "#43C6B7"
11995
+ ];
11996
+ var GRID_LINE2 = "rgba(255,255,255,0.07)";
11997
+ var AXIS_LINE = "rgba(255,255,255,0.18)";
11998
+ var DIAGONAL_LINE = "rgba(255,255,255,0.28)";
11999
+ var DEFAULT_R = 6;
12000
+ var PLOT_LEFT2 = 40;
12001
+ var PLOT_RIGHT = 12;
12002
+ var PLOT_TOP = 12;
12003
+ var PLOT_BOTTOM = 34;
12004
+ var TICK_COUNT = 5;
12005
+ function domainOf(values, min, max) {
12006
+ const lo = min ?? (values.length ? Math.min(...values) : 0);
12007
+ const hi = max ?? (values.length ? Math.max(...values) : 1);
12008
+ if (hi > lo) return { min: lo, max: hi };
12009
+ const pad = Math.abs(hi) * 0.1 || 0.5;
12010
+ return { min: lo - pad, max: hi + pad };
11603
12011
  }
11604
- function WorkoutLevel({ workout, expandedExercise, onToggleExercise }) {
11605
- const exercises = workout.exercises.map((exercise, index) => ({
11606
- ...exercise,
11607
- state: exercise.state === "upcoming" ? exercise.state : index === expandedExercise ? "expanded" : "collapsed",
11608
- onToggle: () => onToggleExercise(index)
11609
- }));
11610
- return /* @__PURE__ */ jsx(reactNative.View, { testID: "program-planning-page-workout-level", children: /* @__PURE__ */ jsx(
11611
- WorkoutCard,
11612
- {
11613
- name: workout.name,
11614
- date: workout.date,
11615
- duration: workout.duration,
11616
- status: workout.status,
11617
- muscleGroups: workout.muscleGroups,
11618
- totalSets: workout.totalSets,
11619
- totalVolume: workout.totalVolume,
11620
- unit: workout.unit,
11621
- exercises,
11622
- expanded: true,
11623
- onToggle: () => void 0
11624
- }
11625
- ) });
12012
+ function ticksOf(d, count) {
12013
+ return Array.from({ length: count }, (_, i) => d.min + (d.max - d.min) * i / (count - 1));
11626
12014
  }
11627
- function ProgramPlanningPage({
11628
- title = "Program Plan",
11629
- mesos,
11630
- className,
11631
- ...props
12015
+ function formatTick(v) {
12016
+ if (Number.isInteger(v)) return `${v}`;
12017
+ return v.toFixed(Math.abs(v) < 1 ? 2 : 1);
12018
+ }
12019
+ function LineSegment({
12020
+ x1,
12021
+ y1,
12022
+ x2,
12023
+ y2,
12024
+ color,
12025
+ testID
11632
12026
  }) {
11633
- const [activeMesoId, setActiveMesoId] = React24.useState(mesos[0]?.id ?? null);
11634
- const [selectedWeek, setSelectedWeek] = React24.useState(null);
11635
- const [selectedWorkoutId, setSelectedWorkoutId] = React24.useState(null);
11636
- const [expandedExercise, setExpandedExercise] = React24.useState(null);
11637
- const progressMesos = React24.useMemo(() => toProgressBarMesos(mesos), [mesos]);
11638
- const activeMeso = findMeso(mesos, activeMesoId);
11639
- const level = deriveNavLevel({ weekNumber: selectedWeek, workoutId: selectedWorkoutId });
11640
- const activeWeek = findWeek(activeMeso, selectedWeek);
11641
- const activeWorkout = findWorkout(activeWeek, selectedWorkoutId);
11642
- const selectMeso = (id) => {
11643
- setActiveMesoId(id);
11644
- setSelectedWeek(null);
11645
- setSelectedWorkoutId(null);
11646
- };
11647
- const selectWeek = (weekNumber) => {
11648
- setSelectedWeek(weekNumber);
11649
- setSelectedWorkoutId(null);
11650
- setExpandedExercise(null);
11651
- };
11652
- const selectWorkout = (id) => {
11653
- setSelectedWorkoutId(id);
11654
- setExpandedExercise(null);
11655
- };
11656
- const toggleExercise = (index) => setExpandedExercise((current) => current === index ? null : index);
11657
- const navigateTo = (target) => {
11658
- if (target === "meso") selectMeso(activeMesoId ?? mesos[0]?.id);
11659
- else if (target === "week" && selectedWeek != null) selectWeek(selectedWeek);
11660
- };
11661
- return /* @__PURE__ */ jsx(
11662
- reactNative.View,
11663
- {
11664
- className,
11665
- style: { width: 390, backgroundColor: PAGE_BG },
11666
- accessibilityRole: "main",
11667
- "aria-label": title,
11668
- testID: "program-planning-page",
11669
- ...props,
11670
- children: /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 16, gap: 14 }, testID: "program-planning-page-content", children: [
11671
- /* @__PURE__ */ jsx(
11672
- reactNative.Text,
11673
- {
11674
- accessibilityRole: "header",
11675
- style: {
11676
- fontSize: 20,
11677
- fontFamily: '"Space Grotesk", sans-serif',
11678
- fontWeight: "700",
11679
- color: TEXT_PRIMARY7
11680
- },
11681
- testID: "program-planning-page-title",
11682
- children: title
11683
- }
11684
- ),
11685
- /* @__PURE__ */ jsx(reactNative.View, { style: { marginHorizontal: -16 }, children: /* @__PURE__ */ jsx(
11686
- MesoProgressBar,
11687
- {
11688
- mesos: progressMesos,
11689
- activeMesoId,
11690
- onMesoPress: selectMeso
11691
- }
11692
- ) }),
11693
- activeMeso != null && /* @__PURE__ */ jsx(
11694
- Breadcrumbs2,
11695
- {
11696
- crumbs: buildBreadcrumbs(activeMeso, selectedWeek, activeWorkout),
11697
- onNavigate: navigateTo
11698
- }
11699
- ),
11700
- /* @__PURE__ */ jsxs(
11701
- reactNative.View,
11702
- {
11703
- style: {
11704
- backgroundColor: SURFACE_ELEVATED,
11705
- borderWidth: 1,
11706
- borderColor: BORDER_DEFAULT3,
11707
- borderRadius: 12,
11708
- padding: 12
11709
- },
11710
- testID: "program-planning-page-body",
11711
- children: [
11712
- level === "meso" && /* @__PURE__ */ jsx(
11713
- MesoLevel,
11714
- {
11715
- mesos,
11716
- activeMesoId,
11717
- onSelectMeso: selectMeso,
11718
- onSelectWeek: selectWeek
11719
- }
11720
- ),
11721
- level === "week" && activeWeek != null && /* @__PURE__ */ jsx(WeekLevel, { week: activeWeek, onSelectWorkout: selectWorkout }),
11722
- level === "workout" && activeWorkout != null && /* @__PURE__ */ jsx(
11723
- WorkoutLevel,
11724
- {
11725
- workout: activeWorkout,
11726
- expandedExercise,
11727
- onToggleExercise: toggleExercise
11728
- }
11729
- )
11730
- ]
11731
- }
11732
- )
11733
- ] })
11734
- }
11735
- );
11736
- }
11737
- var SURFACE_ELEVATED2 = "var(--color-surface-elevated)";
11738
- var BORDER_DEFAULT4 = "var(--color-border-default)";
11739
- var BRAND_PRIMARY12 = "#FF7900";
11740
- var TEXT_PRIMARY8 = "var(--color-text-primary)";
11741
- var TEXT_SECONDARY6 = "var(--color-text-secondary)";
11742
- var TEXT_TERTIARY8 = "var(--color-text-tertiary)";
11743
- var PAGE_BG2 = "var(--color-background-base)";
11744
- var CHART_WIDTH = 326;
11745
- var CHART_HEIGHT = 150;
11746
- var EXERCISE_DETAIL_TABS = [
11747
- { key: "progress", label: "Progress" },
11748
- { key: "history", label: "History" },
11749
- { key: "advanced", label: "Advanced" }
11750
- ];
11751
- function deriveExerciseStats(entries) {
11752
- let bestE1rm = null;
11753
- let prCount = 0;
11754
- for (const entry of entries) {
11755
- if (entry.e1rm != null && (bestE1rm == null || entry.e1rm.value > bestE1rm)) {
11756
- bestE1rm = entry.e1rm.value;
11757
- }
11758
- if (entry.isPR) prCount += 1;
11759
- }
11760
- return { sessions: entries.length, bestE1rm, prCount };
11761
- }
11762
- function toExerciseCardProps(entry, expanded, onToggle) {
11763
- return {
11764
- name: entry.title,
11765
- state: expanded ? "expanded" : "collapsed",
11766
- onToggle,
11767
- summary: entry.summary,
11768
- e1rm: entry.e1rm,
11769
- isPR: entry.isPR,
11770
- tempo: entry.tempo,
11771
- sets: expanded ? entry.sets : void 0
11772
- };
11773
- }
11774
- function summarizeVbt(sets) {
11775
- const allReps = sets.flatMap((set) => set.velocities);
11776
- const meanVelocity = calculateMeanVelocity(allReps);
11777
- let velocityLoss = 0;
11778
- for (const set of sets) {
11779
- velocityLoss = Math.max(velocityLoss, calculateVelocityLoss(set.velocities));
11780
- }
11781
- return { meanVelocity, velocityLoss };
11782
- }
11783
- function TabBar({ active, onSelect }) {
11784
- return /* @__PURE__ */ jsx(
11785
- reactNative.View,
11786
- {
11787
- className: "flex-row",
11788
- style: {
11789
- borderBottomWidth: 1,
11790
- borderBottomColor: BORDER_DEFAULT4
11791
- },
11792
- accessibilityRole: "tablist",
11793
- testID: "exercise-detail-page-tabs",
11794
- children: EXERCISE_DETAIL_TABS.map(({ key, label }) => {
11795
- const isActive = key === active;
11796
- return /* @__PURE__ */ jsx(
11797
- reactNative.Pressable,
11798
- {
11799
- onPress: () => onSelect(key),
11800
- accessibilityRole: "tab",
11801
- accessibilityState: { selected: isActive },
11802
- style: {
11803
- flex: 1,
11804
- alignItems: "center",
11805
- paddingVertical: 10,
11806
- borderBottomWidth: 2,
11807
- borderBottomColor: isActive ? BRAND_PRIMARY12 : "transparent"
11808
- },
11809
- testID: `exercise-detail-page-tab-${key}`,
11810
- children: /* @__PURE__ */ jsx(
11811
- reactNative.Text,
11812
- {
11813
- style: {
11814
- fontSize: 13,
11815
- fontFamily: "Inter, sans-serif",
11816
- fontWeight: isActive ? "700" : "500",
11817
- color: isActive ? TEXT_PRIMARY8 : TEXT_TERTIARY8
11818
- },
11819
- children: label
11820
- }
11821
- )
11822
- },
11823
- key
11824
- );
11825
- })
11826
- }
11827
- );
11828
- }
11829
- var STAT_CARDS = [
11830
- { key: "sessions", label: "Sessions" },
11831
- { key: "bestE1rm", label: "Best e1RM" },
11832
- { key: "prCount", label: "PRs" }
11833
- ];
11834
- function StatStrip({ stats, unit }) {
11835
- return /* @__PURE__ */ jsx(reactNative.View, { className: "flex-row", style: { gap: 8 }, testID: "exercise-detail-page-stats", children: STAT_CARDS.map(({ key, label }) => {
11836
- const raw = stats[key];
11837
- const value = key === "bestE1rm" ? raw == null ? "\u2014" : `${raw} ${unit}` : `${raw}`;
11838
- return /* @__PURE__ */ jsxs(
11839
- reactNative.View,
11840
- {
11841
- style: {
11842
- flex: 1,
11843
- padding: 12,
11844
- borderRadius: 10,
11845
- backgroundColor: SURFACE_ELEVATED2,
11846
- borderWidth: 1,
11847
- borderColor: BORDER_DEFAULT4
11848
- },
11849
- testID: `exercise-detail-page-stat-${key}`,
11850
- children: [
11851
- /* @__PURE__ */ jsx(
11852
- reactNative.Text,
11853
- {
11854
- style: {
11855
- fontSize: 18,
11856
- fontFamily: '"Space Grotesk", sans-serif',
11857
- fontWeight: "700",
11858
- color: TEXT_PRIMARY8
11859
- },
11860
- children: value
11861
- }
11862
- ),
11863
- /* @__PURE__ */ jsx(
11864
- reactNative.Text,
11865
- {
11866
- style: {
11867
- marginTop: 2,
11868
- fontSize: 10,
11869
- fontFamily: "Inter, sans-serif",
11870
- fontWeight: "600",
11871
- letterSpacing: 0.4,
11872
- textTransform: "uppercase",
11873
- color: TEXT_TERTIARY8
11874
- },
11875
- children: label
11876
- }
11877
- )
11878
- ]
11879
- },
11880
- key
11881
- );
11882
- }) });
11883
- }
11884
- function SectionCard({
11885
- title,
11886
- children,
11887
- testID
11888
- }) {
11889
- return /* @__PURE__ */ jsxs(
11890
- reactNative.View,
11891
- {
11892
- style: {
11893
- backgroundColor: SURFACE_ELEVATED2,
11894
- borderWidth: 1,
11895
- borderColor: BORDER_DEFAULT4,
11896
- borderRadius: 12,
11897
- padding: 12,
11898
- gap: 10
11899
- },
11900
- testID,
11901
- children: [
11902
- /* @__PURE__ */ jsx(
11903
- reactNative.Text,
11904
- {
11905
- style: {
11906
- fontSize: 11,
11907
- fontFamily: "Inter, sans-serif",
11908
- fontWeight: "600",
11909
- letterSpacing: 0.6,
11910
- textTransform: "uppercase",
11911
- color: TEXT_TERTIARY8
11912
- },
11913
- children: title
11914
- }
11915
- ),
11916
- children
11917
- ]
11918
- }
11919
- );
11920
- }
11921
- function EntryList({ entries, expandedId, onToggle, emptyLabel, testID }) {
11922
- if (entries.length === 0) {
11923
- return /* @__PURE__ */ jsx(
11924
- reactNative.Text,
11925
- {
11926
- style: { fontSize: 12, fontFamily: "Inter, sans-serif", color: TEXT_TERTIARY8 },
11927
- testID: `${testID}-empty`,
11928
- children: emptyLabel
11929
- }
11930
- );
11931
- }
11932
- return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 8 }, testID, children: entries.map((entry) => /* @__PURE__ */ jsx(
11933
- ExerciseCard,
11934
- {
11935
- ...toExerciseCardProps(entry, entry.id === expandedId, () => onToggle(entry.id))
11936
- },
11937
- entry.id
11938
- )) });
11939
- }
11940
- function VbtBreakdown({ sets }) {
11941
- return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 10 }, testID: "exercise-detail-page-vbt-breakdown", children: sets.map((set) => {
11942
- const mean = calculateMeanVelocity(set.velocities);
11943
- const loss = calculateVelocityLoss(set.velocities);
11944
- return /* @__PURE__ */ jsxs(
11945
- reactNative.View,
11946
- {
11947
- className: "flex-row items-center",
11948
- style: { gap: 10 },
11949
- testID: "exercise-detail-page-vbt-set",
11950
- children: [
11951
- /* @__PURE__ */ jsx(
11952
- reactNative.Text,
11953
- {
11954
- style: {
11955
- width: 44,
11956
- fontSize: 11,
11957
- fontFamily: "Inter, sans-serif",
11958
- fontWeight: "600",
11959
- color: TEXT_SECONDARY6
11960
- },
11961
- children: set.label
11962
- }
11963
- ),
11964
- /* @__PURE__ */ jsx(reactNative.View, { className: "flex-1", style: { height: 8, justifyContent: "center" }, children: /* @__PURE__ */ jsx(VelocityStrip, { velocities: set.velocities, variant: "mini" }) }),
11965
- /* @__PURE__ */ jsx(
11966
- reactNative.Text,
11967
- {
11968
- style: {
11969
- fontSize: 11,
11970
- fontFamily: "Inter, sans-serif",
11971
- fontVariant: ["tabular-nums"],
11972
- color: TEXT_TERTIARY8
11973
- },
11974
- children: `${mean.toFixed(2)} \xB7 -${loss}%`
11975
- }
11976
- )
11977
- ]
11978
- },
11979
- set.label
11980
- );
11981
- }) });
11982
- }
11983
- function VbtSummaryRow({ summary }) {
11984
- const cells = [
11985
- { label: "Mean Velocity", value: `${summary.meanVelocity.toFixed(2)} m/s` },
11986
- { label: "Peak Vel. Loss", value: `-${summary.velocityLoss}%` }
11987
- ];
11988
- return /* @__PURE__ */ jsx(reactNative.View, { className: "flex-row", style: { gap: 8 }, testID: "exercise-detail-page-vbt-summary", children: cells.map((cell) => /* @__PURE__ */ jsxs(
11989
- reactNative.View,
11990
- {
11991
- style: {
11992
- flex: 1,
11993
- padding: 12,
11994
- borderRadius: 10,
11995
- backgroundColor: SURFACE_ELEVATED2,
11996
- borderWidth: 1,
11997
- borderColor: BORDER_DEFAULT4
11998
- },
11999
- children: [
12000
- /* @__PURE__ */ jsx(
12001
- reactNative.Text,
12002
- {
12003
- style: {
12004
- fontSize: 16,
12005
- fontFamily: '"Space Grotesk", sans-serif',
12006
- fontWeight: "700",
12007
- color: TEXT_PRIMARY8
12008
- },
12009
- children: cell.value
12010
- }
12011
- ),
12012
- /* @__PURE__ */ jsx(
12013
- reactNative.Text,
12014
- {
12015
- style: {
12016
- marginTop: 2,
12017
- fontSize: 10,
12018
- fontFamily: "Inter, sans-serif",
12019
- fontWeight: "600",
12020
- letterSpacing: 0.4,
12021
- textTransform: "uppercase",
12022
- color: TEXT_TERTIARY8
12023
- },
12024
- children: cell.label
12025
- }
12026
- )
12027
- ]
12028
- },
12029
- cell.label
12030
- )) });
12031
- }
12032
- function ExerciseDetailPage({
12033
- exercise,
12034
- meso,
12035
- trend,
12036
- progression,
12037
- history,
12038
- vbt,
12039
- className,
12040
- ...props
12041
- }) {
12042
- const [tab, setTab] = React24.useState("progress");
12043
- const [expandedProgressId, setExpandedProgressId] = React24.useState(null);
12044
- const [expandedHistoryId, setExpandedHistoryId] = React24.useState(null);
12045
- const stats = React24.useMemo(() => deriveExerciseStats(history), [history]);
12046
- const vbtSummary = React24.useMemo(() => summarizeVbt(vbt.sets), [vbt.sets]);
12047
- const toggle = (setId) => (id) => setId((current) => current === id ? null : id);
12048
- return /* @__PURE__ */ jsx(
12049
- reactNative.View,
12050
- {
12051
- className,
12052
- style: { width: 390, backgroundColor: PAGE_BG2 },
12053
- accessibilityRole: "main",
12054
- "aria-label": exercise.name,
12055
- testID: "exercise-detail-page",
12056
- ...props,
12057
- children: /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 16, gap: 14 }, testID: "exercise-detail-page-content", children: [
12058
- /* @__PURE__ */ jsxs(
12059
- reactNative.View,
12060
- {
12061
- className: "flex-row items-start justify-between",
12062
- style: { gap: 8 },
12063
- testID: "exercise-detail-page-header",
12064
- children: [
12065
- /* @__PURE__ */ jsxs(reactNative.View, { style: { flexShrink: 1 }, children: [
12066
- /* @__PURE__ */ jsx(
12067
- reactNative.Text,
12068
- {
12069
- accessibilityRole: "header",
12070
- style: {
12071
- fontSize: 20,
12072
- fontFamily: '"Space Grotesk", sans-serif',
12073
- fontWeight: "700",
12074
- color: TEXT_PRIMARY8
12075
- },
12076
- testID: "exercise-detail-page-title",
12077
- children: exercise.name
12078
- }
12079
- ),
12080
- /* @__PURE__ */ jsx(
12081
- reactNative.Text,
12082
- {
12083
- style: {
12084
- marginTop: 2,
12085
- fontSize: 12,
12086
- fontFamily: "Inter, sans-serif",
12087
- color: TEXT_SECONDARY6
12088
- },
12089
- testID: "exercise-detail-page-subtitle",
12090
- children: exercise.subtitle
12091
- }
12092
- )
12093
- ] }),
12094
- exercise.currentE1rm != null && /* @__PURE__ */ jsxs(
12095
- reactNative.View,
12096
- {
12097
- style: {
12098
- paddingVertical: 4,
12099
- paddingHorizontal: 10,
12100
- borderRadius: 6,
12101
- backgroundColor: "rgba(255,121,0,0.10)",
12102
- borderWidth: 1,
12103
- borderColor: "rgba(255,121,0,0.25)"
12104
- },
12105
- testID: "exercise-detail-page-e1rm",
12106
- children: [
12107
- /* @__PURE__ */ jsx(
12108
- reactNative.Text,
12109
- {
12110
- style: {
12111
- fontSize: 9,
12112
- fontFamily: "Inter, sans-serif",
12113
- fontWeight: "600",
12114
- letterSpacing: 0.4,
12115
- textTransform: "uppercase",
12116
- color: TEXT_TERTIARY8
12117
- },
12118
- children: "e1RM"
12119
- }
12120
- ),
12121
- /* @__PURE__ */ jsx(
12122
- reactNative.Text,
12123
- {
12124
- style: {
12125
- fontSize: 15,
12126
- fontFamily: '"Space Grotesk", sans-serif',
12127
- fontWeight: "700",
12128
- color: BRAND_PRIMARY12
12129
- },
12130
- children: `${exercise.currentE1rm} ${exercise.unit}`
12131
- }
12132
- )
12133
- ]
12134
- }
12135
- )
12136
- ]
12137
- }
12138
- ),
12139
- /* @__PURE__ */ jsx(TabBar, { active: tab, onSelect: setTab }),
12140
- tab === "progress" && /* @__PURE__ */ jsxs(reactNative.View, { style: { gap: 14 }, testID: "exercise-detail-page-panel-progress", children: [
12141
- /* @__PURE__ */ jsx(MesoStatusCard, { ...meso }),
12142
- /* @__PURE__ */ jsx(StatStrip, { stats, unit: exercise.unit }),
12143
- /* @__PURE__ */ jsx(SectionCard, { title: "Strength Trend", testID: "exercise-detail-page-trend", children: /* @__PURE__ */ jsx(
12144
- StrengthTrendChart,
12145
- {
12146
- data: trend.data,
12147
- projection: trend.projection,
12148
- mesoBoundaries: trend.mesoBoundaries,
12149
- width: CHART_WIDTH,
12150
- height: CHART_HEIGHT,
12151
- unit: exercise.unit,
12152
- animateOnMount: false
12153
- }
12154
- ) }),
12155
- /* @__PURE__ */ jsx(SectionCard, { title: "Week-by-Week", testID: "exercise-detail-page-progression", children: /* @__PURE__ */ jsx(
12156
- EntryList,
12157
- {
12158
- entries: progression,
12159
- expandedId: expandedProgressId,
12160
- onToggle: toggle(setExpandedProgressId),
12161
- emptyLabel: "No progression logged yet",
12162
- testID: "exercise-detail-page-progression-list"
12163
- }
12164
- ) })
12165
- ] }),
12166
- tab === "history" && /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 14 }, testID: "exercise-detail-page-panel-history", children: /* @__PURE__ */ jsx(SectionCard, { title: "All Sessions", testID: "exercise-detail-page-history", children: /* @__PURE__ */ jsx(
12167
- EntryList,
12168
- {
12169
- entries: history,
12170
- expandedId: expandedHistoryId,
12171
- onToggle: toggle(setExpandedHistoryId),
12172
- emptyLabel: "No sessions logged yet",
12173
- testID: "exercise-detail-page-history-list"
12174
- }
12175
- ) }) }),
12176
- tab === "advanced" && /* @__PURE__ */ jsxs(reactNative.View, { style: { gap: 14 }, testID: "exercise-detail-page-panel-advanced", children: [
12177
- /* @__PURE__ */ jsx(SectionCard, { title: "Capacity Band", testID: "exercise-detail-page-capacity", children: /* @__PURE__ */ jsx(
12178
- CapacityBandChart,
12179
- {
12180
- band: vbt.band,
12181
- workouts: vbt.workouts,
12182
- projection: vbt.projection,
12183
- width: CHART_WIDTH,
12184
- height: CHART_HEIGHT
12185
- }
12186
- ) }),
12187
- /* @__PURE__ */ jsx(VbtSummaryRow, { summary: vbtSummary }),
12188
- /* @__PURE__ */ jsx(SectionCard, { title: "Velocity Breakdown", testID: "exercise-detail-page-velocity", children: /* @__PURE__ */ jsx(VbtBreakdown, { sets: vbt.sets }) })
12189
- ] })
12190
- ] })
12191
- }
12192
- );
12193
- }
12194
- var SURFACE_ELEVATED3 = "var(--color-surface-elevated)";
12195
- var BORDER_DEFAULT5 = "var(--color-border-default)";
12196
- var BRAND_PRIMARY13 = "#FF7900";
12197
- var TEXT_PRIMARY9 = "var(--color-text-primary)";
12198
- var TEXT_SECONDARY7 = "var(--color-text-secondary)";
12199
- var TEXT_TERTIARY9 = "var(--color-text-tertiary)";
12200
- var PAGE_BG3 = "var(--color-background-base)";
12201
- function countCompletedSets(exercise) {
12202
- if (exercise.setVelocities != null) return exercise.setVelocities.length;
12203
- if (exercise.status === "completed") return exercise.totalPlannedSets;
12204
- return 0;
12205
- }
12206
- function deriveWorkoutProgress(exercises) {
12207
- let completedExercises = 0;
12208
- let completedSets = 0;
12209
- let totalSets = 0;
12210
- for (const exercise of exercises) {
12211
- if (exercise.status === "completed") completedExercises += 1;
12212
- completedSets += countCompletedSets(exercise);
12213
- totalSets += exercise.totalPlannedSets;
12214
- }
12215
- return {
12216
- completedExercises,
12217
- totalExercises: exercises.length,
12218
- completedSets,
12219
- totalSets,
12220
- fraction: totalSets === 0 ? 0 : completedSets / totalSets
12221
- };
12222
- }
12223
- function findActiveExercise(exercises) {
12224
- return exercises.find((exercise) => exercise.status === "active") ?? null;
12225
- }
12226
- function statusToCardState(status, focused) {
12227
- if (status === "upcoming") return "upcoming";
12228
- return focused ? "expanded" : "collapsed";
12229
- }
12230
- function toActiveCardProps(exercise, focused, onToggle, supersetPosition = null, supersetColor) {
12231
- const state = statusToCardState(exercise.status, focused);
12232
- return {
12233
- name: exercise.name,
12234
- state,
12235
- onToggle,
12236
- summary: exercise.summary,
12237
- e1rm: exercise.e1rm,
12238
- isPR: exercise.isPR,
12239
- tempo: exercise.tempo,
12240
- setVelocities: exercise.setVelocities,
12241
- totalPlannedSets: exercise.totalPlannedSets,
12242
- sets: state === "expanded" ? exercise.sets : void 0,
12243
- prescription: exercise.prescription,
12244
- previousBest: exercise.previousBest,
12245
- supersetPosition,
12246
- supersetColor
12247
- };
12248
- }
12249
- function groupExercises(exercises, supersets) {
12250
- const byId = new Map(supersets.map((superset) => [superset.id, superset]));
12251
- const groups = [];
12252
- for (const exercise of exercises) {
12253
- const superset = exercise.supersetId != null ? byId.get(exercise.supersetId) : void 0;
12254
- const last = groups[groups.length - 1];
12255
- if (superset != null && last?.type === "superset" && last.superset.id === superset.id) {
12256
- last.exercises.push(exercise);
12257
- } else if (superset != null) {
12258
- groups.push({ type: "superset", superset, exercises: [exercise] });
12259
- } else {
12260
- groups.push({ type: "single", exercise });
12261
- }
12262
- }
12263
- return groups;
12264
- }
12265
- function supersetPositionAt(index, length) {
12266
- if (length === 1) return null;
12267
- if (index === 0) return "first";
12268
- if (index === length - 1) return "last";
12269
- return "middle";
12270
- }
12271
- function WorkoutHeader({
12272
- title,
12273
- subtitle,
12274
- progress
12275
- }) {
12276
- return /* @__PURE__ */ jsxs(reactNative.View, { style: { gap: 10 }, testID: "active-workout-page-header", children: [
12277
- /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-start justify-between", style: { gap: 8 }, children: [
12278
- /* @__PURE__ */ jsxs(reactNative.View, { style: { flexShrink: 1 }, children: [
12279
- /* @__PURE__ */ jsx(
12280
- reactNative.Text,
12281
- {
12282
- accessibilityRole: "header",
12283
- style: {
12284
- fontSize: 20,
12285
- fontFamily: '"Space Grotesk", sans-serif',
12286
- fontWeight: "700",
12287
- color: TEXT_PRIMARY9
12288
- },
12289
- testID: "active-workout-page-title",
12290
- children: title
12291
- }
12292
- ),
12293
- subtitle != null && /* @__PURE__ */ jsx(
12294
- reactNative.Text,
12295
- {
12296
- style: {
12297
- marginTop: 2,
12298
- fontSize: 12,
12299
- fontFamily: "Inter, sans-serif",
12300
- color: TEXT_SECONDARY7
12301
- },
12302
- testID: "active-workout-page-subtitle",
12303
- children: subtitle
12304
- }
12305
- )
12306
- ] }),
12307
- /* @__PURE__ */ jsxs(reactNative.View, { style: { alignItems: "flex-end" }, testID: "active-workout-page-progress-count", children: [
12308
- /* @__PURE__ */ jsx(
12309
- reactNative.Text,
12310
- {
12311
- style: {
12312
- fontSize: 15,
12313
- fontFamily: '"Space Grotesk", sans-serif',
12314
- fontWeight: "700",
12315
- color: BRAND_PRIMARY13,
12316
- fontVariant: ["tabular-nums"]
12317
- },
12318
- children: `${progress.completedExercises}/${progress.totalExercises}`
12319
- }
12320
- ),
12321
- /* @__PURE__ */ jsx(
12322
- reactNative.Text,
12323
- {
12324
- style: {
12325
- fontSize: 10,
12326
- fontFamily: "Inter, sans-serif",
12327
- fontWeight: "600",
12328
- letterSpacing: 0.4,
12329
- textTransform: "uppercase",
12330
- color: TEXT_TERTIARY9
12331
- },
12332
- children: `${progress.completedSets}/${progress.totalSets} sets`
12333
- }
12334
- )
12335
- ] })
12336
- ] }),
12337
- /* @__PURE__ */ jsx(
12338
- reactNative.View,
12339
- {
12340
- style: { height: 4, backgroundColor: BORDER_DEFAULT5, borderRadius: 2 },
12341
- testID: "active-workout-page-progress-track",
12342
- children: /* @__PURE__ */ jsx(
12343
- reactNative.View,
12344
- {
12345
- style: {
12346
- height: "100%",
12347
- width: `${Math.round(progress.fraction * 100)}%`,
12348
- backgroundColor: BRAND_PRIMARY13,
12349
- borderRadius: 2
12350
- },
12351
- testID: "active-workout-page-progress-fill"
12352
- }
12353
- )
12354
- }
12355
- )
12356
- ] });
12357
- }
12358
- function ExerciseList({ groups, focusedId, onToggle }) {
12359
- return /* @__PURE__ */ jsx(reactNative.View, { style: { gap: 10 }, testID: "active-workout-page-exercises", children: groups.map((group) => {
12360
- if (group.type === "single") {
12361
- return /* @__PURE__ */ jsx(
12362
- reactNative.View,
12363
- {
12364
- style: {
12365
- backgroundColor: SURFACE_ELEVATED3,
12366
- borderWidth: 1,
12367
- borderColor: BORDER_DEFAULT5,
12368
- borderRadius: 12
12369
- },
12370
- children: /* @__PURE__ */ jsx(
12371
- ExerciseCard,
12372
- {
12373
- ...toActiveCardProps(
12374
- group.exercise,
12375
- group.exercise.id === focusedId,
12376
- () => onToggle(group.exercise.id)
12377
- )
12378
- }
12379
- )
12380
- },
12381
- group.exercise.id
12382
- );
12383
- }
12384
- return /* @__PURE__ */ jsx(
12385
- SupersetWrapper,
12386
- {
12387
- label: group.superset.label,
12388
- color: group.superset.color,
12389
- children: group.exercises.map((exercise, index) => /* @__PURE__ */ jsx(
12390
- ExerciseCard,
12391
- {
12392
- ...toActiveCardProps(
12393
- exercise,
12394
- exercise.id === focusedId,
12395
- () => onToggle(exercise.id),
12396
- supersetPositionAt(index, group.exercises.length),
12397
- group.superset.color
12398
- )
12399
- },
12400
- exercise.id
12401
- ))
12402
- },
12403
- group.superset.id
12404
- );
12405
- }) });
12406
- }
12407
- function ActiveWorkoutPage({
12408
- title = "Active Workout",
12409
- subtitle,
12410
- exercises,
12411
- supersets = [],
12412
- input,
12413
- rest,
12414
- initialResting = false,
12415
- className,
12416
- ...props
12417
- }) {
12418
- const active = findActiveExercise(exercises);
12419
- const [focusedId, setFocusedId] = React24.useState(active?.id ?? null);
12420
- const [resting, setResting] = React24.useState(initialResting);
12421
- const [reps, setReps] = React24.useState(input?.reps ?? "");
12422
- const [weight, setWeight] = React24.useState(input?.weight ?? "");
12423
- const progress = React24.useMemo(() => deriveWorkoutProgress(exercises), [exercises]);
12424
- const groups = React24.useMemo(() => groupExercises(exercises, supersets), [exercises, supersets]);
12425
- const toggleFocus = (id) => setFocusedId((current) => current === id ? null : id);
12426
- const nextSetNumber = active != null ? countCompletedSets(active) + 1 : 1;
12427
- const canRecord = reps.trim() !== "" && weight.trim() !== "";
12428
- const showRest = resting && rest != null;
12429
- const showInput = !showRest && active != null;
12430
- return /* @__PURE__ */ jsxs(
12431
- reactNative.View,
12432
- {
12433
- className,
12434
- style: { position: "relative", width: 390, backgroundColor: PAGE_BG3 },
12435
- accessibilityRole: "main",
12436
- "aria-label": title,
12437
- testID: "active-workout-page",
12438
- ...props,
12439
- children: [
12440
- /* @__PURE__ */ jsxs(reactNative.View, { style: { padding: 16, gap: 14 }, testID: "active-workout-page-content", children: [
12441
- /* @__PURE__ */ jsx(WorkoutHeader, { title, subtitle, progress }),
12442
- /* @__PURE__ */ jsx(ExerciseList, { groups, focusedId, onToggle: toggleFocus })
12443
- ] }),
12444
- (showRest || showInput) && /* @__PURE__ */ jsx(
12445
- reactNative.View,
12446
- {
12447
- style: { borderTopWidth: 1, borderTopColor: BORDER_DEFAULT5 },
12448
- testID: "active-workout-page-bottom-bar",
12449
- children: showRest && rest != null ? /* @__PURE__ */ jsx(
12450
- RestTimer,
12451
- {
12452
- totalSeconds: rest.totalSeconds,
12453
- elapsedMs: rest.elapsedMs,
12454
- nextSetInfo: rest.nextSetInfo,
12455
- onSkip: () => setResting(false),
12456
- onAddTime: () => void 0,
12457
- visible: true
12458
- }
12459
- ) : active != null && /* @__PURE__ */ jsx(
12460
- InputBar,
12461
- {
12462
- exerciseName: active.name,
12463
- setNumber: nextSetNumber,
12464
- totalSets: active.totalPlannedSets,
12465
- reps,
12466
- weight,
12467
- unit: active.unit,
12468
- onRepsChange: setReps,
12469
- onWeightChange: setWeight,
12470
- onRecord: () => {
12471
- if (rest != null) setResting(true);
12472
- },
12473
- canRecord,
12474
- visible: true
12475
- }
12476
- )
12477
- }
12478
- )
12479
- ]
12480
- }
12481
- );
12482
- }
12483
- var PALETTE = [
12484
- "#5B9BD5",
12485
- "#14B8A6",
12486
- "#F4A736",
12487
- "#F83030",
12488
- "#823CA0",
12489
- "#D4A520",
12490
- "#406D87",
12491
- "#43C6B7"
12492
- ];
12493
- function scaleValue(v, scale) {
12494
- if (v <= 0) return 0;
12495
- if (scale === "sqrt") return Math.sqrt(v);
12496
- if (scale === "log") return Math.log10(v + 1);
12497
- return v;
12498
- }
12499
- function worstRatio(row, side) {
12500
- const sum = row.reduce((s, r) => s + r.area, 0);
12501
- const thickness = sum / side;
12502
- let worst = 0;
12503
- for (const r of row) {
12504
- const len = r.area / thickness;
12505
- const ratio = Math.max(thickness / len, len / thickness);
12506
- if (ratio > worst) worst = ratio;
12507
- }
12508
- return worst;
12509
- }
12510
- function squarify(data, w, h) {
12511
- const total = data.reduce((s, d) => s + d.value, 0);
12512
- if (total <= 0 || w <= 0 || h <= 0) return [];
12513
- const scaled = data.map((d) => ({ datum: d, area: d.value / total * w * h }));
12514
- const out = [];
12515
- let rx = 0;
12516
- let ry = 0;
12517
- let rw = w;
12518
- let rh = h;
12519
- let i = 0;
12520
- while (i < scaled.length) {
12521
- const vertical = rw >= rh;
12522
- const side = vertical ? rh : rw;
12523
- let row = [];
12524
- let bestWorst = Infinity;
12525
- let j = i;
12526
- while (j < scaled.length) {
12527
- const trial = [...row, scaled[j]];
12528
- const worst = worstRatio(trial, side);
12529
- if (row.length > 0 && worst > bestWorst) break;
12530
- row = trial;
12531
- bestWorst = worst;
12532
- j++;
12533
- }
12534
- const rowArea = row.reduce((s, r) => s + r.area, 0);
12535
- const thickness = rowArea / side;
12536
- let along = vertical ? ry : rx;
12537
- for (const r of row) {
12538
- const len = r.area / thickness;
12539
- out.push(
12540
- vertical ? { x: rx, y: along, w: thickness, h: len, datum: r.datum } : { x: along, y: ry, w: len, h: thickness, datum: r.datum }
12541
- );
12542
- along += len;
12543
- }
12544
- if (vertical) {
12545
- rx += thickness;
12546
- rw -= thickness;
12547
- } else {
12548
- ry += thickness;
12549
- rh -= thickness;
12550
- }
12551
- i = j;
12552
- }
12553
- return out;
12554
- }
12555
- function Treemap({
12556
- data,
12557
- width,
12558
- height,
12559
- scale = "sqrt",
12560
- maxTiles = 60,
12561
- onPress,
12562
- selectedId,
12563
- className,
12564
- ...props
12565
- }) {
12566
- const clean = data.filter((d) => d.value > 0).sort((a, b) => b.value - a.value).map((d, i) => ({ ...d, color: d.color ?? PALETTE[i % PALETTE.length] }));
12567
- let tiles = clean;
12568
- if (clean.length > maxTiles) {
12569
- const head = clean.slice(0, maxTiles - 1);
12570
- const restValue = clean.slice(maxTiles - 1).reduce((s, d) => s + d.value, 0);
12571
- tiles = [
12572
- ...head,
12573
- { id: "__more__", value: restValue, color: "#3a3a3a", label: `+${clean.length - (maxTiles - 1)} more` }
12574
- ];
12575
- }
12576
- const weighted = tiles.map((d) => ({ ...d, value: scaleValue(d.value, scale) }));
12577
- const rects = squarify(weighted, width, height);
12578
- return /* @__PURE__ */ jsx(reactNative.View, { className: cn("relative", className), style: { width, height }, ...props, children: rects.map((r) => {
12579
- const selected = r.datum.id === selectedId;
12580
- const labelFits = r.w > 54 && r.h > 22;
12581
- const pressable = r.datum.id !== "__more__";
12582
- return /* @__PURE__ */ jsx(
12583
- reactNative.Pressable,
12584
- {
12585
- testID: `treemap-tile-${r.datum.id}`,
12586
- accessibilityLabel: r.datum.label ?? r.datum.id,
12587
- onPress: pressable ? () => onPress?.(r.datum.id) : void 0,
12588
- style: {
12589
- position: "absolute",
12590
- left: r.x,
12591
- top: r.y,
12592
- width: Math.max(0, r.w - 2),
12593
- height: Math.max(0, r.h - 2),
12594
- backgroundColor: r.datum.color,
12595
- borderRadius: 3,
12596
- opacity: selected ? 1 : 0.9,
12597
- borderWidth: selected ? 2 : 0,
12598
- borderColor: "#ffffff",
12599
- padding: 4,
12600
- overflow: "hidden"
12601
- },
12602
- children: labelFits && /* @__PURE__ */ jsx(reactNative.Text, { numberOfLines: 2, className: "text-[10px] font-semibold text-[#0b0b0b]", children: r.datum.label ?? r.datum.id })
12603
- },
12604
- r.datum.id
12605
- );
12606
- }) });
12607
- }
12608
- var PALETTE2 = [
12609
- "#5B9BD5",
12610
- "#14B8A6",
12611
- "#F4A736",
12612
- "#F83030",
12613
- "#823CA0",
12614
- "#D4A520",
12615
- "#406D87",
12616
- "#43C6B7"
12617
- ];
12618
- var GRID_LINE2 = "rgba(255,255,255,0.07)";
12619
- var AXIS_LINE = "rgba(255,255,255,0.18)";
12620
- var DIAGONAL_LINE = "rgba(255,255,255,0.28)";
12621
- var DEFAULT_R = 6;
12622
- var PLOT_LEFT2 = 40;
12623
- var PLOT_RIGHT = 12;
12624
- var PLOT_TOP = 12;
12625
- var PLOT_BOTTOM = 34;
12626
- var TICK_COUNT = 5;
12627
- function domainOf(values, min, max) {
12628
- const lo = min ?? (values.length ? Math.min(...values) : 0);
12629
- const hi = max ?? (values.length ? Math.max(...values) : 1);
12630
- if (hi > lo) return { min: lo, max: hi };
12631
- const pad = Math.abs(hi) * 0.1 || 0.5;
12632
- return { min: lo - pad, max: hi + pad };
12633
- }
12634
- function ticksOf(d, count) {
12635
- return Array.from({ length: count }, (_, i) => d.min + (d.max - d.min) * i / (count - 1));
12636
- }
12637
- function formatTick(v) {
12638
- if (Number.isInteger(v)) return `${v}`;
12639
- return v.toFixed(Math.abs(v) < 1 ? 2 : 1);
12640
- }
12641
- function LineSegment({
12642
- x1,
12643
- y1,
12644
- x2,
12645
- y2,
12646
- color,
12647
- testID
12648
- }) {
12649
- const dx = x2 - x1;
12650
- const dy = y2 - y1;
12651
- const length = Math.sqrt(dx * dx + dy * dy);
12652
- const angle = Math.atan2(dy, dx) * (180 / Math.PI);
12027
+ const dx = x2 - x1;
12028
+ const dy = y2 - y1;
12029
+ const length = Math.sqrt(dx * dx + dy * dy);
12030
+ const angle = Math.atan2(dy, dx) * (180 / Math.PI);
12653
12031
  return /* @__PURE__ */ jsx(
12654
12032
  reactNative.View,
12655
12033
  {
@@ -12704,28 +12082,28 @@ function Scatter({
12704
12082
  testID: "scatter-canvas",
12705
12083
  style: { position: "absolute", top: 0, left: 0, width, height, overflow: "hidden" },
12706
12084
  children: [
12707
- ticksOf(yd, TICK_COUNT).map((t, i) => {
12708
- const y = toY(t);
12085
+ ticksOf(yd, TICK_COUNT).map((t2, i) => {
12086
+ const y = toY(t2);
12709
12087
  return /* @__PURE__ */ jsx(
12710
12088
  reactNative.View,
12711
12089
  {
12712
12090
  accessibilityElementsHidden: true,
12713
12091
  testID: "scatter-gridline-y",
12714
12092
  style: { position: "absolute", left: PLOT_LEFT2, width: innerW, top: y, height: 1, backgroundColor: GRID_LINE2 },
12715
- children: /* @__PURE__ */ jsx(reactNative.Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", left: -PLOT_LEFT2, top: -6, width: PLOT_LEFT2 - 6, textAlign: "right" }, children: formatTick(t) })
12093
+ children: /* @__PURE__ */ jsx(reactNative.Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", left: -PLOT_LEFT2, top: -6, width: PLOT_LEFT2 - 6, textAlign: "right" }, children: formatTick(t2) })
12716
12094
  },
12717
12095
  `y-${i}`
12718
12096
  );
12719
12097
  }),
12720
- ticksOf(xd, TICK_COUNT).map((t, i) => {
12721
- const x = toX(t);
12098
+ ticksOf(xd, TICK_COUNT).map((t2, i) => {
12099
+ const x = toX(t2);
12722
12100
  return /* @__PURE__ */ jsx(
12723
12101
  reactNative.View,
12724
12102
  {
12725
12103
  accessibilityElementsHidden: true,
12726
12104
  testID: "scatter-gridline-x",
12727
12105
  style: { position: "absolute", top: PLOT_TOP, height: innerH, left: x, width: 1, backgroundColor: GRID_LINE2 },
12728
- children: /* @__PURE__ */ jsx(reactNative.Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", top: innerH + 4, left: -16, width: 32, textAlign: "center" }, children: formatTick(t) })
12106
+ children: /* @__PURE__ */ jsx(reactNative.Text, { className: "text-[9px] text-text-tertiary", style: { position: "absolute", top: innerH + 4, left: -16, width: 32, textAlign: "center" }, children: formatTick(t2) })
12729
12107
  },
12730
12108
  `x-${i}`
12731
12109
  );
@@ -12787,24 +12165,24 @@ function Scatter({
12787
12165
  }
12788
12166
  var STATUS_SUCCESS4 = "#14B8A6";
12789
12167
  var STATUS_WARNING4 = "#FFB020";
12790
- var STATUS_ERROR3 = "#D14343";
12168
+ var STATUS_ERROR4 = "#D14343";
12791
12169
  var TRACK = "rgba(255,255,255,0.08)";
12792
12170
  var DEFAULT_THRESHOLDS = [
12793
- { value: 0, color: STATUS_ERROR3 },
12171
+ { value: 0, color: STATUS_ERROR4 },
12794
12172
  { value: 60, color: STATUS_WARNING4 },
12795
12173
  { value: 80, color: STATUS_SUCCESS4 }
12796
12174
  ];
12797
12175
  var ARC_START = 135;
12798
12176
  var ARC_SWEEP = 270;
12799
12177
  var SEGMENTS = 40;
12800
- function clamp013(n) {
12178
+ function clamp012(n) {
12801
12179
  if (Number.isNaN(n)) return 0;
12802
12180
  return Math.max(0, Math.min(1, n));
12803
12181
  }
12804
12182
  function formatValue2(v) {
12805
12183
  return Number.isInteger(v) ? `${v}` : v.toFixed(1);
12806
12184
  }
12807
- function bandColor(fraction, bands, min, max) {
12185
+ function bandColor2(fraction, bands, min, max) {
12808
12186
  const value = min + fraction * (max - min);
12809
12187
  const sorted = [...bands].sort((a, b) => a.value - b.value);
12810
12188
  let color = sorted[0]?.color ?? STATUS_SUCCESS4;
@@ -12842,10 +12220,10 @@ function Gauge2({
12842
12220
  ...props
12843
12221
  }) {
12844
12222
  const span = max - min || 1;
12845
- const fill = clamp013((value - min) / span);
12846
- const activeColor = (f) => color ?? bandColor(f, thresholds, min, max);
12223
+ const fill = clamp012((value - min) / span);
12224
+ const activeColor = (f) => color ?? bandColor2(f, thresholds, min, max);
12847
12225
  const ticks = buildTicks(fill, size, activeColor);
12848
- const displayColor = color ?? bandColor(fill, thresholds, min, max);
12226
+ const displayColor = color ?? bandColor2(fill, thresholds, min, max);
12849
12227
  const tickLength = size * 0.11;
12850
12228
  const tickThickness = Math.max(2, size * 0.9 / SEGMENTS);
12851
12229
  const ariaLabel = `${label ? `${label}: ` : ""}${formatValue2(value)}${unit ?? ""} of ${formatValue2(max)}`;
@@ -12859,23 +12237,23 @@ function Gauge2({
12859
12237
  testID: "gauge",
12860
12238
  ...props,
12861
12239
  children: [
12862
- ticks.map((t) => /* @__PURE__ */ jsx(
12240
+ ticks.map((t2) => /* @__PURE__ */ jsx(
12863
12241
  reactNative.View,
12864
12242
  {
12865
12243
  accessibilityElementsHidden: true,
12866
12244
  testID: "gauge-segment",
12867
12245
  style: {
12868
12246
  position: "absolute",
12869
- left: t.left - tickThickness / 2,
12870
- top: t.top - tickLength / 2,
12247
+ left: t2.left - tickThickness / 2,
12248
+ top: t2.top - tickLength / 2,
12871
12249
  width: tickThickness,
12872
12250
  height: tickLength,
12873
12251
  borderRadius: tickThickness / 2,
12874
- backgroundColor: t.color,
12875
- transform: [{ rotate: t.rotate }]
12252
+ backgroundColor: t2.color,
12253
+ transform: [{ rotate: t2.rotate }]
12876
12254
  }
12877
12255
  },
12878
- t.key
12256
+ t2.key
12879
12257
  )),
12880
12258
  /* @__PURE__ */ jsxs(reactNative.View, { style: { position: "absolute", top: 0, left: 0, width: size, height: size }, className: "items-center justify-center", children: [
12881
12259
  /* @__PURE__ */ jsxs(reactNative.View, { className: "flex-row items-baseline gap-0.5", children: [
@@ -12889,65 +12267,6 @@ function Gauge2({
12889
12267
  );
12890
12268
  }
12891
12269
 
12892
- // src/utils/colors.ts
12893
- function getStatusColor(status) {
12894
- return `var(--color-status-${status})`;
12895
- }
12896
- function getResultColor(result) {
12897
- return `var(--color-result-${result})`;
12898
- }
12899
- function alpha(color, opacity) {
12900
- const clampedOpacity = Math.max(0, Math.min(1, opacity));
12901
- if (color.startsWith("#")) {
12902
- const hex = color.slice(1);
12903
- let r, g, b;
12904
- if (hex.length === 3) {
12905
- r = parseInt(hex[0] + hex[0], 16);
12906
- g = parseInt(hex[1] + hex[1], 16);
12907
- b = parseInt(hex[2] + hex[2], 16);
12908
- } else if (hex.length === 6) {
12909
- r = parseInt(hex.slice(0, 2), 16);
12910
- g = parseInt(hex.slice(2, 4), 16);
12911
- b = parseInt(hex.slice(4, 6), 16);
12912
- } else {
12913
- return color;
12914
- }
12915
- return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
12916
- }
12917
- const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
12918
- if (rgbMatch) {
12919
- const [, r, g, b] = rgbMatch;
12920
- return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
12921
- }
12922
- const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
12923
- if (rgbaMatch) {
12924
- const [, r, g, b] = rgbaMatch;
12925
- return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
12926
- }
12927
- return color;
12928
- }
12929
- function getLuminance(color) {
12930
- if (!color.startsWith("#")) return "dark";
12931
- const hex = color.slice(1);
12932
- let r, g, b;
12933
- if (hex.length === 3) {
12934
- r = parseInt(hex[0] + hex[0], 16);
12935
- g = parseInt(hex[1] + hex[1], 16);
12936
- b = parseInt(hex[2] + hex[2], 16);
12937
- } else if (hex.length === 6) {
12938
- r = parseInt(hex.slice(0, 2), 16);
12939
- g = parseInt(hex.slice(2, 4), 16);
12940
- b = parseInt(hex.slice(4, 6), 16);
12941
- } else {
12942
- return "dark";
12943
- }
12944
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
12945
- return luminance > 0.5 ? "light" : "dark";
12946
- }
12947
- function getContrastText(backgroundColor) {
12948
- return getLuminance(backgroundColor) === "light" ? "#000000" : "#FFFFFF";
12949
- }
12950
-
12951
12270
  // src/utils/form.ts
12952
12271
  function getFieldValidationProps(state) {
12953
12272
  return {
@@ -13040,7 +12359,6 @@ exports.Accordion = Accordion;
13040
12359
  exports.AccordionButton = AccordionButton;
13041
12360
  exports.AccordionItem = AccordionItem;
13042
12361
  exports.AccordionPanel = AccordionPanel;
13043
- exports.ActiveWorkoutPage = ActiveWorkoutPage;
13044
12362
  exports.Alert = Alert;
13045
12363
  exports.AlertDescription = AlertDescription;
13046
12364
  exports.AlertTitle = AlertTitle;
@@ -13081,10 +12399,8 @@ exports.Drawer = Drawer;
13081
12399
  exports.DrawerBody = DrawerBody;
13082
12400
  exports.DrawerFooter = DrawerFooter;
13083
12401
  exports.DrawerHeader = DrawerHeader;
13084
- exports.EXERCISE_DETAIL_TABS = EXERCISE_DETAIL_TABS;
13085
12402
  exports.EmptyState = EmptyState;
13086
12403
  exports.ExerciseCard = ExerciseCard;
13087
- exports.ExerciseDetailPage = ExerciseDetailPage;
13088
12404
  exports.FormActions = FormActions;
13089
12405
  exports.FormField = FormField;
13090
12406
  exports.FormRow = FormRow;
@@ -13137,7 +12453,6 @@ exports.PopoverContent = PopoverContent;
13137
12453
  exports.PopoverTrigger = PopoverTrigger;
13138
12454
  exports.PrBadge = PrBadge;
13139
12455
  exports.PrHistoryModal = PrHistoryModal;
13140
- exports.ProgramPlanningPage = ProgramPlanningPage;
13141
12456
  exports.Progress = Progress;
13142
12457
  exports.ProgressSteps = ProgressSteps;
13143
12458
  exports.Radio = Radio;
@@ -13175,6 +12490,7 @@ exports.StrengthTrendChart = StrengthTrendChart;
13175
12490
  exports.SupersetWrapper = SupersetWrapper;
13176
12491
  exports.Surface = Surface;
13177
12492
  exports.Switch = Switch;
12493
+ exports.TEMPO_PACING = TEMPO_PACING;
13178
12494
  exports.Tab = Tab;
13179
12495
  exports.TabList = TabList;
13180
12496
  exports.TabPanel = TabPanel;
@@ -13187,6 +12503,7 @@ exports.TableHeaderCell = TableHeaderCell;
13187
12503
  exports.TablePagination = TablePagination;
13188
12504
  exports.TableRow = TableRow;
13189
12505
  exports.Tabs = Tabs;
12506
+ exports.TempoBar = TempoBar;
13190
12507
  exports.TempoDisplay = TempoDisplay;
13191
12508
  exports.Toast = Toast;
13192
12509
  exports.ToastProvider = ToastProvider;
@@ -13204,14 +12521,12 @@ exports.WorkoutPill = WorkoutPill;
13204
12521
  exports.alpha = alpha;
13205
12522
  exports.applyThemePreset = applyThemePreset;
13206
12523
  exports.audiobookPreset = audiobookPreset;
13207
- exports.buildBreadcrumbs = buildBreadcrumbs;
13208
12524
  exports.buttonSizes = buttonSizes;
13209
12525
  exports.calculateMeanVelocity = calculateMeanVelocity;
13210
12526
  exports.calculateVelocityLoss = calculateVelocityLoss;
13211
12527
  exports.cn = cn;
13212
12528
  exports.componentElevationRanges = componentElevationRanges;
13213
12529
  exports.composeValidators = composeValidators;
13214
- exports.countCompletedSets = countCompletedSets;
13215
12530
  exports.createFieldId = createFieldId;
13216
12531
  exports.createFlatShadow = createFlatShadow;
13217
12532
  exports.createPressedHoverShadow = createPressedHoverShadow;
@@ -13221,15 +12536,8 @@ exports.createRaisedShadow = createRaisedShadow;
13221
12536
  exports.darkThemeCSSVars = darkThemeCSSVars;
13222
12537
  exports.darken = darken;
13223
12538
  exports.defaultPreset = defaultPreset;
13224
- exports.deriveExerciseStats = deriveExerciseStats;
13225
- exports.deriveNavLevel = deriveNavLevel;
13226
- exports.deriveWorkoutProgress = deriveWorkoutProgress;
13227
12539
  exports.discreteRainbow = discreteRainbow;
13228
12540
  exports.elevationSystem = elevationSystem;
13229
- exports.findActiveExercise = findActiveExercise;
13230
- exports.findMeso = findMeso;
13231
- exports.findWeek = findWeek;
13232
- exports.findWorkout = findWorkout;
13233
12541
  exports.formatDateTime = formatDateTime;
13234
12542
  exports.formatPrescription = formatPrescription;
13235
12543
  exports.formatSignedPct = formatSignedPct;
@@ -13248,12 +12556,13 @@ exports.getLuminance = getLuminance;
13248
12556
  exports.getResultColor = getResultColor;
13249
12557
  exports.getSemanticColors = getSemanticColors;
13250
12558
  exports.getStatusColor = getStatusColor;
12559
+ exports.getTempoFillPct = getTempoFillPct;
12560
+ exports.getTempoPacingState = getTempoPacingState;
13251
12561
  exports.getThemeCSSVars = getThemeCSSVars;
13252
12562
  exports.getValidatedElevation = getValidatedElevation;
13253
12563
  exports.getVelocityZoneColor = getVelocityZoneColor;
13254
12564
  exports.getVelocityZoneName = getVelocityZoneName;
13255
12565
  exports.gluestackConfig = gluestackConfig;
13256
- exports.groupExercises = groupExercises;
13257
12566
  exports.hasMaxLength = hasMaxLength;
13258
12567
  exports.hasMinLength = hasMinLength;
13259
12568
  exports.hexToRgb = hexToRgb;
@@ -13272,6 +12581,7 @@ exports.primitiveShadows = primitiveShadows;
13272
12581
  exports.primitiveSpacing = primitiveSpacing;
13273
12582
  exports.primitiveTypography = primitiveTypography;
13274
12583
  exports.primitiveZIndex = primitiveZIndex;
12584
+ exports.resolveColor = resolveColor;
13275
12585
  exports.rgbToHex = rgbToHex;
13276
12586
  exports.rgbToHsv = rgbToHsv;
13277
12587
  exports.roundRpe = roundRpe;
@@ -13282,11 +12592,6 @@ exports.semanticColorsDark = semanticColorsDark;
13282
12592
  exports.semanticColorsLight = semanticColorsLight;
13283
12593
  exports.semanticTypography = semanticTypography;
13284
12594
  exports.shadowColors = shadowColors;
13285
- exports.statusToCardState = statusToCardState;
13286
- exports.summarizeVbt = summarizeVbt;
13287
- exports.toActiveCardProps = toActiveCardProps;
13288
- exports.toExerciseCardProps = toExerciseCardProps;
13289
- exports.toProgressBarMesos = toProgressBarMesos;
13290
12595
  exports.useTable = useTable;
13291
12596
  exports.useToast = useToast;
13292
12597
  exports.useToolbarButton = useToolbarButton;