@titan-design/react-ui 0.13.0 → 0.14.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 (146) hide show
  1. package/README.md +14 -0
  2. package/dist/bodymap.js +129 -76
  3. package/dist/bodymap.js.map +1 -1
  4. package/dist/bodymap.mjs +129 -76
  5. package/dist/bodymap.mjs.map +1 -1
  6. package/dist/index.d.mts +117 -26
  7. package/dist/index.d.ts +117 -26
  8. package/dist/index.js +677 -450
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +673 -451
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{pages-_3Ajpiqu.d.ts → pages-CsjsUn0G.d.ts} +30 -13
  13. package/dist/{pages-CpW3OB3g.d.mts → pages-ZbT_pD_U.d.mts} +30 -13
  14. package/dist/pages.d.mts +1 -1
  15. package/dist/pages.d.ts +1 -1
  16. package/dist/pages.js +676 -701
  17. package/dist/pages.js.map +1 -1
  18. package/dist/pages.mjs +676 -701
  19. package/dist/pages.mjs.map +1 -1
  20. package/dist/theme/index.d.mts +31 -7
  21. package/dist/theme/index.d.ts +31 -7
  22. package/dist/theme/index.js +84 -20
  23. package/dist/theme/index.js.map +1 -1
  24. package/dist/theme/index.mjs +83 -21
  25. package/dist/theme/index.mjs.map +1 -1
  26. package/dist/theme/tokens-css.js +84 -20
  27. package/dist/theme/tokens-css.js.map +1 -1
  28. package/dist/theme/tokens-css.mjs +84 -20
  29. package/dist/theme/tokens-css.mjs.map +1 -1
  30. package/dist/theme/tokens.d.mts +56 -7
  31. package/dist/theme/tokens.d.ts +56 -7
  32. package/dist/theme/tokens.js +72 -20
  33. package/dist/theme/tokens.js.map +1 -1
  34. package/dist/theme/tokens.mjs +71 -21
  35. package/dist/theme/tokens.mjs.map +1 -1
  36. package/docs/render-testing-pattern.md +130 -0
  37. package/package.json +1 -1
  38. package/src/arch/Architecture.stories.tsx +3 -1
  39. package/src/arch/arch-graph.json +74 -72
  40. package/src/components/custom/ActiveWork/InitiativeBrief.tsx +2 -1
  41. package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +8 -3
  42. package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
  43. package/src/components/custom/Gauge/Gauge.stories.tsx +4 -1
  44. package/src/components/custom/Gauge/Gauge.tsx +3 -1
  45. package/src/components/custom/Scatter/Scatter.stories.tsx +10 -7
  46. package/src/components/custom/Scatter/Scatter.tsx +6 -4
  47. package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
  48. package/src/components/custom/Table/Table.stories.tsx +1 -1
  49. package/src/components/custom/Table/Table.tsx +2 -2
  50. package/src/components/custom/Treemap/Treemap.stories.tsx +13 -10
  51. package/src/components/custom/Treemap/Treemap.tsx +4 -1
  52. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +2 -1
  53. package/src/components/custom/Workout/BaseBadge.stories.tsx +11 -7
  54. package/src/components/custom/Workout/BaseBadge.tsx +3 -2
  55. package/src/components/custom/Workout/BodyMap.tsx +5 -3
  56. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +13 -3
  57. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +5 -3
  58. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +2 -1
  59. package/src/components/custom/Workout/CapacityBandChart.tsx +5 -3
  60. package/src/components/custom/Workout/DeviationBar.test.tsx +5 -2
  61. package/src/components/custom/Workout/DeviationBar.tsx +5 -2
  62. package/src/components/custom/Workout/DualVelocityStrip.stories.tsx +7 -6
  63. package/src/components/custom/Workout/DualVelocityStrip.test.tsx +29 -16
  64. package/src/components/custom/Workout/ExerciseCard.stories.tsx +8 -8
  65. package/src/components/custom/Workout/ExerciseCard.test.tsx +39 -19
  66. package/src/components/custom/Workout/ExerciseCard.tsx +92 -167
  67. package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +89 -6
  68. package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +189 -1
  69. package/src/components/custom/Workout/ExerciseCardHeading.tsx +167 -18
  70. package/src/components/custom/Workout/ExerciseDetailPage.tsx +3 -2
  71. package/src/components/custom/Workout/ExerciseHeading.tsx +192 -47
  72. package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +6 -5
  73. package/src/components/custom/Workout/InputBar.tsx +4 -2
  74. package/src/components/custom/Workout/IntensityBar.tsx +4 -1
  75. package/src/components/custom/Workout/MesoCard.tsx +2 -1
  76. package/src/components/custom/Workout/MesoStatusCard.tsx +11 -10
  77. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +8 -3
  78. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +12 -2
  79. package/src/components/custom/Workout/README.md +58 -12
  80. package/src/components/custom/Workout/ReadinessCheck.tsx +4 -2
  81. package/src/components/custom/Workout/RestTimer.stories.tsx +13 -2
  82. package/src/components/custom/Workout/RestTimer.tsx +4 -2
  83. package/src/components/custom/Workout/SegmentedBar.stories.tsx +16 -12
  84. package/src/components/custom/Workout/SegmentedBar.tsx +4 -3
  85. package/src/components/custom/Workout/SetStrip.stories.tsx +3 -2
  86. package/src/components/custom/Workout/SetsRepsLoad.tsx +23 -9
  87. package/src/components/custom/Workout/Sparkline.stories.tsx +13 -10
  88. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +2 -1
  89. package/src/components/custom/Workout/StrengthTrendChart.tsx +8 -6
  90. package/src/components/custom/Workout/TempoDisplay.stories.tsx +4 -3
  91. package/src/components/custom/Workout/TempoDisplay.tsx +2 -0
  92. package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +3 -2
  93. package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +1 -1
  94. package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +40 -0
  95. package/src/components/custom/Workout/VelocityStrip.test.tsx +48 -6
  96. package/src/components/custom/Workout/VelocityStrip.tsx +23 -14
  97. package/src/components/custom/Workout/ZoneTrack.stories.tsx +3 -1
  98. package/src/components/custom/Workout/exerciseRowState.ts +29 -0
  99. package/src/components/custom/Workout/index.ts +12 -2
  100. package/src/components/custom/Workout/metricText.stories.tsx +9 -8
  101. package/src/components/custom/Workout/metricText.tsx +4 -2
  102. package/src/components/custom/Workout/setHeadingKit.tsx +12 -10
  103. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  104. package/src/components/custom/charts/README.md +5 -0
  105. package/src/components/custom/charts/SetBarChart.test.tsx +16 -0
  106. package/src/components/custom/charts/SetBarChart.tsx +14 -5
  107. package/src/components/custom/charts/flatBarGeometry.test.ts +41 -0
  108. package/src/components/custom/charts/flatBarGeometry.ts +32 -0
  109. package/src/components/custom/stepper/Stepper.tsx +8 -6
  110. package/src/components/ui/alert/Alert.stories.tsx +14 -4
  111. package/src/components/ui/alert/Alert.tsx +24 -3
  112. package/src/components/ui/button/Button.tsx +12 -12
  113. package/src/components/ui/checkbox/Checkbox.tsx +2 -2
  114. package/src/components/ui/data-row/DataRow.stories.tsx +6 -2
  115. package/src/components/ui/drawer/Drawer.tsx +4 -1
  116. package/src/components/ui/icon-box/IconBox.tsx +8 -6
  117. package/src/components/ui/indicator/Indicator.stories.tsx +4 -3
  118. package/src/components/ui/list-item/ListItem.stories.tsx +7 -4
  119. package/src/components/ui/modal/Modal.tsx +2 -0
  120. package/src/components/ui/pill/Pill.stories.tsx +53 -0
  121. package/src/components/ui/pill/Pill.tsx +10 -6
  122. package/src/components/ui/popover/Popover.stories.tsx +1 -1
  123. package/src/components/ui/progress/Progress.tsx +9 -6
  124. package/src/components/ui/radio/Radio.tsx +1 -1
  125. package/src/components/ui/section/Section.stories.tsx +10 -10
  126. package/src/components/ui/select/Select.tsx +2 -1
  127. package/src/components/ui/spinner/Spinner.tsx +3 -1
  128. package/src/components/ui/stack/Stack.stories.tsx +7 -7
  129. package/src/components/ui/surface/Surface.stories.tsx +75 -28
  130. package/src/components/ui/switch/Switch.tsx +1 -1
  131. package/src/components/ui/tabs/Tabs.tsx +4 -2
  132. package/src/components/ui/toast/Toast.tsx +6 -4
  133. package/src/components/ui/toolbar-button/ToolbarButton.tsx +13 -6
  134. package/src/stories/PresetShowcase.stories.tsx +1 -1
  135. package/src/test/no-raw-color.test.ts +39 -0
  136. package/src/theme/Alpha.stories.tsx +103 -0
  137. package/src/theme/ProposedTokensVW82.stories.tsx +414 -0
  138. package/src/theme/config.ts +12 -0
  139. package/src/theme/global.css +15 -3
  140. package/src/theme/subtle-tone-weight.test.ts +163 -0
  141. package/src/theme/tailwind-var-opacity.test.ts +163 -0
  142. package/src/theme/tokens/primitives.ts +33 -0
  143. package/src/theme/tokens/semantic-hex-guard.test.ts +322 -0
  144. package/src/theme/tokens/semantic.ts +56 -20
  145. package/src/utils/colors.alpha-adoption.test.ts +408 -0
  146. package/tailwind.config.js +50 -9
package/README.md CHANGED
@@ -319,6 +319,20 @@ pnpm test:coverage
319
319
  pnpm build
320
320
  ```
321
321
 
322
+ ### Guardrails
323
+
324
+ - `pnpm lint` enforces that every Storybook story's `title` starts with an approved
325
+ top-level group (`Foundations`, `Components`, `Custom`, `Shell`, `Pages`, `Lab`,
326
+ `Docs` — see `eslint-rules/story-title-prefix.js`), so a new story can't invent an
327
+ unlisted sidebar root.
328
+ - `pnpm test` runs `src/arch/arch-graph.freshness.test.ts`, which fails CI when
329
+ `src/arch/arch-graph.json`'s recorded component-barrel hash no longer matches the
330
+ current barrels — i.e. when `pnpm arch:graph` needs to be re-run and committed.
331
+ Full regeneration doesn't run in CI itself: it needs the `@codewatch/cli` CLI
332
+ (unpublished, see AW-118) and sibling checkouts of every consumer in
333
+ `scripts/arch.config.json`, neither of which a CI runner has; running it there
334
+ would silently score every consumer as zero usage instead of failing loudly.
335
+
322
336
  ## Storybook Configuration
323
337
 
324
338
  This package uses Storybook 10 with `@storybook/react-native-web-vite` for proper NativeWind support. Key configuration:
package/dist/bodymap.js CHANGED
@@ -18,8 +18,29 @@ function cn(...inputs) {
18
18
 
19
19
  // src/theme/tokens/primitives.ts
20
20
  var primitiveColors = {
21
- white: "#FFFFFF"};
21
+ white: "#FFFFFF",
22
+ black: "#000000"};
22
23
  var alertRedVivid = "#FF4757";
24
+ var resultPaletteColors = {
25
+ improve: "#4caf50",
26
+ improveDark: "#248a24",
27
+ degrade: "#ef5350",
28
+ degradeDark: "#b30000",
29
+ inconclusive: "#9E9A97"
30
+ };
31
+ var semanticPins = {
32
+ // Cool navy-black; darker and more blue-shifted than any warm greyRamp step.
33
+ textPrimaryLight: "#121828",
34
+ // Cool blue-grey; outside greyRamp's warm (R>=G>=B) scale.
35
+ textSecondaryLight: "#65748B",
36
+ // Pure neutral grey (R=G=B); no warm greyRamp step lands on it exactly.
37
+ backgroundBaseLight: "#EBEBEB",
38
+ // Near-neutral grey, kept one step off backgroundBaseLight by design.
39
+ dividerLight: "#E8E9EB",
40
+ // Periwinkle/indigo; more purple than any primitiveRamps.blue step. Shared by
41
+ // dark-mode text-link, border-focus and border-input-focus.
42
+ focusIndigoDark: "#828DF8"
43
+ };
23
44
  var greyRamp = {
24
45
  50: "#F9F6F3",
25
46
  // L*97.0 W6
@@ -53,6 +74,7 @@ var greyRamp = {
53
74
  var primitiveRamps = {
54
75
  red: {
55
76
  50: "#FFF4F4",
77
+ 400: "#F77175",
56
78
  500: "#E05254",
57
79
  600: "#D14343",
58
80
  // pin
@@ -157,6 +179,12 @@ var semanticColorsLight = {
157
179
  // Text on brand backgrounds (on-*)
158
180
  "on-brand-primary": primitiveColors.white,
159
181
  "on-brand-secondary": primitiveColors.white,
182
+ // Text ON a `-subtle` fill. Light mode keeps today's pairing (the base token) so
183
+ // nothing moves here: light has the same disease mirrored — a light rung on a
184
+ // near-white ramp[50] fill — but correcting it belongs with the light-mode pass
185
+ // in AW-121, not this one.
186
+ "on-brand-primary-subtle": primitiveRamps.orange[400],
187
+ "on-brand-secondary-subtle": primitiveRamps.cyan[600],
160
188
  // Status colors (status-*)
161
189
  "status-success": primitiveRamps.green[300],
162
190
  "status-success-light": primitiveRamps.green[200],
@@ -192,21 +220,26 @@ var semanticColorsLight = {
192
220
  "status-info-subtle": primitiveRamps.blue[50],
193
221
  "status-info-muted": "rgba(33, 150, 243, 0.30)",
194
222
  "status-info-strong": "rgba(33, 150, 243, 0.50)",
223
+ // Text ON a `-subtle` fill — see the on-brand-*-subtle note above.
224
+ "on-status-success-subtle": primitiveRamps.green[300],
225
+ "on-status-error-subtle": primitiveRamps.red[600],
226
+ "on-status-warning-subtle": primitiveRamps.amber[300],
227
+ "on-status-info-subtle": primitiveRamps.blue[500],
195
228
  // Text on status backgrounds (on-status-*)
196
229
  "on-status-success": primitiveColors.white,
197
230
  "on-status-error": primitiveColors.white,
198
231
  "on-status-warning": primitiveColors.white,
199
232
  "on-status-info": primitiveColors.white,
200
233
  // Result/outcome indicators (result-*)
201
- "result-improve": "#4caf50",
234
+ "result-improve": resultPaletteColors.improve,
202
235
  // Green - positive outcome
203
236
  "result-improve-light": "rgba(76, 175, 80, 0.12)",
204
- "result-improve-dark": "#248a24",
205
- "result-degrade": "#ef5350",
237
+ "result-improve-dark": resultPaletteColors.improveDark,
238
+ "result-degrade": resultPaletteColors.degrade,
206
239
  // Red - negative outcome
207
240
  "result-degrade-light": "rgba(239, 83, 80, 0.12)",
208
- "result-degrade-dark": "#b30000",
209
- "result-inconclusive": "#9E9A97",
241
+ "result-degrade-dark": resultPaletteColors.degradeDark,
242
+ "result-inconclusive": resultPaletteColors.inconclusive,
210
243
  // Gray - no clear result
211
244
  "result-inconclusive-light": "rgba(158, 154, 151, 0.12)",
212
245
  "result-neutral": greyRamp[600],
@@ -238,8 +271,8 @@ var semanticColorsLight = {
238
271
  "data-10": discreteRainbow[22],
239
272
  // Dark Orange
240
273
  // Text colors (text-*)
241
- "text-primary": "#121828",
242
- "text-secondary": "#65748B",
274
+ "text-primary": semanticPins.textPrimaryLight,
275
+ "text-secondary": semanticPins.textSecondaryLight,
243
276
  "text-tertiary": greyRamp[400],
244
277
  "text-disabled": "rgba(55, 65, 81, 0.48)",
245
278
  "text-inverse": primitiveColors.white,
@@ -255,7 +288,7 @@ var semanticColorsLight = {
255
288
  "surface-input": greyRamp[50],
256
289
  // Input field background (filled variant)
257
290
  // Background colors (background-*)
258
- "background-base": "#EBEBEB",
291
+ "background-base": semanticPins.backgroundBaseLight,
259
292
  "background-default": primitiveColors.white,
260
293
  "background-subtle": greyRamp[50],
261
294
  // Frame/bezel chrome — top bar + side nav shell, one step below
@@ -286,7 +319,7 @@ var semanticColorsLight = {
286
319
  "interactive-disabled": "rgba(55, 65, 81, 0.12)",
287
320
  "interactive-disabled-text": "rgba(55, 65, 81, 0.26)",
288
321
  // Divider
289
- divider: "#E8E9EB",
322
+ divider: semanticPins.dividerLight,
290
323
  // Avatar default
291
324
  "avatar-background": greyRamp[600],
292
325
  "avatar-text": primitiveColors.white
@@ -304,7 +337,7 @@ var semanticColorsDark = {
304
337
  "brand-secondary": primitiveRamps.cyan[600],
305
338
  "brand-secondary-light": primitiveRamps.cyan[500],
306
339
  "brand-secondary-dark": primitiveRamps.cyan[700],
307
- "brand-secondary-subtle": "rgba(48, 123, 155, 0.12)",
340
+ "brand-secondary-subtle": "rgba(34, 211, 238, 0.12)",
308
341
  "brand-secondary-muted": "rgba(48, 123, 155, 0.30)",
309
342
  "brand-secondary-strong": "rgba(48, 123, 155, 0.50)",
310
343
  "brand-secondary-hover": primitiveRamps.cyan[500],
@@ -312,6 +345,14 @@ var semanticColorsDark = {
312
345
  // Text on brand backgrounds
313
346
  "on-brand-primary": primitiveColors.white,
314
347
  "on-brand-secondary": primitiveColors.white,
348
+ // Text ON a `-subtle` fill. Its own role: `brand-primary` and friends are tuned to
349
+ // carry a white label as a solid fill, which makes the two deepest of them
350
+ // (cyan[600], red[600]) too dark to read as text on a dark plane. Levelling the
351
+ // family at rung 300 puts every tone within OKLCH L 0.769-0.813 instead of
352
+ // 0.550-0.813. Brand is deliberately left at its own orange[400] (operator, AW-133)
353
+ // so the Voltras tone never drifts; it is the one tone under AA on a raised card.
354
+ "on-brand-primary-subtle": primitiveRamps.orange[400],
355
+ "on-brand-secondary-subtle": primitiveRamps.cyan[300],
315
356
  // Status colors
316
357
  "status-success": primitiveRamps.green[300],
317
358
  "status-success-light": primitiveRamps.green[200],
@@ -325,7 +366,10 @@ var semanticColorsDark = {
325
366
  "status-error": primitiveRamps.red[600],
326
367
  "status-error-light": primitiveRamps.red[500],
327
368
  "status-error-dark": primitiveRamps.red[700],
328
- "status-error-subtle": "rgba(209, 67, 67, 0.12)",
369
+ // Alpha 0.08, not the family's 0.12: error's label is red[400], a rung darker than
370
+ // its siblings so it reads RED rather than pink (rung 300 has only 0.121 chroma).
371
+ // Thinning the fill buys back the contrast that extra darkness costs. See AW-133.
372
+ "status-error-subtle": "rgba(247, 113, 117, 0.08)",
329
373
  "status-error-muted": "rgba(209, 67, 67, 0.30)",
330
374
  "status-error-strong": "rgba(209, 67, 67, 0.50)",
331
375
  "status-error-vivid": alertRedVivid,
@@ -343,7 +387,7 @@ var semanticColorsDark = {
343
387
  "status-info": primitiveRamps.blue[500],
344
388
  "status-info-light": primitiveRamps.blue[300],
345
389
  "status-info-dark": primitiveRamps.blue[600],
346
- "status-info-subtle": "rgba(33, 150, 243, 0.12)",
390
+ "status-info-subtle": "rgba(120, 194, 255, 0.12)",
347
391
  "status-info-muted": "rgba(33, 150, 243, 0.30)",
348
392
  "status-info-strong": "rgba(33, 150, 243, 0.50)",
349
393
  // Text on status backgrounds
@@ -351,14 +395,22 @@ var semanticColorsDark = {
351
395
  "on-status-error": primitiveColors.white,
352
396
  "on-status-warning": primitiveColors.white,
353
397
  "on-status-info": primitiveColors.white,
398
+ // Text ON a `-subtle` fill — see the on-brand-*-subtle note above. Error is the
399
+ // second deliberate exception to the rung-300 rule (operator, AW-133): red[300]
400
+ // levelled perfectly but read PINK, because a red that light can only hold 0.121
401
+ // chroma. red[400] carries 0.165 and reads red; its fill is thinned to compensate.
402
+ "on-status-success-subtle": primitiveRamps.green[300],
403
+ "on-status-error-subtle": primitiveRamps.red[400],
404
+ "on-status-warning-subtle": primitiveRamps.amber[300],
405
+ "on-status-info-subtle": primitiveRamps.blue[300],
354
406
  // Result/outcome indicators (result-*)
355
- "result-improve": "#4caf50",
407
+ "result-improve": resultPaletteColors.improve,
356
408
  "result-improve-light": "rgba(76, 175, 80, 0.16)",
357
- "result-improve-dark": "#248a24",
358
- "result-degrade": "#ef5350",
409
+ "result-improve-dark": resultPaletteColors.improveDark,
410
+ "result-degrade": resultPaletteColors.degrade,
359
411
  "result-degrade-light": "rgba(239, 83, 80, 0.16)",
360
- "result-degrade-dark": "#b30000",
361
- "result-inconclusive": "#9E9A97",
412
+ "result-degrade-dark": resultPaletteColors.degradeDark,
413
+ "result-inconclusive": resultPaletteColors.inconclusive,
362
414
  "result-inconclusive-light": "rgba(158, 154, 151, 0.16)",
363
415
  "result-neutral": greyRamp[400],
364
416
  // Text on result backgrounds
@@ -389,7 +441,7 @@ var semanticColorsDark = {
389
441
  "text-tertiary": greyRamp[500],
390
442
  "text-disabled": "rgba(255, 255, 255, 0.38)",
391
443
  "text-inverse": greyRamp[950],
392
- "text-link": "#828DF8",
444
+ "text-link": semanticPins.focusIndigoDark,
393
445
  "text-link-hover": primitiveRamps.blue[400],
394
446
  // Surface colors - dark backgrounds — warm-tapered DERIVED ramp (TD-surface-tokens,
395
447
  // S-1, re-spaced S-3). Shipped verbatim from `deriveSurfaceRamp()` — see
@@ -439,10 +491,10 @@ var semanticColorsDark = {
439
491
  // plane, so it keeps its job without collision.
440
492
  "border-prominent": greyRamp[800],
441
493
  // high-visibility divider
442
- "border-focus": "#828DF8",
494
+ "border-focus": semanticPins.focusIndigoDark,
443
495
  "border-input": greyRamp[700],
444
496
  "border-input-hover": greyRamp[600],
445
- "border-input-focus": "#828DF8",
497
+ "border-input-focus": semanticPins.focusIndigoDark,
446
498
  "border-input-error": primitiveRamps.red[500],
447
499
  // Alpha hairline separators — the primary separation cue (§4/S-2). Self-
448
500
  // normalizing: composites toward white by ~the same amount on ANY plane, so
@@ -517,6 +569,38 @@ function hexToRgb(hex) {
517
569
  return { r, g, b };
518
570
  }
519
571
 
572
+ // src/utils/colors.ts
573
+ function alpha(color, opacity) {
574
+ const clampedOpacity = Math.max(0, Math.min(1, opacity));
575
+ if (color.startsWith("#")) {
576
+ const hex = color.slice(1);
577
+ let r, g, b;
578
+ if (hex.length === 3) {
579
+ r = parseInt(hex[0] + hex[0], 16);
580
+ g = parseInt(hex[1] + hex[1], 16);
581
+ b = parseInt(hex[2] + hex[2], 16);
582
+ } else if (hex.length === 6) {
583
+ r = parseInt(hex.slice(0, 2), 16);
584
+ g = parseInt(hex.slice(2, 4), 16);
585
+ b = parseInt(hex.slice(4, 6), 16);
586
+ } else {
587
+ return color;
588
+ }
589
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
590
+ }
591
+ const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
592
+ if (rgbMatch) {
593
+ const [, r, g, b] = rgbMatch;
594
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
595
+ }
596
+ const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
597
+ if (rgbaMatch) {
598
+ const [, r, g, b] = rgbaMatch;
599
+ return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
600
+ }
601
+ return color;
602
+ }
603
+
520
604
  // src/theme/materials.ts
521
605
  var c = getSemanticColors("dark");
522
606
  function insetWell(tone = c["surface-input"]) {
@@ -867,8 +951,8 @@ var jsxs = wrapJsx(jsxRuntime.jsxs);
867
951
  // src/components/custom/Workout/BodyMap.tsx
868
952
  var Body = BodyHighlighter__default.default.default ?? BodyHighlighter__default.default;
869
953
  var BRAND_PRIMARY = getSemanticColors("dark")["brand-primary"];
870
- var OUTLINE_FILL = "rgba(255,255,255,0.08)";
871
- var OUTLINE_BORDER = "rgba(255,255,255,0.12)";
954
+ var OUTLINE_FILL = alpha(primitiveColors.white, 0.08);
955
+ var OUTLINE_BORDER = alpha(primitiveColors.white, 0.12);
872
956
  var BODY_SCALE = 0.8;
873
957
  function buildSlugParts(data) {
874
958
  const bySlug = /* @__PURE__ */ new Map();
@@ -1044,7 +1128,7 @@ function ViewToggle({ view, onViewChange }) {
1044
1128
  paddingHorizontal: 12,
1045
1129
  paddingVertical: 4,
1046
1130
  borderRadius: 9999,
1047
- backgroundColor: active ? "rgba(255,121,0,0.16)" : "transparent",
1131
+ backgroundColor: active ? alpha(BRAND_PRIMARY, 0.16) : "transparent",
1048
1132
  borderWidth: 1,
1049
1133
  borderColor: active ? BRAND_PRIMARY : resolveColor("hairline-strong")
1050
1134
  },
@@ -1222,13 +1306,16 @@ var toneStyles = {
1222
1306
  // host is, and an alpha fill composites by the same amount on all of them.
1223
1307
  // A `surface-*` fill would vanish on the plane it names. Borderless like the
1224
1308
  // six coloured tones — the fill carries the capsule, not a ring.
1309
+ // Label comes from `on-*-subtle`, not the base tone token: the base is tuned to
1310
+ // carry a white label as a solid fill, which left error and accent unreadable
1311
+ // here (AW-133). The on-subtle tokens level the family at one weight.
1225
1312
  neutral: "bg-hairline-subtle border-transparent text-text-secondary",
1226
- brand: "bg-brand-primary-subtle border-transparent text-brand-primary",
1227
- "brand-secondary": "bg-brand-secondary-subtle border-transparent text-brand-secondary",
1228
- success: "bg-status-success-subtle border-transparent text-status-success",
1229
- warning: "bg-status-warning-subtle border-transparent text-status-warning",
1230
- error: "bg-status-error-subtle border-transparent text-status-error",
1231
- info: "bg-status-info-subtle border-transparent text-status-info"
1313
+ brand: "bg-brand-primary-subtle border-transparent text-on-brand-primary-subtle",
1314
+ "brand-secondary": "bg-brand-secondary-subtle border-transparent text-on-brand-secondary-subtle",
1315
+ success: "bg-status-success-subtle border-transparent text-on-status-success-subtle",
1316
+ warning: "bg-status-warning-subtle border-transparent text-on-status-warning-subtle",
1317
+ error: "bg-status-error-subtle border-transparent text-on-status-error-subtle",
1318
+ info: "bg-status-info-subtle border-transparent text-on-status-info-subtle"
1232
1319
  },
1233
1320
  outline: {
1234
1321
  neutral: "border-hairline text-text-secondary",
@@ -1713,7 +1800,7 @@ function BodyMapDetailPanel({
1713
1800
  left: 0,
1714
1801
  right: 0,
1715
1802
  bottom: 0,
1716
- backgroundColor: "#000000",
1803
+ backgroundColor: primitiveColors.black,
1717
1804
  opacity: backdrop
1718
1805
  },
1719
1806
  testID: "body-map-detail-panel-backdrop-anim",
@@ -2054,9 +2141,9 @@ function BodyMapDetailPanel({
2054
2141
  paddingVertical: 10,
2055
2142
  borderRadius: 8,
2056
2143
  alignItems: "center",
2057
- backgroundColor: "rgba(255,121,0,0.12)",
2144
+ backgroundColor: alpha(BRAND_PRIMARY2, 0.12),
2058
2145
  borderWidth: 1,
2059
- borderColor: "rgba(255,121,0,0.3)"
2146
+ borderColor: alpha(BRAND_PRIMARY2, 0.3)
2060
2147
  },
2061
2148
  testID: "body-map-detail-panel-view-exercises",
2062
2149
  children: /* @__PURE__ */ jsx(
@@ -2255,40 +2342,6 @@ function SkeletonBody({ hasHeader, hasFooter, contentLines }) {
2255
2342
  hasFooter && /* @__PURE__ */ jsx(CardFooter, { children: /* @__PURE__ */ jsx(reactNative.View, { className: "h-9 w-24 bg-interactive-disabled rounded" }) })
2256
2343
  ] });
2257
2344
  }
2258
-
2259
- // src/utils/colors.ts
2260
- function alpha(color, opacity) {
2261
- const clampedOpacity = Math.max(0, Math.min(1, opacity));
2262
- if (color.startsWith("#")) {
2263
- const hex = color.slice(1);
2264
- let r, g, b;
2265
- if (hex.length === 3) {
2266
- r = parseInt(hex[0] + hex[0], 16);
2267
- g = parseInt(hex[1] + hex[1], 16);
2268
- b = parseInt(hex[2] + hex[2], 16);
2269
- } else if (hex.length === 6) {
2270
- r = parseInt(hex.slice(0, 2), 16);
2271
- g = parseInt(hex.slice(2, 4), 16);
2272
- b = parseInt(hex.slice(4, 6), 16);
2273
- } else {
2274
- return color;
2275
- }
2276
- return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
2277
- }
2278
- const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
2279
- if (rgbMatch) {
2280
- const [, r, g, b] = rgbMatch;
2281
- return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
2282
- }
2283
- const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/);
2284
- if (rgbaMatch) {
2285
- const [, r, g, b] = rgbaMatch;
2286
- return `rgba(${r}, ${g}, ${b}, ${clampedOpacity})`;
2287
- }
2288
- return color;
2289
- }
2290
-
2291
- // src/components/custom/Workout/StatusDot.tsx
2292
2345
  var solidFillToken = {
2293
2346
  success: "status-success",
2294
2347
  warning: "status-warning",
@@ -2439,11 +2492,11 @@ var WARNING = t3["status-warning"];
2439
2492
  var ERROR = t3["status-error"];
2440
2493
  var ACCENT_STOPS = [BRAND_PRIMARY_DARK, BRAND_PRIMARY3, BRAND_PRIMARY_LIGHT];
2441
2494
  var CARD_GRADIENT = `linear-gradient(135deg, ${resolveColor("surface-elevated")} 0%, ${resolveColor("surface-raised")} 100%)`;
2442
- var GAUGE_GRADIENT = "linear-gradient(90deg, rgba(46,213,115,0.25) 0%, rgba(249,180,21,0.25) 50%, rgba(209,67,67,0.25) 100%)";
2495
+ var GAUGE_GRADIENT = `linear-gradient(90deg, ${alpha(SUCCESS, 0.25)} 0%, ${alpha(WARNING, 0.25)} 50%, ${alpha(ERROR, 0.25)} 100%)`;
2443
2496
  var STATUS_VARIANTS = {
2444
- success: { bg: "rgba(46,213,115,0.15)", border: "rgba(46,213,115,0.3)", text: SUCCESS },
2445
- warning: { bg: "rgba(249,180,21,0.15)", border: "rgba(249,180,21,0.3)", text: WARNING },
2446
- error: { bg: "rgba(209,67,67,0.15)", border: "rgba(209,67,67,0.25)", text: ERROR }
2497
+ success: { bg: alpha(SUCCESS, 0.15), border: alpha(SUCCESS, 0.3), text: SUCCESS },
2498
+ warning: { bg: alpha(WARNING, 0.15), border: alpha(WARNING, 0.3), text: WARNING },
2499
+ error: { bg: alpha(ERROR, 0.15), border: alpha(ERROR, 0.25), text: ERROR }
2447
2500
  };
2448
2501
  function clamp012(value) {
2449
2502
  if (!Number.isFinite(value)) return 0;
@@ -2596,7 +2649,7 @@ function Gauge({ gauge }) {
2596
2649
  bottom: 0,
2597
2650
  width: 1,
2598
2651
  marginLeft: -0.5,
2599
- backgroundColor: "rgba(255,255,255,0.3)"
2652
+ backgroundColor: alpha(primitiveColors.white, 0.3)
2600
2653
  },
2601
2654
  testID: "meso-status-card-gauge-center"
2602
2655
  }
@@ -2705,9 +2758,9 @@ function MesoStatusCard({
2705
2758
  reactNative.View,
2706
2759
  {
2707
2760
  style: {
2708
- backgroundColor: "rgba(249,180,21,0.06)",
2761
+ backgroundColor: alpha(WARNING, 0.06),
2709
2762
  borderWidth: 1,
2710
- borderColor: "rgba(249,180,21,0.15)",
2763
+ borderColor: alpha(WARNING, 0.15),
2711
2764
  borderRadius: 8,
2712
2765
  paddingVertical: 10,
2713
2766
  paddingHorizontal: 12
@@ -2733,9 +2786,9 @@ function MesoStatusCard({
2733
2786
  className: "flex-row items-center",
2734
2787
  style: {
2735
2788
  gap: 8,
2736
- backgroundColor: "rgba(46,213,115,0.06)",
2789
+ backgroundColor: alpha(SUCCESS, 0.06),
2737
2790
  borderWidth: 1,
2738
- borderColor: "rgba(46,213,115,0.2)",
2791
+ borderColor: alpha(SUCCESS, 0.2),
2739
2792
  borderRadius: 8,
2740
2793
  paddingVertical: 10,
2741
2794
  paddingHorizontal: 12