@tenphi/glaze 0.4.0 → 0.5.0
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 +160 -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 +160 -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
|
+
/** Asymptotic max alpha (0-1). Default: 0.6. */
|
|
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,70 @@ 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: .6,
|
|
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
|
+
function computeShadow(bg, fg, intensity, tuning) {
|
|
742
|
+
const EPSILON = 1e-6;
|
|
743
|
+
const clampedIntensity = clamp(intensity, 0, 100);
|
|
744
|
+
const contrastWeight = fg ? Math.abs(bg.l - fg.l) : 1;
|
|
745
|
+
const deltaL = clampedIntensity / 100 * contrastWeight;
|
|
746
|
+
const h = fg ? circularLerp(fg.h, bg.h, tuning.bgHueBlend) : bg.h;
|
|
747
|
+
const s = fg ? Math.min(fg.s * tuning.saturationFactor, tuning.maxSaturation) : 0;
|
|
748
|
+
let lSh = clamp(bg.l * tuning.lightnessFactor, tuning.lightnessBounds[0], tuning.lightnessBounds[1]);
|
|
749
|
+
lSh = Math.max(Math.min(lSh, bg.l - tuning.minGapTarget), 0);
|
|
750
|
+
const t = deltaL / Math.max(bg.l - lSh, EPSILON);
|
|
751
|
+
const alpha = tuning.alphaMax * Math.tanh(t / tuning.alphaMax);
|
|
752
|
+
return {
|
|
753
|
+
h,
|
|
754
|
+
s,
|
|
755
|
+
l: lSh,
|
|
756
|
+
alpha
|
|
757
|
+
};
|
|
758
|
+
}
|
|
712
759
|
function validateColorDefs(defs) {
|
|
713
760
|
const names = new Set(Object.keys(defs));
|
|
714
761
|
for (const [name, def] of Object.entries(defs)) {
|
|
715
|
-
if (def
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
762
|
+
if (isShadowDef(def)) {
|
|
763
|
+
if (!names.has(def.bg)) throw new Error(`glaze: shadow "${name}" references non-existent bg "${def.bg}".`);
|
|
764
|
+
if (isShadowDef(defs[def.bg])) throw new Error(`glaze: shadow "${name}" bg "${def.bg}" references another shadow color.`);
|
|
765
|
+
if (def.fg !== void 0) {
|
|
766
|
+
if (!names.has(def.fg)) throw new Error(`glaze: shadow "${name}" references non-existent fg "${def.fg}".`);
|
|
767
|
+
if (isShadowDef(defs[def.fg])) throw new Error(`glaze: shadow "${name}" fg "${def.fg}" references another shadow color.`);
|
|
768
|
+
}
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
const regDef = def;
|
|
772
|
+
if (regDef.contrast !== void 0 && !regDef.base) throw new Error(`glaze: color "${name}" has "contrast" without "base".`);
|
|
773
|
+
if (regDef.lightness !== void 0 && !isAbsoluteLightness(regDef.lightness) && !regDef.base) throw new Error(`glaze: color "${name}" has relative "lightness" without "base".`);
|
|
774
|
+
if (isAbsoluteLightness(regDef.lightness) && regDef.base !== void 0) console.warn(`glaze: color "${name}" has absolute "lightness" and "base". Absolute lightness takes precedence.`);
|
|
775
|
+
if (regDef.base && !names.has(regDef.base)) throw new Error(`glaze: color "${name}" references non-existent base "${regDef.base}".`);
|
|
776
|
+
if (regDef.base && isShadowDef(defs[regDef.base])) throw new Error(`glaze: color "${name}" base "${regDef.base}" references a shadow color.`);
|
|
777
|
+
if (!isAbsoluteLightness(regDef.lightness) && regDef.base === void 0) throw new Error(`glaze: color "${name}" must have either absolute "lightness" (root) or "base" (dependent).`);
|
|
778
|
+
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
779
|
}
|
|
721
780
|
const visited = /* @__PURE__ */ new Set();
|
|
722
781
|
const inStack = /* @__PURE__ */ new Set();
|
|
@@ -725,7 +784,13 @@ function validateColorDefs(defs) {
|
|
|
725
784
|
if (visited.has(name)) return;
|
|
726
785
|
inStack.add(name);
|
|
727
786
|
const def = defs[name];
|
|
728
|
-
if (
|
|
787
|
+
if (isShadowDef(def)) {
|
|
788
|
+
dfs(def.bg);
|
|
789
|
+
if (def.fg) dfs(def.fg);
|
|
790
|
+
} else {
|
|
791
|
+
const regDef = def;
|
|
792
|
+
if (regDef.base && !isAbsoluteLightness(regDef.lightness)) dfs(regDef.base);
|
|
793
|
+
}
|
|
729
794
|
inStack.delete(name);
|
|
730
795
|
visited.add(name);
|
|
731
796
|
}
|
|
@@ -738,7 +803,13 @@ function topoSort(defs) {
|
|
|
738
803
|
if (visited.has(name)) return;
|
|
739
804
|
visited.add(name);
|
|
740
805
|
const def = defs[name];
|
|
741
|
-
if (
|
|
806
|
+
if (isShadowDef(def)) {
|
|
807
|
+
visit(def.bg);
|
|
808
|
+
if (def.fg) visit(def.fg);
|
|
809
|
+
} else {
|
|
810
|
+
const regDef = def;
|
|
811
|
+
if (regDef.base && !isAbsoluteLightness(regDef.lightness)) visit(regDef.base);
|
|
812
|
+
}
|
|
742
813
|
result.push(name);
|
|
743
814
|
}
|
|
744
815
|
for (const name of Object.keys(defs)) visit(name);
|
|
@@ -802,11 +873,8 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
802
873
|
if (!baseResolved) throw new Error(`glaze: base "${baseName}" not yet resolved for "${name}".`);
|
|
803
874
|
const mode = def.mode ?? "auto";
|
|
804
875
|
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;
|
|
876
|
+
const baseVariant = getSchemeVariant(baseResolved, isDark, isHighContrast);
|
|
877
|
+
const baseL = baseVariant.l * 100;
|
|
810
878
|
let preferredL;
|
|
811
879
|
const rawLightness = def.lightness;
|
|
812
880
|
if (rawLightness === void 0) preferredL = baseL;
|
|
@@ -823,27 +891,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
823
891
|
if (rawContrast !== void 0) {
|
|
824
892
|
const minCr = isHighContrast ? pairHC(rawContrast) : pairNormal(rawContrast);
|
|
825
893
|
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);
|
|
894
|
+
const baseLinearRgb = okhslToLinearSrgb(baseVariant.h, baseVariant.s, baseVariant.l);
|
|
847
895
|
return {
|
|
848
896
|
l: findLightnessForContrast({
|
|
849
897
|
hue: effectiveHue,
|
|
@@ -860,18 +908,26 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, effective
|
|
|
860
908
|
satFactor
|
|
861
909
|
};
|
|
862
910
|
}
|
|
911
|
+
function getSchemeVariant(color, isDark, isHighContrast) {
|
|
912
|
+
if (isDark && isHighContrast) return color.darkContrast;
|
|
913
|
+
if (isDark) return color.dark;
|
|
914
|
+
if (isHighContrast) return color.lightContrast;
|
|
915
|
+
return color.light;
|
|
916
|
+
}
|
|
863
917
|
function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
const
|
|
918
|
+
if (isShadowDef(def)) return resolveShadowForScheme(def, ctx, isDark, isHighContrast);
|
|
919
|
+
const regDef = def;
|
|
920
|
+
const mode = regDef.mode ?? "auto";
|
|
921
|
+
const isRoot = isAbsoluteLightness(regDef.lightness) && !regDef.base;
|
|
922
|
+
const effectiveHue = resolveEffectiveHue(ctx.hue, regDef.hue);
|
|
867
923
|
let lightL;
|
|
868
924
|
let satFactor;
|
|
869
925
|
if (isRoot) {
|
|
870
|
-
const root = resolveRootColor(name,
|
|
926
|
+
const root = resolveRootColor(name, regDef, ctx, isHighContrast);
|
|
871
927
|
lightL = root.lightL;
|
|
872
928
|
satFactor = root.satFactor;
|
|
873
929
|
} else {
|
|
874
|
-
const dep = resolveDependentColor(name,
|
|
930
|
+
const dep = resolveDependentColor(name, regDef, ctx, isHighContrast, isDark, effectiveHue);
|
|
875
931
|
lightL = dep.l;
|
|
876
932
|
satFactor = dep.satFactor;
|
|
877
933
|
}
|
|
@@ -890,9 +946,18 @@ function resolveColorForScheme(name, def, ctx, isDark, isHighContrast) {
|
|
|
890
946
|
return {
|
|
891
947
|
h: effectiveHue,
|
|
892
948
|
s: clamp(finalSat, 0, 1),
|
|
893
|
-
l: clamp(finalL / 100, 0, 1)
|
|
949
|
+
l: clamp(finalL / 100, 0, 1),
|
|
950
|
+
alpha: regDef.opacity ?? 1
|
|
894
951
|
};
|
|
895
952
|
}
|
|
953
|
+
function resolveShadowForScheme(def, ctx, isDark, isHighContrast) {
|
|
954
|
+
const bgVariant = getSchemeVariant(ctx.resolved.get(def.bg), isDark, isHighContrast);
|
|
955
|
+
let fgVariant;
|
|
956
|
+
if (def.fg) fgVariant = getSchemeVariant(ctx.resolved.get(def.fg), isDark, isHighContrast);
|
|
957
|
+
const intensity = isHighContrast ? pairHC(def.intensity) : pairNormal(def.intensity);
|
|
958
|
+
const tuning = resolveShadowTuning(def.tuning);
|
|
959
|
+
return computeShadow(bgVariant, fgVariant, intensity, tuning);
|
|
960
|
+
}
|
|
896
961
|
function resolveAllColors(hue, saturation, defs) {
|
|
897
962
|
validateColorDefs(defs);
|
|
898
963
|
const order = topoSort(defs);
|
|
@@ -902,6 +967,9 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
902
967
|
defs,
|
|
903
968
|
resolved: /* @__PURE__ */ new Map()
|
|
904
969
|
};
|
|
970
|
+
function defMode(def) {
|
|
971
|
+
return isShadowDef(def) ? void 0 : def.mode ?? "auto";
|
|
972
|
+
}
|
|
905
973
|
const lightMap = /* @__PURE__ */ new Map();
|
|
906
974
|
for (const name of order) {
|
|
907
975
|
const variant = resolveColorForScheme(name, defs[name], ctx, false, false);
|
|
@@ -912,7 +980,7 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
912
980
|
dark: variant,
|
|
913
981
|
lightContrast: variant,
|
|
914
982
|
darkContrast: variant,
|
|
915
|
-
mode: defs[name]
|
|
983
|
+
mode: defMode(defs[name])
|
|
916
984
|
});
|
|
917
985
|
}
|
|
918
986
|
const lightHCMap = /* @__PURE__ */ new Map();
|
|
@@ -935,7 +1003,7 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
935
1003
|
dark: lightMap.get(name),
|
|
936
1004
|
lightContrast: lightHCMap.get(name),
|
|
937
1005
|
darkContrast: lightHCMap.get(name),
|
|
938
|
-
mode: defs[name]
|
|
1006
|
+
mode: defMode(defs[name])
|
|
939
1007
|
});
|
|
940
1008
|
for (const name of order) {
|
|
941
1009
|
const variant = resolveColorForScheme(name, defs[name], ctx, true, false);
|
|
@@ -965,7 +1033,7 @@ function resolveAllColors(hue, saturation, defs) {
|
|
|
965
1033
|
dark: darkMap.get(name),
|
|
966
1034
|
lightContrast: lightHCMap.get(name),
|
|
967
1035
|
darkContrast: darkHCMap.get(name),
|
|
968
|
-
mode: defs[name]
|
|
1036
|
+
mode: defMode(defs[name])
|
|
969
1037
|
});
|
|
970
1038
|
return result;
|
|
971
1039
|
}
|
|
@@ -975,8 +1043,14 @@ const formatters = {
|
|
|
975
1043
|
hsl: formatHsl,
|
|
976
1044
|
oklch: formatOklch
|
|
977
1045
|
};
|
|
1046
|
+
function fmt(value, decimals) {
|
|
1047
|
+
return parseFloat(value.toFixed(decimals)).toString();
|
|
1048
|
+
}
|
|
978
1049
|
function formatVariant(v, format = "okhsl") {
|
|
979
|
-
|
|
1050
|
+
const base = formatters[format](v.h, v.s * 100, v.l * 100);
|
|
1051
|
+
if (v.alpha >= 1) return base;
|
|
1052
|
+
const closing = base.lastIndexOf(")");
|
|
1053
|
+
return `${base.slice(0, closing)} / ${fmt(v.alpha, 4)})`;
|
|
980
1054
|
}
|
|
981
1055
|
function resolveModes(override) {
|
|
982
1056
|
return {
|
|
@@ -1227,7 +1301,8 @@ glaze.configure = function configure(config) {
|
|
|
1227
1301
|
modes: {
|
|
1228
1302
|
dark: config.modes?.dark ?? globalConfig.modes.dark,
|
|
1229
1303
|
highContrast: config.modes?.highContrast ?? globalConfig.modes.highContrast
|
|
1230
|
-
}
|
|
1304
|
+
},
|
|
1305
|
+
shadowTuning: config.shadowTuning ?? globalConfig.shadowTuning
|
|
1231
1306
|
};
|
|
1232
1307
|
};
|
|
1233
1308
|
/**
|
|
@@ -1249,6 +1324,40 @@ glaze.color = function color(input) {
|
|
|
1249
1324
|
return createColorToken(input);
|
|
1250
1325
|
};
|
|
1251
1326
|
/**
|
|
1327
|
+
* Compute a shadow color from a bg/fg pair and intensity.
|
|
1328
|
+
*/
|
|
1329
|
+
glaze.shadow = function shadow(input) {
|
|
1330
|
+
const bg = parseOkhslInput(input.bg);
|
|
1331
|
+
const fg = input.fg ? parseOkhslInput(input.fg) : void 0;
|
|
1332
|
+
const tuning = resolveShadowTuning(input.tuning);
|
|
1333
|
+
return computeShadow({
|
|
1334
|
+
...bg,
|
|
1335
|
+
alpha: 1
|
|
1336
|
+
}, fg ? {
|
|
1337
|
+
...fg,
|
|
1338
|
+
alpha: 1
|
|
1339
|
+
} : void 0, input.intensity, tuning);
|
|
1340
|
+
};
|
|
1341
|
+
/**
|
|
1342
|
+
* Format a resolved color variant as a CSS string.
|
|
1343
|
+
*/
|
|
1344
|
+
glaze.format = function format(variant, colorFormat) {
|
|
1345
|
+
return formatVariant(variant, colorFormat);
|
|
1346
|
+
};
|
|
1347
|
+
function parseOkhslInput(input) {
|
|
1348
|
+
if (typeof input === "string") {
|
|
1349
|
+
const rgb = parseHex(input);
|
|
1350
|
+
if (!rgb) throw new Error(`glaze: invalid hex color "${input}".`);
|
|
1351
|
+
const [h, s, l] = srgbToOkhsl(rgb);
|
|
1352
|
+
return {
|
|
1353
|
+
h,
|
|
1354
|
+
s,
|
|
1355
|
+
l
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
return input;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1252
1361
|
* Create a theme from a hex color string.
|
|
1253
1362
|
* Extracts hue and saturation from the color.
|
|
1254
1363
|
*/
|