@tenphi/glaze 0.0.0-snapshot.7f3fb7f → 0.0.0-snapshot.7f7cab2
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 +114 -34
- package/dist/index.cjs +125 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -20
- package/dist/index.d.mts +70 -20
- package/dist/index.mjs +125 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,9 +70,9 @@ const danger = primary.extend({ hue: 23 });
|
|
|
70
70
|
const success = primary.extend({ hue: 157 });
|
|
71
71
|
|
|
72
72
|
// Compose into a palette and export
|
|
73
|
-
const palette = glaze.palette({ primary, danger, success });
|
|
74
|
-
const tokens = palette.tokens(
|
|
75
|
-
// → { light: { '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: { ... } }
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
## Core Concepts
|
|
@@ -194,6 +194,8 @@ A single value applies to both modes. All control is local and explicit.
|
|
|
194
194
|
'muted': { base: 'surface', lightness: ['-35', '-50'], contrast: ['AA-large', 'AA'] }
|
|
195
195
|
```
|
|
196
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
|
+
|
|
197
199
|
## Theme Color Management
|
|
198
200
|
|
|
199
201
|
### Adding Colors
|
|
@@ -601,7 +603,7 @@ Modes control how colors adapt across schemes:
|
|
|
601
603
|
|
|
602
604
|
```ts
|
|
603
605
|
// Light: surface L=97, text lightness='-52' → L=45 (dark text on light bg)
|
|
604
|
-
// Dark: surface inverts to L≈
|
|
606
|
+
// Dark: surface inverts to L≈20 (Möbius curve), sign flips → L=20+52=72
|
|
605
607
|
// contrast solver may push further (light text on dark bg)
|
|
606
608
|
```
|
|
607
609
|
|
|
@@ -618,14 +620,14 @@ Modes control how colors adapt across schemes:
|
|
|
618
620
|
|
|
619
621
|
### Lightness
|
|
620
622
|
|
|
621
|
-
|
|
623
|
+
Absolute lightness values (both root colors and dependent colors with absolute lightness) are mapped linearly within the configured `lightLightness` window:
|
|
622
624
|
|
|
623
625
|
```ts
|
|
624
626
|
const [lo, hi] = lightLightness; // default: [10, 100]
|
|
625
627
|
const mappedL = (lightness * (hi - lo)) / 100 + lo;
|
|
626
628
|
```
|
|
627
629
|
|
|
628
|
-
Both `auto` and `fixed` modes use the same linear formula. `static` mode
|
|
630
|
+
Both `auto` and `fixed` modes use the same linear formula. `static` mode and high-contrast variants bypass the mapping entirely (identity: `mappedL = l`).
|
|
629
631
|
|
|
630
632
|
| Color | Raw L | Mapped L (default [10, 100]) |
|
|
631
633
|
|---|---|---|
|
|
@@ -637,24 +639,29 @@ Both `auto` and `fixed` modes use the same linear formula. `static` mode bypasse
|
|
|
637
639
|
|
|
638
640
|
### Lightness
|
|
639
641
|
|
|
640
|
-
**`auto`** — inverted within the configured window:
|
|
642
|
+
**`auto`** — inverted with a Möbius transformation within the configured window:
|
|
641
643
|
|
|
642
644
|
```ts
|
|
643
645
|
const [lo, hi] = darkLightness; // default: [15, 95]
|
|
644
|
-
const
|
|
646
|
+
const t = (100 - lightness) / 100;
|
|
647
|
+
const invertedL = lo + (hi - lo) * t / (t + darkCurve * (1 - t)); // darkCurve default: 0.5
|
|
645
648
|
```
|
|
646
649
|
|
|
647
|
-
|
|
650
|
+
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.
|
|
651
|
+
|
|
652
|
+
**`fixed`** — mapped without inversion (not affected by `darkCurve`):
|
|
648
653
|
|
|
649
654
|
```ts
|
|
650
655
|
const mappedL = (lightness * (hi - lo)) / 100 + lo;
|
|
651
656
|
```
|
|
652
657
|
|
|
653
|
-
| Color | Light L | Auto (
|
|
654
|
-
|
|
655
|
-
| surface (L=97) | 97 | 17.4 | 92.6 |
|
|
656
|
-
| accent-fill (L=52) | 52 | 53.4 | 56.6 |
|
|
657
|
-
| accent-text (L=100) | 100 | 15 | 95 |
|
|
658
|
+
| Color | Light L | Auto (curve=0.5) | Auto (curve=1, linear) | Fixed (mapped) |
|
|
659
|
+
|---|---|---|---|---|
|
|
660
|
+
| surface (L=97) | 97 | 19.7 | 17.4 | 92.6 |
|
|
661
|
+
| accent-fill (L=52) | 52 | 66.9 | 53.4 | 56.6 |
|
|
662
|
+
| accent-text (L=100) | 100 | 15 | 15 | 95 |
|
|
663
|
+
|
|
664
|
+
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]`).
|
|
658
665
|
|
|
659
666
|
### Saturation
|
|
660
667
|
|
|
@@ -694,12 +701,21 @@ Combine multiple themes into a single palette:
|
|
|
694
701
|
const palette = glaze.palette({ primary, danger, success, warning });
|
|
695
702
|
```
|
|
696
703
|
|
|
697
|
-
|
|
704
|
+
Optionally designate a primary theme at creation time:
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
const palette = glaze.palette(
|
|
708
|
+
{ primary, danger, success, warning },
|
|
709
|
+
{ primary: 'primary' },
|
|
710
|
+
);
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Prefix Behavior
|
|
698
714
|
|
|
699
|
-
|
|
715
|
+
Palette export methods (`tokens()`, `tasty()`, `css()`) default to `prefix: true` — all tokens are automatically prefixed with the theme name to avoid collisions:
|
|
700
716
|
|
|
701
717
|
```ts
|
|
702
|
-
const tokens = palette.tokens(
|
|
718
|
+
const tokens = palette.tokens();
|
|
703
719
|
// → {
|
|
704
720
|
// light: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
|
|
705
721
|
// dark: { 'primary-surface': 'okhsl(...)', 'danger-surface': 'okhsl(...)' },
|
|
@@ -712,15 +728,68 @@ Custom prefix mapping:
|
|
|
712
728
|
palette.tokens({ prefix: { primary: 'brand-', danger: 'error-' } });
|
|
713
729
|
```
|
|
714
730
|
|
|
731
|
+
To disable prefixing entirely, pass `prefix: false` explicitly.
|
|
732
|
+
|
|
733
|
+
### Collision Detection
|
|
734
|
+
|
|
735
|
+
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:
|
|
736
|
+
|
|
737
|
+
```ts
|
|
738
|
+
const palette = glaze.palette({ a, b });
|
|
739
|
+
palette.tokens({ prefix: false });
|
|
740
|
+
// ⚠ glaze: token "surface" from theme "b" collides with theme "a" — skipping.
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Primary Theme
|
|
744
|
+
|
|
745
|
+
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:
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
const palette = glaze.palette(
|
|
749
|
+
{ primary, danger, success },
|
|
750
|
+
{ primary: 'primary' },
|
|
751
|
+
);
|
|
752
|
+
const tokens = palette.tokens();
|
|
753
|
+
// → {
|
|
754
|
+
// light: {
|
|
755
|
+
// 'primary-surface': 'okhsl(...)', // prefixed (all themes)
|
|
756
|
+
// 'danger-surface': 'okhsl(...)',
|
|
757
|
+
// 'success-surface': 'okhsl(...)',
|
|
758
|
+
// 'surface': 'okhsl(...)', // unprefixed alias (primary only)
|
|
759
|
+
// },
|
|
760
|
+
// }
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Override or disable per-export:
|
|
764
|
+
|
|
765
|
+
```ts
|
|
766
|
+
palette.tokens({ primary: 'danger' }); // use danger as primary for this call
|
|
767
|
+
palette.tokens({ primary: false }); // no primary for this call
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
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:
|
|
771
|
+
|
|
772
|
+
```ts
|
|
773
|
+
palette.tokens({ prefix: { primary: 'p-', danger: 'd-' } });
|
|
774
|
+
// → 'p-surface' + 'surface' (alias from palette-level primary) + 'd-surface'
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
An error is thrown if the primary name doesn't match any theme in the palette.
|
|
778
|
+
|
|
715
779
|
### Tasty Export (for [Tasty](https://cube-ui-kit.vercel.app/?path=/docs/tasty-documentation--docs) style system)
|
|
716
780
|
|
|
717
781
|
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.):
|
|
718
782
|
|
|
719
783
|
```ts
|
|
720
|
-
const
|
|
784
|
+
const palette = glaze.palette(
|
|
785
|
+
{ primary, danger, success },
|
|
786
|
+
{ primary: 'primary' },
|
|
787
|
+
);
|
|
788
|
+
const tastyTokens = palette.tasty();
|
|
721
789
|
// → {
|
|
722
790
|
// '#primary-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
723
791
|
// '#danger-surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' },
|
|
792
|
+
// '#surface': { '': 'okhsl(...)', '@dark': 'okhsl(...)' }, // alias
|
|
724
793
|
// }
|
|
725
794
|
```
|
|
726
795
|
|
|
@@ -787,8 +856,10 @@ palette.tasty({ states: { dark: '@dark', highContrast: '@hc' } });
|
|
|
787
856
|
|
|
788
857
|
### JSON Export (Framework-Agnostic)
|
|
789
858
|
|
|
859
|
+
JSON export groups by theme name (no prefix needed):
|
|
860
|
+
|
|
790
861
|
```ts
|
|
791
|
-
const data = palette.json(
|
|
862
|
+
const data = palette.json();
|
|
792
863
|
// → {
|
|
793
864
|
// primary: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
|
|
794
865
|
// danger: { surface: { light: 'okhsl(...)', dark: 'okhsl(...)' } },
|
|
@@ -810,7 +881,11 @@ const css = theme.css();
|
|
|
810
881
|
Use in a stylesheet:
|
|
811
882
|
|
|
812
883
|
```ts
|
|
813
|
-
const
|
|
884
|
+
const palette = glaze.palette(
|
|
885
|
+
{ primary, danger, success },
|
|
886
|
+
{ primary: 'primary' },
|
|
887
|
+
);
|
|
888
|
+
const css = palette.css();
|
|
814
889
|
|
|
815
890
|
const stylesheet = `
|
|
816
891
|
:root { ${css.light} }
|
|
@@ -826,7 +901,8 @@ Options:
|
|
|
826
901
|
|---|---|---|
|
|
827
902
|
| `format` | `'rgb'` | Color format (`'rgb'`, `'hsl'`, `'okhsl'`, `'oklch'`) |
|
|
828
903
|
| `suffix` | `'-color'` | Suffix appended to each CSS property name |
|
|
829
|
-
| `prefix` |
|
|
904
|
+
| `prefix` | `true` (palette) | (palette only) `true` uses `"<themeName>-"`, or provide a custom map |
|
|
905
|
+
| `primary` | inherited | (palette only) Override or disable (`false`) the palette-level primary for this call |
|
|
830
906
|
|
|
831
907
|
```ts
|
|
832
908
|
// Custom suffix
|
|
@@ -837,9 +913,9 @@ theme.css({ suffix: '' });
|
|
|
837
913
|
theme.css({ format: 'hsl' });
|
|
838
914
|
// → "--surface-color: hsl(...);"
|
|
839
915
|
|
|
840
|
-
// Palette with
|
|
841
|
-
palette.css(
|
|
842
|
-
// → "--primary-surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
916
|
+
// Palette with primary (inherited from palette creation)
|
|
917
|
+
palette.css();
|
|
918
|
+
// → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
843
919
|
```
|
|
844
920
|
|
|
845
921
|
## Output Modes
|
|
@@ -872,9 +948,10 @@ Resolution priority (highest first):
|
|
|
872
948
|
|
|
873
949
|
```ts
|
|
874
950
|
glaze.configure({
|
|
875
|
-
lightLightness: [10, 100], // Light scheme lightness window [lo, hi]
|
|
876
|
-
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi]
|
|
951
|
+
lightLightness: [10, 100], // Light scheme lightness window [lo, hi] (bypassed in HC)
|
|
952
|
+
darkLightness: [15, 95], // Dark scheme lightness window [lo, hi] (bypassed in HC)
|
|
877
953
|
darkDesaturation: 0.1, // Saturation reduction in dark scheme (0–1)
|
|
954
|
+
darkCurve: 0.5, // Möbius beta for dark auto-inversion (0–1); or [normal, hc] pair
|
|
878
955
|
states: {
|
|
879
956
|
dark: '@dark', // State alias for dark mode tokens
|
|
880
957
|
highContrast: '@high-contrast',
|
|
@@ -1009,18 +1086,21 @@ const success = primary.extend({ hue: 157 });
|
|
|
1009
1086
|
const warning = primary.extend({ hue: 84 });
|
|
1010
1087
|
const note = primary.extend({ hue: 302 });
|
|
1011
1088
|
|
|
1012
|
-
const palette = glaze.palette(
|
|
1089
|
+
const palette = glaze.palette(
|
|
1090
|
+
{ primary, danger, success, warning, note },
|
|
1091
|
+
{ primary: 'primary' },
|
|
1092
|
+
);
|
|
1013
1093
|
|
|
1014
|
-
// Export as flat token map grouped by variant
|
|
1015
|
-
const tokens = palette.tokens(
|
|
1016
|
-
// tokens.light → { 'primary-surface': '
|
|
1094
|
+
// Export as flat token map grouped by variant (prefix defaults to true)
|
|
1095
|
+
const tokens = palette.tokens();
|
|
1096
|
+
// tokens.light → { 'primary-surface': '...', 'surface': '...', 'danger-surface': '...' }
|
|
1017
1097
|
|
|
1018
1098
|
// Export as tasty style-to-state bindings (for Tasty style system)
|
|
1019
|
-
const tastyTokens = palette.tasty(
|
|
1099
|
+
const tastyTokens = palette.tasty();
|
|
1020
1100
|
|
|
1021
1101
|
// Export as CSS custom properties (rgb format by default)
|
|
1022
|
-
const css = palette.css(
|
|
1023
|
-
// css.light → "--primary-surface-color: rgb(...);\n--
|
|
1102
|
+
const css = palette.css();
|
|
1103
|
+
// css.light → "--primary-surface-color: rgb(...);\n--surface-color: rgb(...);\n--danger-surface-color: rgb(...);"
|
|
1024
1104
|
|
|
1025
1105
|
// Standalone shadow computation
|
|
1026
1106
|
const v = glaze.shadow({ bg: '#f0eef5', fg: '#1a1a2e', intensity: 10 });
|
|
@@ -1075,7 +1155,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
|
|
|
1075
1155
|
| Method | Description |
|
|
1076
1156
|
|---|---|
|
|
1077
1157
|
| `glaze.configure(config)` | Set global configuration |
|
|
1078
|
-
| `glaze.palette(themes)` | Compose themes into a palette |
|
|
1158
|
+
| `glaze.palette(themes, options?)` | Compose themes into a palette (options: `{ primary? }`) |
|
|
1079
1159
|
| `glaze.getConfig()` | Get current global config |
|
|
1080
1160
|
| `glaze.resetConfig()` | Reset to defaults |
|
|
1081
1161
|
|
package/dist/index.cjs
CHANGED
|
@@ -470,7 +470,7 @@ function formatOklch(h, s, l) {
|
|
|
470
470
|
const C = Math.sqrt(a * a + b * b);
|
|
471
471
|
let hh = Math.atan2(b, a) * (180 / Math.PI);
|
|
472
472
|
hh = constrainAngle(hh);
|
|
473
|
-
return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh,
|
|
473
|
+
return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 2)})`;
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
//#endregion
|
|
@@ -620,7 +620,7 @@ function coarseScan(h, s, lo, hi, yBase, target, epsilon, maxIter) {
|
|
|
620
620
|
function findLightnessForContrast(options) {
|
|
621
621
|
const { hue, saturation, preferredLightness, baseLinearRgb, contrast: contrastInput, lightnessRange = [0, 1], epsilon = 1e-4, maxIterations = 14 } = options;
|
|
622
622
|
const target = resolveMinContrast(contrastInput);
|
|
623
|
-
const searchTarget = target * 1.
|
|
623
|
+
const searchTarget = target * 1.007;
|
|
624
624
|
const yBase = gamutClampedLuminance(baseLinearRgb);
|
|
625
625
|
const crPref = contrastRatioFromLuminance(cachedLuminance(hue, saturation, preferredLightness), yBase);
|
|
626
626
|
if (crPref >= searchTarget) return {
|
|
@@ -744,7 +744,7 @@ function searchMixBranch(lo, hi, yBase, target, epsilon, maxIter, preferred, lum
|
|
|
744
744
|
function findValueForMixContrast(options) {
|
|
745
745
|
const { preferredValue, baseLinearRgb, contrast: contrastInput, luminanceAtValue, epsilon = 1e-4, maxIterations = 20 } = options;
|
|
746
746
|
const target = resolveMinContrast(contrastInput);
|
|
747
|
-
const searchTarget = target * 1.
|
|
747
|
+
const searchTarget = target * 1.01;
|
|
748
748
|
const yBase = gamutClampedLuminance(baseLinearRgb);
|
|
749
749
|
const crPref = contrastRatioFromLuminance(luminanceAtValue(preferredValue), yBase);
|
|
750
750
|
if (crPref >= searchTarget) return {
|
|
@@ -814,6 +814,7 @@ let globalConfig = {
|
|
|
814
814
|
lightLightness: [10, 100],
|
|
815
815
|
darkLightness: [15, 95],
|
|
816
816
|
darkDesaturation: .1,
|
|
817
|
+
darkCurve: .5,
|
|
817
818
|
states: {
|
|
818
819
|
dark: "@dark",
|
|
819
820
|
highContrast: "@high-contrast"
|
|
@@ -961,24 +962,42 @@ function topoSort(defs) {
|
|
|
961
962
|
for (const name of Object.keys(defs)) visit(name);
|
|
962
963
|
return result;
|
|
963
964
|
}
|
|
964
|
-
function
|
|
965
|
+
function lightnessWindow(isHighContrast, kind) {
|
|
966
|
+
if (isHighContrast) return [0, 100];
|
|
967
|
+
return kind === "dark" ? globalConfig.darkLightness : globalConfig.lightLightness;
|
|
968
|
+
}
|
|
969
|
+
function mapLightnessLight(l, mode, isHighContrast) {
|
|
965
970
|
if (mode === "static") return l;
|
|
966
|
-
const [lo, hi] =
|
|
971
|
+
const [lo, hi] = lightnessWindow(isHighContrast, "light");
|
|
967
972
|
return l * (hi - lo) / 100 + lo;
|
|
968
973
|
}
|
|
969
|
-
function
|
|
974
|
+
function mobiusCurve(t, beta) {
|
|
975
|
+
if (beta >= 1) return t;
|
|
976
|
+
return t / (t + beta * (1 - t));
|
|
977
|
+
}
|
|
978
|
+
function mapLightnessDark(l, mode, isHighContrast) {
|
|
970
979
|
if (mode === "static") return l;
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
980
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
981
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
982
|
+
if (mode === "fixed") return l * (darkHi - darkLo) / 100 + darkLo;
|
|
983
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
984
|
+
const t = (lightHi - (l * (lightHi - lightLo) / 100 + lightLo)) / (lightHi - lightLo);
|
|
985
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
986
|
+
}
|
|
987
|
+
function lightMappedToDark(lightL, isHighContrast) {
|
|
988
|
+
const beta = isHighContrast ? pairHC(globalConfig.darkCurve) : pairNormal(globalConfig.darkCurve);
|
|
989
|
+
const [lightLo, lightHi] = lightnessWindow(isHighContrast, "light");
|
|
990
|
+
const [darkLo, darkHi] = lightnessWindow(isHighContrast, "dark");
|
|
991
|
+
const t = (lightHi - clamp(lightL, lightLo, lightHi)) / (lightHi - lightLo);
|
|
992
|
+
return darkLo + (darkHi - darkLo) * mobiusCurve(t, beta);
|
|
974
993
|
}
|
|
975
994
|
function mapSaturationDark(s, mode) {
|
|
976
995
|
if (mode === "static") return s;
|
|
977
996
|
return s * (1 - globalConfig.darkDesaturation);
|
|
978
997
|
}
|
|
979
|
-
function schemeLightnessRange(isDark, mode) {
|
|
998
|
+
function schemeLightnessRange(isDark, mode, isHighContrast) {
|
|
980
999
|
if (mode === "static") return [0, 1];
|
|
981
|
-
const [lo, hi] = isDark ?
|
|
1000
|
+
const [lo, hi] = lightnessWindow(isHighContrast, isDark ? "dark" : "light");
|
|
982
1001
|
return [lo / 100, hi / 100];
|
|
983
1002
|
}
|
|
984
1003
|
function clamp(v, min, max) {
|
|
@@ -1037,26 +1056,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
1037
1056
|
else {
|
|
1038
1057
|
const parsed = parseRelativeOrAbsolute(isHighContrast ? pairHC(rawLightness) : pairNormal(rawLightness));
|
|
1039
1058
|
if (parsed.relative) {
|
|
1040
|
-
|
|
1041
|
-
if (isDark && mode === "auto")
|
|
1042
|
-
preferredL = clamp(baseL + delta, 0, 100);
|
|
1043
|
-
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode);
|
|
1044
|
-
else preferredL =
|
|
1059
|
+
const delta = parsed.value;
|
|
1060
|
+
if (isDark && mode === "auto") preferredL = lightMappedToDark(clamp(getSchemeVariant(baseResolved, false, isHighContrast).l * 100 + delta, 0, 100), isHighContrast);
|
|
1061
|
+
else preferredL = clamp(baseL + delta, 0, 100);
|
|
1062
|
+
} else if (isDark) preferredL = mapLightnessDark(parsed.value, mode, isHighContrast);
|
|
1063
|
+
else preferredL = mapLightnessLight(parsed.value, mode, isHighContrast);
|
|
1045
1064
|
}
|
|
1046
1065
|
const rawContrast = def.contrast;
|
|
1047
1066
|
if (rawContrast !== void 0) {
|
|
1048
1067
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
1049
1068
|
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
|
|
1050
1069
|
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
1051
|
-
const
|
|
1070
|
+
const windowRange = schemeLightnessRange(isDark, mode, isHighContrast);
|
|
1052
1071
|
return {
|
|
1053
1072
|
l: findLightnessForContrast({
|
|
1054
1073
|
hue: effectiveHue,
|
|
1055
1074
|
saturation: effectiveSat,
|
|
1056
|
-
preferredLightness: clamp(preferredL / 100,
|
|
1075
|
+
preferredLightness: clamp(preferredL / 100, windowRange[0], windowRange[1]),
|
|
1057
1076
|
baseLinearRgb,
|
|
1058
1077
|
contrast: minCr,
|
|
1059
|
-
lightnessRange
|
|
1078
|
+
lightnessRange: [0, 1]
|
|
1060
1079
|
}).lightness * 100,
|
|
1061
1080
|
satFactor
|
|
1062
1081
|
};
|
|
@@ -1093,13 +1112,13 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
|
1093
1112
|
let finalL;
|
|
1094
1113
|
let finalSat;
|
|
1095
1114
|
if (isDark && isRoot) {
|
|
1096
|
-
finalL = mapLightnessDark(lightL, mode);
|
|
1115
|
+
finalL = mapLightnessDark(lightL, mode, isHighContrast);
|
|
1097
1116
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1098
1117
|
} else if (isDark && !isRoot) {
|
|
1099
1118
|
finalL = lightL;
|
|
1100
1119
|
finalSat = mapSaturationDark(satFactor * ctx.saturation / 100, mode);
|
|
1101
1120
|
} else if (isRoot) {
|
|
1102
|
-
finalL = mapLightnessLight(lightL, mode);
|
|
1121
|
+
finalL = mapLightnessLight(lightL, mode, isHighContrast);
|
|
1103
1122
|
finalSat = satFactor * ctx.saturation / 100;
|
|
1104
1123
|
} else {
|
|
1105
1124
|
finalL = lightL;
|
|
@@ -1396,10 +1415,14 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
1396
1415
|
};
|
|
1397
1416
|
},
|
|
1398
1417
|
extend(options) {
|
|
1399
|
-
|
|
1400
|
-
|
|
1418
|
+
const newHue = options.hue ?? hue;
|
|
1419
|
+
const newSat = options.saturation ?? saturation;
|
|
1420
|
+
const inheritedColors = {};
|
|
1421
|
+
for (const [name, def] of Object.entries(colorDefs)) if (def.inherit !== false) inheritedColors[name] = def;
|
|
1422
|
+
return createTheme(newHue, newSat, options.colors ? {
|
|
1423
|
+
...inheritedColors,
|
|
1401
1424
|
...options.colors
|
|
1402
|
-
} : { ...
|
|
1425
|
+
} : { ...inheritedColors });
|
|
1403
1426
|
},
|
|
1404
1427
|
resolve() {
|
|
1405
1428
|
return resolveAllColors(hue, saturation, colorDefs);
|
|
@@ -1421,35 +1444,88 @@ function createTheme(hue, saturation, initialColors) {
|
|
|
1421
1444
|
}
|
|
1422
1445
|
};
|
|
1423
1446
|
}
|
|
1424
|
-
function resolvePrefix(options, themeName) {
|
|
1425
|
-
|
|
1426
|
-
if (
|
|
1447
|
+
function resolvePrefix(options, themeName, defaultPrefix = false) {
|
|
1448
|
+
const prefix = options?.prefix ?? defaultPrefix;
|
|
1449
|
+
if (prefix === true) return `${themeName}-`;
|
|
1450
|
+
if (typeof prefix === "object" && prefix !== null) return prefix[themeName] ?? `${themeName}-`;
|
|
1427
1451
|
return "";
|
|
1428
1452
|
}
|
|
1429
|
-
function
|
|
1453
|
+
function validatePrimaryTheme(primary, themes) {
|
|
1454
|
+
if (primary !== void 0 && !(primary in themes)) {
|
|
1455
|
+
const available = Object.keys(themes).join(", ");
|
|
1456
|
+
throw new Error(`glaze: primary theme "${primary}" not found in palette. Available: ${available}.`);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Resolve the effective primary for an export call.
|
|
1461
|
+
* `false` disables, a string overrides, `undefined` inherits from palette.
|
|
1462
|
+
*/
|
|
1463
|
+
function resolveEffectivePrimary(exportPrimary, palettePrimary) {
|
|
1464
|
+
if (exportPrimary === false) return void 0;
|
|
1465
|
+
return exportPrimary ?? palettePrimary;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Filter a resolved color map, skipping keys already in `seen`.
|
|
1469
|
+
* Warns on collision and keeps the first-written value (first-write-wins).
|
|
1470
|
+
* Returns a new map containing only non-colliding entries.
|
|
1471
|
+
*/
|
|
1472
|
+
function filterCollisions(resolved, prefix, seen, themeName, isPrimary) {
|
|
1473
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
1474
|
+
const label = isPrimary ? `${themeName} (primary)` : themeName;
|
|
1475
|
+
for (const [name, color] of resolved) {
|
|
1476
|
+
const key = `${prefix}${name}`;
|
|
1477
|
+
if (seen.has(key)) {
|
|
1478
|
+
console.warn(`glaze: token "${key}" from theme "${label}" collides with theme "${seen.get(key)}" — skipping.`);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
seen.set(key, label);
|
|
1482
|
+
filtered.set(name, color);
|
|
1483
|
+
}
|
|
1484
|
+
return filtered;
|
|
1485
|
+
}
|
|
1486
|
+
function createPalette(themes, paletteOptions) {
|
|
1487
|
+
validatePrimaryTheme(paletteOptions?.primary, themes);
|
|
1430
1488
|
return {
|
|
1431
1489
|
tokens(options) {
|
|
1490
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1491
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1432
1492
|
const modes = resolveModes(options?.modes);
|
|
1433
1493
|
const allTokens = {};
|
|
1494
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1434
1495
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1435
|
-
const
|
|
1496
|
+
const resolved = theme.resolve();
|
|
1497
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1498
|
+
const tokens = buildFlatTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, modes, options?.format);
|
|
1436
1499
|
for (const variant of Object.keys(tokens)) {
|
|
1437
1500
|
if (!allTokens[variant]) allTokens[variant] = {};
|
|
1438
1501
|
Object.assign(allTokens[variant], tokens[variant]);
|
|
1439
1502
|
}
|
|
1503
|
+
if (themeName === effectivePrimary) {
|
|
1504
|
+
const unprefixed = buildFlatTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", modes, options?.format);
|
|
1505
|
+
for (const variant of Object.keys(unprefixed)) Object.assign(allTokens[variant], unprefixed[variant]);
|
|
1506
|
+
}
|
|
1440
1507
|
}
|
|
1441
1508
|
return allTokens;
|
|
1442
1509
|
},
|
|
1443
1510
|
tasty(options) {
|
|
1511
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1512
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1444
1513
|
const states = {
|
|
1445
1514
|
dark: options?.states?.dark ?? globalConfig.states.dark,
|
|
1446
1515
|
highContrast: options?.states?.highContrast ?? globalConfig.states.highContrast
|
|
1447
1516
|
};
|
|
1448
1517
|
const modes = resolveModes(options?.modes);
|
|
1449
1518
|
const allTokens = {};
|
|
1519
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1450
1520
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1451
|
-
const
|
|
1521
|
+
const resolved = theme.resolve();
|
|
1522
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1523
|
+
const tokens = buildTokenMap(filterCollisions(resolved, prefix, seen, themeName), prefix, states, modes, options?.format);
|
|
1452
1524
|
Object.assign(allTokens, tokens);
|
|
1525
|
+
if (themeName === effectivePrimary) {
|
|
1526
|
+
const unprefixed = buildTokenMap(filterCollisions(resolved, "", seen, themeName, true), "", states, modes, options?.format);
|
|
1527
|
+
Object.assign(allTokens, unprefixed);
|
|
1528
|
+
}
|
|
1453
1529
|
}
|
|
1454
1530
|
return allTokens;
|
|
1455
1531
|
},
|
|
@@ -1460,6 +1536,8 @@ function createPalette(themes) {
|
|
|
1460
1536
|
return result;
|
|
1461
1537
|
},
|
|
1462
1538
|
css(options) {
|
|
1539
|
+
const effectivePrimary = resolveEffectivePrimary(options?.primary, paletteOptions?.primary);
|
|
1540
|
+
if (options?.primary !== void 0) validatePrimaryTheme(effectivePrimary, themes);
|
|
1463
1541
|
const suffix = options?.suffix ?? "-color";
|
|
1464
1542
|
const format = options?.format ?? "rgb";
|
|
1465
1543
|
const allLines = {
|
|
@@ -1468,14 +1546,26 @@ function createPalette(themes) {
|
|
|
1468
1546
|
lightContrast: [],
|
|
1469
1547
|
darkContrast: []
|
|
1470
1548
|
};
|
|
1549
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1471
1550
|
for (const [themeName, theme] of Object.entries(themes)) {
|
|
1472
|
-
const
|
|
1551
|
+
const resolved = theme.resolve();
|
|
1552
|
+
const prefix = resolvePrefix(options, themeName, true);
|
|
1553
|
+
const css = buildCssMap(filterCollisions(resolved, prefix, seen, themeName), prefix, suffix, format);
|
|
1473
1554
|
for (const key of [
|
|
1474
1555
|
"light",
|
|
1475
1556
|
"dark",
|
|
1476
1557
|
"lightContrast",
|
|
1477
1558
|
"darkContrast"
|
|
1478
1559
|
]) if (css[key]) allLines[key].push(css[key]);
|
|
1560
|
+
if (themeName === effectivePrimary) {
|
|
1561
|
+
const unprefixed = buildCssMap(filterCollisions(resolved, "", seen, themeName, true), "", suffix, format);
|
|
1562
|
+
for (const key of [
|
|
1563
|
+
"light",
|
|
1564
|
+
"dark",
|
|
1565
|
+
"lightContrast",
|
|
1566
|
+
"darkContrast"
|
|
1567
|
+
]) if (unprefixed[key]) allLines[key].push(unprefixed[key]);
|
|
1568
|
+
}
|
|
1479
1569
|
}
|
|
1480
1570
|
return {
|
|
1481
1571
|
light: allLines.light.join("\n"),
|
|
@@ -1535,6 +1625,7 @@ glaze.configure = function configure(config) {
|
|
|
1535
1625
|
lightLightness: config.lightLightness ?? globalConfig.lightLightness,
|
|
1536
1626
|
darkLightness: config.darkLightness ?? globalConfig.darkLightness,
|
|
1537
1627
|
darkDesaturation: config.darkDesaturation ?? globalConfig.darkDesaturation,
|
|
1628
|
+
darkCurve: config.darkCurve ?? globalConfig.darkCurve,
|
|
1538
1629
|
states: {
|
|
1539
1630
|
dark: config.states?.dark ?? globalConfig.states.dark,
|
|
1540
1631
|
highContrast: config.states?.highContrast ?? globalConfig.states.highContrast
|
|
@@ -1549,8 +1640,8 @@ glaze.configure = function configure(config) {
|
|
|
1549
1640
|
/**
|
|
1550
1641
|
* Compose multiple themes into a palette.
|
|
1551
1642
|
*/
|
|
1552
|
-
glaze.palette = function palette(themes) {
|
|
1553
|
-
return createPalette(themes);
|
|
1643
|
+
glaze.palette = function palette(themes, options) {
|
|
1644
|
+
return createPalette(themes, options);
|
|
1554
1645
|
};
|
|
1555
1646
|
/**
|
|
1556
1647
|
* Create a theme from a serialized export.
|
|
@@ -1634,6 +1725,7 @@ glaze.resetConfig = function resetConfig() {
|
|
|
1634
1725
|
lightLightness: [10, 100],
|
|
1635
1726
|
darkLightness: [15, 95],
|
|
1636
1727
|
darkDesaturation: .1,
|
|
1728
|
+
darkCurve: .5,
|
|
1637
1729
|
states: {
|
|
1638
1730
|
dark: "@dark",
|
|
1639
1731
|
highContrast: "@high-contrast"
|