color-map 2.2.0 → 2.3.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/.claude-plugin/plugin.json +12 -0
- package/.codex-plugin/plugin.json +12 -0
- package/.cursor-plugin/plugin.json +12 -0
- package/CHANGELOG.md +100 -0
- package/README.md +71 -8
- package/bin/cli-CELmx3Y9.js +6414 -0
- package/bin/color-map.js +9 -0
- package/bin/index.js +1 -0
- package/cjs/color/index.d.ts +5 -0
- package/cjs/color/index.js +52 -0
- package/cjs/color-wNrkrhC7.js +374 -0
- package/cjs/color-wNrkrhC7.js.map +1 -0
- package/cjs/contrast-XjF5Rwui.js +293 -0
- package/cjs/contrast-XjF5Rwui.js.map +1 -0
- package/cjs/gamut-CG1J2aat.d.ts +158 -0
- package/cjs/gamut-CG1J2aat.d.ts.map +1 -0
- package/cjs/index-BcEqCPHz.d.ts +137 -0
- package/cjs/index-BcEqCPHz.d.ts.map +1 -0
- package/cjs/index-CGKCqRqD.d.ts +111 -0
- package/cjs/index-CGKCqRqD.d.ts.map +1 -0
- package/cjs/index-Cv3iUUYE.d.ts +171 -0
- package/cjs/index-Cv3iUUYE.d.ts.map +1 -0
- package/cjs/index-Dw43Hj4v.d.ts +41 -0
- package/cjs/index-Dw43Hj4v.d.ts.map +1 -0
- package/cjs/index-Dy8UARqO.d.ts +72 -0
- package/cjs/index-Dy8UARqO.d.ts.map +1 -0
- package/cjs/index.d.ts +26 -36
- package/cjs/index.d.ts.map +1 -1
- package/cjs/index.js +83 -62
- package/cjs/index.js.map +1 -1
- package/cjs/legacy/index.d.ts +2 -0
- package/cjs/legacy/index.js +75 -0
- package/cjs/legacy/index.js.map +1 -0
- package/cjs/mix-Cs4owSbW.js +158 -0
- package/cjs/mix-Cs4owSbW.js.map +1 -0
- package/cjs/mix-DGlKoUw4.d.ts +31 -0
- package/cjs/mix-DGlKoUw4.d.ts.map +1 -0
- package/cjs/palette/index.d.ts +3 -0
- package/cjs/palette/index.js +8 -0
- package/cjs/palette-DyFV_KoJ.js +332 -0
- package/cjs/palette-DyFV_KoJ.js.map +1 -0
- package/cjs/parse-8mt2OMKf.js +1508 -0
- package/cjs/parse-8mt2OMKf.js.map +1 -0
- package/cjs/scale/index.d.ts +2 -0
- package/cjs/scale/index.js +11 -0
- package/cjs/scale-Cq9pXrc1.js +318 -0
- package/cjs/scale-Cq9pXrc1.js.map +1 -0
- package/cjs/semantic-CrEIo8tS.d.ts +52 -0
- package/cjs/semantic-CrEIo8tS.d.ts.map +1 -0
- package/cjs/serialize-C69DAmoE.d.ts +25 -0
- package/cjs/serialize-C69DAmoE.d.ts.map +1 -0
- package/cjs/serialize-Ci2X0cPN.js +103 -0
- package/cjs/serialize-Ci2X0cPN.js.map +1 -0
- package/cjs/tokens/index.d.ts +2 -0
- package/cjs/tokens/index.js +6 -0
- package/cjs/tokens-CzXxaMgM.js +168 -0
- package/cjs/tokens-CzXxaMgM.js.map +1 -0
- package/cjs/tone-BAhu-QzQ.js +118 -0
- package/cjs/tone-BAhu-QzQ.js.map +1 -0
- package/dist/color-map.es5.js.map +1 -1
- package/esm/color/a98-rgb.d.ts +7 -0
- package/esm/color/a98-rgb.d.ts.map +1 -0
- package/esm/color/a98-rgb.js +13 -0
- package/esm/color/a98-rgb.js.map +1 -0
- package/esm/color/argb.d.ts +6 -0
- package/esm/color/argb.d.ts.map +1 -0
- package/esm/color/argb.js +25 -0
- package/esm/color/argb.js.map +1 -0
- package/esm/color/calc.js +82 -0
- package/esm/color/calc.js.map +1 -0
- package/esm/color/compare.d.ts +20 -0
- package/esm/color/compare.d.ts.map +1 -0
- package/esm/color/compare.js +46 -0
- package/esm/color/compare.js.map +1 -0
- package/esm/color/contrast.d.ts +107 -0
- package/esm/color/contrast.d.ts.map +1 -0
- package/esm/color/contrast.js +242 -0
- package/esm/color/contrast.js.map +1 -0
- package/esm/color/convert.d.ts +9 -0
- package/esm/color/convert.d.ts.map +1 -0
- package/esm/color/convert.js +45 -0
- package/esm/color/convert.js.map +1 -0
- package/esm/color/display-p3-linear.d.ts +7 -0
- package/esm/color/display-p3-linear.d.ts.map +1 -0
- package/esm/color/display-p3-linear.js +12 -0
- package/esm/color/display-p3-linear.js.map +1 -0
- package/esm/color/display-p3.d.ts +7 -0
- package/esm/color/display-p3.d.ts.map +1 -0
- package/esm/color/display-p3.js +12 -0
- package/esm/color/display-p3.js.map +1 -0
- package/esm/color/expr.d.ts +43 -0
- package/esm/color/expr.d.ts.map +1 -0
- package/esm/color/expr.js +160 -0
- package/esm/color/expr.js.map +1 -0
- package/esm/color/gamut.d.ts +31 -0
- package/esm/color/gamut.d.ts.map +1 -0
- package/esm/color/gamut.js +210 -0
- package/esm/color/gamut.js.map +1 -0
- package/esm/color/hsl.d.ts +7 -0
- package/esm/color/hsl.d.ts.map +1 -0
- package/esm/color/hsl.js +67 -0
- package/esm/color/hsl.js.map +1 -0
- package/esm/color/hwb.d.ts +7 -0
- package/esm/color/hwb.d.ts.map +1 -0
- package/esm/color/hwb.js +40 -0
- package/esm/color/hwb.js.map +1 -0
- package/esm/color/index.d.ts +28 -0
- package/esm/color/index.js +27 -0
- package/esm/color/lab.d.ts +7 -0
- package/esm/color/lab.d.ts.map +1 -0
- package/esm/color/lab.js +33 -0
- package/esm/color/lab.js.map +1 -0
- package/esm/color/lch.d.ts +7 -0
- package/esm/color/lch.d.ts.map +1 -0
- package/esm/color/lch.js +11 -0
- package/esm/color/lch.js.map +1 -0
- package/esm/color/math.js +32 -0
- package/esm/color/math.js.map +1 -0
- package/esm/color/matrices.js +180 -0
- package/esm/color/matrices.js.map +1 -0
- package/esm/color/mix.d.ts +30 -0
- package/esm/color/mix.d.ts.map +1 -0
- package/esm/color/mix.js +152 -0
- package/esm/color/mix.js.map +1 -0
- package/esm/color/named.js +11 -0
- package/esm/color/named.js.map +1 -0
- package/esm/color/oklab.d.ts +7 -0
- package/esm/color/oklab.d.ts.map +1 -0
- package/esm/color/oklab.js +21 -0
- package/esm/color/oklab.js.map +1 -0
- package/esm/color/oklch.d.ts +7 -0
- package/esm/color/oklch.d.ts.map +1 -0
- package/esm/color/oklch.js +11 -0
- package/esm/color/oklch.js.map +1 -0
- package/esm/color/parse.d.ts +9 -0
- package/esm/color/parse.d.ts.map +1 -0
- package/esm/color/parse.js +452 -0
- package/esm/color/parse.js.map +1 -0
- package/esm/color/polar.js +27 -0
- package/esm/color/polar.js.map +1 -0
- package/esm/color/prophoto-rgb.d.ts +7 -0
- package/esm/color/prophoto-rgb.d.ts.map +1 -0
- package/esm/color/prophoto-rgb.js +18 -0
- package/esm/color/prophoto-rgb.js.map +1 -0
- package/esm/color/rec2020.d.ts +7 -0
- package/esm/color/rec2020.d.ts.map +1 -0
- package/esm/color/rec2020.js +13 -0
- package/esm/color/rec2020.js.map +1 -0
- package/esm/color/resolve.d.ts +18 -0
- package/esm/color/resolve.d.ts.map +1 -0
- package/esm/color/resolve.js +98 -0
- package/esm/color/resolve.js.map +1 -0
- package/esm/color/rgb.js +20 -0
- package/esm/color/rgb.js.map +1 -0
- package/esm/color/serialize.d.ts +24 -0
- package/esm/color/serialize.d.ts.map +1 -0
- package/esm/color/serialize.js +88 -0
- package/esm/color/serialize.js.map +1 -0
- package/esm/color/spaces.d.ts +6 -0
- package/esm/color/spaces.d.ts.map +1 -0
- package/esm/color/spaces.js +42 -0
- package/esm/color/spaces.js.map +1 -0
- package/esm/color/srgb-linear.d.ts +7 -0
- package/esm/color/srgb-linear.d.ts.map +1 -0
- package/esm/color/srgb-linear.js +12 -0
- package/esm/color/srgb-linear.js.map +1 -0
- package/esm/color/srgb.d.ts +7 -0
- package/esm/color/srgb.d.ts.map +1 -0
- package/esm/color/srgb.js +12 -0
- package/esm/color/srgb.js.map +1 -0
- package/esm/color/transfer.js +11 -0
- package/esm/color/transfer.js.map +1 -0
- package/esm/color/types.d.ts +23 -0
- package/esm/color/types.d.ts.map +1 -0
- package/esm/color/unknown-name.js +16 -0
- package/esm/color/unknown-name.js.map +1 -0
- package/esm/color/xyz-d50.d.ts +7 -0
- package/esm/color/xyz-d50.d.ts.map +1 -0
- package/esm/color/xyz-d50.js +15 -0
- package/esm/color/xyz-d50.js.map +1 -0
- package/esm/color/xyz-d65.d.ts +4 -0
- package/esm/color/xyz-d65.d.ts.map +1 -0
- package/esm/color/xyz-d65.js +10 -0
- package/esm/color/xyz-d65.js.map +1 -0
- package/esm/index.d.ts +63 -6
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +54 -5
- package/esm/index.js.map +1 -0
- package/esm/legacy/createColors.d.ts +9 -0
- package/esm/legacy/createColors.d.ts.map +1 -0
- package/esm/{createColors.js → legacy/createColors.js} +7 -2
- package/esm/legacy/createColors.js.map +1 -0
- package/esm/legacy/createColorsFromMap.d.ts +9 -0
- package/esm/legacy/createColorsFromMap.d.ts.map +1 -0
- package/esm/{createColorsFromMap.js → legacy/createColorsFromMap.js} +4 -2
- package/esm/legacy/createColorsFromMap.js.map +1 -0
- package/esm/legacy/index.d.ts +6 -0
- package/esm/legacy/index.js +5 -0
- package/esm/legacy/interfaces.d.ts +19 -0
- package/esm/legacy/interfaces.d.ts.map +1 -0
- package/esm/{rgbHex.d.ts → legacy/rgbHex.d.ts} +1 -4
- package/esm/legacy/rgbHex.d.ts.map +1 -0
- package/esm/{rgbHex.js → legacy/rgbHex.js} +1 -2
- package/esm/legacy/rgbHex.js.map +1 -0
- package/esm/legacy/rgbaString.d.ts +4 -0
- package/esm/{rgbaString.d.ts.map → legacy/rgbaString.d.ts.map} +1 -1
- package/esm/legacy/rgbaString.js +8 -0
- package/esm/legacy/rgbaString.js.map +1 -0
- package/esm/palette/dark.d.ts +23 -0
- package/esm/palette/dark.d.ts.map +1 -0
- package/esm/palette/dark.js +54 -0
- package/esm/palette/dark.js.map +1 -0
- package/esm/palette/harmony.d.ts +56 -0
- package/esm/palette/harmony.d.ts.map +1 -0
- package/esm/palette/harmony.js +115 -0
- package/esm/palette/harmony.js.map +1 -0
- package/esm/palette/index.d.ts +5 -0
- package/esm/palette/index.js +5 -0
- package/esm/palette/semantic.d.ts +53 -0
- package/esm/palette/semantic.d.ts.map +1 -0
- package/esm/palette/semantic.js +96 -0
- package/esm/palette/semantic.js.map +1 -0
- package/esm/palette/validate.d.ts +39 -0
- package/esm/palette/validate.d.ts.map +1 -0
- package/esm/palette/validate.js +56 -0
- package/esm/palette/validate.js.map +1 -0
- package/esm/scale/brand.d.ts +21 -0
- package/esm/scale/brand.d.ts.map +1 -0
- package/esm/scale/brand.js +63 -0
- package/esm/scale/brand.js.map +1 -0
- package/esm/scale/contrast-scale.d.ts +33 -0
- package/esm/scale/contrast-scale.d.ts.map +1 -0
- package/esm/scale/contrast-scale.js +26 -0
- package/esm/scale/contrast-scale.js.map +1 -0
- package/esm/scale/gradient.d.ts +38 -0
- package/esm/scale/gradient.d.ts.map +1 -0
- package/esm/scale/gradient.js +73 -0
- package/esm/scale/gradient.js.map +1 -0
- package/esm/scale/index.d.ts +6 -0
- package/esm/scale/index.js +6 -0
- package/esm/scale/scale.d.ts +31 -0
- package/esm/scale/scale.d.ts.map +1 -0
- package/esm/scale/scale.js +152 -0
- package/esm/scale/scale.js.map +1 -0
- package/esm/scale/tone.d.ts +25 -0
- package/esm/scale/tone.d.ts.map +1 -0
- package/esm/scale/tone.js +96 -0
- package/esm/scale/tone.js.map +1 -0
- package/esm/tokens/css.d.ts +18 -0
- package/esm/tokens/css.d.ts.map +1 -0
- package/esm/tokens/css.js +23 -0
- package/esm/tokens/css.js.map +1 -0
- package/esm/tokens/dtcg.d.ts +33 -0
- package/esm/tokens/dtcg.d.ts.map +1 -0
- package/esm/tokens/dtcg.js +42 -0
- package/esm/tokens/dtcg.js.map +1 -0
- package/esm/tokens/index.d.ts +5 -0
- package/esm/tokens/index.js +4 -0
- package/esm/tokens/tailwind.d.ts +19 -0
- package/esm/tokens/tailwind.d.ts.map +1 -0
- package/esm/tokens/tailwind.js +19 -0
- package/esm/tokens/tailwind.js.map +1 -0
- package/esm/tokens/tokens.d.ts +11 -0
- package/esm/tokens/tokens.d.ts.map +1 -0
- package/esm/tokens/tokens.js +77 -0
- package/esm/tokens/tokens.js.map +1 -0
- package/llms.txt +48 -0
- package/package.json +277 -64
- package/plugin.json +18 -0
- package/schemas/contrast.schema.json +24 -0
- package/schemas/convert.schema.json +60 -0
- package/schemas/export.schema.json +106 -0
- package/schemas/find-contrast.schema.json +51 -0
- package/schemas/harmony.schema.json +58 -0
- package/schemas/mix.schema.json +72 -0
- package/schemas/palette.schema.json +56 -0
- package/schemas/scale.schema.json +86 -0
- package/skills/color-map/SKILL.md +38 -0
- package/src/cli/bin.ts +11 -0
- package/src/cli/cli.ts +40 -0
- package/src/cli/commands/contrast.ts +36 -0
- package/src/cli/commands/convert.ts +37 -0
- package/src/cli/commands/export.ts +102 -0
- package/src/cli/commands/find-contrast.ts +37 -0
- package/src/cli/commands/harmony.ts +30 -0
- package/src/cli/commands/mix.ts +29 -0
- package/src/cli/commands/palette.ts +66 -0
- package/src/cli/commands/scale.ts +75 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/schema-command.ts +31 -0
- package/src/cli/schemas.ts +86 -0
- package/src/cli/shared.ts +72 -0
- package/src/cli/testing.ts +27 -0
- package/src/color/a98-rgb.ts +11 -0
- package/src/color/argb.ts +21 -0
- package/src/color/calc.ts +84 -0
- package/src/color/compare.ts +53 -0
- package/src/color/contrast.ts +277 -0
- package/src/color/convert.ts +43 -0
- package/src/color/display-p3-linear.ts +9 -0
- package/src/color/display-p3.ts +9 -0
- package/src/color/expr.ts +158 -0
- package/src/color/gamut.ts +215 -0
- package/src/color/hsl.ts +57 -0
- package/src/color/hwb.ts +26 -0
- package/src/color/index.ts +43 -0
- package/src/color/lab.ts +29 -0
- package/src/color/lch.ts +8 -0
- package/src/color/math.ts +30 -0
- package/src/color/matrices.ts +91 -0
- package/src/color/mix.ts +158 -0
- package/src/color/named.ts +13 -0
- package/src/color/oklab.ts +19 -0
- package/src/color/oklch.ts +8 -0
- package/src/color/parse.ts +355 -0
- package/src/color/polar.ts +17 -0
- package/src/color/prophoto-rgb.ts +18 -0
- package/src/color/rec2020.ts +11 -0
- package/src/color/resolve.ts +87 -0
- package/src/color/rgb.ts +20 -0
- package/src/color/serialize.ts +96 -0
- package/src/color/spaces.ts +42 -0
- package/src/color/srgb-linear.ts +9 -0
- package/src/color/srgb.ts +9 -0
- package/src/color/test-utils.ts +10 -0
- package/src/color/transfer.ts +14 -0
- package/src/color/types.ts +41 -0
- package/src/color/unknown-name.ts +15 -0
- package/src/color/xyz-d50.ts +13 -0
- package/src/color/xyz-d65.ts +8 -0
- package/src/index.ts +37 -0
- package/{ts → src/legacy}/createColors.ts +6 -0
- package/{ts → src/legacy}/createColorsFromMap.ts +3 -0
- package/{ts → src/legacy}/interfaces.ts +4 -0
- package/{ts → src/legacy}/rgbaString.ts +1 -0
- package/src/palette/dark.ts +55 -0
- package/src/palette/harmony.ts +107 -0
- package/src/palette/index.ts +18 -0
- package/src/palette/semantic.ts +120 -0
- package/src/palette/validate.ts +82 -0
- package/src/scale/brand.ts +50 -0
- package/src/scale/contrast-scale.ts +48 -0
- package/src/scale/gradient.ts +91 -0
- package/src/scale/index.ts +5 -0
- package/src/scale/scale.ts +127 -0
- package/src/scale/tone.ts +69 -0
- package/src/tokens/css.ts +32 -0
- package/src/tokens/dtcg.ts +62 -0
- package/src/tokens/index.ts +11 -0
- package/src/tokens/tailwind.ts +24 -0
- package/src/tokens/tokens.ts +82 -0
- package/esm/createColors.d.ts +0 -6
- package/esm/createColors.d.ts.map +0 -1
- package/esm/createColors.js.map +0 -1
- package/esm/createColorsFromMap.d.ts +0 -9
- package/esm/createColorsFromMap.d.ts.map +0 -1
- package/esm/createColorsFromMap.js.map +0 -1
- package/esm/interfaces.d.ts +0 -18
- package/esm/interfaces.d.ts.map +0 -1
- package/esm/rgbHex.d.ts.map +0 -1
- package/esm/rgbHex.js.map +0 -1
- package/esm/rgbaString.d.ts +0 -6
- package/esm/rgbaString.js +0 -8
- package/esm/rgbaString.js.map +0 -1
- /package/{ts → src/legacy}/index.ts +0 -0
- /package/{ts → src/legacy}/rgbHex.ts +0 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const require_parse = require("./parse-8mt2OMKf.js");
|
|
2
|
+
const require_tone = require("./tone-BAhu-QzQ.js");
|
|
3
|
+
const require_contrast = require("./contrast-XjF5Rwui.js");
|
|
4
|
+
// src/palette/dark.ts
|
|
5
|
+
const STRATEGIES = [
|
|
6
|
+
"mirror",
|
|
7
|
+
"tone-swap",
|
|
8
|
+
"lightness-invert"
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* A dark version of a light scale (ordered light to dark), with the same
|
|
12
|
+
* number of steps.
|
|
13
|
+
* - `mirror`: step n takes the color of step N+1-n, so the scale runs dark to
|
|
14
|
+
* light and step 1 is the old last step. Colors are unchanged.
|
|
15
|
+
* - `tone-swap`: each color keeps its OKLCH hue and chroma and takes the CIE
|
|
16
|
+
* L* tone 100 minus its own, as Material swaps tone 40 for 80. Contrast
|
|
17
|
+
* between two steps is kept roughly the same.
|
|
18
|
+
* - `lightness-invert`: OKLCH lightness is reflected within the scale's own
|
|
19
|
+
* range, keeping step order, with chroma eased by 10% to curb vibration on
|
|
20
|
+
* dark backgrounds.
|
|
21
|
+
*/
|
|
22
|
+
function darkVariant(scale, strategy = "tone-swap", options = {}) {
|
|
23
|
+
const { gamut = "srgb" } = options;
|
|
24
|
+
if (!STRATEGIES.includes(strategy)) throw require_parse.unknownName("dark variant strategy", strategy, STRATEGIES);
|
|
25
|
+
if (strategy === "mirror") return [...scale].reverse();
|
|
26
|
+
if (strategy === "tone-swap") return scale.map((c) => {
|
|
27
|
+
const [, chroma, hue] = require_parse.componentsIn(c, "oklch");
|
|
28
|
+
return withAlpha(require_tone.toneColor(100 - require_tone.lstar(c), chroma, hue ?? 0, gamut), c);
|
|
29
|
+
});
|
|
30
|
+
const oklch = scale.map((c) => require_parse.componentsIn(c, "oklch"));
|
|
31
|
+
const ls = oklch.map((c) => c[0]);
|
|
32
|
+
const sum = Math.min(...ls) + Math.max(...ls);
|
|
33
|
+
return scale.map((c, i) => {
|
|
34
|
+
const [l, chroma, hue] = oklch[i];
|
|
35
|
+
return withAlpha(require_parse.toGamut({
|
|
36
|
+
colorSpace: "oklch",
|
|
37
|
+
components: [
|
|
38
|
+
sum - l,
|
|
39
|
+
chroma * .9,
|
|
40
|
+
hue
|
|
41
|
+
]
|
|
42
|
+
}, { gamut }), c);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function withAlpha(color, from) {
|
|
46
|
+
return from.alpha === void 0 ? color : {
|
|
47
|
+
...color,
|
|
48
|
+
alpha: from.alpha
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// src/palette/harmony.ts
|
|
52
|
+
/** The hue rotations, in degrees, that each scheme adds to the base color. */
|
|
53
|
+
const SCHEMES = {
|
|
54
|
+
__proto__: null,
|
|
55
|
+
complementary: [0, 180],
|
|
56
|
+
split: [
|
|
57
|
+
0,
|
|
58
|
+
150,
|
|
59
|
+
210
|
|
60
|
+
],
|
|
61
|
+
analogous: [
|
|
62
|
+
-30,
|
|
63
|
+
0,
|
|
64
|
+
30
|
|
65
|
+
],
|
|
66
|
+
triadic: [
|
|
67
|
+
0,
|
|
68
|
+
120,
|
|
69
|
+
240
|
|
70
|
+
],
|
|
71
|
+
tetradic: [
|
|
72
|
+
0,
|
|
73
|
+
90,
|
|
74
|
+
180,
|
|
75
|
+
270
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
const HUE_SPACES = [
|
|
79
|
+
"oklch",
|
|
80
|
+
"lch",
|
|
81
|
+
"hsl",
|
|
82
|
+
"hwb"
|
|
83
|
+
];
|
|
84
|
+
const hueIndex = (space) => space === "hsl" || space === "hwb" ? 0 : 2;
|
|
85
|
+
/**
|
|
86
|
+
* The colors of a harmony: `color` rotated around the hue wheel of `space`,
|
|
87
|
+
* with lightness and chroma held, in the order of the scheme's angles
|
|
88
|
+
* (`complementary` 0 and 180; `split` 0, 150, 210; `analogous` -30, 0, 30;
|
|
89
|
+
* `triadic` 0, 120, 240; `tetradic` 0, 90, 180, 270). The color itself is the
|
|
90
|
+
* entry at angle 0, returned as given. Rotated colors are mapped into `gamut`.
|
|
91
|
+
* The same angle looks different in `hsl` and `oklch`, so name the space.
|
|
92
|
+
*/
|
|
93
|
+
function harmony(color, scheme, options = {}) {
|
|
94
|
+
const { space = "oklch", gamut = "srgb" } = options;
|
|
95
|
+
const angles = SCHEMES[scheme];
|
|
96
|
+
if (!angles) throw require_parse.unknownName("harmony scheme", scheme, Object.keys(SCHEMES));
|
|
97
|
+
if (!HUE_SPACES.includes(space)) throw require_parse.unknownName("harmony space", space, HUE_SPACES);
|
|
98
|
+
const base = require_parse.toColor(color);
|
|
99
|
+
const inSpace = require_parse.convert(base, space);
|
|
100
|
+
const at = hueIndex(space);
|
|
101
|
+
const hue = inSpace.components[at] ?? 0;
|
|
102
|
+
return angles.map((angle) => {
|
|
103
|
+
if (angle === 0) return base;
|
|
104
|
+
const components = [...inSpace.components];
|
|
105
|
+
components[at] = require_parse.normalizeHue(hue + angle);
|
|
106
|
+
return require_parse.toGamut({
|
|
107
|
+
...inSpace,
|
|
108
|
+
components
|
|
109
|
+
}, { gamut });
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Nudges `color`'s hue toward `toward`, as Material's `Blend.harmonize` does:
|
|
114
|
+
* by half the hue difference, but at most 15 degrees, in OKLCH, keeping
|
|
115
|
+
* lightness and chroma. Use it to make a status color feel at home with a
|
|
116
|
+
* brand color without changing its meaning.
|
|
117
|
+
*/
|
|
118
|
+
function harmonize(color, toward, options = {}) {
|
|
119
|
+
const { max = 15, factor = .5, gamut = "srgb" } = options;
|
|
120
|
+
const from = require_parse.convert(require_parse.toColor(color), "oklch");
|
|
121
|
+
const to = require_parse.convert(require_parse.toColor(toward), "oklch");
|
|
122
|
+
const h0 = from.components[2] ?? 0;
|
|
123
|
+
const h1 = to.components[2] ?? h0;
|
|
124
|
+
const diff = require_parse.normalizeHue(h1 - h0 + 180) - 180;
|
|
125
|
+
const rotation = Math.min(Math.abs(diff) * factor, max) * Math.sign(diff);
|
|
126
|
+
const components = [...from.components];
|
|
127
|
+
components[2] = require_parse.normalizeHue(h0 + rotation);
|
|
128
|
+
return require_parse.toGamut({
|
|
129
|
+
...from,
|
|
130
|
+
components
|
|
131
|
+
}, { gamut });
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The opposite of a color. `lightness` flips OKLCH lightness (L becomes 1 - L)
|
|
135
|
+
* and keeps hue and chroma, so light turns dark without changing its hue;
|
|
136
|
+
* `rgb` inverts each sRGB channel, which also turns the hue to its complement.
|
|
137
|
+
* Alpha is kept. The result is mapped into sRGB.
|
|
138
|
+
*/
|
|
139
|
+
function inverse(color, mode = "lightness") {
|
|
140
|
+
const base = require_parse.toColor(color);
|
|
141
|
+
if (mode === "rgb") {
|
|
142
|
+
const c = require_parse.convert(base, "srgb");
|
|
143
|
+
return require_parse.toGamut({
|
|
144
|
+
...c,
|
|
145
|
+
components: c.components.map((x) => 1 - x)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const c = require_parse.convert(base, "oklch");
|
|
149
|
+
return require_parse.toGamut({
|
|
150
|
+
...c,
|
|
151
|
+
components: [
|
|
152
|
+
1 - c.components[0],
|
|
153
|
+
c.components[1],
|
|
154
|
+
c.components[2]
|
|
155
|
+
]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// src/palette/semantic.ts
|
|
159
|
+
const BG_TONE = {
|
|
160
|
+
primary: [40, 80],
|
|
161
|
+
primaryContainer: [90, 30],
|
|
162
|
+
surface: [98, 6],
|
|
163
|
+
surfaceVariant: [90, 30]
|
|
164
|
+
};
|
|
165
|
+
const FG_TONE = {
|
|
166
|
+
primary: [100, 20],
|
|
167
|
+
primaryContainer: [10, 90],
|
|
168
|
+
surface: [10, 90],
|
|
169
|
+
surfaceVariant: [30, 80]
|
|
170
|
+
};
|
|
171
|
+
const STATUS = {
|
|
172
|
+
success: 145,
|
|
173
|
+
warning: 85,
|
|
174
|
+
danger: 25,
|
|
175
|
+
info: 240
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* A role-based palette from one brand color: primary and its container, four
|
|
179
|
+
* status colors (success, warning, danger, info) harmonized toward the brand's
|
|
180
|
+
* hue, a surface and a variant tinted by the brand hue, and an outline. Every
|
|
181
|
+
* pair is built from L* tones, so a 50-tone gap or more reaches about 4.5 on
|
|
182
|
+
* WCAG 2 (the worst pair, 100 on 50, gives 4.48). Each pair records the ratio
|
|
183
|
+
* it reached; a foreground that falls short of `target` is replaced by the
|
|
184
|
+
* closest one that meets it, and `validate` reports any that cannot.
|
|
185
|
+
*/
|
|
186
|
+
function semanticPalette(options) {
|
|
187
|
+
const { mode = "light", contrast = "wcag2", target = 4.5, gamut = "srgb" } = options;
|
|
188
|
+
const fn = require_contrast.pick(contrast);
|
|
189
|
+
const dark = require_parse.checkMode(mode) === "dark";
|
|
190
|
+
const i = dark ? 1 : 0;
|
|
191
|
+
const brand = require_parse.toColor(options.brand);
|
|
192
|
+
const [, c, h] = require_parse.componentsIn(brand, "oklch");
|
|
193
|
+
const hue = h ?? 0;
|
|
194
|
+
const chroma = Math.max(c, .04);
|
|
195
|
+
const tint = .015;
|
|
196
|
+
const pair = (bg, fgTone, fgChroma, need = target) => {
|
|
197
|
+
let fg = require_tone.toneColor(fgTone, fgChroma, hue, gamut);
|
|
198
|
+
let ratio = Math.abs(fn(fg, bg));
|
|
199
|
+
if (ratio < need) {
|
|
200
|
+
const found = require_contrast.findContrast(bg, need, {
|
|
201
|
+
contrast: fn,
|
|
202
|
+
chroma: fgChroma,
|
|
203
|
+
hue
|
|
204
|
+
});
|
|
205
|
+
fg = found.color;
|
|
206
|
+
ratio = Math.abs(found.contrast);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
bg,
|
|
210
|
+
fg,
|
|
211
|
+
ratio,
|
|
212
|
+
target: need
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
const status = (name) => {
|
|
216
|
+
const base = require_tone.toneColor(BG_TONE.primary[i], .14, STATUS[name], gamut);
|
|
217
|
+
return pair(harmonize(base, brand, { gamut }), FG_TONE.primary[i], .02);
|
|
218
|
+
};
|
|
219
|
+
const surface = require_tone.toneColor(BG_TONE.surface[i], tint, hue, gamut);
|
|
220
|
+
const outline = pair(surface, dark ? 60 : 50, tint, 3);
|
|
221
|
+
return {
|
|
222
|
+
meta: {
|
|
223
|
+
mode,
|
|
224
|
+
contrast: fn.algorithm ?? "custom",
|
|
225
|
+
target,
|
|
226
|
+
gamut
|
|
227
|
+
},
|
|
228
|
+
scales: {
|
|
229
|
+
primary: require_tone.tonalPalette(hue, chroma, void 0, { gamut }),
|
|
230
|
+
neutral: require_tone.tonalPalette(hue, tint, void 0, { gamut })
|
|
231
|
+
},
|
|
232
|
+
roles: {
|
|
233
|
+
primary: pair(require_tone.toneColor(BG_TONE.primary[i], chroma, hue, gamut), FG_TONE.primary[i], .02),
|
|
234
|
+
primaryContainer: pair(require_tone.toneColor(BG_TONE.primaryContainer[i], chroma, hue, gamut), FG_TONE.primaryContainer[i], .02),
|
|
235
|
+
success: status("success"),
|
|
236
|
+
warning: status("warning"),
|
|
237
|
+
danger: status("danger"),
|
|
238
|
+
info: status("info"),
|
|
239
|
+
surface: pair(surface, FG_TONE.surface[i], tint),
|
|
240
|
+
surfaceVariant: pair(require_tone.toneColor(BG_TONE.surfaceVariant[i], tint, hue, gamut), FG_TONE.surfaceVariant[i], tint),
|
|
241
|
+
outline
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// src/palette/validate.ts
|
|
246
|
+
/**
|
|
247
|
+
* Checks a palette and lists what is wrong, so an agent can fix it:
|
|
248
|
+
* - `contrast`: a role pair whose measured contrast is under its target;
|
|
249
|
+
* - `lightness`: a scale whose OKLCH lightness does not change steadily in one
|
|
250
|
+
* direction from one step to the next;
|
|
251
|
+
* - `gamut`: a color outside the gamut (it would be clipped on display).
|
|
252
|
+
*/
|
|
253
|
+
function validate(palette, options = {}) {
|
|
254
|
+
const { contrast = "wcag2", target = 4.5, gamut = "srgb" } = options;
|
|
255
|
+
const fn = require_contrast.pick(contrast);
|
|
256
|
+
const issues = [];
|
|
257
|
+
const outside = (path, color) => {
|
|
258
|
+
if (!require_parse.inGamut(color, gamut)) issues.push({
|
|
259
|
+
kind: "gamut",
|
|
260
|
+
path,
|
|
261
|
+
message: `${path} is outside ${gamut}`
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
for (const [name, p] of Object.entries(palette.roles ?? {})) {
|
|
265
|
+
const need = p.target ?? target;
|
|
266
|
+
const ratio = Math.abs(fn(p.fg, p.bg));
|
|
267
|
+
if (ratio < need) issues.push({
|
|
268
|
+
kind: "contrast",
|
|
269
|
+
path: `roles.${name}`,
|
|
270
|
+
message: `roles.${name} reaches ${ratio.toFixed(2)}, needs ${need}`
|
|
271
|
+
});
|
|
272
|
+
outside(`roles.${name}.bg`, p.bg);
|
|
273
|
+
outside(`roles.${name}.fg`, p.fg);
|
|
274
|
+
}
|
|
275
|
+
for (const [name, scale] of Object.entries(palette.scales ?? {})) {
|
|
276
|
+
const ls = scale.map((c) => require_parse.componentsIn(c, "oklch")[0]);
|
|
277
|
+
const steps = ls.slice(1).map((l, k) => l - ls[k]);
|
|
278
|
+
const direction = Math.sign(steps.find((d) => d !== 0) ?? 0);
|
|
279
|
+
steps.forEach((d, k) => {
|
|
280
|
+
if (Math.sign(d) !== direction || Math.abs(d) < 1e-9) issues.push({
|
|
281
|
+
kind: "lightness",
|
|
282
|
+
path: `scales.${name}[${k + 1}]`,
|
|
283
|
+
message: `scales.${name} lightness does not change steadily at step ${k + 1}`
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
scale.forEach((c, k) => {
|
|
287
|
+
outside(`scales.${name}[${k}]`, c);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
ok: issues.length === 0,
|
|
292
|
+
issues
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
Object.defineProperty(exports, "darkVariant", {
|
|
296
|
+
enumerable: true,
|
|
297
|
+
get: function() {
|
|
298
|
+
return darkVariant;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
Object.defineProperty(exports, "harmonize", {
|
|
302
|
+
enumerable: true,
|
|
303
|
+
get: function() {
|
|
304
|
+
return harmonize;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
Object.defineProperty(exports, "harmony", {
|
|
308
|
+
enumerable: true,
|
|
309
|
+
get: function() {
|
|
310
|
+
return harmony;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
Object.defineProperty(exports, "inverse", {
|
|
314
|
+
enumerable: true,
|
|
315
|
+
get: function() {
|
|
316
|
+
return inverse;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
Object.defineProperty(exports, "semanticPalette", {
|
|
320
|
+
enumerable: true,
|
|
321
|
+
get: function() {
|
|
322
|
+
return semanticPalette;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
Object.defineProperty(exports, "validate", {
|
|
326
|
+
enumerable: true,
|
|
327
|
+
get: function() {
|
|
328
|
+
return validate;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
//# sourceMappingURL=palette-DyFV_KoJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"palette-DyFV_KoJ.js","names":["unknownName","componentsIn","toneColor","lstar","toGamut","unknownName","toColor","convert","normalizeHue","toGamut","pick","checkMode","toColor","componentsIn","toneColor","findContrast","tonalPalette","pick","inGamut","componentsIn"],"sources":["../src/palette/dark.ts","../src/palette/harmony.ts","../src/palette/semantic.ts","../src/palette/validate.ts"],"sourcesContent":["import { type GamutId, toGamut } from '../color/gamut.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport { unknownName } from '../color/unknown-name.js'\nimport { lstar, toneColor } from '../scale/tone.js'\n\n/** How `darkVariant` builds a dark scale from a light one: `mirror`, `tone-swap` or `lightness-invert`. */\nexport type DarkStrategy = 'mirror' | 'tone-swap' | 'lightness-invert'\n\nconst STRATEGIES: readonly string[] = ['mirror', 'tone-swap', 'lightness-invert']\n\n/** Options for `darkVariant`. */\nexport interface DarkVariantOptions {\n\t/** The gamut every color must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n/**\n * A dark version of a light scale (ordered light to dark), with the same\n * number of steps.\n * - `mirror`: step n takes the color of step N+1-n, so the scale runs dark to\n * light and step 1 is the old last step. Colors are unchanged.\n * - `tone-swap`: each color keeps its OKLCH hue and chroma and takes the CIE\n * L* tone 100 minus its own, as Material swaps tone 40 for 80. Contrast\n * between two steps is kept roughly the same.\n * - `lightness-invert`: OKLCH lightness is reflected within the scale's own\n * range, keeping step order, with chroma eased by 10% to curb vibration on\n * dark backgrounds.\n */\nexport function darkVariant(\n\tscale: readonly Color[],\n\tstrategy: DarkStrategy = 'tone-swap',\n\toptions: DarkVariantOptions = {}\n): Color[] {\n\tconst { gamut = 'srgb' } = options\n\tif (!STRATEGIES.includes(strategy)) throw unknownName('dark variant strategy', strategy, STRATEGIES)\n\tif (strategy === 'mirror') return [...scale].reverse()\n\tif (strategy === 'tone-swap') {\n\t\treturn scale.map((c) => {\n\t\t\tconst [, chroma, hue] = componentsIn(c, 'oklch') as [number, number, number | null]\n\t\t\treturn withAlpha(toneColor(100 - lstar(c), chroma, hue ?? 0, gamut), c)\n\t\t})\n\t}\n\tconst oklch = scale.map((c) => componentsIn(c, 'oklch') as [number, number, number | null])\n\tconst ls = oklch.map((c) => c[0])\n\tconst sum = Math.min(...ls) + Math.max(...ls)\n\treturn scale.map((c, i) => {\n\t\tconst [l, chroma, hue] = oklch[i] as [number, number, number | null]\n\t\treturn withAlpha(toGamut({ colorSpace: 'oklch', components: [sum - l, chroma * 0.9, hue] }, { gamut }), c)\n\t})\n}\n\nfunction withAlpha(color: Color, from: Color): Color {\n\treturn from.alpha === undefined ? color : { ...color, alpha: from.alpha }\n}\n","import { type GamutId, toGamut } from '../color/gamut.js'\nimport { normalizeHue } from '../color/math.js'\nimport { toColor } from '../color/parse.js'\nimport { convert } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport { unknownName } from '../color/unknown-name.js'\n\ntype ColorInput = Color | string\n\n/** The hue rotations, in degrees, that each scheme adds to the base color. */\nconst SCHEMES = {\n\t__proto__: null,\n\tcomplementary: [0, 180],\n\tsplit: [0, 150, 210],\n\tanalogous: [-30, 0, 30],\n\ttriadic: [0, 120, 240],\n\ttetradic: [0, 90, 180, 270]\n} as const\n\n/** A harmony scheme `harmony` accepts: `complementary`, `split`, `analogous`, `triadic` or `tetradic`. */\nexport type HarmonyScheme = keyof typeof SCHEMES\n\n/** Spaces with a hue channel that a scheme can rotate in. */\nexport type HarmonySpace = 'oklch' | 'lch' | 'hsl' | 'hwb'\n\n/** Options for `harmony`. */\nexport interface HarmonyOptions {\n\t/** The space whose hue is rotated. Default `oklch`, which keeps perceived lightness steady. */\n\tspace?: HarmonySpace\n\t/** The gamut every color must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\nconst HUE_SPACES: readonly string[] = ['oklch', 'lch', 'hsl', 'hwb']\n\nconst hueIndex = (space: HarmonySpace): number => (space === 'hsl' || space === 'hwb' ? 0 : 2)\n\n/**\n * The colors of a harmony: `color` rotated around the hue wheel of `space`,\n * with lightness and chroma held, in the order of the scheme's angles\n * (`complementary` 0 and 180; `split` 0, 150, 210; `analogous` -30, 0, 30;\n * `triadic` 0, 120, 240; `tetradic` 0, 90, 180, 270). The color itself is the\n * entry at angle 0, returned as given. Rotated colors are mapped into `gamut`.\n * The same angle looks different in `hsl` and `oklch`, so name the space.\n */\nexport function harmony(color: ColorInput, scheme: HarmonyScheme, options: HarmonyOptions = {}): Color[] {\n\tconst { space = 'oklch', gamut = 'srgb' } = options\n\tconst angles = SCHEMES[scheme]\n\tif (!angles) throw unknownName('harmony scheme', scheme, Object.keys(SCHEMES))\n\tif (!HUE_SPACES.includes(space)) throw unknownName('harmony space', space, HUE_SPACES)\n\tconst base = toColor(color)\n\tconst inSpace = convert(base, space)\n\tconst at = hueIndex(space)\n\tconst hue = (inSpace.components[at] as number | null) ?? 0\n\treturn angles.map((angle) => {\n\t\tif (angle === 0) return base\n\t\tconst components = [...inSpace.components]\n\t\tcomponents[at] = normalizeHue(hue + angle)\n\t\treturn toGamut({ ...inSpace, components }, { gamut })\n\t})\n}\n\n/** Options for `harmonize`. */\nexport interface HarmonizeOptions {\n\t/** The most the hue may move, in degrees. Default 15. */\n\tmax?: number\n\t/** The share of the hue difference to move. Default 0.5. */\n\tfactor?: number\n\t/** The gamut the result must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n/**\n * Nudges `color`'s hue toward `toward`, as Material's `Blend.harmonize` does:\n * by half the hue difference, but at most 15 degrees, in OKLCH, keeping\n * lightness and chroma. Use it to make a status color feel at home with a\n * brand color without changing its meaning.\n */\nexport function harmonize(color: ColorInput, toward: ColorInput, options: HarmonizeOptions = {}): Color {\n\tconst { max = 15, factor = 0.5, gamut = 'srgb' } = options\n\tconst from = convert(toColor(color), 'oklch')\n\tconst to = convert(toColor(toward), 'oklch')\n\tconst h0 = (from.components[2] as number | null) ?? 0\n\tconst h1 = (to.components[2] as number | null) ?? h0\n\t// Signed shortest way round, -180 to 180.\n\tconst diff = normalizeHue(h1 - h0 + 180) - 180\n\tconst rotation = Math.min(Math.abs(diff) * factor, max) * Math.sign(diff)\n\tconst components = [...from.components]\n\tcomponents[2] = normalizeHue(h0 + rotation)\n\treturn toGamut({ ...from, components }, { gamut })\n}\n\n/**\n * The opposite of a color. `lightness` flips OKLCH lightness (L becomes 1 - L)\n * and keeps hue and chroma, so light turns dark without changing its hue;\n * `rgb` inverts each sRGB channel, which also turns the hue to its complement.\n * Alpha is kept. The result is mapped into sRGB.\n */\nexport function inverse(color: ColorInput, mode: 'lightness' | 'rgb' = 'lightness'): Color {\n\tconst base = toColor(color)\n\tif (mode === 'rgb') {\n\t\tconst c = convert(base, 'srgb')\n\t\treturn toGamut({ ...c, components: c.components.map((x) => 1 - (x as number)) })\n\t}\n\tconst c = convert(base, 'oklch')\n\treturn toGamut({ ...c, components: [1 - (c.components[0] as number), c.components[1], c.components[2]] })\n}\n","import { type ContrastOption, findContrast, pick } from '../color/contrast.js'\nimport type { GamutId } from '../color/gamut.js'\nimport { toColor } from '../color/parse.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport { checkMode } from '../color/unknown-name.js'\nimport { tonalPalette, toneColor } from '../scale/tone.js'\nimport { harmonize } from './harmony.js'\n\n/** A background, the foreground drawn on it, and the contrast they reach. */\nexport interface RolePair {\n\tbg: Color\n\tfg: Color\n\t/** The contrast of `fg` on `bg`, as the algorithm reports it (absolute value). */\n\tratio: number\n\t/** The contrast the pair was built to reach. */\n\ttarget: number\n}\n\n/** Options for `semanticPalette`. Only `brand` is required. */\nexport interface SemanticPaletteOptions {\n\t/** The brand color; it sets the hue of the primary, the neutrals and the harmonized status colors. */\n\tbrand: Color | string\n\t/** Default `light`. */\n\tmode?: 'light' | 'dark'\n\t/** The contrast measure. Default `wcag2`. */\n\tcontrast?: ContrastOption\n\t/** The contrast text pairs must reach. Default 4.5, which fits WCAG 2 (pass about 60 for APCA Lc). */\n\ttarget?: number\n\t/** The gamut every color must fit. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n/** A role in a {@link SemanticPalette}. Each role holds a {@link RolePair}. */\nexport type SemanticRole =\n\t| 'primary'\n\t| 'primaryContainer'\n\t| 'success'\n\t| 'warning'\n\t| 'danger'\n\t| 'info'\n\t| 'surface'\n\t| 'surfaceVariant'\n\t| 'outline'\n\n/** What `semanticPalette` returns: the settings it used, the tonal scales it built from, and a pair for each role. */\nexport interface SemanticPalette {\n\tmeta: { mode: 'light' | 'dark'; contrast: string; target: number; gamut: GamutId }\n\t/** Material's 13 tones, dark to light, in the brand's hue (`primary`) and a faint tint of it (`neutral`). */\n\tscales: { primary: Color[]; neutral: Color[] }\n\troles: Record<SemanticRole, RolePair>\n}\n\n// Tones (light, dark) per role: Material's 2021 assignments (see the research notes, B-palettes).\nconst BG_TONE = { primary: [40, 80], primaryContainer: [90, 30], surface: [98, 6], surfaceVariant: [90, 30] } as const\nconst FG_TONE = { primary: [100, 20], primaryContainer: [10, 90], surface: [10, 90], surfaceVariant: [30, 80] } as const\n\n// OKLCH hues of the status colors, before they are harmonized toward the brand.\nconst STATUS = { success: 145, warning: 85, danger: 25, info: 240 } as const\n\n/**\n * A role-based palette from one brand color: primary and its container, four\n * status colors (success, warning, danger, info) harmonized toward the brand's\n * hue, a surface and a variant tinted by the brand hue, and an outline. Every\n * pair is built from L* tones, so a 50-tone gap or more reaches about 4.5 on\n * WCAG 2 (the worst pair, 100 on 50, gives 4.48). Each pair records the ratio\n * it reached; a foreground that falls short of `target` is replaced by the\n * closest one that meets it, and `validate` reports any that cannot.\n */\nexport function semanticPalette(options: SemanticPaletteOptions): SemanticPalette {\n\tconst { mode = 'light', contrast = 'wcag2', target = 4.5, gamut = 'srgb' } = options\n\tconst fn = pick(contrast)\n\tconst dark = checkMode(mode) === 'dark'\n\tconst i = dark ? 1 : 0\n\tconst brand = toColor(options.brand)\n\tconst [, c, h] = componentsIn(brand, 'oklch') as [number, number, number | null]\n\tconst hue = h ?? 0\n\tconst chroma = Math.max(c, 0.04)\n\tconst tint = 0.015\n\n\tconst pair = (bg: Color, fgTone: number, fgChroma: number, need = target): RolePair => {\n\t\tlet fg = toneColor(fgTone, fgChroma, hue, gamut)\n\t\tlet ratio = Math.abs(fn(fg, bg))\n\t\tif (ratio < need) {\n\t\t\tconst found = findContrast(bg, need, { contrast: fn, chroma: fgChroma, hue })\n\t\t\tfg = found.color\n\t\t\tratio = Math.abs(found.contrast)\n\t\t}\n\t\treturn { bg, fg, ratio, target: need }\n\t}\n\tconst status = (name: keyof typeof STATUS): RolePair => {\n\t\tconst base = toneColor(BG_TONE.primary[i], 0.14, STATUS[name], gamut)\n\t\treturn pair(harmonize(base, brand, { gamut }), FG_TONE.primary[i], 0.02)\n\t}\n\tconst surface = toneColor(BG_TONE.surface[i], tint, hue, gamut)\n\tconst outline = pair(surface, dark ? 60 : 50, tint, 3)\n\n\treturn {\n\t\tmeta: { mode, contrast: fn.algorithm ?? 'custom', target, gamut },\n\t\tscales: {\n\t\t\tprimary: tonalPalette(hue, chroma, undefined, { gamut }),\n\t\t\tneutral: tonalPalette(hue, tint, undefined, { gamut })\n\t\t},\n\t\troles: {\n\t\t\tprimary: pair(toneColor(BG_TONE.primary[i], chroma, hue, gamut), FG_TONE.primary[i], 0.02),\n\t\t\tprimaryContainer: pair(\n\t\t\t\ttoneColor(BG_TONE.primaryContainer[i], chroma, hue, gamut),\n\t\t\t\tFG_TONE.primaryContainer[i],\n\t\t\t\t0.02\n\t\t\t),\n\t\t\tsuccess: status('success'),\n\t\t\twarning: status('warning'),\n\t\t\tdanger: status('danger'),\n\t\t\tinfo: status('info'),\n\t\t\tsurface: pair(surface, FG_TONE.surface[i], tint),\n\t\t\tsurfaceVariant: pair(toneColor(BG_TONE.surfaceVariant[i], tint, hue, gamut), FG_TONE.surfaceVariant[i], tint),\n\t\t\toutline\n\t\t}\n\t}\n}\n","import { type ContrastOption, pick } from '../color/contrast.js'\nimport { type GamutId, inGamut } from '../color/gamut.js'\nimport { componentsIn } from '../color/spaces.js'\nimport type { Color } from '../color/types.js'\nimport type { RolePair } from './semantic.js'\n\n/** What `validate` accepts: a {@link SemanticPalette}, or any object with role pairs and/or scales. */\nexport interface PaletteLike {\n\troles?: Record<string, RolePair>\n\tscales?: Record<string, readonly Color[]>\n}\n\n/** Options for `validate`. */\nexport interface ValidateOptions {\n\t/** The measure used to re-check each pair. Default `wcag2`. */\n\tcontrast?: ContrastOption\n\t/** The target for a pair that carries none. Default 4.5. */\n\ttarget?: number\n\t/** The gamut colors must fit; a color outside it was clipped or never mapped. Default `srgb`. */\n\tgamut?: GamutId\n}\n\n/** One problem `validate` found in a palette. */\nexport interface PaletteIssue {\n\tkind: 'contrast' | 'lightness' | 'gamut'\n\t/** Where: `roles.danger`, `scales.primary[3]`. */\n\tpath: string\n\tmessage: string\n}\n\n/** What `validate` returns: `ok` is true when `issues` is empty. */\nexport interface ValidateResult {\n\tok: boolean\n\tissues: PaletteIssue[]\n}\n\n/**\n * Checks a palette and lists what is wrong, so an agent can fix it:\n * - `contrast`: a role pair whose measured contrast is under its target;\n * - `lightness`: a scale whose OKLCH lightness does not change steadily in one\n * direction from one step to the next;\n * - `gamut`: a color outside the gamut (it would be clipped on display).\n */\nexport function validate(palette: PaletteLike, options: ValidateOptions = {}): ValidateResult {\n\tconst { contrast = 'wcag2', target = 4.5, gamut = 'srgb' } = options\n\tconst fn = pick(contrast)\n\tconst issues: PaletteIssue[] = []\n\tconst outside = (path: string, color: Color) => {\n\t\tif (!inGamut(color, gamut)) issues.push({ kind: 'gamut', path, message: `${path} is outside ${gamut}` })\n\t}\n\n\tfor (const [name, p] of Object.entries(palette.roles ?? {})) {\n\t\tconst need = p.target ?? target\n\t\tconst ratio = Math.abs(fn(p.fg, p.bg))\n\t\tif (ratio < need)\n\t\t\tissues.push({\n\t\t\t\tkind: 'contrast',\n\t\t\t\tpath: `roles.${name}`,\n\t\t\t\tmessage: `roles.${name} reaches ${ratio.toFixed(2)}, needs ${need}`\n\t\t\t})\n\t\toutside(`roles.${name}.bg`, p.bg)\n\t\toutside(`roles.${name}.fg`, p.fg)\n\t}\n\n\tfor (const [name, scale] of Object.entries(palette.scales ?? {})) {\n\t\tconst ls = scale.map((c) => componentsIn(c, 'oklch')[0] as number)\n\t\tconst steps = ls.slice(1).map((l, k) => l - (ls[k] as number))\n\t\tconst direction = Math.sign(steps.find((d) => d !== 0) ?? 0)\n\t\tsteps.forEach((d, k) => {\n\t\t\tif (Math.sign(d) !== direction || Math.abs(d) < 1e-9)\n\t\t\t\tissues.push({\n\t\t\t\t\tkind: 'lightness',\n\t\t\t\t\tpath: `scales.${name}[${k + 1}]`,\n\t\t\t\t\tmessage: `scales.${name} lightness does not change steadily at step ${k + 1}`\n\t\t\t\t})\n\t\t})\n\t\tscale.forEach((c, k) => {\n\t\t\toutside(`scales.${name}[${k}]`, c)\n\t\t})\n\t}\n\treturn { ok: issues.length === 0, issues }\n}\n"],"mappings":"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"}
|