@titan-design/react-ui 0.11.0 → 0.12.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 (142) hide show
  1. package/dist/bodymap.js +173 -343
  2. package/dist/bodymap.js.map +1 -1
  3. package/dist/bodymap.mjs +173 -343
  4. package/dist/bodymap.mjs.map +1 -1
  5. package/dist/index.d.mts +24 -21
  6. package/dist/index.d.ts +24 -21
  7. package/dist/index.js +366 -561
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +360 -553
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/pages.js +242 -407
  12. package/dist/pages.js.map +1 -1
  13. package/dist/pages.mjs +242 -407
  14. package/dist/pages.mjs.map +1 -1
  15. package/dist/theme/index.d.mts +409 -415
  16. package/dist/theme/index.d.ts +409 -415
  17. package/dist/theme/index.js +234 -408
  18. package/dist/theme/index.js.map +1 -1
  19. package/dist/theme/index.mjs +227 -399
  20. package/dist/theme/index.mjs.map +1 -1
  21. package/dist/theme/tokens-css.js +141 -140
  22. package/dist/theme/tokens-css.js.map +1 -1
  23. package/dist/theme/tokens-css.mjs +141 -140
  24. package/dist/theme/tokens-css.mjs.map +1 -1
  25. package/package.json +1 -1
  26. package/src/arch/Architecture.stories.tsx +9 -9
  27. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  28. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +3 -3
  29. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  30. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +3 -3
  31. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +1 -1
  32. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +2 -2
  33. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  34. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +4 -4
  35. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +3 -3
  36. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  37. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -12
  38. package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
  39. package/src/components/custom/Table/Table.tsx +2 -2
  40. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
  41. package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
  42. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  43. package/src/components/custom/Workout/BodyMap.tsx +2 -2
  44. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
  45. package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
  46. package/src/components/custom/Workout/DeviationBar.tsx +3 -2
  47. package/src/components/custom/Workout/ExerciseCard.test.tsx +3 -3
  48. package/src/components/custom/Workout/ExerciseCard.tsx +2 -2
  49. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -4
  50. package/src/components/custom/Workout/InputBar.tsx +2 -2
  51. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
  52. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
  53. package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
  54. package/src/components/custom/Workout/MesoCard.tsx +3 -1
  55. package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
  56. package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
  57. package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
  58. package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
  59. package/src/components/custom/Workout/RestTimer.tsx +2 -2
  60. package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
  61. package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
  62. package/src/components/custom/Workout/SessionHeader.tsx +1 -1
  63. package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
  64. package/src/components/custom/Workout/SessionRail.tsx +13 -5
  65. package/src/components/custom/Workout/SetBar.tsx +3 -3
  66. package/src/components/custom/Workout/SetRow.test.tsx +6 -3
  67. package/src/components/custom/Workout/StatusDot.tsx +5 -4
  68. package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
  69. package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
  70. package/src/components/custom/Workout/VelocityStrip.test.tsx +5 -2
  71. package/src/components/custom/Workout/VelocityStrip.tsx +4 -4
  72. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
  73. package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
  74. package/src/components/custom/Workout/WeightBadge.tsx +2 -1
  75. package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
  76. package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
  77. package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
  78. package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
  79. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  80. package/src/components/custom/charts/SetBarChart.tsx +3 -3
  81. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  82. package/src/components/custom/stepper/Stepper.tsx +3 -3
  83. package/src/components/shell/DeviceMenu.tsx +2 -2
  84. package/src/components/shell/SideNav.tsx +1 -1
  85. package/src/components/shell/TopBar.tsx +1 -1
  86. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  87. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  88. package/src/components/ui/avatar/Avatar.tsx +2 -2
  89. package/src/components/ui/badge/Badge.tsx +2 -2
  90. package/src/components/ui/card/Card.tsx +3 -3
  91. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  92. package/src/components/ui/collapse/Collapse.tsx +1 -1
  93. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  94. package/src/components/ui/drawer/Drawer.tsx +3 -3
  95. package/src/components/ui/form-field/FormField.tsx +1 -1
  96. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  97. package/src/components/ui/menu/Menu.tsx +2 -2
  98. package/src/components/ui/pill/Pill.tsx +2 -2
  99. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  100. package/src/components/ui/popover/Popover.tsx +1 -1
  101. package/src/components/ui/progress/Progress.tsx +3 -3
  102. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  103. package/src/components/ui/radio/Radio.tsx +4 -4
  104. package/src/components/ui/select/Select.tsx +3 -3
  105. package/src/components/ui/surface/Surface.stories.tsx +3 -3
  106. package/src/components/ui/surface/Surface.test.tsx +32 -19
  107. package/src/components/ui/surface/Surface.tsx +3 -3
  108. package/src/components/ui/surface/SurfaceContext.ts +19 -29
  109. package/src/components/ui/surface/surface.contract.test.ts +57 -44
  110. package/src/components/ui/switch/Switch.tsx +1 -1
  111. package/src/components/ui/tabs/Tabs.tsx +2 -2
  112. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
  113. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
  114. package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
  115. package/src/stories/PresetShowcase.stories.tsx +21 -21
  116. package/src/theme/ColorPalettes.stories.tsx +41 -38
  117. package/src/theme/ColorPrimitives.stories.tsx +89 -41
  118. package/src/theme/Depth.stories.tsx +312 -0
  119. package/src/theme/DepthCalibration.stories.tsx +655 -0
  120. package/src/theme/ThemeProvider.tsx +1 -1
  121. package/src/theme/barrel.ts +7 -14
  122. package/src/theme/color-stories.coverage.test.ts +15 -20
  123. package/src/theme/color-story-kit.tsx +1 -1
  124. package/src/theme/color-utils.ts +197 -0
  125. package/src/theme/config.ts +0 -8
  126. package/src/theme/depth-calibration.test.tsx +97 -0
  127. package/src/theme/elevation.test.ts +62 -1
  128. package/src/theme/elevation.ts +34 -8
  129. package/src/theme/global.css +47 -53
  130. package/src/theme/grey-ramp.test.ts +166 -0
  131. package/src/theme/materials.test.ts +106 -0
  132. package/src/theme/materials.ts +211 -0
  133. package/src/theme/presets/audiobook.ts +4 -4
  134. package/src/theme/presets/types.ts +3 -3
  135. package/src/theme/semantic-contrast.test.ts +83 -0
  136. package/src/theme/tokens/primitives.ts +62 -115
  137. package/src/theme/tokens/semantic.ts +130 -82
  138. package/src/utils/colors.ts +1 -1
  139. package/tailwind.config.js +2 -5
  140. package/src/theme/bar-paper.ts +0 -59
  141. package/src/theme/shadows.stories.tsx +0 -523
  142. package/src/theme/shadows.ts +0 -439
package/dist/pages.mjs CHANGED
@@ -8,53 +8,37 @@ import { twMerge } from 'tailwind-merge';
8
8
 
9
9
  // src/theme/tokens/primitives.ts
10
10
  var primitiveColors = {
11
- // Blue scale
12
- blue: {
13
- 400: "#60A5FA",
14
- 600: "#5048E5",
15
- 700: "#3832A0"},
16
- // Red scale
17
- red: {
18
- 500: "#EF4444",
19
- 600: "#D14343"},
20
- // Vivid red scale
21
- redVivid: {
22
- 400: "#FF6070",
23
- 500: "#FF4757",
24
- 700: "#C42539"},
25
- // Neutral/Gray scale
26
- neutral: {
27
- 50: "#FAFAFA",
28
- 100: "#F3F4F6",
29
- 300: "#D1D5DB",
30
- 400: "#9CA3AF",
31
- 500: "#6B7280",
32
- 600: "#4B5563",
33
- 900: "#111827"},
34
- // Charcoal scale (dark backgrounds)
35
- charcoal: {
36
- 0: "#6E6E6E",
37
- 200: "#3C3C3C",
38
- 300: "#2C2C2C",
39
- 400: "#1F1F1F",
40
- 500: "#1C1C1C"},
41
- // Pure colors
42
11
  white: "#FFFFFF"};
43
- var surfaceRampDark = {
44
- inset: "#13100D",
45
- // L*4.5 — sub-shell well / pressed
46
- background: "#1C1916",
47
- // L*9 — shell / frame
48
- base: "#252321",
49
- // L*13.5 — main content plane
50
- elevated: "#2C2A28",
51
- // L*17 — nav / rail
52
- raised: "#31302F",
53
- // L*20 — cards
54
- overlay: "#373635"
55
- // L*22.5 — hero / popover
12
+ var greyRamp = {
13
+ 50: "#F9F6F3",
14
+ // L*97.0 W6
15
+ 100: "#EDEAE7",
16
+ // L*92.8 W6
17
+ 200: "#D4D1CE",
18
+ // L*75.7 W6
19
+ 400: "#A29F9D",
20
+ // L*65.7 W5
21
+ 500: "#888684",
22
+ // L*56.0 W4
23
+ 600: "#72716F",
24
+ // L*47.7 W3
25
+ 700: "#5A5958",
26
+ // L*37.9 W2
27
+ 800: "#424140",
28
+ // L*27.6 W2 — generated bridge
29
+ 850: "#373635",
30
+ // L*22.7 W2 — surface overlay
31
+ 875: "#31302F",
32
+ // L*19.9 W2 — surface raised
33
+ 900: "#2C2A28",
34
+ // L*17.2 W4 — surface elevated
35
+ 925: "#252321",
36
+ // L*13.9 W4 — surface base
37
+ 950: "#1C1916",
38
+ // L*9.0 W6 — background base / shell
39
+ 975: "#100D0A"
40
+ // L*3.8 W6 — background frame / bezel
56
41
  };
57
- var backgroundFrameDark = "#100D0A";
58
42
  var primitiveRamps = {
59
43
  red: {
60
44
  50: "#FFF4F4",
@@ -97,9 +81,11 @@ var primitiveRamps = {
97
81
  blue: {
98
82
  50: "#EFF8FF",
99
83
  300: "#78C2FF",
84
+ 400: "#3CA8FF",
100
85
  500: "#2196F3",
101
86
  // pin
102
- 600: "#1072CB"},
87
+ 600: "#1072CB",
88
+ 700: "#135AA8"},
103
89
  magenta: {
104
90
  400: "#ED69C3",
105
91
  600: "#BA2996"}
@@ -209,10 +195,10 @@ var semanticColorsLight = {
209
195
  "status-error-light": primitiveRamps.red[500],
210
196
  "status-error-dark": primitiveRamps.red[700],
211
197
  "status-error-subtle": primitiveRamps.red[50],
212
- "status-error-vivid": primitiveColors.redVivid[500],
198
+ "status-error-vivid": primitiveRamps.red[600],
213
199
  // vivid alert red
214
- "status-error-vivid-light": primitiveColors.redVivid[400],
215
- "status-error-vivid-dark": primitiveColors.redVivid[700],
200
+ "status-error-vivid-light": primitiveRamps.red[500],
201
+ "status-error-vivid-dark": primitiveRamps.red[700],
216
202
  "status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
217
203
  "status-warning": primitiveRamps.amber[300],
218
204
  "status-warning-light": primitiveRamps.amber[200],
@@ -239,7 +225,7 @@ var semanticColorsLight = {
239
225
  "result-inconclusive": "#9E9A97",
240
226
  // Gray - no clear result
241
227
  "result-inconclusive-light": "rgba(158, 154, 151, 0.12)",
242
- "result-neutral": primitiveColors.neutral[500],
228
+ "result-neutral": greyRamp[600],
243
229
  // Neutral baseline
244
230
  // Text on result backgrounds (on-result-*)
245
231
  "on-result-improve": primitiveColors.white,
@@ -270,47 +256,38 @@ var semanticColorsLight = {
270
256
  // Text colors (text-*)
271
257
  "text-primary": "#121828",
272
258
  "text-secondary": "#65748B",
273
- "text-tertiary": primitiveColors.neutral[400],
259
+ "text-tertiary": greyRamp[400],
274
260
  "text-disabled": "rgba(55, 65, 81, 0.48)",
275
261
  "text-inverse": primitiveColors.white,
276
- "text-link": primitiveColors.blue[600],
277
- "text-link-hover": primitiveColors.blue[700],
262
+ "text-link": primitiveRamps.blue[600],
263
+ "text-link-hover": primitiveRamps.blue[700],
278
264
  // Surface colors (surface-*) - for elevated containers
279
265
  "surface-base": primitiveColors.white,
280
- "surface-elevated": primitiveColors.neutral[50],
266
+ "surface-elevated": greyRamp[50],
281
267
  // slightly off-white for elevated cards
282
- "surface-raised": primitiveColors.neutral[100],
268
+ "surface-raised": greyRamp[100],
283
269
  // light gray for raised cards
284
270
  "surface-overlay": primitiveColors.white,
285
- "surface-input": primitiveColors.neutral[50],
271
+ "surface-input": greyRamp[50],
286
272
  // Input field background (filled variant)
287
- // Deepest pressed pit (TD-surface-tokens, S-3) — light-mode counterpart of
288
- // the dark ramp's `inset` plane; a placeholder pairing, not part of the
289
- // dark-ramp investigation (surface.contract.test.ts is dark-mode only).
290
- "surface-inset": primitiveColors.neutral[300],
291
273
  // Background colors (background-*)
292
274
  "background-base": "#EBEBEB",
293
275
  "background-default": primitiveColors.white,
294
- "background-subtle": primitiveColors.neutral[50],
295
- // Frame/bezel chrome (TD-surface-tokens, S-3) — top bar + side nav shell,
296
- // one step below `background-base` (darker still than `surface-inset`
297
- // above, mirroring the dark-mode ordering). Placeholder pairing for light
298
- // mode (see dark-mode note on `surface-inset` above).
299
- "background-frame": primitiveColors.neutral[400],
276
+ "background-subtle": greyRamp[50],
277
+ // Frame/bezel chrome — top bar + side nav shell, one step below
278
+ // `background-base`. Placeholder pairing for light mode, which is deferred.
279
+ "background-frame": greyRamp[400],
300
280
  // Border colors (border-*)
301
- "border-default": "#E8E9EB",
302
- "border-subtle": primitiveColors.neutral[100],
303
- "border-strong": primitiveColors.neutral[300],
304
- "border-prominent": primitiveColors.neutral[400],
281
+ "border-prominent": greyRamp[400],
305
282
  // high-visibility divider
306
- "border-focus": primitiveColors.blue[600],
307
- "border-input": primitiveColors.neutral[300],
283
+ "border-focus": primitiveRamps.blue[600],
284
+ "border-input": greyRamp[200],
308
285
  // Input field border
309
- "border-input-hover": primitiveColors.neutral[400],
286
+ "border-input-hover": greyRamp[400],
310
287
  // Input field border on hover
311
- "border-input-focus": primitiveColors.blue[600],
288
+ "border-input-focus": primitiveRamps.blue[600],
312
289
  // Input field border on focus
313
- "border-input-error": primitiveColors.red[600],
290
+ "border-input-error": primitiveRamps.red[600],
314
291
  // Input field border on error
315
292
  // Alpha hairline separators (surface-independent — composite toward black on
316
293
  // light surfaces, mirroring the dark-mode white-alpha family). See §4/S-2.
@@ -327,7 +304,7 @@ var semanticColorsLight = {
327
304
  // Divider
328
305
  "divider": "#E8E9EB",
329
306
  // Avatar default
330
- "avatar-background": primitiveColors.neutral[500],
307
+ "avatar-background": greyRamp[600],
331
308
  "avatar-text": primitiveColors.white
332
309
  };
333
310
  var semanticColorsDark = {
@@ -359,9 +336,9 @@ var semanticColorsDark = {
359
336
  "status-error-light": primitiveRamps.red[500],
360
337
  "status-error-dark": primitiveRamps.red[700],
361
338
  "status-error-subtle": "rgba(209, 67, 67, 0.12)",
362
- "status-error-vivid": primitiveColors.redVivid[500],
363
- "status-error-vivid-light": primitiveColors.redVivid[400],
364
- "status-error-vivid-dark": primitiveColors.redVivid[700],
339
+ "status-error-vivid": primitiveRamps.red[600],
340
+ "status-error-vivid-light": primitiveRamps.red[500],
341
+ "status-error-vivid-dark": primitiveRamps.red[700],
365
342
  "status-error-vivid-subtle": "rgba(255, 71, 87, 0.12)",
366
343
  "status-warning": primitiveRamps.amber[300],
367
344
  "status-warning-light": primitiveRamps.amber[200],
@@ -385,7 +362,7 @@ var semanticColorsDark = {
385
362
  "result-degrade-dark": "#b30000",
386
363
  "result-inconclusive": "#9E9A97",
387
364
  "result-inconclusive-light": "rgba(158, 154, 151, 0.16)",
388
- "result-neutral": primitiveColors.neutral[400],
365
+ "result-neutral": greyRamp[400],
389
366
  // Text on result backgrounds
390
367
  "on-result-improve": primitiveColors.white,
391
368
  "on-result-degrade": primitiveColors.white,
@@ -402,74 +379,106 @@ var semanticColorsDark = {
402
379
  "data-9": discreteRainbow[3],
403
380
  "data-10": discreteRainbow[22],
404
381
  // Text colors - inverted for dark mode
405
- "text-primary": primitiveColors.neutral[100],
406
- "text-secondary": primitiveColors.neutral[400],
407
- "text-tertiary": primitiveColors.neutral[500],
382
+ "text-primary": greyRamp[50],
383
+ "text-secondary": greyRamp[400],
384
+ // Deliberately ONE step lighter than the L*-nearest match (`greyRamp[600]`).
385
+ // The colour this replaced failed WCAG large-text outright on the three
386
+ // lightest planes — 2.96 / 2.72 / 2.49 against elevated / raised / overlay —
387
+ // and `600` carried that failure forward almost exactly (2.93 / 2.70 / 2.47).
388
+ // `500` clears 3:1 everywhere (3.32–5.34). Contrast beats colour fidelity for
389
+ // text roles; borders and surfaces still use strict L*-nearest. See
390
+ // semantic-contrast.test.ts, which fails if this is moved back down.
391
+ "text-tertiary": greyRamp[500],
408
392
  "text-disabled": "rgba(255, 255, 255, 0.38)",
409
- "text-inverse": primitiveColors.neutral[900],
393
+ "text-inverse": greyRamp[950],
410
394
  "text-link": "#828DF8",
411
- "text-link-hover": primitiveColors.blue[400],
395
+ "text-link-hover": primitiveRamps.blue[400],
412
396
  // Surface colors - dark backgrounds — warm-tapered DERIVED ramp (TD-surface-tokens,
413
397
  // S-1, re-spaced S-3). Shipped verbatim from `deriveSurfaceRamp()` — see
414
398
  // `surfaceRampDark` in primitives.ts for the full derivation note. `surface-overlay`
415
399
  // and `surface-elevated` are distinct (were both #191919 pre-S-1); `surface-input`
416
400
  // tracks one plane above `surface-base`, same relative position as before the remap.
417
401
  // `background-base` backs `Surface level="background"` (SurfaceContext.SURFACE_LEVEL_TOKEN),
418
- // so it takes the ramp's `background` (shell/frame) role, NOT `inset`. The ramp's
419
- // deepest `inset` plane is now promoted to `surface-inset` below (S-3) still no
420
- // `SurfaceLevel` member for it (follow-up, alongside the elevation.ts -1/-2 pressed
421
- // levels — parallel workstream, not this branch).
422
- "surface-base": surfaceRampDark.base,
423
- // main surface (#252321, L*13.5)
424
- "surface-elevated": surfaceRampDark.elevated,
425
- // elevated surface (#2C2A28, L*17 nav/rail)
426
- "surface-raised": surfaceRampDark.raised,
427
- // raised surface (#31302F, L*20 cards)
428
- "surface-overlay": surfaceRampDark.overlay,
429
- // overlay surface (#373635, L*22.5 — hero/popover)
430
- "surface-input": surfaceRampDark.elevated,
402
+ // so it takes the ramp's shell role. The deepest plane is `background-frame`,
403
+ // which is also the `SurfaceLevel` floor a pressed surface clamps at.
404
+ "surface-base": greyRamp[925],
405
+ // main surface (#252321, L*13.9)
406
+ "surface-elevated": greyRamp[900],
407
+ // elevated surface (#2C2A28, L*17.2 — nav/rail)
408
+ "surface-raised": greyRamp[875],
409
+ // raised surface (#31302F, L*19.9 cards)
410
+ "surface-overlay": greyRamp[850],
411
+ // overlay surface (#373635, L*22.7 hero/popover)
412
+ "surface-input": greyRamp[900],
431
413
  // input surface (#2C2A28 — one plane above base)
432
- "surface-inset": surfaceRampDark.inset,
433
- // deepest pressed pit (#13100D, L*4.5 — sub-shell well)
434
414
  // Background colors — same ramp, the frame/shell end of it.
435
- "background-base": surfaceRampDark.background,
436
- // shell / frame (#1C1916, L*9 — Surface level="background")
437
- "background-default": surfaceRampDark.base,
438
- // main background (#252321, L*13.5 — matches surface-base)
439
- "background-subtle": surfaceRampDark.elevated,
440
- // subtle background (#2C2A28, L*17 — matches surface-elevated)
441
- // Frame/bezel chrome (S-3) — the top bar + side nav shell; one step BELOW
442
- // `background-base`, darker even than `surface-inset` it exits the content
443
- // ramp entirely rather than sitting in it. See `backgroundFrameDark` in
444
- // primitives.ts.
445
- "background-frame": backgroundFrameDark,
446
- // frame / bezel (#100D0A, L*3.79)
447
- // Border colors `border-subtle` (#1C1C1C) previously collided with
448
- // `surface-raised` (also #1C1C1C, an invisible border on raised cards). The
449
- // re-spaced ramp above moved `surface-raised` to #2D2C2B, so this value is now
450
- // distinct from every surface hex without needing to change it see
451
- // `surface.contract.test.ts` R2/R4.
452
- "border-default": primitiveColors.charcoal[400],
453
- // default border
454
- "border-subtle": primitiveColors.charcoal[500],
455
- // subtle border
456
- "border-strong": primitiveColors.charcoal[300],
457
- // strong border
458
- "border-prominent": primitiveColors.charcoal[200],
415
+ "background-base": greyRamp[950],
416
+ // shell (#1C1916, L*9 — Surface level="background")
417
+ "background-default": greyRamp[925],
418
+ // main background (#252321, L*13.9 — matches surface-base)
419
+ "background-subtle": greyRamp[900],
420
+ // subtle background (#2C2A28, L*17.2 — matches surface-elevated)
421
+ // Frame/bezel chrome — the top bar + side nav shell, one step BELOW
422
+ // `background-base`. It used to be described as sitting OUTSIDE the ramp; it
423
+ // is simply the ramp's last step now, and the floor `<Surface pressed>` clamps at.
424
+ "background-frame": greyRamp[975],
425
+ // frame / bezel (#100D0A, L*3.8)
426
+ // Border colors — solid dark borders are RETIRED (TD-07.14). Not a preference — a structural
427
+ // consequence of one ramp. Every step from 850 to 975 is now a surface plane,
428
+ // so a solid border dark enough to read as a border necessarily equals some
429
+ // plane's fill, which is exactly the collision `surface.contract.test.ts` R2
430
+ // forbids. The only non-plane steps left in that band are 800 and 700, and
431
+ // borders there jump from L*~10-25 to L*~28-38 — a restyle, not a migration.
432
+ //
433
+ // So separation moves to the alpha hairlines, which dark mode already called
434
+ // "the primary separation cue". Being alpha, they composite against whatever
435
+ // plane they land on and cannot collide with any of them — the problem stops
436
+ // existing rather than being re-solved per plane. The three solid tokens were
437
+ // deleted outright; consumers point at the `hairline-*` family below.
438
+ //
439
+ // `border-prominent` stays SOLID: it is the one border meant to be seen
440
+ // outright (4 call sites, high-visibility dividers), and grey-800 is not a
441
+ // plane, so it keeps its job without collision.
442
+ "border-prominent": greyRamp[800],
459
443
  // high-visibility divider
460
444
  "border-focus": "#828DF8",
461
- "border-input": primitiveColors.neutral[600],
462
- "border-input-hover": primitiveColors.neutral[500],
445
+ "border-input": greyRamp[700],
446
+ "border-input-hover": greyRamp[600],
463
447
  "border-input-focus": "#828DF8",
464
- "border-input-error": primitiveColors.red[500],
448
+ "border-input-error": primitiveRamps.red[500],
465
449
  // Alpha hairline separators — the primary separation cue (§4/S-2). Self-
466
450
  // normalizing: composites toward white by ~the same amount on ANY plane, so
467
451
  // one family works at every elevation instead of per-surface border tokens.
468
452
  // Shadows are demoted to floating-overlay use only (not shipped as a fill
469
453
  // separator here — see elevation.ts, follow-up S-4).
470
- "hairline-subtle": "rgba(255, 255, 255, 0.06)",
471
- "hairline-default": "rgba(255, 255, 255, 0.09)",
472
- "hairline-strong": "rgba(255, 255, 255, 0.14)",
454
+ //
455
+ // RETUNED on the wall (VW-99, S-6). The original .06/.09/.14 were set at a
456
+ // desk. On the panel at ~3 m `subtle` rendered but was indiscernible, and
457
+ // `default` — the load-bearing one, with no solid-border fallback since
458
+ // TD-07.14 — went weak on the lightest plane. `strong` read cleanly with room
459
+ // to spare, which is what made raising the whole family viable.
460
+ //
461
+ // Each tier steps up onto the next MEASURED-GOOD rung rather than an
462
+ // interpolated one: the new `subtle` is the old `default`, and the new
463
+ // `default` is the old `strong` — a value just confirmed legible on all four
464
+ // planes. Spacing goes 1 : 1.56 : 2.33, against the original 1 : 1.5 : 2.33.
465
+ //
466
+ // RUN 2 (VW-99): all three tiers were seen on all four planes — the retune
467
+ // worked — but `subtle` was still "a bit hard to read", so the whole family
468
+ // took a further +.01. That is a comfort margin on a passing row, not a fix
469
+ // for a failing one, which is why it is a flat offset: the 1 : 1.56 : 2.33
470
+ // spacing that the run validated is preserved rather than re-derived.
471
+ //
472
+ // Keep these to TWO decimals. react-native-web quantises alpha to 8 bits, so
473
+ // a 3-decimal value silently rounds on the way to the DOM (.135 renders as
474
+ // .13) — the token would say one thing and paint another.
475
+ //
476
+ // DARK ONLY. The light family above composites toward BLACK on light planes,
477
+ // which this run says nothing about — do not mirror these numbers into it
478
+ // without its own verification.
479
+ "hairline-subtle": "rgba(255, 255, 255, 0.10)",
480
+ "hairline-default": "rgba(255, 255, 255, 0.15)",
481
+ "hairline-strong": "rgba(255, 255, 255, 0.22)",
473
482
  // Interactive states
474
483
  "interactive-hover": "rgba(255, 255, 255, 0.04)",
475
484
  "interactive-focus": "rgba(255, 255, 255, 0.12)",
@@ -478,15 +487,16 @@ var semanticColorsDark = {
478
487
  "interactive-disabled": "rgba(255, 255, 255, 0.12)",
479
488
  "interactive-disabled-text": "rgba(255, 255, 255, 0.26)",
480
489
  // Divider
481
- "divider": primitiveColors.charcoal[400],
482
- // divider color
490
+ "divider": "rgba(255, 255, 255, 0.09)",
483
491
  // Avatar default
484
- "avatar-background": primitiveColors.neutral[600],
492
+ "avatar-background": greyRamp[700],
485
493
  "avatar-text": primitiveColors.white
486
494
  };
487
495
  function getSemanticColors(mode) {
488
496
  return mode === "dark" ? semanticColorsDark : semanticColorsLight;
489
497
  }
498
+
499
+ // src/theme/color-utils.ts
490
500
  function hexToRgb(hex) {
491
501
  const cleaned = hex.replace("#", "");
492
502
  let r, g, b;
@@ -526,32 +536,32 @@ function rgbToHsv(r, g, b) {
526
536
  return { h, s, v };
527
537
  }
528
538
  function hsvToRgb(h, s, v) {
529
- const c = v * s;
530
- const x = c * (1 - Math.abs(h / 60 % 2 - 1));
531
- const m = v - c;
539
+ const c2 = v * s;
540
+ const x = c2 * (1 - Math.abs(h / 60 % 2 - 1));
541
+ const m = v - c2;
532
542
  let rPrime, gPrime, bPrime;
533
543
  if (h >= 0 && h < 60) {
534
- rPrime = c;
544
+ rPrime = c2;
535
545
  gPrime = x;
536
546
  bPrime = 0;
537
547
  } else if (h >= 60 && h < 120) {
538
548
  rPrime = x;
539
- gPrime = c;
549
+ gPrime = c2;
540
550
  bPrime = 0;
541
551
  } else if (h >= 120 && h < 180) {
542
552
  rPrime = 0;
543
- gPrime = c;
553
+ gPrime = c2;
544
554
  bPrime = x;
545
555
  } else if (h >= 180 && h < 240) {
546
556
  rPrime = 0;
547
557
  gPrime = x;
548
- bPrime = c;
558
+ bPrime = c2;
549
559
  } else if (h >= 240 && h < 300) {
550
560
  rPrime = x;
551
561
  gPrime = 0;
552
- bPrime = c;
562
+ bPrime = c2;
553
563
  } else {
554
- rPrime = c;
564
+ rPrime = c2;
555
565
  gPrime = 0;
556
566
  bPrime = x;
557
567
  }
@@ -576,194 +586,6 @@ function lighten(hex, amount = 0.1) {
576
586
  const newRgb = hsvToRgb(hsv.h, hsv.s, newV);
577
587
  return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
578
588
  }
579
- var shadowColors = {
580
- // For charcoal surfaces (~#2C2C2C to #3C3C3C)
581
- charcoal: {
582
- dark: "rgba(0, 0, 0, 0.5)",
583
- light: "rgba(255, 255, 255, 0.12)",
584
- // brighter for crisp rim light
585
- darker: "rgba(0, 0, 0, 0.6)"
586
- },
587
- // For neutral dark surfaces (~#1F2937)
588
- neutralDark: {
589
- dark: "rgba(0, 0, 0, 0.4)",
590
- light: "rgba(255, 255, 255, 0.08)",
591
- darker: "rgba(0, 0, 0, 0.5)"
592
- },
593
- // For light surfaces (white/light gray backgrounds)
594
- neutralLight: {
595
- dark: "rgba(0, 0, 0, 0.15)",
596
- // Visible dark shadow for depth on light backgrounds
597
- light: "rgba(255, 255, 255, 1)",
598
- // Bright highlight for raised effect (visible on light gray)
599
- darker: "rgba(0, 0, 0, 0.2)"
600
- // Deeper shadow for pressed states
601
- }
602
- };
603
- var intensityConfig = {
604
- subtle: { offset: 1, blur: 2 },
605
- // very light, barely visible
606
- medium: { offset: 2, blur: 4 },
607
- // noticeable but restrained
608
- strong: { offset: 2, blur: 5 }
609
- // slightly deeper blur, same offset as medium
610
- };
611
- function createRaisedShadow(surface = "charcoal", intensity = "medium") {
612
- const colors = shadowColors[surface];
613
- const { offset, blur } = intensityConfig[intensity];
614
- const rimBlur = 1;
615
- return Platform.select({
616
- web: {
617
- boxShadow: `${offset}px ${offset}px ${blur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
618
- },
619
- default: {
620
- // React Native shadow approximation (limited compared to web)
621
- shadowColor: "#000",
622
- shadowOffset: { width: offset, height: offset },
623
- shadowOpacity: 0.3,
624
- shadowRadius: blur / 2,
625
- elevation: offset
626
- }
627
- });
628
- }
629
- function createPressedShadow(surface = "charcoal", intensity = "medium") {
630
- const colors = shadowColors[surface];
631
- const { offset, blur } = intensityConfig[intensity];
632
- const rimOffset = 1;
633
- const rimBlur = 1;
634
- return Platform.select({
635
- web: {
636
- boxShadow: `inset ${offset}px ${offset}px ${blur}px ${colors.darker}, inset -${rimOffset}px -${rimOffset}px ${rimBlur}px ${colors.light}`
637
- },
638
- default: {
639
- // React Native doesn't support inset shadows, approximate with darker bg
640
- shadowOpacity: 0,
641
- elevation: 0
642
- }
643
- });
644
- }
645
- function createFlatShadow() {
646
- return Platform.select({
647
- web: {
648
- boxShadow: "none"
649
- },
650
- default: {
651
- shadowOpacity: 0,
652
- elevation: 0
653
- }
654
- });
655
- }
656
- function createRaisedHoverShadow(surface = "charcoal", intensity = "medium") {
657
- const colors = shadowColors[surface];
658
- const { offset, blur } = intensityConfig[intensity];
659
- const hoverOffset = Math.max(1, Math.round(offset / 2));
660
- const hoverBlur = Math.max(1, Math.round(blur / 2));
661
- const rimBlur = 1;
662
- return Platform.select({
663
- web: {
664
- // Keep same offsets as normal state for rim to prevent "shrinking" effect
665
- boxShadow: `${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.dark}, -${offset}px -${offset}px ${rimBlur}px ${colors.light}`
666
- },
667
- default: {
668
- shadowColor: "#000",
669
- shadowOffset: { width: hoverOffset, height: hoverOffset },
670
- shadowOpacity: 0.2,
671
- shadowRadius: hoverBlur / 2,
672
- elevation: hoverOffset
673
- }
674
- });
675
- }
676
- function createPressedHoverShadow(surface = "charcoal", intensity = "medium") {
677
- const colors = shadowColors[surface];
678
- const { offset, blur } = intensityConfig[intensity];
679
- const hoverOffset = Math.max(1, Math.round(offset / 2));
680
- const hoverBlur = Math.max(1, Math.round(blur / 2));
681
- const rimBlur = 1;
682
- return Platform.select({
683
- web: {
684
- // Keep rim offset at 1px (same as pressed normal) to prevent "shrinking" effect
685
- boxShadow: `inset ${hoverOffset}px ${hoverOffset}px ${hoverBlur}px ${colors.darker}, inset -1px -1px ${rimBlur}px ${colors.light}`
686
- },
687
- default: {
688
- shadowOpacity: 0,
689
- elevation: 0
690
- }
691
- });
692
- }
693
- ({
694
- // Charcoal surface shadows (for dark toolbars, cards)
695
- charcoal: {
696
- raised: {
697
- subtle: createRaisedShadow("charcoal", "subtle"),
698
- medium: createRaisedShadow("charcoal", "medium"),
699
- strong: createRaisedShadow("charcoal", "strong")
700
- },
701
- raisedHover: {
702
- subtle: createRaisedHoverShadow("charcoal", "subtle"),
703
- medium: createRaisedHoverShadow("charcoal", "medium"),
704
- strong: createRaisedHoverShadow("charcoal", "strong")
705
- },
706
- pressed: {
707
- subtle: createPressedShadow("charcoal", "subtle"),
708
- medium: createPressedShadow("charcoal", "medium"),
709
- strong: createPressedShadow("charcoal", "strong")
710
- },
711
- pressedHover: {
712
- subtle: createPressedHoverShadow("charcoal", "subtle"),
713
- medium: createPressedHoverShadow("charcoal", "medium"),
714
- strong: createPressedHoverShadow("charcoal", "strong")
715
- },
716
- flat: createFlatShadow()
717
- },
718
- // Neutral dark surface shadows
719
- neutralDark: {
720
- raised: {
721
- subtle: createRaisedShadow("neutralDark", "subtle"),
722
- medium: createRaisedShadow("neutralDark", "medium"),
723
- strong: createRaisedShadow("neutralDark", "strong")
724
- },
725
- raisedHover: {
726
- subtle: createRaisedHoverShadow("neutralDark", "subtle"),
727
- medium: createRaisedHoverShadow("neutralDark", "medium"),
728
- strong: createRaisedHoverShadow("neutralDark", "strong")
729
- },
730
- pressed: {
731
- subtle: createPressedShadow("neutralDark", "subtle"),
732
- medium: createPressedShadow("neutralDark", "medium"),
733
- strong: createPressedShadow("neutralDark", "strong")
734
- },
735
- pressedHover: {
736
- subtle: createPressedHoverShadow("neutralDark", "subtle"),
737
- medium: createPressedHoverShadow("neutralDark", "medium"),
738
- strong: createPressedHoverShadow("neutralDark", "strong")
739
- },
740
- flat: createFlatShadow()
741
- },
742
- // Neutral light surface shadows
743
- neutralLight: {
744
- raised: {
745
- subtle: createRaisedShadow("neutralLight", "subtle"),
746
- medium: createRaisedShadow("neutralLight", "medium"),
747
- strong: createRaisedShadow("neutralLight", "strong")
748
- },
749
- raisedHover: {
750
- subtle: createRaisedHoverShadow("neutralLight", "subtle"),
751
- medium: createRaisedHoverShadow("neutralLight", "medium"),
752
- strong: createRaisedHoverShadow("neutralLight", "strong")
753
- },
754
- pressed: {
755
- subtle: createPressedShadow("neutralLight", "subtle"),
756
- medium: createPressedShadow("neutralLight", "medium"),
757
- strong: createPressedShadow("neutralLight", "strong")
758
- },
759
- pressedHover: {
760
- subtle: createPressedHoverShadow("neutralLight", "subtle"),
761
- medium: createPressedHoverShadow("neutralLight", "medium"),
762
- strong: createPressedHoverShadow("neutralLight", "strong")
763
- },
764
- flat: createFlatShadow()
765
- }
766
- });
767
589
 
768
590
  // src/utils/colors.ts
769
591
  function alpha(color, opacity) {
@@ -810,29 +632,37 @@ function formatVelocity(velocity) {
810
632
  function roundTempo(tempo) {
811
633
  return tempo.map((v) => Math.round(v * 10) / 10);
812
634
  }
813
-
814
- // src/theme/bar-paper.ts
815
- function grainOpacityForColor(hex) {
635
+ function parseHex(hex) {
816
636
  const h = hex.replace("#", "");
817
637
  const full = h.length === 3 ? h.split("").map((ch) => ch + ch).join("") : h;
818
- const r = parseInt(full.slice(0, 2), 16);
819
- const g = parseInt(full.slice(2, 4), 16);
820
- const b = parseInt(full.slice(4, 6), 16);
821
- if ([r, g, b].some(Number.isNaN)) return 0.14;
822
- const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
823
- return Math.min(0.24, Math.max(0.04, 0.02 + 0.31 * lum));
824
- }
825
- function barGrain(color) {
826
- const op = grainOpacityForColor(color).toFixed(3);
638
+ const rgb = [
639
+ parseInt(full.slice(0, 2), 16),
640
+ parseInt(full.slice(2, 4), 16),
641
+ parseInt(full.slice(4, 6), 16)
642
+ ];
643
+ return rgb.some(Number.isNaN) ? null : rgb;
644
+ }
645
+ function perceivedLuminance(hex) {
646
+ const rgb = parseHex(hex);
647
+ if (!rgb) return 0.5;
648
+ const [r, g, b] = rgb;
649
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
650
+ }
651
+ function grainOpacityForTone(baseColor) {
652
+ return Math.min(0.24, Math.max(0.04, 0.02 + 0.31 * perceivedLuminance(baseColor)));
653
+ }
654
+ function grainForTone(baseColor) {
655
+ const op = grainOpacityForTone(baseColor).toFixed(3);
827
656
  return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='${op}'/%3E%3C/svg%3E")`;
828
657
  }
829
658
  function barPaper(color, flip = false) {
830
659
  return {
831
- backgroundImage: barGrain(color),
660
+ backgroundImage: grainForTone(color),
832
661
  boxShadow: flip ? "0 -6px 16px rgba(0,0,0,0.45)" : "inset 0 1.5px 0 rgba(255,255,255,0.22), 0 6px 16px rgba(0,0,0,0.45)"
833
662
  };
834
663
  }
835
664
  var SURFACE_LEVEL_TOKEN = {
665
+ frame: "background-frame",
836
666
  background: "background-base",
837
667
  base: "surface-base",
838
668
  elevated: "surface-elevated",
@@ -855,12 +685,7 @@ function useSurfaceMode() {
855
685
  function useOnSurfaceColor(role = "primary") {
856
686
  return getSemanticColors(useSurfaceMode())[ON_SURFACE_TOKEN[role]];
857
687
  }
858
- function insetFloor(mode) {
859
- const colors = getSemanticColors(mode);
860
- return colors["surface-inset"] ?? surfaceRampDark.inset;
861
- }
862
688
  function surfaceBackground(level, mode) {
863
- if (level === "inset") return insetFloor(mode);
864
689
  return getSemanticColors(mode)[SURFACE_LEVEL_TOKEN[level]];
865
690
  }
866
691
  var ANIMATION_EASING = Easing.bezier(0.22, 1, 0.36, 1);
@@ -1419,7 +1244,7 @@ function setAccessibilityLabel(set, repCount) {
1419
1244
  }
1420
1245
  var EXPANDED_HEIGHT = 60;
1421
1246
  var COMPACT_HEIGHT = 8;
1422
- var HERO_REFERENCE_COLOR = primitiveColors.charcoal[0];
1247
+ var HERO_REFERENCE_COLOR = greyRamp[600];
1423
1248
  function DashedReferenceLine({
1424
1249
  anchor,
1425
1250
  offset,
@@ -1989,7 +1814,7 @@ var baseBadgeSizeConfig = {
1989
1814
  lg: { fontSize: 12, paddingH: 10, paddingV: 4, iconSize: 14 }
1990
1815
  };
1991
1816
  var variantColors = {
1992
- plain: { backgroundColor: t["surface-raised"], borderColor: "#1F1F1F" },
1817
+ plain: { backgroundColor: t["surface-raised"], borderColor: t["hairline-default"] },
1993
1818
  pr: {
1994
1819
  backgroundColor: "rgba(255, 121, 0, 0.12)",
1995
1820
  borderColor: "rgba(255, 121, 0, 0.3)"
@@ -2428,7 +2253,7 @@ function LiveTempoCell({
2428
2253
  alignItems: "center",
2429
2254
  justifyContent: "center"
2430
2255
  };
2431
- const num = (c, text) => /* @__PURE__ */ jsx(Text, { style: { fontFamily: MONO, fontSize, color: c, fontWeight: "700" }, children: text });
2256
+ const num = (c2, text) => /* @__PURE__ */ jsx(Text, { style: { fontFamily: MONO, fontSize, color: c2, fontWeight: "700" }, children: text });
2432
2257
  if (status === "upcoming") {
2433
2258
  const targetText = targetMs != null ? (targetMs / 1e3).toFixed(1) : String(value);
2434
2259
  return /* @__PURE__ */ jsx(View, { style: wrap, children: num(color, targetText) });
@@ -2839,7 +2664,7 @@ var SET_STRIP_ZONES = {
2839
2664
  fast: primitiveRamps.amber[300],
2840
2665
  fastest: primitiveRamps.green[300]
2841
2666
  };
2842
- var TODO_COLOR = primitiveColors.charcoal[300];
2667
+ var TODO_COLOR = greyRamp[900];
2843
2668
  var SET_STRIP_VARIABLE_COLOR = primitiveRamps.cyan[900];
2844
2669
  var NOTCH_GAP = 2;
2845
2670
  var CLUSTER_GAP = 3;
@@ -3131,7 +2956,7 @@ function CollapsedCard({
3131
2956
  );
3132
2957
  }
3133
2958
  var DARK2 = getSemanticColors("dark");
3134
- var BODY_DIVIDER = DARK2["border-subtle"];
2959
+ var BODY_DIVIDER = DARK2["hairline-subtle"];
3135
2960
  function deriveHeaderSetStates(sets) {
3136
2961
  return sets.map((set) => {
3137
2962
  switch (set.state) {
@@ -3165,7 +2990,7 @@ function ExpandedCard({
3165
2990
  return /* @__PURE__ */ jsxs(
3166
2991
  View,
3167
2992
  {
3168
- className: "bg-surface-elevated border-border",
2993
+ className: "bg-surface-elevated border-hairline",
3169
2994
  style: {
3170
2995
  borderWidth: 1,
3171
2996
  ...borderRadius,
@@ -3298,7 +3123,7 @@ function resolveColor(token, mode = "dark") {
3298
3123
 
3299
3124
  // src/components/custom/Workout/InputBar.tsx
3300
3125
  var BRAND_PRIMARY2 = getSemanticColors("dark")["brand-primary"];
3301
- var INPUT_CLASSNAME = "bg-surface-raised border-border-strong text-text-primary";
3126
+ var INPUT_CLASSNAME = "bg-surface-raised border-hairline-strong text-text-primary";
3302
3127
  var inputStyle = {
3303
3128
  fontSize: 14,
3304
3129
  fontWeight: "600",
@@ -3331,7 +3156,7 @@ function InputBar({
3331
3156
  style: {
3332
3157
  width: "100%",
3333
3158
  borderTopWidth: 1,
3334
- borderTopColor: resolveColor("border-default"),
3159
+ borderTopColor: resolveColor("hairline-default"),
3335
3160
  paddingTop: 10,
3336
3161
  paddingHorizontal: 16,
3337
3162
  paddingBottom: 12,
@@ -3546,7 +3371,7 @@ function CircularProgress({
3546
3371
  cy: center,
3547
3372
  r: radius,
3548
3373
  fill: "none",
3549
- stroke: "var(--color-border-default)",
3374
+ stroke: "var(--color-hairline-default)",
3550
3375
  strokeWidth
3551
3376
  }
3552
3377
  ),
@@ -3801,7 +3626,7 @@ function RestTimer({
3801
3626
  style: {
3802
3627
  width: "100%",
3803
3628
  borderTopWidth: 1,
3804
- borderTopColor: resolveColor("border-default"),
3629
+ borderTopColor: resolveColor("hairline-default"),
3805
3630
  paddingVertical: 12,
3806
3631
  paddingHorizontal: 16
3807
3632
  },
@@ -3870,7 +3695,7 @@ function RestTimer({
3870
3695
  /* @__PURE__ */ jsx(
3871
3696
  View,
3872
3697
  {
3873
- className: "bg-border",
3698
+ className: "bg-hairline",
3874
3699
  style: {
3875
3700
  height: 3,
3876
3701
  borderRadius: 2,
@@ -4090,7 +3915,7 @@ function WorkoutHeader({
4090
3915
  /* @__PURE__ */ jsx(
4091
3916
  View,
4092
3917
  {
4093
- className: "bg-border",
3918
+ className: "bg-hairline",
4094
3919
  style: { height: 4, borderRadius: 2 },
4095
3920
  testID: "active-workout-page-progress-track",
4096
3921
  children: /* @__PURE__ */ jsx(
@@ -4115,7 +3940,7 @@ function ExerciseList({ groups, focusedId, onToggle }) {
4115
3940
  return /* @__PURE__ */ jsx(
4116
3941
  View,
4117
3942
  {
4118
- className: "bg-surface-elevated border-border",
3943
+ className: "bg-surface-elevated border-hairline",
4119
3944
  style: {
4120
3945
  borderWidth: 1,
4121
3946
  borderRadius: 12
@@ -4197,7 +4022,7 @@ function ActiveWorkoutPage({
4197
4022
  (showRest || showInput) && /* @__PURE__ */ jsx(
4198
4023
  View,
4199
4024
  {
4200
- style: { borderTopWidth: 1, borderTopColor: resolveColor("border-default") },
4025
+ style: { borderTopWidth: 1, borderTopColor: resolveColor("hairline-default") },
4201
4026
  testID: "active-workout-page-bottom-bar",
4202
4027
  children: showRest && rest != null ? /* @__PURE__ */ jsx(
4203
4028
  RestTimer,
@@ -4261,7 +4086,13 @@ var elevationSystem = {
4261
4086
  // Level 1: Subtle elevation (outline cards, subtle containers)
4262
4087
  [1]: {
4263
4088
  name: "subtle",
4264
- colorAdjustment: 0.01,
4089
+ // The whole ladder was re-spaced in TD-07.16. It used to run
4090
+ // 0.01/0.02/0.04/0.06/0.08, where the first step was ~2 RGB values — under
4091
+ // the perceptual floor, so level 1 existed in the type system and nowhere on
4092
+ // screen. With drop-shadow now gated off below FLOATING_ELEVATION_MIN, TONE
4093
+ // is the only thing separating 0-3, so every step has to actually land.
4094
+ // Even 0.025 spacing, verified in elevation.test.ts as ΔL* > 1 per step.
4095
+ colorAdjustment: 0.025,
4265
4096
  shadowIntensity: "subtle",
4266
4097
  shadowStyle: "raised",
4267
4098
  description: "Subtle elevation (outline cards, subtle containers)"
@@ -4269,7 +4100,7 @@ var elevationSystem = {
4269
4100
  // Level 2: Standard elevation (default cards, buttons)
4270
4101
  [2]: {
4271
4102
  name: "standard",
4272
- colorAdjustment: 0.02,
4103
+ colorAdjustment: 0.05,
4273
4104
  shadowIntensity: "medium",
4274
4105
  shadowStyle: "raised",
4275
4106
  description: "Standard elevation (default cards, raised buttons)"
@@ -4277,7 +4108,7 @@ var elevationSystem = {
4277
4108
  // Level 3: Prominent elevation (important cards, modals)
4278
4109
  [3]: {
4279
4110
  name: "prominent",
4280
- colorAdjustment: 0.04,
4111
+ colorAdjustment: 0.075,
4281
4112
  shadowIntensity: "strong",
4282
4113
  shadowStyle: "raised",
4283
4114
  description: "Prominent elevation (important cards, modals, dropdowns)"
@@ -4285,7 +4116,7 @@ var elevationSystem = {
4285
4116
  // Level 4: High elevation (overlays, popovers)
4286
4117
  [4]: {
4287
4118
  name: "high",
4288
- colorAdjustment: 0.06,
4119
+ colorAdjustment: 0.1,
4289
4120
  shadowIntensity: "strong",
4290
4121
  shadowStyle: "raised",
4291
4122
  description: "High elevation (overlays, popovers, tooltips)"
@@ -4293,7 +4124,7 @@ var elevationSystem = {
4293
4124
  // Level 5: Floating (toasts, floating action buttons)
4294
4125
  [5]: {
4295
4126
  name: "floating",
4296
- colorAdjustment: 0.08,
4127
+ colorAdjustment: 0.13,
4297
4128
  shadowIntensity: "strong",
4298
4129
  shadowStyle: "raised",
4299
4130
  description: "Floating elevation (toasts, floating action buttons, highest z-index)"
@@ -4409,12 +4240,12 @@ function calculateElevationShadow(baseColor, level, theme, isHovered = false) {
4409
4240
  const elevatedSurfaceColor = calculateElevationSurface(baseColor, level);
4410
4241
  const rimLightColor = calculateRimLightColor(baseColor, level);
4411
4242
  const darkShadowColors = calculateDarkShadowColors(elevatedSurfaceColor);
4412
- const intensityConfig2 = {
4243
+ const intensityConfig = {
4413
4244
  subtle: { offset: 1, blur: 2 },
4414
4245
  medium: { offset: 2, blur: 4 },
4415
4246
  strong: { offset: 2, blur: 5 }
4416
4247
  };
4417
- const { offset, blur } = intensityConfig2[config.shadowIntensity];
4248
+ const { offset, blur } = intensityConfig[config.shadowIntensity];
4418
4249
  const rimConfig = getRimLightConfig();
4419
4250
  if (config.shadowStyle === "pressed") {
4420
4251
  if (isHovered) {
@@ -4470,12 +4301,13 @@ function calculateElevationShadow(baseColor, level, theme, isHovered = false) {
4470
4301
  });
4471
4302
  }
4472
4303
  }
4304
+ var FLOATING_ELEVATION_MIN = 4;
4473
4305
  function getElevationShadow(baseColor, level, theme, isHovered = false) {
4474
4306
  const cacheKey = getShadowCacheKey(baseColor, level, theme, isHovered);
4475
4307
  if (elevationShadowCache.has(cacheKey)) {
4476
4308
  return elevationShadowCache.get(cacheKey);
4477
4309
  }
4478
- const result = calculateElevationShadow(baseColor, level, theme, isHovered);
4310
+ const result = level > 0 && level < FLOATING_ELEVATION_MIN ? {} : calculateElevationShadow(baseColor, level, theme, isHovered);
4479
4311
  if (elevationShadowCache.size >= MAX_SHADOW_CACHE_SIZE) {
4480
4312
  const firstKey = elevationShadowCache.keys().next().value;
4481
4313
  elevationShadowCache.delete(firstKey);
@@ -4561,12 +4393,12 @@ function useTheme() {
4561
4393
  var variantStyles = {
4562
4394
  elevated: "",
4563
4395
  // Will be set dynamically via elevation system
4564
- outline: "border-2 border-border-strong",
4396
+ outline: "border-2 border-hairline-strong",
4565
4397
  // Thicker border with stronger contrast
4566
4398
  filled: "",
4567
4399
  // Will be set dynamically via elevation system
4568
- accent: "border border-border",
4569
- subtle: "border border-border-subtle"
4400
+ accent: "border border-hairline",
4401
+ subtle: "border border-hairline-subtle"
4570
4402
  };
4571
4403
  function Card({
4572
4404
  variant = "elevated",
@@ -4686,7 +4518,7 @@ var solidVariantColors = {
4686
4518
  success: t5["status-success"],
4687
4519
  warning: t5["status-warning"],
4688
4520
  error: t5["status-error"],
4689
- neutral: "#6B7280"
4521
+ neutral: greyRamp[500]
4690
4522
  };
4691
4523
  var ringVariantStyles = {
4692
4524
  "on-track": {
@@ -4724,10 +4556,10 @@ var iconColors = {
4724
4556
  success: "#0A5C52",
4725
4557
  warning: "#6B4000",
4726
4558
  error: "#5C1A1A",
4727
- neutral: "#D1D5DB",
4559
+ neutral: greyRamp[200],
4728
4560
  "on-track": t5["status-success"],
4729
4561
  deviation: t5["status-warning"],
4730
- future: "#6B7280"
4562
+ future: greyRamp[500]
4731
4563
  };
4732
4564
  var sizeDimensions = {
4733
4565
  sm: { size: 8, showIcon: false },
@@ -4796,7 +4628,7 @@ function StatusDot({
4796
4628
  /* @__PURE__ */ jsx(
4797
4629
  Text,
4798
4630
  {
4799
- style: { fontSize: 12, lineHeight: 16, color: "#9CA3AF" },
4631
+ style: { fontSize: 12, lineHeight: 16, color: greyRamp[400] },
4800
4632
  accessibilityElementsHidden: true,
4801
4633
  children: label
4802
4634
  }
@@ -5269,11 +5101,11 @@ function ChartLine({
5269
5101
  dashed,
5270
5102
  testID
5271
5103
  }) {
5272
- return /* @__PURE__ */ jsx(Fragment, { children: coords.map((c, i) => {
5104
+ return /* @__PURE__ */ jsx(Fragment, { children: coords.map((c2, i) => {
5273
5105
  if (i === 0) return null;
5274
5106
  const prev = coords[i - 1];
5275
- const dx = c.x - prev.x;
5276
- const dy = c.y - prev.y;
5107
+ const dx = c2.x - prev.x;
5108
+ const dy = c2.y - prev.y;
5277
5109
  const length = Math.sqrt(dx * dx + dy * dy);
5278
5110
  const angle = Math.atan2(dy, dx) * (180 / Math.PI);
5279
5111
  return /* @__PURE__ */ jsx(
@@ -5476,21 +5308,21 @@ function StrengthTrendChart({
5476
5308
  }
5477
5309
  ),
5478
5310
  geometry.actual.map(
5479
- (c) => c.point.isPR ? /* @__PURE__ */ jsx(
5311
+ (c2) => c2.point.isPR ? /* @__PURE__ */ jsx(
5480
5312
  Text,
5481
5313
  {
5482
5314
  accessibilityElementsHidden: true,
5483
5315
  testID: "strength-trend-chart-pr-star",
5484
5316
  style: {
5485
5317
  position: "absolute",
5486
- left: c.x - 7,
5487
- top: c.y - 20,
5318
+ left: c2.x - 7,
5319
+ top: c2.y - 20,
5488
5320
  fontSize: 12,
5489
5321
  color: STATUS_WARNING2
5490
5322
  },
5491
5323
  children: "\u2605"
5492
5324
  },
5493
- `star-${c.index}`
5325
+ `star-${c2.index}`
5494
5326
  ) : null
5495
5327
  )
5496
5328
  ]
@@ -5501,29 +5333,29 @@ function StrengthTrendChart({
5501
5333
  {
5502
5334
  style: { position: "absolute", top: 0, left: 0, width, height },
5503
5335
  testID: "strength-trend-chart-points",
5504
- children: geometry.actual.map((c) => {
5505
- const label = c.point.sessionLabel ?? c.point.date;
5336
+ children: geometry.actual.map((c2) => {
5337
+ const label = c2.point.sessionLabel ?? c2.point.date;
5506
5338
  return /* @__PURE__ */ jsx(
5507
5339
  Pressable,
5508
5340
  {
5509
5341
  testID: "strength-trend-chart-point",
5510
5342
  accessibilityRole: "button",
5511
- accessibilityLabel: `${label}: ${formatValue(c.point.e1rm)} ${unit}${c.point.isPR ? ", personal record" : ""}`,
5343
+ accessibilityLabel: `${label}: ${formatValue(c2.point.e1rm)} ${unit}${c2.point.isPR ? ", personal record" : ""}`,
5512
5344
  onPress: () => {
5513
- setSelected(c.index);
5514
- onPointPress?.(c.point);
5345
+ setSelected(c2.index);
5346
+ onPointPress?.(c2.point);
5515
5347
  },
5516
5348
  style: {
5517
5349
  position: "absolute",
5518
- left: c.x - 4,
5519
- top: c.y - 4,
5350
+ left: c2.x - 4,
5351
+ top: c2.y - 4,
5520
5352
  width: 8,
5521
5353
  height: 8,
5522
5354
  borderRadius: 1,
5523
5355
  backgroundColor: BRAND_PRIMARY6
5524
5356
  }
5525
5357
  },
5526
- `point-${c.index}`
5358
+ `point-${c2.index}`
5527
5359
  );
5528
5360
  })
5529
5361
  }
@@ -5533,7 +5365,7 @@ function StrengthTrendChart({
5533
5365
  {
5534
5366
  testID: "strength-trend-chart-tooltip",
5535
5367
  accessibilityElementsHidden: true,
5536
- className: "bg-surface-elevated border-border-strong",
5368
+ className: "bg-surface-elevated border-hairline-strong",
5537
5369
  style: {
5538
5370
  position: "absolute",
5539
5371
  left: Math.max(
@@ -6104,7 +5936,7 @@ function TabBar({ active, onSelect }) {
6104
5936
  className: "flex-row",
6105
5937
  style: {
6106
5938
  borderBottomWidth: 1,
6107
- borderBottomColor: resolveColor("border-default")
5939
+ borderBottomColor: resolveColor("hairline-default")
6108
5940
  },
6109
5941
  accessibilityRole: "tablist",
6110
5942
  testID: "exercise-detail-page-tabs",
@@ -6155,7 +5987,7 @@ function StatStrip({ stats, unit }) {
6155
5987
  return /* @__PURE__ */ jsxs(
6156
5988
  View,
6157
5989
  {
6158
- className: "bg-surface-elevated border-border",
5990
+ className: "bg-surface-elevated border-hairline",
6159
5991
  style: {
6160
5992
  flex: 1,
6161
5993
  padding: 12,
@@ -6205,7 +6037,7 @@ function SectionCard({
6205
6037
  return /* @__PURE__ */ jsxs(
6206
6038
  View,
6207
6039
  {
6208
- className: "bg-surface-elevated border-border",
6040
+ className: "bg-surface-elevated border-hairline",
6209
6041
  style: {
6210
6042
  borderWidth: 1,
6211
6043
  borderRadius: 12,
@@ -6316,7 +6148,7 @@ function VbtSummaryRow({ summary }) {
6316
6148
  return /* @__PURE__ */ jsx(View, { className: "flex-row", style: { gap: 8 }, testID: "exercise-detail-page-vbt-summary", children: cells.map((cell) => /* @__PURE__ */ jsxs(
6317
6149
  View,
6318
6150
  {
6319
- className: "bg-surface-elevated border-border",
6151
+ className: "bg-surface-elevated border-hairline",
6320
6152
  style: {
6321
6153
  flex: 1,
6322
6154
  padding: 12,
@@ -6708,7 +6540,7 @@ function Indicator({
6708
6540
  // src/components/ui/badge/Badge.tsx
6709
6541
  var variantColorStyles = {
6710
6542
  solid: {
6711
- default: "bg-border-strong text-text-inverse",
6543
+ default: "bg-hairline-strong text-text-inverse",
6712
6544
  primary: "bg-brand-primary text-text-inverse",
6713
6545
  secondary: "bg-brand-secondary text-text-inverse",
6714
6546
  success: "bg-status-success text-text-inverse",
@@ -6726,7 +6558,7 @@ var variantColorStyles = {
6726
6558
  info: "bg-status-info-subtle text-status-info"
6727
6559
  },
6728
6560
  outline: {
6729
- default: "border border-border-strong text-text-secondary",
6561
+ default: "border border-hairline-strong text-text-secondary",
6730
6562
  primary: "border border-brand-primary text-brand-primary",
6731
6563
  secondary: "border border-brand-secondary text-brand-secondary",
6732
6564
  success: "border border-status-success text-status-success",
@@ -6774,6 +6606,7 @@ function Badge({
6774
6606
  }
6775
6607
  );
6776
6608
  }
6609
+ var HAIRLINE_DEFAULT = getSemanticColors("dark")["hairline-default"];
6777
6610
  var t8 = getSemanticColors("dark");
6778
6611
  var statusBgColors = {
6779
6612
  completed: "rgba(46,213,115,0.15)",
@@ -6787,7 +6620,7 @@ var statusBorderStyles = {
6787
6620
  completed: { borderWidth: 1, borderColor: "rgba(46,213,115,0.3)" },
6788
6621
  current: { borderWidth: 1, borderColor: "rgba(255,121,0,0.3)" },
6789
6622
  next: { borderWidth: 1, borderColor: "rgba(255,121,0,0.4)" },
6790
- upcoming: { borderWidth: 1, borderColor: "#1F1F1F" },
6623
+ upcoming: { borderWidth: 1, borderColor: HAIRLINE_DEFAULT },
6791
6624
  missed: { borderWidth: 1, borderColor: "rgba(209,67,67,0.25)" },
6792
6625
  deload: { borderWidth: 1, borderColor: "rgba(186,41,150,0.3)" }
6793
6626
  };
@@ -6795,7 +6628,7 @@ var textColors = {
6795
6628
  completed: t8["status-success"],
6796
6629
  current: t8["brand-primary"],
6797
6630
  next: t8["brand-primary"],
6798
- upcoming: "#6B7280",
6631
+ upcoming: greyRamp[500],
6799
6632
  missed: "rgba(209,67,67,0.7)",
6800
6633
  deload: WORKOUT_PILL_DELOAD
6801
6634
  };
@@ -7206,10 +7039,11 @@ function WeekRow({
7206
7039
  }
7207
7040
 
7208
7041
  // src/components/custom/Workout/MesoCard.tsx
7042
+ var HAIRLINE_DEFAULT2 = getSemanticColors("dark")["hairline-default"];
7209
7043
  var BRAND_PRIMARY10 = getSemanticColors("dark")["brand-primary"];
7210
7044
  var BRAND_PRIMARY_DARK2 = MESO_ACCENT_GRADIENT_DARK;
7211
7045
  var BRAND_PRIMARY_LIGHT2 = MESO_ACCENT_GRADIENT_LIGHT;
7212
- var BORDER_DEFAULT = "#1F1F1F";
7046
+ var BORDER_DEFAULT = HAIRLINE_DEFAULT2;
7213
7047
  var ACCENT_STOPS2 = [BRAND_PRIMARY_DARK2, BRAND_PRIMARY10, BRAND_PRIMARY_LIGHT2];
7214
7048
  function heatmapColor(percentage) {
7215
7049
  const clamped = Math.max(0, Math.min(100, percentage));
@@ -7364,7 +7198,7 @@ function MesoCard({
7364
7198
  }
7365
7199
  var t10 = getSemanticColors("dark");
7366
7200
  var dotColorMap = {
7367
- untrained: "#2C2C2C",
7201
+ untrained: greyRamp[900],
7368
7202
  behind: t10["brand-secondary"],
7369
7203
  ontrack: t10["status-success"],
7370
7204
  target: t10["brand-primary"],
@@ -7377,12 +7211,12 @@ function MuscleGroupChip({
7377
7211
  className,
7378
7212
  ...props
7379
7213
  }) {
7380
- const dotColor = volumeStatus ? dotColorMap[volumeStatus] : "#2C2C2C";
7214
+ const dotColor = volumeStatus ? dotColorMap[volumeStatus] : greyRamp[900];
7381
7215
  const statusLabel = volumeStatus ?? "no status";
7382
7216
  const content = /* @__PURE__ */ jsxs(
7383
7217
  View,
7384
7218
  {
7385
- className: ["bg-surface-raised border-border", className].filter(Boolean).join(" "),
7219
+ className: ["bg-surface-raised border-hairline", className].filter(Boolean).join(" "),
7386
7220
  style: {
7387
7221
  flexDirection: "row",
7388
7222
  alignItems: "center",
@@ -7442,11 +7276,12 @@ function MuscleGroupChip({
7442
7276
  }
7443
7277
 
7444
7278
  // src/components/custom/Workout/WorkoutCard.tsx
7279
+ var HAIRLINE_DEFAULT3 = getSemanticColors("dark")["hairline-default"];
7445
7280
  var t11 = getSemanticColors("dark");
7446
7281
  var COLORS = {
7447
7282
  statusSuccess: t11["status-success"],
7448
7283
  brandPrimary: t11["brand-primary"],
7449
- borderDefault: "#1F1F1F",
7284
+ borderDefault: HAIRLINE_DEFAULT3,
7450
7285
  brandPrimarySubtle: "rgba(255,121,0,0.06)"
7451
7286
  };
7452
7287
  var statusBorderColors = {
@@ -7825,7 +7660,7 @@ function ProgramPlanningPage({
7825
7660
  /* @__PURE__ */ jsxs(
7826
7661
  View,
7827
7662
  {
7828
- className: "bg-surface-elevated border-border",
7663
+ className: "bg-surface-elevated border-hairline",
7829
7664
  style: {
7830
7665
  borderWidth: 1,
7831
7666
  borderRadius: 12,