@tenphi/glaze 0.4.0 → 0.5.1
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 +190 -42
- package/dist/index.cjs +174 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -6
- package/dist/index.d.mts +85 -6
- package/dist/index.mjs +174 -51
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -62,7 +62,15 @@ interface GlazeOutputModes {
|
|
|
62
62
|
/** Include high-contrast variants (both light-HC and dark-HC). Default: false. */
|
|
63
63
|
highContrast?: boolean;
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
/** Hex color string for DX hints. Runtime validation in `parseHex()`. */
|
|
66
|
+
type HexColor = `#${string}`;
|
|
67
|
+
/** Direct OKHSL color input. */
|
|
68
|
+
interface OkhslColor {
|
|
69
|
+
h: number;
|
|
70
|
+
s: number;
|
|
71
|
+
l: number;
|
|
72
|
+
}
|
|
73
|
+
interface RegularColorDef {
|
|
66
74
|
/**
|
|
67
75
|
* Lightness value (0–100).
|
|
68
76
|
* - Number: absolute lightness.
|
|
@@ -83,7 +91,60 @@ interface ColorDef {
|
|
|
83
91
|
contrast?: HCPair<MinContrast>;
|
|
84
92
|
/** Adaptation mode. Default: 'auto'. */
|
|
85
93
|
mode?: AdaptationMode;
|
|
94
|
+
/**
|
|
95
|
+
* Fixed opacity (0–1).
|
|
96
|
+
* Output includes alpha in the CSS value.
|
|
97
|
+
* Does not affect contrast resolution — a semi-transparent color
|
|
98
|
+
* has no fixed perceived lightness, so `contrast` and `opacity`
|
|
99
|
+
* should not be combined (a console.warn is emitted).
|
|
100
|
+
*/
|
|
101
|
+
opacity?: number;
|
|
86
102
|
}
|
|
103
|
+
/** Shadow tuning knobs. All values use the 0–1 scale (OKHSL). */
|
|
104
|
+
interface ShadowTuning {
|
|
105
|
+
/** Fraction of fg saturation kept in pigment (0-1). Default: 0.18. */
|
|
106
|
+
saturationFactor?: number;
|
|
107
|
+
/** Upper clamp on pigment saturation (0-1). Default: 0.25. */
|
|
108
|
+
maxSaturation?: number;
|
|
109
|
+
/** Multiplier for bg lightness → pigment lightness. Default: 0.25. */
|
|
110
|
+
lightnessFactor?: number;
|
|
111
|
+
/** [min, max] clamp for pigment lightness (0-1). Default: [0.05, 0.20]. */
|
|
112
|
+
lightnessBounds?: [number, number];
|
|
113
|
+
/**
|
|
114
|
+
* Target minimum gap between pigment lightness and bg lightness (0-1).
|
|
115
|
+
* Default: 0.05.
|
|
116
|
+
*/
|
|
117
|
+
minGapTarget?: number;
|
|
118
|
+
/** Max alpha (0-1). Reached at intensity=100 with max contrast. Default: 1.0. */
|
|
119
|
+
alphaMax?: number;
|
|
120
|
+
/**
|
|
121
|
+
* Blend weight (0-1) pulling pigment hue toward bg hue.
|
|
122
|
+
* 0 = pure fg hue, 1 = pure bg hue. Default: 0.2.
|
|
123
|
+
*/
|
|
124
|
+
bgHueBlend?: number;
|
|
125
|
+
}
|
|
126
|
+
interface ShadowColorDef {
|
|
127
|
+
type: 'shadow';
|
|
128
|
+
/**
|
|
129
|
+
* Background color name — the surface the shadow sits on.
|
|
130
|
+
* Must reference a non-shadow color in the same theme.
|
|
131
|
+
*/
|
|
132
|
+
bg: string;
|
|
133
|
+
/**
|
|
134
|
+
* Foreground color name for tinting and intensity modulation.
|
|
135
|
+
* Must reference a non-shadow color in the same theme.
|
|
136
|
+
* Omit for achromatic shadow at full user-specified intensity.
|
|
137
|
+
*/
|
|
138
|
+
fg?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Shadow intensity, 0-100.
|
|
141
|
+
* Supports [normal, highContrast] pair.
|
|
142
|
+
*/
|
|
143
|
+
intensity: HCPair<number>;
|
|
144
|
+
/** Override default tuning. Merged field-by-field with global `shadowTuning`. */
|
|
145
|
+
tuning?: ShadowTuning;
|
|
146
|
+
}
|
|
147
|
+
type ColorDef = RegularColorDef | ShadowColorDef;
|
|
87
148
|
type ColorMap = Record<string, ColorDef>;
|
|
88
149
|
/** Resolved color for a single scheme variant. */
|
|
89
150
|
interface ResolvedColorVariant {
|
|
@@ -93,6 +154,8 @@ interface ResolvedColorVariant {
|
|
|
93
154
|
s: number;
|
|
94
155
|
/** OKHSL lightness (0–1). */
|
|
95
156
|
l: number;
|
|
157
|
+
/** Opacity (0–1). Default: 1. */
|
|
158
|
+
alpha: number;
|
|
96
159
|
}
|
|
97
160
|
/** Fully resolved color across all scheme variants. */
|
|
98
161
|
interface ResolvedColor {
|
|
@@ -101,7 +164,8 @@ interface ResolvedColor {
|
|
|
101
164
|
dark: ResolvedColorVariant;
|
|
102
165
|
lightContrast: ResolvedColorVariant;
|
|
103
166
|
darkContrast: ResolvedColorVariant;
|
|
104
|
-
mode
|
|
167
|
+
/** Adaptation mode. Present only for regular colors, omitted for shadows. */
|
|
168
|
+
mode?: AdaptationMode;
|
|
105
169
|
}
|
|
106
170
|
interface GlazeConfig {
|
|
107
171
|
/** Dark scheme lightness window [lo, hi]. Default: [10, 90]. */
|
|
@@ -115,6 +179,8 @@ interface GlazeConfig {
|
|
|
115
179
|
};
|
|
116
180
|
/** Which scheme variants to include in exports. Default: both true. */
|
|
117
181
|
modes?: GlazeOutputModes;
|
|
182
|
+
/** Default tuning for all shadow colors. Per-color tuning merges field-by-field. */
|
|
183
|
+
shadowTuning?: ShadowTuning;
|
|
118
184
|
}
|
|
119
185
|
interface GlazeConfigResolved {
|
|
120
186
|
darkLightness: [number, number];
|
|
@@ -124,6 +190,7 @@ interface GlazeConfigResolved {
|
|
|
124
190
|
highContrast: string;
|
|
125
191
|
};
|
|
126
192
|
modes: Required<GlazeOutputModes>;
|
|
193
|
+
shadowTuning?: ShadowTuning;
|
|
127
194
|
}
|
|
128
195
|
/** Serialized theme configuration (no resolved values). */
|
|
129
196
|
interface GlazeThemeExport {
|
|
@@ -131,6 +198,16 @@ interface GlazeThemeExport {
|
|
|
131
198
|
saturation: number;
|
|
132
199
|
colors: ColorMap;
|
|
133
200
|
}
|
|
201
|
+
/** Input for `glaze.shadow()` standalone factory. */
|
|
202
|
+
interface GlazeShadowInput {
|
|
203
|
+
/** Background color — hex string or OKHSL { h, s (0-1), l (0-1) }. */
|
|
204
|
+
bg: HexColor | OkhslColor;
|
|
205
|
+
/** Foreground color for tinting + intensity modulation. */
|
|
206
|
+
fg?: HexColor | OkhslColor;
|
|
207
|
+
/** Intensity 0-100. */
|
|
208
|
+
intensity: number;
|
|
209
|
+
tuning?: ShadowTuning;
|
|
210
|
+
}
|
|
134
211
|
/** Input for `glaze.color()` standalone factory. */
|
|
135
212
|
interface GlazeColorInput {
|
|
136
213
|
hue: number;
|
|
@@ -298,6 +375,8 @@ declare namespace glaze {
|
|
|
298
375
|
};
|
|
299
376
|
var from: (data: GlazeThemeExport) => GlazeTheme;
|
|
300
377
|
var color: (input: GlazeColorInput) => GlazeColorToken;
|
|
378
|
+
var shadow: (input: GlazeShadowInput) => ResolvedColorVariant;
|
|
379
|
+
var format: (variant: ResolvedColorVariant, colorFormat?: GlazeColorFormat) => string;
|
|
301
380
|
var fromHex: (hex: string) => GlazeTheme;
|
|
302
381
|
var fromRgb: (r: number, g: number, b: number) => GlazeTheme;
|
|
303
382
|
var getConfig: () => GlazeConfigResolved;
|
|
@@ -350,20 +429,20 @@ declare function parseHex(hex: string): [number, number, number] | null;
|
|
|
350
429
|
*/
|
|
351
430
|
declare function formatOkhsl(h: number, s: number, l: number): string;
|
|
352
431
|
/**
|
|
353
|
-
* Format OKHSL values as a CSS `rgb(R
|
|
432
|
+
* Format OKHSL values as a CSS `rgb(R G B)` string with rounded integer values.
|
|
354
433
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
355
434
|
*/
|
|
356
435
|
declare function formatRgb(h: number, s: number, l: number): string;
|
|
357
436
|
/**
|
|
358
|
-
* Format OKHSL values as a CSS `hsl(H
|
|
437
|
+
* Format OKHSL values as a CSS `hsl(H S% L%)` string.
|
|
359
438
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
360
439
|
*/
|
|
361
440
|
declare function formatHsl(h: number, s: number, l: number): string;
|
|
362
441
|
/**
|
|
363
|
-
* Format OKHSL values as a CSS `oklch(L
|
|
442
|
+
* Format OKHSL values as a CSS `oklch(L C H)` string.
|
|
364
443
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
365
444
|
*/
|
|
366
445
|
declare function formatOklch(h: number, s: number, l: number): string;
|
|
367
446
|
//#endregion
|
|
368
|
-
export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type MinContrast, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, contrastRatioFromLuminance, findLightnessForContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
447
|
+
export { type AdaptationMode, type ColorDef, type ColorMap, type ContrastPreset, type FindLightnessForContrastOptions, type FindLightnessForContrastResult, type GlazeColorFormat, type GlazeColorInput, type GlazeColorToken, type GlazeConfig, type GlazeCssOptions, type GlazeCssResult, type GlazeExtendOptions, type GlazeJsonOptions, type GlazeOutputModes, type GlazePalette, type GlazeShadowInput, type GlazeTheme, type GlazeThemeExport, type GlazeTokenOptions, type HCPair, type HexColor, type MinContrast, type OkhslColor, type RegularColorDef, type RelativeValue, type ResolvedColor, type ResolvedColorVariant, type ShadowColorDef, type ShadowTuning, contrastRatioFromLuminance, findLightnessForContrast, formatHsl, formatOkhsl, formatOklch, formatRgb, glaze, okhslToLinearSrgb, okhslToOklab, okhslToSrgb, parseHex, relativeLuminanceFromLinearRgb, resolveMinContrast, srgbToOkhsl };
|
|
369
448
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -439,23 +439,26 @@ function parseHex(hex) {
|
|
|
439
439
|
}
|
|
440
440
|
return null;
|
|
441
441
|
}
|
|
442
|
+
function fmt$1(value, decimals) {
|
|
443
|
+
return parseFloat(value.toFixed(decimals)).toString();
|
|
444
|
+
}
|
|
442
445
|
/**
|
|
443
446
|
* Format OKHSL values as a CSS `okhsl(H S% L%)` string.
|
|
444
447
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
445
448
|
*/
|
|
446
449
|
function formatOkhsl(h, s, l) {
|
|
447
|
-
return `okhsl(${h
|
|
450
|
+
return `okhsl(${fmt$1(h, 1)} ${fmt$1(s, 1)}% ${fmt$1(l, 1)}%)`;
|
|
448
451
|
}
|
|
449
452
|
/**
|
|
450
|
-
* Format OKHSL values as a CSS `rgb(R
|
|
453
|
+
* Format OKHSL values as a CSS `rgb(R G B)` string with rounded integer values.
|
|
451
454
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
452
455
|
*/
|
|
453
456
|
function formatRgb(h, s, l) {
|
|
454
457
|
const [r, g, b] = okhslToSrgb(h, s / 100, l / 100);
|
|
455
|
-
return `rgb(${(r * 255)
|
|
458
|
+
return `rgb(${Math.round(r * 255)} ${Math.round(g * 255)} ${Math.round(b * 255)})`;
|
|
456
459
|
}
|
|
457
460
|
/**
|
|
458
|
-
* Format OKHSL values as a CSS `hsl(H
|
|
461
|
+
* Format OKHSL values as a CSS `hsl(H S% L%)` string.
|
|
459
462
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
460
463
|
*/
|
|
461
464
|
function formatHsl(h, s, l) {
|
|
@@ -472,10 +475,10 @@ function formatHsl(h, s, l) {
|
|
|
472
475
|
else if (max === g) hh = ((b - r) / delta + 2) * 60;
|
|
473
476
|
else hh = ((r - g) / delta + 4) * 60;
|
|
474
477
|
}
|
|
475
|
-
return `hsl(${hh
|
|
478
|
+
return `hsl(${fmt$1(hh, 1)} ${fmt$1(ss * 100, 1)}% ${fmt$1(ll * 100, 1)}%)`;
|
|
476
479
|
}
|
|
477
480
|
/**
|
|
478
|
-
* Format OKHSL values as a CSS `oklch(L
|
|
481
|
+
* Format OKHSL values as a CSS `oklch(L C H)` string.
|
|
479
482
|
* h: 0–360, s: 0–100, l: 0–100 (percentage scale for s and l).
|
|
480
483
|
*/
|
|
481
484
|
function formatOklch(h, s, l) {
|
|
@@ -483,7 +486,7 @@ function formatOklch(h, s, l) {
|
|
|
483
486
|
const C = Math.sqrt(a * a + b * b);
|
|
484
487
|
let hh = Math.atan2(b, a) * (180 / Math.PI);
|
|
485
488
|
hh = constrainAngle(hh);
|
|
486
|
-
return `oklch(${(L
|
|
489
|
+
return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 1)})`;
|
|
487
490
|
}
|
|
488
491
|
|
|
489
492
|
//#endregion
|
|
@@ -709,14 +712,84 @@ function pairNormal(p) {
|
|
|
709
712
|
function pairHC(p) {
|
|
710
713
|
return Array.isArray(p) ? p[1] : p;
|
|
711
714
|
}
|
|
715
|
+
function isShadowDef(def) {
|
|
716
|
+
return def.type === "shadow";
|
|
717
|
+
}
|
|
718
|
+
const DEFAULT_SHADOW_TUNING = {
|
|
719
|
+
saturationFactor: .18,
|
|
720
|
+
maxSaturation: .25,
|
|
721
|
+
lightnessFactor: .25,
|
|
722
|
+
lightnessBounds: [.05, .2],
|
|
723
|
+
minGapTarget: .05,
|
|
724
|
+
alphaMax: 1,
|
|
725
|
+
bgHueBlend: .2
|
|
726
|
+
};
|
|
727
|
+
function resolveShadowTuning(perColor) {
|
|
728
|
+
return {
|
|
729
|
+
...DEFAULT_SHADOW_TUNING,
|
|
730
|
+
...globalConfig.shadowTuning,
|
|
731
|
+
...perColor,
|
|
732
|
+
lightnessBounds: perColor?.lightnessBounds ?? globalConfig.shadowTuning?.lightnessBounds ?? DEFAULT_SHADOW_TUNING.lightnessBounds
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function circularLerp(a, b, t) {
|
|
736
|
+
let diff = b - a;
|
|
737
|
+
if (diff > 180) diff -= 360;
|
|
738
|
+
else if (diff < -180) diff += 360;
|
|
739
|
+
return ((a + diff * t) % 360 + 360) % 360;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Compute the canonical max-contrast reference t value for normalization.
|
|
743
|
+
* Uses bg.l=1, fg.l=0, intensity=100 — the theoretical maximum.
|
|
744
|
+
* This is a fixed constant per tuning configuration, ensuring uniform
|
|
745
|
+
* scaling across all bg/fg pairs at low intensities.
|
|
746
|
+
*/
|
|
747
|
+
function computeRefT(tuning) {
|
|
748
|
+
const EPSILON = 1e-6;
|
|
749
|
+
let lShRef = clamp(tuning.lightnessFactor, tuning.lightnessBounds[0], tuning.lightnessBounds[1]);
|
|
750
|
+
lShRef = Math.max(Math.min(lShRef, 1 - tuning.minGapTarget), 0);
|
|
751
|
+
return 1 / Math.max(1 - lShRef, EPSILON);
|
|
752
|
+
}
|
|
753
|
+
function computeShadow(bg, fg, intensity, tuning) {
|
|
754
|
+
const EPSILON = 1e-6;
|
|
755
|
+
const clampedIntensity = clamp(intensity, 0, 100);
|
|
756
|
+
const contrastWeight = fg ? Math.abs(bg.l - fg.l) : 1;
|
|
757
|
+
const deltaL = clampedIntensity / 100 * contrastWeight;
|
|
758
|
+
const h = fg ? circularLerp(fg.h, bg.h, tuning.bgHueBlend) : bg.h;
|
|
759
|
+
const s = fg ? Math.min(fg.s * tuning.saturationFactor, tuning.maxSaturation) : 0;
|
|
760
|
+
let lSh = clamp(bg.l * tuning.lightnessFactor, tuning.lightnessBounds[0], tuning.lightnessBounds[1]);
|
|
761
|
+
lSh = Math.max(Math.min(lSh, bg.l - tuning.minGapTarget), 0);
|
|
762
|
+
const t = deltaL / Math.max(bg.l - lSh, EPSILON);
|
|
763
|
+
const tRef = computeRefT(tuning);
|
|
764
|
+
const norm = Math.tanh(tRef / tuning.alphaMax);
|
|
765
|
+
const alpha = Math.min(tuning.alphaMax * Math.tanh(t / tuning.alphaMax) / norm, tuning.alphaMax);
|
|
766
|
+
return {
|
|
767
|
+
h,
|
|
768
|
+
s,
|
|
769
|
+
l: lSh,
|
|
770
|
+
alpha
|
|
771
|
+
};
|
|
772
|
+
}
|
|
712
773
|
function validateColorDefs(defs) {
|
|
713
774
|
const names = new Set(Object.keys(defs));
|
|
714
775
|
for (const [name, def] of Object.entries(defs)) {
|
|
715
|
-
if (def
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
776
|
+
if (isShadowDef(def)) {
|
|
777
|
+
if (!names.has(def.bg)) throw new Error(`glaze: shadow "${name}" references non-existent bg "${def.bg}".`);
|
|
778
|
+
if (isShadowDef(defs[def.bg])) throw new Error(`glaze: shadow "${name}" bg "${def.bg}" references another shadow color.`);
|
|
779
|
+
if (def.fg !== void 0) {
|
|
780
|
+
if (!names.has(def.fg)) throw new Error(`glaze: shadow "${name}" references non-existent fg "${def.fg}".`);
|
|
781
|
+
if (isShadowDef(defs[def.fg])) throw new Error(`glaze: shadow "${name}" fg "${def.fg}" references another shadow color.`);
|
|
782
|
+
}
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const regDef = def;
|
|
786
|
+
if (regDef.contrast !== void 0 && !regDef.base) throw new Error(`glaze: color "${name}" has "contrast" without "base".`);
|
|
787
|
+
if (regDef.lightness !== void 0 && !isAbsoluteLightness(regDef.lightness) && !regDef.base) throw new Error(`glaze: color "${name}" has relative "lightness" without "base".`);
|
|
788
|
+
if (isAbsoluteLightness(regDef.lightness) && regDef.base !== void 0) console.warn(`glaze: color "${name}" has absolute "lightness" and "base". Absolute lightness takes precedence.`);
|
|
789
|
+
if (regDef.base && !names.has(regDef.base)) throw new Error(`glaze: color "${name}" references non-existent base "${regDef.base}".`);
|
|
790
|
+
if (regDef.base && isShadowDef(defs[regDef.base])) throw new Error(`glaze: color "${name}" base "${regDef.base}" references a shadow color.`);
|
|
791
|
+
if (!isAbsoluteLightness(regDef.lightness) && regDef.base === void 0) throw new Error(`glaze: color "${name}" must have either absolute "lightness" (root) or "base" (dependent).`);
|
|
792
|
+
if (regDef.contrast !== void 0 && regDef.opacity !== void 0) console.warn(`glaze: color "${name}" has both "contrast" and "opacity". Opacity makes perceived lightness unpredictable.`);
|
|
720
793
|
}
|
|
721
794
|
const visited = /* @__PURE__ */ new Set();
|
|
722
795
|
const inStack = /* @__PURE__ */ new Set();
|
|
@@ -725,7 +798,13 @@ function validateColorDefs(defs) {
|
|
|
725
798
|
if (visited.has(name)) return;
|
|
726
799
|
inStack.add(name);
|
|
727
800
|
const def = defs[name];
|
|
728
|
-
if (
|
|
801
|
+
if (isShadowDef(def)) {
|
|
802
|
+
dfs(def.bg);
|
|
803
|
+
if (def.fg) dfs(def.fg);
|
|
804
|
+
} else {
|
|
805
|
+
const regDef = def;
|
|
806
|
+
if (regDef.base && !isAbsoluteLightness(regDef.lightness)) dfs(regDef.base);
|
|
807
|
+
}
|
|
729
808
|
inStack.delete(name);
|
|
730
809
|
visited.add(name);
|
|
731
810
|
}
|
|
@@ -738,7 +817,13 @@ function topoSort(defs) {
|
|
|
738
817
|
if (visited.has(name)) return;
|
|
739
818
|
visited.add(name);
|
|
740
819
|
const def = defs[name];
|
|
741
|
-
if (
|
|
820
|
+
if (isShadowDef(def)) {
|
|
821
|
+
visit(def.bg);
|
|
822
|
+
if (def.fg) visit(def.fg);
|
|
823
|
+
} else {
|
|
824
|
+
const regDef = def;
|
|
825
|
+
if (regDef.base && !isAbsoluteLightness(regDef.lightness)) visit(regDef.base);
|
|
826
|
+
}
|
|
742
827
|
result.push(name);
|
|
743
828
|
}
|
|
744
829
|
for (const name of Object.keys(defs)) visit(name);
|
|
@@ -802,11 +887,8 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
802
887
|
if (!baseResolved) throw new Error(`glaze: base "${baseName}" not yet resolved for "${name}".`);
|
|
803
888
|
const mode = def.mode ?? "auto";
|
|
804
889
|
const satFactor = clamp(def.saturation ?? 1, 0, 1);
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
else if (isDark) baseL = baseResolved.dark.l * 100;
|
|
808
|
-
else if (isHighContrast) baseL = baseResolved.lightContrast.l * 100;
|
|
809
|
-
else baseL = baseResolved.light.l * 100;
|
|
890
|
+
const baseVariant = getSchemeVariant(baseResolved, isDark, isHighContrast);
|
|
891
|
+
const baseL = baseVariant.l * 100;
|
|
810
892
|
let preferredL;
|
|
811
893
|
const rawLightness = def.lightness;
|
|
812
894
|
if (rawLightness === void 0) preferredL = baseL;
|
|
@@ -823,27 +905,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
823
905
|
if (rawContrast !== void 0) {
|
|
824
906
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
825
907
|
const effectiveSat = isDark ? mapSaturationDark(satFactor * ctx.saturation / 100, mode) : satFactor * ctx.saturation / 100;
|
|
826
|
-
|
|
827
|
-
let baseS;
|
|
828
|
-
let baseLNorm;
|
|
829
|
-
if (isDark && isHighContrast) {
|
|
830
|
-
baseH = baseResolved.darkContrast.h;
|
|
831
|
-
baseS = baseResolved.darkContrast.s;
|
|
832
|
-
baseLNorm = baseResolved.darkContrast.l;
|
|
833
|
-
} else if (isDark) {
|
|
834
|
-
baseH = baseResolved.dark.h;
|
|
835
|
-
baseS = baseResolved.dark.s;
|
|
836
|
-
baseLNorm = baseResolved.dark.l;
|
|
837
|
-
} else if (isHighContrast) {
|
|
838
|
-
baseH = baseResolved.lightContrast.h;
|
|
839
|
-
baseS = baseResolved.lightContrast.s;
|
|
840
|
-
baseLNorm = baseResolved.lightContrast.l;
|
|
841
|
-
} else {
|
|
842
|
-
baseH = baseResolved.light.h;
|
|
843
|
-
baseS = baseResolved.light.s;
|
|
844
|
-
baseLNorm = baseResolved.light.l;
|
|
845
|
-
}
|
|
846
|
-
const baseLinearRgb = okhslToLinearSrgb(baseH, baseS, baseLNorm);
|
|
908
|
+
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
847
909
|
return {
|
|
848
910
|
l: findLightnessForContrast({
|
|
849
911
|
hue: effectiveHue,
|
|
@@ -860,18 +922,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
860
922
|
satFactor
|
|
861
923
|
};
|
|
862
924
|
}
|
|
925
|
+
function getSchemeVariant(color, isDark, isHighContrast) {
|
|
926
|
+
if (isDark && isHighContrast) return color.darkContrast;
|
|
927
|
+
if (isDark) return color.dark;
|
|
928
|
+
if (isHighContrast) return color.lightContrast;
|
|
929
|
+
return color.light;
|
|
930
|
+
}
|
|
863
931
|
function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
const
|
|
932
|
+
if (isShadowDef(def)) return resolveShadowForScheme(def, ctx, isDark, isHighContrast);
|
|
933
|
+
const regDef = def;
|
|
934
|
+
const mode = regDef.mode ?? "auto";
|
|
935
|
+
const isRoot = isAbsoluteLightness(regDef.lightness) && !regDef.base;
|
|
936
|
+
const effectiveHue = resolveEffectiveHue(ctx.hue, regDef.hue);
|
|
867
937
|
let lightL;
|
|
868
938
|
let satFactor;
|
|
869
939
|
if (isRoot) {
|
|
870
|
-
const root = resolveRootColor(name,
|
|
940
|
+
const root = resolveRootColor(name, regDef, ctx, isHighContrast);
|
|
871
941
|
lightL = root.lightL;
|
|
872
942
|
satFactor = root.satFactor;
|
|
873
943
|
} else {
|
|
874
|
-
const dep = resolveDependentColor(name,
|
|
944
|
+
const dep = resolveDependentColor(name, regDef, ctx, isHighContrast, isDark, effectiveHue);
|
|
875
945
|
lightL = dep.l;
|
|
876
946
|
satFactor = dep.satFactor;
|
|
877
947
|
}
|
|
@@ -890,9 +960,18 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
|
890
960
|
return {
|
|
891
961
|
h: effectiveHue,
|
|
892
962
|
s: clamp(finalSat, 0, 1),
|
|
893
|
-
l: clamp(finalL / 100, 0, 1)
|
|
963
|
+
l: clamp(finalL / 100, 0, 1),
|
|
964
|
+
alpha: regDef.opacity ?? 1
|
|
894
965
|
};
|
|
895
966
|
}
|
|
967
|
+
function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
|
|
968
|
+
const bgVariant = getSchemeVariant(ctx.resolved.get(def.bg), isDark, isHighContrast);
|
|
969
|
+
let fgVariant;
|
|
970
|
+
if (def.fg) fgVariant = getSchemeVariant(ctx.resolved.get(def.fg), isDark, isHighContrast);
|
|
971
|
+
const intensity = isHighContrast ? pairHC(def.intensity) : pairNormal(def.intensity);
|
|
972
|
+
const tuning = resolveShadowTuning(def.tuning);
|
|
973
|
+
return computeShadow(bgVariant, fgVariant, intensity, tuning);
|
|
974
|
+
}
|
|
896
975
|
function resolveAllColors(hue, saturation, defs) {
|
|
897
976
|
validateColorDefs(defs);
|
|
898
977
|
const order = topoSort(defs);
|
|
@@ -902,6 +981,9 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
902
981
|
defs,
|
|
903
982
|
resolved: /* @__PURE__ */ new Map()
|
|
904
983
|
};
|
|
984
|
+
function defMode(def) {
|
|
985
|
+
return isShadowDef(def) ? void 0 : def.mode ?? "auto";
|
|
986
|
+
}
|
|
905
987
|
const lightMap = /* @__PURE__ */ new Map();
|
|
906
988
|
for (const name of order) {
|
|
907
989
|
const variant = resolveColorForScheme(name, defs[name], ctx, false, false);
|
|
@@ -912,7 +994,7 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
912
994
|
dark: variant,
|
|
913
995
|
lightContrast: variant,
|
|
914
996
|
darkContrast: variant,
|
|
915
|
-
mode: defs[name]
|
|
997
|
+
mode: defMode(defs[name])
|
|
916
998
|
});
|
|
917
999
|
}
|
|
918
1000
|
const lightHCMap = /* @__PURE__ */ new Map();
|
|
@@ -935,7 +1017,7 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
935
1017
|
dark: lightMap.get(name),
|
|
936
1018
|
lightContrast: lightHCMap.get(name),
|
|
937
1019
|
darkContrast: lightHCMap.get(name),
|
|
938
|
-
mode: defs[name]
|
|
1020
|
+
mode: defMode(defs[name])
|
|
939
1021
|
});
|
|
940
1022
|
for (const name of order) {
|
|
941
1023
|
const variant = resolveColorForScheme(name, defs[name], ctx, true, false);
|
|
@@ -965,7 +1047,7 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
965
1047
|
dark: darkMap.get(name),
|
|
966
1048
|
lightContrast: lightHCMap.get(name),
|
|
967
1049
|
darkContrast: darkHCMap.get(name),
|
|
968
|
-
mode: defs[name]
|
|
1050
|
+
mode: defMode(defs[name])
|
|
969
1051
|
});
|
|
970
1052
|
return result;
|
|
971
1053
|
}
|
|
@@ -975,8 +1057,14 @@ const formatters = {
|
|
|
975
1057
|
hsl: formatHsl,
|
|
976
1058
|
oklch: formatOklch
|
|
977
1059
|
};
|
|
1060
|
+
function fmt(value, decimals) {
|
|
1061
|
+
return parseFloat(value.toFixed(decimals)).toString();
|
|
1062
|
+
}
|
|
978
1063
|
function formatVariant(v, format = "okhsl") {
|
|
979
|
-
|
|
1064
|
+
const base = formatters[format](v.h, v.s * 100, v.l * 100);
|
|
1065
|
+
if (v.alpha >= 1) return base;
|
|
1066
|
+
const closing = base.lastIndexOf(")");
|
|
1067
|
+
return `${base.slice(0, closing)} / ${fmt(v.alpha, 4)})`;
|
|
980
1068
|
}
|
|
981
1069
|
function resolveModes(override) {
|
|
982
1070
|
return {
|
|
@@ -1227,7 +1315,8 @@ glaze.configure = function configure(config) {
|
|
|
1227
1315
|
modes: {
|
|
1228
1316
|
dark: config.modes?.dark ?? globalConfig.modes.dark,
|
|
1229
1317
|
highContrast: config.modes?.highContrast ?? globalConfig.modes.highContrast
|
|
1230
|
-
}
|
|
1318
|
+
},
|
|
1319
|
+
shadowTuning: config.shadowTuning ?? globalConfig.shadowTuning
|
|
1231
1320
|
};
|
|
1232
1321
|
};
|
|
1233
1322
|
/**
|
|
@@ -1249,6 +1338,40 @@ glaze.color = function color(input) {
|
|
|
1249
1338
|
return createColorToken(input);
|
|
1250
1339
|
};
|
|
1251
1340
|
/**
|
|
1341
|
+
* Compute a shadow color from a bg/fg pair and intensity.
|
|
1342
|
+
*/
|
|
1343
|
+
glaze.shadow = function shadow(input) {
|
|
1344
|
+
const bg = parseOkhslInput(input.bg);
|
|
1345
|
+
const fg = input.fg ? parseOkhslInput(input.fg) : void 0;
|
|
1346
|
+
const tuning = resolveShadowTuning(input.tuning);
|
|
1347
|
+
return computeShadow({
|
|
1348
|
+
...bg,
|
|
1349
|
+
alpha: 1
|
|
1350
|
+
}, fg ? {
|
|
1351
|
+
...fg,
|
|
1352
|
+
alpha: 1
|
|
1353
|
+
} : void 0, input.intensity, tuning);
|
|
1354
|
+
};
|
|
1355
|
+
/**
|
|
1356
|
+
* Format a resolved color variant as a CSS string.
|
|
1357
|
+
*/
|
|
1358
|
+
glaze.format = function format(variant, colorFormat) {
|
|
1359
|
+
return formatVariant(variant, colorFormat);
|
|
1360
|
+
};
|
|
1361
|
+
function parseOkhslInput(input) {
|
|
1362
|
+
if (typeof input === "string") {
|
|
1363
|
+
const rgb = parseHex(input);
|
|
1364
|
+
if (!rgb) throw new Error(`glaze: invalid hex color "${input}".`);
|
|
1365
|
+
const [h, s, l] = srgbToOkhsl(rgb);
|
|
1366
|
+
return {
|
|
1367
|
+
h,
|
|
1368
|
+
s,
|
|
1369
|
+
l
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
return input;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1252
1375
|
* Create a theme from a hex color string.
|
|
1253
1376
|
* Extracts hue and saturation from the color.
|
|
1254
1377
|
*/
|