@webmate-studio/builder 0.2.130 → 0.2.133

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.
@@ -0,0 +1,738 @@
1
+ /**
2
+ * Design Token Schema V2
3
+ *
4
+ * Single Source of Truth für das gesamte Design-Token-System.
5
+ * Alle Konsumenten (webmate-studio, webmate-workbench, preview, build)
6
+ * importieren dieses Schema und die zugehörigen Generatoren.
7
+ *
8
+ * Dokumentation: webmate-studio/docs/DESIGN-TOKEN-SCHEMA-V2.md
9
+ */
10
+
11
+ // ─── Farbskala-Generierung ──────────────────────────────────────────────────
12
+
13
+ /**
14
+ * Generiert eine 12-Stufen-Farbskala aus einer Basisfarbe.
15
+ * Stufe 9 = Basisfarbe. Stufen 1-8 heller, 10-12 dunkler.
16
+ *
17
+ * Für Neutral-Töne (niedrige Sättigung) wird die Sättigung automatisch
18
+ * stark gedämpft, damit echte Grautöne entstehen statt verfärbter Pastelltöne.
19
+ */
20
+ export function generateColorScale(baseHex) {
21
+ const rgb = hexToRgb(baseHex);
22
+ if (!rgb) return null;
23
+
24
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
25
+
26
+ // Neutral-Erkennung: Basis mit niedriger Sättigung (<15%)
27
+ // bekommt deutlich reduzierte Sättigung über die gesamte Skala
28
+ const isNeutral = hsl.s < 15;
29
+ const sFactor = isNeutral ? 0.3 : 1; // Sättigung auf 30% für Neutraltöne
30
+
31
+ return {
32
+ 1: hslToHex(hsl.h, Math.max(hsl.s * 0.15 * sFactor, isNeutral ? 0 : 2), 97),
33
+ 2: hslToHex(hsl.h, Math.max(hsl.s * 0.2 * sFactor, isNeutral ? 0.5 : 3), 95),
34
+ 3: hslToHex(hsl.h, Math.max(hsl.s * 0.3 * sFactor, isNeutral ? 1 : 5), 91),
35
+ 4: hslToHex(hsl.h, Math.max(hsl.s * 0.4 * sFactor, isNeutral ? 1.5 : 7), 86),
36
+ 5: hslToHex(hsl.h, Math.max(hsl.s * 0.5 * sFactor, isNeutral ? 2 : 9), 80),
37
+ 6: hslToHex(hsl.h, hsl.s * 0.65 * sFactor, 70),
38
+ 7: hslToHex(hsl.h, hsl.s * 0.8 * sFactor, 58),
39
+ 8: hslToHex(hsl.h, hsl.s * 0.9 * sFactor, 48),
40
+ 9: baseHex,
41
+ 10: hslToHex(hsl.h, Math.min(hsl.s * 1.05 * sFactor, 100), Math.max(hsl.l - 8, 10)),
42
+ 11: hslToHex(hsl.h, Math.min(hsl.s * 0.9 * sFactor, 100), Math.max(hsl.l - 20, 15)),
43
+ 12: hslToHex(hsl.h, Math.min(hsl.s * 0.7 * sFactor, 80), Math.max(hsl.l - 35, 8)),
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Generiert eine invertierte 12-Stufen-Skala für Dark Mode.
49
+ * Stufe 1 = dunkelster Wert, Stufe 12 = hellster.
50
+ */
51
+ export function generateDarkColorScale(baseHex) {
52
+ const rgb = hexToRgb(baseHex);
53
+ if (!rgb) return null;
54
+
55
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
56
+
57
+ // Neutral-Erkennung: gleiche Logik wie generateColorScale
58
+ const isNeutral = hsl.s < 15;
59
+ const sFactor = isNeutral ? 0.3 : 1;
60
+
61
+ // Dark Mode: Basis etwas heller für bessere Sichtbarkeit
62
+ const darkBase = hslToHex(hsl.h, Math.min(hsl.s * 1.1 * sFactor, 100), Math.min(hsl.l + 10, 65));
63
+
64
+ return {
65
+ 1: hslToHex(hsl.h, Math.max(hsl.s * 0.3 * sFactor, isNeutral ? 0 : 5), 8),
66
+ 2: hslToHex(hsl.h, Math.max(hsl.s * 0.35 * sFactor, isNeutral ? 0.5 : 6), 12),
67
+ 3: hslToHex(hsl.h, Math.max(hsl.s * 0.45 * sFactor, isNeutral ? 1 : 8), 18),
68
+ 4: hslToHex(hsl.h, Math.max(hsl.s * 0.55 * sFactor, isNeutral ? 1.5 : 10), 24),
69
+ 5: hslToHex(hsl.h, Math.max(hsl.s * 0.6 * sFactor, isNeutral ? 2 : 12), 30),
70
+ 6: hslToHex(hsl.h, hsl.s * 0.65 * sFactor, 38),
71
+ 7: hslToHex(hsl.h, hsl.s * 0.75 * sFactor, 48),
72
+ 8: hslToHex(hsl.h, hsl.s * 0.85 * sFactor, 58),
73
+ 9: darkBase,
74
+ 10: hslToHex(hsl.h, Math.min(hsl.s * 1.0 * sFactor, 100), Math.min(hsl.l + 20, 75)),
75
+ 11: hslToHex(hsl.h, Math.min(hsl.s * 0.7 * sFactor, 80), Math.min(hsl.l + 35, 85)),
76
+ 12: hslToHex(hsl.h, Math.min(hsl.s * 0.4 * sFactor, 50), Math.min(hsl.l + 50, 95)),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Berechnet die beste Kontrastfarbe für Text auf einer Hintergrundfarbe.
82
+ * Gibt eine Stufe der Neutral-Skala zurück.
83
+ */
84
+ export function calculateOnColor(bgHex, neutralScale) {
85
+ const bgRgb = hexToRgb(bgHex);
86
+ if (!bgRgb) return neutralScale['12']; // Fallback: dunkelster Neutral
87
+
88
+ const bgLuminance = relativeLuminance(bgRgb.r, bgRgb.g, bgRgb.b);
89
+
90
+ // Teste helle und dunkle Neutral-Stufen
91
+ const light = neutralScale['1'] || '#ffffff';
92
+ const dark = neutralScale['12'] || '#111827';
93
+
94
+ const lightRgb = hexToRgb(light);
95
+ const darkRgb = hexToRgb(dark);
96
+
97
+ const lightContrast = contrastRatio(bgLuminance, relativeLuminance(lightRgb.r, lightRgb.g, lightRgb.b));
98
+ const darkContrast = contrastRatio(bgLuminance, relativeLuminance(darkRgb.r, darkRgb.g, darkRgb.b));
99
+
100
+ return lightContrast > darkContrast ? light : dark;
101
+ }
102
+
103
+
104
+ // ─── Farb-Hilfsfunktionen ───────────────────────────────────────────────────
105
+
106
+ function hexToRgb(hex) {
107
+ if (!hex || typeof hex !== 'string') return null;
108
+ hex = hex.replace('#', '');
109
+ if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
110
+ if (hex.length !== 6) return null;
111
+ return {
112
+ r: parseInt(hex.slice(0, 2), 16),
113
+ g: parseInt(hex.slice(2, 4), 16),
114
+ b: parseInt(hex.slice(4, 6), 16)
115
+ };
116
+ }
117
+
118
+ function rgbToHex(r, g, b) {
119
+ return '#' + [r, g, b].map(v => {
120
+ const hex = Math.max(0, Math.min(255, Math.round(v))).toString(16);
121
+ return hex.length === 1 ? '0' + hex : hex;
122
+ }).join('');
123
+ }
124
+
125
+ function rgbToHsl(r, g, b) {
126
+ r /= 255; g /= 255; b /= 255;
127
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
128
+ let h, s;
129
+ const l = (max + min) / 2;
130
+
131
+ if (max === min) {
132
+ h = s = 0;
133
+ } else {
134
+ const d = max - min;
135
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
136
+ switch (max) {
137
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
138
+ case g: h = ((b - r) / d + 2) / 6; break;
139
+ case b: h = ((r - g) / d + 4) / 6; break;
140
+ }
141
+ }
142
+
143
+ return { h: h * 360, s: s * 100, l: l * 100 };
144
+ }
145
+
146
+ function hslToHex(h, s, l) {
147
+ s = Math.max(0, Math.min(100, s));
148
+ l = Math.max(0, Math.min(100, l));
149
+ h = ((h % 360) + 360) % 360;
150
+
151
+ const sNorm = s / 100;
152
+ const lNorm = l / 100;
153
+ const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
154
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
155
+ const m = lNorm - c / 2;
156
+
157
+ let r, g, b;
158
+ if (h < 60) { r = c; g = x; b = 0; }
159
+ else if (h < 120) { r = x; g = c; b = 0; }
160
+ else if (h < 180) { r = 0; g = c; b = x; }
161
+ else if (h < 240) { r = 0; g = x; b = c; }
162
+ else if (h < 300) { r = x; g = 0; b = c; }
163
+ else { r = c; g = 0; b = x; }
164
+
165
+ return rgbToHex((r + m) * 255, (g + m) * 255, (b + m) * 255);
166
+ }
167
+
168
+ function relativeLuminance(r, g, b) {
169
+ const [rs, gs, bs] = [r, g, b].map(c => {
170
+ c = c / 255;
171
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
172
+ });
173
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
174
+ }
175
+
176
+ function contrastRatio(l1, l2) {
177
+ const lighter = Math.max(l1, l2);
178
+ const darker = Math.min(l1, l2);
179
+ return (lighter + 0.05) / (darker + 0.05);
180
+ }
181
+
182
+
183
+ // ─── Farbwelten ─────────────────────────────────────────────────────────────
184
+
185
+ /**
186
+ * Die 8 Farbwelten des V2-Systems.
187
+ * Brand-Welten (primary, secondary, accent) + Status-Welten (success, warning, error, info) + Neutral.
188
+ */
189
+ export const COLOR_WORLDS = ['primary', 'secondary', 'accent', 'success', 'warning', 'error', 'info', 'neutral'];
190
+ export const SEMANTIC_COLOR_WORLDS = ['primary', 'secondary', 'accent', 'success', 'warning', 'error', 'info'];
191
+
192
+ /**
193
+ * Semantische Utility-Defaults: Welche Stufe mappt auf welchen Alias.
194
+ */
195
+ export const DEFAULT_SEMANTIC_MAPPINGS = {
196
+ bg: '9',
197
+ bgHover: '10',
198
+ bgSubtle: '3',
199
+ bgSubtleHover: '4',
200
+ text: '11',
201
+ textOn: null, // Auto-berechnet
202
+ border: '7',
203
+ ring: '8'
204
+ };
205
+
206
+
207
+ // ─── Typografie ─────────────────────────────────────────────────────────────
208
+
209
+ /**
210
+ * Die 4 Stimmen des Typografie-Systems.
211
+ */
212
+ export const TEXT_VOICES = ['display', 'body', 'ui', 'accent'];
213
+
214
+ /**
215
+ * Jede Stimme hat 5 Stufen (1 = größte, 5 = kleinste).
216
+ */
217
+ export const TEXT_LEVELS = [1, 2, 3, 4, 5];
218
+
219
+ /**
220
+ * Semantische Text-Aliase mit Defaults.
221
+ */
222
+ export const DEFAULT_TEXT_ALIASES = {
223
+ hero: 'display-1',
224
+ headline: 'display-3',
225
+ title: 'display-5',
226
+ body: 'body-3',
227
+ label: 'ui-3',
228
+ caption: 'body-4',
229
+ overline: 'accent-4',
230
+ fine: 'body-5'
231
+ };
232
+
233
+
234
+ // ─── Buttons ────────────────────────────────────────────────────────────────
235
+
236
+ /**
237
+ * Die 6 festen Button-Varianten.
238
+ */
239
+ export const BUTTON_VARIANTS = ['filled', 'tonal', 'outline', 'ghost', 'text', 'destructive'];
240
+
241
+ /**
242
+ * Die 3 Button-Größen.
243
+ */
244
+ export const BUTTON_SIZES = ['sm', 'md', 'lg'];
245
+
246
+
247
+ // ─── Default Token-Objekt ───────────────────────────────────────────────────
248
+
249
+ /**
250
+ * Vollständiges Default-Token-Objekt für V2.
251
+ * Wird verwendet wenn kein Design System konfiguriert ist.
252
+ */
253
+ export const defaultDesignTokensV2 = {
254
+ version: 2,
255
+
256
+ // ── Farben ──
257
+ colors: {
258
+ primary: {
259
+ base: '#1E40AF',
260
+ scale: generateColorScale('#1E40AF')
261
+ },
262
+ secondary: {
263
+ base: '#6366F1',
264
+ scale: generateColorScale('#6366F1')
265
+ },
266
+ accent: {
267
+ base: '#8B5CF6',
268
+ scale: generateColorScale('#8B5CF6')
269
+ },
270
+ success: {
271
+ base: '#16A34A',
272
+ scale: generateColorScale('#16A34A')
273
+ },
274
+ warning: {
275
+ base: '#D97706',
276
+ scale: generateColorScale('#D97706')
277
+ },
278
+ error: {
279
+ base: '#DC2626',
280
+ scale: generateColorScale('#DC2626')
281
+ },
282
+ info: {
283
+ base: '#2563EB',
284
+ scale: generateColorScale('#2563EB')
285
+ },
286
+ neutral: {
287
+ base: '#6B7280', // Kühles Grau — gibt dem Design System seinen Charakter
288
+ scale: generateColorScale('#6B7280')
289
+ }
290
+ },
291
+
292
+ semanticMappings: {
293
+ primary: { ...DEFAULT_SEMANTIC_MAPPINGS },
294
+ secondary: { ...DEFAULT_SEMANTIC_MAPPINGS },
295
+ accent: { ...DEFAULT_SEMANTIC_MAPPINGS },
296
+ success: { ...DEFAULT_SEMANTIC_MAPPINGS },
297
+ warning: { ...DEFAULT_SEMANTIC_MAPPINGS },
298
+ error: { ...DEFAULT_SEMANTIC_MAPPINGS },
299
+ info: { ...DEFAULT_SEMANTIC_MAPPINGS }
300
+ },
301
+
302
+ darkMode: {
303
+ enabled: false,
304
+ colors: null, // Wird bei Aktivierung auto-generiert
305
+ semanticMappings: null
306
+ },
307
+
308
+ // ── Typografie ──
309
+ typography: {
310
+ fonts: [
311
+ {
312
+ name: 'Inter',
313
+ source: 'fontsource',
314
+ id: 'fontsource:inter',
315
+ fallback: 'system-ui, -apple-system, sans-serif'
316
+ }
317
+ ],
318
+ monoFont: {
319
+ name: 'JetBrains Mono',
320
+ source: 'fontsource',
321
+ id: 'fontsource:jetbrains-mono',
322
+ fallback: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
323
+ },
324
+ textStyles: {
325
+ display: {
326
+ 1: {
327
+ font: 'Inter',
328
+ fontSize: { base: '2.5rem', md: '3.5rem', lg: '4.5rem' },
329
+ fontWeight: 700,
330
+ lineHeight: { base: '1.15', md: '1.1' },
331
+ letterSpacing: '-0.02em',
332
+ textTransform: 'none'
333
+ },
334
+ 2: {
335
+ font: 'Inter',
336
+ fontSize: { base: '2rem', md: '2.5rem', lg: '3rem' },
337
+ fontWeight: 700,
338
+ lineHeight: '1.15',
339
+ letterSpacing: '-0.015em',
340
+ textTransform: 'none'
341
+ },
342
+ 3: {
343
+ font: 'Inter',
344
+ fontSize: { base: '1.5rem', md: '1.75rem', lg: '2rem' },
345
+ fontWeight: 600,
346
+ lineHeight: '1.2',
347
+ letterSpacing: '-0.01em',
348
+ textTransform: 'none'
349
+ },
350
+ 4: {
351
+ font: 'Inter',
352
+ fontSize: { base: '1.25rem', md: '1.5rem' },
353
+ fontWeight: 600,
354
+ lineHeight: '1.3',
355
+ letterSpacing: '-0.005em',
356
+ textTransform: 'none'
357
+ },
358
+ 5: {
359
+ font: 'Inter',
360
+ fontSize: { base: '1.125rem', md: '1.25rem' },
361
+ fontWeight: 600,
362
+ lineHeight: '1.3',
363
+ letterSpacing: '0em',
364
+ textTransform: 'none'
365
+ }
366
+ },
367
+ body: {
368
+ 1: {
369
+ font: 'Inter',
370
+ fontSize: { base: '1.125rem', md: '1.25rem' },
371
+ fontWeight: 400,
372
+ lineHeight: '1.6',
373
+ letterSpacing: '0em',
374
+ textTransform: 'none'
375
+ },
376
+ 2: {
377
+ font: 'Inter',
378
+ fontSize: '1.0625rem',
379
+ fontWeight: 400,
380
+ lineHeight: '1.6',
381
+ letterSpacing: '0em',
382
+ textTransform: 'none'
383
+ },
384
+ 3: {
385
+ font: 'Inter',
386
+ fontSize: '1rem',
387
+ fontWeight: 400,
388
+ lineHeight: '1.5',
389
+ letterSpacing: '0em',
390
+ textTransform: 'none'
391
+ },
392
+ 4: {
393
+ font: 'Inter',
394
+ fontSize: '0.875rem',
395
+ fontWeight: 400,
396
+ lineHeight: '1.5',
397
+ letterSpacing: '0em',
398
+ textTransform: 'none'
399
+ },
400
+ 5: {
401
+ font: 'Inter',
402
+ fontSize: '0.75rem',
403
+ fontWeight: 400,
404
+ lineHeight: '1.5',
405
+ letterSpacing: '0em',
406
+ textTransform: 'none'
407
+ }
408
+ },
409
+ ui: {
410
+ 1: {
411
+ font: 'Inter',
412
+ fontSize: '1.125rem',
413
+ fontWeight: 500,
414
+ lineHeight: '1.4',
415
+ letterSpacing: '0em',
416
+ textTransform: 'none'
417
+ },
418
+ 2: {
419
+ font: 'Inter',
420
+ fontSize: '1rem',
421
+ fontWeight: 500,
422
+ lineHeight: '1.4',
423
+ letterSpacing: '0em',
424
+ textTransform: 'none'
425
+ },
426
+ 3: {
427
+ font: 'Inter',
428
+ fontSize: '0.875rem',
429
+ fontWeight: 500,
430
+ lineHeight: '1.4',
431
+ letterSpacing: '0em',
432
+ textTransform: 'none'
433
+ },
434
+ 4: {
435
+ font: 'Inter',
436
+ fontSize: '0.8125rem',
437
+ fontWeight: 500,
438
+ lineHeight: '1.4',
439
+ letterSpacing: '0.01em',
440
+ textTransform: 'none'
441
+ },
442
+ 5: {
443
+ font: 'Inter',
444
+ fontSize: '0.6875rem',
445
+ fontWeight: 500,
446
+ lineHeight: '1.4',
447
+ letterSpacing: '0.02em',
448
+ textTransform: 'none'
449
+ }
450
+ },
451
+ accent: {
452
+ 1: {
453
+ font: 'Inter',
454
+ fontSize: { base: '1.5rem', md: '1.75rem', lg: '2rem' },
455
+ fontWeight: 400,
456
+ lineHeight: '1.5',
457
+ letterSpacing: '0em',
458
+ textTransform: 'none'
459
+ },
460
+ 2: {
461
+ font: 'Inter',
462
+ fontSize: { base: '1.25rem', md: '1.5rem' },
463
+ fontWeight: 400,
464
+ lineHeight: '1.4',
465
+ letterSpacing: '0em',
466
+ textTransform: 'none'
467
+ },
468
+ 3: {
469
+ font: 'Inter',
470
+ fontSize: '1.125rem',
471
+ fontWeight: 400,
472
+ lineHeight: '1.4',
473
+ letterSpacing: '0em',
474
+ textTransform: 'none'
475
+ },
476
+ 4: {
477
+ font: 'Inter',
478
+ fontSize: '0.8125rem',
479
+ fontWeight: 600,
480
+ lineHeight: '1.4',
481
+ letterSpacing: '0.08em',
482
+ textTransform: 'uppercase'
483
+ },
484
+ 5: {
485
+ font: 'Inter',
486
+ fontSize: '0.6875rem',
487
+ fontWeight: 600,
488
+ lineHeight: '1.4',
489
+ letterSpacing: '0.1em',
490
+ textTransform: 'uppercase'
491
+ }
492
+ }
493
+ },
494
+ aliases: { ...DEFAULT_TEXT_ALIASES }
495
+ },
496
+
497
+ // ── Buttons ──
498
+ buttons: {
499
+ sizes: {
500
+ sm: {
501
+ paddingX: '0.75rem',
502
+ paddingY: '0.375rem',
503
+ fontSize: '0.8125rem',
504
+ fontWeight: 500,
505
+ lineHeight: '1.4',
506
+ letterSpacing: '0.01em',
507
+ borderRadius: '0.375rem',
508
+ minHeight: '2rem',
509
+ gap: '0.375rem',
510
+ iconSize: '1rem'
511
+ },
512
+ md: {
513
+ paddingX: '1.25rem',
514
+ paddingY: '0.625rem',
515
+ fontSize: '0.875rem',
516
+ fontWeight: 500,
517
+ lineHeight: '1.4',
518
+ letterSpacing: '0em',
519
+ borderRadius: '0.5rem',
520
+ minHeight: '2.5rem',
521
+ gap: '0.5rem',
522
+ iconSize: '1.25rem'
523
+ },
524
+ lg: {
525
+ paddingX: '1.75rem',
526
+ paddingY: '0.875rem',
527
+ fontSize: '1rem',
528
+ fontWeight: 600,
529
+ lineHeight: '1.4',
530
+ letterSpacing: '0em',
531
+ borderRadius: '0.625rem',
532
+ minHeight: '3rem',
533
+ gap: '0.625rem',
534
+ iconSize: '1.5rem'
535
+ }
536
+ },
537
+ variants: {
538
+ filled: {
539
+ normal: {
540
+ bg: 'primary-9',
541
+ bgHover: 'primary-10',
542
+ text: 'on-primary',
543
+ textHover: 'on-primary',
544
+ border: 'none',
545
+ borderHover: 'none',
546
+ shadow: 'none',
547
+ shadowHover: 'none',
548
+ focusRingColor: 'primary-8',
549
+ customCSS: null
550
+ },
551
+ onSurface: {
552
+ bg: 'neutral-1',
553
+ bgHover: 'neutral-2',
554
+ text: 'neutral-12',
555
+ textHover: 'neutral-12',
556
+ border: 'none',
557
+ borderHover: 'none',
558
+ customCSS: null
559
+ }
560
+ },
561
+ tonal: {
562
+ normal: {
563
+ bg: 'primary-3',
564
+ bgHover: 'primary-4',
565
+ text: 'primary-11',
566
+ textHover: 'primary-12',
567
+ border: 'none',
568
+ borderHover: 'none',
569
+ shadow: 'none',
570
+ shadowHover: 'none',
571
+ focusRingColor: 'primary-8',
572
+ customCSS: null
573
+ },
574
+ onSurface: {
575
+ bg: 'neutral-1/15',
576
+ bgHover: 'neutral-1/25',
577
+ text: 'on-primary',
578
+ textHover: 'on-primary',
579
+ border: 'none',
580
+ borderHover: 'none',
581
+ customCSS: null
582
+ }
583
+ },
584
+ outline: {
585
+ normal: {
586
+ bg: 'transparent',
587
+ bgHover: 'primary-3',
588
+ text: 'primary-11',
589
+ textHover: 'primary-12',
590
+ border: 'primary-7',
591
+ borderHover: 'primary-8',
592
+ shadow: 'none',
593
+ shadowHover: 'none',
594
+ focusRingColor: 'primary-8',
595
+ customCSS: null
596
+ },
597
+ onSurface: {
598
+ bg: 'transparent',
599
+ bgHover: 'neutral-1/15',
600
+ text: 'on-primary',
601
+ textHover: 'on-primary',
602
+ border: 'on-primary/40',
603
+ borderHover: 'on-primary/70',
604
+ customCSS: null
605
+ }
606
+ },
607
+ ghost: {
608
+ normal: {
609
+ bg: 'transparent',
610
+ bgHover: 'neutral-3',
611
+ text: 'primary-11',
612
+ textHover: 'primary-12',
613
+ border: 'none',
614
+ borderHover: 'none',
615
+ shadow: 'none',
616
+ shadowHover: 'none',
617
+ focusRingColor: 'primary-8',
618
+ customCSS: null
619
+ },
620
+ onSurface: {
621
+ bg: 'transparent',
622
+ bgHover: 'neutral-1/15',
623
+ text: 'on-primary',
624
+ textHover: 'on-primary',
625
+ border: 'none',
626
+ borderHover: 'none',
627
+ customCSS: null
628
+ }
629
+ },
630
+ text: {
631
+ normal: {
632
+ bg: 'transparent',
633
+ bgHover: 'transparent',
634
+ text: 'primary-11',
635
+ textHover: 'primary-12',
636
+ border: 'none',
637
+ borderHover: 'none',
638
+ shadow: 'none',
639
+ shadowHover: 'none',
640
+ textDecoration: 'none',
641
+ textDecorationHover: 'underline',
642
+ focusRingColor: 'primary-8',
643
+ customCSS: null
644
+ },
645
+ onSurface: {
646
+ bg: 'transparent',
647
+ bgHover: 'transparent',
648
+ text: 'on-primary',
649
+ textHover: 'on-primary',
650
+ textDecoration: 'none',
651
+ textDecorationHover: 'underline',
652
+ customCSS: null
653
+ }
654
+ },
655
+ destructive: {
656
+ normal: {
657
+ bg: 'error-9',
658
+ bgHover: 'error-10',
659
+ text: 'on-error',
660
+ textHover: 'on-error',
661
+ border: 'none',
662
+ borderHover: 'none',
663
+ shadow: 'none',
664
+ shadowHover: 'none',
665
+ focusRingColor: 'error-8',
666
+ customCSS: null
667
+ },
668
+ onSurface: {
669
+ bg: 'error-9',
670
+ bgHover: 'error-10',
671
+ text: 'on-error',
672
+ textHover: 'on-error',
673
+ border: 'none',
674
+ borderHover: 'none',
675
+ customCSS: null
676
+ }
677
+ }
678
+ },
679
+ global: {
680
+ transition: 'all 150ms ease',
681
+ focusRingWidth: '2px',
682
+ focusRingOffset: '2px',
683
+ disabledOpacity: 0.5,
684
+ onSurfaceFocusRingColor: 'on-primary/50'
685
+ }
686
+ },
687
+
688
+ // ── Spacing ──
689
+ spacing: {
690
+ component: {
691
+ base: '3rem',
692
+ sm: '3rem',
693
+ md: '3.5rem',
694
+ lg: '4rem',
695
+ xl: '4.5rem',
696
+ '2xl': '5rem'
697
+ }
698
+ },
699
+
700
+ // ── Layout ──
701
+ borderRadius: '0.25rem',
702
+ borderWidth: '1px',
703
+
704
+ container: {
705
+ sm: '640px',
706
+ md: '768px',
707
+ lg: '1024px',
708
+ xl: '1280px',
709
+ '2xl': '1536px'
710
+ },
711
+
712
+ breakpoints: {
713
+ sm: '640px',
714
+ md: '768px',
715
+ lg: '1024px',
716
+ xl: '1280px',
717
+ '2xl': '1536px'
718
+ }
719
+ };
720
+
721
+
722
+ // ─── Erkennung ──────────────────────────────────────────────────────────────
723
+
724
+ /**
725
+ * Prüft ob ein Token-Objekt V2-Format hat.
726
+ */
727
+ export function isV2Format(tokens) {
728
+ return tokens && tokens.version === 2;
729
+ }
730
+
731
+ /**
732
+ * Prüft ob ein Token-Objekt V1-Format hat.
733
+ */
734
+ export function isV1Format(tokens) {
735
+ if (!tokens) return false;
736
+ // V1 hat flache Farben (tokens.colors.primary ist ein String)
737
+ return tokens.colors && typeof tokens.colors.primary === 'string';
738
+ }