@pixerate/editor 0.146.0 → 0.148.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 +3 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/agent/index.cjs +36 -34
- package/dist/agent/index.js +7 -5
- package/dist/animation/index.cjs +5 -3
- package/dist/animation/index.js +4 -2
- package/dist/animation-editor/index.cjs +62 -60
- package/dist/animation-editor/index.js +6 -4
- package/dist/canvas/index.cjs +36 -34
- package/dist/canvas/index.js +19 -17
- package/dist/chart/index.cjs +88 -86
- package/dist/chart/index.js +7 -5
- package/dist/{chunk-WBJ6BG4R.js → chunk-2AQL4JHM.js} +6 -6
- package/dist/{chunk-HLMF3E53.cjs → chunk-2WDZI6QD.cjs} +15 -15
- package/dist/{chunk-ZNHB5RG6.js → chunk-37RLSNQ3.js} +2 -2
- package/dist/{chunk-5WOY52O6.cjs → chunk-4YQKRHMI.cjs} +8 -8
- package/dist/chunk-6ONMQ6WF.js +113 -0
- package/dist/{chunk-L434J57V.cjs → chunk-6QTON3VW.cjs} +16 -16
- package/dist/chunk-AA34BA7G.js +1913 -0
- package/dist/{chunk-QX25HWFL.js → chunk-AY65VQSU.js} +1 -1
- package/dist/{chunk-ONZJD2OD.cjs → chunk-BROJYMWU.cjs} +57 -153
- package/dist/{chunk-YM75DN54.js → chunk-DSZCDNTM.js} +1 -1
- package/dist/{chunk-PUR6BZFA.cjs → chunk-DVLMW3GB.cjs} +53 -53
- package/dist/{chunk-ALUWD3FG.js → chunk-EIUTJPEL.js} +1 -1
- package/dist/{chunk-X65C4DWV.js → chunk-EQ4SF4X4.js} +1 -1
- package/dist/{chunk-77SG47WT.js → chunk-FLOGD3QB.js} +1 -1
- package/dist/{chunk-AJSW5GMP.js → chunk-HVQVHUTV.js} +19 -115
- package/dist/{chunk-UR37VCIR.cjs → chunk-I4AHLP7F.cjs} +16 -35
- package/dist/chunk-IPJ3TEYR.cjs +113 -0
- package/dist/chunk-KY3FYKP3.js +52 -0
- package/dist/chunk-LEUZ4FRC.cjs +52 -0
- package/dist/{chunk-H6FX45CP.cjs → chunk-LIXLD433.cjs} +37 -37
- package/dist/{chunk-6GAYGQDC.js → chunk-O5ML2I6O.js} +3 -3
- package/dist/{chunk-KI2VBONS.js → chunk-OET45HIR.js} +2 -2
- package/dist/{chunk-XNZWATSB.cjs → chunk-QCEQIRJQ.cjs} +85 -85
- package/dist/{chunk-HRI3ZMDM.cjs → chunk-RHXW4TA5.cjs} +3 -3
- package/dist/{chunk-UUTTJK77.cjs → chunk-SUF6WRXR.cjs} +2 -2
- package/dist/{chunk-RN767PO6.js → chunk-SUVZJ2CP.js} +15 -34
- package/dist/{chunk-FLUTRPLY.cjs → chunk-TLMRGLQQ.cjs} +16 -16
- package/dist/{chunk-DAS7M4RY.js → chunk-UILRJQFD.js} +2 -2
- package/dist/chunk-UKCJ43YL.cjs +1913 -0
- package/dist/{chunk-XQ33O5BC.cjs → chunk-X35BV5YT.cjs} +44 -44
- package/dist/{chunk-6HMEHNWW.cjs → chunk-XTU6QO2W.cjs} +26 -26
- package/dist/{chunk-VM6JYWXQ.js → chunk-ZQUANEK4.js} +2 -2
- package/dist/cms/index.cjs +5 -3
- package/dist/cms/index.js +18 -16
- package/dist/color/contrast.d.cts +14 -0
- package/dist/color/contrast.d.ts +14 -0
- package/dist/color/convert.d.cts +30 -0
- package/dist/color/convert.d.ts +30 -0
- package/dist/color/dual.d.cts +98 -0
- package/dist/color/dual.d.ts +98 -0
- package/dist/color/format.d.cts +12 -0
- package/dist/color/format.d.ts +12 -0
- package/dist/color/gamut.d.cts +12 -0
- package/dist/color/gamut.d.ts +12 -0
- package/dist/color/harmony.d.cts +22 -0
- package/dist/color/harmony.d.ts +22 -0
- package/dist/color/index.cjs +115 -0
- package/dist/color/index.d.cts +18 -0
- package/dist/color/index.d.ts +18 -0
- package/dist/color/index.js +115 -0
- package/dist/color/mix.d.cts +11 -0
- package/dist/color/mix.d.ts +11 -0
- package/dist/color/named.d.cts +5 -0
- package/dist/color/named.d.ts +5 -0
- package/dist/color/parse.d.cts +11 -0
- package/dist/color/parse.d.ts +11 -0
- package/dist/color/picker.d.cts +52 -0
- package/dist/color/picker.d.ts +52 -0
- package/dist/color/recent.d.cts +8 -0
- package/dist/color/recent.d.ts +8 -0
- package/dist/color/tools.d.cts +15 -0
- package/dist/color/tools.d.ts +15 -0
- package/dist/color/types.d.cts +41 -0
- package/dist/color/types.d.ts +41 -0
- package/dist/color/vision.d.cts +36 -0
- package/dist/color/vision.d.ts +36 -0
- package/dist/effects/authoring-session.d.cts +25 -0
- package/dist/effects/authoring-session.d.ts +25 -0
- package/dist/effects/authoring-tools.d.cts +31 -0
- package/dist/effects/authoring-tools.d.ts +31 -0
- package/dist/effects/authoring.d.cts +152 -0
- package/dist/effects/authoring.d.ts +152 -0
- package/dist/effects/conformance.d.cts +2 -0
- package/dist/effects/conformance.d.ts +2 -0
- package/dist/effects/fake.d.cts +2 -0
- package/dist/effects/fake.d.ts +2 -0
- package/dist/effects/index.cjs +1216 -9
- package/dist/effects/index.d.cts +3 -0
- package/dist/effects/index.d.ts +3 -0
- package/dist/effects/index.js +1210 -3
- package/dist/effects/types.d.cts +9 -2
- package/dist/effects/types.d.ts +9 -2
- package/dist/effects/ui.d.cts +8 -0
- package/dist/effects/ui.d.ts +8 -0
- package/dist/export/index.cjs +6 -4
- package/dist/export/index.js +5 -3
- package/dist/extensions/index.cjs +10 -8
- package/dist/extensions/index.js +9 -7
- package/dist/gradient/controller.d.cts +109 -0
- package/dist/gradient/controller.d.ts +109 -0
- package/dist/gradient/editor.d.cts +38 -0
- package/dist/gradient/editor.d.ts +38 -0
- package/dist/gradient/index.cjs +1549 -0
- package/dist/gradient/index.d.cts +5 -0
- package/dist/gradient/index.d.ts +5 -0
- package/dist/gradient/index.js +1549 -0
- package/dist/gradient/model.d.cts +94 -0
- package/dist/gradient/model.d.ts +94 -0
- package/dist/gradient/tools.d.cts +11 -0
- package/dist/gradient/tools.d.ts +11 -0
- package/dist/gradient/types.d.cts +44 -0
- package/dist/gradient/types.d.ts +44 -0
- package/dist/image-editor/index.cjs +649 -647
- package/dist/image-editor/index.js +6 -4
- package/dist/index.cjs +19 -17
- package/dist/index.js +16 -14
- package/dist/paint-editor/color.d.cts +3 -13
- package/dist/paint-editor/color.d.ts +3 -13
- package/dist/paint-editor/index.cjs +23 -68
- package/dist/paint-editor/index.js +15 -60
- package/dist/vector/index.cjs +950 -948
- package/dist/vector/index.js +5 -3
- package/dist/video-editor/index.cjs +5 -3
- package/dist/video-editor/index.js +4 -2
- package/package.json +21 -1
- package/skills/README.md +2 -0
- package/skills/pixerate-color/SKILL.md +69 -0
- package/skills/pixerate-effect-authoring/SKILL.md +80 -0
- package/dist/chunk-45VRG4RB.js +0 -65
- package/dist/chunk-5FXOR7F2.cjs +0 -65
|
@@ -0,0 +1,1913 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
var _chunkIPJ3TEYRcjs = require('./chunk-IPJ3TEYR.cjs');
|
|
9
|
+
|
|
10
|
+
// src/color/convert.ts
|
|
11
|
+
function srgbToLinear(v) {
|
|
12
|
+
const sign = Math.sign(v);
|
|
13
|
+
const abs = Math.abs(v);
|
|
14
|
+
return sign * (abs <= 0.04045 ? abs / 12.92 : ((abs + 0.055) / 1.055) ** 2.4);
|
|
15
|
+
}
|
|
16
|
+
function linearToSrgb(v) {
|
|
17
|
+
const sign = Math.sign(v);
|
|
18
|
+
const abs = Math.abs(v);
|
|
19
|
+
return sign * (abs <= 31308e-7 ? 12.92 * abs : 1.055 * abs ** (1 / 2.4) - 0.055);
|
|
20
|
+
}
|
|
21
|
+
function rgbToHsl(rgba) {
|
|
22
|
+
const { r, g, b, a } = rgba;
|
|
23
|
+
const max = Math.max(r, g, b);
|
|
24
|
+
const min = Math.min(r, g, b);
|
|
25
|
+
const d = max - min;
|
|
26
|
+
const l = (max + min) / 2;
|
|
27
|
+
let h = 0;
|
|
28
|
+
if (d > 0) {
|
|
29
|
+
if (max === r) h = 60 * ((g - b) / d % 6);
|
|
30
|
+
else if (max === g) h = 60 * ((b - r) / d + 2);
|
|
31
|
+
else h = 60 * ((r - g) / d + 4);
|
|
32
|
+
}
|
|
33
|
+
if (h < 0) h += 360;
|
|
34
|
+
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
35
|
+
return { h, s, l, alpha: a };
|
|
36
|
+
}
|
|
37
|
+
function hslToRgb(hsla) {
|
|
38
|
+
const { h, s, l, alpha } = hsla;
|
|
39
|
+
const hh = (h % 360 + 360) % 360 / 60;
|
|
40
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
41
|
+
const x = c * (1 - Math.abs(hh % 2 - 1));
|
|
42
|
+
const m = l - c / 2;
|
|
43
|
+
let r = 0;
|
|
44
|
+
let g = 0;
|
|
45
|
+
let b = 0;
|
|
46
|
+
if (hh < 1) {
|
|
47
|
+
r = c;
|
|
48
|
+
g = x;
|
|
49
|
+
b = 0;
|
|
50
|
+
} else if (hh < 2) {
|
|
51
|
+
r = x;
|
|
52
|
+
g = c;
|
|
53
|
+
b = 0;
|
|
54
|
+
} else if (hh < 3) {
|
|
55
|
+
r = 0;
|
|
56
|
+
g = c;
|
|
57
|
+
b = x;
|
|
58
|
+
} else if (hh < 4) {
|
|
59
|
+
r = 0;
|
|
60
|
+
g = x;
|
|
61
|
+
b = c;
|
|
62
|
+
} else if (hh < 5) {
|
|
63
|
+
r = x;
|
|
64
|
+
g = 0;
|
|
65
|
+
b = c;
|
|
66
|
+
} else {
|
|
67
|
+
r = c;
|
|
68
|
+
g = 0;
|
|
69
|
+
b = x;
|
|
70
|
+
}
|
|
71
|
+
return { r: r + m, g: g + m, b: b + m, a: alpha };
|
|
72
|
+
}
|
|
73
|
+
function rgbToHsv(rgba) {
|
|
74
|
+
const { r, g, b, a } = rgba;
|
|
75
|
+
const max = Math.max(r, g, b);
|
|
76
|
+
const min = Math.min(r, g, b);
|
|
77
|
+
const d = max - min;
|
|
78
|
+
let h = 0;
|
|
79
|
+
if (d > 0) {
|
|
80
|
+
if (max === r) h = 60 * ((g - b) / d % 6);
|
|
81
|
+
else if (max === g) h = 60 * ((b - r) / d + 2);
|
|
82
|
+
else h = 60 * ((r - g) / d + 4);
|
|
83
|
+
}
|
|
84
|
+
if (h < 0) h += 360;
|
|
85
|
+
const s = max === 0 ? 0 : d / max;
|
|
86
|
+
return { h, s, v: max, alpha: a };
|
|
87
|
+
}
|
|
88
|
+
function hsvToRgb(hsva) {
|
|
89
|
+
const { h, s, v, alpha } = hsva;
|
|
90
|
+
const hh = (h % 360 + 360) % 360 / 60;
|
|
91
|
+
const c = v * s;
|
|
92
|
+
const x = c * (1 - Math.abs(hh % 2 - 1));
|
|
93
|
+
const m = v - c;
|
|
94
|
+
let r = 0;
|
|
95
|
+
let g = 0;
|
|
96
|
+
let b = 0;
|
|
97
|
+
if (hh < 1) {
|
|
98
|
+
r = c;
|
|
99
|
+
g = x;
|
|
100
|
+
b = 0;
|
|
101
|
+
} else if (hh < 2) {
|
|
102
|
+
r = x;
|
|
103
|
+
g = c;
|
|
104
|
+
b = 0;
|
|
105
|
+
} else if (hh < 3) {
|
|
106
|
+
r = 0;
|
|
107
|
+
g = c;
|
|
108
|
+
b = x;
|
|
109
|
+
} else if (hh < 4) {
|
|
110
|
+
r = 0;
|
|
111
|
+
g = x;
|
|
112
|
+
b = c;
|
|
113
|
+
} else if (hh < 5) {
|
|
114
|
+
r = x;
|
|
115
|
+
g = 0;
|
|
116
|
+
b = c;
|
|
117
|
+
} else {
|
|
118
|
+
r = c;
|
|
119
|
+
g = 0;
|
|
120
|
+
b = x;
|
|
121
|
+
}
|
|
122
|
+
return { r: r + m, g: g + m, b: b + m, a: alpha };
|
|
123
|
+
}
|
|
124
|
+
function rgbToOklab(rgba) {
|
|
125
|
+
const lr = srgbToLinear(rgba.r);
|
|
126
|
+
const lg = srgbToLinear(rgba.g);
|
|
127
|
+
const lb = srgbToLinear(rgba.b);
|
|
128
|
+
const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
|
|
129
|
+
const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
|
|
130
|
+
const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
|
|
131
|
+
return {
|
|
132
|
+
l: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
133
|
+
a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
134
|
+
b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
|
135
|
+
alpha: rgba.a
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function oklabToRgb(lab) {
|
|
139
|
+
const l_ = lab.l + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
|
|
140
|
+
const m_ = lab.l - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
|
|
141
|
+
const s_ = lab.l - 0.0894841775 * lab.a - 1.291485548 * lab.b;
|
|
142
|
+
const l = l_ ** 3;
|
|
143
|
+
const m = m_ ** 3;
|
|
144
|
+
const s = s_ ** 3;
|
|
145
|
+
return {
|
|
146
|
+
r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
|
147
|
+
g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
|
148
|
+
b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
|
|
149
|
+
a: lab.alpha
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function oklabToOklch(lab) {
|
|
153
|
+
const c = Math.hypot(lab.a, lab.b);
|
|
154
|
+
let h = 0;
|
|
155
|
+
if (c >= 1e-6) {
|
|
156
|
+
h = Math.atan2(lab.b, lab.a) * 180 / Math.PI;
|
|
157
|
+
if (h < 0) h += 360;
|
|
158
|
+
if (h >= 360) h -= 360;
|
|
159
|
+
}
|
|
160
|
+
return { l: lab.l, c, h, alpha: lab.alpha };
|
|
161
|
+
}
|
|
162
|
+
function oklchToOklab(lch) {
|
|
163
|
+
const rad = lch.h * Math.PI / 180;
|
|
164
|
+
return {
|
|
165
|
+
l: lch.l,
|
|
166
|
+
a: lch.c * Math.cos(rad),
|
|
167
|
+
b: lch.c * Math.sin(rad),
|
|
168
|
+
alpha: lch.alpha
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
var D65_XYZ = _chunkIPJ3TEYRcjs.xyToXyz.call(void 0, _chunkIPJ3TEYRcjs.D65_XY);
|
|
172
|
+
var SRGB_TO_XYZ = _chunkIPJ3TEYRcjs.rgbToXyzMatrix.call(void 0, [0.64, 0.33], [0.3, 0.6], [0.15, 0.06], D65_XYZ);
|
|
173
|
+
var P3_TO_XYZ = _chunkIPJ3TEYRcjs.rgbToXyzMatrix.call(void 0, [0.68, 0.32], [0.265, 0.69], [0.15, 0.06], D65_XYZ);
|
|
174
|
+
var XYZ_TO_SRGB = _chunkIPJ3TEYRcjs.invert.call(void 0, SRGB_TO_XYZ);
|
|
175
|
+
var XYZ_TO_P3 = _chunkIPJ3TEYRcjs.invert.call(void 0, P3_TO_XYZ);
|
|
176
|
+
var P3_TO_SRGB_MAT = _chunkIPJ3TEYRcjs.mul.call(void 0, XYZ_TO_SRGB, P3_TO_XYZ);
|
|
177
|
+
var SRGB_TO_P3_MAT = _chunkIPJ3TEYRcjs.mul.call(void 0, XYZ_TO_P3, SRGB_TO_XYZ);
|
|
178
|
+
function linearSrgbToDisplayP3(rgb) {
|
|
179
|
+
const out = _chunkIPJ3TEYRcjs.apply.call(void 0, SRGB_TO_P3_MAT, [rgb.r, rgb.g, rgb.b]);
|
|
180
|
+
return { r: out[0], g: out[1], b: out[2], a: rgb.a };
|
|
181
|
+
}
|
|
182
|
+
function displayP3ToLinearSrgb(rgb) {
|
|
183
|
+
const out = _chunkIPJ3TEYRcjs.apply.call(void 0, P3_TO_SRGB_MAT, [rgb.r, rgb.g, rgb.b]);
|
|
184
|
+
return { r: out[0], g: out[1], b: out[2], a: rgb.a };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/color/named.ts
|
|
188
|
+
var NAMED_COLORS = {
|
|
189
|
+
aliceblue: "#f0f8ff",
|
|
190
|
+
antiquewhite: "#faebd7",
|
|
191
|
+
aqua: "#00ffff",
|
|
192
|
+
aquamarine: "#7fffd4",
|
|
193
|
+
azure: "#f0ffff",
|
|
194
|
+
beige: "#f5f5dc",
|
|
195
|
+
bisque: "#ffe4c4",
|
|
196
|
+
black: "#000000",
|
|
197
|
+
blanchedalmond: "#ffebcd",
|
|
198
|
+
blue: "#0000ff",
|
|
199
|
+
blueviolet: "#8a2be2",
|
|
200
|
+
brown: "#a52a2a",
|
|
201
|
+
burlywood: "#deb887",
|
|
202
|
+
cadetblue: "#5f9ea0",
|
|
203
|
+
chartreuse: "#7fff00",
|
|
204
|
+
chocolate: "#d2691e",
|
|
205
|
+
coral: "#ff7f50",
|
|
206
|
+
cornflowerblue: "#6495ed",
|
|
207
|
+
cornsilk: "#fff8dc",
|
|
208
|
+
crimson: "#dc143c",
|
|
209
|
+
cyan: "#00ffff",
|
|
210
|
+
darkblue: "#00008b",
|
|
211
|
+
darkcyan: "#008b8b",
|
|
212
|
+
darkgoldenrod: "#b8860b",
|
|
213
|
+
darkgray: "#a9a9a9",
|
|
214
|
+
darkgreen: "#006400",
|
|
215
|
+
darkgrey: "#a9a9a9",
|
|
216
|
+
darkkhaki: "#bdb76b",
|
|
217
|
+
darkmagenta: "#8b008b",
|
|
218
|
+
darkolivegreen: "#556b2f",
|
|
219
|
+
darkorange: "#ff8c00",
|
|
220
|
+
darkorchid: "#9932cc",
|
|
221
|
+
darkred: "#8b0000",
|
|
222
|
+
darksalmon: "#e9967a",
|
|
223
|
+
darkseagreen: "#8fbc8f",
|
|
224
|
+
darkslateblue: "#483d8b",
|
|
225
|
+
darkslategray: "#2f4f4f",
|
|
226
|
+
darkslategrey: "#2f4f4f",
|
|
227
|
+
darkturquoise: "#00ced1",
|
|
228
|
+
darkviolet: "#9400d3",
|
|
229
|
+
deeppink: "#ff1493",
|
|
230
|
+
deepskyblue: "#00bfff",
|
|
231
|
+
dimgray: "#696969",
|
|
232
|
+
dimgrey: "#696969",
|
|
233
|
+
dodgerblue: "#1e90ff",
|
|
234
|
+
firebrick: "#b22222",
|
|
235
|
+
floralwhite: "#fffaf0",
|
|
236
|
+
forestgreen: "#228b22",
|
|
237
|
+
fuchsia: "#ff00ff",
|
|
238
|
+
gainsboro: "#dcdcdc",
|
|
239
|
+
ghostwhite: "#f8f8ff",
|
|
240
|
+
gold: "#ffd700",
|
|
241
|
+
goldenrod: "#daa520",
|
|
242
|
+
gray: "#808080",
|
|
243
|
+
green: "#008000",
|
|
244
|
+
greenyellow: "#adff2f",
|
|
245
|
+
grey: "#808080",
|
|
246
|
+
honeydew: "#f0fff0",
|
|
247
|
+
hotpink: "#ff69b4",
|
|
248
|
+
indianred: "#cd5c5c",
|
|
249
|
+
indigo: "#4b0082",
|
|
250
|
+
ivory: "#fffff0",
|
|
251
|
+
khaki: "#f0e68c",
|
|
252
|
+
lavender: "#e6e6fa",
|
|
253
|
+
lavenderblush: "#fff0f5",
|
|
254
|
+
lawngreen: "#7cfc00",
|
|
255
|
+
lemonchiffon: "#fffacd",
|
|
256
|
+
lightblue: "#add8e6",
|
|
257
|
+
lightcoral: "#f08080",
|
|
258
|
+
lightcyan: "#e0ffff",
|
|
259
|
+
lightgoldenrodyellow: "#fafad2",
|
|
260
|
+
lightgray: "#d3d3d3",
|
|
261
|
+
lightgreen: "#90ee90",
|
|
262
|
+
lightgrey: "#d3d3d3",
|
|
263
|
+
lightpink: "#ffb6c1",
|
|
264
|
+
lightsalmon: "#ffa07a",
|
|
265
|
+
lightseagreen: "#20b2aa",
|
|
266
|
+
lightskyblue: "#87cefa",
|
|
267
|
+
lightslategray: "#778899",
|
|
268
|
+
lightslategrey: "#778899",
|
|
269
|
+
lightsteelblue: "#b0c4de",
|
|
270
|
+
lightyellow: "#ffffe0",
|
|
271
|
+
lime: "#00ff00",
|
|
272
|
+
limegreen: "#32cd32",
|
|
273
|
+
linen: "#faf0e6",
|
|
274
|
+
magenta: "#ff00ff",
|
|
275
|
+
maroon: "#800000",
|
|
276
|
+
mediumaquamarine: "#66cdaa",
|
|
277
|
+
mediumblue: "#0000cd",
|
|
278
|
+
mediumorchid: "#ba55d3",
|
|
279
|
+
mediumpurple: "#9370db",
|
|
280
|
+
mediumseagreen: "#3cb371",
|
|
281
|
+
mediumslateblue: "#7b68ee",
|
|
282
|
+
mediumspringgreen: "#00fa9a",
|
|
283
|
+
mediumturquoise: "#48d1cc",
|
|
284
|
+
mediumvioletred: "#c71585",
|
|
285
|
+
midnightblue: "#191970",
|
|
286
|
+
mintcream: "#f5fffa",
|
|
287
|
+
mistyrose: "#ffe4e1",
|
|
288
|
+
moccasin: "#ffe4b5",
|
|
289
|
+
navajowhite: "#ffdead",
|
|
290
|
+
navy: "#000080",
|
|
291
|
+
oldlace: "#fdf5e6",
|
|
292
|
+
olive: "#808000",
|
|
293
|
+
olivedrab: "#6b8e23",
|
|
294
|
+
orange: "#ffa500",
|
|
295
|
+
orangered: "#ff4500",
|
|
296
|
+
orchid: "#da70d6",
|
|
297
|
+
palegoldenrod: "#eee8aa",
|
|
298
|
+
palegreen: "#98fb98",
|
|
299
|
+
paleturquoise: "#afeeee",
|
|
300
|
+
palevioletred: "#db7093",
|
|
301
|
+
papayawhip: "#ffefd5",
|
|
302
|
+
peachpuff: "#ffdab9",
|
|
303
|
+
peru: "#cd853f",
|
|
304
|
+
pink: "#ffc0cb",
|
|
305
|
+
plum: "#dda0dd",
|
|
306
|
+
powderblue: "#b0e0e6",
|
|
307
|
+
purple: "#800080",
|
|
308
|
+
rebeccapurple: "#663399",
|
|
309
|
+
red: "#ff0000",
|
|
310
|
+
rosybrown: "#bc8f8f",
|
|
311
|
+
royalblue: "#4169e1",
|
|
312
|
+
saddlebrown: "#8b4513",
|
|
313
|
+
salmon: "#fa8072",
|
|
314
|
+
sandybrown: "#f4a460",
|
|
315
|
+
seagreen: "#2e8b57",
|
|
316
|
+
seashell: "#fff5ee",
|
|
317
|
+
sienna: "#a0522d",
|
|
318
|
+
silver: "#c0c0c0",
|
|
319
|
+
skyblue: "#87ceeb",
|
|
320
|
+
slateblue: "#6a5acd",
|
|
321
|
+
slategray: "#708090",
|
|
322
|
+
slategrey: "#708090",
|
|
323
|
+
snow: "#fffafa",
|
|
324
|
+
springgreen: "#00ff7f",
|
|
325
|
+
steelblue: "#4682b4",
|
|
326
|
+
tan: "#d2b48c",
|
|
327
|
+
teal: "#008080",
|
|
328
|
+
thistle: "#d8bfd8",
|
|
329
|
+
tomato: "#ff6347",
|
|
330
|
+
transparent: "#00000000",
|
|
331
|
+
turquoise: "#40e0d0",
|
|
332
|
+
violet: "#ee82ee",
|
|
333
|
+
wheat: "#f5deb3",
|
|
334
|
+
white: "#ffffff",
|
|
335
|
+
whitesmoke: "#f5f5f5",
|
|
336
|
+
yellow: "#ffff00",
|
|
337
|
+
yellowgreen: "#9acd32"
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// src/color/parse.ts
|
|
341
|
+
var HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
342
|
+
var RGB_RE = /^rgba?\(([^()]*)\)$/i;
|
|
343
|
+
var HSL_RE = /^hsla?\(([^()]*)\)$/i;
|
|
344
|
+
var OKLAB_RE = /^oklab\(([^()]*)\)$/i;
|
|
345
|
+
var OKLCH_RE = /^oklch\(([^()]*)\)$/i;
|
|
346
|
+
var COLOR_FN_RE = /^color\(([^()]*)\)$/i;
|
|
347
|
+
function parsePercentOrNumber(v, scale) {
|
|
348
|
+
const text = v.trim();
|
|
349
|
+
const isPct = text.endsWith("%");
|
|
350
|
+
const num = Number(isPct ? text.slice(0, -1) : text);
|
|
351
|
+
if (!Number.isFinite(num)) return null;
|
|
352
|
+
return isPct ? num / 100 : num / scale;
|
|
353
|
+
}
|
|
354
|
+
function parseAlpha(v) {
|
|
355
|
+
if (v === void 0 || v.trim() === "") return 1;
|
|
356
|
+
const text = v.trim();
|
|
357
|
+
const isPct = text.endsWith("%");
|
|
358
|
+
const num = Number(isPct ? text.slice(0, -1) : text);
|
|
359
|
+
if (!Number.isFinite(num)) return null;
|
|
360
|
+
return isPct ? num / 100 : num;
|
|
361
|
+
}
|
|
362
|
+
function parseAngle(v) {
|
|
363
|
+
const text = v.trim().toLowerCase();
|
|
364
|
+
if (text.endsWith("deg")) {
|
|
365
|
+
const num2 = Number(text.slice(0, -3));
|
|
366
|
+
return Number.isFinite(num2) ? num2 : null;
|
|
367
|
+
}
|
|
368
|
+
if (text.endsWith("turn")) {
|
|
369
|
+
const num2 = Number(text.slice(0, -4));
|
|
370
|
+
return Number.isFinite(num2) ? num2 * 360 : null;
|
|
371
|
+
}
|
|
372
|
+
if (text.endsWith("rad")) {
|
|
373
|
+
const num2 = Number(text.slice(0, -3));
|
|
374
|
+
return Number.isFinite(num2) ? num2 * 180 / Math.PI : null;
|
|
375
|
+
}
|
|
376
|
+
const num = Number(text);
|
|
377
|
+
return Number.isFinite(num) ? num : null;
|
|
378
|
+
}
|
|
379
|
+
function splitParts(inner) {
|
|
380
|
+
const slashIndex = inner.indexOf("/");
|
|
381
|
+
let mainStr = inner;
|
|
382
|
+
let alphaPart;
|
|
383
|
+
if (slashIndex !== -1) {
|
|
384
|
+
mainStr = inner.slice(0, slashIndex);
|
|
385
|
+
alphaPart = inner.slice(slashIndex + 1);
|
|
386
|
+
}
|
|
387
|
+
const parts = mainStr.trim().split(/[\s,]+/).filter(Boolean);
|
|
388
|
+
return { parts, alphaPart };
|
|
389
|
+
}
|
|
390
|
+
function parseColor(input) {
|
|
391
|
+
if (typeof input === "string") {
|
|
392
|
+
const text = input.trim();
|
|
393
|
+
if (text.length === 0 || text.length > 200) return null;
|
|
394
|
+
const lower = text.toLowerCase();
|
|
395
|
+
const namedHex = NAMED_COLORS[lower];
|
|
396
|
+
if (namedHex) {
|
|
397
|
+
return parseHex(namedHex);
|
|
398
|
+
}
|
|
399
|
+
const hexMatch = HEX_RE.exec(text);
|
|
400
|
+
if (hexMatch) {
|
|
401
|
+
return parseHex(hexMatch[0]);
|
|
402
|
+
}
|
|
403
|
+
const rgbMatch = RGB_RE.exec(text);
|
|
404
|
+
if (rgbMatch) {
|
|
405
|
+
const { parts, alphaPart } = splitParts(rgbMatch[1]);
|
|
406
|
+
let alphaVal = 1;
|
|
407
|
+
if (alphaPart !== void 0) {
|
|
408
|
+
alphaVal = parseAlpha(alphaPart);
|
|
409
|
+
} else if (parts.length === 4) {
|
|
410
|
+
alphaVal = parseAlpha(parts.pop());
|
|
411
|
+
}
|
|
412
|
+
if (parts.length !== 3 || alphaVal === null) return null;
|
|
413
|
+
const r = parsePercentOrNumber(parts[0], 255);
|
|
414
|
+
const g = parsePercentOrNumber(parts[1], 255);
|
|
415
|
+
const b = parsePercentOrNumber(parts[2], 255);
|
|
416
|
+
if (r === null || g === null || b === null) return null;
|
|
417
|
+
return { r, g, b, a: alphaVal };
|
|
418
|
+
}
|
|
419
|
+
const hslMatch = HSL_RE.exec(text);
|
|
420
|
+
if (hslMatch) {
|
|
421
|
+
const { parts, alphaPart } = splitParts(hslMatch[1]);
|
|
422
|
+
let alphaVal = 1;
|
|
423
|
+
if (alphaPart !== void 0) {
|
|
424
|
+
alphaVal = parseAlpha(alphaPart);
|
|
425
|
+
} else if (parts.length === 4) {
|
|
426
|
+
alphaVal = parseAlpha(parts.pop());
|
|
427
|
+
}
|
|
428
|
+
if (parts.length !== 3 || alphaVal === null) return null;
|
|
429
|
+
const h = parseAngle(parts[0]);
|
|
430
|
+
const s = parsePercentOrNumber(parts[1], 100);
|
|
431
|
+
const l = parsePercentOrNumber(parts[2], 100);
|
|
432
|
+
if (h === null || s === null || l === null) return null;
|
|
433
|
+
return hslToRgb({ h, s, l, alpha: alphaVal });
|
|
434
|
+
}
|
|
435
|
+
const oklabMatch = OKLAB_RE.exec(text);
|
|
436
|
+
if (oklabMatch) {
|
|
437
|
+
const { parts, alphaPart } = splitParts(oklabMatch[1]);
|
|
438
|
+
let alphaVal = 1;
|
|
439
|
+
if (alphaPart !== void 0) {
|
|
440
|
+
alphaVal = parseAlpha(alphaPart);
|
|
441
|
+
} else if (parts.length === 4) {
|
|
442
|
+
alphaVal = parseAlpha(parts.pop());
|
|
443
|
+
}
|
|
444
|
+
if (parts.length !== 3 || alphaVal === null) return null;
|
|
445
|
+
const l = parsePercentOrNumber(parts[0], 1);
|
|
446
|
+
const a = parsePercentOrNumber(parts[1], 1);
|
|
447
|
+
const b = parsePercentOrNumber(parts[2], 1);
|
|
448
|
+
if (l === null || a === null || b === null) return null;
|
|
449
|
+
return oklabToRgb({ l, a, b, alpha: alphaVal });
|
|
450
|
+
}
|
|
451
|
+
const oklchMatch = OKLCH_RE.exec(text);
|
|
452
|
+
if (oklchMatch) {
|
|
453
|
+
const { parts, alphaPart } = splitParts(oklchMatch[1]);
|
|
454
|
+
let alphaVal = 1;
|
|
455
|
+
if (alphaPart !== void 0) {
|
|
456
|
+
alphaVal = parseAlpha(alphaPart);
|
|
457
|
+
} else if (parts.length === 4) {
|
|
458
|
+
alphaVal = parseAlpha(parts.pop());
|
|
459
|
+
}
|
|
460
|
+
if (parts.length !== 3 || alphaVal === null) return null;
|
|
461
|
+
const l = parsePercentOrNumber(parts[0], 1);
|
|
462
|
+
const c = parsePercentOrNumber(parts[1], 1);
|
|
463
|
+
const h = parseAngle(parts[2]);
|
|
464
|
+
if (l === null || c === null || h === null) return null;
|
|
465
|
+
return oklabToRgb(oklchToOklab({ l, c, h, alpha: alphaVal }));
|
|
466
|
+
}
|
|
467
|
+
const colorMatch = COLOR_FN_RE.exec(text);
|
|
468
|
+
if (colorMatch) {
|
|
469
|
+
const { parts, alphaPart } = splitParts(colorMatch[1]);
|
|
470
|
+
if (parts.length < 4) return null;
|
|
471
|
+
const space = parts[0].toLowerCase();
|
|
472
|
+
let alphaVal = 1;
|
|
473
|
+
if (alphaPart !== void 0) {
|
|
474
|
+
alphaVal = parseAlpha(alphaPart);
|
|
475
|
+
} else if (parts.length === 5) {
|
|
476
|
+
alphaVal = parseAlpha(parts.pop());
|
|
477
|
+
}
|
|
478
|
+
if (parts.length !== 4 || alphaVal === null) return null;
|
|
479
|
+
const c1 = parsePercentOrNumber(parts[1], 1);
|
|
480
|
+
const c2 = parsePercentOrNumber(parts[2], 1);
|
|
481
|
+
const c3 = parsePercentOrNumber(parts[3], 1);
|
|
482
|
+
if (c1 === null || c2 === null || c3 === null) return null;
|
|
483
|
+
if (space === "srgb") {
|
|
484
|
+
return { r: c1, g: c2, b: c3, a: alphaVal };
|
|
485
|
+
}
|
|
486
|
+
if (space === "srgb-linear") {
|
|
487
|
+
return {
|
|
488
|
+
r: linearToSrgb(c1),
|
|
489
|
+
g: linearToSrgb(c2),
|
|
490
|
+
b: linearToSrgb(c3),
|
|
491
|
+
a: alphaVal
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (space === "display-p3") {
|
|
495
|
+
const linP3R = srgbToLinear(c1);
|
|
496
|
+
const linP3G = srgbToLinear(c2);
|
|
497
|
+
const linP3B = srgbToLinear(c3);
|
|
498
|
+
const linSrgb = displayP3ToLinearSrgb({ r: linP3R, g: linP3G, b: linP3B, a: alphaVal });
|
|
499
|
+
return {
|
|
500
|
+
r: linearToSrgb(linSrgb.r),
|
|
501
|
+
g: linearToSrgb(linSrgb.g),
|
|
502
|
+
b: linearToSrgb(linSrgb.b),
|
|
503
|
+
a: alphaVal
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
if (typeof input === "object" && input !== null) {
|
|
511
|
+
const obj = input;
|
|
512
|
+
if (Array.isArray(obj.components)) {
|
|
513
|
+
const space = obj.colorSpace;
|
|
514
|
+
if (space === void 0 || space === "srgb") {
|
|
515
|
+
const [r2, g2, b2] = obj.components;
|
|
516
|
+
const a2 = typeof obj.alpha === "number" && Number.isFinite(obj.alpha) ? obj.alpha : 1;
|
|
517
|
+
if ([r2, g2, b2].every((v) => typeof v === "number" && Number.isFinite(v))) {
|
|
518
|
+
return { r: r2, g: g2, b: b2, a: a2 };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (typeof obj.hex === "string") {
|
|
522
|
+
const base = parseColor(obj.hex);
|
|
523
|
+
if (!base) return null;
|
|
524
|
+
const a2 = typeof obj.alpha === "number" && Number.isFinite(obj.alpha) ? obj.alpha : 1;
|
|
525
|
+
return { r: base.r, g: base.g, b: base.b, a: a2 };
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
const { r, g, b } = obj;
|
|
530
|
+
const a = obj.a === void 0 ? 1 : obj.a;
|
|
531
|
+
if ([r, g, b, a].every((v) => typeof v === "number" && Number.isFinite(v))) {
|
|
532
|
+
return { r, g, b, a };
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
function parseHex(hex) {
|
|
538
|
+
let raw = hex.trim();
|
|
539
|
+
if (raw.startsWith("#")) raw = raw.slice(1);
|
|
540
|
+
if (raw.length === 3 || raw.length === 4) {
|
|
541
|
+
raw = [...raw].map((c) => c + c).join("");
|
|
542
|
+
}
|
|
543
|
+
if (raw.length !== 6 && raw.length !== 8) return null;
|
|
544
|
+
const r = parseInt(raw.slice(0, 2), 16) / 255;
|
|
545
|
+
const g = parseInt(raw.slice(2, 4), 16) / 255;
|
|
546
|
+
const b = parseInt(raw.slice(4, 6), 16) / 255;
|
|
547
|
+
const a = raw.length === 8 ? parseInt(raw.slice(6, 8), 16) / 255 : 1;
|
|
548
|
+
return { r, g, b, a };
|
|
549
|
+
}
|
|
550
|
+
function isColor(input) {
|
|
551
|
+
return parseColor(input) !== null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/color/gamut.ts
|
|
555
|
+
var clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
556
|
+
function inSrgbGamut(c, epsilon = 1e-5) {
|
|
557
|
+
return c.r >= -epsilon && c.r <= 1 + epsilon && c.g >= -epsilon && c.g <= 1 + epsilon && c.b >= -epsilon && c.b <= 1 + epsilon;
|
|
558
|
+
}
|
|
559
|
+
function deltaEOK(a, b) {
|
|
560
|
+
const la = rgbToOklab(a);
|
|
561
|
+
const lb = rgbToOklab(b);
|
|
562
|
+
return Math.hypot(la.l - lb.l, la.a - lb.a, la.b - lb.b);
|
|
563
|
+
}
|
|
564
|
+
function toSrgbGamut(c) {
|
|
565
|
+
const lch = oklabToOklch(rgbToOklab(c));
|
|
566
|
+
if (lch.l >= 1) return { r: 1, g: 1, b: 1, a: c.a };
|
|
567
|
+
if (lch.l <= 0) return { r: 0, g: 0, b: 0, a: c.a };
|
|
568
|
+
if (inSrgbGamut(c)) {
|
|
569
|
+
return { r: clamp01(c.r), g: clamp01(c.g), b: clamp01(c.b), a: c.a };
|
|
570
|
+
}
|
|
571
|
+
let low = 0;
|
|
572
|
+
let high = lch.c;
|
|
573
|
+
let clippedCandidate = { r: clamp01(c.r), g: clamp01(c.g), b: clamp01(c.b), a: c.a };
|
|
574
|
+
const JND = 0.02;
|
|
575
|
+
const EPS = 1e-4;
|
|
576
|
+
for (let i = 0; i < 30; i++) {
|
|
577
|
+
const mid = (low + high) / 2;
|
|
578
|
+
const candidate = oklabToRgb(oklchToOklab({ l: lch.l, c: mid, h: lch.h, alpha: c.a }));
|
|
579
|
+
const clipped = {
|
|
580
|
+
r: clamp01(candidate.r),
|
|
581
|
+
g: clamp01(candidate.g),
|
|
582
|
+
b: clamp01(candidate.b),
|
|
583
|
+
a: c.a
|
|
584
|
+
};
|
|
585
|
+
clippedCandidate = clipped;
|
|
586
|
+
const dE = deltaEOK(candidate, clipped);
|
|
587
|
+
if (dE < JND) {
|
|
588
|
+
if (JND - dE < EPS) {
|
|
589
|
+
return clipped;
|
|
590
|
+
}
|
|
591
|
+
low = mid;
|
|
592
|
+
} else {
|
|
593
|
+
high = mid;
|
|
594
|
+
}
|
|
595
|
+
if (high - low < 1e-4) {
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return clippedCandidate;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/color/format.ts
|
|
603
|
+
var clamp012 = (v) => Math.min(1, Math.max(0, v));
|
|
604
|
+
var byte = (v) => Math.round(clamp012(v) * 255);
|
|
605
|
+
var hex2 = (n) => n.toString(16).padStart(2, "0");
|
|
606
|
+
var round2 = (n) => (Math.round(n * 100) / 100).toString();
|
|
607
|
+
function formatHex(c) {
|
|
608
|
+
const mapped = toSrgbGamut(c);
|
|
609
|
+
const head = `#${hex2(byte(mapped.r))}${hex2(byte(mapped.g))}${hex2(byte(mapped.b))}`;
|
|
610
|
+
const alpha = byte(mapped.a);
|
|
611
|
+
return alpha === 255 ? head : head + hex2(alpha);
|
|
612
|
+
}
|
|
613
|
+
function formatColor(c, format) {
|
|
614
|
+
switch (format) {
|
|
615
|
+
case "hex":
|
|
616
|
+
return formatHex(c);
|
|
617
|
+
case "rgb": {
|
|
618
|
+
const inGamut = toSrgbGamut(c);
|
|
619
|
+
const r = byte(inGamut.r);
|
|
620
|
+
const g = byte(inGamut.g);
|
|
621
|
+
const b = byte(inGamut.b);
|
|
622
|
+
return c.a < 1 ? `rgb(${r} ${g} ${b} / ${round2(c.a)})` : `rgb(${r} ${g} ${b})`;
|
|
623
|
+
}
|
|
624
|
+
case "hsl": {
|
|
625
|
+
const hsl = rgbToHsl(toSrgbGamut(c));
|
|
626
|
+
const h = (Math.round(hsl.h * 100) / 100).toString();
|
|
627
|
+
const s = (Math.round(hsl.s * 1e4) / 100).toString();
|
|
628
|
+
const l = (Math.round(hsl.l * 1e4) / 100).toString();
|
|
629
|
+
return c.a < 1 ? `hsl(${h} ${s}% ${l}% / ${round2(c.a)})` : `hsl(${h} ${s}% ${l}%)`;
|
|
630
|
+
}
|
|
631
|
+
case "oklch": {
|
|
632
|
+
const lch = oklabToOklch(rgbToOklab(c));
|
|
633
|
+
const l = (lch.l * 100).toFixed(2);
|
|
634
|
+
const ch = lch.c.toFixed(4);
|
|
635
|
+
const h = lch.h.toFixed(2);
|
|
636
|
+
return c.a < 1 ? `oklch(${l}% ${ch} ${h} / ${round2(c.a)})` : `oklch(${l}% ${ch} ${h})`;
|
|
637
|
+
}
|
|
638
|
+
case "oklab": {
|
|
639
|
+
const lab = rgbToOklab(c);
|
|
640
|
+
const l = (lab.l * 100).toFixed(2);
|
|
641
|
+
const a = lab.a.toFixed(4);
|
|
642
|
+
const b = lab.b.toFixed(4);
|
|
643
|
+
return c.a < 1 ? `oklab(${l}% ${a} ${b} / ${round2(c.a)})` : `oklab(${l}% ${a} ${b})`;
|
|
644
|
+
}
|
|
645
|
+
case "p3": {
|
|
646
|
+
const linSrgb = {
|
|
647
|
+
r: srgbToLinear(c.r),
|
|
648
|
+
g: srgbToLinear(c.g),
|
|
649
|
+
b: srgbToLinear(c.b),
|
|
650
|
+
a: c.a
|
|
651
|
+
};
|
|
652
|
+
const linP3 = linearSrgbToDisplayP3(linSrgb);
|
|
653
|
+
const r = linearToSrgb(linP3.r).toFixed(4);
|
|
654
|
+
const g = linearToSrgb(linP3.g).toFixed(4);
|
|
655
|
+
const b = linearToSrgb(linP3.b).toFixed(4);
|
|
656
|
+
return c.a < 1 ? `color(display-p3 ${r} ${g} ${b} / ${round2(c.a)})` : `color(display-p3 ${r} ${g} ${b})`;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function normalizeColor(input) {
|
|
661
|
+
const parsed = parseColor(input);
|
|
662
|
+
return parsed ? formatHex(parsed) : null;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/color/contrast.ts
|
|
666
|
+
var BLACK = { r: 0, g: 0, b: 0, a: 1 };
|
|
667
|
+
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
668
|
+
function composite(fg, bg) {
|
|
669
|
+
const a = fg.a + bg.a * (1 - fg.a);
|
|
670
|
+
if (a === 0) return { r: 0, g: 0, b: 0, a: 0 };
|
|
671
|
+
return {
|
|
672
|
+
r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / a,
|
|
673
|
+
g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / a,
|
|
674
|
+
b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / a,
|
|
675
|
+
a
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function relativeLuminance(c) {
|
|
679
|
+
const r = srgbToLinear(c.r);
|
|
680
|
+
const g = srgbToLinear(c.g);
|
|
681
|
+
const b = srgbToLinear(c.b);
|
|
682
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
683
|
+
}
|
|
684
|
+
function contrastRatio(foreground, background) {
|
|
685
|
+
let bg = background;
|
|
686
|
+
if (bg.a < 1) {
|
|
687
|
+
bg = composite(bg, WHITE);
|
|
688
|
+
}
|
|
689
|
+
let fg = foreground;
|
|
690
|
+
if (fg.a < 1) {
|
|
691
|
+
fg = composite(fg, bg);
|
|
692
|
+
}
|
|
693
|
+
const lum1 = relativeLuminance(fg);
|
|
694
|
+
const lum2 = relativeLuminance(bg);
|
|
695
|
+
const brighter = Math.max(lum1, lum2);
|
|
696
|
+
const darker = Math.min(lum1, lum2);
|
|
697
|
+
return (brighter + 0.05) / (darker + 0.05);
|
|
698
|
+
}
|
|
699
|
+
function contrastLevel(ratio, options) {
|
|
700
|
+
const large = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _2 => _2.large]), () => ( false));
|
|
701
|
+
if (large) {
|
|
702
|
+
if (ratio >= 4.5) return "AAA";
|
|
703
|
+
if (ratio >= 3) return "AA";
|
|
704
|
+
return "fail";
|
|
705
|
+
}
|
|
706
|
+
if (ratio >= 7) return "AAA";
|
|
707
|
+
if (ratio >= 4.5) return "AA";
|
|
708
|
+
return "fail";
|
|
709
|
+
}
|
|
710
|
+
function readableTextColor(background, candidates = [BLACK, WHITE]) {
|
|
711
|
+
let bestCandidate = _nullishCoalesce(candidates[0], () => ( BLACK));
|
|
712
|
+
let bestRatio = -1;
|
|
713
|
+
for (const candidate of candidates) {
|
|
714
|
+
const ratio = contrastRatio(candidate, background);
|
|
715
|
+
if (ratio > bestRatio) {
|
|
716
|
+
bestRatio = ratio;
|
|
717
|
+
bestCandidate = candidate;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return bestCandidate;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/color/vision.ts
|
|
724
|
+
var CVD = {
|
|
725
|
+
protanopia: [
|
|
726
|
+
[0.152286, 1.052583, -0.204868],
|
|
727
|
+
[0.114503, 0.786281, 0.099216],
|
|
728
|
+
[-3882e-6, -0.048116, 1.051998]
|
|
729
|
+
],
|
|
730
|
+
deuteranopia: [
|
|
731
|
+
[0.367322, 0.860646, -0.227968],
|
|
732
|
+
[0.280085, 0.672501, 0.047413],
|
|
733
|
+
[-0.01182, 0.04294, 0.968881]
|
|
734
|
+
],
|
|
735
|
+
tritanopia: [
|
|
736
|
+
[1.255528, -0.076749, -0.178779],
|
|
737
|
+
[-0.078411, 0.930809, 0.147602],
|
|
738
|
+
[4733e-6, 0.691367, 0.3039]
|
|
739
|
+
]
|
|
740
|
+
};
|
|
741
|
+
var clamp013 = (v) => Math.max(0, Math.min(1, v));
|
|
742
|
+
function simulateColorVision(c, vision) {
|
|
743
|
+
if (vision === "normal") return c;
|
|
744
|
+
const lr = srgbToLinear(c.r);
|
|
745
|
+
const lg = srgbToLinear(c.g);
|
|
746
|
+
const lb = srgbToLinear(c.b);
|
|
747
|
+
const m = CVD[vision];
|
|
748
|
+
const r = linearToSrgb(clamp013(m[0][0] * lr + m[0][1] * lg + m[0][2] * lb));
|
|
749
|
+
const g = linearToSrgb(clamp013(m[1][0] * lr + m[1][1] * lg + m[1][2] * lb));
|
|
750
|
+
const b = linearToSrgb(clamp013(m[2][0] * lr + m[2][1] * lg + m[2][2] * lb));
|
|
751
|
+
return { r, g, b, a: c.a };
|
|
752
|
+
}
|
|
753
|
+
function checkPaletteColors(colors, options = {}) {
|
|
754
|
+
const surfaceStr = _nullishCoalesce(options.surface, () => ( "#ffffff"));
|
|
755
|
+
const minDistance = _nullishCoalesce(options.minDistance, () => ( 0.08));
|
|
756
|
+
const minContrast = _nullishCoalesce(options.minContrast, () => ( 1.5));
|
|
757
|
+
const visions = _nullishCoalesce(options.visions, () => ( ["normal", "protanopia", "deuteranopia", "tritanopia"]));
|
|
758
|
+
const parsed = colors.map((c) => parseColor(c));
|
|
759
|
+
const surfaceParsed = parseColor(surfaceStr);
|
|
760
|
+
const skipped = colors.filter((_, i) => !parsed[i]);
|
|
761
|
+
const issues = [];
|
|
762
|
+
const closest = {};
|
|
763
|
+
for (const vision of visions) {
|
|
764
|
+
const seen = parsed.map((c) => c ? simulateColorVision(c, vision) : null);
|
|
765
|
+
let best;
|
|
766
|
+
for (let a = 0; a < seen.length; a++) {
|
|
767
|
+
for (let b = a + 1; b < seen.length; b++) {
|
|
768
|
+
if (!seen[a] || !seen[b]) continue;
|
|
769
|
+
const distance = deltaEOK(seen[a], seen[b]);
|
|
770
|
+
if (!best || distance < best.distance) {
|
|
771
|
+
best = {
|
|
772
|
+
a,
|
|
773
|
+
b,
|
|
774
|
+
colors: [colors[a], colors[b]],
|
|
775
|
+
distance: Math.round(distance * 1e3) / 1e3
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (best) {
|
|
781
|
+
closest[vision] = best;
|
|
782
|
+
if (best.distance < minDistance) {
|
|
783
|
+
const who = vision === "normal" ? "" : ` with ${vision}`;
|
|
784
|
+
issues.push(
|
|
785
|
+
`Colours ${best.a + 1} (${best.colors[0]}) and ${best.b + 1} (${best.colors[1]}) are hard to tell apart${who} (distance ${best.distance}, want at least ${minDistance}).`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const contrast = colors.map((_, i) => {
|
|
791
|
+
const c = parsed[i];
|
|
792
|
+
if (!c || !surfaceParsed) return null;
|
|
793
|
+
const ratio = contrastRatio(c, surfaceParsed);
|
|
794
|
+
return Math.round(ratio * 100) / 100;
|
|
795
|
+
});
|
|
796
|
+
contrast.forEach((ratio, i) => {
|
|
797
|
+
if (ratio !== null && ratio < minContrast) {
|
|
798
|
+
issues.push(
|
|
799
|
+
`Colour ${i + 1} (${colors[i]}) barely stands out from the background ${surfaceStr} (contrast ${ratio}:1, want at least ${minContrast}:1).`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
return { ok: issues.length === 0, closest, contrast, issues, skipped };
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/color/harmony.ts
|
|
807
|
+
var HARMONY_KINDS = [
|
|
808
|
+
"complementary",
|
|
809
|
+
"split-complementary",
|
|
810
|
+
"analogous",
|
|
811
|
+
"triadic",
|
|
812
|
+
"tetradic",
|
|
813
|
+
"square",
|
|
814
|
+
"monochromatic"
|
|
815
|
+
];
|
|
816
|
+
var HEX_6 = /^#?([0-9a-f]{6})$/i;
|
|
817
|
+
function parseHex2(value, what = "color") {
|
|
818
|
+
const m = typeof value === "string" ? HEX_6.exec(value.trim()) : null;
|
|
819
|
+
if (!m) throw new Error(`${what} is ${JSON.stringify(value)}; use #rrggbb, e.g. "#2a4b8d".`);
|
|
820
|
+
const n = parseInt(m[1], 16);
|
|
821
|
+
return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
|
|
822
|
+
}
|
|
823
|
+
function toHex(c) {
|
|
824
|
+
return "#" + [c.r, c.g, c.b].map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("");
|
|
825
|
+
}
|
|
826
|
+
var RYB_STOPS = [
|
|
827
|
+
[0, 0],
|
|
828
|
+
[60, 30],
|
|
829
|
+
[120, 60],
|
|
830
|
+
[180, 120],
|
|
831
|
+
[240, 240],
|
|
832
|
+
[300, 280],
|
|
833
|
+
[360, 360]
|
|
834
|
+
];
|
|
835
|
+
function piecewise(v, from) {
|
|
836
|
+
const x = (v % 360 + 360) % 360;
|
|
837
|
+
const to = from === 0 ? 1 : 0;
|
|
838
|
+
for (let i = 1; i < RYB_STOPS.length; i++) {
|
|
839
|
+
const a = RYB_STOPS[i - 1];
|
|
840
|
+
const b = RYB_STOPS[i];
|
|
841
|
+
if (x <= b[from]) return a[to] + (x - a[from]) / (b[from] - a[from]) * (b[to] - a[to]);
|
|
842
|
+
}
|
|
843
|
+
return x;
|
|
844
|
+
}
|
|
845
|
+
function wheelToHue(angle, wheel) {
|
|
846
|
+
return wheel === "rgb" ? (angle % 360 + 360) % 360 : piecewise(angle, 0);
|
|
847
|
+
}
|
|
848
|
+
function hueToWheel(hue, wheel) {
|
|
849
|
+
return wheel === "rgb" ? (hue % 360 + 360) % 360 : piecewise(hue, 1);
|
|
850
|
+
}
|
|
851
|
+
function colorHarmony(base, kind, options = {}) {
|
|
852
|
+
const wheel = _nullishCoalesce(options.wheel, () => ( "ryb"));
|
|
853
|
+
const rgb = parseHex2(base, "base");
|
|
854
|
+
const hsv = rgbToHsv(rgb);
|
|
855
|
+
const start = hueToWheel(hsv.h, wheel);
|
|
856
|
+
const at = (angle) => {
|
|
857
|
+
const a = (angle % 360 + 360) % 360;
|
|
858
|
+
return { color: toHex(hsvToRgb({ ...hsv, h: wheelToHue(a, wheel) })), angle: Math.round(a * 10) / 10 };
|
|
859
|
+
};
|
|
860
|
+
const spread = _nullishCoalesce(options.spread, () => ( 30));
|
|
861
|
+
const offsets = {
|
|
862
|
+
complementary: [0, 180],
|
|
863
|
+
"split-complementary": [0, 180 - spread, 180 + spread],
|
|
864
|
+
analogous: [0, -spread, spread],
|
|
865
|
+
triadic: [0, 120, 240],
|
|
866
|
+
tetradic: [0, 60, 180, 240],
|
|
867
|
+
square: [0, 90, 180, 270]
|
|
868
|
+
};
|
|
869
|
+
if (kind === "monochromatic") {
|
|
870
|
+
const hsl = rgbToHsl(rgb);
|
|
871
|
+
const steps = [0, -0.3, -0.15, 0.15, 0.3];
|
|
872
|
+
return steps.map((d) => {
|
|
873
|
+
const c = d === 0 ? rgb : hslToRgb({ ...hsl, l: Math.min(0.97, Math.max(0.03, hsl.l + d)) });
|
|
874
|
+
return { color: toHex(c), angle: Math.round(start * 10) / 10 };
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
return offsets[kind].map(
|
|
878
|
+
(o, i) => i === 0 ? { color: toHex(rgb), angle: Math.round(start * 10) / 10 } : at(start + o)
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/color/mix.ts
|
|
883
|
+
function mixColors(from, to, t, options = {}) {
|
|
884
|
+
const c1 = parseColor(from);
|
|
885
|
+
const c2 = parseColor(to);
|
|
886
|
+
if (!c1 || !c2) return null;
|
|
887
|
+
const space = _nullishCoalesce(options.space, () => ( "oklab"));
|
|
888
|
+
const alpha = c1.a + (c2.a - c1.a) * t;
|
|
889
|
+
if (space === "oklab") {
|
|
890
|
+
const lab1 = rgbToOklab(c1);
|
|
891
|
+
const lab2 = rgbToOklab(c2);
|
|
892
|
+
if (alpha <= 1e-9) {
|
|
893
|
+
const lab3 = { l: lab1.l + (lab2.l - lab1.l) * t, a: 0, b: 0, alpha: 0 };
|
|
894
|
+
return formatHex(oklabToRgb(lab3));
|
|
895
|
+
}
|
|
896
|
+
const premul = (k) => lab1[k] * lab1.alpha + (lab2[k] * lab2.alpha - lab1[k] * lab1.alpha) * t;
|
|
897
|
+
const lab = {
|
|
898
|
+
l: premul("l") / alpha,
|
|
899
|
+
a: premul("a") / alpha,
|
|
900
|
+
b: premul("b") / alpha,
|
|
901
|
+
alpha
|
|
902
|
+
};
|
|
903
|
+
return formatHex(oklabToRgb(lab));
|
|
904
|
+
}
|
|
905
|
+
if (space === "oklch") {
|
|
906
|
+
const lch1 = oklabToOklch(rgbToOklab(c1));
|
|
907
|
+
const lch2 = oklabToOklch(rgbToOklab(c2));
|
|
908
|
+
let h1 = lch1.h;
|
|
909
|
+
let h2 = lch2.h;
|
|
910
|
+
const diff = h2 - h1;
|
|
911
|
+
if (diff > 180) h2 -= 360;
|
|
912
|
+
else if (diff < -180) h2 += 360;
|
|
913
|
+
const h = ((h1 + (h2 - h1) * t) % 360 + 360) % 360;
|
|
914
|
+
if (alpha <= 1e-9) {
|
|
915
|
+
const l2 = lch1.l + (lch2.l - lch1.l) * t;
|
|
916
|
+
return formatHex(oklabToRgb(oklchToOklab({ l: l2, c: 0, h: 0, alpha: 0 })));
|
|
917
|
+
}
|
|
918
|
+
const lPremul = lch1.l * lch1.alpha + (lch2.l * lch2.alpha - lch1.l * lch1.alpha) * t;
|
|
919
|
+
const cPremul = lch1.c * lch1.alpha + (lch2.c * lch2.alpha - lch1.c * lch1.alpha) * t;
|
|
920
|
+
const l = lPremul / alpha;
|
|
921
|
+
const c = cPremul / alpha;
|
|
922
|
+
return formatHex(oklabToRgb(oklchToOklab({ l, c, h, alpha })));
|
|
923
|
+
}
|
|
924
|
+
if (space === "srgb-linear") {
|
|
925
|
+
const lr1 = srgbToLinear(c1.r);
|
|
926
|
+
const lg1 = srgbToLinear(c1.g);
|
|
927
|
+
const lb1 = srgbToLinear(c1.b);
|
|
928
|
+
const lr2 = srgbToLinear(c2.r);
|
|
929
|
+
const lg2 = srgbToLinear(c2.g);
|
|
930
|
+
const lb2 = srgbToLinear(c2.b);
|
|
931
|
+
if (alpha <= 1e-9) {
|
|
932
|
+
return formatHex({
|
|
933
|
+
r: linearToSrgb(lr1 + (lr2 - lr1) * t),
|
|
934
|
+
g: linearToSrgb(lg1 + (lg2 - lg1) * t),
|
|
935
|
+
b: linearToSrgb(lb1 + (lb2 - lb1) * t),
|
|
936
|
+
a: 0
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
const premulR2 = lr1 * c1.a + (lr2 * c2.a - lr1 * c1.a) * t;
|
|
940
|
+
const premulG2 = lg1 * c1.a + (lg2 * c2.a - lg1 * c1.a) * t;
|
|
941
|
+
const premulB2 = lb1 * c1.a + (lb2 * c2.a - lb1 * c1.a) * t;
|
|
942
|
+
return formatHex({
|
|
943
|
+
r: linearToSrgb(premulR2 / alpha),
|
|
944
|
+
g: linearToSrgb(premulG2 / alpha),
|
|
945
|
+
b: linearToSrgb(premulB2 / alpha),
|
|
946
|
+
a: alpha
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
if (alpha <= 1e-9) {
|
|
950
|
+
return formatHex({
|
|
951
|
+
r: c1.r + (c2.r - c1.r) * t,
|
|
952
|
+
g: c1.g + (c2.g - c1.g) * t,
|
|
953
|
+
b: c1.b + (c2.b - c1.b) * t,
|
|
954
|
+
a: 0
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
const premulR = c1.r * c1.a + (c2.r * c2.a - c1.r * c1.a) * t;
|
|
958
|
+
const premulG = c1.g * c1.a + (c2.g * c2.a - c1.g * c1.a) * t;
|
|
959
|
+
const premulB = c1.b * c1.a + (c2.b * c2.a - c1.b * c1.a) * t;
|
|
960
|
+
return formatHex({
|
|
961
|
+
r: premulR / alpha,
|
|
962
|
+
g: premulG / alpha,
|
|
963
|
+
b: premulB / alpha,
|
|
964
|
+
a: alpha
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/color/recent.ts
|
|
969
|
+
function pushRecentColor(list, color, max = 12) {
|
|
970
|
+
const normal = normalizeColor(color);
|
|
971
|
+
if (!normal) return list.slice();
|
|
972
|
+
const next = [normal, ...list.filter((c) => normalizeColor(c) !== normal)];
|
|
973
|
+
return next.slice(0, max);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/color/dual.ts
|
|
977
|
+
var DEFAULT_SCHEME_SURFACES = {
|
|
978
|
+
light: "#ffffff",
|
|
979
|
+
dark: "#0b0b0f"
|
|
980
|
+
};
|
|
981
|
+
var clamp = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
982
|
+
function isDualColor(value) {
|
|
983
|
+
return typeof value === "object" && value !== null && typeof value.light === "string" && typeof value.dark === "string";
|
|
984
|
+
}
|
|
985
|
+
function parseDualColor(input) {
|
|
986
|
+
if (input === null || input === void 0) return null;
|
|
987
|
+
if (isDualColor(input)) {
|
|
988
|
+
if (parseColor(input.light) === null || parseColor(input.dark) === null) {
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
return { light: input.light, dark: input.dark };
|
|
992
|
+
}
|
|
993
|
+
if (typeof input === "string") {
|
|
994
|
+
const trimmed = input.trim();
|
|
995
|
+
if (trimmed.toLowerCase().startsWith("light-dark(") && trimmed.endsWith(")")) {
|
|
996
|
+
const inner = trimmed.slice("light-dark(".length, -1).trim();
|
|
997
|
+
if (inner.toLowerCase().includes("light-dark(")) {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
let depth = 0;
|
|
1001
|
+
let commaIndex = -1;
|
|
1002
|
+
for (let i = 0; i < inner.length; i++) {
|
|
1003
|
+
const char = inner[i];
|
|
1004
|
+
if (char === "(") {
|
|
1005
|
+
depth++;
|
|
1006
|
+
} else if (char === ")") {
|
|
1007
|
+
depth--;
|
|
1008
|
+
if (depth < 0) return null;
|
|
1009
|
+
} else if (char === "," && depth === 0) {
|
|
1010
|
+
if (commaIndex !== -1) {
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
commaIndex = i;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (depth !== 0 || commaIndex === -1) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const lightPart = inner.slice(0, commaIndex).trim();
|
|
1020
|
+
const darkPart = inner.slice(commaIndex + 1).trim();
|
|
1021
|
+
if (!lightPart || !darkPart) return null;
|
|
1022
|
+
if (parseColor(lightPart) === null || parseColor(darkPart) === null) {
|
|
1023
|
+
return null;
|
|
1024
|
+
}
|
|
1025
|
+
return { light: lightPart, dark: darkPart };
|
|
1026
|
+
}
|
|
1027
|
+
if (parseColor(trimmed) !== null) {
|
|
1028
|
+
return { light: trimmed, dark: trimmed };
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
function normalizeDualColor(input) {
|
|
1034
|
+
const parsed = parseDualColor(input);
|
|
1035
|
+
if (!parsed) return null;
|
|
1036
|
+
const light = normalizeColor(parsed.light);
|
|
1037
|
+
const dark = normalizeColor(parsed.dark);
|
|
1038
|
+
if (!light || !dark) return null;
|
|
1039
|
+
return { light, dark };
|
|
1040
|
+
}
|
|
1041
|
+
function resolveColor(value, scheme) {
|
|
1042
|
+
const normalized = normalizeDualColor(value);
|
|
1043
|
+
if (!normalized) return null;
|
|
1044
|
+
return scheme === "dark" ? normalized.dark : normalized.light;
|
|
1045
|
+
}
|
|
1046
|
+
function formatDualColor(value) {
|
|
1047
|
+
const light = _nullishCoalesce(normalizeColor(value.light), () => ( value.light));
|
|
1048
|
+
const dark = _nullishCoalesce(normalizeColor(value.dark), () => ( value.dark));
|
|
1049
|
+
if (light === dark) {
|
|
1050
|
+
return light;
|
|
1051
|
+
}
|
|
1052
|
+
return `light-dark(${light}, ${dark})`;
|
|
1053
|
+
}
|
|
1054
|
+
function isSameInBothSchemes(value) {
|
|
1055
|
+
const light = normalizeColor(value.light);
|
|
1056
|
+
const dark = normalizeColor(value.dark);
|
|
1057
|
+
if (!light || !dark) return value.light === value.dark;
|
|
1058
|
+
return light === dark;
|
|
1059
|
+
}
|
|
1060
|
+
function setDualColorSide(value, scheme, color) {
|
|
1061
|
+
const base = _nullishCoalesce(normalizeDualColor(value), () => ( { light: "#000000", dark: "#000000" }));
|
|
1062
|
+
const normColor = _nullishCoalesce(normalizeColor(color), () => ( color));
|
|
1063
|
+
return {
|
|
1064
|
+
light: scheme === "light" ? normColor : base.light,
|
|
1065
|
+
dark: scheme === "dark" ? normColor : base.dark
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
function deriveDualColor(color, options) {
|
|
1069
|
+
const fromScheme = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _3 => _3.from]), () => ( "light"));
|
|
1070
|
+
const baseRgba = parseColor(color);
|
|
1071
|
+
if (!baseRgba) {
|
|
1072
|
+
throw new Error(`"${String(color)}" is not a colour; pass hex, rgb(), hsl(), oklch() or a CSS colour name.`);
|
|
1073
|
+
}
|
|
1074
|
+
if (_optionalChain([options, 'optionalAccess', _4 => _4.surfaces]) && !normalizeDualColor(options.surfaces)) {
|
|
1075
|
+
throw new Error('The surfaces need a readable light and dark colour, e.g. { light: "#ffffff", dark: "#0b0b0f" }.');
|
|
1076
|
+
}
|
|
1077
|
+
const baseHex = formatHex(baseRgba);
|
|
1078
|
+
const lab = rgbToOklab(baseRgba);
|
|
1079
|
+
const lch = oklabToOklch(lab);
|
|
1080
|
+
const startL = clamp(1.05 - lch.l, 0.12, 0.95);
|
|
1081
|
+
const targetC = lch.c * 0.9;
|
|
1082
|
+
const targetH = lch.h;
|
|
1083
|
+
let derivedHex;
|
|
1084
|
+
if (_optionalChain([options, 'optionalAccess', _5 => _5.surfaces])) {
|
|
1085
|
+
const surfaces = options.surfaces;
|
|
1086
|
+
const fromSurfaceRgba = parseColor(surfaces[fromScheme]);
|
|
1087
|
+
const targetScheme = fromScheme === "light" ? "dark" : "light";
|
|
1088
|
+
const targetSurfaceRgba = parseColor(surfaces[targetScheme]);
|
|
1089
|
+
const targetRatio = contrastRatio(baseRgba, fromSurfaceRgba);
|
|
1090
|
+
const targetSurfaceL = oklabToOklch(rgbToOklab(targetSurfaceRgba)).l;
|
|
1091
|
+
const preferHigherL = targetSurfaceL < 0.5;
|
|
1092
|
+
let bestDiff = Infinity;
|
|
1093
|
+
let bestL = startL;
|
|
1094
|
+
for (let candL = 0.05; candL <= 0.98; candL += 5e-3) {
|
|
1095
|
+
const candRgb = toSrgbGamut(
|
|
1096
|
+
oklabToRgb(oklchToOklab({ l: candL, c: targetC, h: targetH, alpha: baseRgba.a }))
|
|
1097
|
+
);
|
|
1098
|
+
const ratio = contrastRatio(candRgb, targetSurfaceRgba);
|
|
1099
|
+
const diff = Math.abs(ratio - targetRatio);
|
|
1100
|
+
const isBetter = diff < bestDiff - 1e-4 || Math.abs(diff - bestDiff) <= 1e-4 && (preferHigherL ? candL > bestL : candL < bestL);
|
|
1101
|
+
if (isBetter) {
|
|
1102
|
+
bestDiff = diff;
|
|
1103
|
+
bestL = candL;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
const finalRgba = toSrgbGamut(
|
|
1107
|
+
oklabToRgb(oklchToOklab({ l: bestL, c: targetC, h: targetH, alpha: baseRgba.a }))
|
|
1108
|
+
);
|
|
1109
|
+
derivedHex = formatHex(finalRgba);
|
|
1110
|
+
} else {
|
|
1111
|
+
const finalRgba = toSrgbGamut(
|
|
1112
|
+
oklabToRgb(oklchToOklab({ l: startL, c: targetC, h: targetH, alpha: baseRgba.a }))
|
|
1113
|
+
);
|
|
1114
|
+
derivedHex = formatHex(finalRgba);
|
|
1115
|
+
}
|
|
1116
|
+
return fromScheme === "light" ? { light: baseHex, dark: derivedHex } : { light: derivedHex, dark: baseHex };
|
|
1117
|
+
}
|
|
1118
|
+
function checkDualColorContrast(foreground, background, options) {
|
|
1119
|
+
const large = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _6 => _6.large]), () => ( false));
|
|
1120
|
+
const requiredRatio = large ? 3 : 4.5;
|
|
1121
|
+
const fg = normalizeDualColor(foreground);
|
|
1122
|
+
const bg = normalizeDualColor(background);
|
|
1123
|
+
if (!fg) throw new Error(`The foreground "${describeValue(foreground)}" is not a colour, light-dark() or { light, dark }.`);
|
|
1124
|
+
if (!bg) throw new Error(`The background "${describeValue(background)}" is not a colour, light-dark() or { light, dark }.`);
|
|
1125
|
+
const lightFgHex = fg.light;
|
|
1126
|
+
const lightBgHex = bg.light;
|
|
1127
|
+
const darkFgHex = fg.dark;
|
|
1128
|
+
const darkBgHex = bg.dark;
|
|
1129
|
+
const lightFg = parseColor(lightFgHex);
|
|
1130
|
+
const lightBg = parseColor(lightBgHex);
|
|
1131
|
+
const darkFg = parseColor(darkFgHex);
|
|
1132
|
+
const darkBg = parseColor(darkBgHex);
|
|
1133
|
+
const lightRatio = Math.round(contrastRatio(lightFg, lightBg) * 100) / 100;
|
|
1134
|
+
const darkRatio = Math.round(contrastRatio(darkFg, darkBg) * 100) / 100;
|
|
1135
|
+
const lightLevel = contrastLevel(lightRatio, { large });
|
|
1136
|
+
const darkLevel = contrastLevel(darkRatio, { large });
|
|
1137
|
+
const issues = [];
|
|
1138
|
+
if (lightLevel === "fail") {
|
|
1139
|
+
issues.push(
|
|
1140
|
+
`In light mode, ${lightFgHex} on ${lightBgHex} has a contrast of ${lightRatio.toFixed(1)} (needs ${requiredRatio} for AA).`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (darkLevel === "fail") {
|
|
1144
|
+
issues.push(
|
|
1145
|
+
`In dark mode, ${darkFgHex} on ${darkBgHex} has a contrast of ${darkRatio.toFixed(1)} (needs ${requiredRatio} for AA).`
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
light: { ratio: lightRatio, level: lightLevel },
|
|
1150
|
+
dark: { ratio: darkRatio, level: darkLevel },
|
|
1151
|
+
issues
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
var CSS_VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
1155
|
+
function describeValue(value) {
|
|
1156
|
+
return typeof value === "string" ? value : _nullishCoalesce(JSON.stringify(value), () => ( String(value)));
|
|
1157
|
+
}
|
|
1158
|
+
function dualColorsToCss(colors, options) {
|
|
1159
|
+
const selector = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _7 => _7.selector]), () => ( ":root"));
|
|
1160
|
+
const strategy = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _8 => _8.strategy]), () => ( "light-dark"));
|
|
1161
|
+
const darkSelector = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _9 => _9.darkSelector]), () => ( ".dark"));
|
|
1162
|
+
for (const [label, sel] of [["selector", selector], ["darkSelector", darkSelector]]) {
|
|
1163
|
+
if (!sel.trim() || /[{};]/.test(sel)) {
|
|
1164
|
+
throw new Error(`The ${label} "${sel}" is not a CSS selector (it can't be empty or contain "{", "}" or ";").`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
const normalized = {};
|
|
1168
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
1169
|
+
if (!CSS_VARIABLE_NAME.test(key)) {
|
|
1170
|
+
throw new Error(`"${key}" is not a CSS variable name; use letters, digits, "-" and "_" without the leading "--" (e.g. "brand-color").`);
|
|
1171
|
+
}
|
|
1172
|
+
const dual = normalizeDualColor(value);
|
|
1173
|
+
if (!dual) throw new Error(`"${key}" has "${describeValue(value)}", which is not a colour, light-dark() or { light, dark }.`);
|
|
1174
|
+
normalized[key] = dual;
|
|
1175
|
+
}
|
|
1176
|
+
const sortedKeys = Object.keys(normalized).sort();
|
|
1177
|
+
switch (strategy) {
|
|
1178
|
+
case "light-dark": {
|
|
1179
|
+
const lines = [
|
|
1180
|
+
`${selector} {`,
|
|
1181
|
+
...selector === ":root" ? [" color-scheme: light dark;"] : [],
|
|
1182
|
+
...sortedKeys.map((k) => {
|
|
1183
|
+
return ` --${k}: ${formatDualColor(normalized[k])};`;
|
|
1184
|
+
}),
|
|
1185
|
+
"}"
|
|
1186
|
+
];
|
|
1187
|
+
return lines.join("\n");
|
|
1188
|
+
}
|
|
1189
|
+
case "media": {
|
|
1190
|
+
const lightLines = [
|
|
1191
|
+
`${selector} {`,
|
|
1192
|
+
...sortedKeys.map((k) => ` --${k}: ${normalized[k].light};`),
|
|
1193
|
+
"}"
|
|
1194
|
+
];
|
|
1195
|
+
const darkLines = [
|
|
1196
|
+
`@media (prefers-color-scheme: dark) {`,
|
|
1197
|
+
` ${selector} {`,
|
|
1198
|
+
...sortedKeys.map((k) => ` --${k}: ${normalized[k].dark};`),
|
|
1199
|
+
` }`,
|
|
1200
|
+
`}`
|
|
1201
|
+
];
|
|
1202
|
+
return `${lightLines.join("\n")}
|
|
1203
|
+
|
|
1204
|
+
${darkLines.join("\n")}`;
|
|
1205
|
+
}
|
|
1206
|
+
case "class": {
|
|
1207
|
+
const lightLines = [
|
|
1208
|
+
`${selector} {`,
|
|
1209
|
+
...sortedKeys.map((k) => ` --${k}: ${normalized[k].light};`),
|
|
1210
|
+
"}"
|
|
1211
|
+
];
|
|
1212
|
+
const darkLines = [
|
|
1213
|
+
`${darkSelector} {`,
|
|
1214
|
+
...sortedKeys.map((k) => ` --${k}: ${normalized[k].dark};`),
|
|
1215
|
+
"}"
|
|
1216
|
+
];
|
|
1217
|
+
return `${lightLines.join("\n")}
|
|
1218
|
+
|
|
1219
|
+
${darkLines.join("\n")}`;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// src/color/tools.ts
|
|
1225
|
+
var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
1226
|
+
var fail = (message) => {
|
|
1227
|
+
throw new Error(message);
|
|
1228
|
+
};
|
|
1229
|
+
var COLOR_PARAM_DESC = "Any CSS colour: hex, rgb(), hsl(), oklch(), oklab(), color(display-p3 \u2026) or a name.";
|
|
1230
|
+
var DUAL_COLOR_PARAM_DESC = "Any CSS colour, light-dark(#light, #dark) string, or { light, dark } object.";
|
|
1231
|
+
var DUAL_COLOR_SCHEMA = {
|
|
1232
|
+
oneOf: [
|
|
1233
|
+
{ type: "string", description: "Plain CSS colour or light-dark() syntax." },
|
|
1234
|
+
{
|
|
1235
|
+
type: "object",
|
|
1236
|
+
properties: {
|
|
1237
|
+
light: { type: "string", description: "CSS colour for light scheme." },
|
|
1238
|
+
dark: { type: "string", description: "CSS colour for dark scheme." }
|
|
1239
|
+
},
|
|
1240
|
+
required: ["light", "dark"],
|
|
1241
|
+
additionalProperties: false
|
|
1242
|
+
}
|
|
1243
|
+
],
|
|
1244
|
+
description: DUAL_COLOR_PARAM_DESC
|
|
1245
|
+
};
|
|
1246
|
+
function requireColor(input, name) {
|
|
1247
|
+
if (typeof input === "string" && input.trim().toLowerCase().startsWith("light-dark(")) {
|
|
1248
|
+
fail("This is a dual colour (light-dark); use check_dual_color_contrast, or pass one half.");
|
|
1249
|
+
}
|
|
1250
|
+
if (typeof input !== "string" || !input.trim()) {
|
|
1251
|
+
fail(`"${String(input)}" is not a colour I can read; use hex (#2a4b8d), rgb(), hsl(), oklch() or a CSS colour name.`);
|
|
1252
|
+
}
|
|
1253
|
+
const parsed = parseColor(input);
|
|
1254
|
+
if (!parsed) {
|
|
1255
|
+
fail(`"${input}" is not a colour I can read; use hex (#2a4b8d), rgb(), hsl(), oklch() or a CSS colour name.`);
|
|
1256
|
+
}
|
|
1257
|
+
return parsed;
|
|
1258
|
+
}
|
|
1259
|
+
function requireDualColor(input, name) {
|
|
1260
|
+
const parsed = normalizeDualColor(input);
|
|
1261
|
+
if (!parsed) {
|
|
1262
|
+
fail(`"${String(input)}" is not a dual colour I can read; use a CSS colour, light-dark(), or { light, dark }.`);
|
|
1263
|
+
}
|
|
1264
|
+
return parsed;
|
|
1265
|
+
}
|
|
1266
|
+
function findAccessibleLightness(fg, bg, large) {
|
|
1267
|
+
const lab = rgbToOklab(fg);
|
|
1268
|
+
const lch = oklabToOklch(lab);
|
|
1269
|
+
for (let d = 0.01; d <= 1; d += 0.01) {
|
|
1270
|
+
const downL = Math.max(0, lch.l - d);
|
|
1271
|
+
const upL = Math.min(1, lch.l + d);
|
|
1272
|
+
const downCand = toSrgbGamut(oklabToRgb(oklchToOklab({ ...lch, l: downL })));
|
|
1273
|
+
const upCand = toSrgbGamut(oklabToRgb(oklchToOklab({ ...lch, l: upL })));
|
|
1274
|
+
const downRatio = contrastRatio(downCand, bg);
|
|
1275
|
+
const upRatio = contrastRatio(upCand, bg);
|
|
1276
|
+
const downPass = contrastLevel(downRatio, { large }) !== "fail";
|
|
1277
|
+
const upPass = contrastLevel(upRatio, { large }) !== "fail";
|
|
1278
|
+
if (downPass && upPass) {
|
|
1279
|
+
return formatHex(downRatio >= upRatio ? downCand : upCand);
|
|
1280
|
+
} else if (downPass) {
|
|
1281
|
+
return formatHex(downCand);
|
|
1282
|
+
} else if (upPass) {
|
|
1283
|
+
return formatHex(upCand);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
return void 0;
|
|
1287
|
+
}
|
|
1288
|
+
function createColorToolProvider(options = {}) {
|
|
1289
|
+
const id = _nullishCoalesce(options.id, () => ( "color"));
|
|
1290
|
+
const namespace = _nullishCoalesce(options.namespace, () => ( "color"));
|
|
1291
|
+
const inspectColor = {
|
|
1292
|
+
name: "inspect_color",
|
|
1293
|
+
title: "Inspect colour",
|
|
1294
|
+
description: "Inspects a colour string across multiple colour spaces and formats, reporting hex, sRGB channels, HSL, OKLCH, wide-gamut status, gamut-mapped fallback, and relative luminance.",
|
|
1295
|
+
inputSchema: {
|
|
1296
|
+
type: "object",
|
|
1297
|
+
properties: {
|
|
1298
|
+
color: { type: "string", description: COLOR_PARAM_DESC }
|
|
1299
|
+
},
|
|
1300
|
+
required: ["color"],
|
|
1301
|
+
additionalProperties: false
|
|
1302
|
+
},
|
|
1303
|
+
annotations: { title: "Inspect colour", ...READ },
|
|
1304
|
+
handler: (args) => {
|
|
1305
|
+
const { color } = args;
|
|
1306
|
+
const rgba = requireColor(color, "color");
|
|
1307
|
+
const inGamut = inSrgbGamut(rgba);
|
|
1308
|
+
const mappedRgba = inGamut ? rgba : toSrgbGamut(rgba);
|
|
1309
|
+
const hsl = rgbToHsl(mappedRgba);
|
|
1310
|
+
const oklab = rgbToOklab(rgba);
|
|
1311
|
+
const oklch = oklabToOklch(oklab);
|
|
1312
|
+
const formats = ["hex", "rgb", "hsl", "oklab", "oklch"];
|
|
1313
|
+
const css = {};
|
|
1314
|
+
for (const f of formats) {
|
|
1315
|
+
css[f] = formatColor(rgba, f);
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
hex: formatHex(mappedRgba),
|
|
1319
|
+
alpha: Math.round(rgba.a * 1e3) / 1e3,
|
|
1320
|
+
rgb: {
|
|
1321
|
+
r: Math.round(Math.min(1, Math.max(0, mappedRgba.r)) * 255),
|
|
1322
|
+
g: Math.round(Math.min(1, Math.max(0, mappedRgba.g)) * 255),
|
|
1323
|
+
b: Math.round(Math.min(1, Math.max(0, mappedRgba.b)) * 255)
|
|
1324
|
+
},
|
|
1325
|
+
hsl: {
|
|
1326
|
+
h: Math.round(hsl.h * 10) / 10,
|
|
1327
|
+
s: Math.round(hsl.s * 1e3) / 1e3,
|
|
1328
|
+
l: Math.round(hsl.l * 1e3) / 1e3
|
|
1329
|
+
},
|
|
1330
|
+
oklch: {
|
|
1331
|
+
l: Math.round(oklch.l * 1e3) / 1e3,
|
|
1332
|
+
c: Math.round(oklch.c * 1e3) / 1e3,
|
|
1333
|
+
h: Math.round(oklch.h * 10) / 10
|
|
1334
|
+
},
|
|
1335
|
+
css,
|
|
1336
|
+
inSrgbGamut: inGamut,
|
|
1337
|
+
...inGamut ? {} : { gamutMappedHex: formatHex(mappedRgba) },
|
|
1338
|
+
relativeLuminance: Math.round(relativeLuminance(mappedRgba) * 1e4) / 1e4
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
const checkColorContrast = {
|
|
1343
|
+
name: "check_color_contrast",
|
|
1344
|
+
title: "Check colour contrast",
|
|
1345
|
+
description: "Calculates WCAG 2 contrast ratio between foreground and background colours, checks AA and AAA thresholds, and suggests an adjusted foreground when contrast fails.",
|
|
1346
|
+
inputSchema: {
|
|
1347
|
+
type: "object",
|
|
1348
|
+
properties: {
|
|
1349
|
+
foreground: { type: "string", description: `Foreground text colour. ${COLOR_PARAM_DESC}` },
|
|
1350
|
+
background: { type: "string", description: `Background surface colour. ${COLOR_PARAM_DESC}` },
|
|
1351
|
+
large: { type: "boolean", description: "Whether the text is large (at least 18pt / 24px regular, or 14pt / 18.5px bold). Default false." }
|
|
1352
|
+
},
|
|
1353
|
+
required: ["foreground", "background"],
|
|
1354
|
+
additionalProperties: false
|
|
1355
|
+
},
|
|
1356
|
+
annotations: { title: "Check colour contrast", ...READ },
|
|
1357
|
+
handler: (args) => {
|
|
1358
|
+
const { foreground, background, large = false } = args;
|
|
1359
|
+
const fg = requireColor(foreground, "foreground");
|
|
1360
|
+
const bg = requireColor(background, "background");
|
|
1361
|
+
const ratio = Math.round(contrastRatio(fg, bg) * 100) / 100;
|
|
1362
|
+
const level = contrastLevel(ratio, { large });
|
|
1363
|
+
const passesAA = level === "AA" || level === "AAA";
|
|
1364
|
+
const passesAAA = level === "AAA";
|
|
1365
|
+
let suggestion;
|
|
1366
|
+
if (!passesAA) {
|
|
1367
|
+
const lab = rgbToOklab(fg);
|
|
1368
|
+
const lch = oklabToOklch(lab);
|
|
1369
|
+
for (let d = 0.01; d <= 1; d += 0.01) {
|
|
1370
|
+
const downL = Math.max(0, lch.l - d);
|
|
1371
|
+
const upL = Math.min(1, lch.l + d);
|
|
1372
|
+
const downCand = toSrgbGamut(oklabToRgb(oklchToOklab({ ...lch, l: downL })));
|
|
1373
|
+
const upCand = toSrgbGamut(oklabToRgb(oklchToOklab({ ...lch, l: upL })));
|
|
1374
|
+
const downRatio = contrastRatio(downCand, bg);
|
|
1375
|
+
const upRatio = contrastRatio(upCand, bg);
|
|
1376
|
+
const downPass = contrastLevel(downRatio, { large }) !== "fail";
|
|
1377
|
+
const upPass = contrastLevel(upRatio, { large }) !== "fail";
|
|
1378
|
+
if (downPass && upPass) {
|
|
1379
|
+
suggestion = formatHex(downRatio >= upRatio ? downCand : upCand);
|
|
1380
|
+
break;
|
|
1381
|
+
} else if (downPass) {
|
|
1382
|
+
suggestion = formatHex(downCand);
|
|
1383
|
+
break;
|
|
1384
|
+
} else if (upPass) {
|
|
1385
|
+
suggestion = formatHex(upCand);
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
ratio,
|
|
1392
|
+
level,
|
|
1393
|
+
passesAA,
|
|
1394
|
+
passesAAA,
|
|
1395
|
+
...suggestion ? { suggestion } : {}
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
const checkColorPalette = {
|
|
1400
|
+
name: "check_color_palette",
|
|
1401
|
+
title: "Check colour palette",
|
|
1402
|
+
description: "Audits a palette of colours for mutual distinctness under normal vision and colour-vision deficiencies (protanopia, deuteranopia, tritanopia), and checks contrast against a surface.",
|
|
1403
|
+
inputSchema: {
|
|
1404
|
+
type: "object",
|
|
1405
|
+
properties: {
|
|
1406
|
+
colors: {
|
|
1407
|
+
type: "array",
|
|
1408
|
+
items: { type: "string" },
|
|
1409
|
+
minItems: 2,
|
|
1410
|
+
description: "List of at least two palette colours to check."
|
|
1411
|
+
},
|
|
1412
|
+
surface: { type: "string", description: `Background / surface colour (default '#ffffff'). ${COLOR_PARAM_DESC}` },
|
|
1413
|
+
minDistance: { type: "number", description: "Minimum perceptual OKLab distance between series colours (default 0.08)." },
|
|
1414
|
+
minContrast: { type: "number", description: "Minimum contrast ratio against the surface (default 1.5)." },
|
|
1415
|
+
visions: {
|
|
1416
|
+
type: "array",
|
|
1417
|
+
items: { type: "string", enum: ["normal", "protanopia", "deuteranopia", "tritanopia"] },
|
|
1418
|
+
description: "Colour-vision conditions to simulate (defaults to all)."
|
|
1419
|
+
}
|
|
1420
|
+
},
|
|
1421
|
+
required: ["colors"],
|
|
1422
|
+
additionalProperties: false
|
|
1423
|
+
},
|
|
1424
|
+
annotations: { title: "Check colour palette", ...READ },
|
|
1425
|
+
handler: (args) => {
|
|
1426
|
+
const { colors, surface, minDistance, minContrast, visions } = args;
|
|
1427
|
+
if (!Array.isArray(colors) || colors.length < 2) {
|
|
1428
|
+
fail("Palette check requires an array of at least 2 colours.");
|
|
1429
|
+
}
|
|
1430
|
+
for (const c of colors) {
|
|
1431
|
+
requireColor(c, "colors item");
|
|
1432
|
+
}
|
|
1433
|
+
if (surface !== void 0) {
|
|
1434
|
+
requireColor(surface, "surface");
|
|
1435
|
+
}
|
|
1436
|
+
return checkPaletteColors(colors, {
|
|
1437
|
+
surface,
|
|
1438
|
+
minDistance,
|
|
1439
|
+
minContrast,
|
|
1440
|
+
visions
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
const suggestColorHarmony = {
|
|
1445
|
+
name: "suggest_color_harmony",
|
|
1446
|
+
title: "Suggest colour harmony",
|
|
1447
|
+
description: "Generates harmonic colour combinations (complementary, split-complementary, analogous, triadic, tetradic, square, monochromatic) on an RGB or artistic RYB colour wheel.",
|
|
1448
|
+
inputSchema: {
|
|
1449
|
+
type: "object",
|
|
1450
|
+
properties: {
|
|
1451
|
+
base: { type: "string", description: `Base colour. ${COLOR_PARAM_DESC}` },
|
|
1452
|
+
kind: {
|
|
1453
|
+
type: "string",
|
|
1454
|
+
enum: [...HARMONY_KINDS],
|
|
1455
|
+
description: "Harmony arrangement kind."
|
|
1456
|
+
},
|
|
1457
|
+
wheel: {
|
|
1458
|
+
type: "string",
|
|
1459
|
+
enum: ["rgb", "ryb"],
|
|
1460
|
+
description: 'Colour wheel model: "rgb" (default) or "ryb" (artistic / painterly).'
|
|
1461
|
+
},
|
|
1462
|
+
spread: { type: "number", description: "Angular spread in degrees for analogous or split-complementary harmonies (default 30)." }
|
|
1463
|
+
},
|
|
1464
|
+
required: ["base", "kind"],
|
|
1465
|
+
additionalProperties: false
|
|
1466
|
+
},
|
|
1467
|
+
annotations: { title: "Suggest colour harmony", ...READ },
|
|
1468
|
+
handler: (args) => {
|
|
1469
|
+
const { base, kind, wheel = "rgb", spread } = args;
|
|
1470
|
+
const rgba = requireColor(base, "base");
|
|
1471
|
+
const hex = formatHex(toSrgbGamut(rgba));
|
|
1472
|
+
if (!HARMONY_KINDS.includes(kind)) {
|
|
1473
|
+
fail(`Unknown harmony kind "${kind}"; expected one of ${HARMONY_KINDS.join(", ")}.`);
|
|
1474
|
+
}
|
|
1475
|
+
const result = colorHarmony(hex, kind, {
|
|
1476
|
+
wheel,
|
|
1477
|
+
spread
|
|
1478
|
+
});
|
|
1479
|
+
return {
|
|
1480
|
+
colors: result.map((c) => ({ hex: c.color, angle: c.angle }))
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
const blendColors = {
|
|
1485
|
+
name: "blend_colors",
|
|
1486
|
+
title: "Blend colours",
|
|
1487
|
+
description: "Interpolates between two colours in OKLab, OKLCH, sRGB, or sRGB-linear colour space, either at a single factor t (0\u20131) or as a multi-step gradient ramp.",
|
|
1488
|
+
inputSchema: {
|
|
1489
|
+
type: "object",
|
|
1490
|
+
properties: {
|
|
1491
|
+
from: { type: "string", description: `Start colour. ${COLOR_PARAM_DESC}` },
|
|
1492
|
+
to: { type: "string", description: `End colour. ${COLOR_PARAM_DESC}` },
|
|
1493
|
+
t: { type: "number", description: "Blend position between 0 and 1 (required unless steps is provided)." },
|
|
1494
|
+
space: {
|
|
1495
|
+
type: "string",
|
|
1496
|
+
enum: ["oklab", "oklch", "srgb", "srgb-linear"],
|
|
1497
|
+
description: 'Colour space for interpolation (default "oklab").'
|
|
1498
|
+
},
|
|
1499
|
+
steps: {
|
|
1500
|
+
type: "integer",
|
|
1501
|
+
minimum: 2,
|
|
1502
|
+
maximum: 32,
|
|
1503
|
+
description: "When provided, returns this number of evenly spaced colours (2\u201332) from start to end, ignoring t."
|
|
1504
|
+
}
|
|
1505
|
+
},
|
|
1506
|
+
required: ["from", "to"],
|
|
1507
|
+
additionalProperties: false
|
|
1508
|
+
},
|
|
1509
|
+
annotations: { title: "Blend colours", ...READ },
|
|
1510
|
+
handler: (args) => {
|
|
1511
|
+
const { from, to, t, space = "oklab", steps } = args;
|
|
1512
|
+
requireColor(from, "from");
|
|
1513
|
+
requireColor(to, "to");
|
|
1514
|
+
const validSpaces = ["oklab", "oklch", "srgb", "srgb-linear"];
|
|
1515
|
+
if (!validSpaces.includes(space)) {
|
|
1516
|
+
fail(`Unknown space "${space}"; expected one of ${validSpaces.join(", ")}.`);
|
|
1517
|
+
}
|
|
1518
|
+
if (steps !== void 0) {
|
|
1519
|
+
if (!Number.isInteger(steps) || steps < 2 || steps > 32) {
|
|
1520
|
+
fail(`"steps" must be an integer between 2 and 32, got ${steps}.`);
|
|
1521
|
+
}
|
|
1522
|
+
const colors = [];
|
|
1523
|
+
for (let i = 0; i < steps; i++) {
|
|
1524
|
+
const factor = i / (steps - 1);
|
|
1525
|
+
const mixed = mixColors(from, to, factor, { space });
|
|
1526
|
+
if (mixed) colors.push(mixed);
|
|
1527
|
+
}
|
|
1528
|
+
return { colors };
|
|
1529
|
+
}
|
|
1530
|
+
if (t === void 0 || typeof t !== "number" || !Number.isFinite(t) || t < 0 || t > 1) {
|
|
1531
|
+
fail(`"t" must be a number between 0 and 1, got ${t}.`);
|
|
1532
|
+
}
|
|
1533
|
+
return {
|
|
1534
|
+
color: mixColors(from, to, t, { space })
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
const deriveDualColorTool = {
|
|
1539
|
+
name: "derive_dual_color",
|
|
1540
|
+
title: "Derive dual colour",
|
|
1541
|
+
description: "Automatically derives the opposing scheme colour (light or dark) from a base colour, optionally preserving contrast against specified background surfaces.",
|
|
1542
|
+
inputSchema: {
|
|
1543
|
+
type: "object",
|
|
1544
|
+
properties: {
|
|
1545
|
+
color: DUAL_COLOR_SCHEMA,
|
|
1546
|
+
from: {
|
|
1547
|
+
type: "string",
|
|
1548
|
+
enum: ["light", "dark"],
|
|
1549
|
+
description: "Which scheme the base colour belongs to ('light' or 'dark'). Default 'light'."
|
|
1550
|
+
},
|
|
1551
|
+
surfaces: {
|
|
1552
|
+
...DUAL_COLOR_SCHEMA,
|
|
1553
|
+
description: "Background surfaces for light and dark schemes. Default is { light: '#ffffff', dark: '#0b0b0f' }."
|
|
1554
|
+
},
|
|
1555
|
+
keepContrast: {
|
|
1556
|
+
type: "boolean",
|
|
1557
|
+
description: "Whether to preserve contrast against the scheme surface. Default true."
|
|
1558
|
+
}
|
|
1559
|
+
},
|
|
1560
|
+
required: ["color"],
|
|
1561
|
+
additionalProperties: false
|
|
1562
|
+
},
|
|
1563
|
+
annotations: { title: "Derive dual colour", ...READ },
|
|
1564
|
+
handler: (args) => {
|
|
1565
|
+
const { color, from = "light", surfaces, keepContrast = true } = args;
|
|
1566
|
+
if (from !== "light" && from !== "dark") {
|
|
1567
|
+
fail(`"from" must be 'light' or 'dark', got "${from}".`);
|
|
1568
|
+
}
|
|
1569
|
+
const baseDual = requireDualColor(color, "color");
|
|
1570
|
+
const baseHex = from === "dark" ? baseDual.dark : baseDual.light;
|
|
1571
|
+
const resolvedSurfaces = surfaces ? requireDualColor(surfaces, "surfaces") : DEFAULT_SCHEME_SURFACES;
|
|
1572
|
+
const derived = deriveDualColor(baseHex, {
|
|
1573
|
+
from,
|
|
1574
|
+
surfaces: keepContrast ? resolvedSurfaces : void 0
|
|
1575
|
+
});
|
|
1576
|
+
const lightSurfaceRgba = parseColor(resolvedSurfaces.light);
|
|
1577
|
+
const darkSurfaceRgba = parseColor(resolvedSurfaces.dark);
|
|
1578
|
+
const lightRatio = Math.round(contrastRatio(parseColor(derived.light), lightSurfaceRgba) * 100) / 100;
|
|
1579
|
+
const darkRatio = Math.round(contrastRatio(parseColor(derived.dark), darkSurfaceRgba) * 100) / 100;
|
|
1580
|
+
return {
|
|
1581
|
+
light: derived.light,
|
|
1582
|
+
dark: derived.dark,
|
|
1583
|
+
css: formatDualColor(derived),
|
|
1584
|
+
contrast: {
|
|
1585
|
+
light: lightRatio,
|
|
1586
|
+
dark: darkRatio
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
const checkDualColorContrastTool = {
|
|
1592
|
+
name: "check_dual_color_contrast",
|
|
1593
|
+
title: "Check dual colour contrast",
|
|
1594
|
+
description: "Checks WCAG contrast compliance for a foreground and background dual colour across both light and dark modes, reporting issues and suggesting lightness fixes.",
|
|
1595
|
+
inputSchema: {
|
|
1596
|
+
type: "object",
|
|
1597
|
+
properties: {
|
|
1598
|
+
foreground: {
|
|
1599
|
+
...DUAL_COLOR_SCHEMA,
|
|
1600
|
+
description: `Foreground text colour. ${DUAL_COLOR_PARAM_DESC}`
|
|
1601
|
+
},
|
|
1602
|
+
background: {
|
|
1603
|
+
...DUAL_COLOR_SCHEMA,
|
|
1604
|
+
description: `Background surface colour. ${DUAL_COLOR_PARAM_DESC}`
|
|
1605
|
+
},
|
|
1606
|
+
large: {
|
|
1607
|
+
type: "boolean",
|
|
1608
|
+
description: "Whether the text is large (at least 18pt / 24px regular, or 14pt / 18.5px bold). Default false."
|
|
1609
|
+
}
|
|
1610
|
+
},
|
|
1611
|
+
required: ["foreground", "background"],
|
|
1612
|
+
additionalProperties: false
|
|
1613
|
+
},
|
|
1614
|
+
annotations: { title: "Check dual colour contrast", ...READ },
|
|
1615
|
+
handler: (args) => {
|
|
1616
|
+
const { foreground, background, large = false } = args;
|
|
1617
|
+
const fg = requireDualColor(foreground, "foreground");
|
|
1618
|
+
const bg = requireDualColor(background, "background");
|
|
1619
|
+
const res = checkDualColorContrast(fg, bg, { large });
|
|
1620
|
+
let suggestion;
|
|
1621
|
+
if (res.light.level === "fail") {
|
|
1622
|
+
const lightFg = parseColor(fg.light);
|
|
1623
|
+
const lightBg = parseColor(bg.light);
|
|
1624
|
+
const fixed = findAccessibleLightness(lightFg, lightBg, large);
|
|
1625
|
+
if (fixed) {
|
|
1626
|
+
suggestion = { ..._nullishCoalesce(suggestion, () => ( {})), light: fixed };
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
if (res.dark.level === "fail") {
|
|
1630
|
+
const darkFg = parseColor(fg.dark);
|
|
1631
|
+
const darkBg = parseColor(bg.dark);
|
|
1632
|
+
const fixed = findAccessibleLightness(darkFg, darkBg, large);
|
|
1633
|
+
if (fixed) {
|
|
1634
|
+
suggestion = { ..._nullishCoalesce(suggestion, () => ( {})), dark: fixed };
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return {
|
|
1638
|
+
...res,
|
|
1639
|
+
...suggestion ? { suggestion } : {}
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
const exportDualColorsCssTool = {
|
|
1644
|
+
name: "export_dual_colors_css",
|
|
1645
|
+
title: "Export dual colours CSS",
|
|
1646
|
+
description: "Generates CSS custom property declarations for a collection of dual colours using light-dark(), media query, or class-based dark mode strategies.",
|
|
1647
|
+
inputSchema: {
|
|
1648
|
+
type: "object",
|
|
1649
|
+
properties: {
|
|
1650
|
+
colors: {
|
|
1651
|
+
type: "object",
|
|
1652
|
+
minProperties: 1,
|
|
1653
|
+
additionalProperties: DUAL_COLOR_SCHEMA,
|
|
1654
|
+
description: "Dictionary of variable names (without --) to colour values (plain colour, light-dark() string, or { light, dark } object)."
|
|
1655
|
+
},
|
|
1656
|
+
selector: {
|
|
1657
|
+
type: "string",
|
|
1658
|
+
description: "CSS selector for the ruleset. Default ':root'."
|
|
1659
|
+
},
|
|
1660
|
+
strategy: {
|
|
1661
|
+
type: "string",
|
|
1662
|
+
enum: ["light-dark", "media", "class"],
|
|
1663
|
+
description: "CSS export strategy ('light-dark', 'media', or 'class'). Default 'light-dark'."
|
|
1664
|
+
},
|
|
1665
|
+
darkSelector: {
|
|
1666
|
+
type: "string",
|
|
1667
|
+
description: "Dark mode CSS selector when using 'class' strategy. Default '.dark'."
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
required: ["colors"],
|
|
1671
|
+
additionalProperties: false
|
|
1672
|
+
},
|
|
1673
|
+
annotations: { title: "Export dual colours CSS", ...READ },
|
|
1674
|
+
handler: (args) => {
|
|
1675
|
+
const { colors, selector = ":root", strategy = "light-dark", darkSelector = ".dark" } = args;
|
|
1676
|
+
if (!colors || typeof colors !== "object" || Object.keys(colors).length === 0) {
|
|
1677
|
+
fail('"colors" must be an object with at least one colour property.');
|
|
1678
|
+
}
|
|
1679
|
+
for (const [k, v] of Object.entries(colors)) {
|
|
1680
|
+
if (!normalizeDualColor(v)) {
|
|
1681
|
+
fail(`Property "${k}" contains invalid colour "${String(v)}".`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
const css = dualColorsToCss(colors, { selector, strategy, darkSelector });
|
|
1685
|
+
return { css };
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
return {
|
|
1689
|
+
id,
|
|
1690
|
+
namespace,
|
|
1691
|
+
title: "Colour",
|
|
1692
|
+
description: "Inspect colours, check contrast and accessibility, audit palettes, suggest harmonies, and interpolate colour blends.",
|
|
1693
|
+
skill: "pixerate-color",
|
|
1694
|
+
tools: [
|
|
1695
|
+
inspectColor,
|
|
1696
|
+
checkColorContrast,
|
|
1697
|
+
checkColorPalette,
|
|
1698
|
+
suggestColorHarmony,
|
|
1699
|
+
blendColors,
|
|
1700
|
+
deriveDualColorTool,
|
|
1701
|
+
checkDualColorContrastTool,
|
|
1702
|
+
exportDualColorsCssTool
|
|
1703
|
+
]
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// src/color/picker.ts
|
|
1708
|
+
var clamp2 = (val, min, max) => Math.max(min, Math.min(max, val));
|
|
1709
|
+
function pickerStateFromColor(input, previous) {
|
|
1710
|
+
if (input === null || input === void 0) return null;
|
|
1711
|
+
let rgba;
|
|
1712
|
+
if (typeof input === "object" && "r" in input && "g" in input && "b" in input) {
|
|
1713
|
+
rgba = input;
|
|
1714
|
+
} else if (typeof input === "string") {
|
|
1715
|
+
rgba = parseColor(input);
|
|
1716
|
+
} else {
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
if (!rgba) return null;
|
|
1720
|
+
const clampedRgba = toSrgbGamut(rgba);
|
|
1721
|
+
const hsva = rgbToHsv(clampedRgba);
|
|
1722
|
+
let h = hsva.h;
|
|
1723
|
+
let s = hsva.s;
|
|
1724
|
+
const v = hsva.v;
|
|
1725
|
+
const alpha = _nullishCoalesce(rgba.a, () => ( 1));
|
|
1726
|
+
if (previous) {
|
|
1727
|
+
if (v <= 1e-4) {
|
|
1728
|
+
h = previous.h;
|
|
1729
|
+
s = previous.s;
|
|
1730
|
+
} else if (s <= 1e-4) {
|
|
1731
|
+
h = previous.h;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return {
|
|
1735
|
+
h: clamp2(h, 0, 360) % 360,
|
|
1736
|
+
s: clamp2(s, 0, 1),
|
|
1737
|
+
v: clamp2(v, 0, 1),
|
|
1738
|
+
alpha: clamp2(alpha, 0, 1)
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
function pickerStateToHex(state, forceAlpha = false) {
|
|
1742
|
+
const rgba = hsvToRgb({
|
|
1743
|
+
h: state.h,
|
|
1744
|
+
s: state.s,
|
|
1745
|
+
v: state.v,
|
|
1746
|
+
alpha: state.alpha
|
|
1747
|
+
});
|
|
1748
|
+
const hex = formatHex(rgba);
|
|
1749
|
+
if (forceAlpha && hex.length === 7) {
|
|
1750
|
+
return hex + "ff";
|
|
1751
|
+
}
|
|
1752
|
+
return hex;
|
|
1753
|
+
}
|
|
1754
|
+
function pickerStateFromArea(state, x, y) {
|
|
1755
|
+
return {
|
|
1756
|
+
...state,
|
|
1757
|
+
s: clamp2(x, 0, 1),
|
|
1758
|
+
v: clamp2(1 - y, 0, 1)
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
function pickerStateFromHue(state, t) {
|
|
1762
|
+
const clampedT = clamp2(t, 0, 1);
|
|
1763
|
+
const h = clampedT >= 1 ? 0 : clampedT * 360;
|
|
1764
|
+
return {
|
|
1765
|
+
...state,
|
|
1766
|
+
h
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
function pickerStateFromAlpha(state, t) {
|
|
1770
|
+
return {
|
|
1771
|
+
...state,
|
|
1772
|
+
alpha: clamp2(t, 0, 1)
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
function pickerKeyStep(state, target, key, shift) {
|
|
1776
|
+
const step = shift ? 0.1 : 0.01;
|
|
1777
|
+
const hueStep = shift ? 10 : 1;
|
|
1778
|
+
switch (target) {
|
|
1779
|
+
case "area": {
|
|
1780
|
+
if (key === "ArrowLeft") {
|
|
1781
|
+
return { ...state, s: clamp2(state.s - step, 0, 1) };
|
|
1782
|
+
}
|
|
1783
|
+
if (key === "ArrowRight") {
|
|
1784
|
+
return { ...state, s: clamp2(state.s + step, 0, 1) };
|
|
1785
|
+
}
|
|
1786
|
+
if (key === "ArrowDown") {
|
|
1787
|
+
return { ...state, v: clamp2(state.v - step, 0, 1) };
|
|
1788
|
+
}
|
|
1789
|
+
if (key === "ArrowUp") {
|
|
1790
|
+
return { ...state, v: clamp2(state.v + step, 0, 1) };
|
|
1791
|
+
}
|
|
1792
|
+
if (key === "Home") {
|
|
1793
|
+
return { ...state, s: 0, v: 1 };
|
|
1794
|
+
}
|
|
1795
|
+
if (key === "End") {
|
|
1796
|
+
return { ...state, s: 1, v: 0 };
|
|
1797
|
+
}
|
|
1798
|
+
return null;
|
|
1799
|
+
}
|
|
1800
|
+
case "hue": {
|
|
1801
|
+
if (key === "ArrowLeft" || key === "ArrowDown") {
|
|
1802
|
+
const nextH = (state.h - hueStep + 360) % 360;
|
|
1803
|
+
return { ...state, h: nextH };
|
|
1804
|
+
}
|
|
1805
|
+
if (key === "ArrowRight" || key === "ArrowUp") {
|
|
1806
|
+
const nextH = (state.h + hueStep) % 360;
|
|
1807
|
+
return { ...state, h: nextH };
|
|
1808
|
+
}
|
|
1809
|
+
if (key === "Home") {
|
|
1810
|
+
return { ...state, h: 0 };
|
|
1811
|
+
}
|
|
1812
|
+
if (key === "End") {
|
|
1813
|
+
return { ...state, h: 359 };
|
|
1814
|
+
}
|
|
1815
|
+
return null;
|
|
1816
|
+
}
|
|
1817
|
+
case "alpha": {
|
|
1818
|
+
if (key === "ArrowLeft" || key === "ArrowDown") {
|
|
1819
|
+
return { ...state, alpha: clamp2(state.alpha - step, 0, 1) };
|
|
1820
|
+
}
|
|
1821
|
+
if (key === "ArrowRight" || key === "ArrowUp") {
|
|
1822
|
+
return { ...state, alpha: clamp2(state.alpha + step, 0, 1) };
|
|
1823
|
+
}
|
|
1824
|
+
if (key === "Home") {
|
|
1825
|
+
return { ...state, alpha: 0 };
|
|
1826
|
+
}
|
|
1827
|
+
if (key === "End") {
|
|
1828
|
+
return { ...state, alpha: 1 };
|
|
1829
|
+
}
|
|
1830
|
+
return null;
|
|
1831
|
+
}
|
|
1832
|
+
default:
|
|
1833
|
+
return null;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
function pickerAreaBackground(state) {
|
|
1837
|
+
const pureHueRgb = hsvToRgb({ h: state.h, s: 1, v: 1, alpha: 1 });
|
|
1838
|
+
const hueHex = formatHex(pureHueRgb);
|
|
1839
|
+
return `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${hueHex})`;
|
|
1840
|
+
}
|
|
1841
|
+
function pickerAlphaBackground(state) {
|
|
1842
|
+
const opaqueRgb = hsvToRgb({ h: state.h, s: state.s, v: state.v, alpha: 1 });
|
|
1843
|
+
const hex = formatHex(opaqueRgb);
|
|
1844
|
+
return `linear-gradient(to right, transparent, ${hex})`;
|
|
1845
|
+
}
|
|
1846
|
+
var PICKER_HUE_BACKGROUND = "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)";
|
|
1847
|
+
function dualPickerView(value, scheme) {
|
|
1848
|
+
const norm = _nullishCoalesce(normalizeDualColor(value), () => ( { light: "#000000", dark: "#000000" }));
|
|
1849
|
+
const otherScheme = scheme === "light" ? "dark" : "light";
|
|
1850
|
+
return {
|
|
1851
|
+
color: norm[scheme],
|
|
1852
|
+
linked: norm.light === norm.dark,
|
|
1853
|
+
other: norm[otherScheme]
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
|
|
1869
|
+
|
|
1870
|
+
|
|
1871
|
+
|
|
1872
|
+
|
|
1873
|
+
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
|
|
1885
|
+
|
|
1886
|
+
|
|
1887
|
+
|
|
1888
|
+
|
|
1889
|
+
|
|
1890
|
+
|
|
1891
|
+
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
exports.srgbToLinear = srgbToLinear; exports.linearToSrgb = linearToSrgb; exports.rgbToHsl = rgbToHsl; exports.hslToRgb = hslToRgb; exports.rgbToHsv = rgbToHsv; exports.hsvToRgb = hsvToRgb; exports.rgbToOklab = rgbToOklab; exports.oklabToRgb = oklabToRgb; exports.oklabToOklch = oklabToOklch; exports.oklchToOklab = oklchToOklab; exports.linearSrgbToDisplayP3 = linearSrgbToDisplayP3; exports.displayP3ToLinearSrgb = displayP3ToLinearSrgb; exports.NAMED_COLORS = NAMED_COLORS; exports.parseColor = parseColor; exports.isColor = isColor; exports.inSrgbGamut = inSrgbGamut; exports.deltaEOK = deltaEOK; exports.toSrgbGamut = toSrgbGamut; exports.formatHex = formatHex; exports.formatColor = formatColor; exports.normalizeColor = normalizeColor; exports.relativeLuminance = relativeLuminance; exports.contrastRatio = contrastRatio; exports.contrastLevel = contrastLevel; exports.readableTextColor = readableTextColor; exports.simulateColorVision = simulateColorVision; exports.checkPaletteColors = checkPaletteColors; exports.HARMONY_KINDS = HARMONY_KINDS; exports.wheelToHue = wheelToHue; exports.hueToWheel = hueToWheel; exports.colorHarmony = colorHarmony; exports.mixColors = mixColors; exports.pushRecentColor = pushRecentColor; exports.DEFAULT_SCHEME_SURFACES = DEFAULT_SCHEME_SURFACES; exports.isDualColor = isDualColor; exports.parseDualColor = parseDualColor; exports.normalizeDualColor = normalizeDualColor; exports.resolveColor = resolveColor; exports.formatDualColor = formatDualColor; exports.isSameInBothSchemes = isSameInBothSchemes; exports.setDualColorSide = setDualColorSide; exports.deriveDualColor = deriveDualColor; exports.checkDualColorContrast = checkDualColorContrast; exports.dualColorsToCss = dualColorsToCss; exports.createColorToolProvider = createColorToolProvider; exports.pickerStateFromColor = pickerStateFromColor; exports.pickerStateToHex = pickerStateToHex; exports.pickerStateFromArea = pickerStateFromArea; exports.pickerStateFromHue = pickerStateFromHue; exports.pickerStateFromAlpha = pickerStateFromAlpha; exports.pickerKeyStep = pickerKeyStep; exports.pickerAreaBackground = pickerAreaBackground; exports.pickerAlphaBackground = pickerAlphaBackground; exports.PICKER_HUE_BACKGROUND = PICKER_HUE_BACKGROUND; exports.dualPickerView = dualPickerView;
|