@empoweredvote/ev-ui 0.9.8 → 0.9.9

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/src/tokens.js CHANGED
@@ -1,415 +1,415 @@
1
- /**
2
- * Empowered Vote Design Tokens
3
- * Single source of truth for the EV design system.
4
- * Synced to Penpot via /sync-design-tokens
5
- *
6
- * WCAG AA compliance notes:
7
- * Brand colors have limited contrast on white backgrounds.
8
- * Use the `accessibleText` map below for body-text alternatives
9
- * that pass AA (4.5:1) while staying on-brand.
10
- *
11
- * Brand color | On white | AA-safe text alternative
12
- * ───────────────── | ──────── | ────────────────────────
13
- * Coral #FF5740 | 3.14:1 | coral-700 #E61B00 (4.64:1)
14
- * Muted Blue #00657C | 6.66:1 | (already AA — use directly)
15
- * Light Blue #59B0C4 | 2.49:1 | skyblue-700 #327E8F (4.65:1)
16
- * Yellow #FED12E | 1.46:1 | yellow-900 #7B640E (5.72:1)
17
- *
18
- * Coral is AA for large text only (>=18px bold or >=24px).
19
- * Yellow and Light Blue should never be used as text on white.
20
- */
21
-
22
- // ──────────────────────────────────────────────
23
- // Color Scales (050–950 per hue)
24
- // ──────────────────────────────────────────────
25
-
26
- export const colorScales = {
27
- coral: {
28
- '050': '#FAF6F5', '100': '#F6E6E4', '200': '#F2C6C0', '300': '#F2988C',
29
- '400': '#F66855', '500': '#FF5740', '600': '#FF2B0F', '700': '#E61B00',
30
- '800': '#B31D09', '900': '#8E1F10', '950': '#6C1E13',
31
- },
32
- teal: {
33
- // Smoothed scale — original 400→500 had a ~45 lightness jump.
34
- // 400 now bridges the light wash into the deep brand muted-blue.
35
- '050': '#F5F9FA', '100': '#E4F3F6', '200': '#C0E8F2', '300': '#7DD4E8',
36
- '400': '#3AABB8', '500': '#00657C', '600': '#005366', '700': '#003E4D',
37
- '800': '#03303A', '900': '#05262E', '950': '#051A1E',
38
- },
39
- skyblue: {
40
- '050': '#F6F8F8', '100': '#E9F0F1', '200': '#CDE0E5', '300': '#A7CFD8',
41
- '400': '#7FBECC', '500': '#59B0C4', '600': '#3A9BB0', '700': '#327E8F',
42
- '800': '#2B616E', '900': '#264C55', '950': '#1E383D',
43
- },
44
- yellow: {
45
- '050': '#FAF9F5', '100': '#F6F2E4', '200': '#F1E7C0', '300': '#F2DC8D',
46
- '400': '#F5D356', '500': '#FED12E', '600': '#FAC400', '700': '#D0A301',
47
- '800': '#9F7F09', '900': '#7B640E', '950': '#5B4B10',
48
- },
49
- gray: {
50
- '050': '#F7F7F8', '100': '#EBEDEF', '200': '#D3D7DE', '300': '#B3BBCC',
51
- '400': '#8F9EBC', '500': '#6B7280', '600': '#535964', '700': '#41454E',
52
- '800': '#2F3237', '900': '#212326', '950': '#131416',
53
- },
54
- };
55
-
56
- // ──────────────────────────────────────────────
57
- // Tier Colors (teal-scale shade variation)
58
- // ──────────────────────────────────────────────
59
-
60
- export const tierColors = {
61
- federal: {
62
- bg: '#FFFFFF', // white — lightest tier
63
- accent: colorScales.teal['700'], // #003E4D
64
- text: colorScales.teal['700'], // #003E4D — 7.5:1 on white, AA
65
- },
66
- state: {
67
- bg: '#F7FBFC', // gentle teal wash — midpoint between white and 050
68
- accent: colorScales.teal['500'], // #00657C
69
- text: colorScales.teal['500'], // #00657C — 6.66:1 on white, AA
70
- },
71
- local: {
72
- bg: '#EDF6F8', // #EDF6F8 — between 050 and 100, darkest tier
73
- accent: colorScales.teal['200'], // #C0E8F2 — decorative only
74
- text: colorScales.teal['600'], // #005366 — 8.1:1 on white, AA
75
- },
76
- };
77
-
78
- // ──────────────────────────────────────────────
79
- // Brand Colors (flat references for convenience)
80
- // Canonical values — DO NOT change without updating all apps.
81
- // ──────────────────────────────────────────────
82
-
83
- export const colors = {
84
- // Primary brand (locked)
85
- evCoral: '#FF5740', // 3.14:1 on white — large text / UI only
86
- evMutedBlue: '#00657C', // 6.66:1 on white — AA body text safe
87
- evLightBlue: '#59B0C4', // 2.49:1 on white — decorative / dark bg only
88
- evYellow: '#FED12E', // 1.46:1 on white — accent / dark bg only
89
- evYellowLight:'#FEF3C7',
90
- evYellowDark: '#D0A301',
91
- black: '#1C1C1C',
92
- white: '#FFFFFF',
93
-
94
- // Legacy aliases (deprecated — use evMutedBlue / evLightBlue instead)
95
- evTeal: '#00657C',
96
- evTealDark: '#003E4D',
97
- evTealLight: '#59B0C4',
98
-
99
- // Backgrounds
100
- bgLight: '#F0F8FA',
101
- bgWhite: '#FFFFFF',
102
-
103
- // Text
104
- textPrimary: '#00657C', // Muted Blue — 6.66:1 on white ✓ AA
105
- textSecondary: '#4A5568', // ~6.0:1 on white ✓ AA
106
- textMuted: '#718096', // ~4.6:1 on white ✓ AA
107
- textWhite: '#FFFFFF',
108
-
109
- // Borders
110
- borderLight: '#E2EBEF',
111
- borderMedium: '#C9CFD1',
112
-
113
- // State
114
- error: '#EF4444',
115
- errorLight: '#FEF2F2',
116
- success: '#22C55E',
117
- successLight: '#F0FDF4',
118
- warning: '#F59E0B',
119
- warningLight: '#FFFBEB',
120
- info: '#3A9BB0',
121
- infoLight: '#F5F9FA',
122
- };
123
-
124
- // ──────────────────────────────────────────────
125
- // AA-Safe Text Colors (per brand hue)
126
- // Use these when a brand color must appear as readable text.
127
- // Each passes WCAG AA (4.5:1+) on white and bgLight.
128
- // ──────────────────────────────────────────────
129
-
130
- export const accessibleText = {
131
- coral: '#E61B00', // coral-700, 4.64:1 on white
132
- teal: '#00657C', // teal-500 (brand muted blue), 6.66:1
133
- skyblue: '#327E8F', // skyblue-700, 4.65:1 on white
134
- yellow: '#7B640E', // yellow-900, 5.72:1 on white
135
- };
136
-
137
- // ──────────────────────────────────────────────
138
- // Pillar Themes
139
- // ──────────────────────────────────────────────
140
-
141
- export const pillars = {
142
- inform: {
143
- name: 'Inform', accent: 'yellow',
144
- color: '#FED12E', dark: '#D0A301', light: '#FAF9F5',
145
- textColor: '#7B640E', // AA-safe text — yellow-900
146
- darkMode: { color: '#FED12E', dark: '#FEE88A', textOnAccent: '#1C1C1C' },
147
- },
148
- empower: {
149
- name: 'Empower', accent: 'coral',
150
- color: '#FF5740', dark: '#E61B00', light: '#FAF6F5',
151
- textColor: '#E61B00', // AA-safe text — coral-700
152
- darkMode: { color: '#FF5740', dark: '#F66855', textOnAccent: '#FFFFFF' },
153
- },
154
- connect: {
155
- name: 'Connect', accent: 'teal',
156
- color: '#00657C', dark: '#003E4D', light: '#F5F9FA',
157
- textColor: '#00657C', // Already AA — muted blue
158
- darkMode: { color: '#3A9BB0', dark: '#59B0C4', textOnAccent: '#FFFFFF' },
159
- },
160
- };
161
-
162
- // ──────────────────────────────────────────────
163
- // Semantic Tokens (Light / Dark)
164
- // All text-on-background pairings verified AA (4.5:1+).
165
- // ──────────────────────────────────────────────
166
-
167
- export const semanticTokens = {
168
- light: {
169
- layerBase: { background: '#F7F7F8', text: '#535964', icon: '#535964', border: '#D3D7DE' }, // 6.58:1
170
- layerOne: { background: '#EBEDEF', text: '#41454E', icon: '#41454E', border: '#D3D7DE' }, // 7.23:1
171
- layerTwo: { background: '#D3D7DE', text: '#2F3237', icon: '#2F3237', border: '#B3BBCC' }, // 7.60:1
172
- heading: { text: '#2F3237' }, // 12.02:1 on gray-050
173
- divider: '#D3D7DE',
174
- link: { default: '#00657C', hovered: '#003E4D', focused: '#003E4D', pressed: '#003E4D' },
175
- input: { background: '#F7F7F8', text: '#41454E', border: '#41454E', icon: '#6B7280' }, // text bumped from gray-500 to gray-700 for AA
176
- buttonPrimary: {
177
- background: { default: '#005366', hovered: '#003E4D', pressed: '#03303A', focused: '#005366', disabled: '#D3D7DE' },
178
- text: { default: '#FFFFFF', disabled: '#8F9EBC' },
179
- },
180
- buttonSecondary: {
181
- background: { default: '#F7F7F8', hovered: '#EBEDEF', pressed: '#D3D7DE', focused: '#EBEDEF', disabled: '#F7F7F8' },
182
- text: { default: '#003E4D', disabled: '#8F9EBC' },
183
- border: { default: '#003E4D', disabled: '#D3D7DE' },
184
- },
185
- // Feedback states
186
- badge: { background: '#EBEDEF', text: '#41454E', border: '#D3D7DE' },
187
- tooltip: { background: '#2F3237', text: '#EBEDEF' },
188
- overlay: 'rgba(19, 20, 22, 0.5)',
189
- skeleton: { from: '#EBEDEF', to: '#D3D7DE' },
190
- },
191
- dark: {
192
- layerBase: { background: '#131416', text: '#B3BBCC', icon: '#B3BBCC', border: '#41454E' }, // 9.56:1 ✓ AAA
193
- layerOne: { background: '#2F3237', text: '#D3D7DE', icon: '#D3D7DE', border: '#41454E' }, // 8.91:1 ✓ AAA
194
- layerTwo: { background: '#41454E', text: '#EBEDEF', icon: '#EBEDEF', border: '#535964' }, // 8.18:1 ✓ AAA
195
- heading: { text: '#EBEDEF' }, // 15.70:1 on layerBase ✓ AAA
196
- divider: '#41454E',
197
- link: { default: '#59B0C4', hovered: '#7FBECC', focused: '#7FBECC', pressed: '#3A9BB0' }, // 7.40:1 on layerBase ✓ AAA
198
- input: { background: '#1E2024', text: '#D3D7DE', border: '#6B7280', icon: '#B3BBCC' }, // border bumped to gray-500 for 3.37:1 UI ✓
199
- buttonPrimary: {
200
- // Darkened from skyblue to teal for white-text AA compliance
201
- background: { default: '#005366', hovered: '#327E8F', pressed: '#003E4D', focused: '#005366', disabled: '#2F3237' },
202
- text: { default: '#FFFFFF', disabled: '#535964' }, // 8.64:1 / 4.65:1 / 11.69:1 ✓ AA+
203
- },
204
- buttonSecondary: {
205
- background: { default: '#131416', hovered: '#2F3237', pressed: '#41454E', focused: '#2F3237', disabled: '#131416' },
206
- text: { default: '#59B0C4', disabled: '#535964' }, // 7.40:1 ✓ AAA
207
- border: { default: '#59B0C4', disabled: '#41454E' },
208
- },
209
- // Feedback states
210
- badge: { background: '#41454E', text: '#EBEDEF', border: '#535964' },
211
- tooltip: { background: '#EBEDEF', text: '#2F3237' },
212
- overlay: 'rgba(19, 20, 22, 0.75)',
213
- skeleton: { from: '#2F3237', to: '#41454E' },
214
- },
215
- };
216
-
217
- // ──────────────────────────────────────────────
218
- // Data Visualization Palette
219
- // ──────────────────────────────────────────────
220
-
221
- // 10 hues derived from the EV brand palette.
222
- // Core 4 (teal, sky blue, coral, yellow) + 6 harmonious neighbors.
223
- // Each has 5 shades: [100-light, 300, 400, base-500, 700-dark]
224
- export const dataVizPalette = [
225
- { name: 'Teal', base: '#00657C', shades: ['#C0E8F2', '#55D9F6', '#3AABB8', '#00657C', '#003E4D'] },
226
- { name: 'Sky Blue', base: '#59B0C4', shades: ['#CDE0E5', '#A7CFD8', '#7FBECC', '#59B0C4', '#327E8F'] },
227
- { name: 'Ocean', base: '#2563A0', shades: ['#CDDCED', '#7BADD4', '#4D8FC2', '#2563A0', '#193F68'] },
228
- { name: 'Coral', base: '#FF5740', shades: ['#F6E6E4', '#F2988C', '#F66855', '#FF5740', '#B31D09'] },
229
- { name: 'Terracotta', base: '#C2674A', shades: ['#F0DCD4', '#D9A48E', '#CF8568', '#C2674A', '#7A3D2C'] },
230
- { name: 'Yellow', base: '#FED12E', shades: ['#F6F2E4', '#F2DC8D', '#F5D356', '#FED12E', '#9F7F09'] },
231
- { name: 'Honey', base: '#D4940B', shades: ['#F5EACE', '#E8C563', '#DCA930', '#D4940B', '#805A06'] },
232
- { name: 'Sage', base: '#5A9A6E', shades: ['#D4E8DA', '#9ACBAA', '#74B589', '#5A9A6E', '#376043'] },
233
- { name: 'Dusk', base: '#7C6B9E', shades: ['#E4DFF0', '#B5A8CF', '#9788B8', '#7C6B9E', '#4D4263'] },
234
- { name: 'Stone', base: '#6B7280', shades: ['#EBEDEF', '#B3BBCC', '#8F9EBC', '#6B7280', '#41454E'] },
235
- ];
236
-
237
- // ──────────────────────────────────────────────
238
- // Typography
239
- // ──────────────────────────────────────────────
240
-
241
- export const fonts = {
242
- primary: "'Manrope', sans-serif",
243
- fallback: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
244
- };
245
-
246
- export const fontWeights = {
247
- regular: 400,
248
- medium: 500,
249
- semibold: 600,
250
- bold: 700,
251
- extrabold: 800,
252
- };
253
-
254
- export const fontSizes = {
255
- xs: '12px',
256
- sm: '14px',
257
- base: '16px',
258
- lg: '18px',
259
- xl: '20px',
260
- '2xl': '24px',
261
- '3xl': '30px',
262
- '4xl': '36px',
263
- '5xl': '48px',
264
- };
265
-
266
- export const lineHeights = {
267
- tight: 1.25,
268
- normal: 1.5,
269
- relaxed: 1.75,
270
- };
271
-
272
- export const letterSpacing = {
273
- tight: '-0.02em',
274
- normal: '0',
275
- wide: '0.05em',
276
- };
277
-
278
- // Composite text styles — combine size, weight, line-height, and letter-spacing
279
- // into ready-to-spread objects for inline styles: style={{ ...textStyles.displayTitle }}
280
- export const textStyles = {
281
- displayTitle: { fontSize: '48px', fontWeight: 800, lineHeight: 1.1, letterSpacing: '-0.03em' },
282
- displaySub: { fontSize: '36px', fontWeight: 700, lineHeight: 1.15, letterSpacing: '-0.02em' },
283
- h1: { fontSize: '30px', fontWeight: 700, lineHeight: 1.25, letterSpacing: '-0.02em' },
284
- h2: { fontSize: '24px', fontWeight: 700, lineHeight: 1.3, letterSpacing: '-0.01em' },
285
- h3: { fontSize: '20px', fontWeight: 600, lineHeight: 1.35, letterSpacing: '0' },
286
- h4: { fontSize: '18px', fontWeight: 600, lineHeight: 1.4, letterSpacing: '0' },
287
- bodyLg: { fontSize: '18px', fontWeight: 400, lineHeight: 1.6, letterSpacing: '0' },
288
- body: { fontSize: '16px', fontWeight: 400, lineHeight: 1.5, letterSpacing: '0' },
289
- bodySm: { fontSize: '14px', fontWeight: 400, lineHeight: 1.5, letterSpacing: '0' },
290
- label: { fontSize: '14px', fontWeight: 500, lineHeight: 1.4, letterSpacing: '0.01em' },
291
- labelSm: { fontSize: '12px', fontWeight: 500, lineHeight: 1.4, letterSpacing: '0.02em' },
292
- caption: { fontSize: '12px', fontWeight: 400, lineHeight: 1.4, letterSpacing: '0.01em' },
293
- overline: { fontSize: '11px', fontWeight: 700, lineHeight: 1.4, letterSpacing: '0.08em', textTransform: 'uppercase' },
294
- };
295
-
296
- // ──────────────────────────────────────────────
297
- // Spacing (4px base unit)
298
- // ──────────────────────────────────────────────
299
-
300
- export const spacing = {
301
- 0: '0',
302
- '050': '2px',
303
- 1: '4px',
304
- '150': '6px',
305
- 2: '8px',
306
- 3: '12px',
307
- 4: '16px',
308
- 5: '20px',
309
- 6: '24px',
310
- 8: '32px',
311
- 10: '40px',
312
- 12: '48px',
313
- 16: '64px',
314
- 20: '80px',
315
- 24: '96px',
316
- };
317
-
318
- // ──────────────────────────────────────────────
319
- // Border Radius
320
- // ──────────────────────────────────────────────
321
-
322
- export const borderRadius = {
323
- none: '0',
324
- sm: '4px',
325
- md: '8px',
326
- lg: '10px',
327
- xl: '12px',
328
- '2xl': '16px',
329
- full: '9999px',
330
- };
331
-
332
- // ──────────────────────────────────────────────
333
- // Shadows & Elevation
334
- // ──────────────────────────────────────────────
335
-
336
- export const shadows = {
337
- sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
338
- md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
339
- lg: '0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 10px -4px rgba(0, 0, 0, 0.05)',
340
- xl: '0 20px 40px -8px rgba(0, 0, 0, 0.12), 0 8px 16px -6px rgba(0, 0, 0, 0.06)',
341
- inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
342
- glowCoral: '0 0 0 3px rgba(255, 87, 64, 0.25)',
343
- glowTeal: '0 0 0 3px rgba(0, 101, 124, 0.2)',
344
- cardHover: '0 12px 32px -4px rgba(0, 0, 0, 0.12), 0 4px 8px -2px rgba(0, 0, 0, 0.04)',
345
- none: 'none',
346
- };
347
-
348
- // ──────────────────────────────────────────────
349
- // Motion
350
- // ──────────────────────────────────────────────
351
-
352
- export const duration = {
353
- instant: '50ms',
354
- fast: '120ms', // Micro-interactions: toggles, checkboxes
355
- normal: '200ms', // Buttons, inputs, small transitions
356
- slow: '350ms', // Panels, cards, modals
357
- slower: '500ms', // Page transitions, complex orchestration
358
- };
359
-
360
- export const easing = {
361
- default: 'cubic-bezier(0.2, 0, 0, 1)', // General-purpose (Material 3 standard)
362
- enter: 'cubic-bezier(0, 0, 0.2, 1)', // Elements appearing (decelerate)
363
- exit: 'cubic-bezier(0.4, 0, 1, 1)', // Elements leaving (accelerate)
364
- spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)', // Playful overshoot — buttons, badges
365
- linear: 'linear',
366
- };
367
-
368
- // ──────────────────────────────────────────────
369
- // Focus Ring
370
- // ──────────────────────────────────────────────
371
-
372
- export const focus = {
373
- ring: '0 0 0 2px #FFFFFF, 0 0 0 4px #00657C', // White gap + brand muted blue (6.66:1 on white)
374
- ringDark: '0 0 0 2px #131416, 0 0 0 4px #59B0C4', // Dark gap + light blue (6.84:1 on dark)
375
- ringCoral: '0 0 0 2px #FFFFFF, 0 0 0 4px #FF5740', // Coral variant for pillar contexts
376
- offset: '2px',
377
- };
378
-
379
- // ──────────────────────────────────────────────
380
- // Z-Index Scale
381
- // ──────────────────────────────────────────────
382
-
383
- export const zIndex = {
384
- hide: -1,
385
- base: 0,
386
- dropdown: 10,
387
- sticky: 20,
388
- header: 30,
389
- overlay: 40,
390
- modal: 50,
391
- toast: 60,
392
- tooltip: 70,
393
- };
394
-
395
- // ──────────────────────────────────────────────
396
- // Opacity
397
- // ──────────────────────────────────────────────
398
-
399
- export const opacity = {
400
- disabled: 0.4,
401
- muted: 0.6,
402
- subtle: 0.8,
403
- full: 1,
404
- };
405
-
406
- // ──────────────────────────────────────────────
407
- // Breakpoints
408
- // ──────────────────────────────────────────────
409
-
410
- export const breakpoints = {
411
- sm: '640px',
412
- md: '768px',
413
- lg: '1024px',
414
- xl: '1280px',
415
- };
1
+ /**
2
+ * Empowered Vote Design Tokens
3
+ * Single source of truth for the EV design system.
4
+ * Synced to Penpot via /sync-design-tokens
5
+ *
6
+ * WCAG AA compliance notes:
7
+ * Brand colors have limited contrast on white backgrounds.
8
+ * Use the `accessibleText` map below for body-text alternatives
9
+ * that pass AA (4.5:1) while staying on-brand.
10
+ *
11
+ * Brand color | On white | AA-safe text alternative
12
+ * ───────────────── | ──────── | ────────────────────────
13
+ * Coral #FF5740 | 3.14:1 | coral-700 #E61B00 (4.64:1)
14
+ * Muted Blue #00657C | 6.66:1 | (already AA — use directly)
15
+ * Light Blue #59B0C4 | 2.49:1 | skyblue-700 #327E8F (4.65:1)
16
+ * Yellow #FED12E | 1.46:1 | yellow-900 #7B640E (5.72:1)
17
+ *
18
+ * Coral is AA for large text only (>=18px bold or >=24px).
19
+ * Yellow and Light Blue should never be used as text on white.
20
+ */
21
+
22
+ // ──────────────────────────────────────────────
23
+ // Color Scales (050–950 per hue)
24
+ // ──────────────────────────────────────────────
25
+
26
+ export const colorScales = {
27
+ coral: {
28
+ '050': '#FAF6F5', '100': '#F6E6E4', '200': '#F2C6C0', '300': '#F2988C',
29
+ '400': '#F66855', '500': '#FF5740', '600': '#FF2B0F', '700': '#E61B00',
30
+ '800': '#B31D09', '900': '#8E1F10', '950': '#6C1E13',
31
+ },
32
+ teal: {
33
+ // Smoothed scale — original 400→500 had a ~45 lightness jump.
34
+ // 400 now bridges the light wash into the deep brand muted-blue.
35
+ '050': '#F5F9FA', '100': '#E4F3F6', '200': '#C0E8F2', '300': '#7DD4E8',
36
+ '400': '#3AABB8', '500': '#00657C', '600': '#005366', '700': '#003E4D',
37
+ '800': '#03303A', '900': '#05262E', '950': '#051A1E',
38
+ },
39
+ skyblue: {
40
+ '050': '#F6F8F8', '100': '#E9F0F1', '200': '#CDE0E5', '300': '#A7CFD8',
41
+ '400': '#7FBECC', '500': '#59B0C4', '600': '#3A9BB0', '700': '#327E8F',
42
+ '800': '#2B616E', '900': '#264C55', '950': '#1E383D',
43
+ },
44
+ yellow: {
45
+ '050': '#FAF9F5', '100': '#F6F2E4', '200': '#F1E7C0', '300': '#F2DC8D',
46
+ '400': '#F5D356', '500': '#FED12E', '600': '#FAC400', '700': '#D0A301',
47
+ '800': '#9F7F09', '900': '#7B640E', '950': '#5B4B10',
48
+ },
49
+ gray: {
50
+ '050': '#F7F7F8', '100': '#EBEDEF', '200': '#D3D7DE', '300': '#B3BBCC',
51
+ '400': '#8F9EBC', '500': '#6B7280', '600': '#535964', '700': '#41454E',
52
+ '800': '#2F3237', '900': '#212326', '950': '#131416',
53
+ },
54
+ };
55
+
56
+ // ──────────────────────────────────────────────
57
+ // Tier Colors (teal-scale shade variation)
58
+ // ──────────────────────────────────────────────
59
+
60
+ export const tierColors = {
61
+ federal: {
62
+ bg: '#FFFFFF', // white — lightest tier
63
+ accent: colorScales.teal['700'], // #003E4D
64
+ text: colorScales.teal['700'], // #003E4D — 7.5:1 on white, AA
65
+ },
66
+ state: {
67
+ bg: '#F7FBFC', // gentle teal wash — midpoint between white and 050
68
+ accent: colorScales.teal['500'], // #00657C
69
+ text: colorScales.teal['500'], // #00657C — 6.66:1 on white, AA
70
+ },
71
+ local: {
72
+ bg: '#EDF6F8', // #EDF6F8 — between 050 and 100, darkest tier
73
+ accent: colorScales.teal['200'], // #C0E8F2 — decorative only
74
+ text: colorScales.teal['600'], // #005366 — 8.1:1 on white, AA
75
+ },
76
+ };
77
+
78
+ // ──────────────────────────────────────────────
79
+ // Brand Colors (flat references for convenience)
80
+ // Canonical values — DO NOT change without updating all apps.
81
+ // ──────────────────────────────────────────────
82
+
83
+ export const colors = {
84
+ // Primary brand (locked)
85
+ evCoral: '#FF5740', // 3.14:1 on white — large text / UI only
86
+ evMutedBlue: '#00657C', // 6.66:1 on white — AA body text safe
87
+ evLightBlue: '#59B0C4', // 2.49:1 on white — decorative / dark bg only
88
+ evYellow: '#FED12E', // 1.46:1 on white — accent / dark bg only
89
+ evYellowLight:'#FEF3C7',
90
+ evYellowDark: '#D0A301',
91
+ black: '#1C1C1C',
92
+ white: '#FFFFFF',
93
+
94
+ // Legacy aliases (deprecated — use evMutedBlue / evLightBlue instead)
95
+ evTeal: '#00657C',
96
+ evTealDark: '#003E4D',
97
+ evTealLight: '#59B0C4',
98
+
99
+ // Backgrounds
100
+ bgLight: '#F0F8FA',
101
+ bgWhite: '#FFFFFF',
102
+
103
+ // Text
104
+ textPrimary: '#00657C', // Muted Blue — 6.66:1 on white ✓ AA
105
+ textSecondary: '#4A5568', // ~6.0:1 on white ✓ AA
106
+ textMuted: '#718096', // ~4.6:1 on white ✓ AA
107
+ textWhite: '#FFFFFF',
108
+
109
+ // Borders
110
+ borderLight: '#E2EBEF',
111
+ borderMedium: '#C9CFD1',
112
+
113
+ // State
114
+ error: '#EF4444',
115
+ errorLight: '#FEF2F2',
116
+ success: '#22C55E',
117
+ successLight: '#F0FDF4',
118
+ warning: '#F59E0B',
119
+ warningLight: '#FFFBEB',
120
+ info: '#3A9BB0',
121
+ infoLight: '#F5F9FA',
122
+ };
123
+
124
+ // ──────────────────────────────────────────────
125
+ // AA-Safe Text Colors (per brand hue)
126
+ // Use these when a brand color must appear as readable text.
127
+ // Each passes WCAG AA (4.5:1+) on white and bgLight.
128
+ // ──────────────────────────────────────────────
129
+
130
+ export const accessibleText = {
131
+ coral: '#E61B00', // coral-700, 4.64:1 on white
132
+ teal: '#00657C', // teal-500 (brand muted blue), 6.66:1
133
+ skyblue: '#327E8F', // skyblue-700, 4.65:1 on white
134
+ yellow: '#7B640E', // yellow-900, 5.72:1 on white
135
+ };
136
+
137
+ // ──────────────────────────────────────────────
138
+ // Pillar Themes
139
+ // ──────────────────────────────────────────────
140
+
141
+ export const pillars = {
142
+ inform: {
143
+ name: 'Inform', accent: 'yellow',
144
+ color: '#FED12E', dark: '#D0A301', light: '#FAF9F5',
145
+ textColor: '#7B640E', // AA-safe text — yellow-900
146
+ darkMode: { color: '#FED12E', dark: '#FEE88A', textOnAccent: '#1C1C1C' },
147
+ },
148
+ empower: {
149
+ name: 'Empower', accent: 'coral',
150
+ color: '#FF5740', dark: '#E61B00', light: '#FAF6F5',
151
+ textColor: '#E61B00', // AA-safe text — coral-700
152
+ darkMode: { color: '#FF5740', dark: '#F66855', textOnAccent: '#FFFFFF' },
153
+ },
154
+ connect: {
155
+ name: 'Connect', accent: 'teal',
156
+ color: '#00657C', dark: '#003E4D', light: '#F5F9FA',
157
+ textColor: '#00657C', // Already AA — muted blue
158
+ darkMode: { color: '#3A9BB0', dark: '#59B0C4', textOnAccent: '#FFFFFF' },
159
+ },
160
+ };
161
+
162
+ // ──────────────────────────────────────────────
163
+ // Semantic Tokens (Light / Dark)
164
+ // All text-on-background pairings verified AA (4.5:1+).
165
+ // ──────────────────────────────────────────────
166
+
167
+ export const semanticTokens = {
168
+ light: {
169
+ layerBase: { background: '#F7F7F8', text: '#535964', icon: '#535964', border: '#D3D7DE' }, // 6.58:1
170
+ layerOne: { background: '#EBEDEF', text: '#41454E', icon: '#41454E', border: '#D3D7DE' }, // 7.23:1
171
+ layerTwo: { background: '#D3D7DE', text: '#2F3237', icon: '#2F3237', border: '#B3BBCC' }, // 7.60:1
172
+ heading: { text: '#2F3237' }, // 12.02:1 on gray-050
173
+ divider: '#D3D7DE',
174
+ link: { default: '#00657C', hovered: '#003E4D', focused: '#003E4D', pressed: '#003E4D' },
175
+ input: { background: '#F7F7F8', text: '#41454E', border: '#41454E', icon: '#6B7280' }, // text bumped from gray-500 to gray-700 for AA
176
+ buttonPrimary: {
177
+ background: { default: '#005366', hovered: '#003E4D', pressed: '#03303A', focused: '#005366', disabled: '#D3D7DE' },
178
+ text: { default: '#FFFFFF', disabled: '#8F9EBC' },
179
+ },
180
+ buttonSecondary: {
181
+ background: { default: '#F7F7F8', hovered: '#EBEDEF', pressed: '#D3D7DE', focused: '#EBEDEF', disabled: '#F7F7F8' },
182
+ text: { default: '#003E4D', disabled: '#8F9EBC' },
183
+ border: { default: '#003E4D', disabled: '#D3D7DE' },
184
+ },
185
+ // Feedback states
186
+ badge: { background: '#EBEDEF', text: '#41454E', border: '#D3D7DE' },
187
+ tooltip: { background: '#2F3237', text: '#EBEDEF' },
188
+ overlay: 'rgba(19, 20, 22, 0.5)',
189
+ skeleton: { from: '#EBEDEF', to: '#D3D7DE' },
190
+ },
191
+ dark: {
192
+ layerBase: { background: '#131416', text: '#B3BBCC', icon: '#B3BBCC', border: '#41454E' }, // 9.56:1 ✓ AAA
193
+ layerOne: { background: '#2F3237', text: '#D3D7DE', icon: '#D3D7DE', border: '#41454E' }, // 8.91:1 ✓ AAA
194
+ layerTwo: { background: '#41454E', text: '#EBEDEF', icon: '#EBEDEF', border: '#535964' }, // 8.18:1 ✓ AAA
195
+ heading: { text: '#EBEDEF' }, // 15.70:1 on layerBase ✓ AAA
196
+ divider: '#41454E',
197
+ link: { default: '#59B0C4', hovered: '#7FBECC', focused: '#7FBECC', pressed: '#3A9BB0' }, // 7.40:1 on layerBase ✓ AAA
198
+ input: { background: '#1E2024', text: '#D3D7DE', border: '#6B7280', icon: '#B3BBCC' }, // border bumped to gray-500 for 3.37:1 UI ✓
199
+ buttonPrimary: {
200
+ // Darkened from skyblue to teal for white-text AA compliance
201
+ background: { default: '#005366', hovered: '#327E8F', pressed: '#003E4D', focused: '#005366', disabled: '#2F3237' },
202
+ text: { default: '#FFFFFF', disabled: '#535964' }, // 8.64:1 / 4.65:1 / 11.69:1 ✓ AA+
203
+ },
204
+ buttonSecondary: {
205
+ background: { default: '#131416', hovered: '#2F3237', pressed: '#41454E', focused: '#2F3237', disabled: '#131416' },
206
+ text: { default: '#59B0C4', disabled: '#535964' }, // 7.40:1 ✓ AAA
207
+ border: { default: '#59B0C4', disabled: '#41454E' },
208
+ },
209
+ // Feedback states
210
+ badge: { background: '#41454E', text: '#EBEDEF', border: '#535964' },
211
+ tooltip: { background: '#EBEDEF', text: '#2F3237' },
212
+ overlay: 'rgba(19, 20, 22, 0.75)',
213
+ skeleton: { from: '#2F3237', to: '#41454E' },
214
+ },
215
+ };
216
+
217
+ // ──────────────────────────────────────────────
218
+ // Data Visualization Palette
219
+ // ──────────────────────────────────────────────
220
+
221
+ // 10 hues derived from the EV brand palette.
222
+ // Core 4 (teal, sky blue, coral, yellow) + 6 harmonious neighbors.
223
+ // Each has 5 shades: [100-light, 300, 400, base-500, 700-dark]
224
+ export const dataVizPalette = [
225
+ { name: 'Teal', base: '#00657C', shades: ['#C0E8F2', '#55D9F6', '#3AABB8', '#00657C', '#003E4D'] },
226
+ { name: 'Sky Blue', base: '#59B0C4', shades: ['#CDE0E5', '#A7CFD8', '#7FBECC', '#59B0C4', '#327E8F'] },
227
+ { name: 'Ocean', base: '#2563A0', shades: ['#CDDCED', '#7BADD4', '#4D8FC2', '#2563A0', '#193F68'] },
228
+ { name: 'Coral', base: '#FF5740', shades: ['#F6E6E4', '#F2988C', '#F66855', '#FF5740', '#B31D09'] },
229
+ { name: 'Terracotta', base: '#C2674A', shades: ['#F0DCD4', '#D9A48E', '#CF8568', '#C2674A', '#7A3D2C'] },
230
+ { name: 'Yellow', base: '#FED12E', shades: ['#F6F2E4', '#F2DC8D', '#F5D356', '#FED12E', '#9F7F09'] },
231
+ { name: 'Honey', base: '#D4940B', shades: ['#F5EACE', '#E8C563', '#DCA930', '#D4940B', '#805A06'] },
232
+ { name: 'Sage', base: '#5A9A6E', shades: ['#D4E8DA', '#9ACBAA', '#74B589', '#5A9A6E', '#376043'] },
233
+ { name: 'Dusk', base: '#7C6B9E', shades: ['#E4DFF0', '#B5A8CF', '#9788B8', '#7C6B9E', '#4D4263'] },
234
+ { name: 'Stone', base: '#6B7280', shades: ['#EBEDEF', '#B3BBCC', '#8F9EBC', '#6B7280', '#41454E'] },
235
+ ];
236
+
237
+ // ──────────────────────────────────────────────
238
+ // Typography
239
+ // ──────────────────────────────────────────────
240
+
241
+ export const fonts = {
242
+ primary: "'Manrope', sans-serif",
243
+ fallback: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
244
+ };
245
+
246
+ export const fontWeights = {
247
+ regular: 400,
248
+ medium: 500,
249
+ semibold: 600,
250
+ bold: 700,
251
+ extrabold: 800,
252
+ };
253
+
254
+ export const fontSizes = {
255
+ xs: '12px',
256
+ sm: '14px',
257
+ base: '16px',
258
+ lg: '18px',
259
+ xl: '20px',
260
+ '2xl': '24px',
261
+ '3xl': '30px',
262
+ '4xl': '36px',
263
+ '5xl': '48px',
264
+ };
265
+
266
+ export const lineHeights = {
267
+ tight: 1.25,
268
+ normal: 1.5,
269
+ relaxed: 1.75,
270
+ };
271
+
272
+ export const letterSpacing = {
273
+ tight: '-0.02em',
274
+ normal: '0',
275
+ wide: '0.05em',
276
+ };
277
+
278
+ // Composite text styles — combine size, weight, line-height, and letter-spacing
279
+ // into ready-to-spread objects for inline styles: style={{ ...textStyles.displayTitle }}
280
+ export const textStyles = {
281
+ displayTitle: { fontSize: '48px', fontWeight: 800, lineHeight: 1.1, letterSpacing: '-0.03em' },
282
+ displaySub: { fontSize: '36px', fontWeight: 700, lineHeight: 1.15, letterSpacing: '-0.02em' },
283
+ h1: { fontSize: '30px', fontWeight: 700, lineHeight: 1.25, letterSpacing: '-0.02em' },
284
+ h2: { fontSize: '24px', fontWeight: 700, lineHeight: 1.3, letterSpacing: '-0.01em' },
285
+ h3: { fontSize: '20px', fontWeight: 600, lineHeight: 1.35, letterSpacing: '0' },
286
+ h4: { fontSize: '18px', fontWeight: 600, lineHeight: 1.4, letterSpacing: '0' },
287
+ bodyLg: { fontSize: '18px', fontWeight: 400, lineHeight: 1.6, letterSpacing: '0' },
288
+ body: { fontSize: '16px', fontWeight: 400, lineHeight: 1.5, letterSpacing: '0' },
289
+ bodySm: { fontSize: '14px', fontWeight: 400, lineHeight: 1.5, letterSpacing: '0' },
290
+ label: { fontSize: '14px', fontWeight: 500, lineHeight: 1.4, letterSpacing: '0.01em' },
291
+ labelSm: { fontSize: '12px', fontWeight: 500, lineHeight: 1.4, letterSpacing: '0.02em' },
292
+ caption: { fontSize: '12px', fontWeight: 400, lineHeight: 1.4, letterSpacing: '0.01em' },
293
+ overline: { fontSize: '11px', fontWeight: 700, lineHeight: 1.4, letterSpacing: '0.08em', textTransform: 'uppercase' },
294
+ };
295
+
296
+ // ──────────────────────────────────────────────
297
+ // Spacing (4px base unit)
298
+ // ──────────────────────────────────────────────
299
+
300
+ export const spacing = {
301
+ 0: '0',
302
+ '050': '2px',
303
+ 1: '4px',
304
+ '150': '6px',
305
+ 2: '8px',
306
+ 3: '12px',
307
+ 4: '16px',
308
+ 5: '20px',
309
+ 6: '24px',
310
+ 8: '32px',
311
+ 10: '40px',
312
+ 12: '48px',
313
+ 16: '64px',
314
+ 20: '80px',
315
+ 24: '96px',
316
+ };
317
+
318
+ // ──────────────────────────────────────────────
319
+ // Border Radius
320
+ // ──────────────────────────────────────────────
321
+
322
+ export const borderRadius = {
323
+ none: '0',
324
+ sm: '4px',
325
+ md: '8px',
326
+ lg: '10px',
327
+ xl: '12px',
328
+ '2xl': '16px',
329
+ full: '9999px',
330
+ };
331
+
332
+ // ──────────────────────────────────────────────
333
+ // Shadows & Elevation
334
+ // ──────────────────────────────────────────────
335
+
336
+ export const shadows = {
337
+ sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
338
+ md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
339
+ lg: '0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 10px -4px rgba(0, 0, 0, 0.05)',
340
+ xl: '0 20px 40px -8px rgba(0, 0, 0, 0.12), 0 8px 16px -6px rgba(0, 0, 0, 0.06)',
341
+ inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
342
+ glowCoral: '0 0 0 3px rgba(255, 87, 64, 0.25)',
343
+ glowTeal: '0 0 0 3px rgba(0, 101, 124, 0.2)',
344
+ cardHover: '0 12px 32px -4px rgba(0, 0, 0, 0.12), 0 4px 8px -2px rgba(0, 0, 0, 0.04)',
345
+ none: 'none',
346
+ };
347
+
348
+ // ──────────────────────────────────────────────
349
+ // Motion
350
+ // ──────────────────────────────────────────────
351
+
352
+ export const duration = {
353
+ instant: '50ms',
354
+ fast: '120ms', // Micro-interactions: toggles, checkboxes
355
+ normal: '200ms', // Buttons, inputs, small transitions
356
+ slow: '350ms', // Panels, cards, modals
357
+ slower: '500ms', // Page transitions, complex orchestration
358
+ };
359
+
360
+ export const easing = {
361
+ default: 'cubic-bezier(0.2, 0, 0, 1)', // General-purpose (Material 3 standard)
362
+ enter: 'cubic-bezier(0, 0, 0.2, 1)', // Elements appearing (decelerate)
363
+ exit: 'cubic-bezier(0.4, 0, 1, 1)', // Elements leaving (accelerate)
364
+ spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)', // Playful overshoot — buttons, badges
365
+ linear: 'linear',
366
+ };
367
+
368
+ // ──────────────────────────────────────────────
369
+ // Focus Ring
370
+ // ──────────────────────────────────────────────
371
+
372
+ export const focus = {
373
+ ring: '0 0 0 2px #FFFFFF, 0 0 0 4px #00657C', // White gap + brand muted blue (6.66:1 on white)
374
+ ringDark: '0 0 0 2px #131416, 0 0 0 4px #59B0C4', // Dark gap + light blue (6.84:1 on dark)
375
+ ringCoral: '0 0 0 2px #FFFFFF, 0 0 0 4px #FF5740', // Coral variant for pillar contexts
376
+ offset: '2px',
377
+ };
378
+
379
+ // ──────────────────────────────────────────────
380
+ // Z-Index Scale
381
+ // ──────────────────────────────────────────────
382
+
383
+ export const zIndex = {
384
+ hide: -1,
385
+ base: 0,
386
+ dropdown: 10,
387
+ sticky: 20,
388
+ header: 30,
389
+ overlay: 40,
390
+ modal: 50,
391
+ toast: 60,
392
+ tooltip: 70,
393
+ };
394
+
395
+ // ──────────────────────────────────────────────
396
+ // Opacity
397
+ // ──────────────────────────────────────────────
398
+
399
+ export const opacity = {
400
+ disabled: 0.4,
401
+ muted: 0.6,
402
+ subtle: 0.8,
403
+ full: 1,
404
+ };
405
+
406
+ // ──────────────────────────────────────────────
407
+ // Breakpoints
408
+ // ──────────────────────────────────────────────
409
+
410
+ export const breakpoints = {
411
+ sm: '640px',
412
+ md: '768px',
413
+ lg: '1024px',
414
+ xl: '1280px',
415
+ };