@webmate-studio/builder 0.2.130 → 0.2.132

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