@tenphi/glaze 1.4.0 → 2.0.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 +1 -1
- package/dist/index.cjs +104 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -21
- package/dist/index.d.mts +29 -21
- package/dist/index.mjs +104 -47
- package/dist/index.mjs.map +1 -1
- package/docs/api.md +96 -44
- package/docs/methodology.md +15 -13
- package/docs/migration.md +36 -0
- package/docs/okhst.md +4 -0
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -234,9 +234,10 @@ interface GlazeOutputModes {
|
|
|
234
234
|
/**
|
|
235
235
|
* Include high-contrast variants (both light-HC and dark-HC). Default: false.
|
|
236
236
|
*
|
|
237
|
-
*
|
|
238
|
-
* high-contrast
|
|
239
|
-
*
|
|
237
|
+
* Independent of `contrastLevel` — the level positions the normal variants
|
|
238
|
+
* while these stay the true high-contrast resolution, so the two compose. The
|
|
239
|
+
* exception is a global level of 100, where the normal variants already *are*
|
|
240
|
+
* the high-contrast ones: the tier would duplicate them and is dropped.
|
|
240
241
|
* @see GlazeConfig.contrastLevel
|
|
241
242
|
*/
|
|
242
243
|
highContrast?: boolean;
|
|
@@ -572,15 +573,15 @@ interface GlazeConfig {
|
|
|
572
573
|
shadowTuning?: ShadowTuning;
|
|
573
574
|
/**
|
|
574
575
|
* Manual contrast level — a 0–100 slider from normal contrast (`0`) to high
|
|
575
|
-
* contrast (`100`), or `'auto'` (the default) to
|
|
576
|
-
*
|
|
576
|
+
* contrast (`100`), or `'auto'` (the default) to take the authored normal
|
|
577
|
+
* entries as-is.
|
|
577
578
|
*
|
|
578
|
-
* With a number, Glaze resolves the `light` / `dark` variants *at* that
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
579
|
+
* With a number, Glaze resolves the `light` / `dark` variants *at* that level.
|
|
580
|
+
* That is all it does: `lightContrast` / `darkContrast` stay the true
|
|
581
|
+
* high-contrast resolution at every level, and `modes.highContrast` alone
|
|
582
|
+
* decides whether they are emitted. Level `0` therefore reproduces `'auto'`
|
|
583
|
+
* bit for bit. At `100` the normal variants *are* the high-contrast ones, so a
|
|
584
|
+
* separate tier would duplicate them and a global level of 100 drops it.
|
|
584
585
|
*
|
|
585
586
|
* The level interpolates all three things that make high contrast differ:
|
|
586
587
|
* authored `[normal, highContrast]` pairs, the tone-window widening, and
|
|
@@ -630,8 +631,8 @@ interface GlazeConfigResolved {
|
|
|
630
631
|
shadowTuning?: ShadowTuning;
|
|
631
632
|
autoFlip: boolean;
|
|
632
633
|
/**
|
|
633
|
-
* Manual contrast level (0–100), or `'auto'`
|
|
634
|
-
* normal
|
|
634
|
+
* Manual contrast level (0–100) for the normal variants, or `'auto'` to take
|
|
635
|
+
* the authored normal entries as-is.
|
|
635
636
|
* @see GlazeConfig.contrastLevel
|
|
636
637
|
*/
|
|
637
638
|
contrastLevel: number | 'auto';
|
|
@@ -661,8 +662,10 @@ interface GlazeConfigOverride {
|
|
|
661
662
|
/** Whether to auto-flip tone when contrast can't be met. */
|
|
662
663
|
autoFlip?: boolean;
|
|
663
664
|
/**
|
|
664
|
-
* Manual contrast level (0–100) for this instance, or `'auto'` to opt out
|
|
665
|
-
*
|
|
665
|
+
* Manual contrast level (0–100) for this instance, or `'auto'` to opt out of a
|
|
666
|
+
* global level and take the authored normal entries as-is. Either way the
|
|
667
|
+
* instance keeps its high-contrast tier; a per-instance level never changes
|
|
668
|
+
* which modes are emitted.
|
|
666
669
|
*
|
|
667
670
|
* Only an instance-authored level is frozen into `.export()` snapshots — a
|
|
668
671
|
* level inherited from the global config is treated as a live preference and
|
|
@@ -1695,7 +1698,11 @@ declare function okhslToOkhst(c: {
|
|
|
1695
1698
|
};
|
|
1696
1699
|
/**
|
|
1697
1700
|
* Edge adapter: a resolved variant stores canonical tone `t` (0–1). Convert
|
|
1698
|
-
* it to the OKHSL `{ h, s, l }` the formatters and luminance pipeline expect
|
|
1701
|
+
* it to the OKHSL `{ h, s, l }` the formatters and luminance pipeline expect —
|
|
1702
|
+
* `s` and `l` on the same 0–1 factor scale, so `const { h, s, l } =
|
|
1703
|
+
* variantToOkhsl(v)` destructures straight into `formatOkhsl` / `formatRgb` /
|
|
1704
|
+
* `okhslToLinearSrgb` with no rescaling. `alpha` stays on the variant; use
|
|
1705
|
+
* `glaze.format(v, format)` for a string that carries it.
|
|
1699
1706
|
*/
|
|
1700
1707
|
declare function variantToOkhsl(v: {
|
|
1701
1708
|
h: number;
|
|
@@ -1750,6 +1757,7 @@ declare function relativeLuminanceFromLinearRgb(rgb: [number, number, number]):
|
|
|
1750
1757
|
declare function contrastRatioFromLuminance(yA: number, yB: number): number;
|
|
1751
1758
|
/**
|
|
1752
1759
|
* Convert OKHSL to gamma-encoded sRGB (clamped to 0–1).
|
|
1760
|
+
* h: 0–360, s: 0–1, l: 0–1. Returns [r, g, b] with each channel 0–1.
|
|
1753
1761
|
*/
|
|
1754
1762
|
declare function okhslToSrgb(h: number, s: number, l: number, pastel?: boolean): [number, number, number];
|
|
1755
1763
|
/**
|
|
@@ -1796,12 +1804,12 @@ declare function parseHexAlpha(hex: string): {
|
|
|
1796
1804
|
} | null;
|
|
1797
1805
|
/**
|
|
1798
1806
|
* Format OKHSL values as a CSS `okhsl(H S% L%)` string.
|
|
1799
|
-
* h: 0–360, s: 0–
|
|
1807
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1800
1808
|
*/
|
|
1801
1809
|
declare function formatOkhsl(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1802
1810
|
/**
|
|
1803
1811
|
* Format OKHST values as a CSS `okhst(H S% T%)` string.
|
|
1804
|
-
* h: 0–360, s: 0–
|
|
1812
|
+
* h: 0–360, s: 0–1, t: 0–1 (factor scale, matching `ResolvedColorVariant`).
|
|
1805
1813
|
*
|
|
1806
1814
|
* Pastel recompute matches `formatOkhsl`: convert via OKLab so external
|
|
1807
1815
|
* parsers that only understand non-pastel OKHST render identically.
|
|
@@ -1810,17 +1818,17 @@ declare function formatOkhst(h: number, s: number, t: number, pastel?: boolean):
|
|
|
1810
1818
|
/**
|
|
1811
1819
|
* Format OKHSL values as a CSS `rgb(R G B)` string.
|
|
1812
1820
|
* Uses 2 decimal places to avoid 8-bit quantization contrast loss.
|
|
1813
|
-
* h: 0–360, s: 0–
|
|
1821
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1814
1822
|
*/
|
|
1815
1823
|
declare function formatRgb(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1816
1824
|
/**
|
|
1817
1825
|
* Format OKHSL values as a CSS `hsl(H S% L%)` string.
|
|
1818
|
-
* h: 0–360, s: 0–
|
|
1826
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1819
1827
|
*/
|
|
1820
1828
|
declare function formatHsl(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1821
1829
|
/**
|
|
1822
1830
|
* Format OKHSL values as a CSS `oklch(L C H)` string.
|
|
1823
|
-
* h: 0–360, s: 0–
|
|
1831
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1824
1832
|
*/
|
|
1825
1833
|
declare function formatOklch(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1826
1834
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -234,9 +234,10 @@ interface GlazeOutputModes {
|
|
|
234
234
|
/**
|
|
235
235
|
* Include high-contrast variants (both light-HC and dark-HC). Default: false.
|
|
236
236
|
*
|
|
237
|
-
*
|
|
238
|
-
* high-contrast
|
|
239
|
-
*
|
|
237
|
+
* Independent of `contrastLevel` — the level positions the normal variants
|
|
238
|
+
* while these stay the true high-contrast resolution, so the two compose. The
|
|
239
|
+
* exception is a global level of 100, where the normal variants already *are*
|
|
240
|
+
* the high-contrast ones: the tier would duplicate them and is dropped.
|
|
240
241
|
* @see GlazeConfig.contrastLevel
|
|
241
242
|
*/
|
|
242
243
|
highContrast?: boolean;
|
|
@@ -572,15 +573,15 @@ interface GlazeConfig {
|
|
|
572
573
|
shadowTuning?: ShadowTuning;
|
|
573
574
|
/**
|
|
574
575
|
* Manual contrast level — a 0–100 slider from normal contrast (`0`) to high
|
|
575
|
-
* contrast (`100`), or `'auto'` (the default) to
|
|
576
|
-
*
|
|
576
|
+
* contrast (`100`), or `'auto'` (the default) to take the authored normal
|
|
577
|
+
* entries as-is.
|
|
577
578
|
*
|
|
578
|
-
* With a number, Glaze resolves the `light` / `dark` variants *at* that
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
579
|
+
* With a number, Glaze resolves the `light` / `dark` variants *at* that level.
|
|
580
|
+
* That is all it does: `lightContrast` / `darkContrast` stay the true
|
|
581
|
+
* high-contrast resolution at every level, and `modes.highContrast` alone
|
|
582
|
+
* decides whether they are emitted. Level `0` therefore reproduces `'auto'`
|
|
583
|
+
* bit for bit. At `100` the normal variants *are* the high-contrast ones, so a
|
|
584
|
+
* separate tier would duplicate them and a global level of 100 drops it.
|
|
584
585
|
*
|
|
585
586
|
* The level interpolates all three things that make high contrast differ:
|
|
586
587
|
* authored `[normal, highContrast]` pairs, the tone-window widening, and
|
|
@@ -630,8 +631,8 @@ interface GlazeConfigResolved {
|
|
|
630
631
|
shadowTuning?: ShadowTuning;
|
|
631
632
|
autoFlip: boolean;
|
|
632
633
|
/**
|
|
633
|
-
* Manual contrast level (0–100), or `'auto'`
|
|
634
|
-
* normal
|
|
634
|
+
* Manual contrast level (0–100) for the normal variants, or `'auto'` to take
|
|
635
|
+
* the authored normal entries as-is.
|
|
635
636
|
* @see GlazeConfig.contrastLevel
|
|
636
637
|
*/
|
|
637
638
|
contrastLevel: number | 'auto';
|
|
@@ -661,8 +662,10 @@ interface GlazeConfigOverride {
|
|
|
661
662
|
/** Whether to auto-flip tone when contrast can't be met. */
|
|
662
663
|
autoFlip?: boolean;
|
|
663
664
|
/**
|
|
664
|
-
* Manual contrast level (0–100) for this instance, or `'auto'` to opt out
|
|
665
|
-
*
|
|
665
|
+
* Manual contrast level (0–100) for this instance, or `'auto'` to opt out of a
|
|
666
|
+
* global level and take the authored normal entries as-is. Either way the
|
|
667
|
+
* instance keeps its high-contrast tier; a per-instance level never changes
|
|
668
|
+
* which modes are emitted.
|
|
666
669
|
*
|
|
667
670
|
* Only an instance-authored level is frozen into `.export()` snapshots — a
|
|
668
671
|
* level inherited from the global config is treated as a live preference and
|
|
@@ -1695,7 +1698,11 @@ declare function okhslToOkhst(c: {
|
|
|
1695
1698
|
};
|
|
1696
1699
|
/**
|
|
1697
1700
|
* Edge adapter: a resolved variant stores canonical tone `t` (0–1). Convert
|
|
1698
|
-
* it to the OKHSL `{ h, s, l }` the formatters and luminance pipeline expect
|
|
1701
|
+
* it to the OKHSL `{ h, s, l }` the formatters and luminance pipeline expect —
|
|
1702
|
+
* `s` and `l` on the same 0–1 factor scale, so `const { h, s, l } =
|
|
1703
|
+
* variantToOkhsl(v)` destructures straight into `formatOkhsl` / `formatRgb` /
|
|
1704
|
+
* `okhslToLinearSrgb` with no rescaling. `alpha` stays on the variant; use
|
|
1705
|
+
* `glaze.format(v, format)` for a string that carries it.
|
|
1699
1706
|
*/
|
|
1700
1707
|
declare function variantToOkhsl(v: {
|
|
1701
1708
|
h: number;
|
|
@@ -1750,6 +1757,7 @@ declare function relativeLuminanceFromLinearRgb(rgb: [number, number, number]):
|
|
|
1750
1757
|
declare function contrastRatioFromLuminance(yA: number, yB: number): number;
|
|
1751
1758
|
/**
|
|
1752
1759
|
* Convert OKHSL to gamma-encoded sRGB (clamped to 0–1).
|
|
1760
|
+
* h: 0–360, s: 0–1, l: 0–1. Returns [r, g, b] with each channel 0–1.
|
|
1753
1761
|
*/
|
|
1754
1762
|
declare function okhslToSrgb(h: number, s: number, l: number, pastel?: boolean): [number, number, number];
|
|
1755
1763
|
/**
|
|
@@ -1796,12 +1804,12 @@ declare function parseHexAlpha(hex: string): {
|
|
|
1796
1804
|
} | null;
|
|
1797
1805
|
/**
|
|
1798
1806
|
* Format OKHSL values as a CSS `okhsl(H S% L%)` string.
|
|
1799
|
-
* h: 0–360, s: 0–
|
|
1807
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1800
1808
|
*/
|
|
1801
1809
|
declare function formatOkhsl(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1802
1810
|
/**
|
|
1803
1811
|
* Format OKHST values as a CSS `okhst(H S% T%)` string.
|
|
1804
|
-
* h: 0–360, s: 0–
|
|
1812
|
+
* h: 0–360, s: 0–1, t: 0–1 (factor scale, matching `ResolvedColorVariant`).
|
|
1805
1813
|
*
|
|
1806
1814
|
* Pastel recompute matches `formatOkhsl`: convert via OKLab so external
|
|
1807
1815
|
* parsers that only understand non-pastel OKHST render identically.
|
|
@@ -1810,17 +1818,17 @@ declare function formatOkhst(h: number, s: number, t: number, pastel?: boolean):
|
|
|
1810
1818
|
/**
|
|
1811
1819
|
* Format OKHSL values as a CSS `rgb(R G B)` string.
|
|
1812
1820
|
* Uses 2 decimal places to avoid 8-bit quantization contrast loss.
|
|
1813
|
-
* h: 0–360, s: 0–
|
|
1821
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1814
1822
|
*/
|
|
1815
1823
|
declare function formatRgb(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1816
1824
|
/**
|
|
1817
1825
|
* Format OKHSL values as a CSS `hsl(H S% L%)` string.
|
|
1818
|
-
* h: 0–360, s: 0–
|
|
1826
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1819
1827
|
*/
|
|
1820
1828
|
declare function formatHsl(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1821
1829
|
/**
|
|
1822
1830
|
* Format OKHSL values as a CSS `oklch(L C H)` string.
|
|
1823
|
-
* h: 0–360, s: 0–
|
|
1831
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
1824
1832
|
*/
|
|
1825
1833
|
declare function formatOklch(h: number, s: number, l: number, pastel?: boolean): string;
|
|
1826
1834
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -370,6 +370,7 @@ const sRGBGammaToLinear = (val) => {
|
|
|
370
370
|
};
|
|
371
371
|
/**
|
|
372
372
|
* Convert OKHSL to gamma-encoded sRGB (clamped to 0–1).
|
|
373
|
+
* h: 0–360, s: 0–1, l: 0–1. Returns [r, g, b] with each channel 0–1.
|
|
373
374
|
*/
|
|
374
375
|
function okhslToSrgb(h, s, l, pastel = false) {
|
|
375
376
|
const lin = okhslToLinearSrgb(h, s, l, pastel);
|
|
@@ -579,47 +580,72 @@ function fmt$1(value, decimals) {
|
|
|
579
580
|
return parseFloat(value.toFixed(decimals)).toString();
|
|
580
581
|
}
|
|
581
582
|
/**
|
|
583
|
+
* Every `format*` writer takes `s` / `l` / `t` on the **0–1 factor scale** —
|
|
584
|
+
* the same scale every producer in the library hands back (`resolve()`,
|
|
585
|
+
* `variantToOkhsl`, `srgbToOkhsl`, `oklabToOkhsl`, `okhslToOklab`,
|
|
586
|
+
* `okhslToSrgb`). The percentages are an output detail: the writers multiply
|
|
587
|
+
* by 100 themselves when the CSS syntax asks for a percentage.
|
|
588
|
+
*
|
|
589
|
+
* Before 2.0 the writers took 0–100 while everything producing those values
|
|
590
|
+
* returned 0–1, so the obvious composition was off by 100x and failed
|
|
591
|
+
* silently — `0.7` is a legal percentage, so the result was a valid CSS string
|
|
592
|
+
* naming a near-black color. A value above 1 can only be that old
|
|
593
|
+
* percentage-scale input, so warn about it loudly rather than emit a wrong
|
|
594
|
+
* color quietly.
|
|
595
|
+
*/
|
|
596
|
+
const scaleWarnCache = /* @__PURE__ */ new Set();
|
|
597
|
+
function warnPercentScale(fn, ...values) {
|
|
598
|
+
if (!values.some((v) => v > 1.000001)) return;
|
|
599
|
+
if (!scaleWarnCache.has(fn)) {
|
|
600
|
+
scaleWarnCache.add(fn);
|
|
601
|
+
console.warn(`glaze: ${fn}() got a value above 1 — it takes the 0-1 factors every Glaze producer returns (resolve(), variantToOkhsl, srgbToOkhsl), not 0-100 percentages. Divide by 100, or drop the "* 100" left over from the pre-2.0 signature.`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
582
605
|
* Format OKHSL values as a CSS `okhsl(H S% L%)` string.
|
|
583
|
-
* h: 0–360, s: 0–
|
|
606
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
584
607
|
*/
|
|
585
608
|
function formatOkhsl(h, s, l, pastel = false) {
|
|
609
|
+
warnPercentScale("formatOkhsl", s, l);
|
|
586
610
|
let outS = s;
|
|
587
|
-
if (pastel) outS = oklabToOkhsl(okhslToOklab(h, s
|
|
588
|
-
return `okhsl(${fmt$1(h, 2)} ${fmt$1(outS, 2)}% ${fmt$1(l, 2)}%)`;
|
|
611
|
+
if (pastel) outS = oklabToOkhsl(okhslToOklab(h, s, l, true), false)[1];
|
|
612
|
+
return `okhsl(${fmt$1(h, 2)} ${fmt$1(outS * 100, 2)}% ${fmt$1(l * 100, 2)}%)`;
|
|
589
613
|
}
|
|
590
614
|
/**
|
|
591
615
|
* Format OKHST values as a CSS `okhst(H S% T%)` string.
|
|
592
|
-
* h: 0–360, s: 0–
|
|
616
|
+
* h: 0–360, s: 0–1, t: 0–1 (factor scale, matching `ResolvedColorVariant`).
|
|
593
617
|
*
|
|
594
618
|
* Pastel recompute matches `formatOkhsl`: convert via OKLab so external
|
|
595
619
|
* parsers that only understand non-pastel OKHST render identically.
|
|
596
620
|
*/
|
|
597
621
|
function formatOkhst(h, s, t, pastel = false) {
|
|
622
|
+
warnPercentScale("formatOkhst", s, t);
|
|
598
623
|
let outS = s;
|
|
599
624
|
if (pastel) {
|
|
600
625
|
const REF_EPS = .05;
|
|
601
626
|
const den = Math.log(1 + REF_EPS) - Math.log(REF_EPS);
|
|
602
|
-
const y = Math.exp(t
|
|
603
|
-
|
|
604
|
-
outS = oklabToOkhsl(okhslToOklab(h, s / 100, l, true), false)[1] * 100;
|
|
627
|
+
const y = Math.exp(t * den + Math.log(REF_EPS)) - REF_EPS;
|
|
628
|
+
outS = oklabToOkhsl(okhslToOklab(h, s, toe(Math.cbrt(Math.max(0, y))), true), false)[1];
|
|
605
629
|
}
|
|
606
|
-
return `okhst(${fmt$1(h, 2)} ${fmt$1(outS, 2)}% ${fmt$1(t, 2)}%)`;
|
|
630
|
+
return `okhst(${fmt$1(h, 2)} ${fmt$1(outS * 100, 2)}% ${fmt$1(t * 100, 2)}%)`;
|
|
607
631
|
}
|
|
608
632
|
/**
|
|
609
633
|
* Format OKHSL values as a CSS `rgb(R G B)` string.
|
|
610
634
|
* Uses 2 decimal places to avoid 8-bit quantization contrast loss.
|
|
611
|
-
* h: 0–360, s: 0–
|
|
635
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
612
636
|
*/
|
|
613
637
|
function formatRgb(h, s, l, pastel = false) {
|
|
614
|
-
|
|
638
|
+
warnPercentScale("formatRgb", s, l);
|
|
639
|
+
const [r, g, b] = okhslToSrgb(h, s, l, pastel);
|
|
615
640
|
return `rgb(${parseFloat((r * 255).toFixed(2))} ${parseFloat((g * 255).toFixed(2))} ${parseFloat((b * 255).toFixed(2))})`;
|
|
616
641
|
}
|
|
617
642
|
/**
|
|
618
643
|
* Format OKHSL values as a CSS `hsl(H S% L%)` string.
|
|
619
|
-
* h: 0–360, s: 0–
|
|
644
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
620
645
|
*/
|
|
621
646
|
function formatHsl(h, s, l, pastel = false) {
|
|
622
|
-
|
|
647
|
+
warnPercentScale("formatHsl", s, l);
|
|
648
|
+
const [r, g, b] = okhslToSrgb(h, s, l, pastel);
|
|
623
649
|
const max = Math.max(r, g, b);
|
|
624
650
|
const min = Math.min(r, g, b);
|
|
625
651
|
const delta = max - min;
|
|
@@ -636,10 +662,11 @@ function formatHsl(h, s, l, pastel = false) {
|
|
|
636
662
|
}
|
|
637
663
|
/**
|
|
638
664
|
* Format OKHSL values as a CSS `oklch(L C H)` string.
|
|
639
|
-
* h: 0–360, s: 0–
|
|
665
|
+
* h: 0–360, s: 0–1, l: 0–1 (factor scale, as returned by every converter).
|
|
640
666
|
*/
|
|
641
667
|
function formatOklch(h, s, l, pastel = false) {
|
|
642
|
-
|
|
668
|
+
warnPercentScale("formatOklch", s, l);
|
|
669
|
+
const [L, C, hh] = okhslToOklch(h, s, l, pastel);
|
|
643
670
|
return `oklch(${fmt$1(L, 4)} ${fmt$1(C, 4)} ${fmt$1(hh, 2)})`;
|
|
644
671
|
}
|
|
645
672
|
/**
|
|
@@ -904,10 +931,14 @@ function levelFraction(level) {
|
|
|
904
931
|
}
|
|
905
932
|
/**
|
|
906
933
|
* Manual-contrast blend fraction (0–1) for a resolved config, or `undefined`
|
|
907
|
-
* in `'auto'` mode
|
|
934
|
+
* in `'auto'` mode, where the normal variants take the authored normal entries
|
|
935
|
+
* as-is.
|
|
908
936
|
*
|
|
909
|
-
* Note `contrastLevel: 0` yields `0`, not `undefined
|
|
910
|
-
*
|
|
937
|
+
* Note `contrastLevel: 0` yields `0`, not `undefined`. The two select different
|
|
938
|
+
* branches, and an authored `0` is a slider position that `.export()` freezes
|
|
939
|
+
* where an absent level is not — but they are not an *output* distinction: at
|
|
940
|
+
* fraction 0 every interpolation returns its normal entry verbatim, so level 0
|
|
941
|
+
* reproduces `'auto'` bit for bit.
|
|
911
942
|
*/
|
|
912
943
|
function contrastFraction(config) {
|
|
913
944
|
const level = config.contrastLevel;
|
|
@@ -915,6 +946,20 @@ function contrastFraction(config) {
|
|
|
915
946
|
return levelFraction(level);
|
|
916
947
|
}
|
|
917
948
|
/**
|
|
949
|
+
* Whether the high-contrast variants are exact duplicates of the normal ones —
|
|
950
|
+
* true only at `contrastLevel: 100`, where the normal passes already resolve at
|
|
951
|
+
* full contrast.
|
|
952
|
+
*
|
|
953
|
+
* The level otherwise says nothing about high contrast: it positions the normal
|
|
954
|
+
* variants, while `lightContrast` / `darkContrast` stay the true high-contrast
|
|
955
|
+
* resolution at every level. This predicate marks the one level where that tier
|
|
956
|
+
* carries no new information, so callers can skip the two high-contrast passes,
|
|
957
|
+
* their drift checks, and the redundant output tier.
|
|
958
|
+
*/
|
|
959
|
+
function hcMirrorsNormal(config) {
|
|
960
|
+
return contrastFraction(config) === 1;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
918
963
|
* A numeric HC pair at a blend fraction (shadow `intensity`, mix `value`).
|
|
919
964
|
*
|
|
920
965
|
* The endpoints return the authored entry *by identity* rather than through
|
|
@@ -1099,7 +1144,11 @@ function okhslToOkhst(c) {
|
|
|
1099
1144
|
}
|
|
1100
1145
|
/**
|
|
1101
1146
|
* Edge adapter: a resolved variant stores canonical tone `t` (0–1). Convert
|
|
1102
|
-
* it to the OKHSL `{ h, s, l }` the formatters and luminance pipeline expect
|
|
1147
|
+
* it to the OKHSL `{ h, s, l }` the formatters and luminance pipeline expect —
|
|
1148
|
+
* `s` and `l` on the same 0–1 factor scale, so `const { h, s, l } =
|
|
1149
|
+
* variantToOkhsl(v)` destructures straight into `formatOkhsl` / `formatRgb` /
|
|
1150
|
+
* `okhslToLinearSrgb` with no rescaling. `alpha` stays on the variant; use
|
|
1151
|
+
* `glaze.format(v, format)` for a string that carries it.
|
|
1103
1152
|
*/
|
|
1104
1153
|
function variantToOkhsl(v) {
|
|
1105
1154
|
return {
|
|
@@ -2274,12 +2323,14 @@ function warnContrastDrift(name, isDark, isHighContrast, contrast, yColor, yBase
|
|
|
2274
2323
|
* Color resolution engine.
|
|
2275
2324
|
*
|
|
2276
2325
|
* Runs the four-pass solver (light → light-HC → dark → dark-HC) that
|
|
2277
|
-
* turns a `ColorMap` into a fully resolved `ResolvedColor` per name
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2280
|
-
*
|
|
2281
|
-
*
|
|
2282
|
-
*
|
|
2326
|
+
* turns a `ColorMap` into a fully resolved `ResolvedColor` per name. A manual
|
|
2327
|
+
* `contrastLevel` interpolates the authored HC pairs, tone window, and contrast
|
|
2328
|
+
* targets at that level for the two *normal* passes only; the high-contrast
|
|
2329
|
+
* passes bypass the level entirely, so the high-contrast slots are the same
|
|
2330
|
+
* values `'auto'` resolves. At level 100 the normal passes already are the
|
|
2331
|
+
* high-contrast ones, so the solver drops to two passes (light → dark) and
|
|
2332
|
+
* mirrors the slots. Owns the per-scheme resolve helpers for regular, shadow,
|
|
2333
|
+
* and mix color defs.
|
|
2283
2334
|
*
|
|
2284
2335
|
* Variants are stored in OKHST: `h` / `s` are OKHSL hue/saturation and
|
|
2285
2336
|
* `t` is the canonical contrast-uniform tone (0–1, reference eps). The
|
|
@@ -2536,7 +2587,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, channels,
|
|
|
2536
2587
|
flip,
|
|
2537
2588
|
pastel
|
|
2538
2589
|
};
|
|
2539
|
-
const level = contrastFraction(
|
|
2590
|
+
const level = isHighContrast ? void 0 : contrastFraction(config);
|
|
2540
2591
|
let preferInitial = false;
|
|
2541
2592
|
if (level !== void 0 && level > 0 && level < 1) {
|
|
2542
2593
|
initialDirection = findToneForContrast({
|
|
@@ -2725,10 +2776,11 @@ function seedField(order, ctx, field, source) {
|
|
|
2725
2776
|
* resolved with a `base` + `contrast` may land slightly under the contrast
|
|
2726
2777
|
* its tone implies because chromatic luminance drifts from the gray tone.
|
|
2727
2778
|
*
|
|
2728
|
-
* Under a manual `contrastLevel`
|
|
2729
|
-
*
|
|
2730
|
-
*
|
|
2731
|
-
*
|
|
2779
|
+
* Under a manual `contrastLevel` the spec is resolved at the level, so the check
|
|
2780
|
+
* measures each normal variant against the target it was actually solved for. At
|
|
2781
|
+
* level 100 the high-contrast slots are mirrors and are skipped: the normal
|
|
2782
|
+
* schemes already carry that check, and repeating it would emit a byte-identical
|
|
2783
|
+
* advisory under a different scheme label, which dedupes as a separate key.
|
|
2732
2784
|
*/
|
|
2733
2785
|
function verifyContrastDrift(order, defs, result, config) {
|
|
2734
2786
|
const roles = /* @__PURE__ */ new Map();
|
|
@@ -2763,9 +2815,9 @@ function verifyContrastDrift(order, defs, result, config) {
|
|
|
2763
2815
|
field: "darkContrast"
|
|
2764
2816
|
}
|
|
2765
2817
|
];
|
|
2766
|
-
const
|
|
2818
|
+
const mirrored = hcMirrorsNormal(config);
|
|
2767
2819
|
for (const s of schemes) {
|
|
2768
|
-
if (
|
|
2820
|
+
if (mirrored && s.isHighContrast) continue;
|
|
2769
2821
|
const spec = resolveContrastSpec(regDef.contrast, s.isHighContrast, config, polarity);
|
|
2770
2822
|
const cVariant = color[s.field];
|
|
2771
2823
|
const bVariant = base[s.field];
|
|
@@ -2790,10 +2842,10 @@ function resolveAllColors(seed, defs, config, externalBases) {
|
|
|
2790
2842
|
roles: /* @__PURE__ */ new Map()
|
|
2791
2843
|
};
|
|
2792
2844
|
if (externalBases) for (const [name, color] of externalBases) ctx.resolved.set(name, color);
|
|
2793
|
-
const
|
|
2845
|
+
const mirrored = hcMirrorsNormal(config);
|
|
2794
2846
|
const lightMap = runPass(order, defs, ctx, false, false, "light");
|
|
2795
2847
|
let lightHCMap = lightMap;
|
|
2796
|
-
if (!
|
|
2848
|
+
if (!mirrored) {
|
|
2797
2849
|
seedField(order, ctx, "lightContrast", lightMap);
|
|
2798
2850
|
lightHCMap = runPass(order, defs, ctx, false, true, "lightContrast");
|
|
2799
2851
|
}
|
|
@@ -2801,7 +2853,7 @@ function resolveAllColors(seed, defs, config, externalBases) {
|
|
|
2801
2853
|
seedField(order, ctx, "darkContrast", lightHCMap);
|
|
2802
2854
|
const darkMap = runPass(order, defs, ctx, true, false, "dark");
|
|
2803
2855
|
let darkHCMap = darkMap;
|
|
2804
|
-
if (!
|
|
2856
|
+
if (!mirrored) {
|
|
2805
2857
|
seedField(order, ctx, "darkContrast", darkMap);
|
|
2806
2858
|
darkHCMap = runPass(order, defs, ctx, true, true, "darkContrast");
|
|
2807
2859
|
}
|
|
@@ -3003,10 +3055,10 @@ function fmt(value, decimals) {
|
|
|
3003
3055
|
function formatVariant(v, format = "oklch", pastel = false) {
|
|
3004
3056
|
const effectivePastel = v.pastel ?? pastel;
|
|
3005
3057
|
let base;
|
|
3006
|
-
if (format === "okhst") base = formatOkhst(v.h, v.s
|
|
3058
|
+
if (format === "okhst") base = formatOkhst(v.h, v.s, v.t, effectivePastel);
|
|
3007
3059
|
else {
|
|
3008
3060
|
const { l } = variantToOkhsl(v);
|
|
3009
|
-
base = formatters[format](v.h, v.s
|
|
3061
|
+
base = formatters[format](v.h, v.s, l, effectivePastel);
|
|
3010
3062
|
}
|
|
3011
3063
|
if (v.alpha >= 1) return base;
|
|
3012
3064
|
const closing = base.lastIndexOf(")");
|
|
@@ -3022,7 +3074,7 @@ function formatVariantHue(v, plan, pastel = false) {
|
|
|
3022
3074
|
const [L, C] = okhslToOklch(v.h, v.s, l, effectivePastel);
|
|
3023
3075
|
let base;
|
|
3024
3076
|
if (plan.inline) if (v.s <= 1e-6) base = `oklch(${fmt(L, 4)} 0 0)`;
|
|
3025
|
-
else base = formatOklch(v.h, v.s
|
|
3077
|
+
else base = formatOklch(v.h, v.s, l, effectivePastel);
|
|
3026
3078
|
else base = `oklch(${fmt(L, 4)} ${fmt(C, 4)} ${plan.hueVar})`;
|
|
3027
3079
|
if (v.alpha >= 1) return base;
|
|
3028
3080
|
const closing = base.lastIndexOf(")");
|
|
@@ -3035,23 +3087,28 @@ function formatColorValue(v, format, pastel, huePlan) {
|
|
|
3035
3087
|
/**
|
|
3036
3088
|
* Resolve the effective output modes for an export.
|
|
3037
3089
|
*
|
|
3038
|
-
*
|
|
3039
|
-
*
|
|
3040
|
-
*
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3090
|
+
* `modes.highContrast` and `contrastLevel` are independent: the level positions
|
|
3091
|
+
* the normal variants, the high-contrast tier stays the true high-contrast
|
|
3092
|
+
* resolution, and the two compose — a slider raises the baseline while a
|
|
3093
|
+
* `prefers-contrast: more` block still escalates on top of it.
|
|
3094
|
+
*
|
|
3095
|
+
* The one exception is a **global** level of 100, where the normal variants
|
|
3096
|
+
* already *are* the high-contrast ones. The tier would be an exact duplicate, so
|
|
3097
|
+
* it is dropped and a single light/dark set is emitted — including against an
|
|
3098
|
+
* explicit `highContrast: true` override, which has nothing different left to
|
|
3099
|
+
* ask for.
|
|
3044
3100
|
*
|
|
3045
3101
|
* A level set on a single theme or token does not change which modes are
|
|
3046
|
-
* emitted:
|
|
3047
|
-
*
|
|
3048
|
-
* the
|
|
3102
|
+
* emitted: `modes` is global-only, and a palette must not have one sibling
|
|
3103
|
+
* collapse the shared token structure. A per-instance level of 100 therefore
|
|
3104
|
+
* still reports into the tier, with values equal to its own normal ones.
|
|
3049
3105
|
*/
|
|
3050
3106
|
function resolveModes(override) {
|
|
3051
3107
|
const cfg = getConfig();
|
|
3108
|
+
const highContrast = override?.highContrast ?? cfg.modes.highContrast;
|
|
3052
3109
|
return {
|
|
3053
3110
|
dark: override?.dark ?? cfg.modes.dark,
|
|
3054
|
-
highContrast:
|
|
3111
|
+
highContrast: highContrast && !hcMirrorsNormal(cfg)
|
|
3055
3112
|
};
|
|
3056
3113
|
}
|
|
3057
3114
|
function buildTokenMap(resolved, prefix, states, modes, format = "oklch", pastel = false, channelCtx) {
|