@opendata-ai/openchart-core 8.0.0-rc.2 → 8.0.0-rc.21

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 (82) hide show
  1. package/dist/index.d.ts +490 -25
  2. package/dist/index.js +57 -19
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/dist/styles.css.map +1 -1
  6. package/package.json +1 -2
  7. package/schema/chart.schema.json +485 -35
  8. package/schema/table.schema.json +35 -1
  9. package/schema/vizspec.schema.json +1305 -120
  10. package/src/accessibility/__tests__/alt-text.test.ts +0 -110
  11. package/src/accessibility/__tests__/aria.test.ts +0 -125
  12. package/src/accessibility/alt-text.ts +0 -155
  13. package/src/accessibility/aria.ts +0 -75
  14. package/src/accessibility/index.ts +0 -6
  15. package/src/colors/__tests__/colorblind.test.ts +0 -75
  16. package/src/colors/__tests__/contrast.test.ts +0 -119
  17. package/src/colors/__tests__/palettes.test.ts +0 -74
  18. package/src/colors/colorblind.ts +0 -131
  19. package/src/colors/contrast.ts +0 -143
  20. package/src/colors/index.ts +0 -36
  21. package/src/colors/palettes.ts +0 -206
  22. package/src/helpers/__tests__/deep-merge.test.ts +0 -54
  23. package/src/helpers/__tests__/spec-builders.test.ts +0 -338
  24. package/src/helpers/deep-merge.ts +0 -29
  25. package/src/helpers/spec-builders.ts +0 -479
  26. package/src/index.ts +0 -215
  27. package/src/labels/__tests__/collision.test.ts +0 -310
  28. package/src/labels/collision.ts +0 -249
  29. package/src/labels/index.ts +0 -13
  30. package/src/layout/__tests__/chrome.test.ts +0 -208
  31. package/src/layout/__tests__/text-measure.test.ts +0 -57
  32. package/src/layout/chrome.ts +0 -470
  33. package/src/layout/index.ts +0 -18
  34. package/src/layout/text-measure.ts +0 -404
  35. package/src/layout/text-wrap.ts +0 -93
  36. package/src/locale/__tests__/format.test.ts +0 -606
  37. package/src/locale/format.ts +0 -459
  38. package/src/locale/index.ts +0 -21
  39. package/src/responsive/__tests__/breakpoints.test.ts +0 -146
  40. package/src/responsive/__tests__/metrics.test.ts +0 -52
  41. package/src/responsive/breakpoints.ts +0 -172
  42. package/src/responsive/index.ts +0 -53
  43. package/src/responsive/metrics.ts +0 -299
  44. package/src/schema/__tests__/generated-artifacts.test.ts +0 -42
  45. package/src/schema/__tests__/schema.test.ts +0 -179
  46. package/src/styles/__tests__/reduced-motion-selectors.test.ts +0 -28
  47. package/src/styles/animation.css +0 -199
  48. package/src/styles/base.css +0 -102
  49. package/src/styles/chrome.css +0 -174
  50. package/src/styles/dark.css +0 -44
  51. package/src/styles/graph.css +0 -135
  52. package/src/styles/index.css +0 -28
  53. package/src/styles/keyframes.css +0 -135
  54. package/src/styles/legend.css +0 -18
  55. package/src/styles/reduced-motion.css +0 -67
  56. package/src/styles/series-search.css +0 -153
  57. package/src/styles/sparkline.css +0 -16
  58. package/src/styles/table-animation.css +0 -93
  59. package/src/styles/table.css +0 -398
  60. package/src/styles/token-definitions.ts +0 -199
  61. package/src/styles/tokens.css +0 -156
  62. package/src/styles/tooltip.css +0 -126
  63. package/src/styles/you-draw-it.css +0 -126
  64. package/src/theme/__tests__/dark-mode.test.ts +0 -232
  65. package/src/theme/__tests__/defaults.test.ts +0 -73
  66. package/src/theme/__tests__/resolve.test.ts +0 -277
  67. package/src/theme/dark-mode.ts +0 -258
  68. package/src/theme/defaults.ts +0 -110
  69. package/src/theme/index.ts +0 -8
  70. package/src/theme/presets.ts +0 -64
  71. package/src/theme/resolve.ts +0 -312
  72. package/src/types/__tests__/encoding.test.ts +0 -368
  73. package/src/types/__tests__/gradient.test.ts +0 -95
  74. package/src/types/__tests__/spec.test.ts +0 -499
  75. package/src/types/encoding.ts +0 -280
  76. package/src/types/events.ts +0 -154
  77. package/src/types/index.ts +0 -287
  78. package/src/types/layout.ts +0 -1966
  79. package/src/types/schema-roots.ts +0 -32
  80. package/src/types/spec.ts +0 -2867
  81. package/src/types/table.ts +0 -108
  82. package/src/types/theme.ts +0 -209
@@ -1,398 +0,0 @@
1
- @layer oc.components {
2
- /* ---------------------------------------------------------------------------
3
- * Table base
4
- * --------------------------------------------------------------------------- */
5
-
6
- .oc-table-wrapper {
7
- font-family: var(--oc-font-family);
8
- color: var(--oc-text);
9
- background: var(--oc-bg);
10
-
11
- & > .oc-chrome {
12
- margin-bottom: 16px;
13
- padding-left: 16px;
14
- padding-right: 16px;
15
- }
16
-
17
- & > .oc-chrome:first-child {
18
- padding-top: 4px;
19
- }
20
-
21
- & table {
22
- width: 100%;
23
- border-collapse: collapse;
24
- }
25
-
26
- & th,
27
- & td {
28
- padding: 10px 16px;
29
- text-align: left;
30
- border-bottom: 1px solid var(--oc-border);
31
- }
32
-
33
- & th {
34
- font-weight: 600;
35
- font-size: 12px;
36
- text-transform: uppercase;
37
- letter-spacing: 0.05em;
38
- color: var(--oc-text-secondary);
39
- white-space: nowrap;
40
- }
41
-
42
- & thead {
43
- position: sticky;
44
- top: 0;
45
- z-index: 2;
46
- background: var(--oc-bg);
47
- }
48
-
49
- & thead th {
50
- border-bottom-width: 2px;
51
- }
52
-
53
- & td {
54
- font-size: 14px;
55
- font-variant-numeric: tabular-nums;
56
- }
57
-
58
- & th:focus {
59
- outline: 2px solid var(--oc-focus);
60
- outline-offset: -2px;
61
- }
62
-
63
- & tbody:focus {
64
- outline: none;
65
- }
66
- }
67
-
68
- .oc-table-title {
69
- margin-bottom: 4px;
70
- font-size: var(--oc-title-computed-size, var(--oc-title-size));
71
- font-weight: var(--oc-title-computed-weight, var(--oc-title-weight));
72
- color: var(--oc-title-computed-color, var(--oc-text));
73
- }
74
-
75
- .oc-table-subtitle {
76
- margin-bottom: 8px;
77
- font-size: var(--oc-subtitle-computed-size, var(--oc-subtitle-size));
78
- font-weight: var(--oc-subtitle-computed-weight, var(--oc-subtitle-weight));
79
- color: var(--oc-subtitle-computed-color, var(--oc-text-secondary));
80
- }
81
-
82
- .oc-table-source {
83
- font-size: var(--oc-source-computed-size, var(--oc-source-size));
84
- color: var(--oc-source-computed-color, var(--oc-text-muted));
85
- }
86
-
87
- .oc-table-footer-text {
88
- font-size: var(--oc-footer-computed-size, var(--oc-source-size));
89
- color: var(--oc-footer-computed-color, var(--oc-text-muted));
90
- }
91
-
92
- .oc-table-scroll {
93
- overflow-x: auto;
94
- }
95
-
96
- /* ---------------------------------------------------------------------------
97
- * Sticky first column
98
- * --------------------------------------------------------------------------- */
99
-
100
- .oc-table--sticky {
101
- & th:first-child,
102
- & td:first-child {
103
- position: sticky;
104
- left: 0;
105
- z-index: 1;
106
- background: var(--oc-bg);
107
- }
108
- }
109
-
110
- /* ---------------------------------------------------------------------------
111
- * Sort controls
112
- * --------------------------------------------------------------------------- */
113
-
114
- .oc-table-sort-btn {
115
- background: none;
116
- border: none;
117
- cursor: pointer;
118
- padding: 2px;
119
- margin-left: 6px;
120
- display: inline-flex;
121
- flex-direction: column;
122
- align-items: center;
123
- vertical-align: middle;
124
- gap: 2px;
125
-
126
- &::before,
127
- &::after {
128
- content: "";
129
- display: block;
130
- width: 0;
131
- height: 0;
132
- border-left: 5px solid transparent;
133
- border-right: 5px solid transparent;
134
- transition:
135
- opacity 0.15s,
136
- border-color 0.15s;
137
- }
138
-
139
- &::before {
140
- border-bottom: 4.5px solid var(--oc-text-secondary);
141
- opacity: 0.45;
142
- }
143
-
144
- &::after {
145
- border-top: 4.5px solid var(--oc-text-secondary);
146
- opacity: 0.45;
147
- }
148
-
149
- &:hover::before,
150
- &:hover::after {
151
- opacity: 0.75;
152
- }
153
- }
154
-
155
- th[aria-sort="ascending"] .oc-table-sort-btn {
156
- &::before {
157
- opacity: 1;
158
- border-bottom-color: var(--oc-text);
159
- }
160
-
161
- &::after {
162
- opacity: 0.15;
163
- }
164
- }
165
-
166
- th[aria-sort="descending"] .oc-table-sort-btn {
167
- &::after {
168
- opacity: 1;
169
- border-top-color: var(--oc-text);
170
- }
171
-
172
- &::before {
173
- opacity: 0.15;
174
- }
175
- }
176
-
177
- /* ---------------------------------------------------------------------------
178
- * Search
179
- * --------------------------------------------------------------------------- */
180
-
181
- /* Shared search-input skin (table + series-search).
182
- Padding excluded: it diverges (table: 8px 12px; series: 0 10px 0 28px)
183
- and series-search.css is imported before table.css — a padding declaration
184
- here would beat the series-search override by source order. */
185
- .oc-table-search input,
186
- .oc-series-search-input {
187
- width: 100%;
188
- border: 1px solid var(--oc-border);
189
- border-radius: 6px;
190
- font-size: 13px;
191
- font-family: inherit;
192
- background: var(--oc-bg);
193
- color: var(--oc-text);
194
- box-sizing: border-box;
195
- transition: border-color 0.15s;
196
- }
197
-
198
- .oc-table-search input::placeholder,
199
- .oc-series-search-input::placeholder {
200
- color: var(--oc-text-muted);
201
- font-size: 13px;
202
- }
203
-
204
- .oc-table-search input:focus,
205
- .oc-series-search-input:focus {
206
- outline: none;
207
- border-color: var(--oc-focus);
208
- box-shadow: 0 0 0 3px var(--oc-focus-ring);
209
- }
210
-
211
- .oc-table-search {
212
- padding: 8px 0;
213
-
214
- & input {
215
- padding: 8px 12px;
216
- }
217
- }
218
-
219
- /* ---------------------------------------------------------------------------
220
- * Pagination
221
- * --------------------------------------------------------------------------- */
222
-
223
- .oc-table-pagination {
224
- display: flex;
225
- align-items: center;
226
- justify-content: space-between;
227
- padding: 12px 0 4px;
228
- font-size: 13px;
229
- color: var(--oc-text-secondary);
230
-
231
- & button {
232
- padding: 6px 14px;
233
- border: 1px solid var(--oc-border);
234
- border-radius: 6px;
235
- background: var(--oc-bg);
236
- color: var(--oc-text);
237
- cursor: pointer;
238
- font-size: 13px;
239
- font-family: inherit;
240
- transition:
241
- background 0.15s,
242
- border-color 0.15s;
243
-
244
- &:disabled {
245
- opacity: 0.35;
246
- cursor: not-allowed;
247
- }
248
-
249
- &:hover:not(:disabled) {
250
- background: var(--oc-hover-bg);
251
- border-color: var(--oc-axis);
252
- }
253
-
254
- &:focus-visible {
255
- outline: 2px solid var(--oc-focus);
256
- outline-offset: 1px;
257
- }
258
- }
259
- }
260
-
261
- .oc-table-pagination-info {
262
- font-variant-numeric: tabular-nums;
263
- }
264
-
265
- .oc-table-pagination-btns {
266
- display: flex;
267
- gap: 8px;
268
- }
269
-
270
- /* ---------------------------------------------------------------------------
271
- * Bar cells
272
- * --------------------------------------------------------------------------- */
273
-
274
- .oc-table-bar {
275
- position: relative;
276
- }
277
-
278
- .oc-table-bar-fill {
279
- position: absolute;
280
- top: 6px;
281
- left: 0;
282
- bottom: 6px;
283
- border-radius: 2px;
284
- opacity: 0.15;
285
- pointer-events: none;
286
- }
287
-
288
- .oc-table-bar-value {
289
- position: relative;
290
- z-index: 1;
291
- }
292
-
293
- /* ---------------------------------------------------------------------------
294
- * Sparkline cells
295
- * --------------------------------------------------------------------------- */
296
-
297
- .oc-table-sparkline {
298
- display: block;
299
- width: 100%;
300
- position: relative;
301
-
302
- & svg {
303
- display: block;
304
- width: 100%;
305
- overflow: visible;
306
- }
307
- }
308
-
309
- .oc-table-sparkline-dot {
310
- position: absolute;
311
- border-radius: 50%;
312
- width: 5px;
313
- height: 5px;
314
- }
315
-
316
- .oc-table-sparkline-labels {
317
- display: flex;
318
- justify-content: space-between;
319
- font-size: 11px;
320
- line-height: 1;
321
- }
322
-
323
- /* ---------------------------------------------------------------------------
324
- * Image cells
325
- * --------------------------------------------------------------------------- */
326
-
327
- .oc-table-image {
328
- display: inline-block;
329
- vertical-align: middle;
330
-
331
- & img {
332
- object-fit: cover;
333
- }
334
- }
335
-
336
- .oc-table-image-rounded img {
337
- border-radius: 50%;
338
- }
339
-
340
- /* ---------------------------------------------------------------------------
341
- * Flag cells
342
- * --------------------------------------------------------------------------- */
343
-
344
- .oc-table-flag {
345
- font-size: 1.2em;
346
- }
347
-
348
- /* ---------------------------------------------------------------------------
349
- * Compact mode
350
- * --------------------------------------------------------------------------- */
351
-
352
- .oc-table--compact {
353
- & th,
354
- & td {
355
- padding: 4px 8px;
356
- font-size: 13px;
357
- }
358
-
359
- & th {
360
- font-size: 11px;
361
- }
362
- }
363
-
364
- /* ---------------------------------------------------------------------------
365
- * Row hover (when onRowClick is set)
366
- * --------------------------------------------------------------------------- */
367
-
368
- .oc-table--clickable tbody {
369
- & tr {
370
- cursor: pointer;
371
- }
372
-
373
- & tr:hover {
374
- background: var(--oc-hover-bg);
375
- }
376
- }
377
-
378
- /* ---------------------------------------------------------------------------
379
- * Keyboard cell focus indicator
380
- * --------------------------------------------------------------------------- */
381
-
382
- .oc-table-cell-focus {
383
- outline: 2px solid var(--oc-focus);
384
- outline-offset: -2px;
385
- }
386
-
387
- /* ---------------------------------------------------------------------------
388
- * Empty state
389
- * --------------------------------------------------------------------------- */
390
-
391
- .oc-table-empty {
392
- padding: 32px 16px;
393
- text-align: center;
394
- color: var(--oc-text-secondary);
395
- font-size: 14px;
396
- font-style: italic;
397
- }
398
- }
@@ -1,199 +0,0 @@
1
- export interface CssTokenDef {
2
- name: string;
3
- light: string;
4
- dark?: string;
5
- comment?: string;
6
- consumers?: string;
7
- section?: string;
8
- }
9
-
10
- export const CSS_TOKEN_ROOT_SELECTORS: readonly string[] = [
11
- '.oc-root',
12
- '.oc-chart-root',
13
- '.oc-table-wrapper',
14
- '.oc-table-root',
15
- '.oc-graph-wrapper',
16
- '.oc-graph-root',
17
- '.oc-sankey-root',
18
- '.oc-tilemap-root',
19
- '.oc-barlist-root',
20
- '.oc-map-root',
21
- ];
22
-
23
- /**
24
- * Text levels. Names invert across modes: in light mode "muted" sits at
25
- * a lighter zinc step than "subtle" because both are picked relative to
26
- * the active background, not from a fixed lightness ladder. The intent
27
- * is "muted = first step away from primary text"; "subtle = next step
28
- * down"; etc.
29
- *
30
- * Light mode: text=zinc-950, secondary=zinc-700, muted=zinc-500,
31
- * subtle=zinc-400, faint=zinc-300
32
- * Dark mode (dark.css): inverts the surface tokens but keeps the same
33
- * muted -> subtle -> faint progression away from primary.
34
- */
35
-
36
- export const CSS_TOKENS: readonly CssTokenDef[] = [
37
- {
38
- name: '--oc-font-family',
39
- light:
40
- '\n "Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
41
- },
42
- {
43
- name: '--oc-font-mono',
44
- light: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
45
- },
46
-
47
- {
48
- section: 'Animation easing presets via CSS linear()',
49
- name: '--oc-ease-smooth',
50
- light:
51
- 'linear(\n 0,\n 0.157,\n 0.438,\n 0.64,\n 0.766,\n 0.85,\n 0.906,\n 0.941,\n 0.964,\n 0.978,\n 0.988,\n 0.994,\n 0.998,\n 1\n )',
52
- },
53
- {
54
- name: '--oc-ease-snappy',
55
- light:
56
- 'linear(\n 0,\n 0.012,\n 0.048,\n 0.108,\n 0.194,\n 0.302,\n 0.426,\n 0.559,\n 0.69,\n 0.808,\n 0.905,\n 0.973,\n 1.013,\n 1.028,\n 1.023,\n 1.006,\n 0.984,\n 0.966,\n 0.957,\n 0.957,\n 0.964,\n 0.975,\n 0.986,\n 0.995,\n 1,\n 1.003,\n 1.002,\n 1,\n 0.998,\n 0.998,\n 0.999,\n 1\n )',
57
- consumers: 'EASE_VAR_MAP in vanilla renderers',
58
- },
59
-
60
- { section: 'Animation timing defaults', name: '--oc-animation-duration', light: '500ms' },
61
- { name: '--oc-animation-stagger', light: '80ms' },
62
- { name: '--oc-annotation-delay', light: '200ms' },
63
-
64
- { section: 'Typography scale (editorial design system)', name: '--oc-title-size', light: '26px' },
65
- { name: '--oc-title-weight', light: '590' },
66
- { name: '--oc-title-tracking', light: '-0.022em' },
67
- { name: '--oc-subtitle-size', light: '14px' },
68
- { name: '--oc-subtitle-weight', light: '450' },
69
- { name: '--oc-source-size', light: '11px' },
70
- { name: '--oc-source-weight', light: '450' },
71
- { name: '--oc-body-size', light: '13px' },
72
- { name: '--oc-eyebrow-size', light: '11px' },
73
- { name: '--oc-eyebrow-weight', light: '550' },
74
- { name: '--oc-eyebrow-tracking', light: '0.08em' },
75
-
76
- { section: 'Surfaces (light mode defaults)', name: '--oc-bg', light: '#ffffff', dark: '#09090b' },
77
- { name: '--oc-card', light: '#ffffff', dark: '#111113' },
78
- {
79
- name: '--oc-secondary',
80
- light: '#f4f4f5',
81
- dark: '#27272a',
82
- comment: 'zinc-100, raised surface',
83
- },
84
-
85
- {
86
- section: `Text levels. Names invert across modes: in light mode "muted" sits at
87
- * a lighter zinc step than "subtle" because both are picked relative to
88
- * the active background, not from a fixed lightness ladder. The intent
89
- * is "muted = first step away from primary text"; "subtle = next step
90
- * down"; etc.
91
- *
92
- * Light mode: text=zinc-950, secondary=zinc-700, muted=zinc-500,
93
- * subtle=zinc-400, faint=zinc-300
94
- * Dark mode (dark.css): inverts the surface tokens but keeps the same
95
- * muted -> subtle -> faint progression away from primary.`,
96
- name: '--oc-text',
97
- light: '#09090b',
98
- dark: '#f7f8f8',
99
- },
100
- { name: '--oc-text-secondary', light: '#3f3f46', dark: '#d0d6e0' },
101
- { name: '--oc-text-muted', light: '#71717a', dark: '#a1a1aa' },
102
- { name: '--oc-text-faint', light: '#d4d4d8', dark: '#52525b' },
103
-
104
- {
105
- section: 'Lines',
106
- name: '--oc-gridline',
107
- light: 'rgba(0, 0, 0, 0.1)',
108
- dark: 'rgba(255, 255, 255, 0.05)',
109
- },
110
- {
111
- name: '--oc-axis',
112
- light: 'rgba(0, 0, 0, 0.1)',
113
- dark: 'rgba(255, 255, 255, 0.1)',
114
- },
115
- {
116
- name: '--oc-border',
117
- light: 'rgba(0, 0, 0, 0.08)',
118
- dark: 'rgba(255, 255, 255, 0.1)',
119
- },
120
- { name: '--oc-border-radius', light: '2px' },
121
-
122
- {
123
- section: 'Brand and semantic',
124
- name: '--oc-accent',
125
- light: '#06b6d4',
126
- dark: '#06b6d4',
127
- },
128
- {
129
- name: '--oc-accent-strong',
130
- light: '#0891b2',
131
- dark: '#06b6d4',
132
- comment: 'darker cyan for line strokes on light bg',
133
- consumers: 'stamped via adaptForLightLineStroke',
134
- },
135
- { name: '--oc-positive', light: '#10b981', dark: '#34d399' },
136
- { name: '--oc-negative', light: '#e11d48', dark: '#fb7185' },
137
- { name: '--oc-focus', light: '#3b82f6', dark: '#60a5fa' },
138
- {
139
- name: '--oc-focus-ring',
140
- light: 'rgba(59, 130, 246, 0.1)',
141
- comment: 'static, intentionally NOT derived from --oc-focus and NOT flipped in dark mode',
142
- },
143
- {
144
- name: '--oc-focus-ring-strong',
145
- light: 'rgba(59, 130, 246, 0.25)',
146
- },
147
- {
148
- name: '--oc-editable-hover',
149
- light: 'rgba(79, 70, 229, 0.35)',
150
- comment: 'edit-mode hover outline, indigo one-off',
151
- },
152
-
153
- {
154
- section: 'Spacing scale (4px base) — only --oc-space-2/4 are consumed (JS-stamped)',
155
- name: '--oc-space-2',
156
- light: '8px',
157
- consumers:
158
- 'stamped at mount from theme.spacing (theme-tokens.ts); component CSS must NOT consume these',
159
- },
160
- {
161
- name: '--oc-space-4',
162
- light: '16px',
163
- consumers:
164
- 'stamped at mount from theme.spacing (theme-tokens.ts); component CSS must NOT consume these',
165
- },
166
-
167
- {
168
- section: 'Interactive states',
169
- name: '--oc-hover-bg',
170
- light: 'rgba(0, 0, 0, 0.025)',
171
- dark: 'rgba(255, 255, 255, 0.05)',
172
- },
173
- {
174
- name: '--oc-tooltip-bg',
175
- light: 'rgba(255, 255, 255, 0.88)',
176
- dark: 'rgba(17, 17, 19, 0.92)',
177
- },
178
- {
179
- name: '--oc-tooltip-border',
180
- light: 'rgba(0, 0, 0, 0.08)',
181
- dark: 'rgba(255, 255, 255, 0.08)',
182
- },
183
- {
184
- name: '--oc-tooltip-shadow',
185
- light: '0 2px 8px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.12)',
186
- dark: '0 2px 8px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.4)',
187
- },
188
- { name: '--oc-tooltip-text', light: '#09090b', dark: '#f7f8f8' },
189
- { name: '--oc-legend-text', light: '#3f3f46', dark: '#d0d6e0' },
190
- ];
191
-
192
- const TOKEN_MAP = new Map(CSS_TOKENS.map((t) => [t.name, t]));
193
-
194
- export function cssTokenDefault(name: string, mode: 'light' | 'dark'): string {
195
- const token = TOKEN_MAP.get(name);
196
- if (!token) throw new Error(`Unknown CSS token: ${name}`);
197
- if (mode === 'dark' && token.dark !== undefined) return token.dark;
198
- return token.light;
199
- }