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