@titan-design/react-ui 0.11.0 → 0.12.1

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 (152) 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 +27 -22
  6. package/dist/index.d.ts +27 -22
  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 +250 -1157
  16. package/dist/theme/index.d.ts +250 -1157
  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/dist/theme/tokens.d.mts +904 -0
  26. package/dist/theme/tokens.d.ts +904 -0
  27. package/dist/theme/tokens.js +856 -0
  28. package/dist/theme/tokens.js.map +1 -0
  29. package/dist/theme/tokens.mjs +833 -0
  30. package/dist/theme/tokens.mjs.map +1 -0
  31. package/package.json +7 -1
  32. package/src/arch/Architecture.stories.tsx +9 -9
  33. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  34. package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +3 -3
  35. package/src/components/custom/Fatigue/FatigueLights.stories.tsx +2 -2
  36. package/src/components/custom/Fatigue/GhostSpark.stories.tsx +3 -3
  37. package/src/components/custom/Fatigue/LiveFatigueCard.tsx +1 -1
  38. package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +2 -2
  39. package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +2 -2
  40. package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +4 -4
  41. package/src/components/custom/Fatigue/VelocityHero.stories.tsx +3 -3
  42. package/src/components/custom/Fatigue/VerdictHero.stories.tsx +2 -2
  43. package/src/components/custom/Fatigue/fatigue-tokens.ts +12 -12
  44. package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
  45. package/src/components/custom/Table/Table.tsx +2 -2
  46. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
  47. package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
  48. package/src/components/custom/Workout/BaseBadge.tsx +1 -1
  49. package/src/components/custom/Workout/BodyMap.tsx +2 -2
  50. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
  51. package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
  52. package/src/components/custom/Workout/DeviationBar.tsx +3 -2
  53. package/src/components/custom/Workout/ExerciseCard.test.tsx +3 -3
  54. package/src/components/custom/Workout/ExerciseCard.tsx +2 -2
  55. package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -4
  56. package/src/components/custom/Workout/InputBar.tsx +2 -2
  57. package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
  58. package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
  59. package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
  60. package/src/components/custom/Workout/MesoCard.tsx +3 -1
  61. package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
  62. package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
  63. package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
  64. package/src/components/custom/Workout/README.md +1 -1
  65. package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
  66. package/src/components/custom/Workout/RestTimer.tsx +2 -2
  67. package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
  68. package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
  69. package/src/components/custom/Workout/SessionHeader.tsx +1 -1
  70. package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
  71. package/src/components/custom/Workout/SessionRail.tsx +13 -5
  72. package/src/components/custom/Workout/SetBar.tsx +3 -3
  73. package/src/components/custom/Workout/SetRow.test.tsx +6 -3
  74. package/src/components/custom/Workout/StatusDot.tsx +5 -4
  75. package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
  76. package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
  77. package/src/components/custom/Workout/VelocityStrip.test.tsx +5 -2
  78. package/src/components/custom/Workout/VelocityStrip.tsx +4 -4
  79. package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
  80. package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
  81. package/src/components/custom/Workout/WeightBadge.tsx +2 -1
  82. package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
  83. package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
  84. package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
  85. package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
  86. package/src/components/custom/Workout/velocity-story-kit.tsx +2 -2
  87. package/src/components/custom/charts/SetBarChart.tsx +3 -3
  88. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  89. package/src/components/custom/stepper/Stepper.tsx +3 -3
  90. package/src/components/shell/DeviceMenu.tsx +2 -2
  91. package/src/components/shell/README.md +1 -1
  92. package/src/components/shell/SessionRailSpecimen.stories.tsx +1 -1
  93. package/src/components/shell/SideNav.tsx +1 -1
  94. package/src/components/shell/TopBar.tsx +1 -1
  95. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  96. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  97. package/src/components/ui/avatar/Avatar.tsx +2 -2
  98. package/src/components/ui/badge/Badge.tsx +2 -2
  99. package/src/components/ui/card/Card.tsx +3 -3
  100. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  101. package/src/components/ui/collapse/Collapse.tsx +1 -1
  102. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  103. package/src/components/ui/drawer/Drawer.tsx +3 -3
  104. package/src/components/ui/form-field/FormField.tsx +1 -1
  105. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  106. package/src/components/ui/menu/Menu.tsx +2 -2
  107. package/src/components/ui/pill/Pill.tsx +2 -2
  108. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  109. package/src/components/ui/popover/Popover.tsx +1 -1
  110. package/src/components/ui/progress/Progress.tsx +3 -3
  111. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  112. package/src/components/ui/radio/Radio.tsx +4 -4
  113. package/src/components/ui/select/Select.tsx +3 -3
  114. package/src/components/ui/surface/Surface.stories.tsx +3 -3
  115. package/src/components/ui/surface/Surface.test.tsx +32 -19
  116. package/src/components/ui/surface/Surface.tsx +3 -3
  117. package/src/components/ui/surface/SurfaceContext.ts +19 -29
  118. package/src/components/ui/surface/surface.contract.test.ts +58 -45
  119. package/src/components/ui/switch/Switch.tsx +1 -1
  120. package/src/components/ui/tabs/Tabs.tsx +2 -2
  121. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
  122. package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
  123. package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
  124. package/src/stories/PresetShowcase.stories.tsx +21 -21
  125. package/src/theme/ColorPalettes.stories.tsx +41 -38
  126. package/src/theme/ColorPrimitives.stories.tsx +89 -41
  127. package/src/theme/Depth.stories.tsx +312 -0
  128. package/src/theme/DepthCalibration.stories.tsx +655 -0
  129. package/src/theme/ThemeProvider.tsx +1 -1
  130. package/src/theme/barrel.ts +7 -14
  131. package/src/theme/color-stories.coverage.test.ts +15 -20
  132. package/src/theme/color-story-kit.tsx +1 -1
  133. package/src/theme/color-utils.ts +197 -0
  134. package/src/theme/config.ts +0 -8
  135. package/src/theme/depth-calibration.test.tsx +97 -0
  136. package/src/theme/elevation.test.ts +62 -1
  137. package/src/theme/elevation.ts +34 -8
  138. package/src/theme/global.css +47 -53
  139. package/src/theme/grey-ramp.test.ts +166 -0
  140. package/src/theme/materials.test.ts +106 -0
  141. package/src/theme/materials.ts +211 -0
  142. package/src/theme/presets/audiobook.ts +4 -4
  143. package/src/theme/presets/types.ts +3 -3
  144. package/src/theme/semantic-contrast.test.ts +83 -0
  145. package/src/theme/token-export-guard.test.ts +104 -0
  146. package/src/theme/tokens/primitives.ts +63 -116
  147. package/src/theme/tokens/semantic.ts +130 -82
  148. package/src/utils/colors.ts +1 -1
  149. package/tailwind.config.js +2 -5
  150. package/src/theme/bar-paper.ts +0 -59
  151. package/src/theme/shadows.stories.tsx +0 -523
  152. package/src/theme/shadows.ts +0 -439
@@ -17,13 +17,34 @@
17
17
  * - interactive-* : Hover/focus/active/disabled states
18
18
  */
19
19
 
20
- import {
21
- primitiveColors as p,
22
- primitiveRamps as ramp,
23
- discreteRainbow,
24
- surfaceRampDark as surf,
25
- backgroundFrameDark,
26
- } from './primitives'
20
+ import { primitiveColors as p, primitiveRamps as ramp, greyRamp, discreteRainbow } from './primitives'
21
+
22
+ /**
23
+ * GREY MAPPING (TD-07.14) — how the old cool scales resolved onto `greyRamp`.
24
+ *
25
+ * Every grey below came off `grey` (pure R=G=B) or `neutral` (cool, R−B
26
+ * down to −26). They were snapped by NEAREST L*, not by matching step numbers,
27
+ * because the two old scales were numbered incompatibly: `grey` ran
28
+ * INVERTED and compressed (0 = #6E6E6E lightest, 900 = #101010 darkest), so
29
+ * `grey[400]` and `greyRamp[400]` have nothing to do with each other.
30
+ *
31
+ * grey 200→800 300→900 400→925 500→950
32
+ * neutral 50→50 100→100 300→200 400→400 500→600 600→700 900→950
33
+ *
34
+ * Surfaces move ΔE 1.9–4.8 — at or near imperceptible. The TEXT roles move
35
+ * further (8.6–12.8), and that is the intended part of the change: they are
36
+ * picking up the warmth the surfaces already had.
37
+ *
38
+ * Light gets its own column because strict L*-nearest collapsed `neutral[50]`
39
+ * and `[100]` onto one step, flattening surface-elevated into surface-raised.
40
+ * Light is still deferred as a design question — there is no `surfaceRampLight`.
41
+ *
42
+ * `border-default`, `-subtle` and `-strong` are GONE, replaced by `hairline-*`.
43
+ * Once the planes became ramp steps, every value dark enough to read as a
44
+ * border was also a plane's fill. Alpha composites instead of colliding.
45
+ *
46
+ * `text-tertiary` on dark is the one role that ignores L*-nearest; see it.
47
+ */
27
48
 
28
49
  // Light mode semantic colors (default)
29
50
  export const semanticColorsLight = {
@@ -61,9 +82,9 @@ export const semanticColorsLight = {
61
82
  'status-error-dark': ramp.red[700],
62
83
  'status-error-subtle': ramp.red[50],
63
84
 
64
- 'status-error-vivid': p.redVivid[500], // vivid alert red
65
- 'status-error-vivid-light': p.redVivid[400],
66
- 'status-error-vivid-dark': p.redVivid[700],
85
+ 'status-error-vivid': ramp.red[600], // vivid alert red
86
+ 'status-error-vivid-light': ramp.red[500],
87
+ 'status-error-vivid-dark': ramp.red[700],
67
88
  'status-error-vivid-subtle': 'rgba(255, 71, 87, 0.12)',
68
89
 
69
90
  'status-warning': ramp.amber[300],
@@ -91,7 +112,7 @@ export const semanticColorsLight = {
91
112
  'result-degrade-dark': '#b30000',
92
113
  'result-inconclusive': '#9E9A97', // Gray - no clear result
93
114
  'result-inconclusive-light': 'rgba(158, 154, 151, 0.12)',
94
- 'result-neutral': p.neutral[500], // Neutral baseline
115
+ 'result-neutral': greyRamp[600], // Neutral baseline
95
116
 
96
117
  // Text on result backgrounds (on-result-*)
97
118
  'on-result-improve': p.white,
@@ -114,43 +135,34 @@ export const semanticColorsLight = {
114
135
  // Text colors (text-*)
115
136
  'text-primary': '#121828',
116
137
  'text-secondary': '#65748B',
117
- 'text-tertiary': p.neutral[400],
138
+ 'text-tertiary': greyRamp[400],
118
139
  'text-disabled': 'rgba(55, 65, 81, 0.48)',
119
140
  'text-inverse': p.white,
120
- 'text-link': p.blue[600],
121
- 'text-link-hover': p.blue[700],
141
+ 'text-link': ramp.blue[600],
142
+ 'text-link-hover': ramp.blue[700],
122
143
 
123
144
  // Surface colors (surface-*) - for elevated containers
124
145
  'surface-base': p.white,
125
- 'surface-elevated': p.neutral[50], // slightly off-white for elevated cards
126
- 'surface-raised': p.neutral[100], // light gray for raised cards
146
+ 'surface-elevated': greyRamp[50], // slightly off-white for elevated cards
147
+ 'surface-raised': greyRamp[100], // light gray for raised cards
127
148
  'surface-overlay': p.white,
128
- 'surface-input': p.neutral[50], // Input field background (filled variant)
129
- // Deepest pressed pit (TD-surface-tokens, S-3) — light-mode counterpart of
130
- // the dark ramp's `inset` plane; a placeholder pairing, not part of the
131
- // dark-ramp investigation (surface.contract.test.ts is dark-mode only).
132
- 'surface-inset': p.neutral[300],
149
+ 'surface-input': greyRamp[50], // Input field background (filled variant)
133
150
 
134
151
  // Background colors (background-*)
135
152
  'background-base': '#EBEBEB',
136
153
  'background-default': p.white,
137
- 'background-subtle': p.neutral[50],
138
- // Frame/bezel chrome (TD-surface-tokens, S-3) — top bar + side nav shell,
139
- // one step below `background-base` (darker still than `surface-inset`
140
- // above, mirroring the dark-mode ordering). Placeholder pairing for light
141
- // mode (see dark-mode note on `surface-inset` above).
142
- 'background-frame': p.neutral[400],
154
+ 'background-subtle': greyRamp[50],
155
+ // Frame/bezel chrome — top bar + side nav shell, one step below
156
+ // `background-base`. Placeholder pairing for light mode, which is deferred.
157
+ 'background-frame': greyRamp[400],
143
158
 
144
159
  // Border colors (border-*)
145
- 'border-default': '#E8E9EB',
146
- 'border-subtle': p.neutral[100],
147
- 'border-strong': p.neutral[300],
148
- 'border-prominent': p.neutral[400], // high-visibility divider
149
- 'border-focus': p.blue[600],
150
- 'border-input': p.neutral[300], // Input field border
151
- 'border-input-hover': p.neutral[400], // Input field border on hover
152
- 'border-input-focus': p.blue[600], // Input field border on focus
153
- 'border-input-error': p.red[600], // Input field border on error
160
+ 'border-prominent': greyRamp[400], // high-visibility divider
161
+ 'border-focus': ramp.blue[600],
162
+ 'border-input': greyRamp[200], // Input field border
163
+ 'border-input-hover': greyRamp[400], // Input field border on hover
164
+ 'border-input-focus': ramp.blue[600], // Input field border on focus
165
+ 'border-input-error': ramp.red[600], // Input field border on error
154
166
 
155
167
  // Alpha hairline separators (surface-independent — composite toward black on
156
168
  // light surfaces, mirroring the dark-mode white-alpha family). See §4/S-2.
@@ -170,7 +182,7 @@ export const semanticColorsLight = {
170
182
  'divider': '#E8E9EB',
171
183
 
172
184
  // Avatar default
173
- 'avatar-background': p.neutral[500],
185
+ 'avatar-background': greyRamp[600],
174
186
  'avatar-text': p.white,
175
187
  } as const
176
188
 
@@ -210,9 +222,9 @@ export const semanticColorsDark = {
210
222
  'status-error-dark': ramp.red[700],
211
223
  'status-error-subtle': 'rgba(209, 67, 67, 0.12)',
212
224
 
213
- 'status-error-vivid': p.redVivid[500],
214
- 'status-error-vivid-light': p.redVivid[400],
215
- 'status-error-vivid-dark': p.redVivid[700],
225
+ 'status-error-vivid': ramp.red[600],
226
+ 'status-error-vivid-light': ramp.red[500],
227
+ 'status-error-vivid-dark': ramp.red[700],
216
228
  'status-error-vivid-subtle': 'rgba(255, 71, 87, 0.12)',
217
229
 
218
230
  'status-warning': ramp.amber[300],
@@ -240,7 +252,7 @@ export const semanticColorsDark = {
240
252
  'result-degrade-dark': '#b30000',
241
253
  'result-inconclusive': '#9E9A97',
242
254
  'result-inconclusive-light': 'rgba(158, 154, 151, 0.16)',
243
- 'result-neutral': p.neutral[400],
255
+ 'result-neutral': greyRamp[400],
244
256
 
245
257
  // Text on result backgrounds
246
258
  'on-result-improve': p.white,
@@ -260,13 +272,20 @@ export const semanticColorsDark = {
260
272
  'data-10': discreteRainbow[22],
261
273
 
262
274
  // Text colors - inverted for dark mode
263
- 'text-primary': p.neutral[100],
264
- 'text-secondary': p.neutral[400],
265
- 'text-tertiary': p.neutral[500],
275
+ 'text-primary': greyRamp[50],
276
+ 'text-secondary': greyRamp[400],
277
+ // Deliberately ONE step lighter than the L*-nearest match (`greyRamp[600]`).
278
+ // The colour this replaced failed WCAG large-text outright on the three
279
+ // lightest planes — 2.96 / 2.72 / 2.49 against elevated / raised / overlay —
280
+ // and `600` carried that failure forward almost exactly (2.93 / 2.70 / 2.47).
281
+ // `500` clears 3:1 everywhere (3.32–5.34). Contrast beats colour fidelity for
282
+ // text roles; borders and surfaces still use strict L*-nearest. See
283
+ // semantic-contrast.test.ts, which fails if this is moved back down.
284
+ 'text-tertiary': greyRamp[500],
266
285
  'text-disabled': 'rgba(255, 255, 255, 0.38)',
267
- 'text-inverse': p.neutral[900],
286
+ 'text-inverse': greyRamp[950],
268
287
  'text-link': '#828DF8',
269
- 'text-link-hover': p.blue[400],
288
+ 'text-link-hover': ramp.blue[400],
270
289
 
271
290
  // Surface colors - dark backgrounds — warm-tapered DERIVED ramp (TD-surface-tokens,
272
291
  // S-1, re-spaced S-3). Shipped verbatim from `deriveSurfaceRamp()` — see
@@ -274,50 +293,79 @@ export const semanticColorsDark = {
274
293
  // and `surface-elevated` are distinct (were both #191919 pre-S-1); `surface-input`
275
294
  // tracks one plane above `surface-base`, same relative position as before the remap.
276
295
  // `background-base` backs `Surface level="background"` (SurfaceContext.SURFACE_LEVEL_TOKEN),
277
- // so it takes the ramp's `background` (shell/frame) role, NOT `inset`. The ramp's
278
- // deepest `inset` plane is now promoted to `surface-inset` below (S-3) still no
279
- // `SurfaceLevel` member for it (follow-up, alongside the elevation.ts -1/-2 pressed
280
- // levels — parallel workstream, not this branch).
281
- 'surface-base': surf.base, // main surface (#252321, L*13.5)
282
- 'surface-elevated': surf.elevated, // elevated surface (#2C2A28, L*17 nav/rail)
283
- 'surface-raised': surf.raised, // raised surface (#31302F, L*20 cards)
284
- 'surface-overlay': surf.overlay, // overlay surface (#373635, L*22.5 — hero/popover)
285
- 'surface-input': surf.elevated, // input surface (#2C2A28 — one plane above base)
286
- 'surface-inset': surf.inset, // deepest pressed pit (#13100D, L*4.5 — sub-shell well)
296
+ // so it takes the ramp's shell role. The deepest plane is `background-frame`,
297
+ // which is also the `SurfaceLevel` floor a pressed surface clamps at.
298
+ 'surface-base': greyRamp[925], // main surface (#252321, L*13.9)
299
+ 'surface-elevated': greyRamp[900], // elevated surface (#2C2A28, L*17.2 nav/rail)
300
+ 'surface-raised': greyRamp[875], // raised surface (#31302F, L*19.9 — cards)
301
+ 'surface-overlay': greyRamp[850], // overlay surface (#373635, L*22.7 hero/popover)
302
+ 'surface-input': greyRamp[900], // input surface (#2C2A28one plane above base)
287
303
 
288
304
  // Background colors — same ramp, the frame/shell end of it.
289
- 'background-base': surf.background, // shell / frame (#1C1916, L*9 — Surface level="background")
290
- 'background-default': surf.base, // main background (#252321, L*13.5 — matches surface-base)
291
- 'background-subtle': surf.elevated, // subtle background (#2C2A28, L*17 — matches surface-elevated)
292
- // Frame/bezel chrome (S-3) — the top bar + side nav shell; one step BELOW
293
- // `background-base`, darker even than `surface-inset` it exits the content
294
- // ramp entirely rather than sitting in it. See `backgroundFrameDark` in
295
- // primitives.ts.
296
- 'background-frame': backgroundFrameDark, // frame / bezel (#100D0A, L*3.79)
297
-
298
- // Border colors `border-subtle` (#1C1C1C) previously collided with
299
- // `surface-raised` (also #1C1C1C, an invisible border on raised cards). The
300
- // re-spaced ramp above moved `surface-raised` to #2D2C2B, so this value is now
301
- // distinct from every surface hex without needing to change it see
302
- // `surface.contract.test.ts` R2/R4.
303
- 'border-default': p.charcoal[400], // default border
304
- 'border-subtle': p.charcoal[500], // subtle border
305
- 'border-strong': p.charcoal[300], // strong border
306
- 'border-prominent': p.charcoal[200], // high-visibility divider
305
+ 'background-base': greyRamp[950], // shell (#1C1916, L*9 — Surface level="background")
306
+ 'background-default': greyRamp[925], // main background (#252321, L*13.9 — matches surface-base)
307
+ 'background-subtle': greyRamp[900], // subtle background (#2C2A28, L*17.2 — matches surface-elevated)
308
+ // Frame/bezel chrome — the top bar + side nav shell, one step BELOW
309
+ // `background-base`. It used to be described as sitting OUTSIDE the ramp; it
310
+ // is simply the ramp's last step now, and the floor `<Surface pressed>` clamps at.
311
+ 'background-frame': greyRamp[975], // frame / bezel (#100D0A, L*3.8)
312
+
313
+ // Border colors — solid dark borders are RETIRED (TD-07.14). Not a preference — a structural
314
+ // consequence of one ramp. Every step from 850 to 975 is now a surface plane,
315
+ // so a solid border dark enough to read as a border necessarily equals some
316
+ // plane's fill, which is exactly the collision `surface.contract.test.ts` R2
317
+ // forbids. The only non-plane steps left in that band are 800 and 700, and
318
+ // borders there jump from L*~10-25 to L*~28-38 — a restyle, not a migration.
319
+ //
320
+ // So separation moves to the alpha hairlines, which dark mode already called
321
+ // "the primary separation cue". Being alpha, they composite against whatever
322
+ // plane they land on and cannot collide with any of them — the problem stops
323
+ // existing rather than being re-solved per plane. The three solid tokens were
324
+ // deleted outright; consumers point at the `hairline-*` family below.
325
+ //
326
+ // `border-prominent` stays SOLID: it is the one border meant to be seen
327
+ // outright (4 call sites, high-visibility dividers), and grey-800 is not a
328
+ // plane, so it keeps its job without collision.
329
+ 'border-prominent': greyRamp[800], // high-visibility divider
307
330
  'border-focus': '#828DF8',
308
- 'border-input': p.neutral[600],
309
- 'border-input-hover': p.neutral[500],
331
+ 'border-input': greyRamp[700],
332
+ 'border-input-hover': greyRamp[600],
310
333
  'border-input-focus': '#828DF8',
311
- 'border-input-error': p.red[500],
334
+ 'border-input-error': ramp.red[500],
312
335
 
313
336
  // Alpha hairline separators — the primary separation cue (§4/S-2). Self-
314
337
  // normalizing: composites toward white by ~the same amount on ANY plane, so
315
338
  // one family works at every elevation instead of per-surface border tokens.
316
339
  // Shadows are demoted to floating-overlay use only (not shipped as a fill
317
340
  // separator here — see elevation.ts, follow-up S-4).
318
- 'hairline-subtle': 'rgba(255, 255, 255, 0.06)',
319
- 'hairline-default': 'rgba(255, 255, 255, 0.09)',
320
- 'hairline-strong': 'rgba(255, 255, 255, 0.14)',
341
+ //
342
+ // RETUNED on the wall (VW-99, S-6). The original .06/.09/.14 were set at a
343
+ // desk. On the panel at ~3 m `subtle` rendered but was indiscernible, and
344
+ // `default` — the load-bearing one, with no solid-border fallback since
345
+ // TD-07.14 — went weak on the lightest plane. `strong` read cleanly with room
346
+ // to spare, which is what made raising the whole family viable.
347
+ //
348
+ // Each tier steps up onto the next MEASURED-GOOD rung rather than an
349
+ // interpolated one: the new `subtle` is the old `default`, and the new
350
+ // `default` is the old `strong` — a value just confirmed legible on all four
351
+ // planes. Spacing goes 1 : 1.56 : 2.33, against the original 1 : 1.5 : 2.33.
352
+ //
353
+ // RUN 2 (VW-99): all three tiers were seen on all four planes — the retune
354
+ // worked — but `subtle` was still "a bit hard to read", so the whole family
355
+ // took a further +.01. That is a comfort margin on a passing row, not a fix
356
+ // for a failing one, which is why it is a flat offset: the 1 : 1.56 : 2.33
357
+ // spacing that the run validated is preserved rather than re-derived.
358
+ //
359
+ // Keep these to TWO decimals. react-native-web quantises alpha to 8 bits, so
360
+ // a 3-decimal value silently rounds on the way to the DOM (.135 renders as
361
+ // .13) — the token would say one thing and paint another.
362
+ //
363
+ // DARK ONLY. The light family above composites toward BLACK on light planes,
364
+ // which this run says nothing about — do not mirror these numbers into it
365
+ // without its own verification.
366
+ 'hairline-subtle': 'rgba(255, 255, 255, 0.10)',
367
+ 'hairline-default': 'rgba(255, 255, 255, 0.15)',
368
+ 'hairline-strong': 'rgba(255, 255, 255, 0.22)',
321
369
 
322
370
  // Interactive states
323
371
  'interactive-hover': 'rgba(255, 255, 255, 0.04)',
@@ -328,10 +376,10 @@ export const semanticColorsDark = {
328
376
  'interactive-disabled-text': 'rgba(255, 255, 255, 0.26)',
329
377
 
330
378
  // Divider
331
- 'divider': p.charcoal[400], // divider color
379
+ 'divider': 'rgba(255, 255, 255, 0.09)',
332
380
 
333
381
  // Avatar default
334
- 'avatar-background': p.neutral[600],
382
+ 'avatar-background': greyRamp[700],
335
383
  'avatar-text': p.white,
336
384
  } as const
337
385
 
@@ -5,7 +5,7 @@
5
5
  * color math for more perceptually consistent results across hues.
6
6
  */
7
7
 
8
- export { lighten, darken } from '../theme/shadows'
8
+ export { lighten, darken } from '../theme/color-utils'
9
9
 
10
10
  export type StatusType = 'success' | 'error' | 'warning' | 'info'
11
11
  export type ResultType = 'improve' | 'degrade' | 'inconclusive' | 'neutral'
@@ -119,7 +119,6 @@ module.exports = {
119
119
  raised: 'var(--color-surface-raised)',
120
120
  overlay: 'var(--color-surface-overlay)',
121
121
  input: 'var(--color-surface-input)',
122
- inset: 'var(--color-surface-inset)',
123
122
  },
124
123
  // Background colors
125
124
  background: {
@@ -140,11 +139,9 @@ module.exports = {
140
139
  hover: 'var(--color-text-link-hover)',
141
140
  },
142
141
  },
143
- // Border colors
142
+ // Border colors — DEFAULT/subtle/strong are RETIRED (TD-07.14); use the
143
+ // `hairline` family below for separation.
144
144
  border: {
145
- DEFAULT: 'var(--color-border-default)',
146
- subtle: 'var(--color-border-subtle)',
147
- strong: 'var(--color-border-strong)',
148
145
  prominent: 'var(--color-border-prominent)',
149
146
  focus: 'var(--color-border-focus)',
150
147
  input: {
@@ -1,59 +0,0 @@
1
- // The matte "paper" treatment for a colored bar — a brightness-scaled fractal-noise
2
- // grain + a crisp top rim-light + a soft contact shadow, so filled bars read as one
3
- // tactile material across the system (velocity bars, ROM bars, …). Web-only CSS
4
- // (backgroundImage + boxShadow) — harmless on native (ignored), styled for RNW/web.
5
- //
6
- // Promoted out of VelocityStrip so every colored-bar family composes ONE treatment
7
- // instead of re-rolling it. Consumers spread it onto a bar's style: `...barPaper(hex)`.
8
- import type { ViewStyle } from 'react-native'
9
-
10
- /**
11
- * Grain opacity scaled by a color's perceived brightness: a bright bar keeps full
12
- * texture, a darker bar carries proportionally less so the grain never reads hot on a
13
- * dark tone. Anchored so lum ≈ 0.13 → ≈ 0.06 and lum ≈ 0.58 → ≈ 0.20.
14
- */
15
- function grainOpacityForColor(hex: string): number {
16
- const h = hex.replace('#', '')
17
- const full =
18
- h.length === 3
19
- ? h
20
- .split('')
21
- .map((ch) => ch + ch)
22
- .join('')
23
- : h
24
- const r = parseInt(full.slice(0, 2), 16)
25
- const g = parseInt(full.slice(2, 4), 16)
26
- const b = parseInt(full.slice(4, 6), 16)
27
- if ([r, g, b].some(Number.isNaN)) return 0.14
28
- const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
29
- return Math.min(0.24, Math.max(0.04, 0.02 + 0.31 * lum))
30
- }
31
-
32
- /** The matte paper grain tile (fractalNoise), opacity brightness-scaled to `color`. */
33
- function barGrain(color: string): string {
34
- const op = grainOpacityForColor(color).toFixed(3)
35
- return (
36
- `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E` +
37
- `%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E` +
38
- `%3Crect width='120' height='120' filter='url(%23n)' opacity='${op}'/%3E%3C/svg%3E")`
39
- )
40
- }
41
-
42
- /**
43
- * Paper/raised treatment for a colored bar of the given `color`: brightness-scaled
44
- * matte grain + a crisp top rim-light + a defined contact shadow. Spread onto a bar's
45
- * style. Presentation-only; scoped to filled bars (not shared strips/dividers).
46
- *
47
- * `flip` = a vertically-mirrored (`scaleY(-1)`) plot — a `down` wing of the diverging
48
- * chart. The contact shadow's y-offset is pre-inverted so the mirror lands it pointing
49
- * AWAY from the shared axis (not up across it), and the top rim-light is DROPPED (mirrored
50
- * it would sit on the axis edge and read wrong; grain + shadow alone carry the material).
51
- */
52
- export function barPaper(color: string, flip = false): ViewStyle {
53
- return {
54
- backgroundImage: barGrain(color),
55
- boxShadow: flip
56
- ? '0 -6px 16px rgba(0,0,0,0.45)'
57
- : 'inset 0 1.5px 0 rgba(255,255,255,0.22), 0 6px 16px rgba(0,0,0,0.45)',
58
- } as unknown as ViewStyle
59
- }