@tenphi/glaze 0.0.0-snapshot.c84faa6 → 0.0.0-snapshot.cdd8acc

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/README.md CHANGED
@@ -22,6 +22,7 @@ Glaze generates robust **light**, **dark**, and **high-contrast** color schemes
22
22
 
23
23
  - **OKHSL color space** — perceptually uniform hue and saturation
24
24
  - **WCAG 2 contrast solving** — automatic lightness adjustment to meet AA/AAA targets
25
+ - **Mix colors** — blend two colors with OKHSL or sRGB interpolation, opaque or transparent, with optional contrast solving
25
26
  - **Shadow colors** — OKHSL-native shadow computation with automatic alpha, fg/bg tinting, and per-scheme adaptation
26
27
  - **Light + Dark + High-Contrast** — all schemes from one definition
27
28
  - **Per-color hue override** — absolute or relative hue shifts within a theme
@@ -69,9 +70,9 @@ const danger = primary.extend({ hue: 23 });
69
70
  const success = primary.extend({ hue: 157 });
70
71
 
71
72
  // Compose into a palette and export
72
- const palette = glaze.palette({ primary, danger, success });
73
- const tokens = palette.tokens({ prefix: true });
74
- // → { light: { 'primary-surface': 'okhsl(...)', ... }, dark: { 'primary-surface': 'okhsl(...)', ... } }
73
+ const palette = glaze.palette({ primary, danger, success }, { primary: 'primary' });
74
+ const tokens = palette.tokens();
75
+ // → { light: { 'primary-surface': 'okhsl(...)', 'surface': 'okhsl(...)', ... }, dark: { ... } }
75
76
  ```
76
77
 
77
78
  ## Core Concepts
@@ -193,6 +194,8 @@ A single value applies to both modes. All control is local and explicit.
193
194
  'muted': { base: 'surface', lightness: ['-35', '-50'], contrast: ['AA-large', 'AA'] }
194
195
  ```
195
196
 
197
+ **Full lightness spectrum in HC mode:** In high-contrast variants, the `lightLightness` and `darkLightness` window constraints are bypassed entirely. Colors can reach the full 0–100 lightness range, maximizing perceivable contrast. Normal (non-HC) variants continue to use the configured windows.
198
+
196
199
  ## Theme Color Management
197
200
 
198
201
  ### Adding Colors
@@ -262,13 +265,70 @@ Create a single color token without a full theme:
262
265
  ```ts
263
266
  const accent = glaze.color({ hue: 280, saturation: 80, lightness: 52, mode: 'fixed' });
264
267
 
265
- accent.resolve(); // → ResolvedColor with light/dark/lightContrast/darkContrast
266
- accent.token(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (tasty format)
267
- accent.tasty(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (same as token)
268
- accent.json(); // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
268
+ accent.resolve(); // → ResolvedColor with light/dark/lightContrast/darkContrast
269
+ accent.token(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (tasty format)
270
+ accent.tasty(); // → { '': 'okhsl(...)', '@dark': 'okhsl(...)' } (same as token)
271
+ accent.json(); // → { light: 'okhsl(...)', dark: 'okhsl(...)' }
272
+ accent.css({ name: 'accent' });
273
+ // → { light: '--accent-color: rgb(...);', dark: '--accent-color: rgb(...);', ... }
269
274
  ```
270
275
 
271
- Standalone colors are always root colors (no `base`/`contrast`).
276
+ ### Value Shorthand
277
+
278
+ The first argument can also be a color value — Glaze extracts the seed
279
+ hue/saturation/lightness for you. All forms support the same exports
280
+ (`resolve / token / tasty / json / css`):
281
+
282
+ ```ts
283
+ // Hex (3 or 6 digits)
284
+ glaze.color('#26fcb2').tasty();
285
+
286
+ // CSS color functions Glaze itself emits (`rgb()`, `hsl()`, `okhsl()`, `oklch()`)
287
+ // — anything from theme.tasty()/json()/css() round-trips back in.
288
+ glaze.color('rgb(38 252 178)').tasty();
289
+ glaze.color('hsl(152 97% 57%)').tasty();
290
+ glaze.color('okhsl(152 95% 74%)').tasty();
291
+ glaze.color('oklch(0.85 0.18 152)').tasty();
292
+
293
+ // OKHSL object — Glaze's native shape (h: 0–360, s/l: 0–1)
294
+ glaze.color({ h: 152, s: 0.95, l: 0.74 }).tasty();
295
+
296
+ // RGB tuple, 0–255 (same range as glaze.fromRgb)
297
+ glaze.color([38, 252, 178]).tasty();
298
+ ```
299
+
300
+ Optional second argument supplies overrides — including `base` (any color
301
+ value), the WCAG `contrast` solver, and relative `hue` / `lightness`:
302
+
303
+ ```ts
304
+ // Brand color seeded from a hex, with seed/lightness/mode overrides
305
+ glaze.color('#26fcb2', { saturation: 80, mode: 'fixed' }).tasty();
306
+
307
+ // Brand text guaranteed AAA against a brand-tinted dark surface
308
+ glaze.color('#26fcb2', {
309
+ base: '#1a1a2e', // any GlazeColorValue
310
+ lightness: '+48', // relative offset from base lightness
311
+ contrast: 'AAA',
312
+ mode: 'fixed',
313
+ }).tasty();
314
+ ```
315
+
316
+ All overrides:
317
+
318
+ | Option | Notes |
319
+ |---|---|
320
+ | `hue` | Number (absolute 0–360) or `'+N'`/`'-N'` (relative to seed) |
321
+ | `saturation` | Override seed saturation (0–100) |
322
+ | `lightness` | Number (absolute 0–100) or `'+N'`/`'-N'` (relative to base — requires `base`). Supports `[normal, hc]` pairs |
323
+ | `saturationFactor` | Multiplier on seed (0–1, default 1) |
324
+ | `mode` | `'auto'` (default) / `'fixed'` / `'static'` — see [Adaptation Modes](#adaptation-modes) |
325
+ | `base` | Any `GlazeColorValue` — required for relative `lightness` and `contrast` |
326
+ | `contrast` | WCAG floor against `base`. Same shape as `RegularColorDef.contrast` |
327
+
328
+ Alpha components in `rgb(... / A)` / `hsl(... / A)` / `rgba(...)` / `hsla(...)` are
329
+ parsed but the alpha channel is dropped with a `console.warn` (standalone
330
+ colors have no opacity field — use `opacity` on a theme color if you need
331
+ alpha). Named CSS colors (`'red'`, `'blueviolet'`) are not supported.
272
332
 
273
333
  ## From Existing Colors
274
334
 
@@ -413,6 +473,139 @@ const css = glaze.format(v, 'oklch');
413
473
  }
414
474
  ```
415
475
 
476
+ ## Mix Colors
477
+
478
+ Mix colors blend two existing colors together. Use them for hover overlays, tints, shades, and any derived color that sits between two reference colors.
479
+
480
+ ### Opaque Mix
481
+
482
+ Produces a solid color by interpolating between `base` and `target`:
483
+
484
+ ```ts
485
+ theme.colors({
486
+ surface: { lightness: 95 },
487
+ accent: { lightness: 30 },
488
+
489
+ // 30% of the way from surface toward accent
490
+ tint: { type: 'mix', base: 'surface', target: 'accent', value: 30 },
491
+ });
492
+ ```
493
+
494
+ - `value` — mix ratio 0–100 (0 = pure base, 100 = pure target)
495
+ - The result is a fully opaque color (alpha = 1)
496
+ - Adapts to light/dark/HC schemes automatically via the resolved base and target
497
+
498
+ ### Transparent Mix
499
+
500
+ Produces the target color with a controlled opacity — useful for hover overlays:
501
+
502
+ ```ts
503
+ theme.colors({
504
+ surface: { lightness: 95 },
505
+ black: { lightness: 0, saturation: 0 },
506
+
507
+ hover: {
508
+ type: 'mix',
509
+ base: 'surface',
510
+ target: 'black',
511
+ value: 8,
512
+ blend: 'transparent',
513
+ },
514
+ });
515
+ // hover → target color (black) with alpha = 0.08
516
+ ```
517
+
518
+ The output color has `h`, `s`, `l` from the target and `alpha = value / 100`.
519
+
520
+ ### Blend Space
521
+
522
+ By default, opaque mixing interpolates in OKHSL (perceptually uniform, consistent with Glaze's model). Use `space: 'srgb'` for linear sRGB interpolation, which matches browser compositing:
523
+
524
+ ```ts
525
+ theme.colors({
526
+ surface: { lightness: 95 },
527
+ accent: { lightness: 30 },
528
+
529
+ // sRGB blend — matches what the browser would render
530
+ hover: { type: 'mix', base: 'surface', target: 'accent', value: 20, space: 'srgb' },
531
+ });
532
+ ```
533
+
534
+ | Space | Behavior | Best for |
535
+ |---|---|---|
536
+ | `'okhsl'` (default) | Perceptually uniform OKHSL interpolation | Design token derivation |
537
+ | `'srgb'` | Linear sRGB channel interpolation | Matching browser compositing |
538
+
539
+ The `space` option only affects opaque blending. Transparent blending always composites in linear sRGB (matching browser alpha compositing).
540
+
541
+ ### Contrast Solving
542
+
543
+ Mix colors support the same `contrast` prop as regular colors. The solver adjusts the mix ratio (opaque) or opacity (transparent) to meet the WCAG target:
544
+
545
+ ```ts
546
+ theme.colors({
547
+ surface: { lightness: 95 },
548
+ accent: { lightness: 30 },
549
+
550
+ // Ensure the mixed color has at least AA contrast against surface
551
+ tint: {
552
+ type: 'mix',
553
+ base: 'surface',
554
+ target: 'accent',
555
+ value: 10,
556
+ contrast: 'AA',
557
+ },
558
+
559
+ // Ensure the transparent overlay has at least 3:1 contrast
560
+ overlay: {
561
+ type: 'mix',
562
+ base: 'surface',
563
+ target: 'accent',
564
+ value: 5,
565
+ blend: 'transparent',
566
+ contrast: 3,
567
+ },
568
+ });
569
+ ```
570
+
571
+ ### High-Contrast Pairs
572
+
573
+ Both `value` and `contrast` support `[normal, highContrast]` pairs:
574
+
575
+ ```ts
576
+ theme.colors({
577
+ surface: { lightness: 95 },
578
+ accent: { lightness: 30 },
579
+
580
+ tint: {
581
+ type: 'mix',
582
+ base: 'surface',
583
+ target: 'accent',
584
+ value: [20, 40], // stronger mix in high-contrast mode
585
+ contrast: [3, 'AAA'], // stricter contrast in high-contrast mode
586
+ },
587
+ });
588
+ ```
589
+
590
+ ### Achromatic Colors
591
+
592
+ When mixing with achromatic colors (saturation near zero, e.g., white or black) in `okhsl` space, the hue comes from whichever color has saturation. This prevents meaningless hue artifacts and matches CSS `color-mix()` "missing component" behavior. For purely achromatic mixes, prefer `space: 'srgb'` where hue is irrelevant.
593
+
594
+ ### Mix Chaining
595
+
596
+ Mix colors can reference other mix colors, enabling multi-step derivations:
597
+
598
+ ```ts
599
+ theme.colors({
600
+ white: { lightness: 100, saturation: 0 },
601
+ black: { lightness: 0, saturation: 0 },
602
+ gray: { type: 'mix', base: 'white', target: 'black', value: 50, space: 'srgb' },
603
+ lightGray: { type: 'mix', base: 'white', target: 'gray', value: 50, space: 'srgb' },
604
+ });
605
+ ```
606
+
607
+ Mix colors cannot reference shadow colors (same restriction as regular dependent colors).
608
+
416
609
  ## Output Formats
417
610
 
418
611
  Control the color format in exports with the `format` option:
@@ -431,7 +624,7 @@ theme.tokens({ format: 'hsl' }); // → 'hsl(270.5 45.2% 95.8%)'
431
624
  theme.tokens({ format: 'oklch' }); // → 'oklch(0.965 0.0123 280)'
432
625
  ```
433
626
 
434
- The `format` option works on all export methods: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `palette.tokens()`, `palette.tasty()`, `palette.json()`, `palette.css()`, and standalone `glaze.color().token()` / `.tasty()` / `.json()`.
627
+ The `format` option works on all export methods: `theme.tokens()`, `theme.tasty()`, `theme.json()`, `theme.css()`, `palette.tokens()`, `palette.tasty()`, `palette.json()`, `palette.css()`, and standalone `glaze.color().token()` / `.tasty()` / `.json()` / `.css()`.
435
628
 
436
629
  Colors with `alpha < 1` (shadow colors, or regular colors with `opacity`) include an alpha component:
437
630
 
@@ -467,7 +660,7 @@ Modes control how colors adapt across schemes:
467
660
 
468
661
  ```ts
469
662
  // Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
470
- // Dark: surface inverts to L≈14, sign flips → L=14+52=66
663
+ // Dark: surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
471
664
  // contrast solver may push further (light text on dark bg)
472
665
  ```
473
666
 
@@ -484,14 +677,14 @@ Modes control how colors adapt across schemes:
484
677
 
485
678
  ### Lightness
486
679
 
487
- Root color lightness is mapped linearly within the configured `lightLightness` window:
680
+ Absolute lightness values (both root colors and dependent colors with absolute lightness) are mapped linearly within the configured `lightLightness` window:
488
681
 
489
682
  ```ts
490
683
  const [lo, hi] = lightLightness; // default: [10, 100]
491
684
  const mappedL = (lightness * (hi - lo)) / 100 + lo;
492
685
  ```
493
686
 
494
- Both `auto` and `fixed` modes use the same linear formula. `static` mode bypasses the mapping entirely.
687
+ Both `auto` and `fixed` modes use the same linear formula. `static` mode and high-contrast variants bypass the mapping entirely (identity: `mappedL = l`).
495
688
 
496
689
  | Color | Raw L | Mapped L (default [10, 100]) |
497
690
  |---|---|---|
@@ -503,24 +696,29 @@ Both `auto` and `fixed` modes use the same linear formula. `static` mode bypasse
503
696
 
504
697
  ### Lightness
505
698
 
506
- **`auto`** — inverted within the configured window:
699
+ **`auto`** — inverted with a Möbius transformation within the configured window:
507
700
 
508
701
  ```ts
509
702
  const [lo, hi] = darkLightness; // default: [15, 95]
510
- const invertedL = ((100 - lightness) * (hi - lo)) / 100 + lo;
703
+ const t = (100 - lightness) / 100;
704
+ const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5
511
705
  ```
512
706
 
513
- **`fixed`** mapped without inversion:
707
+ The `darkCurve` parameter (default `0.5`, range 0–1) controls how much the dark-mode inversion expands lightness deltas. Lower values produce stronger expansion; `1` gives linear (legacy) behavior. Accepts a `[normal, highContrast]` pair for separate HC tuning (e.g. `darkCurve: [0.5, 0.3]`); a single number applies to both. Unlike a power curve, the Möbius transformation provides **proportional expansion** — small and large deltas are scaled by similar ratios, preserving the visual hierarchy of the light theme.
708
+
709
+ **`fixed`** — mapped without inversion (not affected by `darkCurve`):
514
710
 
515
711
  ```ts
516
712
  const mappedL = (lightness * (hi - lo)) / 100 + lo;
517
713
  ```
518
714
 
519
- | Color | Light L | Auto (inverted) | Fixed (mapped) |
520
- |---|---|---|---|
521
- | surface (L=97) | 97 | 17.4 | 92.6 |
522
- | accent-fill (L=52) | 52 | 53.4 | 56.6 |
523
- | accent-text (L=100) | 100 | 15 | 95 |
715
+ | Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
716
+ |---|---|---|---|---|
717
+ | surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
718
+ | accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
719
+ | accent-text (L=100) | 100 | 15 | 15 | 95 |
720
+
721
+ In high-contrast variants, the `darkLightness` window is bypassed — auto uses the Möbius curve over the full [0, 100] range, and fixed uses identity (`L`). To use a different curve shape for HC, pass a `[normal, hc]` pair to `darkCurve` (e.g. `darkCurve: [0.5, 0.3]`).
524
722
 
525
723
  ### Saturation
526
724
 
@@ -560,12 +758,21 @@ Combine multiple themes into a single palette:
560
758
  const palette = glaze.palette({ primary, danger, success, warning });
561
759
  ```
562
760
 
563
- ### Token Export
761
+ Optionally designate a primary theme at creation time:
762
+
763
+ ```ts
764
+ const palette = glaze.palette(
765
+ { primary, danger, success, warning },
766
+ { primary: 'primary' },
767
+ );
768
+ ```
769
+
770
+ ### Prefix Behavior
564
771
 
565
- Tokens are grouped by scheme variant, with plain color names as keys:
772
+ Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
566
773
 
567
774
  ```ts
568
- const tokens = palette.tokens({ prefix: true });
775
+ const tokens = palette.tokens();
569
776
  // → {
570
777
  // light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
571
778
  // dark: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
@@ -578,15 +785,68 @@ Custom prefix mapping:
578
785
  palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
579
786
  ```
580
787
 
581
- ### Tasty Export (for [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style system)
788
+ To disable prefixing entirely, pass `prefix: false` explicitly.
789
+
790
+ ### Collision Detection
791
+
792
+ When two themes produce the same output key (via `prefix: false`, custom prefix maps, or primary unprefixed aliases), the first-written value wins and a `console.warn` is emitted:
793
+
794
+ ```ts
795
+ const palette = glaze.palette({ a, b });
796
+ palette.tokens({ prefix: false });
797
+ // ⚠ glaze: token "surface" from theme "b" collides with theme "a" — skipping.
798
+ ```
799
+
800
+ ### Primary Theme
801
+
802
+ The primary theme's tokens are duplicated without prefix, providing convenient short aliases alongside the prefixed versions. Set at palette creation to apply to all exports automatically:
803
+
804
+ ```ts
805
+ const palette = glaze.palette(
806
+ { primary, danger, success },
807
+ { primary: 'primary' },
808
+ );
809
+ const tokens = palette.tokens();
810
+ // → {
811
+ // light: {
812
+ // 'primary-surface': 'okhsl(...)', // prefixed (all themes)
813
+ // 'danger-surface': 'okhsl(...)',
814
+ // 'success-surface': 'okhsl(...)',
815
+ // 'surface': 'okhsl(...)', // unprefixed alias (primary only)
816
+ // },
817
+ // }
818
+ ```
819
+
820
+ Override or disable per-export:
582
821
 
583
- The `tasty()` method exports tokens in the [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state binding format — `#name` color token keys with state aliases (`''`, `@dark`, etc.):
822
+ ```ts
823
+ palette.tokens({ primary: 'danger' }); // use danger as primary for this call
824
+ palette.tokens({ primary: false }); // no primary for this call
825
+ ```
826
+
827
+ The `primary` option works on `tokens()`, `tasty()`, and `css()`. It combines with any prefix mode — when using a custom prefix map, primary tokens are still duplicated without prefix:
828
+
829
+ ```ts
830
+ palette.tokens({ prefix: { primary: 'p-', danger: 'd-' } });
831
+ // → 'p-surface' + 'surface' (alias from palette-level primary) + 'd-surface'
832
+ ```
833
+
834
+ An error is thrown if the primary name doesn't match any theme in the palette.
835
+
836
+ ### Tasty Export (for [Tasty](https://tasty.style) style system)
837
+
838
+ The `tasty()` method exports tokens in the [Tasty](https://tasty.style/docs) style-to-state binding format — `#name` color token keys with state aliases (`''`, `@dark`, etc.). See the [Playground](https://tasty.style/playground) for live examples of Glaze integration:
584
839
 
585
840
  ```ts
586
- const tastyTokens = palette.tasty({ prefix: true });
841
+ const palette = glaze.palette(
842
+ { primary, danger, success },
843
+ { primary: 'primary' },
844
+ );
845
+ const tastyTokens = palette.tasty();
587
846
  // → {
588
847
  // '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
589
848
  // '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
849
+ // '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' }, // alias
590
850
  // }
591
851
  ```
592
852
 
@@ -653,8 +913,10 @@ palette.tasty({ states: { dark: '@dark', highContrast: '@hc' } });
653
913
 
654
914
  ### JSON Export (Framework-Agnostic)
655
915
 
916
+ JSON export groups by theme name (no prefix needed):
917
+
656
918
  ```ts
657
- const data = palette.json({ prefix: true });
919
+ const data = palette.json();
658
920
  // → {
659
921
  // primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
660
922
  // danger: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
@@ -676,7 +938,11 @@ const css = theme.css();
676
938
  Use in a stylesheet:
677
939
 
678
940
  ```ts
679
- const css = palette.css({ prefix: true });
941
+ const palette = glaze.palette(
942
+ { primary, danger, success },
943
+ { primary: 'primary' },
944
+ );
945
+ const css = palette.css();
680
946
 
681
947
  const stylesheet = `
682
948
  :root { ${css.light} }
@@ -692,7 +958,8 @@ Options:
692
958
  |---|---|---|
693
959
  | `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
694
960
  | `suffix` | `'-color'` | Suffix appended to each CSS property name |
695
- | `prefix` | | (palette only) Same prefix behavior as `tokens()` |
961
+ | `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
962
+ | `primary` | inherited | (palette only) Override or disable (`false`) the palette-level primary for this call |
696
963
 
697
964
  ```ts
698
965
  // Custom suffix
@@ -703,9 +970,9 @@ theme.css({ suffix: '' });
703
970
  theme.css({ format: 'hsl' });
704
971
  // → "--surface-color: hsl(...);"
705
972
 
706
- // Palette with prefix
707
- palette.css({ prefix: true });
708
- // → "--primary-surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
973
+ // Palette with primary (inherited from palette creation)
974
+ palette.css();
975
+ // → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
709
976
  ```
710
977
 
711
978
  ## Output Modes
@@ -738,9 +1005,10 @@ Resolution priority (highest first):
738
1005
 
739
1006
  ```ts
740
1007
  glaze.configure({
741
- lightLightness: [10, 100], // Light scheme lightness window [lo, hi]
742
- darkLightness: [15, 95], // Dark scheme lightness window [lo, hi]
1008
+ lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
1009
+ darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
743
1010
  darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
1011
+ darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1); or [normal, hc] pair
744
1012
  states: {
745
1013
  dark: '@dark', // State alias for dark mode tokens
746
1014
  highContrast: '@high-contrast',
@@ -758,10 +1026,10 @@ glaze.configure({
758
1026
 
759
1027
  ## Color Definition Shape
760
1028
 
761
- `ColorDef` is a discriminated union of regular colors and shadow colors:
1029
+ `ColorDef` is a discriminated union of regular colors, shadow colors, and mix colors:
762
1030
 
763
1031
  ```ts
764
- type ColorDef = RegularColorDef | ShadowColorDef;
1032
+ type ColorDef = RegularColorDef | ShadowColorDef | MixColorDef;
765
1033
 
766
1034
  interface RegularColorDef {
767
1035
  lightness?: HCPair<number | RelativeValue>;
@@ -780,9 +1048,19 @@ interface ShadowColorDef {
780
1048
  intensity: HCPair<number>; // 0–100
781
1049
  tuning?: ShadowTuning;
782
1050
  }
1051
+
1052
+ interface MixColorDef {
1053
+ type: 'mix';
1054
+ base: string; // "from" color name
1055
+ target: string; // "to" color name
1056
+ value: HCPair<number>; // 0–100 (mix ratio or opacity)
1057
+ blend?: 'opaque' | 'transparent'; // default: 'opaque'
1058
+ space?: 'okhsl' | 'srgb'; // default: 'okhsl'
1059
+ contrast?: HCPair<MinContrast>;
1060
+ }
783
1061
  ```
784
1062
 
785
- A root color must have absolute `lightness` (a number). A dependent color must have `base`. Relative `lightness` (a string) requires `base`. Shadow colors use `type: 'shadow'` and must reference a non-shadow `bg` color.
1063
+ A root color must have absolute `lightness` (a number). A dependent color must have `base`. Relative `lightness` (a string) requires `base`. Shadow colors use `type: 'shadow'` and must reference a non-shadow `bg` color. Mix colors use `type: 'mix'` and must reference two non-shadow colors.
786
1064
 
787
1065
  ## Validation
788
1066
 
@@ -801,6 +1079,12 @@ A root color must have absolute `lightness` (a number). A dependent color must h
801
1079
  | Regular color `base` references a shadow color | Validation error |
802
1080
  | Shadow `intensity` outside 0–100 | Clamp silently |
803
1081
  | `contrast` + `opacity` combined | Warning |
1082
+ | Mix `base` references non-existent color | Validation error |
1083
+ | Mix `target` references non-existent color | Validation error |
1084
+ | Mix `base` references a shadow color | Validation error |
1085
+ | Mix `target` references a shadow color | Validation error |
1086
+ | Mix `value` outside 0–100 | Clamp silently |
1087
+ | Circular references involving mix colors | Validation error |
804
1088
 
805
1089
  ## Advanced: Color Math Utilities
806
1090
 
@@ -846,6 +1130,10 @@ primary.colors({
846
1130
  'shadow-md': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 10 },
847
1131
  'shadow-lg': { type: 'shadow', bg: 'surface', fg: 'text', intensity: 20 },
848
1132
 
1133
+ // Mix colors — hover overlays and tints
1134
+ 'hover': { type: 'mix', base: 'surface', target: 'accent-fill', value: 8, blend: 'transparent' },
1135
+ 'tint': { type: 'mix', base: 'surface', target: 'accent-fill', value: 20 },
1136
+
849
1137
  // Fixed-alpha overlay
850
1138
  overlay: { lightness: 0, opacity: 0.5 },
851
1139
  });
@@ -855,18 +1143,21 @@ const success = primary.extend({ hue: 157 });
855
1143
  const warning = primary.extend({ hue: 84 });
856
1144
  const note = primary.extend({ hue: 302 });
857
1145
 
858
- const palette = glaze.palette({ primary, danger, success, warning, note });
1146
+ const palette = glaze.palette(
1147
+ { primary, danger, success, warning, note },
1148
+ { primary: 'primary' },
1149
+ );
859
1150
 
860
- // Export as flat token map grouped by variant
861
- const tokens = palette.tokens({ prefix: true });
862
- // tokens.light → { 'primary-surface': 'okhsl(...)', 'primary-shadow-md': 'okhsl(... / 0.1)' }
1151
+ // Export as flat token map grouped by variant (prefix defaults to true)
1152
+ const tokens = palette.tokens();
1153
+ // tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
863
1154
 
864
1155
  // Export as tasty style-to-state bindings (for Tasty style system)
865
- const tastyTokens = palette.tasty({ prefix: true });
1156
+ const tastyTokens = palette.tasty();
866
1157
 
867
1158
  // Export as CSS custom properties (rgb format by default)
868
- const css = palette.css({ prefix: true });
869
- // css.light → "--primary-surface-color: rgb(...);\n--primary-shadow-md-color: rgb(... / 0.1);"
1159
+ const css = palette.css();
1160
+ // css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
870
1161
 
871
1162
  // Standalone shadow computation
872
1163
  const v = glaze.shadow({ bg: '#f0eef5', fg: '#1a1a2e', intensity: 10 });
@@ -893,7 +1184,8 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
893
1184
  | `glaze.from(data)` | Create a theme from an exported configuration |
894
1185
  | `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`) |
895
1186
  | `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255) |
896
- | `glaze.color(input)` | Create a standalone color token |
1187
+ | `glaze.color(input)` | Create a standalone color token from `{ hue, saturation, lightness, ... }` |
1188
+ | `glaze.color(value, overrides?)` | Create a standalone color token from a hex string, an `rgb()` / `hsl()` / `okhsl()` / `oklch()` string, an `{ h, s, l }` OKHSL object, or an `[r, g, b]` (0–255) tuple. Overrides accept absolute or relative `hue` / `lightness`, `saturation`, `mode`, `base` (any value form), and `contrast` |
897
1189
  | `glaze.shadow(input)` | Compute a standalone shadow color (returns `ResolvedColorVariant`) |
898
1190
  | `glaze.format(variant, format?)` | Format any `ResolvedColorVariant` as a CSS string |
899
1191
 
@@ -912,7 +1204,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
912
1204
  | `theme.extend(options)` | Create a child theme |
913
1205
  | `theme.resolve()` | Resolve all colors |
914
1206
  | `theme.tokens(options?)` | Export as flat token map grouped by variant |
915
- | `theme.tasty(options?)` | Export as [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style-to-state bindings |
1207
+ | `theme.tasty(options?)` | Export as [Tasty](https://tasty.style/docs) style-to-state bindings |
916
1208
  | `theme.json(options?)` | Export as plain JSON |
917
1209
  | `theme.css(options?)` | Export as CSS custom property declarations |
918
1210
 
@@ -921,7 +1213,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
921
1213
  | Method | Description |
922
1214
  |---|---|
923
1215
  | `glaze.configure(config)` | Set global configuration |
924
- | `glaze.palette(themes)` | Compose themes into a palette |
1216
+ | `glaze.palette(themes, options?)` | Compose themes into a palette (options: `{ primary? }`) |
925
1217
  | `glaze.getConfig()` | Get current global config |
926
1218
  | `glaze.resetConfig()` | Reset to defaults |
927
1219