@titan-design/react-ui 0.5.0 → 0.6.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.
- package/dist/bodymap.js +232 -185
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +232 -185
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +164 -8
- package/dist/index.d.ts +164 -8
- package/dist/index.js +835 -342
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +816 -343
- package/dist/index.mjs.map +1 -1
- package/dist/pages.js +570 -522
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +570 -522
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +461 -199
- package/dist/theme/index.d.ts +461 -199
- package/dist/theme/index.js +311 -97
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +303 -98
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +148 -139
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +148 -139
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +3 -1
- package/src/components/custom/DateTime/DateTime.stories.tsx +82 -6
- package/src/components/custom/DateTime/DateTime.test.tsx +29 -3
- package/src/components/custom/DateTime/DateTime.tsx +55 -15
- package/src/components/custom/Gauge/Gauge.test.tsx +2 -2
- package/src/components/custom/Gauge/Gauge.tsx +6 -3
- package/src/components/custom/Scatter/Scatter.tsx +3 -5
- package/src/components/custom/Treemap/Treemap.tsx +3 -5
- package/src/components/custom/Typography/Typography.stories.tsx +45 -27
- package/src/components/custom/Typography/Typography.test.tsx +13 -3
- package/src/components/custom/Typography/Typography.tsx +5 -0
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +2 -1
- package/src/components/custom/Workout/BodyMap.tsx +6 -3
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +6 -3
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +2 -2
- package/src/components/custom/Workout/CapacityBandChart.tsx +9 -6
- package/src/components/custom/Workout/DeviationBar.test.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.tsx +7 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +2 -1
- package/src/components/custom/Workout/InputBar.tsx +2 -1
- package/src/components/custom/Workout/IntensityBar.test.tsx +8 -8
- package/src/components/custom/Workout/IntensityBar.tsx +10 -6
- package/src/components/custom/Workout/MesoCard.tsx +8 -3
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.tsx +3 -2
- package/src/components/custom/Workout/MesoStatusCard.tsx +20 -13
- package/src/components/custom/Workout/MuscleGroupChip.tsx +7 -4
- package/src/components/custom/Workout/PrBadge.tsx +5 -2
- package/src/components/custom/Workout/PrHistoryModal.tsx +4 -3
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +2 -1
- package/src/components/custom/Workout/ReadinessCheck.tsx +7 -4
- package/src/components/custom/Workout/RestTimer.tsx +2 -1
- package/src/components/custom/Workout/SetRow.tsx +2 -1
- package/src/components/custom/Workout/Sparkline.tsx +2 -1
- package/src/components/custom/Workout/StatusDot.test.tsx +2 -2
- package/src/components/custom/Workout/StatusDot.tsx +12 -9
- package/src/components/custom/Workout/StrengthTrendChart.tsx +9 -6
- package/src/components/custom/Workout/SupersetWrapper.tsx +2 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +6 -3
- package/src/components/custom/Workout/VelocityStrip.test.tsx +3 -3
- package/src/components/custom/Workout/WeekRow.tsx +3 -2
- package/src/components/custom/Workout/WeightBadge.tsx +6 -2
- package/src/components/custom/Workout/WorkoutCard.tsx +5 -2
- package/src/components/custom/Workout/WorkoutPill.tsx +12 -8
- package/src/components/custom/Workout/icons.tsx +6 -67
- package/src/components/icons/SvgIcon.tsx +50 -0
- package/src/components/icons/icons.stories.tsx +57 -0
- package/src/components/icons/icons.test.tsx +43 -0
- package/src/components/icons/icons.tsx +45 -0
- package/src/components/icons/index.ts +4 -0
- package/src/components/index.ts +6 -0
- package/src/components/shell/BrandLockup.stories.tsx +27 -0
- package/src/components/shell/BrandLockup.test.tsx +23 -0
- package/src/components/shell/BrandLockup.tsx +47 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +28 -0
- package/src/components/shell/DeviceIndicator.test.tsx +26 -0
- package/src/components/shell/DeviceIndicator.tsx +52 -0
- package/src/components/shell/DeviceMenu.stories.tsx +47 -0
- package/src/components/shell/DeviceMenu.test.tsx +40 -0
- package/src/components/shell/DeviceMenu.tsx +65 -0
- package/src/components/shell/DeviceRow.stories.tsx +38 -0
- package/src/components/shell/DeviceRow.test.tsx +37 -0
- package/src/components/shell/DeviceRow.tsx +58 -0
- package/src/components/shell/README.md +86 -0
- package/src/components/shell/SessionStatePill.stories.tsx +27 -0
- package/src/components/shell/SessionStatePill.test.tsx +25 -0
- package/src/components/shell/SessionStatePill.tsx +46 -0
- package/src/components/shell/TopBar.stories.tsx +49 -0
- package/src/components/shell/TopBar.test.tsx +36 -0
- package/src/components/shell/TopBar.tsx +89 -0
- package/src/components/shell/index.ts +8 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +37 -1
- package/src/components/ui/indicator/Indicator.test.tsx +19 -1
- package/src/components/ui/indicator/Indicator.tsx +45 -6
- package/src/components/ui/indicator/index.ts +1 -1
- package/src/components/ui/spinner/Spinner.tsx +3 -2
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +2 -1
- package/src/theme/ColorSystem.stories.tsx +323 -0
- package/src/theme/Colors.stories.tsx +69 -1
- package/src/theme/barrel.ts +1 -0
- package/src/theme/config.ts +16 -3
- package/src/theme/extracted-colors-dataviz.ts +21 -0
- package/src/theme/extracted-colors-ui.ts +20 -0
- package/src/theme/global.css +64 -52
- package/src/theme/gradients.test.ts +25 -0
- package/src/theme/gradients.ts +36 -0
- package/src/theme/tokens/primitives.test.ts +232 -0
- package/src/theme/tokens/primitives.ts +247 -0
- package/src/theme/tokens/semantic.ts +93 -81
- package/src/theme/workout-tokens.ts +22 -21
- package/src/utils/avatar-color.ts +3 -3
- package/tailwind.config.js +10 -3
|
@@ -62,6 +62,34 @@ export const primitiveColors = {
|
|
|
62
62
|
900: '#7F1D1D',
|
|
63
63
|
},
|
|
64
64
|
|
|
65
|
+
// Vivid green scale (live/go success)
|
|
66
|
+
greenVivid: {
|
|
67
|
+
50: '#E9FBF0',
|
|
68
|
+
100: '#C8F7DA',
|
|
69
|
+
200: '#9FF0BF',
|
|
70
|
+
300: '#6BE79D',
|
|
71
|
+
400: '#47DE84', // Success vivid light
|
|
72
|
+
500: '#2ED573', // Success vivid main
|
|
73
|
+
600: '#22B85F',
|
|
74
|
+
700: '#1A9950', // Success vivid dark
|
|
75
|
+
800: '#157A40',
|
|
76
|
+
900: '#105C30',
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// Vivid red scale (alert)
|
|
80
|
+
redVivid: {
|
|
81
|
+
50: '#FFECEE',
|
|
82
|
+
100: '#FFD6DB',
|
|
83
|
+
200: '#FFB3BC',
|
|
84
|
+
300: '#FF8593',
|
|
85
|
+
400: '#FF6070', // Error vivid light
|
|
86
|
+
500: '#FF4757', // Error vivid main
|
|
87
|
+
600: '#E63548',
|
|
88
|
+
700: '#C42539', // Error vivid dark
|
|
89
|
+
800: '#9E1C2C',
|
|
90
|
+
900: '#7A1520',
|
|
91
|
+
},
|
|
92
|
+
|
|
65
93
|
// Amber scale (warning)
|
|
66
94
|
amber: {
|
|
67
95
|
50: '#FFFBEB',
|
|
@@ -152,6 +180,225 @@ export const primitiveColors = {
|
|
|
152
180
|
transparent: 'transparent',
|
|
153
181
|
} as const
|
|
154
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Derived tonal ramps (TD-05.09 color foundations)
|
|
185
|
+
*
|
|
186
|
+
* Seven OKLCH-generated hue ramps, 11 perceptual lightness steps each (50 -> 950).
|
|
187
|
+
* Built with hue-torsion + a chroma arc, anchored through existing brand/semantic
|
|
188
|
+
* hexes. `pin` marks the step each ramp flows through its anchor.
|
|
189
|
+
* This is the single source of truth for chromatic hexes — the categorical palette
|
|
190
|
+
* below references these steps rather than duplicating values.
|
|
191
|
+
* See coordination/design-explorations/foundations.
|
|
192
|
+
*/
|
|
193
|
+
export const primitiveRamps = {
|
|
194
|
+
red: {
|
|
195
|
+
50: '#FFF4F4',
|
|
196
|
+
100: '#FFE3E5',
|
|
197
|
+
200: '#FFC2C5',
|
|
198
|
+
300: '#FF9A9D',
|
|
199
|
+
400: '#F77175',
|
|
200
|
+
500: '#E05254',
|
|
201
|
+
600: '#D14343', // pin
|
|
202
|
+
700: '#A4221C',
|
|
203
|
+
800: '#7E1002',
|
|
204
|
+
900: '#5A0900',
|
|
205
|
+
950: '#3D0400',
|
|
206
|
+
},
|
|
207
|
+
orange: {
|
|
208
|
+
50: '#FFF5ED',
|
|
209
|
+
100: '#FFE6D4',
|
|
210
|
+
200: '#FFC7A2',
|
|
211
|
+
300: '#FFA063',
|
|
212
|
+
400: '#FF7900', // pin
|
|
213
|
+
500: '#DA5F00',
|
|
214
|
+
600: '#B94A00',
|
|
215
|
+
700: '#983804',
|
|
216
|
+
800: '#6F290F',
|
|
217
|
+
900: '#4E1C0C',
|
|
218
|
+
950: '#331107',
|
|
219
|
+
},
|
|
220
|
+
amber: {
|
|
221
|
+
50: '#FFF7DD',
|
|
222
|
+
100: '#FFEAA9',
|
|
223
|
+
200: '#FFD352',
|
|
224
|
+
300: '#F9B415', // pin
|
|
225
|
+
400: '#E08C00',
|
|
226
|
+
500: '#C27400',
|
|
227
|
+
600: '#A45E00',
|
|
228
|
+
700: '#814D14',
|
|
229
|
+
800: '#5B3A1A',
|
|
230
|
+
900: '#442503',
|
|
231
|
+
950: '#301500',
|
|
232
|
+
},
|
|
233
|
+
green: {
|
|
234
|
+
50: '#E3FFEE',
|
|
235
|
+
100: '#B5FFD2',
|
|
236
|
+
200: '#58F69E',
|
|
237
|
+
300: '#2ED573', // pin
|
|
238
|
+
400: '#21C05D',
|
|
239
|
+
500: '#22A444',
|
|
240
|
+
600: '#298732',
|
|
241
|
+
700: '#2B6B25',
|
|
242
|
+
800: '#264D1C',
|
|
243
|
+
900: '#1B3515',
|
|
244
|
+
950: '#11220D',
|
|
245
|
+
},
|
|
246
|
+
cyan: {
|
|
247
|
+
50: '#E6FBFF',
|
|
248
|
+
100: '#C2F6FF',
|
|
249
|
+
200: '#62EAFF',
|
|
250
|
+
300: '#22D3EE', // pin
|
|
251
|
+
400: '#01B5D1',
|
|
252
|
+
500: '#2697B7',
|
|
253
|
+
600: '#307B9B',
|
|
254
|
+
700: '#2A617F',
|
|
255
|
+
800: '#22465F',
|
|
256
|
+
900: '#0B3149',
|
|
257
|
+
950: '#001F36',
|
|
258
|
+
},
|
|
259
|
+
blue: {
|
|
260
|
+
50: '#EFF8FF',
|
|
261
|
+
100: '#D9EFFF',
|
|
262
|
+
200: '#ACDBFF',
|
|
263
|
+
300: '#78C2FF',
|
|
264
|
+
400: '#3CA8FF',
|
|
265
|
+
500: '#2196F3', // pin
|
|
266
|
+
600: '#1072CB',
|
|
267
|
+
700: '#135AA8',
|
|
268
|
+
800: '#14407E',
|
|
269
|
+
900: '#112C5A',
|
|
270
|
+
950: '#091B3C',
|
|
271
|
+
},
|
|
272
|
+
magenta: {
|
|
273
|
+
50: '#FFF3F9',
|
|
274
|
+
100: '#FFE2F1',
|
|
275
|
+
200: '#FFBDE2',
|
|
276
|
+
300: '#FF8FD5',
|
|
277
|
+
400: '#ED69C3',
|
|
278
|
+
500: '#D548AF',
|
|
279
|
+
600: '#BA2996',
|
|
280
|
+
700: '#9C0D7A', // pin
|
|
281
|
+
800: '#77005A',
|
|
282
|
+
900: '#56003F',
|
|
283
|
+
950: '#3B0029',
|
|
284
|
+
},
|
|
285
|
+
} as const
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Categorical (qualitative) palette — an ordered, CVD-safe series expressed as
|
|
289
|
+
* references into {@link primitiveRamps} (one source of truth; the palette moves
|
|
290
|
+
* with the ramps).
|
|
291
|
+
*
|
|
292
|
+
* Design: a single canonical hue order (blue, magenta, red, orange, green, cyan,
|
|
293
|
+
* amber), with steps chosen vivid-midtone-first and fanned lighter/darker as the
|
|
294
|
+
* series grows to preserve separation. The sequence is nested-stable — a chart
|
|
295
|
+
* with N series uses the first N colors, and the series index is stable as N
|
|
296
|
+
* changes. Colorblind-safe worst-case deuteranopia/protanopia floor 8 holds
|
|
297
|
+
* through {@link CATEGORICAL_CVD_SAFE_MAX} colors; the 7th is an extended color
|
|
298
|
+
* that should be paired with a legend or other secondary encoding.
|
|
299
|
+
*
|
|
300
|
+
* Two variants: `default` (vivid, sits on neutral/light surfaces, legible under
|
|
301
|
+
* black text) and `dark` (deeper shades legible under white text on a fill). A
|
|
302
|
+
* separate "light" variant was dropped — the vivid `default` picks already clear
|
|
303
|
+
* black-text contrast, so it doubles as the light-context palette.
|
|
304
|
+
*/
|
|
305
|
+
export const categoricalPalette = {
|
|
306
|
+
default: [
|
|
307
|
+
primitiveRamps.blue[500],
|
|
308
|
+
primitiveRamps.magenta[500],
|
|
309
|
+
primitiveRamps.red[500],
|
|
310
|
+
primitiveRamps.orange[400],
|
|
311
|
+
primitiveRamps.green[300],
|
|
312
|
+
primitiveRamps.cyan[300],
|
|
313
|
+
primitiveRamps.amber[600], // extended (7th) — pair with a legend
|
|
314
|
+
],
|
|
315
|
+
dark: [
|
|
316
|
+
primitiveRamps.blue[600],
|
|
317
|
+
primitiveRamps.magenta[600],
|
|
318
|
+
primitiveRamps.red[500],
|
|
319
|
+
primitiveRamps.orange[600],
|
|
320
|
+
primitiveRamps.green[800],
|
|
321
|
+
primitiveRamps.cyan[800],
|
|
322
|
+
primitiveRamps.amber[500], // extended (7th) — pair with a legend
|
|
323
|
+
],
|
|
324
|
+
} as const
|
|
325
|
+
|
|
326
|
+
/** Largest series count that holds the CVD floor without a legend. */
|
|
327
|
+
export const CATEGORICAL_CVD_SAFE_MAX = 6
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Categorical palette variant.
|
|
331
|
+
* - `default` : vivid; neutral/light surfaces, legible under black text
|
|
332
|
+
* - `dark` : deeper shades, legible under white text on a filled swatch
|
|
333
|
+
*/
|
|
334
|
+
export type CategoricalVariant = keyof typeof categoricalPalette
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get a color from the ordered categorical palette by series index.
|
|
338
|
+
* The palette is nested-stable, so the color for a given index does not change
|
|
339
|
+
* with the total series count; `index` simply wraps past the end.
|
|
340
|
+
*
|
|
341
|
+
* @param index - 0-based series index (wraps past the end)
|
|
342
|
+
* @param variant - `default` (black-text / light surfaces) or `dark` (white text)
|
|
343
|
+
* @returns The hex color string
|
|
344
|
+
*/
|
|
345
|
+
export function getCategoricalColor(index: number, variant: CategoricalVariant = 'default'): string {
|
|
346
|
+
const palette = categoricalPalette[variant]
|
|
347
|
+
index = Math.max(0, Math.floor(index))
|
|
348
|
+
return palette[index % palette.length]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Diverging status scale (BodyMap training-status: under → optimal → over) as
|
|
353
|
+
* references into {@link primitiveRamps}. A true diverging shape — `optimal` is
|
|
354
|
+
* the lightest center, the extremes go darker — so the signal reads in lightness
|
|
355
|
+
* (colorblind-robust; worst-case deut/protanopia min ΔE ~10.9) and the direction
|
|
356
|
+
* reads in the blue↔red axis (the CVD-safe hue pair).
|
|
357
|
+
*
|
|
358
|
+
* Tuned so every stop is dark enough for the fill yet light enough that BLACK
|
|
359
|
+
* text clears 4.5:1 on all five — no per-cell white/black flipping. The value
|
|
360
|
+
* valley is symmetric: the two mid-arms (maintenance / approaching) sit at equal
|
|
361
|
+
* lightness (both ~0.53 relative luminance), the ends darker.
|
|
362
|
+
*/
|
|
363
|
+
export const divergingScale = [
|
|
364
|
+
primitiveRamps.blue[500], // under
|
|
365
|
+
primitiveRamps.cyan[300], // maintenance
|
|
366
|
+
primitiveRamps.green[200], // optimal (light center)
|
|
367
|
+
primitiveRamps.amber[300], // approaching
|
|
368
|
+
primitiveRamps.red[600], // over
|
|
369
|
+
] as const
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Sequential "effort" scale (low → high intensity: green → yellow → orange → red)
|
|
373
|
+
* as references into {@link primitiveRamps}. A 6-stop ordinal palette; the
|
|
374
|
+
* velocity/RPE strip sub-samples it. Every adjacent pair clears the CVD floor
|
|
375
|
+
* (min deut/protan ΔE ≥ 4.5), and within that the hue walk is kept smooth —
|
|
376
|
+
* the amber-200 → amber-300 → orange-400 run steps the yellow→orange transition
|
|
377
|
+
* gradually rather than lurching. Order encodes magnitude, so it tolerates the
|
|
378
|
+
* green↔red CVD pair. Stops 0–3 take black text; the dark reds take white.
|
|
379
|
+
*/
|
|
380
|
+
export const sequentialEffort = [
|
|
381
|
+
primitiveRamps.green[300],
|
|
382
|
+
primitiveRamps.amber[200],
|
|
383
|
+
primitiveRamps.amber[300],
|
|
384
|
+
primitiveRamps.orange[400],
|
|
385
|
+
primitiveRamps.red[600],
|
|
386
|
+
primitiveRamps.red[700],
|
|
387
|
+
] as const
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Best-contrast text color (black or white) for a solid fill — for labels sitting
|
|
391
|
+
* on categorical/diverging/effort swatches. Uses the WCAG relative-luminance ratio.
|
|
392
|
+
*/
|
|
393
|
+
export function bestTextColor(hex: string): '#000000' | '#FFFFFF' {
|
|
394
|
+
const [r, g, b] = [0, 2, 4].map((i) => {
|
|
395
|
+
const c = parseInt(hex.replace('#', '').slice(i, i + 2), 16) / 255
|
|
396
|
+
return c >= 0.04045 ? ((c + 0.055) / 1.055) ** 2.4 : c / 12.92
|
|
397
|
+
})
|
|
398
|
+
const l = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
399
|
+
return (l + 0.05) / 0.05 >= 1.05 / (l + 0.05) ? '#000000' : '#FFFFFF'
|
|
400
|
+
}
|
|
401
|
+
|
|
155
402
|
/**
|
|
156
403
|
* Discrete rainbow palette for data visualization
|
|
157
404
|
* Based on Paul Tol's color schemes: https://personal.sron.nl/~pault/#fig:scheme_rainbow_discrete
|
|
@@ -17,49 +17,54 @@
|
|
|
17
17
|
* - interactive-* : Hover/focus/active/disabled states
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { primitiveColors as p, discreteRainbow } from './primitives'
|
|
20
|
+
import { primitiveColors as p, primitiveRamps as ramp, discreteRainbow } from './primitives'
|
|
21
21
|
|
|
22
22
|
// Light mode semantic colors (default)
|
|
23
23
|
export const semanticColorsLight = {
|
|
24
24
|
// Brand colors (brand-*)
|
|
25
|
-
'brand-primary':
|
|
26
|
-
'brand-primary-light':
|
|
27
|
-
'brand-primary-dark':
|
|
25
|
+
'brand-primary': ramp.orange[400],
|
|
26
|
+
'brand-primary-light': ramp.orange[300],
|
|
27
|
+
'brand-primary-dark': ramp.orange[500],
|
|
28
28
|
'brand-primary-subtle': 'rgba(255, 121, 0, 0.08)',
|
|
29
|
-
'brand-primary-hover':
|
|
30
|
-
'brand-primary-active':
|
|
29
|
+
'brand-primary-hover': ramp.orange[500],
|
|
30
|
+
'brand-primary-active': ramp.orange[600],
|
|
31
31
|
|
|
32
|
-
'brand-secondary':
|
|
33
|
-
'brand-secondary-light':
|
|
34
|
-
'brand-secondary-dark':
|
|
35
|
-
'brand-secondary-subtle': 'rgba(
|
|
36
|
-
'brand-secondary-hover':
|
|
37
|
-
'brand-secondary-active':
|
|
32
|
+
'brand-secondary': ramp.cyan[600],
|
|
33
|
+
'brand-secondary-light': ramp.cyan[500],
|
|
34
|
+
'brand-secondary-dark': ramp.cyan[700],
|
|
35
|
+
'brand-secondary-subtle': 'rgba(48, 123, 155, 0.08)',
|
|
36
|
+
'brand-secondary-hover': ramp.cyan[700],
|
|
37
|
+
'brand-secondary-active': ramp.cyan[800],
|
|
38
38
|
|
|
39
39
|
// Text on brand backgrounds (on-*)
|
|
40
40
|
'on-brand-primary': p.white,
|
|
41
41
|
'on-brand-secondary': p.white,
|
|
42
42
|
|
|
43
43
|
// Status colors (status-*)
|
|
44
|
-
'status-success':
|
|
45
|
-
'status-success-light':
|
|
46
|
-
'status-success-dark':
|
|
47
|
-
'status-success-subtle':
|
|
48
|
-
|
|
49
|
-
'status-error':
|
|
50
|
-
'status-error-light':
|
|
51
|
-
'status-error-dark':
|
|
52
|
-
'status-error-subtle':
|
|
53
|
-
|
|
54
|
-
'status-
|
|
55
|
-
'status-
|
|
56
|
-
'status-
|
|
57
|
-
'status-
|
|
58
|
-
|
|
59
|
-
'status-
|
|
60
|
-
'status-
|
|
61
|
-
'status-
|
|
62
|
-
'status-
|
|
44
|
+
'status-success': ramp.green[300],
|
|
45
|
+
'status-success-light': ramp.green[200],
|
|
46
|
+
'status-success-dark': ramp.green[600],
|
|
47
|
+
'status-success-subtle': ramp.green[50],
|
|
48
|
+
|
|
49
|
+
'status-error': ramp.red[600],
|
|
50
|
+
'status-error-light': ramp.red[500],
|
|
51
|
+
'status-error-dark': ramp.red[700],
|
|
52
|
+
'status-error-subtle': ramp.red[50],
|
|
53
|
+
|
|
54
|
+
'status-error-vivid': p.redVivid[500], // vivid alert red
|
|
55
|
+
'status-error-vivid-light': p.redVivid[400],
|
|
56
|
+
'status-error-vivid-dark': p.redVivid[700],
|
|
57
|
+
'status-error-vivid-subtle': 'rgba(255, 71, 87, 0.12)',
|
|
58
|
+
|
|
59
|
+
'status-warning': ramp.amber[300],
|
|
60
|
+
'status-warning-light': ramp.amber[200],
|
|
61
|
+
'status-warning-dark': ramp.amber[500],
|
|
62
|
+
'status-warning-subtle': ramp.amber[50],
|
|
63
|
+
|
|
64
|
+
'status-info': ramp.blue[500],
|
|
65
|
+
'status-info-light': ramp.blue[300],
|
|
66
|
+
'status-info-dark': ramp.blue[600],
|
|
67
|
+
'status-info-subtle': ramp.blue[50],
|
|
63
68
|
|
|
64
69
|
// Text on status backgrounds (on-status-*)
|
|
65
70
|
'on-status-success': p.white,
|
|
@@ -85,16 +90,16 @@ export const semanticColorsLight = {
|
|
|
85
90
|
|
|
86
91
|
// Data visualization colors (data-*)
|
|
87
92
|
// First 10 colors from discrete rainbow optimized for charts
|
|
88
|
-
'data-1': discreteRainbow[9], //
|
|
89
|
-
'data-2': discreteRainbow[14], //
|
|
90
|
-
'data-3': discreteRainbow[17], //
|
|
91
|
-
'data-4': discreteRainbow[25], //
|
|
92
|
-
'data-5': discreteRainbow[8], //
|
|
93
|
-
'data-6': discreteRainbow[20], //
|
|
94
|
-
'data-7': discreteRainbow[13], //
|
|
95
|
-
'data-8': discreteRainbow[15], //
|
|
96
|
-
'data-9': discreteRainbow[3], //
|
|
97
|
-
'data-10': discreteRainbow[22], //
|
|
93
|
+
'data-1': discreteRainbow[9], // Blue
|
|
94
|
+
'data-2': discreteRainbow[14], // Green
|
|
95
|
+
'data-3': discreteRainbow[17], // Yellow
|
|
96
|
+
'data-4': discreteRainbow[25], // Red
|
|
97
|
+
'data-5': discreteRainbow[8], // Purple
|
|
98
|
+
'data-6': discreteRainbow[20], // Orange
|
|
99
|
+
'data-7': discreteRainbow[13], // Light Blue
|
|
100
|
+
'data-8': discreteRainbow[15], // Light Green
|
|
101
|
+
'data-9': discreteRainbow[3], // Lavender
|
|
102
|
+
'data-10': discreteRainbow[22], // Dark Orange
|
|
98
103
|
|
|
99
104
|
// Text colors (text-*)
|
|
100
105
|
'text-primary': '#121828',
|
|
@@ -107,8 +112,8 @@ export const semanticColorsLight = {
|
|
|
107
112
|
|
|
108
113
|
// Surface colors (surface-*) - for elevated containers
|
|
109
114
|
'surface-base': p.white,
|
|
110
|
-
'surface-elevated': p.neutral[50], //
|
|
111
|
-
'surface-raised': p.neutral[100], //
|
|
115
|
+
'surface-elevated': p.neutral[50], // slightly off-white for elevated cards
|
|
116
|
+
'surface-raised': p.neutral[100], // light gray for raised cards
|
|
112
117
|
'surface-overlay': p.white,
|
|
113
118
|
'surface-input': p.neutral[50], // Input field background (filled variant)
|
|
114
119
|
|
|
@@ -121,6 +126,7 @@ export const semanticColorsLight = {
|
|
|
121
126
|
'border-default': '#E8E9EB',
|
|
122
127
|
'border-subtle': p.neutral[100],
|
|
123
128
|
'border-strong': p.neutral[300],
|
|
129
|
+
'border-prominent': p.neutral[400], // high-visibility divider
|
|
124
130
|
'border-focus': p.blue[600],
|
|
125
131
|
'border-input': p.neutral[300], // Input field border
|
|
126
132
|
'border-input-hover': p.neutral[400], // Input field border on hover
|
|
@@ -146,43 +152,48 @@ export const semanticColorsLight = {
|
|
|
146
152
|
// Dark mode semantic colors
|
|
147
153
|
export const semanticColorsDark = {
|
|
148
154
|
// Brand colors stay the same in dark mode
|
|
149
|
-
'brand-primary':
|
|
150
|
-
'brand-primary-light':
|
|
151
|
-
'brand-primary-dark':
|
|
155
|
+
'brand-primary': ramp.orange[400],
|
|
156
|
+
'brand-primary-light': ramp.orange[300],
|
|
157
|
+
'brand-primary-dark': ramp.orange[500],
|
|
152
158
|
'brand-primary-subtle': 'rgba(255, 121, 0, 0.12)',
|
|
153
|
-
'brand-primary-hover':
|
|
154
|
-
'brand-primary-active':
|
|
159
|
+
'brand-primary-hover': ramp.orange[300],
|
|
160
|
+
'brand-primary-active': ramp.orange[200],
|
|
155
161
|
|
|
156
|
-
'brand-secondary':
|
|
157
|
-
'brand-secondary-light':
|
|
158
|
-
'brand-secondary-dark':
|
|
159
|
-
'brand-secondary-subtle': 'rgba(
|
|
160
|
-
'brand-secondary-hover':
|
|
161
|
-
'brand-secondary-active':
|
|
162
|
+
'brand-secondary': ramp.cyan[600],
|
|
163
|
+
'brand-secondary-light': ramp.cyan[500],
|
|
164
|
+
'brand-secondary-dark': ramp.cyan[700],
|
|
165
|
+
'brand-secondary-subtle': 'rgba(48, 123, 155, 0.12)',
|
|
166
|
+
'brand-secondary-hover': ramp.cyan[500],
|
|
167
|
+
'brand-secondary-active': ramp.cyan[400],
|
|
162
168
|
|
|
163
169
|
// Text on brand backgrounds
|
|
164
170
|
'on-brand-primary': p.white,
|
|
165
171
|
'on-brand-secondary': p.white,
|
|
166
172
|
|
|
167
173
|
// Status colors
|
|
168
|
-
'status-success':
|
|
169
|
-
'status-success-light':
|
|
170
|
-
'status-success-dark':
|
|
171
|
-
'status-success-subtle': 'rgba(
|
|
172
|
-
|
|
173
|
-
'status-error':
|
|
174
|
-
'status-error-light':
|
|
175
|
-
'status-error-dark':
|
|
174
|
+
'status-success': ramp.green[300],
|
|
175
|
+
'status-success-light': ramp.green[200],
|
|
176
|
+
'status-success-dark': ramp.green[600],
|
|
177
|
+
'status-success-subtle': 'rgba(46, 213, 115, 0.12)',
|
|
178
|
+
|
|
179
|
+
'status-error': ramp.red[600],
|
|
180
|
+
'status-error-light': ramp.red[500],
|
|
181
|
+
'status-error-dark': ramp.red[700],
|
|
176
182
|
'status-error-subtle': 'rgba(209, 67, 67, 0.12)',
|
|
177
183
|
|
|
178
|
-
'status-
|
|
179
|
-
'status-
|
|
180
|
-
'status-
|
|
181
|
-
'status-
|
|
184
|
+
'status-error-vivid': p.redVivid[500],
|
|
185
|
+
'status-error-vivid-light': p.redVivid[400],
|
|
186
|
+
'status-error-vivid-dark': p.redVivid[700],
|
|
187
|
+
'status-error-vivid-subtle': 'rgba(255, 71, 87, 0.12)',
|
|
182
188
|
|
|
183
|
-
'status-
|
|
184
|
-
'status-
|
|
185
|
-
'status-
|
|
189
|
+
'status-warning': ramp.amber[300],
|
|
190
|
+
'status-warning-light': ramp.amber[200],
|
|
191
|
+
'status-warning-dark': ramp.amber[500],
|
|
192
|
+
'status-warning-subtle': 'rgba(249, 180, 21, 0.12)',
|
|
193
|
+
|
|
194
|
+
'status-info': ramp.blue[500],
|
|
195
|
+
'status-info-light': ramp.blue[300],
|
|
196
|
+
'status-info-dark': ramp.blue[600],
|
|
186
197
|
'status-info-subtle': 'rgba(33, 150, 243, 0.12)',
|
|
187
198
|
|
|
188
199
|
// Text on status backgrounds
|
|
@@ -229,21 +240,22 @@ export const semanticColorsDark = {
|
|
|
229
240
|
'text-link-hover': p.blue[400],
|
|
230
241
|
|
|
231
242
|
// Surface colors - dark backgrounds
|
|
232
|
-
'surface-base': p.charcoal[700], //
|
|
233
|
-
'surface-elevated': p.charcoal[600], //
|
|
234
|
-
'surface-raised': p.charcoal[500], //
|
|
235
|
-
'surface-overlay': p.charcoal[600], //
|
|
236
|
-
'surface-input': p.charcoal[600], //
|
|
243
|
+
'surface-base': p.charcoal[700], // main surface
|
|
244
|
+
'surface-elevated': p.charcoal[600], // elevated surface
|
|
245
|
+
'surface-raised': p.charcoal[500], // raised surface
|
|
246
|
+
'surface-overlay': p.charcoal[600], // overlay surface
|
|
247
|
+
'surface-input': p.charcoal[600], // input surface
|
|
237
248
|
|
|
238
249
|
// Background colors
|
|
239
|
-
'background-base': p.charcoal[900], //
|
|
240
|
-
'background-default': p.charcoal[700], //
|
|
241
|
-
'background-subtle': p.charcoal[500], //
|
|
250
|
+
'background-base': p.charcoal[900], // darkest charcoal
|
|
251
|
+
'background-default': p.charcoal[700], // main background
|
|
252
|
+
'background-subtle': p.charcoal[500], // subtle background
|
|
242
253
|
|
|
243
254
|
// Border colors
|
|
244
|
-
'border-default': p.charcoal[400], //
|
|
245
|
-
'border-subtle': p.charcoal[500], //
|
|
246
|
-
'border-strong': p.charcoal[300], //
|
|
255
|
+
'border-default': p.charcoal[400], // default border
|
|
256
|
+
'border-subtle': p.charcoal[500], // subtle border
|
|
257
|
+
'border-strong': p.charcoal[300], // strong border
|
|
258
|
+
'border-prominent': p.charcoal[200], // high-visibility divider
|
|
247
259
|
'border-focus': '#828DF8',
|
|
248
260
|
'border-input': p.neutral[600],
|
|
249
261
|
'border-input-hover': p.neutral[500],
|
|
@@ -259,7 +271,7 @@ export const semanticColorsDark = {
|
|
|
259
271
|
'interactive-disabled-text': 'rgba(255, 255, 255, 0.26)',
|
|
260
272
|
|
|
261
273
|
// Divider
|
|
262
|
-
'divider': p.charcoal[400], //
|
|
274
|
+
'divider': p.charcoal[400], // divider color
|
|
263
275
|
|
|
264
276
|
// Avatar default
|
|
265
277
|
'avatar-background': p.neutral[600],
|
|
@@ -2,31 +2,32 @@
|
|
|
2
2
|
* Workout-specific tokens not yet in the main Tailwind config.
|
|
3
3
|
* Use these inline instead of Tailwind classes.
|
|
4
4
|
*/
|
|
5
|
+
import { divergingScale, sequentialEffort, primitiveRamps as ramp } from './tokens/primitives'
|
|
6
|
+
|
|
5
7
|
export const WORKOUT_TOKENS = {
|
|
6
8
|
// Canonical 4-band performance scale — the single source for BOTH the
|
|
7
9
|
// VelocityStrip zone bars and the SetRow RPE color (TD-03.43). The direction
|
|
8
10
|
// is intentionally inverted between the two consumers: for velocity, green =
|
|
9
|
-
// fastest/best; for RPE, green = easiest.
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// scale below — they are consumed inline as a JS import rather than CSS vars.
|
|
13
|
-
// A JS import also resolves on native RN, unlike var(--…) strings.
|
|
11
|
+
// fastest/best; for RPE, green = easiest. This is the [0,1,2,4] subsample of
|
|
12
|
+
// the canonical `sequentialEffort` primitive. The `orange` key is the band
|
|
13
|
+
// label. Consumed inline (RN).
|
|
14
14
|
scale: {
|
|
15
|
-
green:
|
|
16
|
-
yellow:
|
|
17
|
-
orange:
|
|
18
|
-
red:
|
|
15
|
+
green: sequentialEffort[0],
|
|
16
|
+
yellow: sequentialEffort[1],
|
|
17
|
+
orange: sequentialEffort[2], // gold band
|
|
18
|
+
red: sequentialEffort[4],
|
|
19
19
|
},
|
|
20
20
|
|
|
21
|
-
// BodyMap volume heatmap
|
|
22
|
-
//
|
|
21
|
+
// BodyMap volume heatmap — the canonical `divergingScale` (under → optimal →
|
|
22
|
+
// over): a true diverging shape with a light green center, cool-blue under-
|
|
23
|
+
// trained end and warm-red over-reaching end (colorblind-robust in lightness).
|
|
23
24
|
heatmap: {
|
|
24
|
-
none: '#E0E0E0', //
|
|
25
|
-
under:
|
|
26
|
-
maintenance:
|
|
27
|
-
productive:
|
|
28
|
-
approaching:
|
|
29
|
-
over:
|
|
25
|
+
none: '#E0E0E0', // no training data
|
|
26
|
+
under: divergingScale[0], // below MEV
|
|
27
|
+
maintenance: divergingScale[1], // MEV to MAV
|
|
28
|
+
productive: divergingScale[2], // optimal center — MAV to MRV
|
|
29
|
+
approaching: divergingScale[3], // near MRV
|
|
30
|
+
over: divergingScale[4], // over MRV
|
|
30
31
|
},
|
|
31
32
|
|
|
32
33
|
// Badge border-radius (rounded-sm is 4px, we need 2px)
|
|
@@ -36,12 +37,12 @@ export const WORKOUT_TOKENS = {
|
|
|
36
37
|
// components use the CSS custom properties (var(--color-surface-*/border-*))
|
|
37
38
|
// directly. Only theme-independent data-viz values remain below.
|
|
38
39
|
|
|
39
|
-
// Intensity bar specific
|
|
40
|
+
// Intensity bar specific — over-target tiers deepen along the red ramp.
|
|
40
41
|
intensity: {
|
|
41
42
|
track: '#333333',
|
|
42
|
-
over1:
|
|
43
|
-
over2:
|
|
44
|
-
over3:
|
|
43
|
+
over1: ramp.red[600], // matches status-error
|
|
44
|
+
over2: ramp.red[700],
|
|
45
|
+
over3: ramp.red[800],
|
|
45
46
|
targetLine: 'rgba(33, 150, 243, 0.5)',
|
|
46
47
|
atTargetGlow:
|
|
47
48
|
'0 0 5px 1px rgba(33, 150, 243, 0.35), 0 0 10px 3px rgba(33, 150, 243, 0.15)',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { AVATAR_CATEGORICAL_COLORS } from '../theme/extracted-colors-ui'
|
|
2
|
+
|
|
3
|
+
const AVATAR_COLORS = AVATAR_CATEGORICAL_COLORS
|
|
4
4
|
|
|
5
5
|
export function avatarColor(name: string): string {
|
|
6
6
|
let hash = 0
|
package/tailwind.config.js
CHANGED
|
@@ -44,6 +44,12 @@ module.exports = {
|
|
|
44
44
|
dark: 'var(--color-status-error-dark)',
|
|
45
45
|
subtle: 'var(--color-status-error-subtle)',
|
|
46
46
|
},
|
|
47
|
+
'error-vivid': {
|
|
48
|
+
DEFAULT: 'var(--color-status-error-vivid)',
|
|
49
|
+
light: 'var(--color-status-error-vivid-light)',
|
|
50
|
+
dark: 'var(--color-status-error-vivid-dark)',
|
|
51
|
+
subtle: 'var(--color-status-error-vivid-subtle)',
|
|
52
|
+
},
|
|
47
53
|
warning: {
|
|
48
54
|
DEFAULT: 'var(--color-status-warning)',
|
|
49
55
|
light: 'var(--color-status-warning-light)',
|
|
@@ -132,6 +138,7 @@ module.exports = {
|
|
|
132
138
|
DEFAULT: 'var(--color-border-default)',
|
|
133
139
|
subtle: 'var(--color-border-subtle)',
|
|
134
140
|
strong: 'var(--color-border-strong)',
|
|
141
|
+
prominent: 'var(--color-border-prominent)',
|
|
135
142
|
focus: 'var(--color-border-focus)',
|
|
136
143
|
input: {
|
|
137
144
|
DEFAULT: 'var(--color-border-input)',
|
|
@@ -184,10 +191,10 @@ module.exports = {
|
|
|
184
191
|
xl: '0px 20px 25px rgba(100, 116, 139, 0.12)',
|
|
185
192
|
// Glow shadows (colored radial glow for emphasis)
|
|
186
193
|
'glow-primary': '0 0 20px 2px rgba(var(--color-brand-primary-rgb, 255, 121, 0), 0.4)',
|
|
187
|
-
'glow-secondary': '0 0 20px 2px rgba(var(--color-brand-secondary-rgb,
|
|
188
|
-
'glow-success': '0 0 20px 2px rgba(var(--color-status-success-rgb,
|
|
194
|
+
'glow-secondary': '0 0 20px 2px rgba(var(--color-brand-secondary-rgb, 48, 123, 155), 0.4)',
|
|
195
|
+
'glow-success': '0 0 20px 2px rgba(var(--color-status-success-rgb, 46, 213, 115), 0.35)',
|
|
189
196
|
'glow-error': '0 0 20px 2px rgba(var(--color-status-error-rgb, 209, 67, 67), 0.4)',
|
|
190
|
-
'glow-warning': '0 0 20px 2px rgba(var(--color-status-warning-rgb,
|
|
197
|
+
'glow-warning': '0 0 20px 2px rgba(var(--color-status-warning-rgb, 249, 180, 21), 0.35)',
|
|
191
198
|
'glow-info': '0 0 20px 2px rgba(var(--color-status-info-rgb, 33, 150, 243), 0.35)',
|
|
192
199
|
'glow-sm': '0 0 12px 0px',
|
|
193
200
|
'glow-md': '0 0 20px 2px',
|