@tenphi/glaze 1.4.0 → 1.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 +1 -1
- package/dist/index.cjs +55 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -15
- package/dist/index.d.mts +18 -15
- package/dist/index.mjs +55 -29
- package/dist/index.mjs.map +1 -1
- package/docs/api.md +58 -35
- package/docs/methodology.md +15 -13
- package/docs/okhst.md +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ contrast floor.
|
|
|
32
32
|
- **Mix colors** — blend two colors with OKHSL or sRGB interpolation, opaque or transparent, with optional contrast solving
|
|
33
33
|
- **Shadow colors** — OKHSL-native shadow computation with automatic alpha, fg/bg tinting, and per-scheme adaptation
|
|
34
34
|
- **Light + Dark + High-Contrast** — all schemes from one definition
|
|
35
|
-
- **Manual contrast level** —
|
|
35
|
+
- **Manual contrast level** — put your normal palette on a `0–100` slider between itself and the high-contrast one; every level in between is solved, not approximated, and the high-contrast tier stays available on top
|
|
36
36
|
- **Per-color hue override** — absolute or relative hue shifts within a theme
|
|
37
37
|
- **Multi-format output** — native `rgb`, `hsl`, and `oklch`, plus
|
|
38
38
|
[Tasty](https://tasty.style)-compatible `okhsl` and `okhst`
|
package/dist/index.cjs
CHANGED
|
@@ -906,10 +906,14 @@ function levelFraction(level) {
|
|
|
906
906
|
}
|
|
907
907
|
/**
|
|
908
908
|
* Manual-contrast blend fraction (0–1) for a resolved config, or `undefined`
|
|
909
|
-
* in `'auto'` mode
|
|
909
|
+
* in `'auto'` mode, where the normal variants take the authored normal entries
|
|
910
|
+
* as-is.
|
|
910
911
|
*
|
|
911
|
-
* Note `contrastLevel: 0` yields `0`, not `undefined
|
|
912
|
-
*
|
|
912
|
+
* Note `contrastLevel: 0` yields `0`, not `undefined`. The two select different
|
|
913
|
+
* branches, and an authored `0` is a slider position that `.export()` freezes
|
|
914
|
+
* where an absent level is not — but they are not an *output* distinction: at
|
|
915
|
+
* fraction 0 every interpolation returns its normal entry verbatim, so level 0
|
|
916
|
+
* reproduces `'auto'` bit for bit.
|
|
913
917
|
*/
|
|
914
918
|
function contrastFraction(config) {
|
|
915
919
|
const level = config.contrastLevel;
|
|
@@ -917,6 +921,20 @@ function contrastFraction(config) {
|
|
|
917
921
|
return levelFraction(level);
|
|
918
922
|
}
|
|
919
923
|
/**
|
|
924
|
+
* Whether the high-contrast variants are exact duplicates of the normal ones —
|
|
925
|
+
* true only at `contrastLevel: 100`, where the normal passes already resolve at
|
|
926
|
+
* full contrast.
|
|
927
|
+
*
|
|
928
|
+
* The level otherwise says nothing about high contrast: it positions the normal
|
|
929
|
+
* variants, while `lightContrast` / `darkContrast` stay the true high-contrast
|
|
930
|
+
* resolution at every level. This predicate marks the one level where that tier
|
|
931
|
+
* carries no new information, so callers can skip the two high-contrast passes,
|
|
932
|
+
* their drift checks, and the redundant output tier.
|
|
933
|
+
*/
|
|
934
|
+
function hcMirrorsNormal(config) {
|
|
935
|
+
return contrastFraction(config) === 1;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
920
938
|
* A numeric HC pair at a blend fraction (shadow `intensity`, mix `value`).
|
|
921
939
|
*
|
|
922
940
|
* The endpoints return the authored entry *by identity* rather than through
|
|
@@ -2276,12 +2294,14 @@ function warnContrastDrift(name, isDark, isHighContrast, contrast, yColor, yBase
|
|
|
2276
2294
|
* Color resolution engine.
|
|
2277
2295
|
*
|
|
2278
2296
|
* Runs the four-pass solver (light → light-HC → dark → dark-HC) that
|
|
2279
|
-
* turns a `ColorMap` into a fully resolved `ResolvedColor` per name
|
|
2280
|
-
*
|
|
2281
|
-
*
|
|
2282
|
-
*
|
|
2283
|
-
*
|
|
2284
|
-
*
|
|
2297
|
+
* turns a `ColorMap` into a fully resolved `ResolvedColor` per name. A manual
|
|
2298
|
+
* `contrastLevel` interpolates the authored HC pairs, tone window, and contrast
|
|
2299
|
+
* targets at that level for the two *normal* passes only; the high-contrast
|
|
2300
|
+
* passes bypass the level entirely, so the high-contrast slots are the same
|
|
2301
|
+
* values `'auto'` resolves. At level 100 the normal passes already are the
|
|
2302
|
+
* high-contrast ones, so the solver drops to two passes (light → dark) and
|
|
2303
|
+
* mirrors the slots. Owns the per-scheme resolve helpers for regular, shadow,
|
|
2304
|
+
* and mix color defs.
|
|
2285
2305
|
*
|
|
2286
2306
|
* Variants are stored in OKHST: `h` / `s` are OKHSL hue/saturation and
|
|
2287
2307
|
* `t` is the canonical contrast-uniform tone (0–1, reference eps). The
|
|
@@ -2538,7 +2558,7 @@ function resolveDependentColor(name, def, ctx, isHighContrast, isDark, channels,
|
|
|
2538
2558
|
flip,
|
|
2539
2559
|
pastel
|
|
2540
2560
|
};
|
|
2541
|
-
const level = contrastFraction(
|
|
2561
|
+
const level = isHighContrast ? void 0 : contrastFraction(config);
|
|
2542
2562
|
let preferInitial = false;
|
|
2543
2563
|
if (level !== void 0 && level > 0 && level < 1) {
|
|
2544
2564
|
initialDirection = findToneForContrast({
|
|
@@ -2727,10 +2747,11 @@ function seedField(order, ctx, field, source) {
|
|
|
2727
2747
|
* resolved with a `base` + `contrast` may land slightly under the contrast
|
|
2728
2748
|
* its tone implies because chromatic luminance drifts from the gray tone.
|
|
2729
2749
|
*
|
|
2730
|
-
* Under a manual `contrastLevel`
|
|
2731
|
-
*
|
|
2732
|
-
*
|
|
2733
|
-
*
|
|
2750
|
+
* Under a manual `contrastLevel` the spec is resolved at the level, so the check
|
|
2751
|
+
* measures each normal variant against the target it was actually solved for. At
|
|
2752
|
+
* level 100 the high-contrast slots are mirrors and are skipped: the normal
|
|
2753
|
+
* schemes already carry that check, and repeating it would emit a byte-identical
|
|
2754
|
+
* advisory under a different scheme label, which dedupes as a separate key.
|
|
2734
2755
|
*/
|
|
2735
2756
|
function verifyContrastDrift(order, defs, result, config) {
|
|
2736
2757
|
const roles = /* @__PURE__ */ new Map();
|
|
@@ -2765,9 +2786,9 @@ function verifyContrastDrift(order, defs, result, config) {
|
|
|
2765
2786
|
field: "darkContrast"
|
|
2766
2787
|
}
|
|
2767
2788
|
];
|
|
2768
|
-
const
|
|
2789
|
+
const mirrored = hcMirrorsNormal(config);
|
|
2769
2790
|
for (const s of schemes) {
|
|
2770
|
-
if (
|
|
2791
|
+
if (mirrored && s.isHighContrast) continue;
|
|
2771
2792
|
const spec = resolveContrastSpec(regDef.contrast, s.isHighContrast, config, polarity);
|
|
2772
2793
|
const cVariant = color[s.field];
|
|
2773
2794
|
const bVariant = base[s.field];
|
|
@@ -2792,10 +2813,10 @@ function resolveAllColors(seed, defs, config, externalBases) {
|
|
|
2792
2813
|
roles: /* @__PURE__ */ new Map()
|
|
2793
2814
|
};
|
|
2794
2815
|
if (externalBases) for (const [name, color] of externalBases) ctx.resolved.set(name, color);
|
|
2795
|
-
const
|
|
2816
|
+
const mirrored = hcMirrorsNormal(config);
|
|
2796
2817
|
const lightMap = runPass(order, defs, ctx, false, false, "light");
|
|
2797
2818
|
let lightHCMap = lightMap;
|
|
2798
|
-
if (!
|
|
2819
|
+
if (!mirrored) {
|
|
2799
2820
|
seedField(order, ctx, "lightContrast", lightMap);
|
|
2800
2821
|
lightHCMap = runPass(order, defs, ctx, false, true, "lightContrast");
|
|
2801
2822
|
}
|
|
@@ -2803,7 +2824,7 @@ function resolveAllColors(seed, defs, config, externalBases) {
|
|
|
2803
2824
|
seedField(order, ctx, "darkContrast", lightHCMap);
|
|
2804
2825
|
const darkMap = runPass(order, defs, ctx, true, false, "dark");
|
|
2805
2826
|
let darkHCMap = darkMap;
|
|
2806
|
-
if (!
|
|
2827
|
+
if (!mirrored) {
|
|
2807
2828
|
seedField(order, ctx, "darkContrast", darkMap);
|
|
2808
2829
|
darkHCMap = runPass(order, defs, ctx, true, true, "darkContrast");
|
|
2809
2830
|
}
|
|
@@ -3037,23 +3058,28 @@ function formatColorValue(v, format, pastel, huePlan) {
|
|
|
3037
3058
|
/**
|
|
3038
3059
|
* Resolve the effective output modes for an export.
|
|
3039
3060
|
*
|
|
3040
|
-
*
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3044
|
-
*
|
|
3045
|
-
*
|
|
3061
|
+
* `modes.highContrast` and `contrastLevel` are independent: the level positions
|
|
3062
|
+
* the normal variants, the high-contrast tier stays the true high-contrast
|
|
3063
|
+
* resolution, and the two compose — a slider raises the baseline while a
|
|
3064
|
+
* `prefers-contrast: more` block still escalates on top of it.
|
|
3065
|
+
*
|
|
3066
|
+
* The one exception is a **global** level of 100, where the normal variants
|
|
3067
|
+
* already *are* the high-contrast ones. The tier would be an exact duplicate, so
|
|
3068
|
+
* it is dropped and a single light/dark set is emitted — including against an
|
|
3069
|
+
* explicit `highContrast: true` override, which has nothing different left to
|
|
3070
|
+
* ask for.
|
|
3046
3071
|
*
|
|
3047
3072
|
* A level set on a single theme or token does not change which modes are
|
|
3048
|
-
* emitted:
|
|
3049
|
-
*
|
|
3050
|
-
* the
|
|
3073
|
+
* emitted: `modes` is global-only, and a palette must not have one sibling
|
|
3074
|
+
* collapse the shared token structure. A per-instance level of 100 therefore
|
|
3075
|
+
* still reports into the tier, with values equal to its own normal ones.
|
|
3051
3076
|
*/
|
|
3052
3077
|
function resolveModes(override) {
|
|
3053
3078
|
const cfg = getConfig();
|
|
3079
|
+
const highContrast = override?.highContrast ?? cfg.modes.highContrast;
|
|
3054
3080
|
return {
|
|
3055
3081
|
dark: override?.dark ?? cfg.modes.dark,
|
|
3056
|
-
highContrast:
|
|
3082
|
+
highContrast: highContrast && !hcMirrorsNormal(cfg)
|
|
3057
3083
|
};
|
|
3058
3084
|
}
|
|
3059
3085
|
function buildTokenMap(resolved, prefix, states, modes, format = "oklch", pastel = false, channelCtx) {
|