@webmate-studio/builder 0.2.135 → 0.2.137

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmate-studio/builder",
3
- "version": "0.2.135",
3
+ "version": "0.2.137",
4
4
  "type": "module",
5
5
  "description": "Webmate Studio Component Builder",
6
6
  "keywords": [
@@ -8,73 +8,117 @@
8
8
  * Dokumentation: webmate-studio/docs/DESIGN-TOKEN-SCHEMA-V2.md
9
9
  */
10
10
 
11
- // ─── Farbskala-Generierung ──────────────────────────────────────────────────
11
+ // ─── OKLCH-Farbskala-Generierung ────────────────────────────────────────────
12
12
 
13
13
  /**
14
- * Generiert eine 12-Stufen-Farbskala aus einer Basisfarbe.
14
+ * Preset-Kurven für die Chroma-Verteilung über die 12 Stufen.
15
+ * stdDev = Breite der Gaußkurve (wie weit Sättigung von der Base ausstrahlt)
16
+ * chromaScale = globaler Sättigungsmultiplikator
17
+ */
18
+ export const COLOR_SCALE_PRESETS = {
19
+ vivid: { label: 'Leuchtend', stdDev: 28, chromaScale: 1.0 },
20
+ natural: { label: 'Natürlich', stdDev: 20, chromaScale: 0.85 },
21
+ soft: { label: 'Pastellig', stdDev: 13, chromaScale: 0.7 },
22
+ muted: { label: 'Gedämpft', stdDev: 18, chromaScale: 0.5 },
23
+ };
24
+
25
+ /**
26
+ * Generiert eine 12-Stufen-Farbskala aus einer Basisfarbe im OKLCH-Farbraum.
15
27
  * Stufe 9 = Basisfarbe. Stufen 1-8 heller, 10-12 dunkler.
16
28
  *
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.
29
+ * Verwendet OKLCH für perceptuell gleichmäßige Helligkeitsverteilung
30
+ * garantiert monoton abnehmende Helligkeit von Stufe 1 bis 12.
31
+ *
32
+ * @param {string} baseHex - Basisfarbe als Hex-String
33
+ * @param {object} [curve] - Optionale Kurvenparameter: { preset: 'natural' } oder { preset: 'custom', stdDev, chromaScale }
19
34
  */
20
- export function generateColorScale(baseHex) {
21
- const rgb = hexToRgb(baseHex);
22
- if (!rgb) return null;
35
+ export function generateColorScale(baseHex, curve) {
36
+ const base = hexToOklch(baseHex);
37
+ if (!base) return null;
38
+
39
+ // Kurvenparameter bestimmen
40
+ const presetName = curve?.preset || 'natural';
41
+ const preset = COLOR_SCALE_PRESETS[presetName];
42
+ const stdDev = curve?.preset === 'custom' ? (curve.stdDev ?? 20) : (preset?.stdDev ?? 20);
43
+ const chromaScale = curve?.preset === 'custom' ? (curve.chromaScale ?? 0.85) : (preset?.chromaScale ?? 0.85);
44
+
45
+ // Neutral-Erkennung: Basis mit niedriger Chroma
46
+ const isNeutral = base.c < 0.04;
47
+
48
+ // Lightness-Rampe adaptiv an Base-Lightness
49
+ const L = buildLightnessRamp(base.l);
50
+
51
+ // Wurde die Base-Lightness geclampt? (extrem helle/dunkle Farben)
52
+ const baseClamped = Math.abs(L[8] - base.l) > 0.01;
53
+
54
+ const scale = {};
55
+ for (let i = 0; i < 12; i++) {
56
+ const step = i + 1;
57
+ if (step === 9 && !baseClamped) {
58
+ // Step 9 = exakte Base-Farbe (wenn nicht geclampt)
59
+ scale[step] = baseHex;
60
+ continue;
61
+ }
23
62
 
24
- const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
63
+ // Chroma über Gaußkurve berechnen
64
+ let c;
65
+ if (isNeutral) {
66
+ c = base.c * 0.3;
67
+ } else if (step === 9) {
68
+ // Geclampt: volle Base-Chroma bei korrigierter Lightness
69
+ c = base.c;
70
+ } else {
71
+ c = computeChroma(L[i], L[8], base.c, stdDev, chromaScale);
72
+ }
25
73
 
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
74
+ const mapped = gamutMapOklch(L[i], c, base.h);
75
+ scale[step] = oklchToHex(mapped.l, mapped.c, mapped.h);
76
+ }
30
77
 
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
- };
78
+ // Neutral Step 1 = reines Weiß
79
+ if (isNeutral) {
80
+ scale[1] = '#ffffff';
81
+ }
82
+
83
+ return scale;
45
84
  }
46
85
 
47
86
  /**
48
- * Generiert eine invertierte 12-Stufen-Skala für Dark Mode.
87
+ * Generiert eine invertierte 12-Stufen-Skala für Dark Mode im OKLCH-Farbraum.
49
88
  * Stufe 1 = dunkelster Wert, Stufe 12 = hellster.
50
89
  */
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;
90
+ export function generateDarkColorScale(baseHex, curve) {
91
+ const base = hexToOklch(baseHex);
92
+ if (!base) return null;
93
+
94
+ const presetName = curve?.preset || 'natural';
95
+ const preset = COLOR_SCALE_PRESETS[presetName];
96
+ const stdDev = curve?.preset === 'custom' ? (curve.stdDev ?? 20) : (preset?.stdDev ?? 20);
97
+ const chromaScale = curve?.preset === 'custom' ? (curve.chromaScale ?? 0.85) : (preset?.chromaScale ?? 0.85);
98
+
99
+ const isNeutral = base.c < 0.04;
100
+
101
+ // Dark Mode: Base etwas heller für bessere Sichtbarkeit
102
+ const darkBaseL = Math.min(base.l + 0.08, 0.65);
103
+
104
+ // Lightness-Rampe adaptiv (invertiert für Dark Mode)
105
+ const L = buildDarkLightnessRamp(darkBaseL);
106
+
107
+ const scale = {};
108
+ for (let i = 0; i < 12; i++) {
109
+ const step = i + 1;
110
+ let c;
111
+ if (isNeutral) {
112
+ c = base.c * 0.3;
113
+ } else {
114
+ c = computeChroma(L[i], L[8], base.c, stdDev, chromaScale);
115
+ }
60
116
 
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));
117
+ const mapped = gamutMapOklch(L[i], c, base.h);
118
+ scale[step] = oklchToHex(mapped.l, mapped.c, mapped.h);
119
+ }
63
120
 
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
- };
121
+ return scale;
78
122
  }
79
123
 
80
124
  /**
@@ -165,6 +209,274 @@ function hslToHex(h, s, l) {
165
209
  return rgbToHex((r + m) * 255, (g + m) * 255, (b + m) * 255);
166
210
  }
167
211
 
212
+ // ─── OKLCH-Hilfsfunktionen ──────────────────────────────────────────────────
213
+
214
+ /**
215
+ * Baut eine strikt monoton fallende Lightness-Rampe für 12 Stufen.
216
+ * Steps 1-8 werden zwischen topL und baseL verteilt (Ease-Out-Kurve).
217
+ * Steps 10-12 werden zwischen baseL und bottomL verteilt.
218
+ * Step 9 = baseL (exakt).
219
+ *
220
+ * Funktioniert auch bei extremen Base-Werten (sehr hell/dunkel):
221
+ * - Die Rampe wird auf den verfügbaren Bereich komprimiert
222
+ * - Monotonie ist durch die Konstruktion garantiert
223
+ */
224
+ function buildLightnessRamp(baseL) {
225
+ const topL = 0.985; // Step 1: fast Weiß
226
+ const bottomL = 0.25; // Step 12: dunkelster Wert
227
+
228
+ // Base clampen: min. 0.30 damit 3 dunkle Stufen Platz haben,
229
+ // max. 0.80 damit 8 helle Stufen genug Abstand in sRGB haben
230
+ const safeBaseL = Math.max(0.30, Math.min(0.80, baseL));
231
+
232
+ // Steps 1-8: Von topL nach safeBaseL + Lücke
233
+ // Die Lücke stellt sicher, dass Step 8 immer heller als Step 9 ist
234
+ const gap = Math.max(0.02, (topL - safeBaseL) * 0.05);
235
+ const step8Target = safeBaseL + gap;
236
+ const range = topL - step8Target;
237
+
238
+ // Feste Verteilungs-Gewichte: Steps 1-2 bleiben nahe Weiß,
239
+ // dann beschleunigter Abstieg zur Base. Inspiriert von Radix' Lightness-Verteilung.
240
+ const weights = [0.0, 0.04, 0.10, 0.20, 0.34, 0.50, 0.70, 0.92];
241
+ const lightSteps = weights.map(w => topL - w * range);
242
+
243
+ // Steps 10-12: nach unten von Base weg
244
+ const darkRange = safeBaseL - bottomL;
245
+ const step10L = safeBaseL - darkRange * 0.12;
246
+ const step11L = safeBaseL - darkRange * 0.55;
247
+ const step12L = bottomL;
248
+
249
+ const ramp = [
250
+ ...lightSteps, // Steps 1-8
251
+ safeBaseL, // Step 9
252
+ step10L, // Step 10
253
+ step11L, // Step 11
254
+ step12L, // Step 12
255
+ ];
256
+
257
+ // Finale Sicherheit: strikt monoton fallend
258
+ for (let i = 1; i < ramp.length; i++) {
259
+ if (ramp[i] >= ramp[i - 1]) {
260
+ ramp[i] = ramp[i - 1] - 0.003;
261
+ }
262
+ }
263
+
264
+ return ramp;
265
+ }
266
+
267
+ /**
268
+ * Baut eine strikt monoton steigende Lightness-Rampe für Dark Mode.
269
+ * Steps 1-8: dunkel → darkBaseL, Steps 10-12: darkBaseL → hell.
270
+ */
271
+ function buildDarkLightnessRamp(darkBaseL) {
272
+ const bottomL = 0.14;
273
+ const topL = 0.94;
274
+
275
+ const safeBaseL = Math.max(0.22, Math.min(0.85, darkBaseL));
276
+
277
+ const gap = Math.max(0.02, (safeBaseL - bottomL) * 0.05);
278
+ const step8Target = safeBaseL - gap;
279
+ const range = step8Target - bottomL;
280
+
281
+ const weights = [0.0, 0.04, 0.10, 0.20, 0.34, 0.50, 0.70, 0.92];
282
+ const darkSteps = weights.map(w => bottomL + w * range);
283
+
284
+ const lightRange = topL - safeBaseL;
285
+ const step10L = safeBaseL + lightRange * 0.15;
286
+ const step11L = safeBaseL + lightRange * 0.55;
287
+ const step12L = topL;
288
+
289
+ const ramp = [
290
+ ...darkSteps,
291
+ safeBaseL,
292
+ step10L,
293
+ step11L,
294
+ step12L,
295
+ ];
296
+
297
+ for (let i = 1; i < ramp.length; i++) {
298
+ if (ramp[i] <= ramp[i - 1]) {
299
+ ramp[i] = ramp[i - 1] + 0.003;
300
+ }
301
+ }
302
+
303
+ return ramp;
304
+ }
305
+
306
+ /**
307
+ * Chroma-Berechnung für eine Stufe: Gaußkurve × Lightness-Begrenzung.
308
+ *
309
+ * Die Gaußkurve bestimmt den gewünschten Chroma-Anteil basierend auf der
310
+ * Distanz zur Base-Lightness. Zusätzlich wird die Chroma durch einen
311
+ * Lightness-Faktor begrenzt: Je näher an Weiß (L=1) oder Schwarz (L=0),
312
+ * desto weniger Chroma ist physisch/visuell sinnvoll.
313
+ *
314
+ * Das sorgt dafür, dass Steps 1-2 fast weiß und Steps 11-12 fast schwarz
315
+ * sind — wie bei Radix Colors.
316
+ */
317
+ function computeChroma(stepL, baseL, baseC, stdDev, chromaScale) {
318
+ // Gaußkurve: Peak bei baseL
319
+ const diff = baseL - stepL;
320
+ const gaussian = Math.exp((-25 / stdDev) * diff * diff);
321
+
322
+ // Lightness-Begrenzung: Wie viel Chroma ist bei dieser Lightness sinnvoll?
323
+ // Bei L=0 oder L=1 → 0, bei L≈0.5-0.7 → Maximum
324
+ // Formel: sin-artige Kurve die bei L=0 und L=1 null ist
325
+ const lightnessLimit = Math.pow(Math.sin(stepL * Math.PI), 1.2);
326
+
327
+ return baseC * gaussian * lightnessLimit * chromaScale;
328
+ }
329
+
330
+
331
+ /**
332
+ * Hex → OKLCH Konvertierung.
333
+ * Pfad: Hex → sRGB → Linear RGB → OKLab → OKLCH
334
+ */
335
+ function hexToOklch(hex) {
336
+ const rgb = hexToRgb(hex);
337
+ if (!rgb) return null;
338
+
339
+ // sRGB → Linear RGB (Gamma-Dekodierung)
340
+ const lr = srgbToLinear(rgb.r / 255);
341
+ const lg = srgbToLinear(rgb.g / 255);
342
+ const lb = srgbToLinear(rgb.b / 255);
343
+
344
+ // Linear RGB → OKLab
345
+ const lab = linearRgbToOklab(lr, lg, lb);
346
+
347
+ // OKLab → OKLCH
348
+ const c = Math.sqrt(lab.a * lab.a + lab.b * lab.b);
349
+ let h = Math.atan2(lab.b, lab.a) * 180 / Math.PI;
350
+ if (h < 0) h += 360;
351
+
352
+ return { l: lab.l, c, h };
353
+ }
354
+
355
+ /**
356
+ * OKLCH → Hex Konvertierung.
357
+ * Pfad: OKLCH → OKLab → Linear RGB → sRGB → Hex
358
+ */
359
+ function oklchToHex(l, c, h) {
360
+ // OKLCH → OKLab
361
+ const hRad = h * Math.PI / 180;
362
+ const a = c * Math.cos(hRad);
363
+ const b = c * Math.sin(hRad);
364
+
365
+ // OKLab → Linear RGB
366
+ const lin = oklabToLinearRgb(l, a, b);
367
+
368
+ // Linear RGB → sRGB (Gamma-Kodierung)
369
+ const sr = Math.round(linearToSrgb(lin.r) * 255);
370
+ const sg = Math.round(linearToSrgb(lin.g) * 255);
371
+ const sb = Math.round(linearToSrgb(lin.b) * 255);
372
+
373
+ return rgbToHex(
374
+ Math.max(0, Math.min(255, sr)),
375
+ Math.max(0, Math.min(255, sg)),
376
+ Math.max(0, Math.min(255, sb))
377
+ );
378
+ }
379
+
380
+ /**
381
+ * Gamut-Mapping: Reduziert Chroma bis die Farbe in sRGB darstellbar ist.
382
+ * Behält Lightness und Hue bei.
383
+ */
384
+ function gamutMapOklch(l, c, h) {
385
+ // Triviale Fälle
386
+ if (c <= 0) return { l, c: 0, h };
387
+ if (isInSrgbGamut(l, c, h)) return { l, c, h };
388
+
389
+ // Binary Search: Chroma reduzieren
390
+ let low = 0;
391
+ let high = c;
392
+ for (let i = 0; i < 20; i++) {
393
+ const mid = (low + high) / 2;
394
+ if (isInSrgbGamut(l, mid, h)) {
395
+ low = mid;
396
+ } else {
397
+ high = mid;
398
+ }
399
+ }
400
+ return { l, c: low, h };
401
+ }
402
+
403
+ /**
404
+ * Prüft ob eine OKLCH-Farbe innerhalb des sRGB-Gamuts liegt.
405
+ */
406
+ function isInSrgbGamut(l, c, h) {
407
+ const hRad = h * Math.PI / 180;
408
+ const a = c * Math.cos(hRad);
409
+ const b = c * Math.sin(hRad);
410
+ const lin = oklabToLinearRgb(l, a, b);
411
+ const eps = -0.001; // Kleine Toleranz für Rundungsfehler
412
+ return lin.r >= eps && lin.r <= 1.001 &&
413
+ lin.g >= eps && lin.g <= 1.001 &&
414
+ lin.b >= eps && lin.b <= 1.001;
415
+ }
416
+
417
+ /**
418
+ * Linear RGB → OKLab (Bjorn Ottosson, 2020)
419
+ */
420
+ function linearRgbToOklab(r, g, b) {
421
+ // Linear RGB → LMS (via M1 Matrix)
422
+ const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
423
+ const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
424
+ const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
425
+
426
+ // Kubikwurzel
427
+ const l = Math.cbrt(l_);
428
+ const m = Math.cbrt(m_);
429
+ const s = Math.cbrt(s_);
430
+
431
+ // LMS → OKLab (via M2 Matrix)
432
+ return {
433
+ l: 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
434
+ a: 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
435
+ b: 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
436
+ };
437
+ }
438
+
439
+ /**
440
+ * OKLab → Linear RGB (Inverse von linearRgbToOklab)
441
+ */
442
+ function oklabToLinearRgb(L, a, b) {
443
+ // OKLab → LMS (inverse M2)
444
+ const l = L + 0.3963377774 * a + 0.2158037573 * b;
445
+ const m = L - 0.1055613458 * a - 0.0638541728 * b;
446
+ const s = L - 0.0894841775 * a - 1.2914855480 * b;
447
+
448
+ // LMS kubisch
449
+ const l3 = l * l * l;
450
+ const m3 = m * m * m;
451
+ const s3 = s * s * s;
452
+
453
+ // LMS → Linear RGB (inverse M1)
454
+ return {
455
+ r: 4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3,
456
+ g: -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3,
457
+ b: -0.0041960863 * l3 - 0.7034186147 * m3 + 1.7076147010 * s3,
458
+ };
459
+ }
460
+
461
+ /**
462
+ * sRGB Gamma-Dekodierung (sRGB → Linear)
463
+ */
464
+ function srgbToLinear(c) {
465
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
466
+ }
467
+
468
+ /**
469
+ * sRGB Gamma-Kodierung (Linear → sRGB)
470
+ */
471
+ function linearToSrgb(c) {
472
+ if (c <= 0) return 0;
473
+ if (c >= 1) return 1;
474
+ return c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
475
+ }
476
+
477
+
478
+ // ─── Legacy Farb-Hilfsfunktionen (HSL, Luminanz) ───────────────────────────
479
+
168
480
  function relativeLuminance(r, g, b) {
169
481
  const [rs, gs, bs] = [r, g, b].map(c => {
170
482
  c = c / 255;
@@ -187,6 +499,8 @@ function contrastRatio(l1, l2) {
187
499
  * Brand-Welten (primary, secondary, accent) + Status-Welten (success, warning, error, info) + Neutral.
188
500
  */
189
501
  export const COLOR_WORLDS = ['primary', 'secondary', 'accent', 'success', 'warning', 'error', 'info', 'neutral'];
502
+ export const BRAND_COLOR_WORLDS = ['primary', 'secondary', 'accent', 'neutral'];
503
+ export const STATUS_COLOR_WORLDS = ['success', 'warning', 'error', 'info'];
190
504
  export const SEMANTIC_COLOR_WORLDS = ['primary', 'secondary', 'accent', 'success', 'warning', 'error', 'info'];
191
505
 
192
506
  /**
@@ -296,34 +610,42 @@ export const defaultDesignTokensV2 = {
296
610
  colors: {
297
611
  primary: {
298
612
  base: '#1E40AF',
613
+ curve: { preset: 'natural' },
299
614
  scale: generateColorScale('#1E40AF')
300
615
  },
301
616
  secondary: {
302
617
  base: '#6366F1',
618
+ curve: { preset: 'natural' },
303
619
  scale: generateColorScale('#6366F1')
304
620
  },
305
621
  accent: {
306
622
  base: '#8B5CF6',
623
+ curve: { preset: 'natural' },
307
624
  scale: generateColorScale('#8B5CF6')
308
625
  },
309
626
  success: {
310
627
  base: '#16A34A',
628
+ curve: { preset: 'natural' },
311
629
  scale: generateColorScale('#16A34A')
312
630
  },
313
631
  warning: {
314
632
  base: '#D97706',
633
+ curve: { preset: 'natural' },
315
634
  scale: generateColorScale('#D97706')
316
635
  },
317
636
  error: {
318
637
  base: '#DC2626',
638
+ curve: { preset: 'natural' },
319
639
  scale: generateColorScale('#DC2626')
320
640
  },
321
641
  info: {
322
642
  base: '#2563EB',
643
+ curve: { preset: 'natural' },
323
644
  scale: generateColorScale('#2563EB')
324
645
  },
325
646
  neutral: {
326
- base: '#6B7280', // Kühles Grau — gibt dem Design System seinen Charakter
647
+ base: '#6B7280',
648
+ curve: { preset: 'natural' },
327
649
  scale: generateColorScale('#6B7280')
328
650
  }
329
651
  },