okchroma 0.1.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/LICENSE +21 -0
- package/README.md +135 -0
- package/dist-lib/index.cjs +3924 -0
- package/dist-lib/index.mjs +3901 -0
- package/dist-lib/types/engine/alphaPapers.d.ts +14 -0
- package/dist-lib/types/engine/archetypes.d.ts +13 -0
- package/dist-lib/types/engine/collision.d.ts +43 -0
- package/dist-lib/types/engine/colorEngine.d.ts +109 -0
- package/dist-lib/types/engine/colorMath.d.ts +181 -0
- package/dist-lib/types/engine/constraints.d.ts +18 -0
- package/dist-lib/types/engine/cssRender.d.ts +32 -0
- package/dist-lib/types/engine/darkChromaCurve.d.ts +2 -0
- package/dist-lib/types/engine/figmaRender.d.ts +34 -0
- package/dist-lib/types/engine/neutralCurve.d.ts +5 -0
- package/dist-lib/types/engine/p2.d.ts +12 -0
- package/dist-lib/types/engine/perceptualL.d.ts +11 -0
- package/dist-lib/types/engine/requirements/dtcg.d.ts +44 -0
- package/dist-lib/types/engine/requirements/producers.d.ts +160 -0
- package/dist-lib/types/engine/requirements/profiles.d.ts +7 -0
- package/dist-lib/types/engine/requirements/resolve.d.ts +46 -0
- package/dist-lib/types/engine/requirements/spec.d.ts +65 -0
- package/dist-lib/types/engine/resolve.d.ts +129 -0
- package/dist-lib/types/engine/signalShift.d.ts +8 -0
- package/dist-lib/types/engine/signals.d.ts +19 -0
- package/dist-lib/types/engine/stopTable.d.ts +76 -0
- package/dist-lib/types/engine/tokenDescriptions.d.ts +18 -0
- package/dist-lib/types/engine/tokenNames.d.ts +21 -0
- package/dist-lib/types/index.d.ts +11 -0
- package/package.json +68 -0
|
@@ -0,0 +1,3924 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_LINK_HEX: () => DEFAULT_LINK_HEX,
|
|
24
|
+
EXT_KEY: () => EXT_KEY,
|
|
25
|
+
INK_0: () => INK_0,
|
|
26
|
+
MODE_SPECS: () => MODE_SPECS,
|
|
27
|
+
OFFSET_ALPHAS: () => OFFSET_ALPHAS,
|
|
28
|
+
OUTLINE_HOVER_ALPHA: () => OUTLINE_HOVER_ALPHA,
|
|
29
|
+
PAPER_100: () => PAPER_100,
|
|
30
|
+
RESOLVER_ID: () => RESOLVER_ID,
|
|
31
|
+
SCALE_STOP_COUNT: () => SCALE_STOP_COUNT,
|
|
32
|
+
SECONDARY_DISTINCT_DELTA_E: () => SECONDARY_DISTINCT_DELTA_E,
|
|
33
|
+
SIGNALS: () => SIGNALS2,
|
|
34
|
+
SIGNAL_EMIT_NAME: () => SIGNAL_EMIT_NAME,
|
|
35
|
+
SIGNAL_SCALES: () => SIGNAL_SCALES,
|
|
36
|
+
STAMP_EDGE: () => STAMP_EDGE,
|
|
37
|
+
STAMP_FILL: () => STAMP_FILL,
|
|
38
|
+
STAMP_FILL_HOVER: () => STAMP_FILL_HOVER,
|
|
39
|
+
STAMP_FILL_PRESSED: () => STAMP_FILL_PRESSED,
|
|
40
|
+
STAMP_ON: () => STAMP_ON,
|
|
41
|
+
STAMP_STATE_LEAVES: () => STAMP_STATE_LEAVES,
|
|
42
|
+
SUBTLE_PASTEL_K: () => SUBTLE_PASTEL_K,
|
|
43
|
+
SUBTLE_TINT_MULT: () => SUBTLE_TINT_MULT,
|
|
44
|
+
brandCss: () => brandCss,
|
|
45
|
+
checkAllCollisions: () => checkAllCollisions,
|
|
46
|
+
checkCollision: () => checkCollision,
|
|
47
|
+
checkHueCollision: () => checkHueCollision,
|
|
48
|
+
classifyArchetype: () => classifyArchetype,
|
|
49
|
+
emitDtcgRamp: () => emitDtcgRamp,
|
|
50
|
+
generateNeutralScale: () => generateNeutralScale,
|
|
51
|
+
generateScale: () => generateScale,
|
|
52
|
+
generateSubtleSecondary: () => generateSubtleSecondary,
|
|
53
|
+
neutralCss: () => neutralCss,
|
|
54
|
+
parseToken: () => parseToken,
|
|
55
|
+
resolveBrand: () => resolveBrand,
|
|
56
|
+
resolveDtcgRamp: () => resolveDtcgRamp,
|
|
57
|
+
resolveLinkInverseTrio: () => resolveLinkInverseTrio,
|
|
58
|
+
resolveLinkTrio: () => resolveLinkTrio,
|
|
59
|
+
resolveTheme: () => resolveTheme,
|
|
60
|
+
signalScalesFor: () => signalScalesFor,
|
|
61
|
+
signalsCss: () => signalsCss,
|
|
62
|
+
stopHex: () => stopHex,
|
|
63
|
+
stopTokenName: () => stopTokenName,
|
|
64
|
+
stopsToVars: () => stopsToVars,
|
|
65
|
+
themeToFigma: () => themeToFigma,
|
|
66
|
+
toHex: () => toHex2,
|
|
67
|
+
tokenOrder: () => tokenOrder
|
|
68
|
+
});
|
|
69
|
+
module.exports = __toCommonJS(index_exports);
|
|
70
|
+
|
|
71
|
+
// src/engine/archetypes.ts
|
|
72
|
+
var ARCHETYPES = [
|
|
73
|
+
{ name: "near-black", min: 0, max: 0.25, medianL: 0.125 },
|
|
74
|
+
{ name: "dark", min: 0.25, max: 0.4, medianL: 0.325 },
|
|
75
|
+
{ name: "rich", min: 0.4, max: 0.55, medianL: 0.475 },
|
|
76
|
+
{ name: "vivid", min: 0.55, max: 0.65, medianL: 0.6 },
|
|
77
|
+
{ name: "bright", min: 0.65, max: 0.85, medianL: 0.75 },
|
|
78
|
+
{ name: "light", min: 0.85, max: 1, medianL: 0.925 }
|
|
79
|
+
];
|
|
80
|
+
function classifyArchetype(L) {
|
|
81
|
+
return (ARCHETYPES.find((a) => L >= a.min && L < a.max) ?? ARCHETYPES[ARCHETYPES.length - 1]).name;
|
|
82
|
+
}
|
|
83
|
+
function medianLForArchetype(archetype) {
|
|
84
|
+
return ARCHETYPES.find((a) => a.name === archetype).medianL;
|
|
85
|
+
}
|
|
86
|
+
var STATE_HOVER_K = 0.05;
|
|
87
|
+
var STATE_L_MIN = 0.05;
|
|
88
|
+
var STATE_L_MAX = 0.98;
|
|
89
|
+
function stateStepL(restL, _mode, k) {
|
|
90
|
+
return k * STATE_HOVER_K;
|
|
91
|
+
}
|
|
92
|
+
var OVERRIDE_LIGHT_BELOW = ARCHETYPES.find((a) => a.name === "rich").min;
|
|
93
|
+
function stateFillL(restL, mode, k) {
|
|
94
|
+
const natural = mode === "dark" ? 1 : -1;
|
|
95
|
+
const preferred = mode === "light" && restL < OVERRIDE_LIGHT_BELOW ? -natural : natural;
|
|
96
|
+
const fits = (d) => {
|
|
97
|
+
const p = restL + d * stateStepL(restL, mode, 2);
|
|
98
|
+
return p >= STATE_L_MIN && p <= STATE_L_MAX;
|
|
99
|
+
};
|
|
100
|
+
const dir = fits(preferred) ? preferred : fits(-preferred) ? -preferred : preferred;
|
|
101
|
+
const L = restL + dir * stateStepL(restL, mode, k);
|
|
102
|
+
return Math.min(STATE_L_MAX, Math.max(STATE_L_MIN, L));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/engine/constraints.ts
|
|
106
|
+
var MASTER_GAMUT = "p3";
|
|
107
|
+
function oklchToLinearRgb(L, C2, H2) {
|
|
108
|
+
const h = H2 * Math.PI / 180;
|
|
109
|
+
const a = C2 * Math.cos(h);
|
|
110
|
+
const b = C2 * Math.sin(h);
|
|
111
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
112
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
113
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
114
|
+
const l3 = l_ ** 3, m3 = m_ ** 3, s3 = s_ ** 3;
|
|
115
|
+
return [
|
|
116
|
+
4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3,
|
|
117
|
+
-1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3,
|
|
118
|
+
-0.0041960863 * l3 - 0.7034186147 * m3 + 1.707614701 * s3
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
function oklchToXyz(L, C2, H2) {
|
|
122
|
+
const h = H2 * Math.PI / 180;
|
|
123
|
+
const a = C2 * Math.cos(h);
|
|
124
|
+
const b = C2 * Math.sin(h);
|
|
125
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
126
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
127
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
128
|
+
const l3 = l_ ** 3, m3 = m_ ** 3, s3 = s_ ** 3;
|
|
129
|
+
return [
|
|
130
|
+
1.2270138511 * l3 - 0.5577999807 * m3 + 0.281256149 * s3,
|
|
131
|
+
-0.0405801784 * l3 + 1.1122568696 * m3 - 0.0716766787 * s3,
|
|
132
|
+
-0.0763812845 * l3 - 0.4214819784 * m3 + 1.5861632204 * s3
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
function oklchToLinearP3(L, C2, H2) {
|
|
136
|
+
const [x2, y2, z2] = oklchToXyz(L, C2, H2);
|
|
137
|
+
return [
|
|
138
|
+
2.4934969119 * x2 - 0.9313836179 * y2 - 0.4027107845 * z2,
|
|
139
|
+
-0.8294889696 * x2 + 1.7626640603 * y2 + 0.0236246858 * z2,
|
|
140
|
+
0.0358458302 * x2 - 0.0761723893 * y2 + 0.956884524 * z2
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
var linearChannels = (L, C2, H2, gamut) => gamut === "p3" ? oklchToLinearP3(L, C2, H2) : oklchToLinearRgb(L, C2, H2);
|
|
144
|
+
function encodedChannels(L, C2, H2, gamut = MASTER_GAMUT) {
|
|
145
|
+
const [r, g2, b] = linearChannels(L, C2, H2, gamut);
|
|
146
|
+
const gm2 = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
147
|
+
return [gm2(r), gm2(g2), gm2(b)];
|
|
148
|
+
}
|
|
149
|
+
function wcagY(L, C2, H2, gamut = MASTER_GAMUT) {
|
|
150
|
+
if (gamut === "p3") {
|
|
151
|
+
const [r2, g3, b2] = oklchToLinearP3(L, C2, H2);
|
|
152
|
+
return 0.2289745641 * Math.max(0, r2) + 0.6917385218 * Math.max(0, g3) + 0.0792869141 * Math.max(0, b2);
|
|
153
|
+
}
|
|
154
|
+
const [r, g2, b] = oklchToLinearRgb(L, C2, H2);
|
|
155
|
+
return 0.2126 * Math.max(0, r) + 0.7152 * Math.max(0, g2) + 0.0722 * Math.max(0, b);
|
|
156
|
+
}
|
|
157
|
+
function contrastRatio(Y12, Y2) {
|
|
158
|
+
const lighter = Math.max(Y12, Y2);
|
|
159
|
+
const darker = Math.min(Y12, Y2);
|
|
160
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
161
|
+
}
|
|
162
|
+
function legalRatio(L, C2, H2, otherY) {
|
|
163
|
+
const r = contrastRatio(wcagY(L, C2, H2, MASTER_GAMUT), otherY);
|
|
164
|
+
if (MASTER_GAMUT === "srgb") return r;
|
|
165
|
+
const cS = clampChromaToGamut(L, C2, H2, "srgb");
|
|
166
|
+
return Math.min(r, contrastRatio(wcagY(L, cS, H2, "srgb"), otherY));
|
|
167
|
+
}
|
|
168
|
+
function shippedY(L, C2, H2) {
|
|
169
|
+
const cS = clampChromaToGamut(L, C2, H2, "srgb");
|
|
170
|
+
const [rl, gl, bl] = linearChannels(L, cS, H2, "srgb");
|
|
171
|
+
const gm2 = (v2) => {
|
|
172
|
+
const x2 = Math.min(1, Math.max(0, v2));
|
|
173
|
+
return x2 <= 31308e-7 ? 12.92 * x2 : 1.055 * x2 ** (1 / 2.4) - 0.055;
|
|
174
|
+
};
|
|
175
|
+
const lin = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
176
|
+
const q2 = (v2) => Math.round(gm2(v2) * 255) / 255;
|
|
177
|
+
return 0.2126 * lin(q2(rl)) + 0.7152 * lin(q2(gl)) + 0.0722 * lin(q2(bl));
|
|
178
|
+
}
|
|
179
|
+
function findLForContrast(startL, C2, H2, bgY, targetContrast) {
|
|
180
|
+
if (legalRatio(startL, C2, H2, bgY) >= targetContrast) return startL;
|
|
181
|
+
let lo = 0.05, hi = startL;
|
|
182
|
+
for (let i = 0; i < 20; i++) {
|
|
183
|
+
const mid = (lo + hi) / 2;
|
|
184
|
+
legalRatio(mid, C2, H2, bgY) >= targetContrast ? lo = mid : hi = mid;
|
|
185
|
+
}
|
|
186
|
+
return lo;
|
|
187
|
+
}
|
|
188
|
+
function findLForContrastUp(startL, C2, H2, bgY, targetContrast) {
|
|
189
|
+
if (legalRatio(startL, C2, H2, bgY) >= targetContrast) return startL;
|
|
190
|
+
let lo = startL, hi = 0.999;
|
|
191
|
+
for (let i = 0; i < 20; i++) {
|
|
192
|
+
const mid = (lo + hi) / 2;
|
|
193
|
+
legalRatio(mid, C2, H2, bgY) >= targetContrast ? hi = mid : lo = mid;
|
|
194
|
+
}
|
|
195
|
+
return hi;
|
|
196
|
+
}
|
|
197
|
+
function apcaY(r, g2, b, gamut = MASTER_GAMUT) {
|
|
198
|
+
const ch = (c) => Math.min(1, Math.max(0, c)) ** 2.4;
|
|
199
|
+
if (gamut === "p3") return 0.2289829595 * ch(r) + 0.6917492626 * ch(g2) + 0.0792677779 * ch(b);
|
|
200
|
+
return 0.2126729 * ch(r) + 0.7151522 * ch(g2) + 0.072175 * ch(b);
|
|
201
|
+
}
|
|
202
|
+
function apcaLc(txtY, bgY) {
|
|
203
|
+
const fclamp = (Y2) => Y2 > 0.022 ? Y2 : Y2 + (0.022 - Y2) ** 1.414;
|
|
204
|
+
const tY = fclamp(txtY);
|
|
205
|
+
const bY = fclamp(bgY);
|
|
206
|
+
if (Math.abs(bY - tY) < 5e-4) return 0;
|
|
207
|
+
if (bY > tY) {
|
|
208
|
+
const sapc2 = (bY ** 0.56 - tY ** 0.57) * 1.14;
|
|
209
|
+
return sapc2 < 0.1 ? 0 : (sapc2 - 0.027) * 100;
|
|
210
|
+
}
|
|
211
|
+
const sapc = (bY ** 0.65 - tY ** 0.62) * 1.14;
|
|
212
|
+
return sapc > -0.1 ? 0 : (sapc + 0.027) * 100;
|
|
213
|
+
}
|
|
214
|
+
function findMaxLForContrast(C2, H2, bgY, targetContrast) {
|
|
215
|
+
let lo = 5e-3, hi = 0.999;
|
|
216
|
+
for (let i = 0; i < 20; i++) {
|
|
217
|
+
const mid = (lo + hi) / 2;
|
|
218
|
+
legalRatio(mid, C2, H2, bgY) >= targetContrast ? lo = mid : hi = mid;
|
|
219
|
+
}
|
|
220
|
+
return lo;
|
|
221
|
+
}
|
|
222
|
+
function clampChromaToGamut(L, C2, H2, gamut = MASTER_GAMUT) {
|
|
223
|
+
const inGamut = (c) => {
|
|
224
|
+
const [r, g2, b] = linearChannels(L, c, H2, gamut);
|
|
225
|
+
return r >= -1e-4 && r <= 1.0001 && g2 >= -1e-4 && g2 <= 1.0001 && b >= -1e-4 && b <= 1.0001;
|
|
226
|
+
};
|
|
227
|
+
if (inGamut(C2)) return C2;
|
|
228
|
+
let lo = 0, hi = C2;
|
|
229
|
+
for (let i = 0; i < 20; i++) {
|
|
230
|
+
const mid = (lo + hi) / 2;
|
|
231
|
+
inGamut(mid) ? lo = mid : hi = mid;
|
|
232
|
+
}
|
|
233
|
+
return lo;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/engine/stopTable.ts
|
|
237
|
+
var ROOT_L_LIGHT = {
|
|
238
|
+
1: 0.987,
|
|
239
|
+
2: 0.97,
|
|
240
|
+
3: 0.95,
|
|
241
|
+
4: 0.924,
|
|
242
|
+
5: 0.892,
|
|
243
|
+
6: 0.852,
|
|
244
|
+
7: 0.801,
|
|
245
|
+
8: 0.738,
|
|
246
|
+
9: 0.53,
|
|
247
|
+
10: 0.415,
|
|
248
|
+
11: 0.3
|
|
249
|
+
};
|
|
250
|
+
var ROOT_L_DARK = {
|
|
251
|
+
1: 0.178,
|
|
252
|
+
2: 0.213,
|
|
253
|
+
3: 0.252,
|
|
254
|
+
4: 0.285,
|
|
255
|
+
5: 0.313,
|
|
256
|
+
6: 0.348,
|
|
257
|
+
7: 0.42,
|
|
258
|
+
8: 0.55,
|
|
259
|
+
9: 0.767,
|
|
260
|
+
10: 0.843,
|
|
261
|
+
11: 0.919
|
|
262
|
+
};
|
|
263
|
+
var chromaFloorBase = (idx) => 0.02 + (0.04 - 0.02) * (idx / 7);
|
|
264
|
+
var SCALE_C_LIGHT = {
|
|
265
|
+
0: { base: 0, sat: 0 },
|
|
266
|
+
1: { base: 4e-3, sat: 0.5 },
|
|
267
|
+
2: { base: 0.01, sat: 0.85 },
|
|
268
|
+
3: { base: 0.022, sat: 0.95 },
|
|
269
|
+
4: { base: 0.039, sat: 0.95 },
|
|
270
|
+
5: { base: 0.053, sat: 0.92 },
|
|
271
|
+
6: { base: 0.068, sat: 0.85 },
|
|
272
|
+
7: { base: 0.086, sat: 0.78 },
|
|
273
|
+
8: { base: 0.142, sat: 0.78 },
|
|
274
|
+
9: { inkMult: 0.95, inkMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
|
|
275
|
+
// ink-10 keeps the FIRST-text register (ink-9's row, C49): the retired hover law
|
|
276
|
+
// evaluated ink-9's chroma register at the between L, so the same params reproduce it;
|
|
277
|
+
// the damped strong register below belongs to the top text stop alone.
|
|
278
|
+
10: { inkMult: 0.95, inkMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
|
|
279
|
+
11: { inkMult: 0.5, inkMaxC: 0.08, chromaFloor: chromaFloorBase(11) }
|
|
280
|
+
};
|
|
281
|
+
var SCALE_C_DARK = {
|
|
282
|
+
0: { sat: 0.4 },
|
|
283
|
+
1: { sat: 0.4 },
|
|
284
|
+
2: { sat: 0.52 },
|
|
285
|
+
3: { sat: 0.62 },
|
|
286
|
+
4: { sat: 0.68 },
|
|
287
|
+
5: { sat: 0.72 },
|
|
288
|
+
6: { sat: 0.76 },
|
|
289
|
+
7: { sat: 0.8 },
|
|
290
|
+
8: { sat: 0.84 },
|
|
291
|
+
9: { inkMult: 0.95, inkMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
|
|
292
|
+
10: { inkMult: 0.95, inkMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
|
|
293
|
+
11: { inkMult: 0.62, inkMaxC: 0.045, chromaFloor: chromaFloorBase(11) }
|
|
294
|
+
};
|
|
295
|
+
var DARK_CTA_C = {
|
|
296
|
+
brand: {
|
|
297
|
+
policy: "trimmed",
|
|
298
|
+
globalTrim: 0.76,
|
|
299
|
+
lobes: [
|
|
300
|
+
{ center: 265, width: 115, depth: 0.3 },
|
|
301
|
+
// blue
|
|
302
|
+
{ center: 345, width: 110, depth: 0.26 }
|
|
303
|
+
// red-magenta
|
|
304
|
+
]
|
|
305
|
+
},
|
|
306
|
+
signal: { policy: "identity" }
|
|
307
|
+
};
|
|
308
|
+
var STOP_8_NONTEXT_CONTRAST = 3;
|
|
309
|
+
var DARK_BAND_TOP_LIFT = 1.19;
|
|
310
|
+
var INK_9_CONTRAST = 4.5;
|
|
311
|
+
var INK_10_CONTRAST = 4.5;
|
|
312
|
+
var INK_11_CONTRAST_FLOOR = 7;
|
|
313
|
+
var INK_30_GROUND = {
|
|
314
|
+
wcag: {
|
|
315
|
+
light: { L: 0.3334966864733433, C: 0.07399966383476321, H: 104.26776675496032 },
|
|
316
|
+
dark: { L: 0.907688606079988, C: 0.045, H: 293.66431934911327 }
|
|
317
|
+
},
|
|
318
|
+
apca: {
|
|
319
|
+
light: { L: 0.3334966864733433, C: 0.07399966383476321, H: 104.26776675496032 },
|
|
320
|
+
dark: { L: 0.9183200227624329, C: 0.045, H: 153.30922413895024 }
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
var DARK_CTA_MIN_L = 0.63;
|
|
324
|
+
var DARK_BRAND_FILL_MIN_L = 0.7;
|
|
325
|
+
var DARK_SIGNAL_WARM_DRIFT = 1 / 3;
|
|
326
|
+
var NEUTRAL_CTA_DARK_POP_CLEARANCE = 1.2;
|
|
327
|
+
var GOLD_SPINE = [
|
|
328
|
+
[0.3, 47],
|
|
329
|
+
[0.57, 50],
|
|
330
|
+
[0.74, 71],
|
|
331
|
+
[0.8, 88],
|
|
332
|
+
[0.87, 103],
|
|
333
|
+
[0.97, 110]
|
|
334
|
+
];
|
|
335
|
+
var WARM_TORSION = {
|
|
336
|
+
weight: [[40, 0], [50, 1], [88, 1], [104, 0]],
|
|
337
|
+
travel: 0.55,
|
|
338
|
+
capDeg: 24
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// src/engine/neutralCurve.ts
|
|
342
|
+
var SHAPE_POINTS = {
|
|
343
|
+
light: [
|
|
344
|
+
[0.987, 0.108],
|
|
345
|
+
[0.97, 0.179],
|
|
346
|
+
[0.95, 0.253],
|
|
347
|
+
[0.924, 0.32],
|
|
348
|
+
[0.892, 0.45],
|
|
349
|
+
[0.852, 0.503],
|
|
350
|
+
[0.801, 0.599],
|
|
351
|
+
[0.738, 0.818],
|
|
352
|
+
[0.6, 1],
|
|
353
|
+
[0.56, 0.939],
|
|
354
|
+
[0.53, 0.841],
|
|
355
|
+
[0.3, 0.74]
|
|
356
|
+
],
|
|
357
|
+
dark: [
|
|
358
|
+
[0.178, 0.236],
|
|
359
|
+
[0.213, 0.229],
|
|
360
|
+
[0.252, 0.276],
|
|
361
|
+
[0.285, 0.394],
|
|
362
|
+
[0.313, 0.469],
|
|
363
|
+
[0.348, 0.551],
|
|
364
|
+
[0.42, 0.648],
|
|
365
|
+
[0.55, 0.859],
|
|
366
|
+
[0.6, 1],
|
|
367
|
+
[0.64, 0.94],
|
|
368
|
+
[0.767, 0.745],
|
|
369
|
+
[0.919, 0.195]
|
|
370
|
+
]
|
|
371
|
+
};
|
|
372
|
+
var PEAK = [
|
|
373
|
+
{ h: 97, light: 0.0102, dark: 0.0109 },
|
|
374
|
+
{ h: 143, light: 0.0119, dark: 0.0181 },
|
|
375
|
+
{ h: 270, light: 0.0165, dark: 0.0156 },
|
|
376
|
+
{ h: 301, light: 0.0193, dark: 0.0172 }
|
|
377
|
+
];
|
|
378
|
+
var LEVEL = { pure: 0, default: 0.75, medium: 1, branded: 1.75 };
|
|
379
|
+
var NEUTRAL_TINT_PEAK = 95e-4;
|
|
380
|
+
var NEUTRAL_TINT_POINTS = {
|
|
381
|
+
light: [
|
|
382
|
+
[0.987, 0.474],
|
|
383
|
+
// paper-1 ─┐
|
|
384
|
+
[0.97, 0.684],
|
|
385
|
+
// paper-2 │ the differentiation lift: tint grows with elevation
|
|
386
|
+
[0.95, 0.895],
|
|
387
|
+
// paper-3 ─┘
|
|
388
|
+
[0.924, 1],
|
|
389
|
+
// wash-4 ─┐ peak: the mid-wash band, the neutral's most-branded moment
|
|
390
|
+
[0.892, 1],
|
|
391
|
+
// wash-5 ─┘
|
|
392
|
+
[0.852, 0.842],
|
|
393
|
+
// wash-6 ─┐
|
|
394
|
+
[0.801, 0.653],
|
|
395
|
+
// wash-7 │
|
|
396
|
+
[0.738, 0.505],
|
|
397
|
+
// highlight-8
|
|
398
|
+
[0.6, 0.4],
|
|
399
|
+
// knee (ex-scaffold) the drop-off: hue fades as you climb
|
|
400
|
+
[0.56, 0.347],
|
|
401
|
+
// knee (ex-scaffold)
|
|
402
|
+
[0.53, 0.295],
|
|
403
|
+
// first text stop
|
|
404
|
+
[0.3, 0.189]
|
|
405
|
+
// strong text ─┘ lands ~neutral
|
|
406
|
+
],
|
|
407
|
+
dark: [
|
|
408
|
+
[0.178, 0.474],
|
|
409
|
+
// paper-1
|
|
410
|
+
[0.213, 0.684],
|
|
411
|
+
// paper-2
|
|
412
|
+
[0.252, 0.895],
|
|
413
|
+
// paper-3
|
|
414
|
+
[0.285, 1],
|
|
415
|
+
// wash-4
|
|
416
|
+
[0.313, 1],
|
|
417
|
+
// wash-5
|
|
418
|
+
[0.348, 0.842],
|
|
419
|
+
// wash-6
|
|
420
|
+
[0.42, 0.653],
|
|
421
|
+
// wash-7
|
|
422
|
+
[0.55, 0.505],
|
|
423
|
+
// highlight-8
|
|
424
|
+
[0.6, 0.4],
|
|
425
|
+
// knee (ex-scaffold)
|
|
426
|
+
[0.64, 0.347],
|
|
427
|
+
// knee (ex-scaffold)
|
|
428
|
+
[0.767, 0.295],
|
|
429
|
+
// first text stop
|
|
430
|
+
[0.919, 0.189]
|
|
431
|
+
// strong text
|
|
432
|
+
]
|
|
433
|
+
};
|
|
434
|
+
var warmDamp = (hue) => {
|
|
435
|
+
const h = (hue % 360 + 360) % 360;
|
|
436
|
+
const d = Math.min(Math.abs(h - 60), 360 - Math.abs(h - 60));
|
|
437
|
+
return 1 - 0.45 * Math.max(0, 1 - d / 140);
|
|
438
|
+
};
|
|
439
|
+
var neutralTintPeak = (hue) => NEUTRAL_TINT_PEAK * warmDamp(hue);
|
|
440
|
+
var peakC = (hue, mode) => {
|
|
441
|
+
const h = (hue % 360 + 360) % 360;
|
|
442
|
+
const pts = PEAK.map((p) => ({ h: p.h, c: p[mode] })).sort((a, b) => a.h - b.h);
|
|
443
|
+
for (let i = 0; i < pts.length; i++) {
|
|
444
|
+
const a = pts[i], b = pts[(i + 1) % pts.length];
|
|
445
|
+
const span = (b.h - a.h + 360) % 360;
|
|
446
|
+
const off = (h - a.h + 360) % 360;
|
|
447
|
+
if (off <= span) return a.c + (b.c - a.c) * (off / span);
|
|
448
|
+
}
|
|
449
|
+
return mode === "light" ? 0.0145 : 0.0155;
|
|
450
|
+
};
|
|
451
|
+
var interpTintAt = (L, points) => {
|
|
452
|
+
const pts = points.map(([l, s]) => ({ l, s })).sort((a, b) => a.l - b.l);
|
|
453
|
+
if (L <= pts[0].l) return pts[0].s;
|
|
454
|
+
if (L >= pts[pts.length - 1].l) return pts[pts.length - 1].s;
|
|
455
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
456
|
+
if (L >= pts[i].l && L <= pts[i + 1].l) {
|
|
457
|
+
const t = (L - pts[i].l) / (pts[i + 1].l - pts[i].l);
|
|
458
|
+
return pts[i].s + (pts[i + 1].s - pts[i].s) * t;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return pts[pts.length - 1].s;
|
|
462
|
+
};
|
|
463
|
+
function neutralChromaCurve(brandH, level = "default") {
|
|
464
|
+
const mult = LEVEL[level];
|
|
465
|
+
const peak = neutralTintPeak(brandH);
|
|
466
|
+
return (L, mode) => mult * peak * interpTintAt(L, NEUTRAL_TINT_POINTS[mode]);
|
|
467
|
+
}
|
|
468
|
+
var SUBTLE_SECONDARY_MULT = 4.5;
|
|
469
|
+
function subtleSecondaryChromaCurve(brandH, mult = SUBTLE_SECONDARY_MULT) {
|
|
470
|
+
return (L, mode) => mult * peakC(brandH, mode) * interpTintAt(L, SHAPE_POINTS[mode]);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/engine/colorMath.ts
|
|
474
|
+
function goldSpineHueTable(L) {
|
|
475
|
+
const pts = GOLD_SPINE;
|
|
476
|
+
if (L <= pts[0][0]) return pts[0][1];
|
|
477
|
+
for (let i = 1; i < pts.length; i++) {
|
|
478
|
+
if (L <= pts[i][0]) {
|
|
479
|
+
const [l02, h02] = pts[i - 1];
|
|
480
|
+
const [l1, h1] = pts[i];
|
|
481
|
+
return h02 + (h1 - h02) * (L - l02) / (l1 - l02);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return pts[pts.length - 1][1];
|
|
485
|
+
}
|
|
486
|
+
function goldSpineHue(L) {
|
|
487
|
+
return goldSpineHueTable(L);
|
|
488
|
+
}
|
|
489
|
+
function torsionWeight(brandH) {
|
|
490
|
+
const pts = WARM_TORSION.weight;
|
|
491
|
+
if (brandH <= pts[0][0]) return pts[0][1];
|
|
492
|
+
for (let i = 1; i < pts.length; i++) {
|
|
493
|
+
if (brandH <= pts[i][0]) {
|
|
494
|
+
const [h02, w02] = pts[i - 1];
|
|
495
|
+
const [h1, w12] = pts[i];
|
|
496
|
+
return w02 + (w12 - w02) * (brandH - h02) / (h1 - h02);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return pts[pts.length - 1][1];
|
|
500
|
+
}
|
|
501
|
+
function torsionedHue(brandH, stopL, anchorL, offPathG) {
|
|
502
|
+
const { travel, capDeg } = WARM_TORSION;
|
|
503
|
+
const w2 = torsionWeight(brandH);
|
|
504
|
+
if (w2 <= 0) return brandH;
|
|
505
|
+
const drift = travel * (goldSpineHue(stopL) - goldSpineHue(anchorL)) * w2 * offPathG;
|
|
506
|
+
return brandH + Math.max(-capDeg, Math.min(capDeg, drift));
|
|
507
|
+
}
|
|
508
|
+
var SPINE_OFFPATH_SIGMA = 20;
|
|
509
|
+
var sigmoid = (x2) => 1 / (1 + Math.exp(-x2));
|
|
510
|
+
var gauss = (x2, sigma) => Math.exp(-0.5 * (x2 / sigma) ** 2);
|
|
511
|
+
function hueDelta(h, center) {
|
|
512
|
+
let d = (h - center) % 360;
|
|
513
|
+
if (d > 180) d -= 360;
|
|
514
|
+
if (d < -180) d += 360;
|
|
515
|
+
return d;
|
|
516
|
+
}
|
|
517
|
+
function oklabDist(a, b) {
|
|
518
|
+
const rad2 = (h) => h * Math.PI / 180;
|
|
519
|
+
const a1 = a.C * Math.cos(rad2(a.H)), b12 = a.C * Math.sin(rad2(a.H));
|
|
520
|
+
const a2 = b.C * Math.cos(rad2(b.H)), b2 = b.C * Math.sin(rad2(b.H));
|
|
521
|
+
return Math.sqrt((a.L - b.L) ** 2 + (a1 - a2) ** 2 + (b12 - b2) ** 2);
|
|
522
|
+
}
|
|
523
|
+
var RED_GATE = {
|
|
524
|
+
wDark: 0.65,
|
|
525
|
+
wLight: 1.6,
|
|
526
|
+
wDust: 1.4,
|
|
527
|
+
wGoldArc: 1.6,
|
|
528
|
+
G: 0.09,
|
|
529
|
+
releaseMargin: 5e-3
|
|
530
|
+
};
|
|
531
|
+
function redGateDist(c, red) {
|
|
532
|
+
const dh = (c.H - red.H + 540) % 360 - 180;
|
|
533
|
+
const meanC = 2 * Math.sqrt(Math.max(0, c.C) * Math.max(0, red.C));
|
|
534
|
+
const arcMag = meanC * Math.sin(Math.abs(Math.min(0, dh)) * Math.PI / 360);
|
|
535
|
+
const arcGold = meanC * Math.sin(Math.max(0, dh) * Math.PI / 360);
|
|
536
|
+
return Math.hypot(
|
|
537
|
+
RED_GATE.wDark * Math.max(0, red.L - c.L),
|
|
538
|
+
RED_GATE.wLight * Math.max(0, c.L - red.L),
|
|
539
|
+
RED_GATE.wDust * Math.max(0, red.C - c.C),
|
|
540
|
+
Math.max(0, c.C - red.C),
|
|
541
|
+
arcMag,
|
|
542
|
+
RED_GATE.wGoldArc * arcGold
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
var RED_SOLVE = {
|
|
546
|
+
// the region: her v7 metric widened dark-ward + the dead-zone lip (nothing lands on it,
|
|
547
|
+
// nothing de-collides from it)
|
|
548
|
+
wDark: 0.6,
|
|
549
|
+
ring: 0.02,
|
|
550
|
+
// direction rules: noticeably-magenta lightens (unless deep) · gold-side vivid flips to
|
|
551
|
+
// bright orange · on-hue vivid takes the big dark throw · else nearest edge
|
|
552
|
+
magentaDh: -14,
|
|
553
|
+
magentaMinL: 0.53,
|
|
554
|
+
magentaUpRatio: 2.5,
|
|
555
|
+
vividMin: 0.85,
|
|
556
|
+
goldDh: 1.5,
|
|
557
|
+
vividMinL: 0.53,
|
|
558
|
+
// on-hue true reds that would drift UP take the bright landing ONLY when the up-exit reads
|
|
559
|
+
// intentionally light (≥ this cut); below it they commit to the dark throw. Her 27 marks
|
|
560
|
+
// (truered-dir-checks.json) split by lane at ~0.67: wcag up-exits ~0.65 → too dim → dark ·
|
|
561
|
+
// apca ~0.69 → stays bright + red deep. The gap [0.655,0.691] is clean.
|
|
562
|
+
trueRedBrightCut: 0.67,
|
|
563
|
+
// the BRICK band: rusty-to-brick mid-dark vivid warms still read conflict-y (browns and
|
|
564
|
+
// magentas at this depth are fine); warm members are IN-REGION, dark landings must not
|
|
565
|
+
// park there → the DIAGONAL (soft cool + slight desat + extra depth, never the hard cool)
|
|
566
|
+
brickHLo: 20,
|
|
567
|
+
brickHHi: 50,
|
|
568
|
+
brickLLo: 0.36,
|
|
569
|
+
brickLHi: 0.52,
|
|
570
|
+
brickVividMin: 0.55,
|
|
571
|
+
brickCool: -4,
|
|
572
|
+
brickCoolGold: -3,
|
|
573
|
+
brickDesat: 0.85,
|
|
574
|
+
brickExtraDeep: 0.02,
|
|
575
|
+
// red's usable zones (her density map, error-range-checks.json): deep core or the light
|
|
576
|
+
// edge tier — the .50-.58 middle is ring territory, canonical itself lives there, so a
|
|
577
|
+
// lightened brand ALWAYS gets a deep-core red. Cool-first beside warm brands.
|
|
578
|
+
coreL: [0.49, 0.47, 0.45],
|
|
579
|
+
edgeL: [0.65, 0.68, 0.71, 0.75],
|
|
580
|
+
redHuesWarmBrand: [28, 24, 33.3, 38],
|
|
581
|
+
redHuesMagentaBrand: [33.3, 38, 28],
|
|
582
|
+
redHueMagentaDh: -15
|
|
583
|
+
};
|
|
584
|
+
function redSolveDist(c, red) {
|
|
585
|
+
const dh = (c.H - red.H + 540) % 360 - 180;
|
|
586
|
+
const meanC = 2 * Math.sqrt(Math.max(0, c.C) * Math.max(0, red.C));
|
|
587
|
+
const arcMag = meanC * Math.sin(Math.abs(Math.min(0, dh)) * Math.PI / 360);
|
|
588
|
+
const arcGold = meanC * Math.sin(Math.max(0, dh) * Math.PI / 360);
|
|
589
|
+
return Math.hypot(
|
|
590
|
+
RED_SOLVE.wDark * Math.max(0, red.L - c.L),
|
|
591
|
+
RED_GATE.wLight * Math.max(0, c.L - red.L),
|
|
592
|
+
RED_GATE.wDust * Math.max(0, red.C - c.C),
|
|
593
|
+
Math.max(0, c.C - red.C),
|
|
594
|
+
arcMag,
|
|
595
|
+
RED_GATE.wGoldArc * arcGold
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
function inBrickBand(L, C2, H2) {
|
|
599
|
+
const h = (H2 % 360 + 360) % 360;
|
|
600
|
+
return h >= RED_SOLVE.brickHLo && h <= RED_SOLVE.brickHHi && L >= RED_SOLVE.brickLLo && L <= RED_SOLVE.brickLHi && C2 / Math.max(1e-6, maxChromaAt(L, h)) >= RED_SOLVE.brickVividMin;
|
|
601
|
+
}
|
|
602
|
+
var RED_TORSION_CENTER_H = 35.5;
|
|
603
|
+
var RED_TORSION_SOFTNESS = 3.5;
|
|
604
|
+
var RED_BAND_LO_H = 12;
|
|
605
|
+
var RED_BAND_LO_SOFTNESS = 2;
|
|
606
|
+
var VIVID_C = 0.13;
|
|
607
|
+
var LIGHT_DRIFT_COOL_HI = 88;
|
|
608
|
+
var LIGHT_DRIFT_COOL_RANGE = 16;
|
|
609
|
+
var BRAND_BELL_H = 95;
|
|
610
|
+
var BRAND_BELL_SIGMA = 55;
|
|
611
|
+
var BRAND_BELL_AMOUNT = 0.6;
|
|
612
|
+
var BRAND_BELL_L_HI = 0.95;
|
|
613
|
+
var BRAND_BELL_L_LO = 0.76;
|
|
614
|
+
var BRAND_BELL_RED_H = 33.3;
|
|
615
|
+
var BRAND_BELL_RED_SIGMA = 30;
|
|
616
|
+
var HUE_NOISE_C = 8e-3;
|
|
617
|
+
var MUTED_BLEND_DENOM = 0.55;
|
|
618
|
+
var CREAM_UPPER_H = 105;
|
|
619
|
+
var CREAM_UPPER_SOFTNESS = 5;
|
|
620
|
+
var DEEPER_BAND_H_LO = 55;
|
|
621
|
+
var DEEPER_BAND_H_HI = 100;
|
|
622
|
+
var DEEPER_BAND_H_SOFT = 4;
|
|
623
|
+
var DEEPER_BAND_U_LO = 0.1;
|
|
624
|
+
var DEEPER_BAND_U_HI = 0.7;
|
|
625
|
+
var DEEPER_BAND_U_SOFT = 0.015;
|
|
626
|
+
var DEEPER_STRENGTH = 0.85;
|
|
627
|
+
var RED_COOL_DEG = 10.8;
|
|
628
|
+
function redCoolWeight(brandH) {
|
|
629
|
+
return sigmoid((brandH - RED_BAND_LO_H) / RED_BAND_LO_SOFTNESS) * (1 - sigmoid((brandH - RED_TORSION_CENTER_H) / RED_TORSION_SOFTNESS));
|
|
630
|
+
}
|
|
631
|
+
var RED_PIVOT_H = 33.3;
|
|
632
|
+
var RED_WARM_EXIT_H = 44;
|
|
633
|
+
var RED_WARM_EXIT_SOFTNESS = 3.5;
|
|
634
|
+
var RED_EXIT_FLOOR_H = 30.8;
|
|
635
|
+
var RED_EXIT_FLOOR_SOFTNESS = 0.9;
|
|
636
|
+
var RED_WARM_EXIT_FLOOR_H = 35.3;
|
|
637
|
+
var RED_WARM_EXIT_FLOOR_SOFTNESS = 0.8;
|
|
638
|
+
var RED_PIVOT_EXIT_DEG = 14;
|
|
639
|
+
function redRepelShiftDeg(brandH) {
|
|
640
|
+
if (brandH <= RED_PIVOT_H) {
|
|
641
|
+
const shipped2 = RED_COOL_DEG * redCoolWeight(brandH);
|
|
642
|
+
const floor2 = RED_PIVOT_EXIT_DEG * sigmoid((brandH - RED_EXIT_FLOOR_H) / RED_EXIT_FLOOR_SOFTNESS);
|
|
643
|
+
return -Math.max(shipped2, floor2);
|
|
644
|
+
}
|
|
645
|
+
const shipped = RED_COOL_DEG * (1 - sigmoid((brandH - RED_WARM_EXIT_H) / RED_WARM_EXIT_SOFTNESS));
|
|
646
|
+
const floor = RED_PIVOT_EXIT_DEG * (1 - sigmoid((brandH - RED_WARM_EXIT_FLOOR_H) / RED_WARM_EXIT_FLOOR_SOFTNESS));
|
|
647
|
+
return Math.max(shipped, floor);
|
|
648
|
+
}
|
|
649
|
+
function maxChromaAt(L, H2, gamut = MASTER_GAMUT) {
|
|
650
|
+
return clampChromaToGamut(L, 0.52, H2, gamut);
|
|
651
|
+
}
|
|
652
|
+
var medianCapCache = /* @__PURE__ */ new Map();
|
|
653
|
+
function medianGamutCAt(L, gamut = MASTER_GAMUT) {
|
|
654
|
+
const key = `${L}|${gamut}`;
|
|
655
|
+
const hit = medianCapCache.get(key);
|
|
656
|
+
if (hit !== void 0) return hit;
|
|
657
|
+
const caps = [];
|
|
658
|
+
for (let h = 0; h < 360; h += 5) caps.push(maxChromaAt(L, h, gamut));
|
|
659
|
+
caps.sort((a, b) => a - b);
|
|
660
|
+
const median = caps[Math.floor(caps.length / 2)];
|
|
661
|
+
medianCapCache.set(key, median);
|
|
662
|
+
return median;
|
|
663
|
+
}
|
|
664
|
+
function hexToOklch(hex) {
|
|
665
|
+
const h = hex.replace("#", "");
|
|
666
|
+
const full = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
667
|
+
const r = parseInt(full.slice(0, 2), 16) / 255;
|
|
668
|
+
const g2 = parseInt(full.slice(2, 4), 16) / 255;
|
|
669
|
+
const b = parseInt(full.slice(4, 6), 16) / 255;
|
|
670
|
+
const lin = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
671
|
+
const rl = lin(r), gl = lin(g2), bl = lin(b);
|
|
672
|
+
const lms_l = 0.412165612 * rl + 0.536275208 * gl + 0.0514575653 * bl;
|
|
673
|
+
const lms_m = 0.211859107 * rl + 0.6807189584 * gl + 0.107406579 * bl;
|
|
674
|
+
const lms_s = 0.0883097947 * rl + 0.2818474174 * gl + 0.6302613616 * bl;
|
|
675
|
+
const l_ = Math.cbrt(lms_l), m_ = Math.cbrt(lms_m), s_ = Math.cbrt(lms_s);
|
|
676
|
+
const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;
|
|
677
|
+
const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_;
|
|
678
|
+
const bv = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_;
|
|
679
|
+
return { L, C: Math.sqrt(a * a + bv * bv), H: (Math.atan2(bv, a) * 180 / Math.PI + 360) % 360 };
|
|
680
|
+
}
|
|
681
|
+
function oklchToSrgbUnclamped(L, C2, H2) {
|
|
682
|
+
const [rl, gl, bl] = oklchToLinearRgb(L, C2, H2);
|
|
683
|
+
const gm2 = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
684
|
+
return { r: gm2(rl), g: gm2(gl), b: gm2(bl) };
|
|
685
|
+
}
|
|
686
|
+
function srgbEmitChannels(s) {
|
|
687
|
+
const c = clampChromaToGamut(s.L, s.C, s.H, "srgb");
|
|
688
|
+
return oklchToSrgbUnclamped(s.L, c, s.H);
|
|
689
|
+
}
|
|
690
|
+
var masterEmitChannels = (s) => encodedChannels(s.L, s.C, s.H, MASTER_GAMUT);
|
|
691
|
+
function applyChromaFloor(C2, multiplier, floorBase, floorStrength) {
|
|
692
|
+
const raw = C2 * multiplier;
|
|
693
|
+
if (floorStrength <= 0) return raw;
|
|
694
|
+
return Math.max(raw, floorBase * floorStrength);
|
|
695
|
+
}
|
|
696
|
+
var DARK_FLOOR_FULL_C = 0.022;
|
|
697
|
+
var DARK_FLOOR_MUTED_MAX_C = 0.04;
|
|
698
|
+
function makeStop(stop, L, C2, H2, gamut = MASTER_GAMUT) {
|
|
699
|
+
const gamutC = clampChromaToGamut(L, C2, H2, gamut);
|
|
700
|
+
const [r, g2, b] = encodedChannels(L, gamutC, H2, gamut);
|
|
701
|
+
return { stop, L, C: gamutC, H: H2, r, g: g2, b };
|
|
702
|
+
}
|
|
703
|
+
function onTextIsWhite(Y2, L, C2, H2, enforce, ratioFloor) {
|
|
704
|
+
let white = Math.abs(apcaLc(1, Y2)) >= Math.abs(apcaLc(0, Y2));
|
|
705
|
+
if (enforce) {
|
|
706
|
+
if (white && legalRatio(L, C2, H2, 1) < 4.5) {
|
|
707
|
+
if (legalRatio(L, C2, H2, 0) >= 4.5 && Math.abs(apcaLc(0, Y2)) >= 45) white = false;
|
|
708
|
+
} else if (!white && legalRatio(L, C2, H2, 0) < 4.5) {
|
|
709
|
+
if (legalRatio(L, C2, H2, 1) >= 4.5 && Math.abs(apcaLc(1, Y2)) >= 45) white = true;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (ratioFloor !== void 0) {
|
|
713
|
+
const chosen = white ? legalRatio(L, C2, H2, 1) : legalRatio(L, C2, H2, 0);
|
|
714
|
+
if (chosen < ratioFloor) white = !white;
|
|
715
|
+
}
|
|
716
|
+
return white;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/engine/perceptualL.ts
|
|
720
|
+
var Yn = 1;
|
|
721
|
+
function oklchToXyz2(L, C2, H2, gamut) {
|
|
722
|
+
if (gamut === "p3") return oklchToXyz(L, C2, H2);
|
|
723
|
+
const [r, g2, b] = oklchToLinearRgb(L, C2, H2);
|
|
724
|
+
const R2 = Math.max(0, r), G2 = Math.max(0, g2), B2 = Math.max(0, b);
|
|
725
|
+
return [
|
|
726
|
+
0.4123907993 * R2 + 0.3575843394 * G2 + 0.1804807884 * B2,
|
|
727
|
+
0.2126390059 * R2 + 0.7151686788 * G2 + 0.0721923154 * B2,
|
|
728
|
+
0.0193308187 * R2 + 0.1191947798 * G2 + 0.9505321522 * B2
|
|
729
|
+
];
|
|
730
|
+
}
|
|
731
|
+
var labF = (t) => t > (6 / 29) ** 3 ? Math.cbrt(t) : t / (3 * (6 / 29) ** 2) + 4 / 29;
|
|
732
|
+
var lstarFromY = (Y2) => 116 * labF(Y2 / Yn) - 16;
|
|
733
|
+
function xyzToUv(X, Y2, Z2) {
|
|
734
|
+
const d = X + 15 * Y2 + 3 * Z2;
|
|
735
|
+
return d === 0 ? [0, 0] : [4 * X / d, 9 * Y2 / d];
|
|
736
|
+
}
|
|
737
|
+
var La = 65;
|
|
738
|
+
var K_Br = 0.2717 * (6.469 + 6.362 * La ** 0.4495) / (6.469 + La ** 0.4495);
|
|
739
|
+
var nayQ = (t) => -0.01585 - 0.03017 * Math.cos(t) - 0.04556 * Math.cos(2 * t) - 0.02667 * Math.cos(3 * t) - 295e-5 * Math.cos(4 * t) + 0.14592 * Math.sin(t) + 0.05084 * Math.sin(2 * t) - 0.019 * Math.sin(3 * t) - 764e-5 * Math.sin(4 * t);
|
|
740
|
+
var WP = [0.31271, 0.32902];
|
|
741
|
+
var wpUv = xyzToUv(WP[0], WP[1], 1 - WP[0] - WP[1]);
|
|
742
|
+
function nayGamma(X, Y2, Z2, kQ) {
|
|
743
|
+
const [u, v2] = xyzToUv(X, Y2, Z2);
|
|
744
|
+
const sUv = 13 * Math.hypot(u - wpUv[0], v2 - wpUv[1]);
|
|
745
|
+
return 0.4462 * (1 + (kQ * nayQ(Math.atan2(v2 - wpUv[1], u - wpUv[0])) + 0.0872 * K_Br) * sUv + 0.3086) ** 3;
|
|
746
|
+
}
|
|
747
|
+
var GAMMA_WHITE = 0.5 * (nayGamma(WP[0], WP[1], 1 - WP[0] - WP[1], -0.866) + nayGamma(WP[0], WP[1], 1 - WP[0] - WP[1], -0.134));
|
|
748
|
+
function apparentL(L, C2, H2, gamut = MASTER_GAMUT) {
|
|
749
|
+
const [X, Y2, Z2] = oklchToXyz2(L, C2, H2, gamut);
|
|
750
|
+
return lstarFromY(Y2 * (0.5 * (nayGamma(X, Y2, Z2, -0.866) + nayGamma(X, Y2, Z2, -0.134)) / GAMMA_WHITE));
|
|
751
|
+
}
|
|
752
|
+
var grayApparentL = (L, gamut = MASTER_GAMUT) => lstarFromY(wcagY(L, 0, 0, gamut));
|
|
753
|
+
function solveLForApparent(target, C2, H2, gamut = MASTER_GAMUT) {
|
|
754
|
+
let lo = 0.05, hi = 0.999;
|
|
755
|
+
for (let i = 0; i < 34; i++) {
|
|
756
|
+
const m = (lo + hi) / 2;
|
|
757
|
+
apparentL(m, clampChromaToGamut(m, C2, H2, gamut), H2, gamut) < target ? lo = m : hi = m;
|
|
758
|
+
}
|
|
759
|
+
return (lo + hi) / 2;
|
|
760
|
+
}
|
|
761
|
+
function solveCForApparent(L, H2, target, gamut = MASTER_GAMUT) {
|
|
762
|
+
let lo = 0, hi = 0.4;
|
|
763
|
+
for (let i = 0; i < 32; i++) {
|
|
764
|
+
const m = (lo + hi) / 2;
|
|
765
|
+
apparentL(L, clampChromaToGamut(L, m, H2, gamut), H2, gamut) < target ? lo = m : hi = m;
|
|
766
|
+
}
|
|
767
|
+
return (lo + hi) / 2;
|
|
768
|
+
}
|
|
769
|
+
var KEEP_LIGHT = 1;
|
|
770
|
+
var KEEP_DARK = 1;
|
|
771
|
+
var SWEEP = Array.from({ length: 18 }, (_, i) => i * 20);
|
|
772
|
+
var boostCache = /* @__PURE__ */ new Map();
|
|
773
|
+
function meanBoost(rootL, C2, gamut = MASTER_GAMUT) {
|
|
774
|
+
const key = `${rootL}|${C2}|${gamut}`;
|
|
775
|
+
const hit = boostCache.get(key);
|
|
776
|
+
if (hit !== void 0) return hit;
|
|
777
|
+
const gray = grayApparentL(rootL, gamut);
|
|
778
|
+
let s = 0;
|
|
779
|
+
for (const h of SWEEP) s += apparentL(rootL, clampChromaToGamut(rootL, C2, h, gamut), h, gamut) - gray;
|
|
780
|
+
const v2 = s / SWEEP.length;
|
|
781
|
+
boostCache.set(key, v2);
|
|
782
|
+
return v2;
|
|
783
|
+
}
|
|
784
|
+
function perceptualRungL(rootL, C2, H2, keep = KEEP_LIGHT, gamut = MASTER_GAMUT, targetC = C2) {
|
|
785
|
+
return solveLForApparent(grayApparentL(rootL, gamut) + keep * meanBoost(rootL, targetC, gamut), C2, H2, gamut);
|
|
786
|
+
}
|
|
787
|
+
function perceptualDarkC(L, H2, nativeC, keep = KEEP_DARK, gamut = MASTER_GAMUT) {
|
|
788
|
+
if (nativeC <= 0) return 0;
|
|
789
|
+
return solveCForApparent(L, H2, grayApparentL(L, gamut) + keep * meanBoost(L, nativeC, gamut), gamut);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/engine/requirements/spec.ts
|
|
793
|
+
var groupOf = (stop) => stop <= 2 ? "paper" : stop <= 7 ? "wash" : stop === 8 ? "highlight" : "ink";
|
|
794
|
+
var PAPER0_DARK_ROOT_L = 0.16;
|
|
795
|
+
var INK0_SEAM_OVERSHOOT = (ROOT_L_DARK[1] - PAPER0_DARK_ROOT_L) / (ROOT_L_DARK[2] - ROOT_L_DARK[1]);
|
|
796
|
+
var PL_LADDER = { hue: "warm-drift", L: "perceptual", chroma: "ladder" };
|
|
797
|
+
var PL_TEXT = { hue: "warm-drift", L: "perceptual", chroma: "brand" };
|
|
798
|
+
var P_LIFT = { hue: "warm-torsion", L: "perceptual-lift", chroma: "ladder" };
|
|
799
|
+
var P_FIXED = { hue: "warm-torsion", L: "fixed", chroma: "ladder" };
|
|
800
|
+
var P_TEXT = { hue: "warm-torsion", L: "perceptual", chroma: "brand" };
|
|
801
|
+
var S8 = { metric: "wcag", against: "paper-95", target: STOP_8_NONTEXT_CONTRAST, level: "AA" };
|
|
802
|
+
var T9 = { metric: "wcag", against: "paper-97", target: INK_9_CONTRAST, level: "AA" };
|
|
803
|
+
var T10 = { metric: "wcag", against: "wash-80", target: INK_10_CONTRAST, level: "AA" };
|
|
804
|
+
var T11 = { metric: "wcag", against: "paper-97", target: INK_11_CONTRAST_FLOOR, level: "AAA" };
|
|
805
|
+
var ONS = { onFill: { metric: "apca-pole", enforce: true, ratioFloor: 4.5, coEnforceLc: 65 } };
|
|
806
|
+
var LIGHT = {
|
|
807
|
+
stops: [
|
|
808
|
+
// paper-100 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
|
|
809
|
+
{ stop: 0, rootL: 1, group: "paper", produce: { hue: "warm-drift", L: "fixed", chroma: "ladder" }, satFraction: SCALE_C_LIGHT[0].sat, baseC: SCALE_C_LIGHT[0].base },
|
|
810
|
+
// paper/wash/mark-74: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
|
|
811
|
+
// falls out of the shape (see above) — no min-separation require. Only stop 8 carries a require: the WCAG
|
|
812
|
+
// 3:1 vs the resolved paper-97 (paper-2) (re-solves automatically since it references paper-97 (paper-2)).
|
|
813
|
+
...[1, 2, 3, 4, 5, 6, 7, 8].map((stop) => ({
|
|
814
|
+
stop,
|
|
815
|
+
rootL: ROOT_L_LIGHT[stop],
|
|
816
|
+
group: groupOf(stop),
|
|
817
|
+
produce: PL_LADDER,
|
|
818
|
+
satFraction: SCALE_C_LIGHT[stop].sat,
|
|
819
|
+
baseC: SCALE_C_LIGHT[stop].base,
|
|
820
|
+
require: stop === 8 ? S8 : void 0
|
|
821
|
+
})),
|
|
822
|
+
// ink text: perceptual + contrast-required. lead-53 (ink-9) is ALSO the emphasis fill (the
|
|
823
|
+
// highlight-9 collapse, owner 2026-07-29). ink-42 (ink-10) is the between text stop (C49) —
|
|
824
|
+
// a normal stop like its neighbors; the three together are the text-register cta.
|
|
825
|
+
{ stop: 9, rootL: ROOT_L_LIGHT[9], group: "ink", produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[9].inkMult, inkMaxC: SCALE_C_LIGHT[9].inkMaxC, chromaFloor: SCALE_C_LIGHT[9].chromaFloor, require: T9 },
|
|
826
|
+
{ stop: 10, rootL: ROOT_L_LIGHT[10], group: "ink", produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[10].inkMult, inkMaxC: SCALE_C_LIGHT[10].inkMaxC, chromaFloor: SCALE_C_LIGHT[10].chromaFloor, require: T10 },
|
|
827
|
+
{ stop: 11, rootL: ROOT_L_LIGHT[11], group: "ink", produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[11].inkMult, inkMaxC: SCALE_C_LIGHT[11].inkMaxC, chromaFloor: SCALE_C_LIGHT[11].chromaFloor, require: T11 }
|
|
828
|
+
],
|
|
829
|
+
roles: [
|
|
830
|
+
{ role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: 0, chromaMult: 1 },
|
|
831
|
+
{ role: "cta-hover", produce: { hue: "constant", L: "hover", chroma: "brand" }, floorL: 0, chromaMult: 1 },
|
|
832
|
+
{ role: "cta-pressed", produce: { hue: "constant", L: "pressed", chroma: "brand" }, floorL: 0, chromaMult: 1 }
|
|
833
|
+
],
|
|
834
|
+
ons: ONS
|
|
835
|
+
};
|
|
836
|
+
var DARK = {
|
|
837
|
+
stops: [
|
|
838
|
+
// paper-100 (paper-0 pre-Stage-B): the resolved ladder extreme — one seam BELOW paper-99 (paper-1), deep and brand-tinted, never the
|
|
839
|
+
// absolute void (the old hard-coded #000000 was "too much"). Lift applies like the rest of the scale.
|
|
840
|
+
{ stop: 0, rootL: PAPER0_DARK_ROOT_L, group: "paper", produce: P_LIFT, satFraction: SCALE_C_DARK[0].sat },
|
|
841
|
+
// paper/wash 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
|
|
842
|
+
// 3:1 non-text require DECLARED (the Stage-5 flip, owner-approved) — the blue-recede failure is prevented
|
|
843
|
+
// BY RULE, not by patch. The require now genuinely PLACES the stop rather than catching a few low-luminance
|
|
844
|
+
// hues: on the delta-carry path it solves from the sentinel every time, and the old claim that "most hues
|
|
845
|
+
// already clear it from the scaffold and don't move" was only ever true because the 7→8 carry floor had
|
|
846
|
+
// already lifted them past it (owner 2026-07-29 — see the S8 note above).
|
|
847
|
+
...[1, 2, 3, 4, 5, 6, 7, 8].map((stop) => ({
|
|
848
|
+
stop,
|
|
849
|
+
rootL: ROOT_L_DARK[stop],
|
|
850
|
+
group: groupOf(stop),
|
|
851
|
+
produce: stop === 8 ? P_FIXED : P_LIFT,
|
|
852
|
+
satFraction: SCALE_C_DARK[stop].sat,
|
|
853
|
+
require: stop === 8 ? S8 : void 0
|
|
854
|
+
})),
|
|
855
|
+
// ink text: perceptual + the contrast requires DECLARED in dark too (Stage-5 flip): the scaffold already
|
|
856
|
+
// clears them for every hue (the gate proves it), so values don't move — but the guarantee is now a rule.
|
|
857
|
+
{ stop: 9, rootL: ROOT_L_DARK[9], group: "ink", produce: P_TEXT, chromaMult: SCALE_C_DARK[9].inkMult, inkMaxC: SCALE_C_DARK[9].inkMaxC, chromaFloor: SCALE_C_DARK[9].chromaFloor, require: T9 },
|
|
858
|
+
{ stop: 10, rootL: ROOT_L_DARK[10], group: "ink", produce: P_TEXT, chromaMult: SCALE_C_DARK[10].inkMult, inkMaxC: SCALE_C_DARK[10].inkMaxC, chromaFloor: SCALE_C_DARK[10].chromaFloor, require: T10 },
|
|
859
|
+
{ stop: 11, rootL: ROOT_L_DARK[11], group: "ink", produce: P_TEXT, chromaMult: SCALE_C_DARK[11].inkMult, inkMaxC: SCALE_C_DARK[11].inkMaxC, chromaFloor: SCALE_C_DARK[11].chromaFloor, require: T11 }
|
|
860
|
+
],
|
|
861
|
+
roles: [
|
|
862
|
+
{ role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
|
|
863
|
+
{ role: "cta-hover", produce: { hue: "constant", L: "hover", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
|
|
864
|
+
{ role: "cta-pressed", produce: { hue: "constant", L: "pressed", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 }
|
|
865
|
+
],
|
|
866
|
+
ons: ONS
|
|
867
|
+
};
|
|
868
|
+
var MODE_SPECS = { light: LIGHT, dark: DARK };
|
|
869
|
+
|
|
870
|
+
// node_modules/helmlab/dist/index.js
|
|
871
|
+
function o0(n) {
|
|
872
|
+
let t = new Float64Array(9);
|
|
873
|
+
for (let e = 0; e < 3; e++) for (let s = 0; s < 3; s++) t[e * 3 + s] = n[e][s];
|
|
874
|
+
return t;
|
|
875
|
+
}
|
|
876
|
+
function c0(n) {
|
|
877
|
+
let [t, e, s, r, o, c, u, l, a] = n, m = o * a - c * l, f = c * u - r * a, p = r * l - o * u, h = t * m + e * f + s * p;
|
|
878
|
+
if (Math.abs(h) < 1e-15) throw new Error("Singular matrix");
|
|
879
|
+
let i = 1 / h;
|
|
880
|
+
return new Float64Array([m * i, (s * l - e * a) * i, (e * c - s * o) * i, f * i, (t * a - s * u) * i, (s * r - t * c) * i, p * i, (e * u - t * l) * i, (t * o - e * r) * i]);
|
|
881
|
+
}
|
|
882
|
+
function R(n, t, e) {
|
|
883
|
+
return n < t ? t : n > e ? e : n;
|
|
884
|
+
}
|
|
885
|
+
var f0 = { L: [0.067389147, 0.0936969212, 0.1139730201, 0.1311363925, 0.1463042003, 0.1600463499, 0.1727006257, 0.1844870084, 0.1955584048, 0.2060263169, 0.2495294013, 0.2860835212, 0.3179694509, 0.3464074906, 0.3721487066, 0.3956983432, 0.4174178992, 0.437577996, 0.4563883385, 0.4740158912, 0.490596514, 0.5062427405, 0.5210491698, 0.5350963168, 0.5484534356, 0.5611806359, 0.5733305003, 0.5849493431, 0.5960782028, 0.6148144235, 0.6322858997, 0.6486409329, 0.6640041118, 0.6784810883, 0.6921621831, 0.7051251578, 0.7174373816, 0.7291575473, 0.7403370493, 0.7510210992, 0.7612496392, 0.7710580962, 0.7804780082, 0.7895375483, 0.7982619668, 0.8066739644, 0.8147940103, 0.8226406142, 0.8302305594, 0.8375791034, 0.8447001527, 0.8516064125, 0.8583095191, 0.8648201537, 0.8711481432, 0.8773025482, 0.8832917407, 0.8891234725, 0.8948049352, 0.9003428149, 0.9057433385, 0.9110123171, 0.9161551832, 0.9211770246, 0.9260826148, 0.9308764394, 0.935562721, 0.9401454409, 0.9446283586, 0.9490150296, 0.9533088214, 0.9575129281, 0.9616303835, 0.9656640729, 0.9696167442, 0.9734910172, 0.9772893933, 0.981014263, 0.9846679139, 0.9882525372, 0.991770234, 0.9952230212, 0.9986128365, 1.0019415437, 1.0052109364, 1.0084227428, 1.0115786292, 1.0146802032, 1.0177290176, 1.0207265727, 1.0236743194, 1.0265736617, 1.0294259591, 1.0322325287, 1.0349946474, 1.0377135538, 1.0403904497, 1.0430265021, 1.0456228446, 1.048180579, 1.0507007763, 1.0531844784, 1.0556326991, 1.058046425, 1.060426617, 1.062774211, 1.0650901187, 1.067375229, 1.0696304082, 1.0718565013, 1.0740543321, 1.0762247049, 1.078368404, 1.0804861952, 1.082578826, 1.0846470261, 1.0866915084, 1.0887129687, 1.0907120871, 1.0926895276, 1.0946459393, 1.0965819561, 1.0984981978, 1.1003952698, 1.1022737639, 1.1041342585, 1.1059773189, 1.1078034977, 1.1096133351, 1.111407359, 1.1131860853, 1.1149500186, 1.1166996516, 1.1184354663, 1.1201579336, 1.1218675134, 1.1235646554, 1.125249799, 1.1269233732, 1.1285857972, 1.1302374804, 1.1318788226, 1.133510986, 1.1351359833, 1.1367543276, 1.1383664763, 1.1399728781, 1.1415739728, 1.1431701916, 1.1447619578, 1.1463496864, 1.1479337848, 1.149514653, 1.1510926832, 1.152668261, 1.1542417647, 1.155813566, 1.15738403, 1.1589535155, 1.1605223748, 1.1620909545, 1.1636595952, 1.1652286316, 1.1667983929, 1.168369203, 1.1699413804, 1.1715152382, 1.1730910848, 1.1746692236, 1.176249953, 1.1778335671, 1.179420355, 1.1810106018, 1.1826045879, 1.1842025897, 1.1858048793, 1.187411725, 1.1890233909, 1.1906401374, 1.1922622212, 1.1938898951, 1.1955234086, 1.1971630076, 1.1988089344, 1.2004614282, 1.2021207249, 1.2037870571, 1.2054606543, 1.2071417429, 1.2088305465, 1.2105272857, 1.212232178, 1.2139454385, 1.2156672792, 1.2173979097, 1.2191375369, 1.2208863649, 1.2226445956, 1.2244124283, 1.2261900598, 1.2279776848, 1.2297754955, 1.2315836817, 1.2334024313, 1.2352319297, 1.2370723604, 1.2389239048, 1.240786742, 1.2426610494, 1.2445470022, 1.2464447738, 1.2483545356, 1.2502764573, 1.2522107065, 1.2541574494, 1.25611685, 1.2580890708, 1.2600742727, 1.2620726148, 1.2640842546, 1.2661093479, 1.2681480491, 1.2702005109, 1.2722668846, 1.27434732, 1.2764419653, 1.2785509674, 1.2806744717, 1.2828126223, 1.2849655618, 1.2871334316, 1.2893163717, 1.2915145207, 1.2937280162, 1.2959569943, 1.29820159, 1.3004619369, 1.3027381676, 1.3050304135, 1.3073388048, 1.3096634705, 1.3120045386, 1.314362136, 1.3167363883, 1.3191274203, 1.3215353557, 1.323960317, 1.3264024258, 1.3288618027, 1.3313385674, 1.3338328384, 1.3363447334, 1.3388743692, 1.3414218615, 1.3514218615, 1.3614218615, 1.3714218615, 1.3814218615, 1.3914218615, 1.4014218615, 1.4114218615, 1.4214218615, 1.4314218615, 1.4414218615, 1.4514218615, 1.4614218615, 1.4714218615, 1.4814218615, 1.4914218615, 1.5014218615, 1.5114218615, 1.5214218615, 1.5314218615, 1.5414218615, 1.5514218615, 1.5614218615, 1.5714218615, 1.5814218615, 1.5914218615, 1.6014218615, 1.6114218615, 1.6214218615, 1.6314218615, 1.6414218615, 1.6514218615, 1.6614218615, 1.6714218615, 1.6814218615, 1.6914218615, 1.7014218615, 1.7114218615, 1.7214218615, 1.7314218615, 1.7414218615, 1.7514218615, 1.7614218615, 1.7714218615, 1.7814218615, 1.7914218615, 1.8014218615, 1.8114218615, 1.8214218615, 1.8314218615, 1.8414218615, 1.8514218615, 1.8614218615, 1.8714218615, 1.8814218615, 1.8914218615, 1.9014218615, 1.9114218615, 1.9214218615, 1.9314218615, 1.9414218615, 1.9514218615, 1.9614218615, 1.9714218615, 1.9814218615, 1.9914218615, 2.0014218615, 2.0114218615, 2.0214218615, 2.0314218615, 2.0414218615, 2.0514218615, 2.0614218615, 2.0714218615, 2.0814218615, 2.0914218615, 2.1014218615, 2.1114218615, 2.1214218615, 2.1314218615, 2.1414218615, 2.1514218615, 2.1614218615, 2.1714218615, 2.1814218615, 2.1914218615, 2.2014218615, 2.2114218615, 2.2214218615, 2.2314218615, 2.2414218615, 2.2514218615, 2.2614218615, 2.2714218615, 2.2814218615, 2.2914218615, 2.3014218615, 2.3114218615, 2.3214218615, 2.3314218615, 2.3414218615, 2.3514218615, 2.3614218615, 2.3714218615, 2.3814218615, 2.3914218615, 2.4014218615, 2.4114218615, 2.4214218615, 2.4314218615, 2.4414218615, 2.4514218615, 2.4614218615, 2.4714218615, 2.4814218615, 2.4914218615, 2.5014218615, 2.5114218615, 2.5214218615, 2.5314218615, 2.5414218615, 2.5514218615, 2.5614218615, 2.5714218615, 2.5814218615, 2.5914218615], a_err: [0.0398307431, 0.0521727911, 0.0609726013, 0.0680124892, 0.0739539746, 0.0791271549, 0.0837242615, 0.0878686523, 0.0916449929, 0.095114343, 0.1085186828, 0.1185579198, 0.1264152933, 0.1327111431, 0.1378217341, 0.141997189, 0.1454145387, 0.1482048893, 0.1504686967, 0.1522849858, 0.1537172354, 0.1548173064, 0.1556281646, 0.1561858256, 0.1565207819, 0.1566590731, 0.1566231036, 0.1564322782, 0.1561035033, 0.1552204387, 0.154015687, 0.1525462581, 0.1508586625, 0.1489912263, 0.1469758069, 0.1448390868, 0.1426035625, 0.1402883118, 0.1379095939, 0.1354813257, 0.1330154622, 0.1305223043, 0.1280107485, 0.1254884926, 0.1229622057, 0.120437669, 0.1179198951, 0.1154132278, 0.1129214271, 0.1104477415, 0.1079949709, 0.1055655196, 0.1031614435, 0.10078449, 0.098436134, 0.0961176085, 0.0938299319, 0.0915739326, 0.0893502698, 0.0871594527, 0.0850018573, 0.0828777414, 0.0807872579, 0.0787304669, 0.0767073466, 0.0747178023, 0.0727616757, 0.0708387523, 0.0689487685, 0.0670914178, 0.0652663562, 0.0634732078, 0.0617115688, 0.0599810122, 0.0582810907, 0.0566113408, 0.0549712852, 0.053360436, 0.0517782966, 0.0502243641, 0.0486981313, 0.0471990882, 0.0457267237, 0.0442805271, 0.0428599889, 0.0414646022, 0.0400938638, 0.0387472746, 0.0374243406, 0.0361245737, 0.0348474919, 0.0335926199, 0.0323594899, 0.0311476413, 0.0299566216, 0.0287859862, 0.0276352989, 0.0265041319, 0.0253920659, 0.0242986903, 0.0232236033, 0.0221664118, 0.0211267313, 0.0201041862, 0.0190984096, 0.0181090431, 0.017135737, 0.0161781501, 0.0152359496, 0.0143088109, 0.0133964176, 0.0124984617, 0.0116146427, 0.0107446682, 0.0098882534, 0.0090451209, 0.0082150011, 0.0073976312, 0.0065927557, 0.0058001261, 0.0050195006, 0.0042506442, 0.0034933282, 0.0027473305, 2012435e-9, 0.0012884316, 5751163e-10, -1277092e-10, -8202376e-10, -0.0015026562, -2175147e-9, -0.0028378867, -0.0034910469, -0.0041347946, -0.0047692918, -0.0053946958, -0.0060111596, -0.0066188316, -0.0072178563, -0.0078083736, -0.0083905199, -0.0089644272, -0.0095302069, -0.0100879419, -0.0106377402, -0.0111797071, -0.0117139443, -0.01224055, -0.0127596188, -0.0132712422, -0.0137755084, -0.0142725025, -0.0147623068, -0.0152450004, -0.0157206597, -0.0161893584, -0.0166511676, -0.0171061557, -0.0175543887, -0.0179959303, -0.0184308418, -0.0188591821, -0.0192810082, -0.0196963749, -0.0201053349, -0.020507939, -0.0209042361, -0.0212942733, -0.0216780958, -0.0220557473, -0.0224272697, -0.0227927033, -0.023152087, -0.023505458, -0.0238528524, -0.0241943046, -0.0245298478, -0.0248595141, -0.0251833341, -0.0255013374, -0.0258135524, -0.0261200065, -0.026420726, -0.0267157362, -0.0270050615, -0.0272887253, -0.0275667502, -0.027839158, -0.0281059698, -0.0283672057, -0.0286228852, -0.0288730273, -0.0291176501, -0.0293567712, -0.0295904078, -0.0298185764, -0.030041293, -0.0302585731, -0.0304704319, -0.0306768842, -0.0308779443, -0.0310736263, -0.0312639439, -0.0314489106, -0.0316285396, -0.0318028438, -0.0319718362, -0.0321355294, -0.0322939359, -0.032447068, -0.0325949383, -0.0327375589, -0.0328749422, -0.0330071004, -0.0331340458, -0.0332557907, -0.0333723477, -0.0334837291, -0.0335899477, -0.0336910163, -0.0337869478, -0.0338777555, -0.0339634525, -0.0340440527, -0.0341195698, -0.034190018, -0.0342554117, -0.0343157658, -0.0343710953, -0.0344214158, -0.034466743, -0.0345070933, -0.0345424833, -0.0345729301, -0.0345984514, -0.0346190651, -0.0346347899, -0.0346456447, -0.0346516491, -0.0346528233, -0.0346491879, -0.0346407642, -0.034627574, -0.0346096397, -0.0345869845, -0.034559632, -0.0345276065, -0.0344909331, -0.0344496375, -0.0344037459, -0.0343532855, -0.034298284, -0.0342387699, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724, -0.0341747724], b_err: [0.020441741, 0.0292590046, 0.0362118033, 0.0421833406, 0.0475165436, 0.0523881168, 0.0569036356, 0.0611323276, 0.0651226051, 0.0689099677, 0.0847731569, 0.0982116849, 0.1099776805, 0.1204814155, 0.1299798931, 0.1386488022, 0.1466156802, 0.1539772359, 0.1608092534, 0.1671726571, 0.1731174326, 0.1786852753, 0.183911446, 0.1888261104, 0.193455332, 0.1978218237, 0.2019455286, 0.2058440755, 0.2095331415, 0.2156330683, 0.2211797884, 0.2262309468, 0.2308354423, 0.2350352163, 0.2388666008, 0.2423613501, 0.2455474409, 0.2484497011, 0.2510903082, 0.2534891895, 0.2556643451, 0.2576321115, 0.2594073783, 0.2610037679, 0.2624337849, 0.2637089424, 0.2648398684, 0.2658363969, 0.2667076454, 0.2674620828, 0.2681075875, 0.2686514987, 0.2691006616, 0.2694614669, 0.2697398868, 0.2699415059, 0.2700715505, 0.2701349138, 0.2701361792, 0.2700796417, 0.2699693274, 0.2698090107, 0.2696022311, 0.2693523083, 0.2690623554, 0.2687352928, 0.2683738592, 0.2679806232, 0.2675579937, 0.267108229, 0.2666334467, 0.2661356312, 0.2656166422, 0.265078222, 0.264522002, 0.2639495097, 0.2633621748, 0.2627613344, 0.2621482391, 0.2615240576, 0.2608898814, 0.2602467299, 0.2595955538, 0.2589372398, 0.258272614, 0.2576024453, 0.2569274492, 0.2562482905, 0.2555655865, 0.2548799096, 0.2541917902, 0.2535017189, 0.2528101491, 0.252117499, 0.2514241539, 0.2507304678, 0.2500367657, 0.249343345, 0.2486504776, 0.2479584107, 0.2472673692, 0.2465775563, 0.2458891552, 0.2452023305, 0.2445172287, 0.2438339802, 0.2431526997, 0.2424734873, 0.2417964297, 0.2411216006, 0.2404490622, 0.2397788652, 0.2391110503, 0.2384456481, 0.2377826807, 0.2371221615, 0.2364640962, 0.2358084831, 0.2351553141, 0.2345045747, 0.2338562446, 0.2332102984, 0.2325667057, 0.2319254315, 0.2312864371, 0.2306496796, 0.2300151129, 0.2293826878, 0.2287523523, 0.2281240519, 0.2274977298, 0.2268733273, 0.226250784, 0.2256300378, 0.2250110255, 0.2243936828, 0.2237779442, 0.223163744, 0.2225510155, 0.221939692, 0.2213297063, 0.2207209912, 0.2201130808, 0.2195050657, 0.2188968085, 0.2182881966, 0.217679118, 0.2170694619, 0.2164591184, 0.2158479785, 0.2152359343, 0.2146228791, 0.2140087071, 0.2133933138, 0.2127765958, 0.2121584509, 0.2115387781, 0.2109174779, 0.2102944517, 0.2096696024, 0.2090428344, 0.2084140531, 0.2077831654, 0.2071500798, 0.2065147058, 0.2058769547, 0.205236739, 0.2045939727, 0.2039485713, 0.2033004517, 0.2026495325, 0.2019957334, 0.2013389761, 0.2006791834, 0.20001628, 0.199350192, 0.1986808469, 0.198008174, 0.1973321042, 0.1966525698, 0.1959695049, 0.1952828451, 0.1945925277, 0.1938984916, 0.1932006773, 0.1924990272, 0.191793485, 0.1910839964, 0.1903705085, 0.1896529704, 0.1889313327, 0.1882055478, 0.1874755697, 0.1867413544, 0.1860028594, 0.1852600441, 0.1845128695, 0.1837612986, 0.183005296, 0.1822448281, 0.1814798633, 0.1807103716, 0.1799363249, 0.1791576969, 0.1783744631, 0.1775866011, 0.17679409, 0.175996911, 0.1751950471, 0.1743884832, 0.173577206, 0.1727612043, 0.1719404686, 0.1711149916, 0.1702847675, 0.1694497928, 0.1686100658, 0.1677655867, 0.1669163579, 0.1660623834, 0.1652036695, 0.1643402243, 0.1634720579, 0.1625991824, 0.161721612, 0.1608393628, 0.1599524529, 0.1590609025, 0.1581647337, 0.1572639708, 0.15635864, 0.1554487695, 0.1545343897, 0.1536155329, 0.1526922335, 0.1517645279, 0.1508324546, 0.1498960542, 0.1489553692, 0.1480104443, 0.1470613262, 0.1461080635, 0.1451507072, 0.14418931, 0.1432239267, 0.1422546144, 0.1412814321, 0.1403044405, 0.139323703, 0.1383392843, 0.1373512517, 0.1363596742, 0.1353646229, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709, 0.1343661709] };
|
|
886
|
+
var Y = new Float64Array(f0.L);
|
|
887
|
+
var i0 = new Float64Array(f0.a_err);
|
|
888
|
+
var a0 = new Float64Array(f0.b_err);
|
|
889
|
+
var V = Y.length;
|
|
890
|
+
function p1(n) {
|
|
891
|
+
if (n <= Y[0]) return 0;
|
|
892
|
+
if (n >= Y[V - 1]) return V - 2;
|
|
893
|
+
let t = 0, e = V - 1;
|
|
894
|
+
for (; e - t > 1; ) {
|
|
895
|
+
let s = t + e >> 1;
|
|
896
|
+
Y[s] <= n ? t = s : e = s;
|
|
897
|
+
}
|
|
898
|
+
return t;
|
|
899
|
+
}
|
|
900
|
+
function u0(n) {
|
|
901
|
+
if (n <= 0) return [0, 0];
|
|
902
|
+
if (n < Y[0]) {
|
|
903
|
+
let s = n / Y[0];
|
|
904
|
+
return [i0[0] * s, a0[0] * s];
|
|
905
|
+
}
|
|
906
|
+
let t = p1(n);
|
|
907
|
+
if (t >= V - 1) return [i0[V - 1], a0[V - 1]];
|
|
908
|
+
let e = (n - Y[t]) / (Y[t + 1] - Y[t]);
|
|
909
|
+
return [i0[t] + e * (i0[t + 1] - i0[t]), a0[t] + e * (a0[t + 1] - a0[t])];
|
|
910
|
+
}
|
|
911
|
+
var { cos: g, sin: x, sqrt: Z, atan2: C, exp: U, abs: L0, pow: d0, PI: b1 } = Math;
|
|
912
|
+
var $ = class {
|
|
913
|
+
constructor(t, e = {}) {
|
|
914
|
+
this._baseNcL = null;
|
|
915
|
+
this._baseNcA = null;
|
|
916
|
+
this._baseNcB = null;
|
|
917
|
+
this.p = t, this.nc = e.neutralCorrection ?? true;
|
|
918
|
+
let r = (e.abRotateDeg ?? t.raw.display_phi_deg ?? -28.2) * b1 / 180;
|
|
919
|
+
this.rotCos = g(r), this.rotSin = x(r), this.hasRot = L0(r) > 1e-12;
|
|
920
|
+
}
|
|
921
|
+
fromXYZ(t) {
|
|
922
|
+
let e = this.p.raw, s = this.p.M1, r = this.p.M2, o = this.p.gamma, c = s[0] * t[0] + s[1] * t[1] + s[2] * t[2], u = s[3] * t[0] + s[4] * t[1] + s[5] * t[2], l = s[6] * t[0] + s[7] * t[1] + s[8] * t[2], a = c < 0 ? -((-c) ** o[0]) : c ** o[0], m = u < 0 ? -((-u) ** o[1]) : u ** o[1], f = l < 0 ? -((-l) ** o[2]) : l ** o[2], p = r[0] * a + r[1] * m + r[2] * f, h = r[3] * a + r[4] * m + r[5] * f, i = r[6] * a + r[7] * m + r[8] * f;
|
|
923
|
+
if (e.hue_cos1 !== 0 || e.hue_sin1 !== 0 || e.hue_cos2 !== 0 || e.hue_sin2 !== 0 || e.hue_cos3 !== 0 || e.hue_sin3 !== 0 || e.hue_cos4 !== 0 || e.hue_sin4 !== 0) {
|
|
924
|
+
let _ = C(i, h), b = Z(h * h + i * i), d = j0(e, _), L = _ + d;
|
|
925
|
+
h = b * g(L), i = b * x(L);
|
|
926
|
+
}
|
|
927
|
+
if (e.hk_weight !== 0) {
|
|
928
|
+
let _ = Z(h * h + i * i), b = e.hk_weight * d0(_, R(e.hk_power, 0.01, 10));
|
|
929
|
+
if (e.hk_hue_mod !== 0 || e.hk_sin1 !== 0 || e.hk_cos2 !== 0 || e.hk_sin2 !== 0) {
|
|
930
|
+
let d = C(i, h), L = 1 + e.hk_hue_mod * g(d) + e.hk_sin1 * x(d) + e.hk_cos2 * g(2 * d) + e.hk_sin2 * x(2 * d);
|
|
931
|
+
b *= L;
|
|
932
|
+
}
|
|
933
|
+
p += b;
|
|
934
|
+
}
|
|
935
|
+
if (e.L_corr_p1 !== 0 || e.L_corr_p2 !== 0 || e.L_corr_p3 !== 0 || e.Lh_cos1 !== 0 || e.Lh_sin1 !== 0) {
|
|
936
|
+
let _ = e.Lh_cos1 !== 0 || e.Lh_sin1 !== 0 ? C(i, h) : 0;
|
|
937
|
+
p = d1(e, p, _);
|
|
938
|
+
}
|
|
939
|
+
if (e.lp_dark !== 0 || e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0) {
|
|
940
|
+
let _ = e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0 ? C(i, h) : 0;
|
|
941
|
+
p = g1(e, p, _);
|
|
942
|
+
}
|
|
943
|
+
if (e.cs_cos1 !== 0 || e.cs_sin1 !== 0 || e.cs_cos2 !== 0 || e.cs_sin2 !== 0 || e.cs_cos3 !== 0 || e.cs_sin3 !== 0 || e.cs_cos4 !== 0 || e.cs_sin4 !== 0) {
|
|
944
|
+
let _ = C(i, h), b = W0(e, _);
|
|
945
|
+
h *= b, i *= b;
|
|
946
|
+
}
|
|
947
|
+
if (e.cp_cos1 !== 0 || e.cp_sin1 !== 0 || e.cp_cos2 !== 0 || e.cp_sin2 !== 0) {
|
|
948
|
+
let _ = C(i, h), b = Z(h * h + i * i), d = K0(e, _), L = b > 0 ? d0(b, d) : 0;
|
|
949
|
+
h = L * g(_), i = L * x(_);
|
|
950
|
+
}
|
|
951
|
+
if (e.lc1 !== 0 || e.lc2 !== 0) {
|
|
952
|
+
let _ = q0(e, p);
|
|
953
|
+
h *= _, i *= _;
|
|
954
|
+
}
|
|
955
|
+
if (e.hlc_cos1 !== 0 || e.hlc_sin1 !== 0 || e.hlc_cos2 !== 0 || e.hlc_sin2 !== 0) {
|
|
956
|
+
let _ = C(i, h), b = V0(e, _, p);
|
|
957
|
+
h *= b, i *= b;
|
|
958
|
+
}
|
|
959
|
+
if (e.hl_cos1 !== 0 || e.hl_sin1 !== 0 || e.hl_cos2 !== 0 || e.hl_sin2 !== 0) {
|
|
960
|
+
let _ = C(i, h);
|
|
961
|
+
p *= U0(e, _);
|
|
962
|
+
}
|
|
963
|
+
if (this.nc) {
|
|
964
|
+
let [_, b] = u0(p);
|
|
965
|
+
h -= _, i -= b;
|
|
966
|
+
}
|
|
967
|
+
if (this.hasRot) {
|
|
968
|
+
let _ = h * this.rotCos - i * this.rotSin, b = h * this.rotSin + i * this.rotCos;
|
|
969
|
+
h = _, i = b;
|
|
970
|
+
}
|
|
971
|
+
return [p, h, i];
|
|
972
|
+
}
|
|
973
|
+
toXYZ(t) {
|
|
974
|
+
let e = this.p.raw, [s, r, o] = t;
|
|
975
|
+
if (this.hasRot) {
|
|
976
|
+
let _ = r * this.rotCos + o * this.rotSin, b = -r * this.rotSin + o * this.rotCos;
|
|
977
|
+
r = _, o = b;
|
|
978
|
+
}
|
|
979
|
+
if (this.nc) {
|
|
980
|
+
let [_, b] = u0(s);
|
|
981
|
+
r += _, o += b;
|
|
982
|
+
}
|
|
983
|
+
if (e.hl_cos1 !== 0 || e.hl_sin1 !== 0 || e.hl_cos2 !== 0 || e.hl_sin2 !== 0) {
|
|
984
|
+
let _ = C(o, r);
|
|
985
|
+
s /= U0(e, _);
|
|
986
|
+
}
|
|
987
|
+
if (e.hlc_cos1 !== 0 || e.hlc_sin1 !== 0 || e.hlc_cos2 !== 0 || e.hlc_sin2 !== 0) {
|
|
988
|
+
let _ = C(o, r), b = V0(e, _, s);
|
|
989
|
+
r /= b, o /= b;
|
|
990
|
+
}
|
|
991
|
+
if (e.lc1 !== 0 || e.lc2 !== 0) {
|
|
992
|
+
let _ = q0(e, s);
|
|
993
|
+
r /= _, o /= _;
|
|
994
|
+
}
|
|
995
|
+
if (e.cp_cos1 !== 0 || e.cp_sin1 !== 0 || e.cp_cos2 !== 0 || e.cp_sin2 !== 0) {
|
|
996
|
+
let _ = C(o, r), b = Z(r * r + o * o), d = K0(e, _), L = b > 0 ? d0(b, 1 / d) : 0;
|
|
997
|
+
r = L * g(_), o = L * x(_);
|
|
998
|
+
}
|
|
999
|
+
if (e.cs_cos1 !== 0 || e.cs_sin1 !== 0 || e.cs_cos2 !== 0 || e.cs_sin2 !== 0 || e.cs_cos3 !== 0 || e.cs_sin3 !== 0 || e.cs_cos4 !== 0 || e.cs_sin4 !== 0) {
|
|
1000
|
+
let _ = C(o, r), b = W0(e, _);
|
|
1001
|
+
r /= b, o /= b;
|
|
1002
|
+
}
|
|
1003
|
+
if (e.lp_dark !== 0 || e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0) {
|
|
1004
|
+
let _ = e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0 ? C(o, r) : 0;
|
|
1005
|
+
s = x1(e, s, _);
|
|
1006
|
+
}
|
|
1007
|
+
if (e.L_corr_p1 !== 0 || e.L_corr_p2 !== 0 || e.L_corr_p3 !== 0 || e.Lh_cos1 !== 0 || e.Lh_sin1 !== 0) {
|
|
1008
|
+
let _ = e.Lh_cos1 !== 0 || e.Lh_sin1 !== 0 ? C(o, r) : 0;
|
|
1009
|
+
s = L1(e, s, _);
|
|
1010
|
+
}
|
|
1011
|
+
if (e.hk_weight !== 0) {
|
|
1012
|
+
let _ = Z(r * r + o * o), b = e.hk_weight * d0(_, R(e.hk_power, 0.01, 10));
|
|
1013
|
+
if (e.hk_hue_mod !== 0 || e.hk_sin1 !== 0 || e.hk_cos2 !== 0 || e.hk_sin2 !== 0) {
|
|
1014
|
+
let d = C(o, r), L = 1 + e.hk_hue_mod * g(d) + e.hk_sin1 * x(d) + e.hk_cos2 * g(2 * d) + e.hk_sin2 * x(2 * d);
|
|
1015
|
+
b *= L;
|
|
1016
|
+
}
|
|
1017
|
+
s -= b;
|
|
1018
|
+
}
|
|
1019
|
+
if (e.hue_cos1 !== 0 || e.hue_sin1 !== 0 || e.hue_cos2 !== 0 || e.hue_sin2 !== 0 || e.hue_cos3 !== 0 || e.hue_sin3 !== 0 || e.hue_cos4 !== 0 || e.hue_sin4 !== 0) {
|
|
1020
|
+
let _ = C(o, r), b = Z(r * r + o * o), d = _;
|
|
1021
|
+
for (let L = 0; L < 8; L++) {
|
|
1022
|
+
let k = d + j0(e, d) - _, M = 1 + f1(e, d);
|
|
1023
|
+
L0(M) < 1e-10 && (M = 1), d -= k / M;
|
|
1024
|
+
}
|
|
1025
|
+
r = b * g(d), o = b * x(d);
|
|
1026
|
+
}
|
|
1027
|
+
let c = this.p.M2_inv, u = c[0] * s + c[1] * r + c[2] * o, l = c[3] * s + c[4] * r + c[5] * o, a = c[6] * s + c[7] * r + c[8] * o, m = this.p.inv_gamma, f = u < 0 ? -((-u) ** m[0]) : u ** m[0], p = l < 0 ? -((-l) ** m[1]) : l ** m[1], h = a < 0 ? -((-a) ** m[2]) : a ** m[2], i = this.p.M1_inv;
|
|
1028
|
+
return [i[0] * f + i[1] * p + i[2] * h, i[3] * f + i[4] * p + i[5] * h, i[6] * f + i[7] * p + i[8] * h];
|
|
1029
|
+
}
|
|
1030
|
+
baseFromXYZ(t) {
|
|
1031
|
+
let e = this.p.M1, s = this.p.M2, r = this.p.gamma, o = e[0] * t[0] + e[1] * t[1] + e[2] * t[2], c = e[3] * t[0] + e[4] * t[1] + e[5] * t[2], u = e[6] * t[0] + e[7] * t[1] + e[8] * t[2], l = o < 0 ? -((-o) ** r[0]) : o ** r[0], a = c < 0 ? -((-c) ** r[1]) : c ** r[1], m = u < 0 ? -((-u) ** r[2]) : u ** r[2];
|
|
1032
|
+
return [s[0] * l + s[1] * a + s[2] * m, s[3] * l + s[4] * a + s[5] * m, s[6] * l + s[7] * a + s[8] * m];
|
|
1033
|
+
}
|
|
1034
|
+
baseToXYZ(t) {
|
|
1035
|
+
let [e, s, r] = t, o = this.p.M2_inv, c = o[0] * e + o[1] * s + o[2] * r, u = o[3] * e + o[4] * s + o[5] * r, l = o[6] * e + o[7] * s + o[8] * r, a = this.p.inv_gamma, m = c < 0 ? -((-c) ** a[0]) : c ** a[0], f = u < 0 ? -((-u) ** a[1]) : u ** a[1], p = l < 0 ? -((-l) ** a[2]) : l ** a[2], h = this.p.M1_inv;
|
|
1036
|
+
return [h[0] * m + h[1] * f + h[2] * p, h[3] * m + h[4] * f + h[5] * p, h[6] * m + h[7] * f + h[8] * p];
|
|
1037
|
+
}
|
|
1038
|
+
_buildBaseNc() {
|
|
1039
|
+
let e = new Float64Array(256), s = new Float64Array(256), r = new Float64Array(256);
|
|
1040
|
+
for (let o = 0; o < 256; o++) {
|
|
1041
|
+
let c = o / 255, u = this.baseFromXYZ([c * 0.95047, c, c * 1.08883]);
|
|
1042
|
+
e[o] = u[0], s[o] = u[1], r[o] = u[2];
|
|
1043
|
+
}
|
|
1044
|
+
this._baseNcL = e, this._baseNcA = s, this._baseNcB = r;
|
|
1045
|
+
}
|
|
1046
|
+
baseNeutralError(t) {
|
|
1047
|
+
this._baseNcL || this._buildBaseNc();
|
|
1048
|
+
let e = this._baseNcL, s = this._baseNcA, r = this._baseNcB, o = e.length;
|
|
1049
|
+
if (t <= e[0]) return [s[0], r[0]];
|
|
1050
|
+
if (t >= e[o - 1]) return [s[o - 1], r[o - 1]];
|
|
1051
|
+
let c = 0, u = o - 1;
|
|
1052
|
+
for (; u - c > 1; ) {
|
|
1053
|
+
let a = c + u >> 1;
|
|
1054
|
+
e[a] <= t ? c = a : u = a;
|
|
1055
|
+
}
|
|
1056
|
+
let l = (t - e[c]) / (e[c + 1] - e[c]);
|
|
1057
|
+
return [s[c] + l * (s[c + 1] - s[c]), r[c] + l * (r[c + 1] - r[c])];
|
|
1058
|
+
}
|
|
1059
|
+
distance(t, e) {
|
|
1060
|
+
let s = this.p.raw, r = t[0], o = t[1], c = t[2], u = e[0], l = e[1], a = e[2];
|
|
1061
|
+
if (this.nc) {
|
|
1062
|
+
let [h, i] = u0(r), [_, b] = u0(u);
|
|
1063
|
+
o += h, c += i, l += _, a += b;
|
|
1064
|
+
}
|
|
1065
|
+
let m = (r - u) ** 2, f = (o - l) ** 2 + (c - a) ** 2;
|
|
1066
|
+
if (s.dist_sl !== 0) {
|
|
1067
|
+
let h = (r + u) * 0.5, i = 1 + s.dist_sl * (h - 0.5) ** 2;
|
|
1068
|
+
m /= i ** 2;
|
|
1069
|
+
}
|
|
1070
|
+
if (s.dist_sc !== 0) {
|
|
1071
|
+
let h = Z(o ** 2 + c ** 2), i = Z(l ** 2 + a ** 2), _ = 1 + s.dist_sc * (h + i) * 0.5;
|
|
1072
|
+
f /= _ ** 2;
|
|
1073
|
+
}
|
|
1074
|
+
let p;
|
|
1075
|
+
if (s.dist_power !== 1 || s.dist_wC !== 1 ? p = (m + s.dist_wC * f) ** (s.dist_power / 2) : p = Z(m + f), s.dist_compress !== 0) {
|
|
1076
|
+
let h = s.dist_compress;
|
|
1077
|
+
s.dist_linear !== 0 ? p = p * (1 + s.dist_linear * h * p) / (1 + h * p) : p /= 1 + h * p;
|
|
1078
|
+
}
|
|
1079
|
+
return s.dist_post_power !== 1 && (p = p ** s.dist_post_power), p;
|
|
1080
|
+
}
|
|
1081
|
+
distanceFromLab(t, e) {
|
|
1082
|
+
return this.distance(t, e);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
function j0(n, t) {
|
|
1086
|
+
return n.hue_cos1 * g(t) + n.hue_sin1 * x(t) + n.hue_cos2 * g(2 * t) + n.hue_sin2 * x(2 * t) + n.hue_cos3 * g(3 * t) + n.hue_sin3 * x(3 * t) + n.hue_cos4 * g(4 * t) + n.hue_sin4 * x(4 * t);
|
|
1087
|
+
}
|
|
1088
|
+
function f1(n, t) {
|
|
1089
|
+
return -n.hue_cos1 * x(t) + n.hue_sin1 * g(t) + -2 * n.hue_cos2 * x(2 * t) + 2 * n.hue_sin2 * g(2 * t) + -3 * n.hue_cos3 * x(3 * t) + 3 * n.hue_sin3 * g(3 * t) + -4 * n.hue_cos4 * x(4 * t) + 4 * n.hue_sin4 * g(4 * t);
|
|
1090
|
+
}
|
|
1091
|
+
function W0(n, t) {
|
|
1092
|
+
let e = n.cs_cos1 * g(t) + n.cs_sin1 * x(t) + n.cs_cos2 * g(2 * t) + n.cs_sin2 * x(2 * t) + n.cs_cos3 * g(3 * t) + n.cs_sin3 * x(3 * t) + n.cs_cos4 * g(4 * t) + n.cs_sin4 * x(4 * t);
|
|
1093
|
+
return U(e);
|
|
1094
|
+
}
|
|
1095
|
+
function q0(n, t) {
|
|
1096
|
+
let e = t - 0.5, s = n.lc1 * e + n.lc2 * e * e;
|
|
1097
|
+
return U(R(s, -30, 30));
|
|
1098
|
+
}
|
|
1099
|
+
function V0(n, t, e) {
|
|
1100
|
+
let s = n.hlc_cos1 * g(t) + n.hlc_sin1 * x(t) + n.hlc_cos2 * g(2 * t) + n.hlc_sin2 * x(2 * t), r = (e - 0.5) * s;
|
|
1101
|
+
return U(R(r, -30, 30));
|
|
1102
|
+
}
|
|
1103
|
+
function U0(n, t) {
|
|
1104
|
+
let e = n.hl_cos1 * g(t) + n.hl_sin1 * x(t) + n.hl_cos2 * g(2 * t) + n.hl_sin2 * x(2 * t);
|
|
1105
|
+
return U(e);
|
|
1106
|
+
}
|
|
1107
|
+
function K0(n, t) {
|
|
1108
|
+
return 1 + n.cp_cos1 * g(t) + n.cp_sin1 * x(t) + n.cp_cos2 * g(2 * t) + n.cp_sin2 * x(2 * t);
|
|
1109
|
+
}
|
|
1110
|
+
function d1(n, t, e) {
|
|
1111
|
+
let s = t * (1 - t), r = t + n.L_corr_p1 * s + n.L_corr_p2 * s * (0.5 - t) + n.L_corr_p3 * s * s;
|
|
1112
|
+
return (n.Lh_cos1 !== 0 || n.Lh_sin1 !== 0) && (r += s * (n.Lh_cos1 * g(e) + n.Lh_sin1 * x(e))), r;
|
|
1113
|
+
}
|
|
1114
|
+
function L1(n, t, e) {
|
|
1115
|
+
let s = n.Lh_cos1 !== 0 || n.Lh_sin1 !== 0 ? n.Lh_cos1 * g(e) + n.Lh_sin1 * x(e) : 0, r = t;
|
|
1116
|
+
for (let o = 0; o < 15; o++) {
|
|
1117
|
+
let c = r * (1 - r), u = 1 - 2 * r, l = r + (n.L_corr_p1 + s) * c + n.L_corr_p2 * c * (0.5 - r) + n.L_corr_p3 * c * c - t, a = 1 + (n.L_corr_p1 + s) * u + n.L_corr_p2 * (u * (0.5 - r) - c) + n.L_corr_p3 * 2 * c * u;
|
|
1118
|
+
L0(a) < 1e-10 && (a = 1), r -= l / a;
|
|
1119
|
+
}
|
|
1120
|
+
return r;
|
|
1121
|
+
}
|
|
1122
|
+
function g1(n, t, e) {
|
|
1123
|
+
let s = n.lp_dark;
|
|
1124
|
+
(n.lp_dark_hcos !== 0 || n.lp_dark_hsin !== 0) && (s += n.lp_dark_hcos * g(e) + n.lp_dark_hsin * x(e));
|
|
1125
|
+
let r = t < 1 ? 1 - t : 0, o = s * t * r * r;
|
|
1126
|
+
return t * U(R(o, -30, 30));
|
|
1127
|
+
}
|
|
1128
|
+
function x1(n, t, e) {
|
|
1129
|
+
let s = n.lp_dark;
|
|
1130
|
+
(n.lp_dark_hcos !== 0 || n.lp_dark_hsin !== 0) && (s += n.lp_dark_hcos * g(e) + n.lp_dark_hsin * x(e));
|
|
1131
|
+
let r = t;
|
|
1132
|
+
for (let o = 0; o < 12; o++) {
|
|
1133
|
+
let c = r < 1 ? 1 - r : 0, u = s * r * c * c, l = U(R(u, -30, 30)), a = r * l - t, m = s * c * (1 - 3 * r), f = l * (1 + r * m);
|
|
1134
|
+
L0(f) < 1e-10 && (f = 1), r -= a / f;
|
|
1135
|
+
}
|
|
1136
|
+
return r;
|
|
1137
|
+
}
|
|
1138
|
+
var { cos: y, sin: w, sqrt: B, atan2: F, exp: D, abs: v, PI: S } = Math;
|
|
1139
|
+
var k1 = 0.95047;
|
|
1140
|
+
var M1 = 1;
|
|
1141
|
+
var w1 = 1.08883;
|
|
1142
|
+
var _0 = class {
|
|
1143
|
+
constructor(t, e = {}) {
|
|
1144
|
+
this.ncL = null;
|
|
1145
|
+
this.ncA = null;
|
|
1146
|
+
this.ncB = null;
|
|
1147
|
+
this.p = t, this.nc = e.neutralCorrection ?? false;
|
|
1148
|
+
}
|
|
1149
|
+
fromXYZ(t) {
|
|
1150
|
+
let e = this.p.raw, s = this.p.M1, r = this.p.M2, o = this.p.gamma, c = Math.max(s[0] * t[0] + s[1] * t[1] + s[2] * t[2], 0), u = Math.max(s[3] * t[0] + s[4] * t[1] + s[5] * t[2], 0), l = Math.max(s[6] * t[0] + s[7] * t[1] + s[8] * t[2], 0), a, m, f;
|
|
1151
|
+
if (e.transfer === "depcubic") a = w0(c, e.depcubic_alpha), m = w0(u, e.depcubic_alpha), f = w0(l, e.depcubic_alpha);
|
|
1152
|
+
else if (e.transfer === "softcbrt") {
|
|
1153
|
+
let _ = e.softcbrt_eps, b = _ ** (1 / 3);
|
|
1154
|
+
a = (c + _) ** (1 / 3) - b, m = (u + _) ** (1 / 3) - b, f = (l + _) ** (1 / 3) - b;
|
|
1155
|
+
} else a = c ** o[0], m = u ** o[1], f = l ** o[2];
|
|
1156
|
+
if (e.transfer === "depcubic") {
|
|
1157
|
+
let _ = (a + m + f) / 3, b = Math.max(a, m, f), d = Math.min(a, m, f), L = (b - d) / Math.max(v(_), 1e-30), k = D(-((L / 1e-5) ** 2));
|
|
1158
|
+
a = a + k * (_ - a), m = m + k * (_ - m), f = f + k * (_ - f);
|
|
1159
|
+
}
|
|
1160
|
+
let p = r[0] * a + r[1] * m + r[2] * f, h = r[3] * a + r[4] * m + r[5] * f, i = r[6] * a + r[7] * m + r[8] * f;
|
|
1161
|
+
if (e.hue_L_amp !== 0) {
|
|
1162
|
+
let _ = B(h * h + i * i);
|
|
1163
|
+
if (_ > 1e-10) {
|
|
1164
|
+
let b = F(i, h), d = F(w(b - e.hue_L_center), y(b - e.hue_L_center)), L = D(-((d / e.hue_L_width) ** 2)) * _ / (_ + 0.01), k = Math.max(0, p - e.hue_L_knee);
|
|
1165
|
+
p -= e.hue_L_amp * L * k;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (e.hue_cos1 !== 0 || e.hue_sin1 !== 0 || e.hue_cos2 !== 0 || e.hue_sin2 !== 0 || e.hue_cos3 !== 0 || e.hue_sin3 !== 0 || e.hue_cos4 !== 0 || e.hue_sin4 !== 0) {
|
|
1169
|
+
let _ = F(i, h), b = B(h * h + i * i), d = J0(e, _), L = _ + d;
|
|
1170
|
+
h = b * y(L), i = b * w(L);
|
|
1171
|
+
}
|
|
1172
|
+
if (e.chroma_power !== 1) {
|
|
1173
|
+
let _ = B(h * h + i * i);
|
|
1174
|
+
if (_ > 1e-12) {
|
|
1175
|
+
let b = Math.pow(_, e.chroma_power - 1);
|
|
1176
|
+
h *= b, i *= b;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (e.L_corr_pw && e.L_corr_pw.length > 0 ? p = T1(e, p) : (e.L_corr_p1 !== 0 || e.L_corr_p2 !== 0 || e.L_corr_p3 !== 0) && (p = C1(e, p)), e.enrichment && e.enrichment.type === "L_gated_hue" && v(e.enrichment.amp) > 1e-10 && ([h, i] = A1(e, p, h, i)), e.lp_dark !== 0 || e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0) {
|
|
1180
|
+
let _ = e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0 ? F(i, h) : 0;
|
|
1181
|
+
p = R1(e, p, _);
|
|
1182
|
+
}
|
|
1183
|
+
if (e.lc1 !== 0 || e.lc2 !== 0) {
|
|
1184
|
+
let _ = Q0(e, p);
|
|
1185
|
+
h *= _, i *= _;
|
|
1186
|
+
}
|
|
1187
|
+
if (this.nc) {
|
|
1188
|
+
let [_, b] = this.neutralError(p);
|
|
1189
|
+
h -= _, i -= b;
|
|
1190
|
+
}
|
|
1191
|
+
return [p, h, i];
|
|
1192
|
+
}
|
|
1193
|
+
toXYZ(t) {
|
|
1194
|
+
let e = this.p.raw, [s, r, o] = t;
|
|
1195
|
+
if (this.nc) {
|
|
1196
|
+
let [i, _] = this.neutralError(s);
|
|
1197
|
+
r += i, o += _;
|
|
1198
|
+
}
|
|
1199
|
+
if (e.lc1 !== 0 || e.lc2 !== 0) {
|
|
1200
|
+
let i = Q0(e, s);
|
|
1201
|
+
r /= i, o /= i;
|
|
1202
|
+
}
|
|
1203
|
+
if (e.lp_dark !== 0 || e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0) {
|
|
1204
|
+
let i = e.lp_dark_hcos !== 0 || e.lp_dark_hsin !== 0 ? F(o, r) : 0;
|
|
1205
|
+
s = S1(e, s, i);
|
|
1206
|
+
}
|
|
1207
|
+
if (e.enrichment && e.enrichment.type === "L_gated_hue" && v(e.enrichment.amp) > 1e-10 && ([r, o] = F1(e, s, r, o)), e.L_corr_pw && e.L_corr_pw.length > 0 ? s = G1(e, s) : (e.L_corr_p1 !== 0 || e.L_corr_p2 !== 0 || e.L_corr_p3 !== 0) && (s = y1(e, s)), e.chroma_power !== 1) {
|
|
1208
|
+
let i = B(r * r + o * o);
|
|
1209
|
+
if (i > 1e-12) {
|
|
1210
|
+
let _ = Math.pow(i, 1 / e.chroma_power - 1);
|
|
1211
|
+
r *= _, o *= _;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (e.hue_cos1 !== 0 || e.hue_sin1 !== 0 || e.hue_cos2 !== 0 || e.hue_sin2 !== 0 || e.hue_cos3 !== 0 || e.hue_sin3 !== 0 || e.hue_cos4 !== 0 || e.hue_sin4 !== 0) {
|
|
1215
|
+
let i = F(o, r), _ = B(r * r + o * o), b = i;
|
|
1216
|
+
for (let d = 0; d < 8; d++) {
|
|
1217
|
+
let L = b + J0(e, b) - i, k = 1 + H1(e, b);
|
|
1218
|
+
v(k) < 1e-10 && (k = 1), b -= L / k;
|
|
1219
|
+
}
|
|
1220
|
+
r = _ * y(b), o = _ * w(b);
|
|
1221
|
+
}
|
|
1222
|
+
if (e.hue_L_amp !== 0) {
|
|
1223
|
+
let i = B(r * r + o * o);
|
|
1224
|
+
if (i > 1e-10) {
|
|
1225
|
+
let _ = F(o, r), b = F(w(_ - e.hue_L_center), y(_ - e.hue_L_center)), d = D(-((b / e.hue_L_width) ** 2)) * i / (i + 0.01), L = Math.min(e.hue_L_amp * d, 0.99), k = (s - L * e.hue_L_knee) / (1 - L);
|
|
1226
|
+
k > e.hue_L_knee && (s = k);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
let c = this.p.M2_inv, u = c[0] * s + c[1] * r + c[2] * o, l = c[3] * s + c[4] * r + c[5] * o, a = c[6] * s + c[7] * r + c[8] * o;
|
|
1230
|
+
if (e.transfer === "depcubic") {
|
|
1231
|
+
let i = (u + l + a) / 3, _ = Math.max(u, l, a), b = Math.min(u, l, a), d = (_ - b) / Math.max(v(i), 1e-30), L = D(-((d / 1e-5) ** 2));
|
|
1232
|
+
u = u + L * (i - u), l = l + L * (i - l), a = a + L * (i - a);
|
|
1233
|
+
}
|
|
1234
|
+
let m, f, p;
|
|
1235
|
+
if (e.transfer === "depcubic") m = H0(u, e.depcubic_alpha), f = H0(l, e.depcubic_alpha), p = H0(a, e.depcubic_alpha);
|
|
1236
|
+
else if (e.transfer === "softcbrt") {
|
|
1237
|
+
let i = e.softcbrt_eps, _ = i ** (1 / 3);
|
|
1238
|
+
m = (Math.abs(u) + _) ** 3 - i, f = (Math.abs(l) + _) ** 3 - i, p = (Math.abs(a) + _) ** 3 - i, u < 0 && (m = -m), l < 0 && (f = -f), a < 0 && (p = -p);
|
|
1239
|
+
} else {
|
|
1240
|
+
let i = this.p.inv_gamma;
|
|
1241
|
+
m = Math.max(u, 0) ** i[0], f = Math.max(l, 0) ** i[1], p = Math.max(a, 0) ** i[2];
|
|
1242
|
+
}
|
|
1243
|
+
let h = this.p.M1_inv;
|
|
1244
|
+
return [h[0] * m + h[1] * f + h[2] * p, h[3] * m + h[4] * f + h[5] * p, h[6] * m + h[7] * f + h[8] * p];
|
|
1245
|
+
}
|
|
1246
|
+
buildNcLut() {
|
|
1247
|
+
let e = new Float64Array(256), s = new Float64Array(256), r = new Float64Array(256), o = this.nc;
|
|
1248
|
+
this.nc = false;
|
|
1249
|
+
for (let c = 0; c < 256; c++) {
|
|
1250
|
+
let u = c / 255, l = this.fromXYZ([u * k1, u * M1, u * w1]);
|
|
1251
|
+
e[c] = l[0], s[c] = l[1], r[c] = l[2];
|
|
1252
|
+
}
|
|
1253
|
+
this.nc = o, this.ncL = e, this.ncA = s, this.ncB = r;
|
|
1254
|
+
}
|
|
1255
|
+
neutralError(t) {
|
|
1256
|
+
this.ncL || this.buildNcLut();
|
|
1257
|
+
let e = this.ncL, s = this.ncA, r = this.ncB, o = e.length;
|
|
1258
|
+
if (t <= 0) return [0, 0];
|
|
1259
|
+
if (t < e[0]) {
|
|
1260
|
+
let a = t / e[0];
|
|
1261
|
+
return [s[0] * a, r[0] * a];
|
|
1262
|
+
}
|
|
1263
|
+
if (t >= e[o - 1]) return [s[o - 1], r[o - 1]];
|
|
1264
|
+
let c = 0, u = o - 1;
|
|
1265
|
+
for (; u - c > 1; ) {
|
|
1266
|
+
let a = c + u >> 1;
|
|
1267
|
+
e[a] <= t ? c = a : u = a;
|
|
1268
|
+
}
|
|
1269
|
+
let l = (t - e[c]) / (e[c + 1] - e[c]);
|
|
1270
|
+
return [s[c] + l * (s[c + 1] - s[c]), r[c] + l * (r[c + 1] - r[c])];
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
function J0(n, t) {
|
|
1274
|
+
return n.hue_cos1 * y(t) + n.hue_sin1 * w(t) + n.hue_cos2 * y(2 * t) + n.hue_sin2 * w(2 * t) + n.hue_cos3 * y(3 * t) + n.hue_sin3 * w(3 * t) + n.hue_cos4 * y(4 * t) + n.hue_sin4 * w(4 * t);
|
|
1275
|
+
}
|
|
1276
|
+
function H1(n, t) {
|
|
1277
|
+
return -n.hue_cos1 * w(t) + n.hue_sin1 * y(t) + -2 * n.hue_cos2 * w(2 * t) + 2 * n.hue_sin2 * y(2 * t) + -3 * n.hue_cos3 * w(3 * t) + 3 * n.hue_sin3 * y(3 * t) + -4 * n.hue_cos4 * w(4 * t) + 4 * n.hue_sin4 * y(4 * t);
|
|
1278
|
+
}
|
|
1279
|
+
function C1(n, t) {
|
|
1280
|
+
let e = t * (1 - t);
|
|
1281
|
+
return t + n.L_corr_p1 * e + n.L_corr_p2 * e * (0.5 - t) + n.L_corr_p3 * e * e;
|
|
1282
|
+
}
|
|
1283
|
+
function y1(n, t) {
|
|
1284
|
+
let e = t;
|
|
1285
|
+
for (let s = 0; s < 15; s++) {
|
|
1286
|
+
let r = e * (1 - e), o = 1 - 2 * e, c = e + n.L_corr_p1 * r + n.L_corr_p2 * r * (0.5 - e) + n.L_corr_p3 * r * r - t, u = 1 + n.L_corr_p1 * o + n.L_corr_p2 * (o * (0.5 - e) - r) + n.L_corr_p3 * 2 * r * o;
|
|
1287
|
+
v(u) < 1e-10 && (u = 1), e -= c / u;
|
|
1288
|
+
}
|
|
1289
|
+
return e;
|
|
1290
|
+
}
|
|
1291
|
+
function Q0(n, t) {
|
|
1292
|
+
let e = t - 0.5, s = n.lc1 * e + n.lc2 * e * e;
|
|
1293
|
+
return D(R(s, -30, 30));
|
|
1294
|
+
}
|
|
1295
|
+
function R1(n, t, e) {
|
|
1296
|
+
let s = n.lp_dark;
|
|
1297
|
+
(n.lp_dark_hcos !== 0 || n.lp_dark_hsin !== 0) && (s += n.lp_dark_hcos * y(e) + n.lp_dark_hsin * w(e));
|
|
1298
|
+
let r = s * t * (1 - t) ** 2;
|
|
1299
|
+
return t * D(R(r, -30, 30));
|
|
1300
|
+
}
|
|
1301
|
+
function S1(n, t, e) {
|
|
1302
|
+
let s = n.lp_dark;
|
|
1303
|
+
(n.lp_dark_hcos !== 0 || n.lp_dark_hsin !== 0) && (s += n.lp_dark_hcos * y(e) + n.lp_dark_hsin * w(e));
|
|
1304
|
+
let r = t;
|
|
1305
|
+
for (let o = 0; o < 12; o++) {
|
|
1306
|
+
let c = 1 - r, u = s * r * c * c, l = D(R(u, -30, 30)), a = r * l - t, m = s * c * (1 - 3 * r), f = l * (1 + r * m);
|
|
1307
|
+
v(f) < 1e-10 && (f = 1), r -= a / f;
|
|
1308
|
+
}
|
|
1309
|
+
return r;
|
|
1310
|
+
}
|
|
1311
|
+
function w0(n, t) {
|
|
1312
|
+
let e = B(t / 3), s = n / (2 * e * e * e), r = 2 * e * Math.sinh(Math.asinh(s) / 3), o = r * r * r + t * r - n, c = 3 * r * r + t, u = 6 * r, l = 2 * c * c - o * u;
|
|
1313
|
+
return v(l) > 1e-30 && (r = r - 2 * o * c / l), r;
|
|
1314
|
+
}
|
|
1315
|
+
function H0(n, t) {
|
|
1316
|
+
return n * n * n + t * n;
|
|
1317
|
+
}
|
|
1318
|
+
function z0(n, t, e) {
|
|
1319
|
+
let s = Math.max(0, Math.min(1, (n - t) / (e - t)));
|
|
1320
|
+
return w(S * s) ** 2;
|
|
1321
|
+
}
|
|
1322
|
+
function A1(n, t, e, s) {
|
|
1323
|
+
let r = n.enrichment, o = B(e * e + s * s);
|
|
1324
|
+
if (o < 1e-12) return [e, s];
|
|
1325
|
+
let c = F(s, e), u = z0(t, r.L_lo, r.L_hi), l = r.center_deg * S / 180, a = c - l;
|
|
1326
|
+
a = ((a + S) % (2 * S) + 2 * S) % (2 * S) - S;
|
|
1327
|
+
let m = D(-0.5 * (a / r.sigma) ** 2), f = r.amp * u * m, p = c + f;
|
|
1328
|
+
return [o * y(p), o * w(p)];
|
|
1329
|
+
}
|
|
1330
|
+
function F1(n, t, e, s) {
|
|
1331
|
+
let r = n.enrichment, o = B(e * e + s * s);
|
|
1332
|
+
if (o < 1e-12) return [e, s];
|
|
1333
|
+
let c = F(s, e), u = z0(t, r.L_lo, r.L_hi), l = r.center_deg * S / 180, a = r.sigma * r.sigma, m = c;
|
|
1334
|
+
for (let f = 0; f < 8; f++) {
|
|
1335
|
+
let p = m - l;
|
|
1336
|
+
p = ((p + S) % (2 * S) + 2 * S) % (2 * S) - S;
|
|
1337
|
+
let h = D(-0.5 * (p / r.sigma) ** 2), i = r.amp * u, _ = m + i * h - c, b = h * (-p / a), d = 1 + i * b, L = h * (-1 / a + p * p / (a * a)), k = i * L, M = 2 * d * d - _ * k;
|
|
1338
|
+
v(M) < 1e-30 && (M = 1), m = m - 2 * _ * d / M;
|
|
1339
|
+
}
|
|
1340
|
+
return [o * y(m), o * w(m)];
|
|
1341
|
+
}
|
|
1342
|
+
function T1(n, t) {
|
|
1343
|
+
let e = n.L_corr_pw, s = n.L_corr_pw_step ?? 0.05, r = e.length;
|
|
1344
|
+
if (t < 0 || t > 1) return t;
|
|
1345
|
+
let o = Math.floor(t / s);
|
|
1346
|
+
o >= r + 1 && (o = r), o < 0 && (o = 0);
|
|
1347
|
+
let c = o * s, u = o < r + 1 ? Math.min((o + 1) * s, 1) : 1, l = o === 0 ? 0 : o <= r ? e[o - 1] : 0, a = o + 1 === 0 ? 0 : o + 1 <= r ? e[o] : 0, m = c + l, f = u + a, p = u - c, h = p > 1e-30 ? R((t - c) / p, 0, 1) : 0;
|
|
1348
|
+
return m + h * (f - m);
|
|
1349
|
+
}
|
|
1350
|
+
function G1(n, t) {
|
|
1351
|
+
let e = n.L_corr_pw, s = n.L_corr_pw_step ?? 0.05, r = e.length, o = [0];
|
|
1352
|
+
for (let i = 0; i < r; i++) o.push((i + 1) * s + e[i]);
|
|
1353
|
+
if (o.push(1), t < o[0] || t > o[o.length - 1]) return t;
|
|
1354
|
+
let c = 0, u = o.length - 2;
|
|
1355
|
+
for (; c < u; ) {
|
|
1356
|
+
let i = c + u + 1 >> 1;
|
|
1357
|
+
o[i] <= t ? c = i : u = i - 1;
|
|
1358
|
+
}
|
|
1359
|
+
let l = o[c], a = o[c + 1], m = c * s, f = c < r + 1 ? Math.min((c + 1) * s, 1) : 1, p = a - l, h = p > 1e-30 ? R((t - l) / p, 0, 1) : 0;
|
|
1360
|
+
return m + h * (f - m);
|
|
1361
|
+
}
|
|
1362
|
+
var e1 = { M1: [[0.7212986433113499, 0.45344826541531813, -0.19288975751942616], [-0.788211869495579, 1.795241376757236, 0.0876172451181785], [-0.0917700599912156, 0.45765588659459255, 1.2922045513917677]], gamma: [0.47229813098762524, 0.5149184096354483, 0.5113233386366979], M2: [[-0.26355622180094096, 0.4168322883703174, 0.4926763141656403], [1.8897570508777322, -3.1212232034205774, 1.0421666921060384], [0.3585108617962056, 1.7694028193790368, -1.4120626067695372]], hk_weight: 0.2676231133101982, hk_power: 0.8934892185255707, hk_hue_mod: 0.7173169828841472, L_corr_p1: 0.5385456675962418, L_corr_p2: 0.12508858146241716, L_corr_p3: 0.6768950256217603, cs_cos1: -0.195370576218515, cs_sin1: 0.5330819227283227, cs_cos2: 0.08863325582067766, cs_sin2: 0.9365540137751136, cs_cos3: 0.13789738139719568, cs_sin3: 0.061650260197979936, lc1: -1.5239477450767043, lc2: -1.751157310240011, hk_sin1: 0.6915224124600773, hk_cos2: 0.48647127559605596, hk_sin2: 0.9853124591201782, hue_cos1: -0.02833024015436984, hue_sin1: -0.21131429516166544, hue_cos2: 0.2189784817615645, hue_sin2: -0.06871898981942523, hue_cos3: 0.005506053349515315, hue_sin3: -0.0641329861299175, hlc_cos1: -0.43576378069144767, hlc_sin1: 1.060094063845983, hlc_cos2: 0.47931193034584496, hlc_sin2: -0.2622579649434462, hl_cos1: 0.13610794232685908, hl_sin1: 0.1168702235362288, hl_cos2: -0.01617739641422492, hl_sin2: 0.038145638815030566, cp_cos1: -0.09900209889026965, cp_sin1: 0.059635520647228726, cp_cos2: -0.013586499967803128, cp_sin2: 0.2253393118474472, lp_dark: -0.029053748937210654, lp_dark_hcos: 1.3346761652952872, lp_dark_hsin: -0.1698908144723919, dist_power: 1.9737081170404969, dist_wC: 3.966003089807536, hue_cos4: -0.053592461436994296, hue_sin4: -0.00954137464208059, Lh_cos1: -0.4963251525324449, Lh_sin1: -0.09564696283240552, cs_cos4: 0.0641970862504494, cs_sin4: -0.027401052793571013, dist_nl: 0, dist_sat: 0, dist_compress: 52.473130649294724, dist_linear: 0, dist_post_power: 0.47897301074925214, dist_sl: -0.9155125151657894, dist_sc: 2.9268353744941558, dist_sl_hcos1: 0, dist_sl_hsin1: 0, dist_sl_hcos2: 0, dist_sl_hsin2: 0, dist_sc_hcos1: 0, dist_sc_hsin1: 0, dist_sc_hcos2: 0, dist_sc_hsin2: 0, hk_weight_S: 0, hk_power_S: 0, hk_hue_S: 0, lp_dark_S: 0, lp_dark_S2: 0, cs_S_lin: 0, cs_S_quad: 0, lc_S_lin: 0, lc_S_quad: 0, hl_S_lin: 0, L_S_offset: 0, display_phi_deg: -28.2 };
|
|
1363
|
+
var t1 = { M1: [[0.8154374735648701, 0.3603221491264266, -0.12432703417946676], [0.03298391207546648, 0.9292940788255503, 0.03614494665290377], [0.048184113668356454, 0.26427748135788043, 0.6336388271114471]], M2: [[0.21193779684470104, 0.7992121834263127, -0.00410075161564345], [2.4672018828033475, -2.9877348024830788, 0.520532919679731], [-0.11390787868068575, 1.3932982808117473, -1.279390402131062]], transfer: "depcubic", depcubic_alpha: 0.021, L_corr_pw: [-0.040505986477810375, -0.07435430161969014, -0.09474033834131092, -0.09425098468772593, -0.08944146679273975, -0.0859403510700625, -0.08213769491188778, -0.07795647538955015, -0.07609479014797572, -0.06979002219081165, -0.06645348378711273, -0.06001753295886477, -0.054328991866965846, -0.045783833354952314, -0.03846197834800118, -0.029723758728833132, -0.020668653228716388, -0.010593613802940943, -0.003717042652527224], L_corr_pw_step: 0.05, enrichment: { type: "L_gated_hue", amp: 0.058, center_deg: 264.5, sigma: 0.7, L_lo: 0.37, L_hi: 1 }, enrichment_gr: 1.5240031403836274, enrichment_rt: 8326672684688674e-30, chroma_power: 0.978 };
|
|
1364
|
+
function C0(n) {
|
|
1365
|
+
let t = o0(n.M1), e = o0(n.M2);
|
|
1366
|
+
return { M1: t, M1_inv: c0(t), M2: e, M2_inv: c0(e), gamma: new Float64Array(n.gamma), inv_gamma: new Float64Array(n.gamma.map((s) => 1 / s)), raw: n };
|
|
1367
|
+
}
|
|
1368
|
+
function y0() {
|
|
1369
|
+
return e1;
|
|
1370
|
+
}
|
|
1371
|
+
function Y1(n) {
|
|
1372
|
+
return { M1: n.M1, gamma: n.gamma ?? [1 / 3, 1 / 3, 1 / 3], M2: n.M2, hue_cos1: n.hue_cos1 ?? 0, hue_sin1: n.hue_sin1 ?? 0, hue_cos2: n.hue_cos2 ?? 0, hue_sin2: n.hue_sin2 ?? 0, hue_cos3: n.hue_cos3 ?? 0, hue_sin3: n.hue_sin3 ?? 0, hue_cos4: n.hue_cos4 ?? 0, hue_sin4: n.hue_sin4 ?? 0, L_corr_p1: n.L_corr_p1 ?? 0, L_corr_p2: n.L_corr_p2 ?? 0, L_corr_p3: n.L_corr_p3 ?? 0, lp_dark: n.lp_dark ?? 0, lp_dark_hcos: n.lp_dark_hcos ?? 0, lp_dark_hsin: n.lp_dark_hsin ?? 0, lc1: n.lc1 ?? 0, lc2: n.lc2 ?? 0, hue_L_amp: n.hue_L_amp ?? 0, hue_L_center: n.hue_L_center ?? 0, hue_L_width: n.hue_L_width ?? 1, hue_L_knee: n.hue_L_knee ?? 1, transfer: n.transfer ?? "cbrt", softcbrt_eps: n.softcbrt_eps ?? 1e-3, depcubic_alpha: n.depcubic_alpha ?? 0.02, L_corr_pw: n.L_corr_pw ?? [], L_corr_pw_step: n.L_corr_pw_step ?? 0.05, chroma_power: n.chroma_power ?? 1, enrichment: n.enrichment };
|
|
1373
|
+
}
|
|
1374
|
+
function R0(n) {
|
|
1375
|
+
let t = Y1(n), e = o0(t.M1), s = o0(t.M2);
|
|
1376
|
+
return { M1: e, M1_inv: c0(e), M2: s, M2_inv: c0(s), gamma: new Float64Array(t.gamma), inv_gamma: new Float64Array(t.gamma.map((r) => 1 / r)), raw: t };
|
|
1377
|
+
}
|
|
1378
|
+
function S0() {
|
|
1379
|
+
return t1;
|
|
1380
|
+
}
|
|
1381
|
+
var A0 = new Float64Array([3.2404541621141054, -1.5371385940306088, -0.4985314095560162, -0.9692660305051868, 1.8760108454466942, 0.0415560175303498, 0.0556434309591147, -0.2040259135167538, 1.0572251882231791]);
|
|
1382
|
+
var Z1 = new Float64Array([0.4124564390896922, 0.3575760776439511, 0.1804374832663989, 0.2126728514056226, 0.7151521552878178, 0.0721750036064596, 0.0193338955823293, 0.1191920258813418, 0.9503040785363679]);
|
|
1383
|
+
var F0 = new Float64Array([2.4934969119, -0.9313836179, -0.4027107845, -0.8294889696, 1.7626640603, 0.0236246858, 0.0358458302, -0.0761723893, 0.956884524]);
|
|
1384
|
+
var B1 = new Float64Array([0.4865709486, 0.2656676932, 0.1982172852, 0.2289745641, 0.6917385218, 0.0792869141, 0, 0.0451133819, 1.0439443689]);
|
|
1385
|
+
var T0 = new Float64Array([1.716651188, -0.3556707838, -0.2533662814, -0.6666843518, 1.6164812366, 0.0157685458, 0.0176398574, -0.0427706133, 0.9421031212]);
|
|
1386
|
+
var v1 = new Float64Array([0.6369580483, 0.1446169036, 0.1688809752, 0.262700212, 0.6779980715, 0.0593017165, 0, 0.028072693, 1.0609850577]);
|
|
1387
|
+
function j(n) {
|
|
1388
|
+
return n <= 31308e-7 ? 12.92 * n : 1.055 * Math.pow(Math.max(n, 0), 1 / 2.4) - 0.055;
|
|
1389
|
+
}
|
|
1390
|
+
function T(n) {
|
|
1391
|
+
return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
|
|
1392
|
+
}
|
|
1393
|
+
var k0 = 1.09929682680944;
|
|
1394
|
+
var n1 = 0.018053968510807;
|
|
1395
|
+
var D1 = 4.5 * n1;
|
|
1396
|
+
function g0(n) {
|
|
1397
|
+
return n < n1 ? 4.5 * n : k0 * Math.pow(Math.max(n, 0), 0.45) - (k0 - 1);
|
|
1398
|
+
}
|
|
1399
|
+
function K(n, t, e, s) {
|
|
1400
|
+
return [n[0] * t + n[1] * e + n[2] * s, n[3] * t + n[4] * e + n[5] * s, n[6] * t + n[7] * e + n[8] * s];
|
|
1401
|
+
}
|
|
1402
|
+
function J(n) {
|
|
1403
|
+
let [t, e, s] = K(A0, n[0], n[1], n[2]);
|
|
1404
|
+
return [j(t), j(e), j(s)];
|
|
1405
|
+
}
|
|
1406
|
+
function Q(n) {
|
|
1407
|
+
let t = T(n[0]), e = T(n[1]), s = T(n[2]);
|
|
1408
|
+
return K(Z1, t, e, s);
|
|
1409
|
+
}
|
|
1410
|
+
function G0(n) {
|
|
1411
|
+
let [t, e, s] = K(F0, n[0], n[1], n[2]);
|
|
1412
|
+
return [j(t), j(e), j(s)];
|
|
1413
|
+
}
|
|
1414
|
+
function P0(n) {
|
|
1415
|
+
let [t, e, s] = K(T0, n[0], n[1], n[2]);
|
|
1416
|
+
return [g0(t), g0(e), g0(s)];
|
|
1417
|
+
}
|
|
1418
|
+
function G(n) {
|
|
1419
|
+
let t = n.startsWith("#") ? n.slice(1) : n;
|
|
1420
|
+
if (t.length === 3 && (t = t[0] + t[0] + t[1] + t[1] + t[2] + t[2]), t.length !== 6) throw new Error(`Expected 3 or 6-char hex, got '${n}'`);
|
|
1421
|
+
if (!/^[0-9a-fA-F]{6}$/.test(t)) throw new Error(`Invalid hex color: '${n}'`);
|
|
1422
|
+
return [parseInt(t.slice(0, 2), 16) / 255, parseInt(t.slice(2, 4), 16) / 255, parseInt(t.slice(4, 6), 16) / 255];
|
|
1423
|
+
}
|
|
1424
|
+
function z(n) {
|
|
1425
|
+
let t = Math.round(Math.min(Math.max(n[0] * 255, 0), 255)), e = Math.round(Math.min(Math.max(n[1] * 255, 0), 255)), s = Math.round(Math.min(Math.max(n[2] * 255, 0), 255));
|
|
1426
|
+
return "#" + (1 << 24 | t << 16 | e << 8 | s).toString(16).slice(1);
|
|
1427
|
+
}
|
|
1428
|
+
function I(n) {
|
|
1429
|
+
return [Math.min(Math.max(n[0], 0), 1), Math.min(Math.max(n[1], 0), 1), Math.min(Math.max(n[2], 0), 1)];
|
|
1430
|
+
}
|
|
1431
|
+
function e0(n) {
|
|
1432
|
+
return 0.2126 * T(n[0]) + 0.7152 * T(n[1]) + 0.0722 * T(n[2]);
|
|
1433
|
+
}
|
|
1434
|
+
var { cos: Y0, sin: Z0, sqrt: O1, atan2: I1, min: $1 } = Math;
|
|
1435
|
+
function r1(n) {
|
|
1436
|
+
return n === "srgb" ? A0 : n === "display-p3" ? F0 : T0;
|
|
1437
|
+
}
|
|
1438
|
+
function X0(n, t, e, s, r) {
|
|
1439
|
+
let o = n[0] * t + n[1] * e + n[2] * s, c = n[3] * t + n[4] * e + n[5] * s, u = n[6] * t + n[7] * e + n[8] * s;
|
|
1440
|
+
return o >= -r && o <= 1 + r && c >= -r && c <= 1 + r && u >= -r && u <= 1 + r;
|
|
1441
|
+
}
|
|
1442
|
+
function n0(n, t, e = "srgb", s = 1e-4) {
|
|
1443
|
+
let r = t.toXYZ(n);
|
|
1444
|
+
return X0(r1(e), r[0], r[1], r[2], s);
|
|
1445
|
+
}
|
|
1446
|
+
function t0(n, t, e, s = "srgb", r = 1e-4) {
|
|
1447
|
+
let o = Y0(t), c = Z0(t), u = r1(s), l = 0, a = 1;
|
|
1448
|
+
for (; ; ) {
|
|
1449
|
+
let m = e.toXYZ([n, a * o, a * c]);
|
|
1450
|
+
if (!X0(u, m[0], m[1], m[2], r)) break;
|
|
1451
|
+
if (a *= 2, a > 100) return a;
|
|
1452
|
+
}
|
|
1453
|
+
for (let m = 0; m < 50; m++) {
|
|
1454
|
+
let f = (l + a) * 0.5, p = e.toXYZ([n, f * o, f * c]);
|
|
1455
|
+
if (X0(u, p[0], p[1], p[2], r) ? l = f : a = f, a - l < r) break;
|
|
1456
|
+
}
|
|
1457
|
+
return l;
|
|
1458
|
+
}
|
|
1459
|
+
function W1(n, t, e) {
|
|
1460
|
+
if (n0(n, t, e)) return [...n];
|
|
1461
|
+
let [s, r, o] = n, c = O1(r * r + o * o), u = I1(o, r);
|
|
1462
|
+
if (c < 1e-10) return [s, 0, 0];
|
|
1463
|
+
let l = t0(s, u, t, e), a = $1(c, l);
|
|
1464
|
+
return [s, a * Y0(u), a * Z0(u)];
|
|
1465
|
+
}
|
|
1466
|
+
function r0(n, t, e = "srgb") {
|
|
1467
|
+
return W1(n, t, e);
|
|
1468
|
+
}
|
|
1469
|
+
function m0(n, t) {
|
|
1470
|
+
let e = e0(n), s = e0(t), r = Math.max(e, s), o = Math.min(e, s);
|
|
1471
|
+
return (r + 0.05) / (o + 0.05);
|
|
1472
|
+
}
|
|
1473
|
+
var { sqrt: q1, atan2: V1, max: W, min: q, abs: l0, round: N, sign: B0, pow: k2, PI: U1 } = Math;
|
|
1474
|
+
var v0 = Math.cbrt;
|
|
1475
|
+
var E = [0.8189330101, 0.3618667424, -0.1288597137, 0.0329845436, 0.9293118715, 0.0361456387, 0.0482003018, 0.2643662691, 0.633851707];
|
|
1476
|
+
var O = [0.2104542553, 0.793617785, -0.0040720468, 1.9779984951, -2.428592205, 0.4505937099, 0.0259040371, 0.7827717662, -0.808675766];
|
|
1477
|
+
function K1(n) {
|
|
1478
|
+
let [t, e, s] = n, r = E[0] * t + E[1] * e + E[2] * s, o = E[3] * t + E[4] * e + E[5] * s, c = E[6] * t + E[7] * e + E[8] * s, u = B0(r) * v0(l0(r)), l = B0(o) * v0(l0(o)), a = B0(c) * v0(l0(c));
|
|
1479
|
+
return [O[0] * u + O[1] * l + O[2] * a, O[3] * u + O[4] * l + O[5] * a, O[6] * u + O[7] * l + O[8] * a];
|
|
1480
|
+
}
|
|
1481
|
+
function J1(n) {
|
|
1482
|
+
let [t, e, s] = n, r = q1(e * e + s * s), o = (V1(s, e) * 180 / U1 % 360 + 360) % 360;
|
|
1483
|
+
return [t, r, o];
|
|
1484
|
+
}
|
|
1485
|
+
var h0 = class {
|
|
1486
|
+
constructor(t) {
|
|
1487
|
+
this.hl = t;
|
|
1488
|
+
}
|
|
1489
|
+
toCssHex(t) {
|
|
1490
|
+
return this.hl.toHex(t);
|
|
1491
|
+
}
|
|
1492
|
+
toCssRgb(t) {
|
|
1493
|
+
let e = this.hl.toSrgb(t), s = N(q(W(e[0] * 255, 0), 255)), r = N(q(W(e[1] * 255, 0), 255)), o = N(q(W(e[2] * 255, 0), 255));
|
|
1494
|
+
return `rgb(${s}, ${r}, ${o})`;
|
|
1495
|
+
}
|
|
1496
|
+
toCssOklch(t) {
|
|
1497
|
+
let e = this.hl.toXYZ(t), s = K1(e), [r, o, c] = J1(s);
|
|
1498
|
+
return `oklch(${(r * 100).toFixed(1)}% ${o.toFixed(4)} ${c.toFixed(1)})`;
|
|
1499
|
+
}
|
|
1500
|
+
toCssDisplayP3(t) {
|
|
1501
|
+
return this.hl.toHexP3(t);
|
|
1502
|
+
}
|
|
1503
|
+
toCssHsl(t) {
|
|
1504
|
+
let e = this.hl.toSrgb(t), s = e[0], r = e[1], o = e[2], c = W(s, r, o), u = q(s, r, o), l = c - u, a = (c + u) / 2, m = 0, f = 0;
|
|
1505
|
+
return l >= 1e-10 && (f = l0(2 * a - 1) < 1 ? l / (1 - l0(2 * a - 1)) : 1, c === s ? m = 60 * ((r - o) / l % 6) : c === r ? m = 60 * ((o - s) / l + 2) : m = 60 * ((s - r) / l + 4), m = (m % 360 + 360) % 360), `hsl(${N(m)}, ${N(f * 100)}%, ${N(a * 100)}%)`;
|
|
1506
|
+
}
|
|
1507
|
+
toAndroidArgb(t) {
|
|
1508
|
+
let e = this.hl.toSrgb(t), s = N(q(W(e[0] * 255, 0), 255)), r = N(q(W(e[1] * 255, 0), 255)), o = N(q(W(e[2] * 255, 0), 255));
|
|
1509
|
+
return `0xFF${s.toString(16).padStart(2, "0")}${r.toString(16).padStart(2, "0")}${o.toString(16).padStart(2, "0")}`;
|
|
1510
|
+
}
|
|
1511
|
+
toIosP3(t) {
|
|
1512
|
+
let e = this.hl.toDisplayP3(t);
|
|
1513
|
+
return { r: parseFloat(e[0].toFixed(4)), g: parseFloat(e[1].toFixed(4)), b: parseFloat(e[2].toFixed(4)) };
|
|
1514
|
+
}
|
|
1515
|
+
toSwiftLiteral(t) {
|
|
1516
|
+
let e = this.hl.toDisplayP3(t);
|
|
1517
|
+
return `Color(.displayP3, red: ${e[0].toFixed(4)}, green: ${e[1].toFixed(4)}, blue: ${e[2].toFixed(4)})`;
|
|
1518
|
+
}
|
|
1519
|
+
exportScale(t, e, s = ["hex", "oklch", "p3"]) {
|
|
1520
|
+
let r = { hex: (c) => this.toCssHex(c), rgb: (c) => this.toCssRgb(c), oklch: (c) => this.toCssOklch(c), p3: (c) => this.toCssDisplayP3(c), hsl: (c) => this.toCssHsl(c), android: (c) => this.toAndroidArgb(c) }, o = {};
|
|
1521
|
+
for (let [c, u] of Object.entries(t)) {
|
|
1522
|
+
let l = this.hl.fromHex(u), a = {};
|
|
1523
|
+
for (let m of s) m in r && (a[m] = r[m](l));
|
|
1524
|
+
o[c] = a;
|
|
1525
|
+
}
|
|
1526
|
+
return { [e]: o };
|
|
1527
|
+
}
|
|
1528
|
+
exportCssCustomProperties(t, e = "--color") {
|
|
1529
|
+
return Object.entries(t).sort((r, o) => parseInt(r[0]) - parseInt(o[0])).map(([r, o]) => ` ${e}-${r}: ${o};`).join(`
|
|
1530
|
+
`);
|
|
1531
|
+
}
|
|
1532
|
+
exportTailwind(t, e) {
|
|
1533
|
+
return { [e]: { ...t } };
|
|
1534
|
+
}
|
|
1535
|
+
exportJson(t) {
|
|
1536
|
+
let e = {};
|
|
1537
|
+
for (let [s, r] of Object.entries(t)) {
|
|
1538
|
+
let o = this.exportScale(r, s);
|
|
1539
|
+
Object.assign(e, o);
|
|
1540
|
+
}
|
|
1541
|
+
return JSON.stringify(e, null, 2);
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
function s1(n, t) {
|
|
1545
|
+
let e = Math.max(-98.243 * n + -11.538 * t + 30.716, 4.76), s = n * 591.15;
|
|
1546
|
+
return { de: n, deHuman: s, disagreement: e, reliability: s / (s + e), reliable: s > e, extrapolated: n > 0.1484 };
|
|
1547
|
+
}
|
|
1548
|
+
var { sqrt: A, atan2: D0, cos: p0, sin: N0, PI: H, pow: P, abs: Q1 } = Math;
|
|
1549
|
+
function o1(n) {
|
|
1550
|
+
let t = n[0] <= 0.04045 ? n[0] / 12.92 : P((n[0] + 0.055) / 1.055, 2.4), e = n[1] <= 0.04045 ? n[1] / 12.92 : P((n[1] + 0.055) / 1.055, 2.4), s = n[2] <= 0.04045 ? n[2] / 12.92 : P((n[2] + 0.055) / 1.055, 2.4), r = (0.4124564 * t + 0.3575761 * e + 0.1804375 * s) / 0.95047, o = 0.2126729 * t + 0.7151522 * e + 0.072175 * s, c = (0.0193339 * t + 0.119192 * e + 0.9503041 * s) / 1.08883, u = (f) => f > 8856e-6 ? Math.cbrt(f) : 7.787 * f + 16 / 116, l = u(r), a = u(o), m = u(c);
|
|
1551
|
+
return [116 * a - 16, 500 * (l - a), 200 * (a - m)];
|
|
1552
|
+
}
|
|
1553
|
+
function z1(n, t, e, s, r, o) {
|
|
1554
|
+
let c = A(t * t + e * e), u = A(r * r + o * o), l = (c + u) / 2, a = P(l, 7), m = P(25, 7), f = 0.5 * (1 - A(a / (a + m))), p = (1 + f) * t, h = (1 + f) * r, i = A(p * p + e * e), _ = A(h * h + o * o), b = D0(e, p), d = D0(o, h);
|
|
1555
|
+
b < 0 && (b += 2 * H), d < 0 && (d += 2 * H);
|
|
1556
|
+
let L = s - n, k = _ - i, M = 0;
|
|
1557
|
+
i * _ !== 0 && (M = d - b, M > H && (M -= 2 * H), M < -H && (M += 2 * H));
|
|
1558
|
+
let s0 = 2 * A(i * _) * N0(M / 2), c1 = (n + s) / 2, M0 = (i + _) / 2, X = 0;
|
|
1559
|
+
i * _ === 0 ? X = b + d : (X = (b + d) / 2, Q1(b - d) > H && (X += X < H ? H : -H));
|
|
1560
|
+
let i1 = 1 - 0.17 * p0(X - H / 6) + 0.24 * p0(2 * X) + 0.32 * p0(3 * X + H / 30) - 0.2 * p0(4 * X - 63 * H / 180), b0 = c1 - 50, a1 = 1 + 0.015 * b0 * b0 / A(20 + b0 * b0), O0 = 1 + 0.045 * M0, I0 = 1 + 0.015 * M0 * i1, $0 = P(M0, 7), u1 = 2 * A($0 / ($0 + m)), _1 = X * 180 / H, m1 = 30 * Math.exp(-P((_1 - 275) / 25, 2)), l1 = -N0(2 * m1 * H / 180) * u1;
|
|
1561
|
+
return A(P(L / a1, 2) + P(k / O0, 2) + P(s0 / I0, 2) + l1 * (k / O0) * (s0 / I0));
|
|
1562
|
+
}
|
|
1563
|
+
var E0 = class {
|
|
1564
|
+
constructor(t = {}) {
|
|
1565
|
+
let e = C0(t.params ?? y0());
|
|
1566
|
+
this.cp = e, this.metric = new $(e, { neutralCorrection: t.neutralCorrection ?? true, abRotateDeg: t.abRotateDeg ?? -28.2 });
|
|
1567
|
+
let s = R0(t.genParams ?? S0());
|
|
1568
|
+
this.gcp = s, this.gen = new _0(s);
|
|
1569
|
+
let r = [0.95047, 1, 1.08883];
|
|
1570
|
+
this.genWhiteL = this.gen.fromXYZ(r)[0];
|
|
1571
|
+
}
|
|
1572
|
+
fromHex(t) {
|
|
1573
|
+
return this.fromSrgb(G(t));
|
|
1574
|
+
}
|
|
1575
|
+
toHex(t) {
|
|
1576
|
+
return z(this.toSrgb(t));
|
|
1577
|
+
}
|
|
1578
|
+
fromSrgb(t) {
|
|
1579
|
+
return this.metric.fromXYZ(Q(t));
|
|
1580
|
+
}
|
|
1581
|
+
toSrgb(t) {
|
|
1582
|
+
let e = r0(t, this.metric, "srgb");
|
|
1583
|
+
return I(J(this.metric.toXYZ(e)));
|
|
1584
|
+
}
|
|
1585
|
+
fromXYZ(t) {
|
|
1586
|
+
return this.metric.fromXYZ(t);
|
|
1587
|
+
}
|
|
1588
|
+
toXYZ(t) {
|
|
1589
|
+
return this.metric.toXYZ(t);
|
|
1590
|
+
}
|
|
1591
|
+
toDisplayP3(t) {
|
|
1592
|
+
let e = r0(t, this.metric, "display-p3");
|
|
1593
|
+
return I(G0(this.metric.toXYZ(e)));
|
|
1594
|
+
}
|
|
1595
|
+
toHexP3(t) {
|
|
1596
|
+
let e = this.toDisplayP3(t);
|
|
1597
|
+
return `color(display-p3 ${e[0].toFixed(4)} ${e[1].toFixed(4)} ${e[2].toFixed(4)})`;
|
|
1598
|
+
}
|
|
1599
|
+
toRec2020(t) {
|
|
1600
|
+
let e = r0(t, this.metric, "rec2020");
|
|
1601
|
+
return I(P0(this.metric.toXYZ(e)));
|
|
1602
|
+
}
|
|
1603
|
+
toHexRec2020(t) {
|
|
1604
|
+
let e = this.toRec2020(t);
|
|
1605
|
+
return `color(rec2020 ${e[0].toFixed(4)} ${e[1].toFixed(4)} ${e[2].toFixed(4)})`;
|
|
1606
|
+
}
|
|
1607
|
+
isInSrgb(t) {
|
|
1608
|
+
return n0(t, this.metric, "srgb");
|
|
1609
|
+
}
|
|
1610
|
+
isInP3(t) {
|
|
1611
|
+
return n0(t, this.metric, "display-p3");
|
|
1612
|
+
}
|
|
1613
|
+
isInRec2020(t) {
|
|
1614
|
+
return n0(t, this.metric, "rec2020");
|
|
1615
|
+
}
|
|
1616
|
+
genFromHex(t) {
|
|
1617
|
+
return this.gen.fromXYZ(Q(G(t)));
|
|
1618
|
+
}
|
|
1619
|
+
genToHex(t) {
|
|
1620
|
+
return z(this._genToSrgb(t));
|
|
1621
|
+
}
|
|
1622
|
+
genFromSrgb(t) {
|
|
1623
|
+
return this.gen.fromXYZ(Q(t));
|
|
1624
|
+
}
|
|
1625
|
+
genToSrgb(t) {
|
|
1626
|
+
let e = r0(t, this.gen, "srgb");
|
|
1627
|
+
return I(J(this.gen.toXYZ(e)));
|
|
1628
|
+
}
|
|
1629
|
+
_genToSrgb(t) {
|
|
1630
|
+
return this.genToSrgb(t);
|
|
1631
|
+
}
|
|
1632
|
+
baseFromHex(t) {
|
|
1633
|
+
return this.genFromHex(t);
|
|
1634
|
+
}
|
|
1635
|
+
baseToHex(t) {
|
|
1636
|
+
return this.genToHex(t);
|
|
1637
|
+
}
|
|
1638
|
+
baseFromSrgb(t) {
|
|
1639
|
+
return this.genFromSrgb(t);
|
|
1640
|
+
}
|
|
1641
|
+
baseToSrgb(t) {
|
|
1642
|
+
return this.genToSrgb(t);
|
|
1643
|
+
}
|
|
1644
|
+
contrastRatio(t, e) {
|
|
1645
|
+
return m0(G(t), G(e));
|
|
1646
|
+
}
|
|
1647
|
+
ensureContrast(t, e, s = 4.5) {
|
|
1648
|
+
if (this.contrastRatio(t, e) >= s) return t;
|
|
1649
|
+
let r = this.genFromHex(t), o = G(e), c = r[0], u = t, l = 0, a = c;
|
|
1650
|
+
for (let m of ["darken", "lighten"]) {
|
|
1651
|
+
let f = m === "darken" ? 0 : c, p = m === "darken" ? c : 1.5, h = [...r];
|
|
1652
|
+
for (let d = 0; d < 40; d++) {
|
|
1653
|
+
let L = (f + p) / 2;
|
|
1654
|
+
h[0] = L;
|
|
1655
|
+
let k = this._genToSrgb(h), M = G(z(k)), s0 = m0(M, o);
|
|
1656
|
+
m === "darken" ? s0 >= s ? f = L : p = L : s0 >= s ? p = L : f = L;
|
|
1657
|
+
}
|
|
1658
|
+
let i = m === "darken" ? f : p;
|
|
1659
|
+
m === "darken" ? i = Math.max(0, i - 3e-3) : i = Math.min(1.5, i + 3e-3), h[0] = i;
|
|
1660
|
+
let _ = z(this._genToSrgb(h)), b = m0(G(_), o);
|
|
1661
|
+
if (b >= s) {
|
|
1662
|
+
let d = Math.abs(i - c), L = Math.abs(a - c);
|
|
1663
|
+
(l < s || d < L) && (u = _, l = b, a = i);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
if (l < s) {
|
|
1667
|
+
for (let m of ["#000000", "#ffffff"]) if (this.contrastRatio(m, e) >= s) return m;
|
|
1668
|
+
return this.contrastRatio("#000000", e) > this.contrastRatio("#ffffff", e) ? "#000000" : "#ffffff";
|
|
1669
|
+
}
|
|
1670
|
+
return u;
|
|
1671
|
+
}
|
|
1672
|
+
meetsContrast(t, e, s = "AA") {
|
|
1673
|
+
let r = s === "AAA" ? 7 : 4.5;
|
|
1674
|
+
return this.contrastRatio(t, e) >= r;
|
|
1675
|
+
}
|
|
1676
|
+
deltaE(t, e) {
|
|
1677
|
+
let s = this.fromHex(t), r = this.fromHex(e);
|
|
1678
|
+
return A((s[0] - r[0]) ** 2 + (s[1] - r[1]) ** 2 + (s[2] - r[2]) ** 2);
|
|
1679
|
+
}
|
|
1680
|
+
perceptualDistance(t, e) {
|
|
1681
|
+
return this.metric.distance(t, e);
|
|
1682
|
+
}
|
|
1683
|
+
distanceFromLab(t, e) {
|
|
1684
|
+
return this.metric.distanceFromLab(t, e);
|
|
1685
|
+
}
|
|
1686
|
+
difference(t, e) {
|
|
1687
|
+
return this.perceptualDistance(this.fromHex(t), this.fromHex(e));
|
|
1688
|
+
}
|
|
1689
|
+
differenceWithConfidence(t, e) {
|
|
1690
|
+
let s = this.fromHex(t), r = this.fromHex(e), o = this.perceptualDistance(s, r), c = 0.5 * (A(s[1] ** 2 + s[2] ** 2) + A(r[1] ** 2 + r[2] ** 2));
|
|
1691
|
+
return s1(o, c);
|
|
1692
|
+
}
|
|
1693
|
+
palette(t, e = 10) {
|
|
1694
|
+
if (e < 1) return [];
|
|
1695
|
+
let s = this.genFromHex(t);
|
|
1696
|
+
if (e === 1) return [this.genToHex(s)];
|
|
1697
|
+
let r = this.genWhiteL - 0.01, o = 0.05, c = [];
|
|
1698
|
+
for (let u = 0; u < e; u++) {
|
|
1699
|
+
let l = r - u / (e - 1) * (r - o);
|
|
1700
|
+
c.push(this.genToHex([l, s[1], s[2]]));
|
|
1701
|
+
}
|
|
1702
|
+
return c;
|
|
1703
|
+
}
|
|
1704
|
+
paletteHues(t = 0.6, e = 0.15, s = 12) {
|
|
1705
|
+
let r = [];
|
|
1706
|
+
for (let o = 0; o < s; o++) {
|
|
1707
|
+
let c = 2 * H * o / s;
|
|
1708
|
+
r.push(this.genToHex([t, e * p0(c), e * N0(c)]));
|
|
1709
|
+
}
|
|
1710
|
+
return r;
|
|
1711
|
+
}
|
|
1712
|
+
semanticScale(t, e) {
|
|
1713
|
+
e || (e = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]);
|
|
1714
|
+
let s = this.genFromHex(t), r = s[0], o = this.genWhiteL - 0.01, c = 0.05, u = {};
|
|
1715
|
+
for (let l of e) {
|
|
1716
|
+
let a;
|
|
1717
|
+
if (l <= 500) {
|
|
1718
|
+
let m = l / 500;
|
|
1719
|
+
a = o + m * (r - o);
|
|
1720
|
+
} else {
|
|
1721
|
+
let m = (l - 500) / 450;
|
|
1722
|
+
a = r + m * (c - r);
|
|
1723
|
+
}
|
|
1724
|
+
u[String(l)] = this.genToHex([a, s[1], s[2]]);
|
|
1725
|
+
}
|
|
1726
|
+
return u;
|
|
1727
|
+
}
|
|
1728
|
+
gradient(t, e, s = 16) {
|
|
1729
|
+
if (s === 1) return [t];
|
|
1730
|
+
let r = this.genFromHex(t), o = this.genFromHex(e), c = o[0] - r[0], u = o[1] - r[1], l = o[2] - r[2], a = 256, m = [0], f = o1(I(J(this.gen.toXYZ([r[0], r[1], r[2]]))));
|
|
1731
|
+
for (let i = 1; i <= a; i++) {
|
|
1732
|
+
let _ = i / a, b = I(J(this.gen.toXYZ([r[0] + c * _, r[1] + u * _, r[2] + l * _]))), d = o1(b);
|
|
1733
|
+
m.push(m[i - 1] + z1(f[0], f[1], f[2], d[0], d[1], d[2])), f = d;
|
|
1734
|
+
}
|
|
1735
|
+
let p = m[a], h = [];
|
|
1736
|
+
for (let i = 0; i < s; i++) {
|
|
1737
|
+
let _ = i / (s - 1) * p, b = 0, d = a;
|
|
1738
|
+
for (; b < d - 1; ) {
|
|
1739
|
+
let M = b + d >> 1;
|
|
1740
|
+
m[M] < _ ? b = M : d = M;
|
|
1741
|
+
}
|
|
1742
|
+
let L = m[d] - m[b] > 1e-12 ? (_ - m[b]) / (m[d] - m[b]) : 0, k = (b + L) / a;
|
|
1743
|
+
h.push(this.genToHex([r[0] + c * k, r[1] + u * k, r[2] + l * k]));
|
|
1744
|
+
}
|
|
1745
|
+
return h;
|
|
1746
|
+
}
|
|
1747
|
+
adaptToMode(t, e = "light", s = "dark") {
|
|
1748
|
+
if (e === s) return t;
|
|
1749
|
+
let r = this.genWhiteL - 0.02, o = 0.05, c = r, u = 0.08, l = r - 0.05, a = this.genFromHex(t), m = a[0], [f, p] = e === "light" ? [o, c] : [u, l], [h, i] = e === "light" ? [u, l] : [o, c], _ = Math.min(Math.max((m - f) / (p - f), 0), 1), b = i - _ * (i - h);
|
|
1750
|
+
return this.genToHex([b, a[1], a[2]]);
|
|
1751
|
+
}
|
|
1752
|
+
adaptPair(t, e, s = "light", r = "dark", o = 4.5) {
|
|
1753
|
+
let c = this.adaptToMode(t, s, r), u = this.adaptToMode(e, s, r);
|
|
1754
|
+
return [this.ensureContrast(c, u, o), u];
|
|
1755
|
+
}
|
|
1756
|
+
info(t) {
|
|
1757
|
+
let e = G(t), s = Q(e), r = this.metric.fromXYZ(s), o = A(r[1] ** 2 + r[2] ** 2), c = (D0(r[2], r[1]) * 180 / H % 360 + 360) % 360;
|
|
1758
|
+
return { hex: t, srgb: e, xyz: s, lab: r, L: r[0], C: o, H: c, luminance: e0(e) };
|
|
1759
|
+
}
|
|
1760
|
+
export() {
|
|
1761
|
+
return new h0(this);
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
|
|
1765
|
+
// src/engine/p2.ts
|
|
1766
|
+
var P2_D = 0.12;
|
|
1767
|
+
var P2_D_UP = 0.11;
|
|
1768
|
+
var gm = (v2) => {
|
|
1769
|
+
const x2 = Math.min(1, Math.max(0, v2));
|
|
1770
|
+
return x2 <= 31308e-7 ? 12.92 * x2 : 1.055 * x2 ** (1 / 2.4) - 0.055;
|
|
1771
|
+
};
|
|
1772
|
+
var hexOf = (L, C2, H2) => {
|
|
1773
|
+
const [r, g2, b] = oklchToLinearRgb(L, clampChromaToGamut(L, C2, H2), H2);
|
|
1774
|
+
const ch = (v2) => Math.round(gm(v2) * 255).toString(16).padStart(2, "0");
|
|
1775
|
+
return `#${ch(r)}${ch(g2)}${ch(b)}`;
|
|
1776
|
+
};
|
|
1777
|
+
var hl = new E0();
|
|
1778
|
+
function p2Diff(a, b) {
|
|
1779
|
+
return hl.difference(hexOf(a.L, a.C, a.H), hexOf(b.L, b.C, b.H));
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// src/engine/requirements/profiles.ts
|
|
1783
|
+
var DEFAULT_APCA_LC_MAP = { 3: 30, 4.5: 75, 6.5: 85, 7: 90 };
|
|
1784
|
+
function toApca(req, lcMap) {
|
|
1785
|
+
if (req.metric !== "wcag") return req;
|
|
1786
|
+
if (req.against === "wash-80") return { metric: "apca", against: "paper-97", targetLc: 85 };
|
|
1787
|
+
const targetLc = lcMap[req.target];
|
|
1788
|
+
if (targetLc === void 0) throw new Error(`apca profile: no Lc mapping for wcag target ${req.target}`);
|
|
1789
|
+
return { metric: "apca", against: req.against, targetLc };
|
|
1790
|
+
}
|
|
1791
|
+
var CTA_ONFILL_ENFORCE_LC = 60;
|
|
1792
|
+
var CRITICAL_CLEARANCE_LC = 50;
|
|
1793
|
+
function withProfile(spec, profile, lcMap = DEFAULT_APCA_LC_MAP) {
|
|
1794
|
+
if (profile === "wcag") return spec;
|
|
1795
|
+
return {
|
|
1796
|
+
...spec,
|
|
1797
|
+
// (The stop-9 CARVE-OUT is gone with the stop, owner 2026-07-29. It existed because
|
|
1798
|
+
// highlight-9's require was a fill-vs-its-own-plane bar with no honest slot in a map
|
|
1799
|
+
// of TEXT bars — 3 → 30 non-text, 4.5 → 75 body, 7 → 90 — so the apca lane kept its
|
|
1800
|
+
// own hl-9 placement instead. Today's stop 9 is an INK stop: 4.5 is a text bar, the
|
|
1801
|
+
// 75 slot is its correct translation, and it maps like every other require.)
|
|
1802
|
+
stops: spec.stops.map((s) => s.require ? { ...s, require: toApca(s.require, lcMap) } : s),
|
|
1803
|
+
ons: {
|
|
1804
|
+
...spec.ons,
|
|
1805
|
+
// the ratio floor is STRIPPED here, as spec.ts always documented it would be — the apca
|
|
1806
|
+
// law is the Lc bar. Until onFill gained a floor (2026-07-29) there was nothing to strip,
|
|
1807
|
+
// so the promise had never been implemented.
|
|
1808
|
+
onFill: { ...spec.ons.onFill, enforceLc: CTA_ONFILL_ENFORCE_LC, ratioFloor: void 0 }
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// src/engine/darkChromaCurve.ts
|
|
1814
|
+
var rad = (d) => d * Math.PI / 180;
|
|
1815
|
+
var norm = (h) => (h % 360 + 360) % 360;
|
|
1816
|
+
var angDist = (h, c) => Math.abs((h - c + 540) % 360 - 180);
|
|
1817
|
+
var lobe = (h, c, w2) => Math.pow(Math.max(0, Math.cos(rad(90 * angDist(h, c) / w2))), 1.5);
|
|
1818
|
+
var loudnessCap = (H2) => {
|
|
1819
|
+
const h = norm(H2);
|
|
1820
|
+
const b = DARK_CTA_C.brand;
|
|
1821
|
+
return b.globalTrim * (1 - Math.max(...b.lobes.map((l) => l.depth * lobe(h, l.center, l.width))));
|
|
1822
|
+
};
|
|
1823
|
+
var darkChromaCurve = (L, H2, brandC, _ctaC) => perceptualDarkC(L, H2, brandC);
|
|
1824
|
+
var darkCtaTrim = (H2) => 1 - 0.5 * (1 - loudnessCap(H2));
|
|
1825
|
+
|
|
1826
|
+
// src/engine/requirements/producers.ts
|
|
1827
|
+
function buildContext(hex, opts) {
|
|
1828
|
+
const forcedArchetype = opts?.forcedArchetype;
|
|
1829
|
+
const { L: brandL, C: rawC, H: rawH } = hexToOklch(hex);
|
|
1830
|
+
const brandH = (rawH + (opts?.hueShiftDeg ?? 0) + 360) % 360;
|
|
1831
|
+
const brandC = rawC * (opts?.chromaScale ?? 1);
|
|
1832
|
+
const subtleC = brandC * (opts?.subtleChromaScale ?? 1);
|
|
1833
|
+
const cAt = (mode, L, nativeC) => (opts?.chromaCurve ? opts.chromaCurve(L, mode) : nativeC) * (opts?.heat ?? 1);
|
|
1834
|
+
const archetype = forcedArchetype ?? classifyArchetype(brandL);
|
|
1835
|
+
const scaleL = forcedArchetype ? medianLForArchetype(forcedArchetype) : brandL;
|
|
1836
|
+
const darkFloorStrength = brandC >= DARK_FLOOR_MUTED_MAX_C ? 0 : Math.min(1, Math.max(0, (brandC - HUE_NOISE_C) / (DARK_FLOOR_FULL_C - HUE_NOISE_C)));
|
|
1837
|
+
const hueIsNoise = brandC < HUE_NOISE_C;
|
|
1838
|
+
const v2 = Math.min(1, brandC / VIVID_C);
|
|
1839
|
+
const vRef = Math.max(1e-6, Math.min(VIVID_C, medianGamutCAt(brandL)));
|
|
1840
|
+
const vSubtle = opts?.style === "full-chroma" ? subtleC / vRef : Math.min(1, subtleC / vRef);
|
|
1841
|
+
const S2 = hueIsNoise ? 0 : sigmoid(hueDelta(brandH, RED_TORSION_CENTER_H) / RED_TORSION_SOFTNESS);
|
|
1842
|
+
const redShift = hueIsNoise || opts?.suppressRedCool ? 0 : redRepelShiftDeg(brandH);
|
|
1843
|
+
const mutednessRaw = Math.min(1, (1 - v2) / MUTED_BLEND_DENOM);
|
|
1844
|
+
const creamGate = 1 - sigmoid((brandH - CREAM_UPPER_H) / CREAM_UPPER_SOFTNESS);
|
|
1845
|
+
const uRaw = S2 * creamGate * mutednessRaw;
|
|
1846
|
+
const bandGate = hueIsNoise ? 0 : sigmoid((brandH - DEEPER_BAND_H_LO) / DEEPER_BAND_H_SOFT) * (1 - sigmoid((brandH - DEEPER_BAND_H_HI) / DEEPER_BAND_H_SOFT)) * sigmoid((uRaw - DEEPER_BAND_U_LO) / DEEPER_BAND_U_SOFT) * (1 - sigmoid((uRaw - DEEPER_BAND_U_HI) / DEEPER_BAND_U_SOFT));
|
|
1847
|
+
const deeperEffect = opts?.style === "deeper" ? DEEPER_STRENGTH * bandGate : 0;
|
|
1848
|
+
const mutedness = mutednessRaw + deeperEffect * (1 - mutednessRaw);
|
|
1849
|
+
const u = S2 * creamGate * mutedness;
|
|
1850
|
+
const gWarm = gauss(hueDelta(brandH, 83), 28);
|
|
1851
|
+
const wDrift = S2 * (gWarm + (1 - gWarm) * mutedness * creamGate) * (1 - Math.min(1, Math.max(0, (brandH - LIGHT_DRIFT_COOL_HI) / LIGHT_DRIFT_COOL_RANGE)));
|
|
1852
|
+
const driftCapDeg = 24 + 8 * u;
|
|
1853
|
+
const chromaBoost = hueIsNoise || !opts?.goldBoost ? 1 : 1 + 1.7 * gauss(hueDelta(brandH, 90), 35) * S2 * (1 - (1 - creamGate) * (1 - v2));
|
|
1854
|
+
const brandSat = subtleC / Math.max(1e-6, maxChromaAt(brandL, brandH));
|
|
1855
|
+
const envW = u;
|
|
1856
|
+
const bellAt = (L) => {
|
|
1857
|
+
if (hueIsNoise || opts?.goldBoost) return 1;
|
|
1858
|
+
const ramp = Math.min(1, Math.max(0, (L - BRAND_BELL_L_LO) / (BRAND_BELL_L_HI - BRAND_BELL_L_LO)));
|
|
1859
|
+
const redExcl = 1 - gauss(hueDelta(brandH, BRAND_BELL_RED_H), BRAND_BELL_RED_SIGMA);
|
|
1860
|
+
return 1 + BRAND_BELL_AMOUNT * gauss(hueDelta(brandH, BRAND_BELL_H), BRAND_BELL_SIGMA) * S2 * v2 * ramp * redExcl;
|
|
1861
|
+
};
|
|
1862
|
+
const lightSpineRef = goldSpineHue(scaleL);
|
|
1863
|
+
const gOffPath = gauss(hueDelta(brandH, lightSpineRef), SPINE_OFFPATH_SIGMA);
|
|
1864
|
+
const lightHueAt = (L) => {
|
|
1865
|
+
const drift = 0.55 * (goldSpineHue(L) - lightSpineRef) * wDrift * gOffPath;
|
|
1866
|
+
return brandH + Math.max(-driftCapDeg, Math.min(driftCapDeg, drift)) + redShift;
|
|
1867
|
+
};
|
|
1868
|
+
const darkH = opts?.coolRedDark && !hueIsNoise && brandH > RED_BAND_LO_H ? brandH + redRepelShiftDeg(brandH) : brandH;
|
|
1869
|
+
const darkCtaH = brandH;
|
|
1870
|
+
return {
|
|
1871
|
+
hex,
|
|
1872
|
+
opts,
|
|
1873
|
+
brandL,
|
|
1874
|
+
brandC,
|
|
1875
|
+
brandH,
|
|
1876
|
+
subtleC,
|
|
1877
|
+
cAt,
|
|
1878
|
+
archetype,
|
|
1879
|
+
scaleL,
|
|
1880
|
+
darkFloorStrength,
|
|
1881
|
+
hueIsNoise,
|
|
1882
|
+
v: v2,
|
|
1883
|
+
vSubtle,
|
|
1884
|
+
u,
|
|
1885
|
+
envW,
|
|
1886
|
+
chromaBoost,
|
|
1887
|
+
brandSat,
|
|
1888
|
+
lightHueAt,
|
|
1889
|
+
bellAt,
|
|
1890
|
+
darkH,
|
|
1891
|
+
darkCtaH,
|
|
1892
|
+
gOffPath,
|
|
1893
|
+
redShift
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
var lightScaleChromaAt = (ctx, baseC, satFraction, boostOverride) => (L) => {
|
|
1897
|
+
const cLadder = ctx.vSubtle * (boostOverride ?? ctx.chromaBoost) * baseC * ctx.bellAt(L);
|
|
1898
|
+
const cEnv = ctx.brandSat * satFraction * maxChromaAt(L, ctx.lightHueAt(L));
|
|
1899
|
+
return ctx.cAt("light", L, cLadder + ctx.envW * (cEnv - cLadder));
|
|
1900
|
+
};
|
|
1901
|
+
function apcaYAt(L, C2, H2) {
|
|
1902
|
+
const [r, g2, b] = encodedChannels(L, C2, H2);
|
|
1903
|
+
return apcaY(r, g2, b);
|
|
1904
|
+
}
|
|
1905
|
+
function findMaxLForApcaLc(C2, H2, bgApcaY, targetLc) {
|
|
1906
|
+
let lo = 5e-3, hi = 0.999;
|
|
1907
|
+
for (let i = 0; i < 20; i++) {
|
|
1908
|
+
const mid = (lo + hi) / 2;
|
|
1909
|
+
Math.abs(apcaLc(apcaYAt(mid, clampChromaToGamut(mid, C2, H2), H2), bgApcaY)) >= targetLc ? lo = mid : hi = mid;
|
|
1910
|
+
}
|
|
1911
|
+
return lo;
|
|
1912
|
+
}
|
|
1913
|
+
var APCA_SOLVE_MARGIN_LC = 0.5;
|
|
1914
|
+
var APCA_TOL_LC = 0.05;
|
|
1915
|
+
function placeLightScale(ctx, rootL, chromaAt, maxLFor, targetChromaAt) {
|
|
1916
|
+
let L = perceptualRungL(rootL, chromaAt(rootL), ctx.lightHueAt(rootL), void 0, void 0, targetChromaAt?.(rootL));
|
|
1917
|
+
if (maxLFor !== void 0) {
|
|
1918
|
+
for (let pass = 0; pass < 6; pass++) {
|
|
1919
|
+
const next = Math.min(L, maxLFor(chromaAt(L), ctx.lightHueAt(L)));
|
|
1920
|
+
if (Math.abs(next - L) < 1e-4) {
|
|
1921
|
+
L = next;
|
|
1922
|
+
break;
|
|
1923
|
+
}
|
|
1924
|
+
L = next;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return { L, C: chromaAt(L), H: ctx.lightHueAt(L) };
|
|
1928
|
+
}
|
|
1929
|
+
function separationClampLight(ctx, placed, chromaAt, ref, target) {
|
|
1930
|
+
const rad2 = (h) => h * Math.PI / 180;
|
|
1931
|
+
const refA = ref.C * Math.cos(rad2(ref.H)), refB = ref.C * Math.sin(rad2(ref.H));
|
|
1932
|
+
const dEat = (L, C2) => {
|
|
1933
|
+
const H2 = ctx.lightHueAt(L);
|
|
1934
|
+
const gC = clampChromaToGamut(L, C2, H2);
|
|
1935
|
+
const a = gC * Math.cos(rad2(H2)), b = gC * Math.sin(rad2(H2));
|
|
1936
|
+
return Math.sqrt((L - ref.L) ** 2 + (a - refA) ** 2 + (b - refB) ** 2);
|
|
1937
|
+
};
|
|
1938
|
+
if (dEat(placed.L, chromaAt(placed.L)) >= target) return placed;
|
|
1939
|
+
const margin = target + 5e-4;
|
|
1940
|
+
const ceilingAt = (L) => {
|
|
1941
|
+
const H2 = ctx.lightHueAt(L);
|
|
1942
|
+
return clampChromaToGamut(L, Math.max(chromaAt(L), ctx.brandSat * maxChromaAt(L, H2)), H2);
|
|
1943
|
+
};
|
|
1944
|
+
if (dEat(placed.L, ceilingAt(placed.L)) >= margin) {
|
|
1945
|
+
let loC = chromaAt(placed.L), hiC = ceilingAt(placed.L);
|
|
1946
|
+
for (let i = 0; i < 30; i++) {
|
|
1947
|
+
const m = (loC + hiC) / 2;
|
|
1948
|
+
dEat(placed.L, m) >= margin ? hiC = m : loC = m;
|
|
1949
|
+
}
|
|
1950
|
+
return { L: placed.L, C: hiC, H: ctx.lightHueAt(placed.L) };
|
|
1951
|
+
}
|
|
1952
|
+
let fail = placed.L, pass = placed.L - 0.12;
|
|
1953
|
+
const clearsCeil = (L) => dEat(L, ceilingAt(L)) >= margin;
|
|
1954
|
+
for (let guard = 0; guard < 4 && !clearsCeil(pass); guard++) pass -= 0.12;
|
|
1955
|
+
for (let i = 0; i < 30; i++) {
|
|
1956
|
+
const m = (fail + pass) / 2;
|
|
1957
|
+
clearsCeil(m) ? pass = m : fail = m;
|
|
1958
|
+
}
|
|
1959
|
+
return { L: pass, C: ceilingAt(pass), H: ctx.lightHueAt(pass) };
|
|
1960
|
+
}
|
|
1961
|
+
function placeLightText(ctx, rootL, cMult, maxLFor, deepen, maxC = Infinity) {
|
|
1962
|
+
const inkC = Math.min(cMult * ctx.brandC, maxC);
|
|
1963
|
+
const anchorL = perceptualRungL(rootL, clampChromaToGamut(rootL, inkC, ctx.lightHueAt(rootL)), ctx.lightHueAt(rootL));
|
|
1964
|
+
let H2 = ctx.lightHueAt(anchorL);
|
|
1965
|
+
let C2 = clampChromaToGamut(anchorL, inkC, H2);
|
|
1966
|
+
let L = Math.min(anchorL, maxLFor(C2, H2));
|
|
1967
|
+
H2 = ctx.lightHueAt(L);
|
|
1968
|
+
C2 = clampChromaToGamut(L, inkC, H2);
|
|
1969
|
+
L = Math.min(anchorL, maxLFor(C2, H2));
|
|
1970
|
+
L = Math.min(L - deepen, maxLFor(C2, ctx.lightHueAt(L - deepen)));
|
|
1971
|
+
return { L, C: ctx.cAt("light", L, inkC), H: ctx.lightHueAt(L) };
|
|
1972
|
+
}
|
|
1973
|
+
function onFillIsWhiteLight(ctx, enforce, ratioFloor) {
|
|
1974
|
+
const fill9ApcaY = apcaYAt(ctx.scaleL, clampChromaToGamut(ctx.scaleL, ctx.cAt("light", ctx.scaleL, ctx.brandC), ctx.brandH), ctx.brandH);
|
|
1975
|
+
return onTextIsWhite(fill9ApcaY, ctx.scaleL, ctx.brandC, ctx.brandH, enforce, ratioFloor);
|
|
1976
|
+
}
|
|
1977
|
+
function buildDarkContext(ctx, specFloorL = DARK_CTA_MIN_L) {
|
|
1978
|
+
const opts = ctx.opts;
|
|
1979
|
+
const dark9L = Math.max(ctx.scaleL, opts?.darkFillMinL ?? specFloorL);
|
|
1980
|
+
const darkC9 = opts?.darkChromaCurve && DARK_CTA_C[opts?.darkCtaC ?? "brand"].policy === "trimmed" && opts?.style !== "full-chroma" ? ctx.brandC * darkCtaTrim(ctx.darkH) : ctx.brandC;
|
|
1981
|
+
const darkHueAtL = (L) => torsionedHue(ctx.darkH, L, dark9L, ctx.gOffPath);
|
|
1982
|
+
return { dark9L, darkC9, darkHueAtL };
|
|
1983
|
+
}
|
|
1984
|
+
var darkScaleChromaAt = (ctx, dctx, stopIndex, multiplier) => (L) => ctx.cAt("dark", L, ctx.opts?.darkChromaCurve ? ctx.opts.darkChromaCurve(L, dctx.darkHueAtL(L), ctx.brandC, dctx.darkC9) : applyChromaFloor(ctx.subtleC, multiplier, chromaFloorBase(stopIndex), ctx.darkFloorStrength));
|
|
1985
|
+
var darkInkChromaAt = (ctx, dctx, floorBase, multiplier, maxC = Infinity) => (L) => ctx.cAt("dark", L, Math.min(applyChromaFloor(ctx.brandC, multiplier, floorBase, ctx.darkFloorStrength), maxC));
|
|
1986
|
+
function placeDark(dctx, rootL, chromaAt, lift = false) {
|
|
1987
|
+
let L = perceptualRungL(rootL, chromaAt(rootL), dctx.darkHueAtL(rootL));
|
|
1988
|
+
if (lift) L = Math.max(L, rootL);
|
|
1989
|
+
return { L, C: chromaAt(L), H: dctx.darkHueAtL(L) };
|
|
1990
|
+
}
|
|
1991
|
+
var DELTA_DARK_GROUND_APP = grayApparentL(0.178);
|
|
1992
|
+
var DELTA_LIGHT_GROUND_APP = grayApparentL(1);
|
|
1993
|
+
var DELTA_DARK_GROUND_LSTAR = lstarFromY(wcagY(0.178, 0, 0));
|
|
1994
|
+
function deltaDarkTargetL(lightColor, C2, H2, lift = 1) {
|
|
1995
|
+
const lightApp = apparentL(lightColor.L, clampChromaToGamut(lightColor.L, lightColor.C, lightColor.H), lightColor.H);
|
|
1996
|
+
const target = Math.min(
|
|
1997
|
+
DELTA_LIGHT_GROUND_APP - 0.5,
|
|
1998
|
+
Math.max(DELTA_DARK_GROUND_APP + 0.5, DELTA_DARK_GROUND_APP + lift * (DELTA_LIGHT_GROUND_APP - lightApp))
|
|
1999
|
+
);
|
|
2000
|
+
return solveLForApparent(target, C2, H2);
|
|
2001
|
+
}
|
|
2002
|
+
function deltaDarkPlace(lightStops, lightTwin, lift, _tStop, grayLightL) {
|
|
2003
|
+
const cl = clampChromaToGamut(lightTwin.L, lightTwin.C, lightTwin.H);
|
|
2004
|
+
const dApp = DELTA_LIGHT_GROUND_APP - apparentL(lightTwin.L, cl, lightTwin.H);
|
|
2005
|
+
const dLum = 100 - lstarFromY(wcagY(lightTwin.L, cl, lightTwin.H));
|
|
2006
|
+
const C2 = deltaLiftChroma(lightStops, lightTwin, lift * dLum / Math.max(1e-6, dApp));
|
|
2007
|
+
if (grayLightL !== void 0) {
|
|
2008
|
+
const dLumGray = 100 - lstarFromY(wcagY(grayLightL, 0, 0));
|
|
2009
|
+
const targetLstar = DELTA_DARK_GROUND_LSTAR + lift * dLumGray;
|
|
2010
|
+
let lo = 0.05, hi = 0.98;
|
|
2011
|
+
for (let i = 0; i < 40; i++) {
|
|
2012
|
+
const m = (lo + hi) / 2;
|
|
2013
|
+
lstarFromY(wcagY(m, clampChromaToGamut(m, C2, lightTwin.H), lightTwin.H)) < targetLstar ? lo = m : hi = m;
|
|
2014
|
+
}
|
|
2015
|
+
return { L: (lo + hi) / 2, C: C2 };
|
|
2016
|
+
}
|
|
2017
|
+
const target = Math.min(
|
|
2018
|
+
DELTA_LIGHT_GROUND_APP - 0.5,
|
|
2019
|
+
Math.max(DELTA_DARK_GROUND_APP + 0.5, DELTA_DARK_GROUND_APP + lift * dApp)
|
|
2020
|
+
);
|
|
2021
|
+
return { L: solveLForApparent(target, C2, lightTwin.H), C: C2 };
|
|
2022
|
+
}
|
|
2023
|
+
var yOfLstar = (l) => l > 8 ? ((l + 16) / 116) ** 3 : l / 903.2962962;
|
|
2024
|
+
var smoothedLifts = null;
|
|
2025
|
+
function smoothedBandLift(stop) {
|
|
2026
|
+
if (!smoothedLifts) {
|
|
2027
|
+
const yWhite = wcagY(1, 0, 0);
|
|
2028
|
+
const yGround = wcagY(PAPER0_DARK_ROOT_L, 0, 0);
|
|
2029
|
+
const cLight = (n) => (yWhite + 0.05) / (wcagY(ROOT_L_LIGHT[n], 0, 0) + 0.05);
|
|
2030
|
+
const depth = (n) => 100 - lstarFromY(wcagY(ROOT_L_LIGHT[n], 0, 0));
|
|
2031
|
+
const topLstar = DELTA_DARK_GROUND_LSTAR + DARK_BAND_TOP_LIFT * depth(7);
|
|
2032
|
+
const cTop = (yOfLstar(topLstar) + 0.05) / (yGround + 0.05);
|
|
2033
|
+
const lifts = {};
|
|
2034
|
+
for (let n = 1; n <= 7; n++) {
|
|
2035
|
+
const t = Math.log(cLight(n)) / Math.log(cLight(7));
|
|
2036
|
+
const yN = Math.exp(t * Math.log(cTop)) * (yGround + 0.05) - 0.05;
|
|
2037
|
+
lifts[n] = (lstarFromY(yN) - DELTA_DARK_GROUND_LSTAR) / depth(n);
|
|
2038
|
+
}
|
|
2039
|
+
smoothedLifts = lifts;
|
|
2040
|
+
}
|
|
2041
|
+
return smoothedLifts[stop] ?? 1;
|
|
2042
|
+
}
|
|
2043
|
+
function deltaLiftChroma(lightStops, lightTwin, lift) {
|
|
2044
|
+
const appOf = (s) => apparentL(s.L, clampChromaToGamut(s.L, s.C, s.H), s.H);
|
|
2045
|
+
const pairs = lightStops.filter((s) => s.stop >= 1 && s.stop <= 7).map((s) => ({ d: DELTA_LIGHT_GROUND_APP - appOf(s), C: s.C })).sort((a2, b2) => a2.d - b2.d);
|
|
2046
|
+
if (!pairs.length) return lightTwin.C;
|
|
2047
|
+
const target = lift * (DELTA_LIGHT_GROUND_APP - appOf(lightTwin));
|
|
2048
|
+
if (target <= pairs[0].d) return pairs[0].C;
|
|
2049
|
+
for (let k = 1; k < pairs.length; k++)
|
|
2050
|
+
if (target <= pairs[k].d) {
|
|
2051
|
+
const t = (target - pairs[k - 1].d) / (pairs[k].d - pairs[k - 1].d);
|
|
2052
|
+
return pairs[k - 1].C + t * (pairs[k].C - pairs[k - 1].C);
|
|
2053
|
+
}
|
|
2054
|
+
const a = pairs[pairs.length - 2], b = pairs[pairs.length - 1];
|
|
2055
|
+
if (!a || b.d - a.d < 1e-6) return b.C;
|
|
2056
|
+
if (b.C <= a.C) return b.C;
|
|
2057
|
+
return b.C + (target - b.d) / (b.d - a.d) * (b.C - a.C);
|
|
2058
|
+
}
|
|
2059
|
+
function placeDarkDelta(dctx, rootL, chromaAt, lightStop) {
|
|
2060
|
+
const H02 = dctx.darkHueAtL(rootL);
|
|
2061
|
+
const C02 = chromaAt(rootL);
|
|
2062
|
+
const L = deltaDarkTargetL(lightStop, C02, H02);
|
|
2063
|
+
return { L, C: chromaAt(L), H: dctx.darkHueAtL(L) };
|
|
2064
|
+
}
|
|
2065
|
+
function flatDarkCtaL(dctx, chromaAt, H2, gapApp) {
|
|
2066
|
+
const target = DELTA_DARK_GROUND_APP + gapApp;
|
|
2067
|
+
let L = solveLForApparent(target, chromaAt(dctx.dark9L), H2);
|
|
2068
|
+
L = solveLForApparent(target, chromaAt(L), H2);
|
|
2069
|
+
return L;
|
|
2070
|
+
}
|
|
2071
|
+
function ctaLightL(ctx, onFillTextIsWhite, enforce) {
|
|
2072
|
+
let light9L = ctx.scaleL;
|
|
2073
|
+
if (enforce && onFillTextIsWhite && legalRatio(ctx.scaleL, ctx.brandC, ctx.brandH, 1) < 4.5) {
|
|
2074
|
+
light9L = findLForContrast(ctx.scaleL, ctx.brandC, ctx.brandH, 1, 4.6);
|
|
2075
|
+
}
|
|
2076
|
+
return light9L;
|
|
2077
|
+
}
|
|
2078
|
+
function ctaDarkEnforcedL(ctx, base, onFillIsWhiteDark, enforce) {
|
|
2079
|
+
if (!(enforce && onFillIsWhiteDark)) return null;
|
|
2080
|
+
if (legalRatio(base.L, base.C, base.H, 1) >= 4.5) return null;
|
|
2081
|
+
return findLForContrast(base.L, base.C, ctx.darkCtaH, 1, 4.6);
|
|
2082
|
+
}
|
|
2083
|
+
var whiteTextLcAt = (L, C2, H2) => Math.abs(apcaLc(1, apcaYAt(L, clampChromaToGamut(L, C2, H2), H2)));
|
|
2084
|
+
function findLForWhiteTextLc(startL, C2, H2, targetLc) {
|
|
2085
|
+
if (whiteTextLcAt(startL, C2, H2) >= targetLc) return startL;
|
|
2086
|
+
let lo = 0.05, hi = startL;
|
|
2087
|
+
for (let i = 0; i < 20; i++) {
|
|
2088
|
+
const mid = (lo + hi) / 2;
|
|
2089
|
+
whiteTextLcAt(mid, C2, H2) >= targetLc ? lo = mid : hi = mid;
|
|
2090
|
+
}
|
|
2091
|
+
return lo;
|
|
2092
|
+
}
|
|
2093
|
+
var APCA_ENFORCE_MARGIN_LC = 2;
|
|
2094
|
+
function ctaLightLApca(ctx, onFillTextIsWhite, enforce, thresholdLc) {
|
|
2095
|
+
if (!enforce) return ctx.scaleL;
|
|
2096
|
+
const fireLc = thresholdLc + APCA_ENFORCE_MARGIN_LC;
|
|
2097
|
+
if (onFillTextIsWhite) {
|
|
2098
|
+
if (whiteTextLcAt(ctx.scaleL, ctx.brandC, ctx.brandH) >= fireLc) return ctx.scaleL;
|
|
2099
|
+
return findLForWhiteTextLc(ctx.scaleL, ctx.brandC, ctx.brandH, fireLc + APCA_SOLVE_MARGIN_LC);
|
|
2100
|
+
}
|
|
2101
|
+
if (blackTextLcAt(ctx.scaleL, ctx.brandC, ctx.brandH) >= fireLc) return ctx.scaleL;
|
|
2102
|
+
return findLForBlackTextLc(ctx.scaleL, ctx.brandC, ctx.brandH, fireLc + APCA_SOLVE_MARGIN_LC, 0.92);
|
|
2103
|
+
}
|
|
2104
|
+
function ctaDarkEnforcedLApca(ctx, base, onFillIsWhiteDark, enforce, thresholdLc) {
|
|
2105
|
+
if (!enforce) return null;
|
|
2106
|
+
const fireLc = thresholdLc + APCA_ENFORCE_MARGIN_LC;
|
|
2107
|
+
if (onFillIsWhiteDark) {
|
|
2108
|
+
if (whiteTextLcAt(base.L, base.C, base.H) >= fireLc) return null;
|
|
2109
|
+
return findLForWhiteTextLc(base.L, base.C, ctx.darkCtaH, fireLc + APCA_SOLVE_MARGIN_LC);
|
|
2110
|
+
}
|
|
2111
|
+
if (blackTextLcAt(base.L, base.C, base.H) >= fireLc) return null;
|
|
2112
|
+
return findLForBlackTextLc(base.L, base.C, ctx.darkCtaH, fireLc + APCA_SOLVE_MARGIN_LC, 0.92);
|
|
2113
|
+
}
|
|
2114
|
+
var blackTextLcAt = (L, C2, H2) => Math.abs(apcaLc(apcaYAt(0, 0, 0), apcaYAt(L, clampChromaToGamut(L, C2, H2), H2)));
|
|
2115
|
+
function findLForBlackTextLc(startL, C2, H2, targetLc, capHiL) {
|
|
2116
|
+
if (blackTextLcAt(startL, C2, H2) >= targetLc) return startL;
|
|
2117
|
+
if (capHiL <= startL || blackTextLcAt(capHiL, C2, H2) < targetLc) return capHiL;
|
|
2118
|
+
let lo = startL, hi = capHiL;
|
|
2119
|
+
for (let i = 0; i < 20; i++) {
|
|
2120
|
+
const mid = (lo + hi) / 2;
|
|
2121
|
+
blackTextLcAt(mid, C2, H2) >= targetLc ? hi = mid : lo = mid;
|
|
2122
|
+
}
|
|
2123
|
+
return hi;
|
|
2124
|
+
}
|
|
2125
|
+
function ctaDualGateL(ctx, onFillTextIsWhite, enforce, coLc, capLoL, capHiL) {
|
|
2126
|
+
if (!enforce) return ctx.scaleL;
|
|
2127
|
+
const startL = ctx.scaleL, C2 = ctx.brandC, H2 = ctx.brandH;
|
|
2128
|
+
if (onFillTextIsWhite) {
|
|
2129
|
+
const l452 = legalRatio(startL, C2, H2, 1) >= 4.5 ? startL : findLForContrast(startL, C2, H2, 1, 4.6);
|
|
2130
|
+
const lLc2 = findLForWhiteTextLc(startL, C2, H2, coLc + APCA_SOLVE_MARGIN_LC);
|
|
2131
|
+
return Math.min(l452, Math.max(lLc2, capLoL));
|
|
2132
|
+
}
|
|
2133
|
+
const l45 = legalRatio(startL, C2, H2, 0) >= 4.5 ? startL : findLForContrastUp(startL, C2, H2, 0, 4.6);
|
|
2134
|
+
const lLc = findLForBlackTextLc(startL, C2, H2, coLc + APCA_SOLVE_MARGIN_LC, capHiL);
|
|
2135
|
+
return Math.max(l45, Math.min(lLc, capHiL));
|
|
2136
|
+
}
|
|
2137
|
+
function ctaDarkDualGateL(base, darkH, onFillTextIsWhite, enforce, coLc, capLoL, capHiL) {
|
|
2138
|
+
if (!enforce) return base.L;
|
|
2139
|
+
const { L: startL, C: C2 } = base;
|
|
2140
|
+
if (onFillTextIsWhite) {
|
|
2141
|
+
const l452 = legalRatio(startL, C2, darkH, 1) >= 4.5 ? startL : findLForContrast(startL, C2, darkH, 1, 4.6);
|
|
2142
|
+
const lLc2 = findLForWhiteTextLc(startL, C2, darkH, coLc + APCA_SOLVE_MARGIN_LC);
|
|
2143
|
+
return Math.min(l452, Math.max(lLc2, capLoL));
|
|
2144
|
+
}
|
|
2145
|
+
const l45 = legalRatio(startL, C2, darkH, 0) >= 4.5 ? startL : findLForContrastUp(startL, C2, darkH, 0, 4.6);
|
|
2146
|
+
const lLc = findLForBlackTextLc(startL, C2, darkH, coLc + APCA_SOLVE_MARGIN_LC, capHiL);
|
|
2147
|
+
return Math.max(l45, Math.min(lLc, capHiL));
|
|
2148
|
+
}
|
|
2149
|
+
function onFillIsWhiteDarkAt(L, C2, H2, enforce, ratioFloor) {
|
|
2150
|
+
return onTextIsWhite(apcaYAt(L, C2, H2), L, C2, H2, enforce, ratioFloor);
|
|
2151
|
+
}
|
|
2152
|
+
function solveBrandExit(seed, cFor, brandH, red, enforceLc, coLc) {
|
|
2153
|
+
const at = (L) => ({ L, C: clampChromaToGamut(L, cFor(L), brandH), H: brandH });
|
|
2154
|
+
const blackLc = (L, C2, H2) => Math.abs(apcaLc(apcaYAt(0, 0, 0), apcaYAt(L, clampChromaToGamut(L, C2, H2), H2)));
|
|
2155
|
+
const decisionBar = (enforceLc ?? CTA_ONFILL_ENFORCE_LC) + APCA_ENFORCE_MARGIN_LC;
|
|
2156
|
+
const apcaOk = (L, C2, H2) => whiteTextLcAt(L, C2, H2) >= decisionBar || blackLc(L, C2, H2) >= decisionBar;
|
|
2157
|
+
const lawOk = (L, C2, H2) => {
|
|
2158
|
+
if (enforceLc !== void 0) return true;
|
|
2159
|
+
if (coLc === void 0) return legalRatio(L, C2, H2, 1) >= 4.5 || legalRatio(L, C2, H2, 0) >= 4.5;
|
|
2160
|
+
const bar = coLc + APCA_ENFORCE_MARGIN_LC;
|
|
2161
|
+
return legalRatio(L, C2, H2, 1) >= 4.5 && whiteTextLcAt(L, C2, H2) >= bar || legalRatio(L, C2, H2, 0) >= 4.5 && blackLc(L, C2, H2) >= bar;
|
|
2162
|
+
};
|
|
2163
|
+
const anchor = at(seed.L);
|
|
2164
|
+
const dh = hueDelta(seed.H, red.H);
|
|
2165
|
+
const member = redSolveDist(anchor, red) <= RED_GATE.G || inBrickBand(seed.L, anchor.C, brandH) && dh > RED_SOLVE.magentaDh;
|
|
2166
|
+
if (!member) return null;
|
|
2167
|
+
const release = RED_GATE.G + RED_SOLVE.ring;
|
|
2168
|
+
const travel = (dir2, withLaw = false) => {
|
|
2169
|
+
for (let L = seed.L; L >= 0.28 && L <= 0.92; L += dir2 * 2e-3) {
|
|
2170
|
+
const c = at(L);
|
|
2171
|
+
if (redSolveDist(c, red) >= release && apcaOk(L, c.C, c.H) && (!withLaw || lawOk(L, c.C, c.H))) return Math.abs(L - seed.L);
|
|
2172
|
+
}
|
|
2173
|
+
return null;
|
|
2174
|
+
};
|
|
2175
|
+
const dn = travel(-1), up = travel(1);
|
|
2176
|
+
if (dn === null && up === null) return { L: 0.03, H: brandH, cMul: 1, up: false };
|
|
2177
|
+
const vivid = seed.C / Math.max(1e-6, maxChromaAt(seed.L, seed.H));
|
|
2178
|
+
const onHue = dh > RED_SOLVE.magentaDh && dh < RED_SOLVE.goldDh;
|
|
2179
|
+
const magentaUp = dh <= RED_SOLVE.magentaDh && seed.L > RED_SOLVE.magentaMinL && up !== null && dn !== null && up <= RED_SOLVE.magentaUpRatio * dn;
|
|
2180
|
+
const goldBright = !magentaUp && vivid >= RED_SOLVE.vividMin && dh >= RED_SOLVE.goldDh && seed.L > RED_SOLVE.vividMinL && up !== null;
|
|
2181
|
+
const vividDown = !magentaUp && !goldBright && vivid >= RED_SOLVE.vividMin && dh > RED_SOLVE.magentaDh && dn !== null;
|
|
2182
|
+
const nearDark = dn !== null && (up === null || dn <= up);
|
|
2183
|
+
const trueRedDim = onHue && !magentaUp && !goldBright && !vividDown && !nearDark && up !== null && dn !== null && seed.L + up < RED_SOLVE.trueRedBrightCut;
|
|
2184
|
+
const dir = magentaUp || goldBright ? 1 : vividDown || trueRedDim ? -1 : nearDark ? -1 : 1;
|
|
2185
|
+
let landDir = dir;
|
|
2186
|
+
let t = travel(dir, true);
|
|
2187
|
+
if (t === null) {
|
|
2188
|
+
landDir = dir === 1 ? -1 : 1;
|
|
2189
|
+
t = travel(landDir, true);
|
|
2190
|
+
if (t === null) return { L: 0.03, H: brandH, cMul: 1, up: false };
|
|
2191
|
+
}
|
|
2192
|
+
let landing = { L: seed.L + landDir * t, H: brandH, cMul: 1, up: landDir === 1 };
|
|
2193
|
+
if (landDir === -1) {
|
|
2194
|
+
const l = at(landing.L);
|
|
2195
|
+
if (inBrickBand(l.L, l.C, l.H)) {
|
|
2196
|
+
landing = {
|
|
2197
|
+
L: landing.L - RED_SOLVE.brickExtraDeep,
|
|
2198
|
+
H: brandH + (dh >= RED_SOLVE.goldDh ? RED_SOLVE.brickCoolGold : RED_SOLVE.brickCool),
|
|
2199
|
+
cMul: RED_SOLVE.brickDesat,
|
|
2200
|
+
up: false
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
return landing;
|
|
2205
|
+
}
|
|
2206
|
+
function solveDarkCtaExit(cur, cFor, darkH, redDark, enforceLc, coLc) {
|
|
2207
|
+
const at = (L) => ({ L, C: clampChromaToGamut(L, cFor(L), darkH), H: darkH });
|
|
2208
|
+
if (p2Diff(cur, redDark) >= P2_D) return null;
|
|
2209
|
+
const decisionBar = (enforceLc ?? CTA_ONFILL_ENFORCE_LC) + APCA_ENFORCE_MARGIN_LC;
|
|
2210
|
+
const apcaOk = (L, C2, H2) => whiteTextLcAt(L, C2, H2) >= decisionBar || blackTextLcAt(L, C2, H2) >= decisionBar;
|
|
2211
|
+
const lawOk = (L, C2, H2) => {
|
|
2212
|
+
if (enforceLc !== void 0) return true;
|
|
2213
|
+
if (coLc === void 0) return legalRatio(L, C2, H2, 1) >= 4.5 || legalRatio(L, C2, H2, 0) >= 4.5;
|
|
2214
|
+
const bar = coLc + APCA_ENFORCE_MARGIN_LC;
|
|
2215
|
+
return legalRatio(L, C2, H2, 1) >= 4.5 && whiteTextLcAt(L, C2, H2) >= bar || legalRatio(L, C2, H2, 0) >= 4.5 && blackTextLcAt(L, C2, H2) >= bar;
|
|
2216
|
+
};
|
|
2217
|
+
const travel = (dir2, withLaw = false) => {
|
|
2218
|
+
for (let L = cur.L; L >= 0.28 && L <= 0.92; L += dir2 * 2e-3) {
|
|
2219
|
+
const c = at(L);
|
|
2220
|
+
if (p2Diff(c, redDark) >= P2_D && apcaOk(L, c.C, c.H) && (!withLaw || lawOk(L, c.C, c.H))) return Math.abs(L - cur.L);
|
|
2221
|
+
}
|
|
2222
|
+
return null;
|
|
2223
|
+
};
|
|
2224
|
+
const dn = travel(-1), up = travel(1);
|
|
2225
|
+
if (dn === null && up === null) return null;
|
|
2226
|
+
const dir = dn !== null && (up === null || dn <= up) ? -1 : 1;
|
|
2227
|
+
let landDir = dir;
|
|
2228
|
+
let t = travel(dir, true);
|
|
2229
|
+
if (t === null) {
|
|
2230
|
+
landDir = dir === 1 ? -1 : 1;
|
|
2231
|
+
t = travel(landDir, true);
|
|
2232
|
+
}
|
|
2233
|
+
if (t === null) return null;
|
|
2234
|
+
return cur.L + landDir * t;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/engine/requirements/resolve.ts
|
|
2238
|
+
var oklchToSrgb = (L, C2, H2) => Object.values(srgbEmitChannels({ L, C: C2, H: H2 })).map((c) => Math.max(0, Math.min(1, c)));
|
|
2239
|
+
var toHex = (rgb) => "#" + rgb.map((c) => Math.round(c * 255).toString(16).padStart(2, "0")).join("");
|
|
2240
|
+
var CTA_CLEARANCE_CAPS = [0.05, 0.92];
|
|
2241
|
+
function resolveRamp(hex, mode, spec, opts) {
|
|
2242
|
+
const o = hexToOklch(hex);
|
|
2243
|
+
const seed = { hex, ...o };
|
|
2244
|
+
const ctx = buildContext(hex, opts);
|
|
2245
|
+
spec ?? (spec = MODE_SPECS[mode]);
|
|
2246
|
+
if (ctx.opts?.inkGround) {
|
|
2247
|
+
spec = {
|
|
2248
|
+
...spec,
|
|
2249
|
+
stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
const ctaReq = spec.roles.find((r) => r.role === "cta");
|
|
2253
|
+
const hoverReq = spec.roles.find((r) => r.role === "cta-hover");
|
|
2254
|
+
const pressedReq = spec.roles.find((r) => r.role === "cta-pressed");
|
|
2255
|
+
if (!ctaReq) throw new Error("spec has no cta role");
|
|
2256
|
+
const onFillEnforce = ctx.opts?.enforceOnFillContrast ?? spec.ons.onFill.enforce;
|
|
2257
|
+
const dctx = mode === "dark" ? buildDarkContext(ctx, ctaReq.floorL) : null;
|
|
2258
|
+
const stops = [];
|
|
2259
|
+
const emit = (stop, group, L, C2, H2, clamped = false, unresolvable) => {
|
|
2260
|
+
const gC = clampChromaToGamut(L, C2, H2);
|
|
2261
|
+
return { stop, group, L, C: gC, H: H2, hex: toHex(oklchToSrgb(L, gC, H2)), Y: wcagY(L, gC, H2), appL: apparentL(L, gC, H2), clamped, unresolvable };
|
|
2262
|
+
};
|
|
2263
|
+
const refOf = (stopNum, forWhom) => {
|
|
2264
|
+
const ref = stops.find((s) => s.stop === stopNum);
|
|
2265
|
+
if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
|
|
2266
|
+
return ref;
|
|
2267
|
+
};
|
|
2268
|
+
const AGAINST_STOP = { "paper-99": 1, "paper-97": 2, "paper-95": 3, "wash-80": 7 };
|
|
2269
|
+
const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
|
|
2270
|
+
const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
|
|
2271
|
+
const inkGround = ctx.opts?.inkGround?.[mode];
|
|
2272
|
+
const groundOf = (req, stop) => inkGround && stop >= 9 ? inkGround : refOf(wcagAnchorStop(req, stop), stop);
|
|
2273
|
+
const apcaGroundOf = (req, stop) => inkGround && stop >= 9 ? inkGround : refOf(declaredAnchor(req), stop);
|
|
2274
|
+
const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2275
|
+
const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2276
|
+
const maxLForOf = (req, forWhom, withMargin) => {
|
|
2277
|
+
if (req.metric === "wcag") {
|
|
2278
|
+
const g2 = groundOf(req, forWhom);
|
|
2279
|
+
const refY = wcagY(g2.L, g2.C, g2.H);
|
|
2280
|
+
const t = withMargin ? req.target + 0.05 : req.target;
|
|
2281
|
+
return (C2, H2) => findMaxLForContrast(C2, H2, refY, t);
|
|
2282
|
+
}
|
|
2283
|
+
if (req.metric === "apca") {
|
|
2284
|
+
const ga = apcaGroundOf(req, forWhom);
|
|
2285
|
+
const refA = apcaYAt(ga.L, ga.C, ga.H);
|
|
2286
|
+
const t = withMargin ? req.targetLc + APCA_SOLVE_MARGIN_LC : req.targetLc;
|
|
2287
|
+
return (C2, H2) => findMaxLForApcaLc(C2, H2, refA, t);
|
|
2288
|
+
}
|
|
2289
|
+
throw new Error(`stop ${forWhom}: ${req.metric} is not a contrast require`);
|
|
2290
|
+
};
|
|
2291
|
+
const INK_DEEPEN = 0;
|
|
2292
|
+
for (const sp of spec.stops) {
|
|
2293
|
+
let placed;
|
|
2294
|
+
let clamped = false, unresolvable;
|
|
2295
|
+
if (mode === "light") {
|
|
2296
|
+
if (sp.group === "ink") {
|
|
2297
|
+
if (!sp.require) throw new Error(`light ink stop ${sp.stop} must declare a contrast require`);
|
|
2298
|
+
placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false), INK_DEEPEN, sp.inkMaxC);
|
|
2299
|
+
clamped = true;
|
|
2300
|
+
} else if (sp.produce.L === "fixed") {
|
|
2301
|
+
const chromaAt = lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1);
|
|
2302
|
+
placed = { L: sp.rootL, C: chromaAt(sp.rootL), H: ctx.lightHueAt(sp.rootL) };
|
|
2303
|
+
} else {
|
|
2304
|
+
const chromaAt = lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1);
|
|
2305
|
+
const contrastReq = sp.require && sp.require.metric !== "min-separation" ? sp.require : void 0;
|
|
2306
|
+
const targetChromaAt = ctx.chromaBoost !== 1 ? lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1, 1) : void 0;
|
|
2307
|
+
placed = placeLightScale(ctx, sp.rootL, chromaAt, contrastReq ? maxLForOf(contrastReq, sp.stop, true) : void 0, targetChromaAt);
|
|
2308
|
+
clamped = !!contrastReq;
|
|
2309
|
+
if (sp.require?.metric === "min-separation") {
|
|
2310
|
+
const refStop = sp.require.against === "paper-99" ? 1 : sp.stop - 1;
|
|
2311
|
+
const ref = stops.find((s) => s.stop === refStop);
|
|
2312
|
+
if (!ref) throw new Error(`stop ${sp.stop}: min-separation against stop ${refStop} but it is not resolved yet`);
|
|
2313
|
+
const before = placed.L;
|
|
2314
|
+
placed = separationClampLight(ctx, placed, chromaAt, ref, sp.require.target);
|
|
2315
|
+
clamped = placed.L !== before;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
} else {
|
|
2319
|
+
const d = dctx;
|
|
2320
|
+
const inkTwin = sp.group === "ink" && ctx.opts?.deltaCarry && ctx.opts?.chromaCurve ? ctx.opts.deltaLightStops?.find((s) => s.stop === sp.stop) : void 0;
|
|
2321
|
+
const chromaAt = sp.group === "ink" ? inkTwin ? ((_L) => inkTwin.C) : darkInkChromaAt(ctx, d, sp.chromaFloor ?? chromaFloorBase(sp.stop), sp.chromaMult ?? 1, sp.inkMaxC) : darkScaleChromaAt(ctx, d, Math.max(0, sp.stop - 1), sp.satFraction ?? 1);
|
|
2322
|
+
let spineH = null;
|
|
2323
|
+
const dl = ctx.opts?.deltaLightStops;
|
|
2324
|
+
const ls = dl && sp.stop >= 1 && sp.stop <= 8 ? dl.find((s) => s.stop === sp.stop) : void 0;
|
|
2325
|
+
if (ls && ctx.opts?.deltaCarry) {
|
|
2326
|
+
const lift = smoothedBandLift(sp.stop);
|
|
2327
|
+
let C2 = ls.C;
|
|
2328
|
+
let L;
|
|
2329
|
+
if (sp.require && sp.require.metric !== "min-separation") L = 0.05;
|
|
2330
|
+
else if (sp.stop >= 1 && sp.stop <= 7) {
|
|
2331
|
+
const p = deltaDarkPlace(dl, ls, lift, 0, ROOT_L_LIGHT[sp.stop]);
|
|
2332
|
+
L = p.L;
|
|
2333
|
+
C2 = p.C;
|
|
2334
|
+
} else L = deltaDarkTargetL(ls, C2, ls.H);
|
|
2335
|
+
if (ctx.opts?.signalWarmDrift) {
|
|
2336
|
+
const h = ls.H + DARK_SIGNAL_WARM_DRIFT * (ctx.lightHueAt(L) - ls.H);
|
|
2337
|
+
if (Math.abs(h - ls.H) > 1e-9) spineH = h;
|
|
2338
|
+
}
|
|
2339
|
+
if (ctx.opts.deltaHKPlace) L = perceptualRungL(sp.rootL, ls.C, ls.H);
|
|
2340
|
+
if (ctx.opts.deltaLiftFloor) L = Math.max(L, sp.rootL);
|
|
2341
|
+
if (ctx.opts.deltaChromaEq && sp.group !== "ink") C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
|
|
2342
|
+
placed = { L, C: C2, H: spineH ?? ls.H };
|
|
2343
|
+
} else if (sp.produce.L === "fixed") {
|
|
2344
|
+
placed = ls ? placeDarkDelta(d, sp.rootL, chromaAt, ls) : { L: sp.rootL, C: chromaAt(sp.rootL), H: d.darkHueAtL(sp.rootL) };
|
|
2345
|
+
} else {
|
|
2346
|
+
placed = ls ? placeDarkDelta(d, sp.rootL, chromaAt, ls) : placeDark(d, sp.rootL, chromaAt, sp.produce.L === "perceptual-lift");
|
|
2347
|
+
}
|
|
2348
|
+
if (sp.require && sp.require.metric !== "min-separation") {
|
|
2349
|
+
const req = sp.require;
|
|
2350
|
+
const carryReq = !!(ls && ctx.opts?.deltaCarry);
|
|
2351
|
+
const hAtL = (L) => carryReq ? spineH ?? ls.H : d.darkHueAtL(L);
|
|
2352
|
+
const cAtL = (L) => carryReq ? ls.C : chromaAt(L);
|
|
2353
|
+
const isApca = req.metric === "apca";
|
|
2354
|
+
const gMeas = isApca ? apcaGroundOf(req, sp.stop) : groundOf(req, sp.stop);
|
|
2355
|
+
const refMeasY = isApca ? apcaYAt(gMeas.L, gMeas.C, gMeas.H) : wcagY(gMeas.L, gMeas.C, gMeas.H);
|
|
2356
|
+
const measure = (L, C2, H2) => isApca ? Math.abs(apcaLc(apcaYAt(L, C2, H2), refMeasY)) : legalRatio(L, C2, H2, refMeasY);
|
|
2357
|
+
const reqTarget = isApca ? req.targetLc : req.target;
|
|
2358
|
+
const tol = isApca ? APCA_TOL_LC : 1e-5;
|
|
2359
|
+
const got0 = measure(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H);
|
|
2360
|
+
if (got0 < reqTarget - tol) {
|
|
2361
|
+
const target = reqTarget + (isApca ? APCA_SOLVE_MARGIN_LC : 0.05);
|
|
2362
|
+
let lo = placed.L, hi = 1;
|
|
2363
|
+
for (let pass = 0; pass < 24; pass++) {
|
|
2364
|
+
const m = (lo + hi) / 2;
|
|
2365
|
+
const mH = hAtL(m);
|
|
2366
|
+
const mC = isApca ? clampChromaToGamut(m, cAtL(m), mH) : cAtL(m);
|
|
2367
|
+
measure(m, mC, mH) < target ? lo = m : hi = m;
|
|
2368
|
+
}
|
|
2369
|
+
placed = { L: hi, C: cAtL(hi), H: hAtL(hi) };
|
|
2370
|
+
clamped = true;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
if (sp.require?.metric === "wcag") {
|
|
2375
|
+
const anchor = groundOf(sp.require, sp.stop);
|
|
2376
|
+
const anchorShipY = shippedY(anchor.L, anchor.C, anchor.H);
|
|
2377
|
+
const paperBound = sp.stop >= 8 && !(inkGround && sp.stop >= 9);
|
|
2378
|
+
const crossBoundY = wcagAnchorStop(sp.require, sp.stop) === 7 ? NEUTRAL_W80_WORST_SHIP_Y[mode] : NEUTRAL_P3_WORST_SHIP_Y[mode];
|
|
2379
|
+
const shipRatio = (L) => {
|
|
2380
|
+
const y2 = shippedY(L, placed.C, placed.H);
|
|
2381
|
+
const own = contrastRatio(y2, anchorShipY);
|
|
2382
|
+
return paperBound ? Math.min(own, contrastRatio(y2, crossBoundY)) : own;
|
|
2383
|
+
};
|
|
2384
|
+
if (shipRatio(placed.L) < sp.require.target) {
|
|
2385
|
+
const away = shippedY(placed.L, placed.C, placed.H) < anchorShipY ? -1 : 1;
|
|
2386
|
+
let L2 = placed.L;
|
|
2387
|
+
for (let i = 0; i < 100 && shipRatio(L2) < sp.require.target; i++) L2 += away * 1e-3;
|
|
2388
|
+
if (shipRatio(L2) >= sp.require.target) {
|
|
2389
|
+
placed = { ...placed, L: L2 };
|
|
2390
|
+
clamped = true;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
const refY = wcagY(anchor.L, anchor.C, anchor.H);
|
|
2394
|
+
const got = Math.min(
|
|
2395
|
+
legalRatio(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H, refY),
|
|
2396
|
+
shipRatio(placed.L)
|
|
2397
|
+
);
|
|
2398
|
+
if (got < sp.require.target - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${got.toFixed(2)} < required ${sp.require.target}`;
|
|
2399
|
+
} else if (sp.require?.metric === "apca") {
|
|
2400
|
+
const ga = apcaGroundOf(sp.require, sp.stop);
|
|
2401
|
+
const refA = apcaYAt(ga.L, ga.C, ga.H);
|
|
2402
|
+
const got = Math.abs(apcaLc(apcaYAt(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H), refA));
|
|
2403
|
+
if (got < sp.require.targetLc - APCA_TOL_LC) unresolvable = `stop ${sp.stop}: |Lc| ${got.toFixed(1)} < required ${sp.require.targetLc}`;
|
|
2404
|
+
} else if (sp.require?.metric === "min-separation") {
|
|
2405
|
+
const ref = stops.find((s) => s.stop === (sp.require.against === "paper-99" ? 1 : sp.stop - 1));
|
|
2406
|
+
const rad2 = (h) => h * Math.PI / 180;
|
|
2407
|
+
const gC = clampChromaToGamut(placed.L, placed.C, placed.H);
|
|
2408
|
+
const got = Math.sqrt((placed.L - ref.L) ** 2 + (gC * Math.cos(rad2(placed.H)) - ref.C * Math.cos(rad2(ref.H))) ** 2 + (gC * Math.sin(rad2(placed.H)) - ref.C * Math.sin(rad2(ref.H))) ** 2);
|
|
2409
|
+
if (got < sp.require.target - 1e-4) unresolvable = `stop ${sp.stop}: separation ${got.toFixed(4)} < required ${sp.require.target}`;
|
|
2410
|
+
}
|
|
2411
|
+
stops.push(emit(sp.stop, sp.group, placed.L, placed.C, placed.H, clamped, unresolvable));
|
|
2412
|
+
}
|
|
2413
|
+
const emitRole = (role, L, C2, H2) => {
|
|
2414
|
+
const gC = clampChromaToGamut(L, C2, H2);
|
|
2415
|
+
return { role, L, C: gC, H: H2, hex: toHex(oklchToSrgb(L, gC, H2)), Y: wcagY(L, gC, H2), appL: apparentL(L, gC, H2) };
|
|
2416
|
+
};
|
|
2417
|
+
const enforceLc = spec.ons.onFill.enforceLc;
|
|
2418
|
+
const coLc = ctx.opts?.apcaClearance && enforceLc === void 0 ? ctx.opts.apcaClearanceLc ?? spec.ons.onFill.coEnforceLc : void 0;
|
|
2419
|
+
const onFloor = enforceLc !== void 0 ? void 0 : spec.ons.onFill.ratioFloor;
|
|
2420
|
+
let cta, ctaHover, ctaPressed, onFillIsWhite;
|
|
2421
|
+
if (mode === "light") {
|
|
2422
|
+
onFillIsWhite = onFillIsWhiteLight(ctx, enforceLc !== void 0 ? false : onFillEnforce);
|
|
2423
|
+
let light9L = enforceLc !== void 0 ? ctaLightLApca(ctx, onFillIsWhite, onFillEnforce, enforceLc) : ctaLightL(ctx, onFillIsWhite, onFillEnforce);
|
|
2424
|
+
if (enforceLc === void 0 && ctx.opts?.ctaSolve && light9L < ctx.scaleL - 1e-6) {
|
|
2425
|
+
const at = (L) => ({ L, C: clampChromaToGamut(L, ctx.cAt("light", L, ctx.brandC), ctx.brandH), H: ctx.brandH });
|
|
2426
|
+
const orig = at(ctx.scaleL);
|
|
2427
|
+
const nearRed = redSolveDist(orig, ctx.opts.ctaSolve.red) <= RED_GATE.G + 2 * RED_SOLVE.ring;
|
|
2428
|
+
const towardRed = redSolveDist(at(light9L), ctx.opts.ctaSolve.red) < redSolveDist(orig, ctx.opts.ctaSolve.red);
|
|
2429
|
+
if (nearRed && towardRed && legalRatio(orig.L, orig.C, orig.H, 0) >= 4.5) {
|
|
2430
|
+
light9L = ctx.scaleL;
|
|
2431
|
+
onFillIsWhite = false;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
if (coLc !== void 0) {
|
|
2435
|
+
const [capLoL, capHiL] = CTA_CLEARANCE_CAPS;
|
|
2436
|
+
light9L = ctaDualGateL(ctx, onFillIsWhite, onFillEnforce, coLc + APCA_ENFORCE_MARGIN_LC, capLoL, capHiL);
|
|
2437
|
+
}
|
|
2438
|
+
const ctaCFor = (L) => ctx.cAt("light", L, (ctaReq.chromaMult ?? 1) * ctx.brandC);
|
|
2439
|
+
let repelled = false;
|
|
2440
|
+
let ctaH = ctx.brandH;
|
|
2441
|
+
let ctaCMul = 1;
|
|
2442
|
+
if (ctx.opts?.ctaSolve) {
|
|
2443
|
+
const landing = solveBrandExit(ctx.opts.ctaSolve.seed, ctaCFor, ctx.brandH, ctx.opts.ctaSolve.red, enforceLc, coLc);
|
|
2444
|
+
if (landing !== null) {
|
|
2445
|
+
light9L = landing.L;
|
|
2446
|
+
ctaH = landing.H;
|
|
2447
|
+
ctaCMul = landing.cMul;
|
|
2448
|
+
repelled = true;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
cta = emitRole("cta", light9L, ctaCFor(light9L) * ctaCMul, ctaH);
|
|
2452
|
+
const hCFor = (L) => ctx.cAt("light", L, (hoverReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul;
|
|
2453
|
+
const pCFor = (L) => ctx.cAt("light", L, (pressedReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul;
|
|
2454
|
+
const hL = stateFillL(light9L, "light", 1);
|
|
2455
|
+
const pL = stateFillL(light9L, "light", 2);
|
|
2456
|
+
ctaHover = emitRole("cta-hover", hL, hCFor(hL), ctaH);
|
|
2457
|
+
ctaPressed = emitRole("cta-pressed", pL, pCFor(pL), ctaH);
|
|
2458
|
+
if (light9L !== ctx.scaleL) {
|
|
2459
|
+
cta.enforced = true;
|
|
2460
|
+
ctaHover.enforced = true;
|
|
2461
|
+
ctaPressed.enforced = true;
|
|
2462
|
+
}
|
|
2463
|
+
if (repelled) {
|
|
2464
|
+
cta.repelled = true;
|
|
2465
|
+
ctaHover.repelled = true;
|
|
2466
|
+
ctaPressed.repelled = true;
|
|
2467
|
+
onFillIsWhite = onFillIsWhiteDarkAt(
|
|
2468
|
+
cta.L,
|
|
2469
|
+
cta.C,
|
|
2470
|
+
cta.H,
|
|
2471
|
+
enforceLc !== void 0 ? false : onFillEnforce,
|
|
2472
|
+
onFloor
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
} else {
|
|
2476
|
+
const d = dctx;
|
|
2477
|
+
const cta9L = ctx.opts?.darkCtaFlatApp !== void 0 ? flatDarkCtaL(d, (L) => ctx.cAt("dark", L, d.darkC9), ctx.darkCtaH, ctx.opts.darkCtaFlatApp) : d.dark9L;
|
|
2478
|
+
const dCFor = (L) => ctx.cAt("dark", L, d.darkC9);
|
|
2479
|
+
const dTrio = (baseL) => [stateFillL(baseL, "dark", 1), stateFillL(baseL, "dark", 2)];
|
|
2480
|
+
cta = emitRole("cta", cta9L, dCFor(cta9L), ctx.darkCtaH);
|
|
2481
|
+
{
|
|
2482
|
+
const [hL, pL] = dTrio(cta9L);
|
|
2483
|
+
ctaHover = emitRole("cta-hover", hL, dCFor(hL), ctx.darkCtaH);
|
|
2484
|
+
ctaPressed = emitRole("cta-pressed", pL, dCFor(pL), ctx.darkCtaH);
|
|
2485
|
+
}
|
|
2486
|
+
onFillIsWhite = onFillIsWhiteDarkAt(cta.L, cta.C, cta.H, enforceLc !== void 0 ? false : onFillEnforce);
|
|
2487
|
+
const enforcedL = enforceLc !== void 0 ? ctaDarkEnforcedLApca(ctx, cta, onFillIsWhite, onFillEnforce, enforceLc) : ctaDarkEnforcedL(ctx, cta, onFillIsWhite, onFillEnforce);
|
|
2488
|
+
if (enforcedL !== null) {
|
|
2489
|
+
cta = emitRole("cta", enforcedL, dCFor(enforcedL), ctx.darkCtaH);
|
|
2490
|
+
const [hL, pL] = dTrio(enforcedL);
|
|
2491
|
+
ctaHover = emitRole("cta-hover", hL, dCFor(hL), ctx.darkCtaH);
|
|
2492
|
+
ctaPressed = emitRole("cta-pressed", pL, dCFor(pL), ctx.darkCtaH);
|
|
2493
|
+
cta.enforced = true;
|
|
2494
|
+
ctaHover.enforced = true;
|
|
2495
|
+
ctaPressed.enforced = true;
|
|
2496
|
+
}
|
|
2497
|
+
if (coLc !== void 0) {
|
|
2498
|
+
const [capLoL, capHiL] = CTA_CLEARANCE_CAPS;
|
|
2499
|
+
const clearedL = ctaDarkDualGateL(cta, ctx.darkCtaH, onFillIsWhite, onFillEnforce, coLc + APCA_ENFORCE_MARGIN_LC, capLoL, capHiL);
|
|
2500
|
+
if (Math.abs(clearedL - cta.L) > 1e-9) {
|
|
2501
|
+
cta = emitRole("cta", clearedL, dCFor(clearedL), ctx.darkCtaH);
|
|
2502
|
+
const [hL, pL] = dTrio(clearedL);
|
|
2503
|
+
ctaHover = emitRole("cta-hover", hL, dCFor(hL), ctx.darkCtaH);
|
|
2504
|
+
ctaPressed = emitRole("cta-pressed", pL, dCFor(pL), ctx.darkCtaH);
|
|
2505
|
+
cta.enforced = true;
|
|
2506
|
+
ctaHover.enforced = true;
|
|
2507
|
+
ctaPressed.enforced = true;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
if (ctx.opts?.ctaSolve) {
|
|
2511
|
+
const exitL = solveDarkCtaExit(cta, dCFor, ctx.darkCtaH, ctx.opts.ctaSolve.redDark, enforceLc, coLc);
|
|
2512
|
+
if (exitL !== null) {
|
|
2513
|
+
cta = emitRole("cta", exitL, dCFor(exitL), ctx.darkCtaH);
|
|
2514
|
+
const [hL, pL] = dTrio(exitL);
|
|
2515
|
+
ctaHover = emitRole("cta-hover", hL, dCFor(hL), ctx.darkCtaH);
|
|
2516
|
+
ctaPressed = emitRole("cta-pressed", pL, dCFor(pL), ctx.darkCtaH);
|
|
2517
|
+
cta.enforced = true;
|
|
2518
|
+
ctaHover.enforced = true;
|
|
2519
|
+
ctaPressed.enforced = true;
|
|
2520
|
+
cta.repelled = true;
|
|
2521
|
+
ctaHover.repelled = true;
|
|
2522
|
+
ctaPressed.repelled = true;
|
|
2523
|
+
onFillIsWhite = onFillIsWhiteDarkAt(
|
|
2524
|
+
cta.L,
|
|
2525
|
+
cta.C,
|
|
2526
|
+
cta.H,
|
|
2527
|
+
enforceLc !== void 0 ? false : onFillEnforce,
|
|
2528
|
+
enforceLc !== void 0 ? void 0 : 4.5
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
if (onFloor !== void 0) {
|
|
2534
|
+
const chosen = legalRatio(cta.L, cta.C, cta.H, onFillIsWhite ? 1 : 0);
|
|
2535
|
+
const other = legalRatio(cta.L, cta.C, cta.H, onFillIsWhite ? 0 : 1);
|
|
2536
|
+
if (chosen < onFloor && other >= onFloor) onFillIsWhite = !onFillIsWhite;
|
|
2537
|
+
}
|
|
2538
|
+
return { mode, seed, stops, roles: { cta, ctaHover, ctaPressed }, ons: { onFillIsWhite } };
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// src/engine/colorEngine.ts
|
|
2542
|
+
function generateScale(hex, scaleName, forcedArchetype, opts) {
|
|
2543
|
+
const rOpts = { ...opts, forcedArchetype, enforceOnFillContrast: !!opts?.enforceOnFillContrast };
|
|
2544
|
+
const compile = (spec) => withProfile(spec, opts?.contrastProfile ?? "wcag");
|
|
2545
|
+
const lightRamp = resolveRamp(hex, "light", compile(MODE_SPECS.light), rOpts);
|
|
2546
|
+
const darkRamp = resolveRamp(
|
|
2547
|
+
hex,
|
|
2548
|
+
"dark",
|
|
2549
|
+
compile(MODE_SPECS.dark),
|
|
2550
|
+
{ ...rOpts, deltaLightStops: lightRamp.stops, deltaCarry: true }
|
|
2551
|
+
);
|
|
2552
|
+
const { L: brandL, C: rawC, H: rawH } = hexToOklch(hex);
|
|
2553
|
+
const brandH = (rawH + (opts?.hueShiftDeg ?? 0) + 360) % 360;
|
|
2554
|
+
const brandC = rawC * (opts?.chromaScale ?? 1);
|
|
2555
|
+
const archetype = forcedArchetype ?? classifyArchetype(brandL);
|
|
2556
|
+
const toStop = (s) => makeStop(s.stop, s.L, s.C, s.H);
|
|
2557
|
+
const light = lightRamp.stops.filter((s) => s.stop >= 1).map(toStop).sort((a, b) => a.stop - b.stop);
|
|
2558
|
+
const dark = darkRamp.stops.filter((s) => s.stop >= 1).map(toStop).sort((a, b) => a.stop - b.stop);
|
|
2559
|
+
const p0Light = lightRamp.stops.find((s) => s.stop === 0);
|
|
2560
|
+
const p0Dark = darkRamp.stops.find((s) => s.stop === 0);
|
|
2561
|
+
const inkExtreme = (arr) => {
|
|
2562
|
+
const a = arr.find((s) => s.stop === 10), b = arr.find((s) => s.stop === 11);
|
|
2563
|
+
return a && b ? makeStop(12, b.L + (b.L - a.L) * INK0_SEAM_OVERSHOOT, b.C, b.H) : void 0;
|
|
2564
|
+
};
|
|
2565
|
+
const cta = makeStop(9, lightRamp.roles.cta.L, lightRamp.roles.cta.C, lightRamp.roles.cta.H);
|
|
2566
|
+
const ctaDark = makeStop(9, darkRamp.roles.cta.L, darkRamp.roles.cta.C, darkRamp.roles.cta.H);
|
|
2567
|
+
const roleStop = (r, stop) => makeStop(stop, r.L, r.C, r.H);
|
|
2568
|
+
return {
|
|
2569
|
+
name: scaleName,
|
|
2570
|
+
archetype,
|
|
2571
|
+
brandL,
|
|
2572
|
+
brandC,
|
|
2573
|
+
brandH,
|
|
2574
|
+
onFillTextIsWhite: lightRamp.ons.onFillIsWhite,
|
|
2575
|
+
onFillTextIsWhiteDark: darkRamp.ons.onFillIsWhite,
|
|
2576
|
+
light,
|
|
2577
|
+
dark,
|
|
2578
|
+
cta,
|
|
2579
|
+
ctaHover: roleStop(lightRamp.roles.ctaHover, 10),
|
|
2580
|
+
ctaPressed: roleStop(lightRamp.roles.ctaPressed, 11),
|
|
2581
|
+
ctaDark,
|
|
2582
|
+
ctaHoverDark: roleStop(darkRamp.roles.ctaHover, 10),
|
|
2583
|
+
ctaPressedDark: roleStop(darkRamp.roles.ctaPressed, 11),
|
|
2584
|
+
ctaRepelled: { light: !!lightRamp.roles.cta.repelled, dark: !!darkRamp.roles.cta.repelled },
|
|
2585
|
+
identityHex: hex.toUpperCase(),
|
|
2586
|
+
paper0: p0Light ? makeStop(0, p0Light.L, p0Light.C, p0Light.H) : void 0,
|
|
2587
|
+
paper0Dark: p0Dark ? makeStop(0, p0Dark.L, p0Dark.C, p0Dark.H) : void 0,
|
|
2588
|
+
ink0: inkExtreme(light),
|
|
2589
|
+
ink0Dark: inkExtreme(dark)
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
2593
|
+
const h = (brandH % 360 + 360) % 360;
|
|
2594
|
+
const { r, g: g2, b } = oklchToSrgbUnclamped(0.5, 6e-3, h);
|
|
2595
|
+
const ch = (v2) => Math.round(Math.min(1, Math.max(0, v2)) * 255).toString(16).padStart(2, "0");
|
|
2596
|
+
const grayHex = `#${ch(r)}${ch(g2)}${ch(b)}`;
|
|
2597
|
+
const curve = neutralChromaCurve(brandH, level);
|
|
2598
|
+
const scale = generateScale(grayHex, "neutral", "light", {
|
|
2599
|
+
chromaCurve: curve,
|
|
2600
|
+
enforceOnFillContrast: true,
|
|
2601
|
+
contrastProfile
|
|
2602
|
+
});
|
|
2603
|
+
const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
|
|
2604
|
+
scale.cta = asCta(9, scale.light[3]);
|
|
2605
|
+
scale.ctaDark = asCta(9, scale.dark[3]);
|
|
2606
|
+
const popDark = scale.dark[2];
|
|
2607
|
+
const popDarkY = wcagY(popDark.L, popDark.C, popDark.H);
|
|
2608
|
+
const clearedL = findLForContrastUp(scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, popDarkY, NEUTRAL_CTA_DARK_POP_CLEARANCE);
|
|
2609
|
+
const popLift = clearedL - scale.ctaDark.L;
|
|
2610
|
+
if (popLift > 1e-6) {
|
|
2611
|
+
scale.ctaDark = makeStop(9, scale.dark[3].L + popLift, scale.dark[3].C, scale.dark[3].H);
|
|
2612
|
+
}
|
|
2613
|
+
const nState = (stop, mode, rest, k) => {
|
|
2614
|
+
const L = stateFillL(rest.L, mode, k);
|
|
2615
|
+
return makeStop(stop, L, curve(L, mode), rest.H);
|
|
2616
|
+
};
|
|
2617
|
+
scale.ctaHover = nState(10, "light", scale.cta, 1);
|
|
2618
|
+
scale.ctaPressed = nState(11, "light", scale.cta, 2);
|
|
2619
|
+
scale.ctaHoverDark = nState(10, "dark", scale.ctaDark, 1);
|
|
2620
|
+
scale.ctaPressedDark = nState(11, "dark", scale.ctaDark, 2);
|
|
2621
|
+
const onEnforce = contrastProfile !== "apca";
|
|
2622
|
+
const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
|
|
2623
|
+
scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
|
|
2624
|
+
scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
|
|
2625
|
+
return scale;
|
|
2626
|
+
}
|
|
2627
|
+
function generateSubtleSecondary(hex, opts) {
|
|
2628
|
+
const { H: H2 } = hexToOklch(hex);
|
|
2629
|
+
const curve = opts?.curve ?? (opts?.pastelK !== void 0 ? (L, _mode) => opts.pastelK * maxChromaAt(L, H2) : subtleSecondaryChromaCurve(H2, opts?.mult));
|
|
2630
|
+
const scale = generateScale(hex, "secondary", "light", {
|
|
2631
|
+
chromaCurve: curve,
|
|
2632
|
+
enforceOnFillContrast: true,
|
|
2633
|
+
contrastProfile: opts?.contrastProfile
|
|
2634
|
+
});
|
|
2635
|
+
const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
|
|
2636
|
+
const mk = (stop, L, mode) => makeStop(stop, L, curve(L, mode), scale.brandH);
|
|
2637
|
+
const st = (stop, mode, rest, k) => {
|
|
2638
|
+
const L = stateFillL(rest.L, mode, k);
|
|
2639
|
+
return makeStop(stop, L, curve(L, mode), rest.H);
|
|
2640
|
+
};
|
|
2641
|
+
if (opts?.ctaL) {
|
|
2642
|
+
scale.cta = mk(9, opts.ctaL.light, "light");
|
|
2643
|
+
scale.ctaDark = mk(9, opts.ctaL.dark, "dark");
|
|
2644
|
+
} else {
|
|
2645
|
+
scale.cta = asCta(9, scale.light[3]);
|
|
2646
|
+
scale.ctaDark = asCta(9, scale.dark[3]);
|
|
2647
|
+
}
|
|
2648
|
+
scale.ctaHover = st(10, "light", scale.cta, 1);
|
|
2649
|
+
scale.ctaPressed = st(11, "light", scale.cta, 2);
|
|
2650
|
+
scale.ctaHoverDark = st(10, "dark", scale.ctaDark, 1);
|
|
2651
|
+
scale.ctaPressedDark = st(11, "dark", scale.ctaDark, 2);
|
|
2652
|
+
const onEnforce = opts?.contrastProfile !== "apca";
|
|
2653
|
+
const onFloor = opts?.contrastProfile === "apca" ? void 0 : 4.5;
|
|
2654
|
+
scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
|
|
2655
|
+
scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
|
|
2656
|
+
return scale;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// src/engine/tokenDescriptions.ts
|
|
2660
|
+
var AA_LARGE = "AA large text and UI elements";
|
|
2661
|
+
var AA_BODY = "AA standard body text & Level AAA large text";
|
|
2662
|
+
var AAA_BODY = "AAA standard body text";
|
|
2663
|
+
var FAMILY = {
|
|
2664
|
+
neutral: "neutral",
|
|
2665
|
+
brandPrimary: "brand",
|
|
2666
|
+
brandSecondary: "brand-alt",
|
|
2667
|
+
critical: "critical",
|
|
2668
|
+
warning: "warning",
|
|
2669
|
+
positive: "positive",
|
|
2670
|
+
info: "info"
|
|
2671
|
+
};
|
|
2672
|
+
var FAMILIES = Object.values(FAMILY);
|
|
2673
|
+
var SIGNALS = [FAMILY.critical, FAMILY.warning, FAMILY.positive, FAMILY.info];
|
|
2674
|
+
var CSS_FAMILY = {
|
|
2675
|
+
neutral: FAMILY.neutral,
|
|
2676
|
+
brandPrimary: "brand",
|
|
2677
|
+
brandSecondary: "brand-alt"
|
|
2678
|
+
};
|
|
2679
|
+
var TINT = {
|
|
2680
|
+
[FAMILY.neutral]: "tints carry neutral hue (gray)",
|
|
2681
|
+
[FAMILY.brandPrimary]: "tints carry brand hue",
|
|
2682
|
+
[FAMILY.brandSecondary]: "tints carry brand-alt hue",
|
|
2683
|
+
[FAMILY.critical]: "tints carry critical hue (red)",
|
|
2684
|
+
[FAMILY.warning]: "tints carry warning hue (yellow)",
|
|
2685
|
+
[FAMILY.positive]: "tints carry positive hue (green)",
|
|
2686
|
+
[FAMILY.info]: "tints carry info hue (blue)"
|
|
2687
|
+
};
|
|
2688
|
+
var COLOR_WORD = {
|
|
2689
|
+
[FAMILY.neutral]: " (gray)",
|
|
2690
|
+
[FAMILY.brandPrimary]: "",
|
|
2691
|
+
[FAMILY.brandSecondary]: "",
|
|
2692
|
+
[FAMILY.critical]: " (red)",
|
|
2693
|
+
[FAMILY.warning]: " (yellow)",
|
|
2694
|
+
[FAMILY.positive]: " (green)",
|
|
2695
|
+
[FAMILY.info]: " (blue)"
|
|
2696
|
+
};
|
|
2697
|
+
var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
|
|
2698
|
+
var WASH = { req: "subtle interaction states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
|
|
2699
|
+
var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
|
|
2700
|
+
var OVERLAY = {
|
|
2701
|
+
req: "translucent backgrounds that hold their reading on any paper",
|
|
2702
|
+
theming: (f) => `${TINT[f]}; opacity solved against the papers, other backdrops show through unguaranteed`,
|
|
2703
|
+
collides: true
|
|
2704
|
+
};
|
|
2705
|
+
var SCALE = {
|
|
2706
|
+
"paper-99": PAPER,
|
|
2707
|
+
"paper-99-overlay": OVERLAY,
|
|
2708
|
+
"paper-97": PAPER,
|
|
2709
|
+
"paper-97-overlay": OVERLAY,
|
|
2710
|
+
"paper-95": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
|
|
2711
|
+
"paper-95-overlay": OVERLAY,
|
|
2712
|
+
"wash-92": WASH,
|
|
2713
|
+
"wash-89": WASH,
|
|
2714
|
+
"wash-85": WASH,
|
|
2715
|
+
"wash-80": WASH,
|
|
2716
|
+
"mark-74": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
|
|
2717
|
+
"lead-53": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2718
|
+
"ink-42": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2719
|
+
// ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
|
|
2720
|
+
// words, and a body carrying either floods that token's picker search — the C50 law)
|
|
2721
|
+
"ink-30": { req: "strong-emphasis text, inverted backgrounds", contrast: AAA_BODY, theming: solved, collides: true },
|
|
2722
|
+
// the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
|
|
2723
|
+
// these bodies on purpose: it stopped being a token label, so it floods nothing,
|
|
2724
|
+
// and a designer's "cta" query still lands on these rows.
|
|
2725
|
+
"stamp/fill": { req: "CTAs", theming: (f) => `fully re-solved per theme and family${COLOR_WORD[f]}` },
|
|
2726
|
+
"stamp/fill-hover": { req: "CTA pointer-over state", theming: (f) => `follows its rest fill${COLOR_WORD[f]}` },
|
|
2727
|
+
"stamp/fill-pressed": { req: "CTA pressed state", theming: (f) => `follows its rest fill${COLOR_WORD[f]}` },
|
|
2728
|
+
"stamp/edge": { req: "min APCA visibility", theming: (f) => `draws for CTAs that sit close to the page; strength per family tier${COLOR_WORD[f]}` },
|
|
2729
|
+
// ("fill" reworded out 2026-08-18: fill became a label word and is foreign here)
|
|
2730
|
+
"stamp/on": { req: "text over the CTA color", contrast: `${AA_BODY} over its CTA`, theming: (f) => `whichever pole passes; quiet CTAs take the soft pole${COLOR_WORD[f]}` }
|
|
2731
|
+
};
|
|
2732
|
+
var ABS = { req: "max contrast on CTAs, aliased global endpoints" };
|
|
2733
|
+
var OFFSET = { req: "min APCA visibility", theming: "offsets buttons in themes whose CTAs sit close to the page" };
|
|
2734
|
+
var SHADOW = { req: "drop shadows" };
|
|
2735
|
+
var PLANE = (req) => ({ req, theming: "aliased to the gray ramp" });
|
|
2736
|
+
var LINK = (state, contrast) => ({
|
|
2737
|
+
req: "links" + state,
|
|
2738
|
+
contrast,
|
|
2739
|
+
theming: "rides the theme\u2019s text action; custom seed re-solves; overridable per theme"
|
|
2740
|
+
});
|
|
2741
|
+
var LINK_INVERSE = (state, contrast) => ({
|
|
2742
|
+
req: "links on inverted backgrounds" + state,
|
|
2743
|
+
contrast,
|
|
2744
|
+
theming: "same seed as the link, re-solved for inverted backgrounds; overridable per theme"
|
|
2745
|
+
});
|
|
2746
|
+
var SYSTEM = {
|
|
2747
|
+
"system/abs-black": ABS,
|
|
2748
|
+
"system/abs-white": ABS,
|
|
2749
|
+
// (the community system/ink-0 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
|
|
2750
|
+
// the anchor is engine-resolved and rides the neutral in BOTH plugins now —
|
|
2751
|
+
// NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
|
|
2752
|
+
"system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
|
|
2753
|
+
"system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s companion input, as given" },
|
|
2754
|
+
"system/alpha/transparent": { req: "aliased off-states" },
|
|
2755
|
+
// ("dimming" reworded 2026-08-18: the surface planes took dim as a label word)
|
|
2756
|
+
"system/alpha/abs-black-060": { req: "veils the page behind modals" },
|
|
2757
|
+
// ("fills" reworded 2026-08-18: fill became a label word and is foreign here)
|
|
2758
|
+
"system/alpha/ink": { req: "soft on-color for quiet CTAs" },
|
|
2759
|
+
"system/alpha/006": OFFSET,
|
|
2760
|
+
"system/alpha/008": OFFSET,
|
|
2761
|
+
"system/alpha/016": OFFSET,
|
|
2762
|
+
"system/alpha/shadow-04": SHADOW,
|
|
2763
|
+
"system/alpha/shadow-08": SHADOW,
|
|
2764
|
+
"system/alpha/shadow-12": SHADOW,
|
|
2765
|
+
"system/surface/dim": PLANE("recessed elevation plane"),
|
|
2766
|
+
"system/surface/low": PLANE("resting page plane"),
|
|
2767
|
+
"system/surface/mid": PLANE("raised plane \u2014 cards, menus"),
|
|
2768
|
+
// ("overlays" reworded 2026-08-13: paper-overlay took the word as a label — the C50 law)
|
|
2769
|
+
"system/surface/high": PLANE("topmost plane \u2014 modals, dialogs"),
|
|
2770
|
+
"system/link/default": LINK("", AA_BODY),
|
|
2771
|
+
"system/link/hover": LINK(" pointer-over", AA_BODY),
|
|
2772
|
+
"system/link/pressed": LINK(" pressed", AAA_BODY),
|
|
2773
|
+
"system/link/inverse": LINK_INVERSE("", AA_BODY),
|
|
2774
|
+
"system/link/inverse-hover": LINK_INVERSE(" pointer-over", AA_BODY),
|
|
2775
|
+
"system/link/inverse-pressed": LINK_INVERSE(" pressed", AAA_BODY)
|
|
2776
|
+
};
|
|
2777
|
+
var PREFIXES = [
|
|
2778
|
+
// community spellings FIRST: the 2026-08-21 family rename made the ext word a
|
|
2779
|
+
// PREFIX of the community shapes ('brand/' would shadow 'brand/primary/' and
|
|
2780
|
+
// 'brand/alt/'), so most-specific must be tried before the family words
|
|
2781
|
+
["brand/primary/", FAMILY.brandPrimary],
|
|
2782
|
+
["brand/alt/", FAMILY.brandSecondary],
|
|
2783
|
+
...FAMILIES.map((f) => [f + "/", f])
|
|
2784
|
+
];
|
|
2785
|
+
|
|
2786
|
+
// src/engine/signals.ts
|
|
2787
|
+
var SIGNALS2 = [
|
|
2788
|
+
{ name: "red", emitName: FAMILY.critical, hex: "#E54D2E", L: 0.627, C: 0.194, H: 33.3, hueShift: { cool: 0, warm: 15 }, yieldChromaScale: 1 },
|
|
2789
|
+
{ name: "yellow", emitName: FAMILY.warning, hex: "#FFC53D", L: 0.854, C: 0.157, H: 84.1, hueShift: { cool: 23, warm: 0 }, yieldChromaScale: 1.15 },
|
|
2790
|
+
{ name: "green", emitName: FAMILY.positive, hex: "#63C373", L: 0.739, C: 0.146, H: 147.6, hueShift: { cool: 15, warm: 10 }, yieldChromaScale: 1, darkFillMinL: 0.75 },
|
|
2791
|
+
{ name: "blue", emitName: FAMILY.info, hex: "#AFA3FF", L: 0.761, C: 0.13, H: 288.9, hueShift: { cool: 15, warm: 15 }, yieldChromaScale: 1, darkFillMinL: 0.7 }
|
|
2792
|
+
];
|
|
2793
|
+
var SIGNAL_EMIT_NAME = Object.fromEntries(SIGNALS2.map((s) => [s.name, s.emitName]));
|
|
2794
|
+
|
|
2795
|
+
// src/engine/collision.ts
|
|
2796
|
+
var HUE_GATE_DEG = 30;
|
|
2797
|
+
var DELTA_E_THRESHOLD = 0.16;
|
|
2798
|
+
var DARK_DELTA_E_THRESHOLD = 0.1;
|
|
2799
|
+
function hueDistance(h1, h2) {
|
|
2800
|
+
const d = Math.abs(h1 - h2) % 360;
|
|
2801
|
+
return d > 180 ? 360 - d : d;
|
|
2802
|
+
}
|
|
2803
|
+
function stopDeltaE(s12, s2) {
|
|
2804
|
+
return oklabDist(s12, s2);
|
|
2805
|
+
}
|
|
2806
|
+
function checkCollision(brand, signalScale, signalDef, mode) {
|
|
2807
|
+
const brandCta = mode === "light" ? brand.cta : brand.ctaDark;
|
|
2808
|
+
const signalCta = mode === "light" ? signalScale.cta : signalScale.ctaDark;
|
|
2809
|
+
const dHue = hueDistance(brand.brandH, signalDef.H);
|
|
2810
|
+
const hueGate = dHue <= HUE_GATE_DEG;
|
|
2811
|
+
const deltaE = stopDeltaE(brandCta, signalCta);
|
|
2812
|
+
const threshold = mode === "dark" ? DARK_DELTA_E_THRESHOLD : DELTA_E_THRESHOLD;
|
|
2813
|
+
return {
|
|
2814
|
+
signal: signalDef.name,
|
|
2815
|
+
mode,
|
|
2816
|
+
dHue,
|
|
2817
|
+
hueGate,
|
|
2818
|
+
deltaE,
|
|
2819
|
+
collides: hueGate && deltaE <= threshold
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
var HUE_COLLISION_WASH_DEG = 15;
|
|
2823
|
+
var HUE_COLLISION_MIN_V = 0.5;
|
|
2824
|
+
var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
|
|
2825
|
+
var WASH_STOPS = [3, 4, 5, 6, 7];
|
|
2826
|
+
function washProximity(brand, sig, mode) {
|
|
2827
|
+
const b = mode === "light" ? brand.light : brand.dark;
|
|
2828
|
+
const s = mode === "light" ? sig.light : sig.dark;
|
|
2829
|
+
let dH = Infinity, dE = Infinity;
|
|
2830
|
+
for (const k of WASH_STOPS) {
|
|
2831
|
+
const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
|
|
2832
|
+
if (!bs || !ss) continue;
|
|
2833
|
+
dH = Math.min(dH, hueDistance(bs.H, ss.H));
|
|
2834
|
+
dE = Math.min(dE, stopDeltaE(bs, ss));
|
|
2835
|
+
}
|
|
2836
|
+
return { dH, dE };
|
|
2837
|
+
}
|
|
2838
|
+
function checkHueCollision(brand, signalScale, signalDef, opts) {
|
|
2839
|
+
const l = washProximity(brand, signalScale, "light");
|
|
2840
|
+
const d = washProximity(brand, signalScale, "dark");
|
|
2841
|
+
const vividness = Math.min(1, brand.brandC / VIVID_C);
|
|
2842
|
+
return {
|
|
2843
|
+
signal: signalDef.name,
|
|
2844
|
+
dHueWash: { light: l.dH, dark: d.dH },
|
|
2845
|
+
washDeltaE: { light: l.dE, dark: d.dE },
|
|
2846
|
+
vividness,
|
|
2847
|
+
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_WASH_DEG
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
var YELLOW_SPLIT_H = 96;
|
|
2851
|
+
function warningVariant(brand, warningScale, warningDef) {
|
|
2852
|
+
if (!checkHueCollision(brand, warningScale, warningDef).collides) return null;
|
|
2853
|
+
return brand.brandH < YELLOW_SPLIT_H ? "lemon" : "macaroni";
|
|
2854
|
+
}
|
|
2855
|
+
function checkAllCollisions(brand, signalScales) {
|
|
2856
|
+
const checks = [];
|
|
2857
|
+
for (const def of SIGNALS2) {
|
|
2858
|
+
const scale = signalScales.get(def.name);
|
|
2859
|
+
checks.push(checkCollision(brand, scale, def, "light"));
|
|
2860
|
+
checks.push(checkCollision(brand, scale, def, "dark"));
|
|
2861
|
+
}
|
|
2862
|
+
const collidesWith = [...new Set(checks.filter((c) => c.collides).map((c) => c.signal))];
|
|
2863
|
+
return { checks, collidesWith };
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
// src/engine/signalShift.ts
|
|
2867
|
+
var SHIFT_RULES = {
|
|
2868
|
+
yellow: {
|
|
2869
|
+
splitH: YELLOW_SPLIT_H,
|
|
2870
|
+
below: { kind: "shift", note: "yellow \u2192 lemon" },
|
|
2871
|
+
atOrAbove: { kind: "none" }
|
|
2872
|
+
},
|
|
2873
|
+
green: {
|
|
2874
|
+
splitH: 147,
|
|
2875
|
+
below: { kind: "swap", note: "green \u2192 teal-side", baseHex: "#42C585" },
|
|
2876
|
+
atOrAbove: { kind: "swap", note: "green \u2192 yellow-side", baseHex: "#78C062" }
|
|
2877
|
+
},
|
|
2878
|
+
// renamed by identity 2026-07-13 (owner: only purple-blue/cyan-blue remains): info-color → blue.
|
|
2879
|
+
// The magenta side is UNREACHABLE since the 2169337 seed lift (blessed loss, CATALOG C17):
|
|
2880
|
+
// primary gate 0/roster, secondary adoption 0/288 probed — machinery kept pending owner word.
|
|
2881
|
+
"blue": {
|
|
2882
|
+
splitH: 273,
|
|
2883
|
+
below: { kind: "swap", note: "blue \u2192 magenta-side", baseHex: "#EC88FC" },
|
|
2884
|
+
atOrAbove: { kind: "swap", note: "blue \u2192 cyan-side", baseHex: "#6AB5FF" }
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
function swapScale(baseHex, def, contrastProfile) {
|
|
2888
|
+
return generateScale(baseHex, def.name, void 0, {
|
|
2889
|
+
darkChromaCurve,
|
|
2890
|
+
darkCtaC: "signal",
|
|
2891
|
+
darkFillMinL: def.darkFillMinL,
|
|
2892
|
+
enforceOnFillContrast: true,
|
|
2893
|
+
suppressRedCool: true,
|
|
2894
|
+
goldBoost: true,
|
|
2895
|
+
signalWarmDrift: true,
|
|
2896
|
+
contrastProfile,
|
|
2897
|
+
apcaClearance: true
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
function lemonScale(def, contrastProfile) {
|
|
2901
|
+
return generateScale(def.hex, def.name, "light", {
|
|
2902
|
+
hueShiftDeg: def.hueShift.cool,
|
|
2903
|
+
chromaScale: def.yieldChromaScale,
|
|
2904
|
+
darkChromaCurve,
|
|
2905
|
+
darkCtaC: "signal",
|
|
2906
|
+
enforceOnFillContrast: true,
|
|
2907
|
+
suppressRedCool: true,
|
|
2908
|
+
goldBoost: true,
|
|
2909
|
+
signalWarmDrift: true,
|
|
2910
|
+
contrastProfile,
|
|
2911
|
+
apcaClearance: true
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
function signalSwapVariants(def, contrastProfile) {
|
|
2915
|
+
const rule = SHIFT_RULES[def.name];
|
|
2916
|
+
if (!rule || def.name === "yellow") return [];
|
|
2917
|
+
return [rule.below, rule.atOrAbove].filter((s) => s.kind === "swap").map((s) => ({ scale: swapScale(s.baseHex, def, contrastProfile), note: s.note }));
|
|
2918
|
+
}
|
|
2919
|
+
function pickSignalShift(brand, canonicalSignalScale, def, contrastProfile) {
|
|
2920
|
+
const rule = SHIFT_RULES[def.name];
|
|
2921
|
+
if (!rule) return null;
|
|
2922
|
+
if (!checkHueCollision(brand, canonicalSignalScale, def).collides) return null;
|
|
2923
|
+
const side = brand.brandH < rule.splitH ? rule.below : rule.atOrAbove;
|
|
2924
|
+
if (side.kind === "none") return null;
|
|
2925
|
+
if (side.kind === "swap") return { scale: swapScale(side.baseHex, def, contrastProfile), note: side.note };
|
|
2926
|
+
return { scale: lemonScale(def, contrastProfile), note: side.note };
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
// src/engine/resolve.ts
|
|
2930
|
+
var buildSignalScales = (contrastProfile) => new Map(
|
|
2931
|
+
SIGNALS2.map((def) => [
|
|
2932
|
+
def.name,
|
|
2933
|
+
{ def, scale: generateScale(def.hex, def.name, void 0, { darkChromaCurve, darkCtaC: "signal", darkFillMinL: def.darkFillMinL, enforceOnFillContrast: true, suppressRedCool: true, goldBoost: true, signalWarmDrift: true, contrastProfile, apcaClearance: true, apcaClearanceLc: def.name === "red" ? CRITICAL_CLEARANCE_LC : void 0 }) }
|
|
2934
|
+
])
|
|
2935
|
+
);
|
|
2936
|
+
var SIGNAL_SCALES = buildSignalScales();
|
|
2937
|
+
var apcaSignalScales = null;
|
|
2938
|
+
function signalScalesFor(contrastProfile) {
|
|
2939
|
+
if (contrastProfile !== "apca") return SIGNAL_SCALES;
|
|
2940
|
+
return apcaSignalScales ?? (apcaSignalScales = buildSignalScales("apca"));
|
|
2941
|
+
}
|
|
2942
|
+
function hueCollisionPending(scale, sigScales) {
|
|
2943
|
+
const pending = [];
|
|
2944
|
+
for (const { def, scale: sigScale } of sigScales.values()) {
|
|
2945
|
+
if (def.name !== "red" && checkHueCollision(scale, sigScale, def).collides) pending.push(def.name);
|
|
2946
|
+
}
|
|
2947
|
+
return pending;
|
|
2948
|
+
}
|
|
2949
|
+
var blackLcAt = (L, C2, H2) => Math.abs(apcaLc(apcaYAt(0, 0, 0), apcaYAt(L, clampChromaToGamut(L, C2, H2), H2)));
|
|
2950
|
+
var redCleanBeside = (brandCta, c) => p2Diff(brandCta, c) >= (c.L < brandCta.L ? P2_D : P2_D_UP) && redSolveDist(brandCta, c) >= RED_GATE.G + RED_SOLVE.ring;
|
|
2951
|
+
function redComplementVariant(brandCta, seed, brandWentUp, red, contrastProfile) {
|
|
2952
|
+
const rctx = buildContext(red.def.hex, {
|
|
2953
|
+
darkChromaCurve,
|
|
2954
|
+
darkCtaC: "signal",
|
|
2955
|
+
darkFillMinL: red.def.darkFillMinL,
|
|
2956
|
+
enforceOnFillContrast: true,
|
|
2957
|
+
suppressRedCool: true,
|
|
2958
|
+
goldBoost: true,
|
|
2959
|
+
signalWarmDrift: true,
|
|
2960
|
+
contrastProfile
|
|
2961
|
+
});
|
|
2962
|
+
const redCta = red.scale.cta;
|
|
2963
|
+
const at = (L, H2) => ({ L, C: clampChromaToGamut(L, rctx.cAt("light", L, rctx.brandC), H2), H: H2 });
|
|
2964
|
+
const fireLc = CRITICAL_CLEARANCE_LC + APCA_ENFORCE_MARGIN_LC;
|
|
2965
|
+
const shipsWhite = (c) => onFillIsWhiteDarkAt(c.L, c.C, c.H, true, contrastProfile === "apca" ? void 0 : 4.5);
|
|
2966
|
+
const poleOk = (c) => {
|
|
2967
|
+
const white = shipsWhite(c);
|
|
2968
|
+
const lc = white ? whiteTextLcAt(c.L, c.C, c.H) : blackLcAt(c.L, c.C, c.H);
|
|
2969
|
+
const legal = contrastProfile === "apca" || legalRatio(c.L, c.C, c.H, white ? 1 : 0) >= 4.5;
|
|
2970
|
+
return legal && lc >= fireLc;
|
|
2971
|
+
};
|
|
2972
|
+
const slideToBar = (c) => {
|
|
2973
|
+
if (poleOk(c)) return c;
|
|
2974
|
+
const L2 = shipsWhite(c) ? findLForWhiteTextLc(c.L, c.C, c.H, fireLc + APCA_SOLVE_MARGIN_LC) : findLForBlackTextLc(c.L, c.C, c.H, fireLc + APCA_SOLVE_MARGIN_LC, 0.92);
|
|
2975
|
+
const c2 = at(L2, c.H);
|
|
2976
|
+
return poleOk(c2) ? c2 : null;
|
|
2977
|
+
};
|
|
2978
|
+
const clean = (c) => redCleanBeside(brandCta, c) && poleOk(c);
|
|
2979
|
+
if (!brandWentUp && clean(redCta)) return null;
|
|
2980
|
+
const hues = hueDelta(seed.H, redCta.H) <= RED_SOLVE.redHueMagentaDh ? RED_SOLVE.redHuesMagentaBrand : RED_SOLVE.redHuesWarmBrand;
|
|
2981
|
+
const wantLighter = brandCta.L <= redCta.L;
|
|
2982
|
+
const tiers = wantLighter ? [[...RED_SOLVE.edgeL], RED_SOLVE.coreL.filter((l) => l > brandCta.L)] : [[...RED_SOLVE.coreL], [...RED_SOLVE.edgeL]];
|
|
2983
|
+
let pick = null;
|
|
2984
|
+
outer: for (const tier of tiers) {
|
|
2985
|
+
const ls = [...tier].sort((a, b) => wantLighter ? a - b : b - a);
|
|
2986
|
+
for (const L of ls) for (const H2 of hues) {
|
|
2987
|
+
const c = slideToBar(at(L, H2));
|
|
2988
|
+
if (!c) continue;
|
|
2989
|
+
const onSide = wantLighter ? c.L > brandCta.L : c.L < brandCta.L;
|
|
2990
|
+
if (!onSide || !clean(c)) continue;
|
|
2991
|
+
pick = c;
|
|
2992
|
+
break outer;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
if (!pick) return null;
|
|
2996
|
+
const cta = makeStop(redCta.stop, pick.L, rctx.cAt("light", pick.L, rctx.brandC), pick.H);
|
|
2997
|
+
const vCFor = (L) => rctx.cAt("light", L, rctx.brandC);
|
|
2998
|
+
const hL = stateFillL(pick.L, "light", 1);
|
|
2999
|
+
const ctaHover = makeStop(red.scale.ctaHover.stop, hL, vCFor(hL), pick.H);
|
|
3000
|
+
const pL = stateFillL(pick.L, "light", 2);
|
|
3001
|
+
const ctaPressed = makeStop(red.scale.ctaPressed.stop, pL, vCFor(pL), pick.H);
|
|
3002
|
+
const onFillTextIsWhite = onFillIsWhiteDarkAt(cta.L, cta.C, cta.H, true, contrastProfile === "apca" ? void 0 : 4.5);
|
|
3003
|
+
return {
|
|
3004
|
+
scale: { ...red.scale, cta, ctaHover, ctaPressed, onFillTextIsWhite },
|
|
3005
|
+
// naming candidates only — the identity name is the owner's call at bless. No hue suffix:
|
|
3006
|
+
// the plugin's note parser mints Figma variable paths from this string.
|
|
3007
|
+
note: `red \u2192 ${pick.L < redCta.L ? "rich" : "coral"} L${pick.L.toFixed(2)}`
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
function resolveBrand(hex, name, opts) {
|
|
3011
|
+
const sigScales = signalScalesFor(opts?.contrastProfile);
|
|
3012
|
+
const red = sigScales.get("red").scale;
|
|
3013
|
+
const seedO = hexToOklch(hex);
|
|
3014
|
+
const signalMoves = !opts?.skipCollisionRules;
|
|
3015
|
+
const solving = !opts?.exact && signalMoves && !opts?.archetypeOverride;
|
|
3016
|
+
const apcaRed = opts?.contrastProfile === "apca" ? red : signalScalesFor("apca").get("red").scale;
|
|
3017
|
+
const solveOpt = solving ? {
|
|
3018
|
+
ctaSolve: {
|
|
3019
|
+
seed: seedO,
|
|
3020
|
+
red: { L: apcaRed.cta.L, C: apcaRed.cta.C, H: apcaRed.cta.H },
|
|
3021
|
+
redDark: { L: apcaRed.ctaDark.L, C: apcaRed.ctaDark.C, H: apcaRed.ctaDark.H }
|
|
3022
|
+
}
|
|
3023
|
+
} : {};
|
|
3024
|
+
const floor = {
|
|
3025
|
+
...solveOpt,
|
|
3026
|
+
darkFillMinL: DARK_BRAND_FILL_MIN_L,
|
|
3027
|
+
enforceOnFillContrast: !opts?.exact,
|
|
3028
|
+
coolRedDark: !opts?.exact,
|
|
3029
|
+
darkChromaCurve: opts?.exact ? void 0 : darkChromaCurve,
|
|
3030
|
+
style: opts?.style,
|
|
3031
|
+
contrastProfile: opts?.contrastProfile,
|
|
3032
|
+
// DEFAULT ON (owner 2026-07-13, the cta dead-zone ruling: "pick closest to id that
|
|
3033
|
+
// passes apca"): the wcag lane's chosen cta pole must also clear the APCA bar,
|
|
3034
|
+
// moving the fill in that pole's own direction (pole-preserving = her candidate 1;
|
|
3035
|
+
// the apca lane already guarantees its bar, so it ships unmoved). Since C42 the
|
|
3036
|
+
// clearance covers light AND dark and the signal set passes it in its own generation
|
|
3037
|
+
// (buildSignalScales/signalShift) — neutral alone stays outside; exact has enforce off.
|
|
3038
|
+
apcaClearance: opts?.apcaClearance ?? true,
|
|
3039
|
+
darkCtaFlatApp: opts?.darkCtaFlatApp,
|
|
3040
|
+
inkGround: opts?.inkGround
|
|
3041
|
+
};
|
|
3042
|
+
let scale = generateScale(hex, name, void 0, floor);
|
|
3043
|
+
let pending = [];
|
|
3044
|
+
if (opts?.archetypeOverride) {
|
|
3045
|
+
scale = generateScale(hex, name, opts.archetypeOverride, floor);
|
|
3046
|
+
} else if (signalMoves) {
|
|
3047
|
+
pending = hueCollisionPending(scale, sigScales);
|
|
3048
|
+
}
|
|
3049
|
+
const redRepel = scale.ctaRepelled?.light || scale.ctaRepelled?.dark ? { light: !!scale.ctaRepelled?.light, dark: !!scale.ctaRepelled?.dark } : null;
|
|
3050
|
+
const signalOverrides = [];
|
|
3051
|
+
if (solving || opts?.exact && signalMoves && !opts?.archetypeOverride) {
|
|
3052
|
+
const brandWentUp = !!scale.ctaRepelled?.light && scale.cta.L > seedO.L + 1e-6;
|
|
3053
|
+
const v2 = redComplementVariant(scale.cta, seedO, brandWentUp, sigScales.get("red"), opts?.contrastProfile);
|
|
3054
|
+
if (v2) signalOverrides.push({ name: "red", scale: v2.scale, note: v2.note });
|
|
3055
|
+
}
|
|
3056
|
+
let warnVariant = null;
|
|
3057
|
+
if (signalMoves) {
|
|
3058
|
+
const warn = sigScales.get("yellow");
|
|
3059
|
+
warnVariant = warningVariant(scale, warn.scale, warn.def);
|
|
3060
|
+
if (warnVariant) pending = pending.filter((n) => n !== "yellow");
|
|
3061
|
+
for (const sigName of ["yellow", "green", "blue"]) {
|
|
3062
|
+
const { def, scale: canonical } = sigScales.get(sigName);
|
|
3063
|
+
const shift = pickSignalShift(scale, canonical, def, opts?.contrastProfile);
|
|
3064
|
+
if (shift) {
|
|
3065
|
+
signalOverrides.push({ name: sigName, scale: shift.scale, note: shift.note });
|
|
3066
|
+
pending = pending.filter((n) => n !== sigName);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
return { scale, shearDeg: 0, redRepel, warningVariant: warnVariant, pending, signalOverrides };
|
|
3071
|
+
}
|
|
3072
|
+
var normalizeSecondaryStyle = (s) => s === "tint" || s === "pastel" || s === "muted" || s === "vibrant" ? "exact" : s;
|
|
3073
|
+
var DEFAULT_SECONDARY = { kL: 0.65, kC: 0.5, kR: 0.4, lRoom: 0.97, minGapApp: 10, darkFlatGapApp: 40 };
|
|
3074
|
+
var LIGHT_GROUND_APP = grayApparentL(1);
|
|
3075
|
+
function defaultSecondarySeed(hex) {
|
|
3076
|
+
const seed = hexToOklch(hex);
|
|
3077
|
+
const d = DEFAULT_SECONDARY;
|
|
3078
|
+
let L2 = seed.L + d.kL * Math.max(0, d.lRoom - seed.L);
|
|
3079
|
+
const H2 = seed.H;
|
|
3080
|
+
let C2 = Math.min(d.kC * seed.C, d.kR * maxChromaAt(L2, H2));
|
|
3081
|
+
for (let i = 0; i < 2; i++) {
|
|
3082
|
+
if (LIGHT_GROUND_APP - apparentL(L2, clampChromaToGamut(L2, C2, H2), H2) < d.minGapApp) {
|
|
3083
|
+
L2 = solveLForApparent(LIGHT_GROUND_APP - d.minGapApp, C2, H2);
|
|
3084
|
+
C2 = Math.min(d.kC * seed.C, d.kR * maxChromaAt(L2, H2));
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
const cc = clampChromaToGamut(L2, C2, H2);
|
|
3088
|
+
const [rl, gl, bl] = oklchToLinearRgb(L2, cc, H2);
|
|
3089
|
+
const gm2 = (v2) => {
|
|
3090
|
+
const x2 = Math.min(1, Math.max(0, v2));
|
|
3091
|
+
return x2 <= 31308e-7 ? 12.92 * x2 : 1.055 * x2 ** (1 / 2.4) - 0.055;
|
|
3092
|
+
};
|
|
3093
|
+
const ch = (v2) => Math.round(gm2(v2) * 255).toString(16).padStart(2, "0");
|
|
3094
|
+
return `#${ch(rl)}${ch(gl)}${ch(bl)}`;
|
|
3095
|
+
}
|
|
3096
|
+
var SUBTLE_TINT_MULT = 8;
|
|
3097
|
+
var SUBTLE_PASTEL_K = 0.35;
|
|
3098
|
+
var OUTLINE_HOVER_ALPHA = 0.09;
|
|
3099
|
+
var OUTLINE_PRESSED_ALPHA = 0.18;
|
|
3100
|
+
var SOFT_ON_CTA_ALPHA = { light: 0.75, dark: 0.8 };
|
|
3101
|
+
function softOnCtaPasses(s, mode) {
|
|
3102
|
+
const white = mode === "light" ? s.onFillTextIsWhite : s.onFillTextIsWhiteDark;
|
|
3103
|
+
const pole = white ? 1 : 0;
|
|
3104
|
+
const a = SOFT_ON_CTA_ALPHA[mode];
|
|
3105
|
+
const states = mode === "light" ? [s.cta, s.ctaHover, s.ctaPressed] : [s.ctaDark, s.ctaHoverDark, s.ctaPressedDark];
|
|
3106
|
+
const lin = (v2) => v2 <= 0.04045 ? v2 / 12.92 : ((v2 + 0.055) / 1.055) ** 2.4;
|
|
3107
|
+
const relY = (r, g2, b) => 0.2126 * lin(r) + 0.7152 * lin(g2) + 0.0722 * lin(b);
|
|
3108
|
+
const q2 = (v2) => Math.round(Math.min(1, Math.max(0, v2)) * 255) / 255;
|
|
3109
|
+
for (const st of states) {
|
|
3110
|
+
const e = srgbEmitChannels(st);
|
|
3111
|
+
const fr = q2(e.r), fg = q2(e.g), fb = q2(e.b);
|
|
3112
|
+
const fillY = relY(fr, fg, fb);
|
|
3113
|
+
const textY = relY(pole * a + fr * (1 - a), pole * a + fg * (1 - a), pole * a + fb * (1 - a));
|
|
3114
|
+
if (contrastRatio(textY, fillY) < 4.5) return false;
|
|
3115
|
+
}
|
|
3116
|
+
return true;
|
|
3117
|
+
}
|
|
3118
|
+
var DEFAULT_LINK_HEX = "#0B57D0";
|
|
3119
|
+
function resolveLinkTrio(linkHex, contrastProfile) {
|
|
3120
|
+
const s = resolveBrand(linkHex, "link", { skipCollisionRules: true, contrastProfile }).scale;
|
|
3121
|
+
const at = (arr, n) => {
|
|
3122
|
+
const st = arr.find((x2) => x2.stop === n);
|
|
3123
|
+
if (!st) throw new Error(`resolveLinkTrio: the link resolve has no ink stop ${n}`);
|
|
3124
|
+
return st;
|
|
3125
|
+
};
|
|
3126
|
+
return {
|
|
3127
|
+
link: at(s.light, 9),
|
|
3128
|
+
linkHover: at(s.light, 10),
|
|
3129
|
+
linkPressed: at(s.light, 11),
|
|
3130
|
+
linkDark: at(s.dark, 9),
|
|
3131
|
+
linkHoverDark: at(s.dark, 10),
|
|
3132
|
+
linkPressedDark: at(s.dark, 11)
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
function resolveLinkInverseTrio(seedHex, contrastProfile) {
|
|
3136
|
+
const ground = INK_30_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
|
|
3137
|
+
const s = resolveBrand(seedHex, "link-inverse", {
|
|
3138
|
+
skipCollisionRules: true,
|
|
3139
|
+
contrastProfile,
|
|
3140
|
+
// the cross — ramp keyed, ground mode-swapped
|
|
3141
|
+
inkGround: { light: ground.dark, dark: ground.light }
|
|
3142
|
+
}).scale;
|
|
3143
|
+
const at = (arr, n) => {
|
|
3144
|
+
const st = arr.find((x2) => x2.stop === n);
|
|
3145
|
+
if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no ink stop ${n}`);
|
|
3146
|
+
return st;
|
|
3147
|
+
};
|
|
3148
|
+
return {
|
|
3149
|
+
// light MODE reads the DARK ramp, and vice versa — the cross above
|
|
3150
|
+
link: at(s.dark, 9),
|
|
3151
|
+
linkHover: at(s.dark, 10),
|
|
3152
|
+
linkPressed: at(s.dark, 11),
|
|
3153
|
+
linkDark: at(s.light, 9),
|
|
3154
|
+
linkHoverDark: at(s.light, 10),
|
|
3155
|
+
linkPressedDark: at(s.light, 11)
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
function escapeCtaFamily(nScale, mode, contrastProfile) {
|
|
3159
|
+
const strongInk = (mode === "light" ? nScale.light : nScale.dark).find((s) => s.stop === 11);
|
|
3160
|
+
if (!strongInk) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
|
|
3161
|
+
const mk = (stop, L) => makeStop(stop, L, strongInk.C, strongInk.H);
|
|
3162
|
+
const cta = mk(9, strongInk.L);
|
|
3163
|
+
const ctaHover = mk(10, stateFillL(strongInk.L, mode, 1));
|
|
3164
|
+
const ctaPressed = mk(11, stateFillL(strongInk.L, mode, 2));
|
|
3165
|
+
const onEnforce = contrastProfile !== "apca";
|
|
3166
|
+
const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
|
|
3167
|
+
const onFillIsWhite = onTextIsWhite(apcaY(cta.r, cta.g, cta.b), cta.L, cta.C, cta.H, onEnforce, onFloor);
|
|
3168
|
+
return { cta, ctaHover, ctaPressed, onFillIsWhite };
|
|
3169
|
+
}
|
|
3170
|
+
var SECONDARY_DISTINCT_DELTA_E = 0.12;
|
|
3171
|
+
var ctaDistinctness = (p, s) => {
|
|
3172
|
+
const light = stopDeltaE(p.cta, s.cta);
|
|
3173
|
+
const dark = stopDeltaE(p.ctaDark, s.ctaDark);
|
|
3174
|
+
return { light, dark, close: Math.min(light, dark) < SECONDARY_DISTINCT_DELTA_E };
|
|
3175
|
+
};
|
|
3176
|
+
function resolveTheme(input) {
|
|
3177
|
+
const pExact = input.primaryMode ? input.primaryMode === "exact" : input.exact;
|
|
3178
|
+
const pArchetype = input.primaryArchetype ?? input.archetypeOverride;
|
|
3179
|
+
const secStyle = (input.secondaryStyle && normalizeSecondaryStyle(input.secondaryStyle)) ?? (input.secondaryLevel === "standard" ? "exact" : input.secondaryLevel === "subtle" ? "exact" : "exact");
|
|
3180
|
+
const sArchetype = input.secondaryArchetype;
|
|
3181
|
+
const opts = { exact: pExact, style: input.style, contrastProfile: input.contrastProfile, apcaClearance: input.apcaClearance };
|
|
3182
|
+
const primary = resolveBrand(input.primaryHex, input.name ?? "brand", { ...opts, archetypeOverride: pArchetype });
|
|
3183
|
+
const cp = input.contrastProfile;
|
|
3184
|
+
const sigScales = signalScalesFor(cp);
|
|
3185
|
+
const notes = [];
|
|
3186
|
+
if (pExact) {
|
|
3187
|
+
const red = sigScales.get("red");
|
|
3188
|
+
const redMoved = primary.signalOverrides.some((o) => o.name === "red");
|
|
3189
|
+
if (!redMoved && redGateDist(primary.scale.cta, red.scale.cta) <= RED_GATE.G)
|
|
3190
|
+
notes.push("exact mode: this color sits in the red signal's family \u2014 outline style is recommended for destructive actions beside the primary cta");
|
|
3191
|
+
const c = primary.scale.cta;
|
|
3192
|
+
const Ybg = apcaY(...encodedChannels(c.L, c.C, c.H));
|
|
3193
|
+
const best = Math.max(
|
|
3194
|
+
Math.abs(apcaLc(apcaY(...encodedChannels(1, 0, 0)), Ybg)),
|
|
3195
|
+
Math.abs(apcaLc(apcaY(...encodedChannels(0, 0, 0)), Ybg))
|
|
3196
|
+
);
|
|
3197
|
+
if (best < 60)
|
|
3198
|
+
notes.push(`exact mode: no on-cta text reaches APCA Lc 60 on this cta (best ${best.toFixed(0)}) \u2014 the better pole ships; recommended mode guarantees text contrast`);
|
|
3199
|
+
}
|
|
3200
|
+
const mergedOverrides = [...primary.signalOverrides];
|
|
3201
|
+
const adoptedForSecondary = /* @__PURE__ */ new Set();
|
|
3202
|
+
const effectiveOf = (name) => mergedOverrides.find((o) => o.name === name)?.scale ?? sigScales.get(name).scale;
|
|
3203
|
+
const mergeSecondarySignals = (secScale, secSeedHex) => {
|
|
3204
|
+
const cp2 = opts?.contrastProfile;
|
|
3205
|
+
const adopt = (name, scale2, note) => {
|
|
3206
|
+
const i = mergedOverrides.findIndex((o) => o.name === name);
|
|
3207
|
+
if (i >= 0) mergedOverrides.splice(i, 1);
|
|
3208
|
+
mergedOverrides.push({ name, scale: scale2, note });
|
|
3209
|
+
adoptedForSecondary.add(name);
|
|
3210
|
+
};
|
|
3211
|
+
const red = sigScales.get("red");
|
|
3212
|
+
const primaryRed = mergedOverrides.find((o) => o.name === "red");
|
|
3213
|
+
if (!primaryRed) {
|
|
3214
|
+
const v2 = redComplementVariant(secScale.cta, hexToOklch(secSeedHex), false, red, cp2);
|
|
3215
|
+
if (v2 && redCleanBeside(primary.scale.cta, v2.scale.cta)) adopt("red", v2.scale, `${v2.note} (for the secondary)`);
|
|
3216
|
+
}
|
|
3217
|
+
for (const sigName of ["yellow", "green", "blue"]) {
|
|
3218
|
+
const { def, scale: canonical } = sigScales.get(sigName);
|
|
3219
|
+
const existing = mergedOverrides.find((o) => o.name === sigName);
|
|
3220
|
+
const effective = existing?.scale ?? canonical;
|
|
3221
|
+
if (!checkHueCollision(secScale, effective, def).collides) continue;
|
|
3222
|
+
const clearsBoth = (cand) => !checkHueCollision(primary.scale, cand, def).collides && !checkHueCollision(secScale, cand, def).collides;
|
|
3223
|
+
if (!existing) {
|
|
3224
|
+
const shift = pickSignalShift(secScale, canonical, def, cp2);
|
|
3225
|
+
const ok = !!shift && (sigName === "yellow" ? !checkHueCollision(primary.scale, shift.scale, def).collides : clearsBoth(shift.scale));
|
|
3226
|
+
if (shift && ok) {
|
|
3227
|
+
adopt(sigName, shift.scale, `${shift.note} (for the secondary)`);
|
|
3228
|
+
continue;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
const alt = signalSwapVariants(def, cp2).find((s) => clearsBoth(s.scale));
|
|
3232
|
+
if (alt) adopt(sigName, alt.scale, `${alt.note} (for the secondary)`);
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
const signalNotesFor = (scale2, wording) => {
|
|
3236
|
+
const out = [];
|
|
3237
|
+
for (const def of SIGNALS2) {
|
|
3238
|
+
if (adoptedForSecondary.has(def.name)) continue;
|
|
3239
|
+
const h = checkHueCollision(scale2, effectiveOf(def.name), def, { minV: SECONDARY_NOTE_MIN_V });
|
|
3240
|
+
if (h.collides) out.push(wording(def.name, Math.min(h.washDeltaE.light, h.washDeltaE.dark)));
|
|
3241
|
+
}
|
|
3242
|
+
return out;
|
|
3243
|
+
};
|
|
3244
|
+
const secOpts = {
|
|
3245
|
+
skipCollisionRules: true,
|
|
3246
|
+
contrastProfile: cp,
|
|
3247
|
+
darkCtaFlatApp: DEFAULT_SECONDARY.darkFlatGapApp
|
|
3248
|
+
};
|
|
3249
|
+
const resolveDefaultModel = (seedHex) => {
|
|
3250
|
+
const liftedHex = defaultSecondarySeed(seedHex);
|
|
3251
|
+
return {
|
|
3252
|
+
liftedHex,
|
|
3253
|
+
scale: resolveBrand(liftedHex, "secondary", { ...secOpts, archetypeOverride: sArchetype }).scale
|
|
3254
|
+
};
|
|
3255
|
+
};
|
|
3256
|
+
const resolveCustomModel = (seedHex) => {
|
|
3257
|
+
const tintedHex = defaultSecondarySeed(seedHex);
|
|
3258
|
+
const own = resolveBrand(seedHex, "secondary", { ...opts, exact: true, skipCollisionRules: true, archetypeOverride: sArchetype }).scale;
|
|
3259
|
+
const tinted = resolveBrand(tintedHex, "secondary", secOpts).scale;
|
|
3260
|
+
return {
|
|
3261
|
+
tintedHex,
|
|
3262
|
+
scale: {
|
|
3263
|
+
...own,
|
|
3264
|
+
cta: tinted.cta,
|
|
3265
|
+
ctaHover: tinted.ctaHover,
|
|
3266
|
+
ctaPressed: tinted.ctaPressed,
|
|
3267
|
+
ctaDark: tinted.ctaDark,
|
|
3268
|
+
ctaHoverDark: tinted.ctaHoverDark,
|
|
3269
|
+
ctaPressedDark: tinted.ctaPressedDark,
|
|
3270
|
+
onFillTextIsWhite: tinted.onFillTextIsWhite,
|
|
3271
|
+
onFillTextIsWhiteDark: tinted.onFillTextIsWhiteDark
|
|
3272
|
+
}
|
|
3273
|
+
};
|
|
3274
|
+
};
|
|
3275
|
+
if (!input.secondaryHex) {
|
|
3276
|
+
if (!input.deriveSecondary) return { primary, themed: primary, secondary: null, signalOverrides: primary.signalOverrides, notes };
|
|
3277
|
+
const { liftedHex, scale: scale2 } = resolveDefaultModel(input.primaryHex);
|
|
3278
|
+
mergeSecondarySignals(scale2, liftedHex);
|
|
3279
|
+
return {
|
|
3280
|
+
primary,
|
|
3281
|
+
themed: { ...primary, signalOverrides: mergedOverrides },
|
|
3282
|
+
secondary: {
|
|
3283
|
+
scale: scale2,
|
|
3284
|
+
style: "default",
|
|
3285
|
+
level: "subtle",
|
|
3286
|
+
demoted: false,
|
|
3287
|
+
derived: true,
|
|
3288
|
+
notes: [
|
|
3289
|
+
`secondary derived from the brand color (default model, seed ${liftedHex})`,
|
|
3290
|
+
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (wash \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
|
|
3291
|
+
],
|
|
3292
|
+
distinctness: ctaDistinctness(primary.scale, scale2)
|
|
3293
|
+
},
|
|
3294
|
+
signalOverrides: mergedOverrides,
|
|
3295
|
+
notes
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
if (secStyle === "default") {
|
|
3299
|
+
const { tintedHex, scale: scale2 } = resolveCustomModel(input.secondaryHex);
|
|
3300
|
+
mergeSecondarySignals(scale2, input.secondaryHex);
|
|
3301
|
+
const distinctness2 = ctaDistinctness(primary.scale, scale2);
|
|
3302
|
+
if (distinctness2.close)
|
|
3303
|
+
notes.push(`secondary reads close to the primary (\u0394E ${Math.min(distinctness2.light, distinctness2.dark).toFixed(2)}) \u2014 consider a more distinct color`);
|
|
3304
|
+
return {
|
|
3305
|
+
primary,
|
|
3306
|
+
themed: { ...primary, signalOverrides: mergedOverrides },
|
|
3307
|
+
secondary: {
|
|
3308
|
+
scale: scale2,
|
|
3309
|
+
style: "default",
|
|
3310
|
+
level: "subtle",
|
|
3311
|
+
demoted: false,
|
|
3312
|
+
derived: false,
|
|
3313
|
+
notes: [
|
|
3314
|
+
`secondary keeps your color through the ramp; the cta is a tint of it (tint seed ${tintedHex})`,
|
|
3315
|
+
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (wash \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
|
|
3316
|
+
],
|
|
3317
|
+
distinctness: distinctness2
|
|
3318
|
+
},
|
|
3319
|
+
signalOverrides: mergedOverrides,
|
|
3320
|
+
notes
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
const rSec = resolveBrand(input.secondaryHex, "secondary", { ...opts, exact: true, skipCollisionRules: true, archetypeOverride: sArchetype });
|
|
3324
|
+
const scale = rSec.scale;
|
|
3325
|
+
const level = "standard";
|
|
3326
|
+
mergeSecondarySignals(scale, input.secondaryHex);
|
|
3327
|
+
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (wash \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
|
|
3328
|
+
const distinctness = ctaDistinctness(primary.scale, scale);
|
|
3329
|
+
if (distinctness.close)
|
|
3330
|
+
notes.push(`secondary reads close to the primary (\u0394E ${Math.min(distinctness.light, distinctness.dark).toFixed(2)}) \u2014 consider a more distinct color`);
|
|
3331
|
+
return {
|
|
3332
|
+
primary,
|
|
3333
|
+
themed: { ...primary, signalOverrides: mergedOverrides },
|
|
3334
|
+
secondary: { scale, style: secStyle, level, demoted: false, derived: false, notes: secNotes, distinctness },
|
|
3335
|
+
signalOverrides: mergedOverrides,
|
|
3336
|
+
notes
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
// src/engine/tokenNames.ts
|
|
3341
|
+
var SHARED_NAMES = {
|
|
3342
|
+
1: "paper-99",
|
|
3343
|
+
2: "paper-97",
|
|
3344
|
+
// stop 3 renamed wash-3 → paper-3 (owner 2026-07-24, elevation round): it is a
|
|
3345
|
+
// PLANE in both themes (light sink / dark pop), so it belongs to the surface band.
|
|
3346
|
+
// NAME ONLY — generation is index-keyed and unchanged; the internal wash machinery
|
|
3347
|
+
// (reqtoken groupOf, collision WASH_STOPS) deliberately still spans stops 3–7.
|
|
3348
|
+
// Stage B (owner 2026-08-07, names only): relabeled to the ROOT_L-derived digit,
|
|
3349
|
+
// paper-95 — same band, same index.
|
|
3350
|
+
3: "paper-95",
|
|
3351
|
+
4: "wash-92",
|
|
3352
|
+
5: "wash-89",
|
|
3353
|
+
6: "wash-85",
|
|
3354
|
+
7: "wash-80",
|
|
3355
|
+
8: "mark-74",
|
|
3356
|
+
// THE INK BAND (C49, owner 2026-08-05): the first text stop + emphasis fill (the
|
|
3357
|
+
// 2026-07-29 highlight collapse — the old highlight-9 and on-highlight are
|
|
3358
|
+
// deleted; this stop carries both jobs, its on-color a paper token), the between
|
|
3359
|
+
// text stop (a value the retired text-cta hover state used to generate bespokely,
|
|
3360
|
+
// promoted to a normal stop), and the strong text stop. The three read as states
|
|
3361
|
+
// ARE the text-style cta (owner 2026-08-12). C49 restored the top
|
|
3362
|
+
// two pre-collapse stop INDICES (11, and the off-scale anchor's 12 — emitted as a
|
|
3363
|
+
// literal in cssRender, not through this table) with the between stop taking the
|
|
3364
|
+
// vacated index 10; the indices (9/10/11) are unchanged since.
|
|
3365
|
+
9: "lead-53",
|
|
3366
|
+
10: "ink-42",
|
|
3367
|
+
11: "ink-30"
|
|
3368
|
+
};
|
|
3369
|
+
var SCALE_STOP_COUNT = Object.keys(SHARED_NAMES).length;
|
|
3370
|
+
function stopTokenName(stop) {
|
|
3371
|
+
const name = SHARED_NAMES[stop];
|
|
3372
|
+
if (!name) throw new Error(`stopTokenName: unexpected stop ${stop}`);
|
|
3373
|
+
return name;
|
|
3374
|
+
}
|
|
3375
|
+
var PAPER_100 = "paper-100";
|
|
3376
|
+
var INK_0 = "ink-0";
|
|
3377
|
+
var STAMP_FILL = "stamp-fill";
|
|
3378
|
+
var STAMP_FILL_HOVER = "stamp-fill-hover";
|
|
3379
|
+
var STAMP_FILL_PRESSED = "stamp-fill-pressed";
|
|
3380
|
+
var STAMP_EDGE = "stamp-edge";
|
|
3381
|
+
var STAMP_ON = "stamp-on";
|
|
3382
|
+
var STAMP_STATE_LEAVES = {
|
|
3383
|
+
[STAMP_FILL]: "stamp/fill",
|
|
3384
|
+
[STAMP_FILL_HOVER]: "stamp/fill-hover",
|
|
3385
|
+
[STAMP_FILL_PRESSED]: "stamp/fill-pressed",
|
|
3386
|
+
[STAMP_EDGE]: "stamp/edge",
|
|
3387
|
+
[STAMP_ON]: "stamp/on"
|
|
3388
|
+
};
|
|
3389
|
+
var STAMP_LEAF = {
|
|
3390
|
+
FILL: STAMP_STATE_LEAVES[STAMP_FILL],
|
|
3391
|
+
FILL_HOVER: STAMP_STATE_LEAVES[STAMP_FILL_HOVER],
|
|
3392
|
+
FILL_PRESSED: STAMP_STATE_LEAVES[STAMP_FILL_PRESSED],
|
|
3393
|
+
EDGE: STAMP_STATE_LEAVES[STAMP_EDGE],
|
|
3394
|
+
ON: STAMP_STATE_LEAVES[STAMP_ON]
|
|
3395
|
+
};
|
|
3396
|
+
var TOKEN_ORDER = [
|
|
3397
|
+
...Object.values(SHARED_NAMES),
|
|
3398
|
+
STAMP_FILL,
|
|
3399
|
+
STAMP_FILL_HOVER,
|
|
3400
|
+
STAMP_FILL_PRESSED,
|
|
3401
|
+
STAMP_ON,
|
|
3402
|
+
"identity"
|
|
3403
|
+
];
|
|
3404
|
+
function tokenOrder(name) {
|
|
3405
|
+
const i = TOKEN_ORDER.indexOf(name);
|
|
3406
|
+
return i === -1 ? TOKEN_ORDER.length : i;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
// src/engine/cssRender.ts
|
|
3410
|
+
var { brandPrimary: CSS_BRAND, brandSecondary: CSS_SECONDARY, neutral: CSS_NEUTRAL } = CSS_FAMILY;
|
|
3411
|
+
function toHex2(r, g2, b) {
|
|
3412
|
+
const ch = (v2) => Math.round(Math.min(1, Math.max(0, v2)) * 255).toString(16).padStart(2, "0");
|
|
3413
|
+
return `#${ch(r)}${ch(g2)}${ch(b)}`;
|
|
3414
|
+
}
|
|
3415
|
+
var stopHex = (s) => {
|
|
3416
|
+
const { r, g: g2, b } = srgbEmitChannels(s);
|
|
3417
|
+
return toHex2(r, g2, b);
|
|
3418
|
+
};
|
|
3419
|
+
var p3Value = (s) => {
|
|
3420
|
+
const e = (v2) => Math.min(1, Math.max(0, v2)).toFixed(4);
|
|
3421
|
+
const [r, g2, b] = masterEmitChannels(s);
|
|
3422
|
+
return `color(display-p3 ${e(r)} ${e(g2)} ${e(b)})`;
|
|
3423
|
+
};
|
|
3424
|
+
var p3Differs = (s) => s.C > clampChromaToGamut(s.L, s.C, s.H, "srgb") + 1e-4;
|
|
3425
|
+
var P3_SUPPORTS = "@supports (color: color(display-p3 1 1 1))";
|
|
3426
|
+
var P3_MEDIA = "@media (color-gamut: p3)";
|
|
3427
|
+
function stopsToVars(stops, prefix) {
|
|
3428
|
+
return [...stops].sort((a, b) => tokenOrder(stopTokenName(a.stop)) - tokenOrder(stopTokenName(b.stop))).map((s) => ` --${prefix}-${stopTokenName(s.stop)}: ${stopHex(s)};`).join("\n");
|
|
3429
|
+
}
|
|
3430
|
+
var onColor = (white) => white ? "#ffffff" : "#000000";
|
|
3431
|
+
var CTA_BORDER_LC_FLOOR = 15;
|
|
3432
|
+
var CTA_BORDER_PAGE_STOP = { light: 2, dark: 1 };
|
|
3433
|
+
function pageStopFor(neutral, mode) {
|
|
3434
|
+
const stops = mode === "light" ? neutral.light : neutral.dark;
|
|
3435
|
+
return stops.find((x2) => x2.stop === CTA_BORDER_PAGE_STOP[mode]);
|
|
3436
|
+
}
|
|
3437
|
+
var OFFSET_ALPHAS = { 6: 0.06, 8: 0.08, 16: 0.16 };
|
|
3438
|
+
var ctaBorderRung = (prefix) => prefix === CSS_NEUTRAL ? 8 : prefix === CSS_SECONDARY ? 6 : 16;
|
|
3439
|
+
var offsetLeafName = (rung) => String(rung).padStart(3, "0");
|
|
3440
|
+
var offsetVarName = (rung) => `--alpha-${offsetLeafName(rung)}`;
|
|
3441
|
+
var TRANSPARENT_VAR = "--alpha-transparent";
|
|
3442
|
+
var offsetRgba = (rung, mode) => mode === "light" ? `rgba(0, 0, 0, ${OFFSET_ALPHAS[rung]})` : `rgba(255, 255, 255, ${OFFSET_ALPHAS[rung]})`;
|
|
3443
|
+
var alphaRootVars = (mode) => [
|
|
3444
|
+
` ${TRANSPARENT_VAR}: transparent;`,
|
|
3445
|
+
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`)
|
|
3446
|
+
];
|
|
3447
|
+
function ctaPageLc(s, mode, page) {
|
|
3448
|
+
const cta = mode === "light" ? s.cta : s.ctaDark;
|
|
3449
|
+
return Math.abs(apcaLc(apcaY(cta.r, cta.g, cta.b), apcaY(page.r, page.g, page.b)));
|
|
3450
|
+
}
|
|
3451
|
+
function ctaNeedsBorder(s, mode, page) {
|
|
3452
|
+
if (!page) return false;
|
|
3453
|
+
return ctaPageLc(s, mode, page) < CTA_BORDER_LC_FLOOR;
|
|
3454
|
+
}
|
|
3455
|
+
function brandKindBody(prefix, s, mode, page) {
|
|
3456
|
+
const stops = mode === "light" ? s.light : s.dark;
|
|
3457
|
+
const f = ctaFamilyOf(s, mode);
|
|
3458
|
+
const onCta = mode === "light" ? s.onFillTextIsWhite : s.onFillTextIsWhiteDark;
|
|
3459
|
+
const border = ctaNeedsBorder(s, mode, page);
|
|
3460
|
+
const scaleBlock = [
|
|
3461
|
+
...stops.map((x2) => ({ name: stopTokenName(x2.stop), line: ` --${prefix}-${stopTokenName(x2.stop)}: ${stopHex(x2)};` }))
|
|
3462
|
+
].sort((a, b) => tokenOrder(a.name) - tokenOrder(b.name)).map((v2) => v2.line).join("\n");
|
|
3463
|
+
return [
|
|
3464
|
+
scaleBlock,
|
|
3465
|
+
` --${prefix}-stamp-fill: ${stopHex(f.cta)};`,
|
|
3466
|
+
` --${prefix}-stamp-fill-hover: ${stopHex(f.ctaHover)};`,
|
|
3467
|
+
` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
|
|
3468
|
+
` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
|
|
3469
|
+
// the SOFT on-cta (owner 2026-08-04: "neutral cta on's should also be the alpha"): the
|
|
3470
|
+
// neutral's cta is the scale-fed WASH-level fill, the system's other quiet cta, so its
|
|
3471
|
+
// button text takes the pole AT ALPHA like the default-model secondary's — composited
|
|
3472
|
+
// over whatever state the fill is in, so hover/pressed carry their own legibility. Same
|
|
3473
|
+
// register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
|
|
3474
|
+
// primitive. Emitted HERE rather than as a post-body override because the neutral's is
|
|
3475
|
+
// unconditional — the secondary's rides the cascade only because it gates on the style
|
|
3476
|
+
// chip. Loud fills (brand, signals, the cta escape) keep the solid pole.
|
|
3477
|
+
prefix === CSS_NEUTRAL ? ` --${CSS_NEUTRAL}-stamp-on: rgba(${onCta ? "255, 255, 255" : "0, 0, 0"}, ${SOFT_ON_CTA_ALPHA[mode]});` : ` --${prefix}-stamp-on: ${onColor(onCta)};`
|
|
3478
|
+
];
|
|
3479
|
+
}
|
|
3480
|
+
function ctaFamilyOf(s, mode) {
|
|
3481
|
+
return mode === "light" ? { cta: s.cta, ctaHover: s.ctaHover, ctaPressed: s.ctaPressed } : { cta: s.ctaDark, ctaHover: s.ctaHoverDark, ctaPressed: s.ctaPressedDark };
|
|
3482
|
+
}
|
|
3483
|
+
function brandKindP3Body(prefix, s, mode) {
|
|
3484
|
+
const stops = mode === "light" ? s.light : s.dark;
|
|
3485
|
+
const f = ctaFamilyOf(s, mode);
|
|
3486
|
+
const out = [];
|
|
3487
|
+
for (const st of stops) if (p3Differs(st)) out.push(` --${prefix}-${stopTokenName(st.stop)}: ${p3Value(st)};`);
|
|
3488
|
+
for (const [name, st] of [["stamp-fill", f.cta], ["stamp-fill-hover", f.ctaHover], ["stamp-fill-pressed", f.ctaPressed]])
|
|
3489
|
+
if (p3Differs(st)) out.push(` --${prefix}-${name}: ${p3Value(st)};`);
|
|
3490
|
+
return out;
|
|
3491
|
+
}
|
|
3492
|
+
function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBorder = true) {
|
|
3493
|
+
const s = generateNeutralScale(brandH, level, contrastProfile);
|
|
3494
|
+
const nPage = (mode) => ctaBorder ? pageStopFor(s, mode) : void 0;
|
|
3495
|
+
const p02 = (st, fallback) => st ? stopHex(st) : fallback;
|
|
3496
|
+
const p3Light = brandKindP3Body(CSS_NEUTRAL, s, "light");
|
|
3497
|
+
const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
|
|
3498
|
+
return [
|
|
3499
|
+
`${selector} {`,
|
|
3500
|
+
` --${PAPER_100}: ${p02(s.paper0, "#ffffff")};`,
|
|
3501
|
+
` --${INK_0}: ${p02(s.ink0, "#000000")};`,
|
|
3502
|
+
// the neutral IS the page, so it is judged against its own paper stop
|
|
3503
|
+
// the neutral's overlays never read the visibility bar (the exempt family), so
|
|
3504
|
+
// the brandless chrome context passes 0
|
|
3505
|
+
...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
|
|
3506
|
+
`}`,
|
|
3507
|
+
`${selector}[data-theme="dark"] {`,
|
|
3508
|
+
` --${PAPER_100}: ${p02(s.paper0Dark, "#000000")};`,
|
|
3509
|
+
` --${INK_0}: ${p02(s.ink0Dark, "#ffffff")};`,
|
|
3510
|
+
...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
|
|
3511
|
+
`}`,
|
|
3512
|
+
...p3Light.length || p3Dark.length ? [
|
|
3513
|
+
`${P3_SUPPORTS} {`,
|
|
3514
|
+
`${P3_MEDIA} {`,
|
|
3515
|
+
...p3Light.length ? [`${selector} {`, ...p3Light, `}`] : [],
|
|
3516
|
+
...p3Dark.length ? [`${selector}[data-theme="dark"] {`, ...p3Dark, `}`] : [],
|
|
3517
|
+
`}`,
|
|
3518
|
+
`}`
|
|
3519
|
+
] : []
|
|
3520
|
+
].join("\n");
|
|
3521
|
+
}
|
|
3522
|
+
var SIGNALS_DARK_SELECTOR = ':root[data-theme="dark"], [data-theme="dark"]';
|
|
3523
|
+
function signalsCss(contrastProfile) {
|
|
3524
|
+
const sigScales = signalScalesFor(contrastProfile);
|
|
3525
|
+
const canonicalNeutral = generateNeutralScale(0, "default", contrastProfile);
|
|
3526
|
+
const sigPage = { light: pageStopFor(canonicalNeutral, "light"), dark: pageStopFor(canonicalNeutral, "dark") };
|
|
3527
|
+
const lightBlocks = [];
|
|
3528
|
+
const darkBlocks = [];
|
|
3529
|
+
const p3LightBlocks = [];
|
|
3530
|
+
const p3DarkBlocks = [];
|
|
3531
|
+
for (const sig of SIGNALS2) {
|
|
3532
|
+
const { scale } = sigScales.get(sig.name);
|
|
3533
|
+
lightBlocks.push(...brandKindBody(sig.emitName, scale, "light", sigPage.light));
|
|
3534
|
+
darkBlocks.push(...brandKindBody(sig.emitName, scale, "dark", sigPage.dark));
|
|
3535
|
+
p3LightBlocks.push(...brandKindP3Body(sig.emitName, scale, "light"));
|
|
3536
|
+
p3DarkBlocks.push(...brandKindP3Body(sig.emitName, scale, "dark"));
|
|
3537
|
+
}
|
|
3538
|
+
return [
|
|
3539
|
+
`/* Signal scales \u2014 engine-generated from canonical hexes, shared across brands */`,
|
|
3540
|
+
`:root {`,
|
|
3541
|
+
// the system alpha variables every family's cta-border aliases (owner 2026-07-29) — this is
|
|
3542
|
+
// the engine's one global :root, the CSS counterpart of the Figma side's system/alpha/* rows.
|
|
3543
|
+
// Emitted in BOTH blocks because the offset rungs are scheme-DIVERGENT (black in light,
|
|
3544
|
+
// white in dark); --alpha-transparent repeats harmlessly and keeps the set together.
|
|
3545
|
+
...alphaRootVars("light"),
|
|
3546
|
+
...lightBlocks,
|
|
3547
|
+
`}`,
|
|
3548
|
+
`${SIGNALS_DARK_SELECTOR} {`,
|
|
3549
|
+
...alphaRootVars("dark"),
|
|
3550
|
+
...darkBlocks,
|
|
3551
|
+
`}`,
|
|
3552
|
+
...p3LightBlocks.length || p3DarkBlocks.length ? [
|
|
3553
|
+
`${P3_SUPPORTS} {`,
|
|
3554
|
+
`${P3_MEDIA} {`,
|
|
3555
|
+
...p3LightBlocks.length ? [`:root {`, ...p3LightBlocks, `}`] : [],
|
|
3556
|
+
...p3DarkBlocks.length ? [`${SIGNALS_DARK_SELECTOR} {`, ...p3DarkBlocks, `}`] : [],
|
|
3557
|
+
`}`,
|
|
3558
|
+
`}`
|
|
3559
|
+
] : []
|
|
3560
|
+
].join("\n");
|
|
3561
|
+
}
|
|
3562
|
+
function annotationNote(r, opts) {
|
|
3563
|
+
let note = "";
|
|
3564
|
+
if (r.shearDeg !== 0) note += ` \xB7 shear ${r.shearDeg > 0 ? "+" : ""}${r.shearDeg.toFixed(1)}\xB0`;
|
|
3565
|
+
if (opts?.archetypeOverride) note += ` \xB7 archetype override \u2192 ${opts.archetypeOverride}`;
|
|
3566
|
+
if (r.redRepel) note += ` \xB7 conflict with red resolved \u2014 the action color exits the error register by its nearest edge`;
|
|
3567
|
+
else if (r.signalOverrides.some((o) => o.name === "red")) note += ` \xB7 conflict with red resolved \u2014 the brand keeps its exact color and the error signal ships a per-brand variant`;
|
|
3568
|
+
if (r.warningVariant === "lemon") note += ` \xB7 yellow signal shifted to a cooler lemon`;
|
|
3569
|
+
if (r.warningVariant === "macaroni") note += ` \xB7 yellow signal kept standard amber (cool-yellow brand)`;
|
|
3570
|
+
for (const o of r.signalOverrides.filter((o2) => o2.name !== "yellow")) note += ` \xB7 ${o.note}`;
|
|
3571
|
+
if (r.pending.length) note += ` \xB7 overlaps ${r.pending.join(", ")} \u2014 softened treatment still in design`;
|
|
3572
|
+
return note;
|
|
3573
|
+
}
|
|
3574
|
+
function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel = "default", contrastProfile, secondaryStyle, ctaEscape, linkHex, ctaBorder = true, neutralH) {
|
|
3575
|
+
const { scale } = r;
|
|
3576
|
+
const effOverrides = ctaEscape ? r.signalOverrides.filter((o) => o.name !== "red") : r.signalOverrides;
|
|
3577
|
+
const rEff = ctaEscape ? { ...r, redRepel: null, signalOverrides: effOverrides } : r;
|
|
3578
|
+
const note = annotationNote(rEff) + (ctaEscape ? " \xB7 neutral cta escape active \u2014 the action colors ride the brand neutral; the red signal ships canonical" : "") + noteSuffix;
|
|
3579
|
+
const nScale = generateNeutralScale(neutralH ?? scale.brandH, neutralLevel, contrastProfile);
|
|
3580
|
+
const page = ctaBorder ? { light: pageStopFor(nScale, "light"), dark: pageStopFor(nScale, "dark") } : { light: void 0, dark: void 0 };
|
|
3581
|
+
const mirrorBody = (prefix, mode) => {
|
|
3582
|
+
const stops = mode === "light" ? scale.light : scale.dark;
|
|
3583
|
+
const alias = (name) => ` --${prefix}-${name}: var(--${CSS_BRAND}-${name});`;
|
|
3584
|
+
return [
|
|
3585
|
+
...stops.map((x2) => alias(stopTokenName(x2.stop))),
|
|
3586
|
+
alias("stamp-fill"),
|
|
3587
|
+
alias("stamp-fill-hover"),
|
|
3588
|
+
alias("stamp-fill-pressed"),
|
|
3589
|
+
alias("stamp-edge"),
|
|
3590
|
+
alias("stamp-on")
|
|
3591
|
+
];
|
|
3592
|
+
};
|
|
3593
|
+
const secondaryLight = secondary ? brandKindBody(CSS_SECONDARY, secondary, "light", page.light) : mirrorBody(CSS_SECONDARY, "light");
|
|
3594
|
+
const secondaryDark = secondary ? brandKindBody(CSS_SECONDARY, secondary, "dark", page.dark) : mirrorBody(CSS_SECONDARY, "dark");
|
|
3595
|
+
const brandIdentity = ` --${CSS_BRAND}-identity: ${scale.identityHex};`;
|
|
3596
|
+
const secondaryIdentity = secondary ? ` --${CSS_SECONDARY}-identity: ${secondary.identityHex};` : ` --${CSS_SECONDARY}-identity: var(--${CSS_BRAND}-identity);`;
|
|
3597
|
+
const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
|
|
3598
|
+
const lightAnchors = [` --${PAPER_100}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${INK_0}: ${p0hex(nScale.ink0, "#000000")};`];
|
|
3599
|
+
const darkAnchors = [` --${PAPER_100}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${INK_0}: ${p0hex(nScale.ink0Dark, "#ffffff")};`];
|
|
3600
|
+
const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
|
|
3601
|
+
const link = (mode) => linkTrio ? mode === "light" ? [` --link: ${stopHex(linkTrio.link)};`, ` --link-hover: ${stopHex(linkTrio.linkHover)};`, ` --link-pressed: ${stopHex(linkTrio.linkPressed)};`] : [` --link: ${stopHex(linkTrio.linkDark)};`, ` --link-hover: ${stopHex(linkTrio.linkHoverDark)};`, ` --link-pressed: ${stopHex(linkTrio.linkPressedDark)};`] : [
|
|
3602
|
+
` --link: var(--${CSS_BRAND}-${stopTokenName(9)});`,
|
|
3603
|
+
` --link-hover: var(--${CSS_BRAND}-${stopTokenName(10)});`,
|
|
3604
|
+
` --link-pressed: var(--${CSS_BRAND}-${stopTokenName(11)});`
|
|
3605
|
+
];
|
|
3606
|
+
const linkP3 = (mode) => {
|
|
3607
|
+
if (!linkTrio) return [];
|
|
3608
|
+
const trio = mode === "light" ? [["link", linkTrio.link], ["link-hover", linkTrio.linkHover], ["link-pressed", linkTrio.linkPressed]] : [["link", linkTrio.linkDark], ["link-hover", linkTrio.linkHoverDark], ["link-pressed", linkTrio.linkPressedDark]];
|
|
3609
|
+
return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` --${n}: ${p3Value(s)};`);
|
|
3610
|
+
};
|
|
3611
|
+
const inverseSeed = linkHex ?? scale.identityHex ?? stopHex(scale.light.find((x2) => x2.stop === 9));
|
|
3612
|
+
const linkInverseTrio = resolveLinkInverseTrio(inverseSeed, contrastProfile);
|
|
3613
|
+
const linkInverse = (mode) => mode === "light" ? [` --link-inverse: ${stopHex(linkInverseTrio.link)};`, ` --link-inverse-hover: ${stopHex(linkInverseTrio.linkHover)};`, ` --link-inverse-pressed: ${stopHex(linkInverseTrio.linkPressed)};`] : [` --link-inverse: ${stopHex(linkInverseTrio.linkDark)};`, ` --link-inverse-hover: ${stopHex(linkInverseTrio.linkHoverDark)};`, ` --link-inverse-pressed: ${stopHex(linkInverseTrio.linkPressedDark)};`];
|
|
3614
|
+
const linkInverseP3 = (mode) => {
|
|
3615
|
+
const trio = mode === "light" ? [["link-inverse", linkInverseTrio.link], ["link-inverse-hover", linkInverseTrio.linkHover], ["link-inverse-pressed", linkInverseTrio.linkPressed]] : [["link-inverse", linkInverseTrio.linkDark], ["link-inverse-hover", linkInverseTrio.linkHoverDark], ["link-inverse-pressed", linkInverseTrio.linkPressedDark]];
|
|
3616
|
+
return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` --${n}: ${p3Value(s)};`);
|
|
3617
|
+
};
|
|
3618
|
+
const escape = (mode) => {
|
|
3619
|
+
if (!ctaEscape) return [];
|
|
3620
|
+
const esc = escapeCtaFamily(nScale, mode, contrastProfile);
|
|
3621
|
+
return [
|
|
3622
|
+
` --${CSS_BRAND}-stamp-fill: ${stopHex(esc.cta)};`,
|
|
3623
|
+
` --${CSS_BRAND}-stamp-fill-hover: ${stopHex(esc.ctaHover)};`,
|
|
3624
|
+
` --${CSS_BRAND}-stamp-fill-pressed: ${stopHex(esc.ctaPressed)};`,
|
|
3625
|
+
` --${CSS_BRAND}-stamp-on: ${onColor(esc.onFillIsWhite)};`
|
|
3626
|
+
];
|
|
3627
|
+
};
|
|
3628
|
+
const softOnCta = (mode) => {
|
|
3629
|
+
if (!secondary || secondaryStyle === "outline") return [];
|
|
3630
|
+
if (secondaryStyle !== "default" && !softOnCtaPasses(secondary, mode)) return [];
|
|
3631
|
+
const white = mode === "light" ? secondary.onFillTextIsWhite : secondary.onFillTextIsWhiteDark;
|
|
3632
|
+
return [` --${CSS_SECONDARY}-stamp-on: rgba(${white ? "255, 255, 255" : "0, 0, 0"}, ${SOFT_ON_CTA_ALPHA[mode]});`];
|
|
3633
|
+
};
|
|
3634
|
+
const outline = (mode) => {
|
|
3635
|
+
if (secondaryStyle !== "outline" || !secondary) return [];
|
|
3636
|
+
const s8 = (mode === "light" ? secondary.light : secondary.dark).find((s) => s.stop === 8);
|
|
3637
|
+
const c = (v2) => Math.round(Math.min(1, Math.max(0, v2)) * 255);
|
|
3638
|
+
const s8e = s8 ? srgbEmitChannels(s8) : null;
|
|
3639
|
+
return [
|
|
3640
|
+
` --${CSS_SECONDARY}-stamp-fill: transparent;`,
|
|
3641
|
+
...s8e ? [
|
|
3642
|
+
` --${CSS_SECONDARY}-stamp-fill-hover: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_HOVER_ALPHA});`,
|
|
3643
|
+
` --${CSS_SECONDARY}-stamp-fill-pressed: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_PRESSED_ALPHA});`
|
|
3644
|
+
] : [],
|
|
3645
|
+
` --${CSS_SECONDARY}-stamp-edge: var(--${CSS_SECONDARY}-${stopTokenName(8)});`,
|
|
3646
|
+
` --${CSS_SECONDARY}-stamp-on: var(--${CSS_SECONDARY}-${stopTokenName(9)});`
|
|
3647
|
+
];
|
|
3648
|
+
};
|
|
3649
|
+
const dropOutlineCta = (lines) => secondaryStyle === "outline" ? lines.filter((l) => !l.startsWith(` --${CSS_SECONDARY}-stamp-fill:`) && !l.startsWith(` --${CSS_SECONDARY}-stamp-fill-hover:`) && !l.startsWith(` --${CSS_SECONDARY}-stamp-fill-pressed:`)) : lines;
|
|
3650
|
+
const dropEscapeCta = (lines) => ctaEscape ? lines.filter((l) => !new RegExp(`^ --${CSS_BRAND}-stamp-fill(-hover|-pressed)?:`).test(l)) : lines;
|
|
3651
|
+
const p3Light = [
|
|
3652
|
+
...dropEscapeCta(brandKindP3Body(CSS_BRAND, scale, "light")),
|
|
3653
|
+
...secondary ? dropOutlineCta(brandKindP3Body(CSS_SECONDARY, secondary, "light")) : [],
|
|
3654
|
+
...brandKindP3Body(CSS_NEUTRAL, nScale, "light"),
|
|
3655
|
+
...effOverrides.flatMap((o) => brandKindP3Body(SIGNAL_EMIT_NAME[o.name], o.scale, "light")),
|
|
3656
|
+
...linkP3("light"),
|
|
3657
|
+
...linkInverseP3("light")
|
|
3658
|
+
];
|
|
3659
|
+
const p3Dark = [
|
|
3660
|
+
...dropEscapeCta(brandKindP3Body(CSS_BRAND, scale, "dark")),
|
|
3661
|
+
...secondary ? dropOutlineCta(brandKindP3Body(CSS_SECONDARY, secondary, "dark")) : [],
|
|
3662
|
+
...brandKindP3Body(CSS_NEUTRAL, nScale, "dark"),
|
|
3663
|
+
...effOverrides.flatMap((o) => brandKindP3Body(SIGNAL_EMIT_NAME[o.name], o.scale, "dark")),
|
|
3664
|
+
...linkP3("dark"),
|
|
3665
|
+
...linkInverseP3("dark")
|
|
3666
|
+
];
|
|
3667
|
+
return [
|
|
3668
|
+
``,
|
|
3669
|
+
`[data-brand="${slug}"] {`,
|
|
3670
|
+
...lightAnchors,
|
|
3671
|
+
...brandKindBody(CSS_BRAND, scale, "light", page.light),
|
|
3672
|
+
...escape("light"),
|
|
3673
|
+
...link("light"),
|
|
3674
|
+
...linkInverse("light"),
|
|
3675
|
+
brandIdentity,
|
|
3676
|
+
...secondaryLight,
|
|
3677
|
+
...softOnCta("light"),
|
|
3678
|
+
...outline("light"),
|
|
3679
|
+
secondaryIdentity,
|
|
3680
|
+
...brandKindBody(CSS_NEUTRAL, nScale, "light", page.light),
|
|
3681
|
+
...effOverrides.flatMap((o) => brandKindBody(SIGNAL_EMIT_NAME[o.name], o.scale, "light", page.light)),
|
|
3682
|
+
`}`,
|
|
3683
|
+
`[data-brand="${slug}"][data-theme="dark"] {`,
|
|
3684
|
+
...darkAnchors,
|
|
3685
|
+
...brandKindBody(CSS_BRAND, scale, "dark", page.dark),
|
|
3686
|
+
...escape("dark"),
|
|
3687
|
+
...link("dark"),
|
|
3688
|
+
...linkInverse("dark"),
|
|
3689
|
+
...secondaryDark,
|
|
3690
|
+
...softOnCta("dark"),
|
|
3691
|
+
...outline("dark"),
|
|
3692
|
+
...brandKindBody(CSS_NEUTRAL, nScale, "dark", page.dark),
|
|
3693
|
+
...effOverrides.flatMap((o) => brandKindBody(SIGNAL_EMIT_NAME[o.name], o.scale, "dark", page.dark)),
|
|
3694
|
+
`}`,
|
|
3695
|
+
...p3Light.length || p3Dark.length ? [
|
|
3696
|
+
`${P3_SUPPORTS} {`,
|
|
3697
|
+
`${P3_MEDIA} {`,
|
|
3698
|
+
...p3Light.length ? [`[data-brand="${slug}"] {`, ...p3Light, `}`] : [],
|
|
3699
|
+
...p3Dark.length ? [`[data-brand="${slug}"][data-theme="dark"] {`, ...p3Dark, `}`] : [],
|
|
3700
|
+
`}`,
|
|
3701
|
+
`}`
|
|
3702
|
+
] : []
|
|
3703
|
+
].join("\n");
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
// src/engine/figmaRender.ts
|
|
3707
|
+
var clamp01 = (v2) => Math.min(1, Math.max(0, v2));
|
|
3708
|
+
var TRANSPARENT_TOKEN = {
|
|
3709
|
+
$type: "color",
|
|
3710
|
+
$value: { colorSpace: "srgb", components: [0, 0, 0], alpha: 0, hex: "#000000" }
|
|
3711
|
+
};
|
|
3712
|
+
var OFFSET_TOKEN = (rung, mode) => {
|
|
3713
|
+
const c = mode === "light" ? 0 : 1;
|
|
3714
|
+
return {
|
|
3715
|
+
$type: "color",
|
|
3716
|
+
$value: { colorSpace: "srgb", components: [c, c, c], alpha: OFFSET_ALPHAS[rung], hex: mode === "light" ? "#000000" : "#ffffff" }
|
|
3717
|
+
};
|
|
3718
|
+
};
|
|
3719
|
+
function colorFromStop(s) {
|
|
3720
|
+
const { r, g: g2, b } = srgbEmitChannels(s);
|
|
3721
|
+
return {
|
|
3722
|
+
$type: "color",
|
|
3723
|
+
$value: { colorSpace: "srgb", components: [clamp01(r), clamp01(g2), clamp01(b)], alpha: 1, hex: toHex2(r, g2, b) }
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
function colorFromHex(white) {
|
|
3727
|
+
const v2 = white ? 1 : 0;
|
|
3728
|
+
return { $type: "color", $value: { colorSpace: "srgb", components: [v2, v2, v2], alpha: 1, hex: white ? "#ffffff" : "#000000" } };
|
|
3729
|
+
}
|
|
3730
|
+
function colorFromHexString(hex) {
|
|
3731
|
+
const h = hex.replace("#", "");
|
|
3732
|
+
const r = parseInt(h.slice(0, 2), 16) / 255;
|
|
3733
|
+
const g2 = parseInt(h.slice(2, 4), 16) / 255;
|
|
3734
|
+
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
3735
|
+
return { $type: "color", $value: { colorSpace: "srgb", components: [r, g2, b], alpha: 1, hex: hex.toLowerCase() } };
|
|
3736
|
+
}
|
|
3737
|
+
function bandedLeaf(flat) {
|
|
3738
|
+
return STAMP_STATE_LEAVES[flat] ?? flat;
|
|
3739
|
+
}
|
|
3740
|
+
function putLeaf(g2, flat, tok) {
|
|
3741
|
+
const path = bandedLeaf(flat).split("/");
|
|
3742
|
+
let cur = g2;
|
|
3743
|
+
for (const seg of path.slice(0, -1)) {
|
|
3744
|
+
if (!(seg in cur) || "$type" in cur[seg]) cur[seg] = {};
|
|
3745
|
+
cur = cur[seg];
|
|
3746
|
+
}
|
|
3747
|
+
cur[path[path.length - 1]] = tok;
|
|
3748
|
+
}
|
|
3749
|
+
function rampGroup(stops, onFillWhite, extra) {
|
|
3750
|
+
const g2 = {};
|
|
3751
|
+
const leaves = stops.map((s) => ({ name: stopTokenName(s.stop), tok: colorFromStop(s) })).sort((a, b) => tokenOrder(a.name) - tokenOrder(b.name));
|
|
3752
|
+
for (const l of leaves) putLeaf(g2, l.name, l.tok);
|
|
3753
|
+
if (extra?.cta) putLeaf(g2, STAMP_FILL, colorFromStop(extra.cta));
|
|
3754
|
+
if (extra?.ctaHover) putLeaf(g2, STAMP_FILL_HOVER, colorFromStop(extra.ctaHover));
|
|
3755
|
+
if (extra?.ctaPressed) putLeaf(g2, STAMP_FILL_PRESSED, colorFromStop(extra.ctaPressed));
|
|
3756
|
+
if (extra?.cta) putLeaf(g2, STAMP_EDGE, extra.ctaBorder ?? TRANSPARENT_TOKEN);
|
|
3757
|
+
putLeaf(g2, STAMP_ON, colorFromHex(onFillWhite));
|
|
3758
|
+
if (extra?.identityHex) g2["identity"] = colorFromHexString(extra.identityHex);
|
|
3759
|
+
return g2;
|
|
3760
|
+
}
|
|
3761
|
+
function themeToFigma(r, input) {
|
|
3762
|
+
const { scale } = r;
|
|
3763
|
+
const secondary = input.secondary ?? scale;
|
|
3764
|
+
const secondaryOnFillLight = input.secondary ? input.secondary.onFillTextIsWhite : scale.onFillTextIsWhite;
|
|
3765
|
+
const secondaryOnFillDark = input.secondary ? input.secondary.onFillTextIsWhiteDark : scale.onFillTextIsWhiteDark;
|
|
3766
|
+
const borderPage = (mode) => input.ctaBorder ?? true ? pageStopFor(nScale, mode) : void 0;
|
|
3767
|
+
const ctaFamily = (s, mode, prefix) => ({
|
|
3768
|
+
ctaBorder: ctaNeedsBorder(s, mode, borderPage(mode)) ? OFFSET_TOKEN(ctaBorderRung(prefix), mode) : TRANSPARENT_TOKEN,
|
|
3769
|
+
...mode === "light" ? { cta: s.cta, ctaHover: s.ctaHover, ctaPressed: s.ctaPressed } : { cta: s.ctaDark, ctaHover: s.ctaHoverDark, ctaPressed: s.ctaPressedDark }
|
|
3770
|
+
});
|
|
3771
|
+
const brandExtra = (s, mode, prefix) => ({
|
|
3772
|
+
identityHex: s.identityHex,
|
|
3773
|
+
...ctaFamily(s, mode, prefix)
|
|
3774
|
+
});
|
|
3775
|
+
const nScale = generateNeutralScale(input.neutralH ?? scale.brandH, input.neutralLevel ?? "default", input.contrastProfile);
|
|
3776
|
+
const lt = input.linkHex ? resolveLinkTrio(input.linkHex, input.contrastProfile) : null;
|
|
3777
|
+
const invSeed = input.linkHex ?? scale.identityHex ?? (() => {
|
|
3778
|
+
const s9 = scale.light.find((x2) => x2.stop === 9);
|
|
3779
|
+
const e = srgbEmitChannels(s9);
|
|
3780
|
+
return toHex2(e.r, e.g, e.b);
|
|
3781
|
+
})();
|
|
3782
|
+
const invLt = resolveLinkInverseTrio(invSeed, input.contrastProfile);
|
|
3783
|
+
const neutralExtra = (mode) => ctaFamily(nScale, mode, CSS_FAMILY.neutral);
|
|
3784
|
+
const build = (mode) => {
|
|
3785
|
+
const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
|
|
3786
|
+
const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, neutralExtra(mode));
|
|
3787
|
+
const i02 = mode === "light" ? nScale.ink0 : nScale.ink0Dark;
|
|
3788
|
+
const spliceInk0 = (g3) => {
|
|
3789
|
+
if (!i02) return g3;
|
|
3790
|
+
const out = {};
|
|
3791
|
+
for (const [k, v2] of Object.entries(g3)) {
|
|
3792
|
+
out[k] = v2;
|
|
3793
|
+
if (k === stopTokenName(11)) out[INK_0] = colorFromStop(i02);
|
|
3794
|
+
}
|
|
3795
|
+
return out;
|
|
3796
|
+
};
|
|
3797
|
+
const neutralGroup = spliceInk0(p02 ? { [PAPER_100]: colorFromStop(p02), ...ramp } : ramp);
|
|
3798
|
+
const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, brandExtra(secondary, mode, CSS_FAMILY.brandSecondary));
|
|
3799
|
+
if (input.secondaryStyle === "outline" && input.secondary) {
|
|
3800
|
+
const s8 = secondary[mode].find((s) => s.stop === 8);
|
|
3801
|
+
const s9 = secondary[mode].find((s) => s.stop === 9);
|
|
3802
|
+
putLeaf(secondaryGroup, STAMP_FILL, TRANSPARENT_TOKEN);
|
|
3803
|
+
if (s8) {
|
|
3804
|
+
const e = srgbEmitChannels(s8);
|
|
3805
|
+
const alphaTint = (alpha) => ({
|
|
3806
|
+
$type: "color",
|
|
3807
|
+
$value: { colorSpace: "srgb", components: [clamp01(e.r), clamp01(e.g), clamp01(e.b)], alpha, hex: toHex2(e.r, e.g, e.b) }
|
|
3808
|
+
});
|
|
3809
|
+
putLeaf(secondaryGroup, STAMP_FILL_HOVER, alphaTint(OUTLINE_HOVER_ALPHA));
|
|
3810
|
+
putLeaf(secondaryGroup, STAMP_FILL_PRESSED, alphaTint(OUTLINE_PRESSED_ALPHA));
|
|
3811
|
+
}
|
|
3812
|
+
if (s8) putLeaf(secondaryGroup, STAMP_EDGE, colorFromStop(s8));
|
|
3813
|
+
if (s9) putLeaf(secondaryGroup, STAMP_ON, colorFromStop(s9));
|
|
3814
|
+
}
|
|
3815
|
+
const softOnCta = (g3, white) => {
|
|
3816
|
+
const p = white ? 1 : 0;
|
|
3817
|
+
putLeaf(g3, STAMP_ON, {
|
|
3818
|
+
$type: "color",
|
|
3819
|
+
$value: { colorSpace: "srgb", components: [p, p, p], alpha: SOFT_ON_CTA_ALPHA[mode], hex: white ? "#ffffff" : "#000000" }
|
|
3820
|
+
});
|
|
3821
|
+
};
|
|
3822
|
+
softOnCta(neutralGroup, mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark);
|
|
3823
|
+
if (input.secondary && input.secondaryStyle !== "outline" && (input.secondaryStyle === "default" || softOnCtaPasses(input.secondary, mode)))
|
|
3824
|
+
softOnCta(secondaryGroup, mode === "light" ? secondaryOnFillLight : secondaryOnFillDark);
|
|
3825
|
+
const brandGroup = rampGroup(scale[mode], mode === "light" ? scale.onFillTextIsWhite : scale.onFillTextIsWhiteDark, brandExtra(scale, mode, CSS_FAMILY.brandPrimary));
|
|
3826
|
+
const esc = input.ctaEscape ? escapeCtaFamily(nScale, mode, input.contrastProfile) : null;
|
|
3827
|
+
if (esc) {
|
|
3828
|
+
for (const g3 of input.secondary ? [brandGroup] : [brandGroup, secondaryGroup]) {
|
|
3829
|
+
putLeaf(g3, STAMP_FILL, colorFromStop(esc.cta));
|
|
3830
|
+
putLeaf(g3, STAMP_FILL_HOVER, colorFromStop(esc.ctaHover));
|
|
3831
|
+
putLeaf(g3, STAMP_FILL_PRESSED, colorFromStop(esc.ctaPressed));
|
|
3832
|
+
putLeaf(g3, STAMP_ON, colorFromHex(esc.onFillIsWhite));
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
const scaleInkAt = (n) => {
|
|
3836
|
+
const s = scale[mode].find((x2) => x2.stop === n);
|
|
3837
|
+
if (!s) throw new Error(`themeToFigma link: the brand scale has no ink stop ${n}`);
|
|
3838
|
+
return s;
|
|
3839
|
+
};
|
|
3840
|
+
const linkGroup = lt ? mode === "light" ? { "link": colorFromStop(lt.link), "link-hover": colorFromStop(lt.linkHover), "link-pressed": colorFromStop(lt.linkPressed) } : { "link": colorFromStop(lt.linkDark), "link-hover": colorFromStop(lt.linkHoverDark), "link-pressed": colorFromStop(lt.linkPressedDark) } : { "link": colorFromStop(scaleInkAt(9)), "link-hover": colorFromStop(scaleInkAt(10)), "link-pressed": colorFromStop(scaleInkAt(11)) };
|
|
3841
|
+
const linkInverseGroup = mode === "light" ? { "link": colorFromStop(invLt.link), "link-hover": colorFromStop(invLt.linkHover), "link-pressed": colorFromStop(invLt.linkPressed) } : { "link": colorFromStop(invLt.linkDark), "link-hover": colorFromStop(invLt.linkHoverDark), "link-pressed": colorFromStop(invLt.linkPressedDark) };
|
|
3842
|
+
const g2 = {
|
|
3843
|
+
brand: brandGroup,
|
|
3844
|
+
secondary: secondaryGroup,
|
|
3845
|
+
neutral: neutralGroup,
|
|
3846
|
+
link: linkGroup,
|
|
3847
|
+
"link-inverse": linkInverseGroup
|
|
3848
|
+
};
|
|
3849
|
+
for (const sig of input.signals) {
|
|
3850
|
+
g2[sig.name] = rampGroup(
|
|
3851
|
+
sig.scale[mode],
|
|
3852
|
+
mode === "light" ? sig.scale.onFillTextIsWhite : sig.scale.onFillTextIsWhiteDark,
|
|
3853
|
+
// signals rank with the primary (ctaBorderRung: anything not neutral/secondary takes 16).
|
|
3854
|
+
// Unreachable at the Lc 15 gate — no signal gets within 4 Lc of it — but defined, not accidental.
|
|
3855
|
+
ctaFamily(sig.scale, mode, sig.name)
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
return g2;
|
|
3859
|
+
};
|
|
3860
|
+
return { light: build("light"), dark: build("dark") };
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
// src/engine/requirements/dtcg.ts
|
|
3864
|
+
var EXT_KEY = "org.okchroma.requirement";
|
|
3865
|
+
var RESOLVER_ID = "okchroma-reqtoken@2";
|
|
3866
|
+
var hexToValue = (hex) => {
|
|
3867
|
+
const h = hex.replace("#", "");
|
|
3868
|
+
const c = (i) => parseInt(h.slice(i, i + 2), 16) / 255;
|
|
3869
|
+
return { colorSpace: "srgb", components: [c(0), c(2), c(4)], alpha: 1, hex: hex.toLowerCase() };
|
|
3870
|
+
};
|
|
3871
|
+
function emitDtcgRamp(hex, mode, groupName, spec) {
|
|
3872
|
+
spec ?? (spec = MODE_SPECS[mode]);
|
|
3873
|
+
const ramp = resolveRamp(hex, mode, spec);
|
|
3874
|
+
const seedRef = `{${groupName}.seed}`;
|
|
3875
|
+
const group = {
|
|
3876
|
+
seed: { $type: "color", $value: hexToValue(hex) },
|
|
3877
|
+
$extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, ons: spec.ons } }
|
|
3878
|
+
};
|
|
3879
|
+
for (const sp of spec.stops) {
|
|
3880
|
+
const st = ramp.stops.find((s) => s.stop === sp.stop);
|
|
3881
|
+
group[String(sp.stop)] = { $type: "color", $value: hexToValue(st.hex), $extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, seed: seedRef, mode, ...sp } } };
|
|
3882
|
+
}
|
|
3883
|
+
const roleVal = {
|
|
3884
|
+
"cta": ramp.roles.cta,
|
|
3885
|
+
"cta-hover": ramp.roles.ctaHover,
|
|
3886
|
+
"cta-pressed": ramp.roles.ctaPressed
|
|
3887
|
+
};
|
|
3888
|
+
for (const rr of spec.roles) {
|
|
3889
|
+
const rv = roleVal[rr.role];
|
|
3890
|
+
group[rr.role] = { $type: "color", $value: hexToValue(rv.hex), $extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, seed: seedRef, mode, ...rr } } };
|
|
3891
|
+
}
|
|
3892
|
+
return group;
|
|
3893
|
+
}
|
|
3894
|
+
function parseToken(token) {
|
|
3895
|
+
const ext = token.$extensions?.[EXT_KEY];
|
|
3896
|
+
if (!ext) throw new Error("missing $extensions." + EXT_KEY);
|
|
3897
|
+
if (ext.resolver !== RESOLVER_ID) throw new Error(`unknown resolver "${ext.resolver}" (this resolver is ${RESOLVER_ID})`);
|
|
3898
|
+
const { resolver, seed, mode, ...req } = ext;
|
|
3899
|
+
const isRole = "role" in req;
|
|
3900
|
+
if (!isRole) {
|
|
3901
|
+
for (const k of ["stop", "rootL", "group", "produce"]) if (req[k] === void 0) throw new Error(`stop requirement missing "${k}"`);
|
|
3902
|
+
}
|
|
3903
|
+
if (isRole) {
|
|
3904
|
+
for (const k of ["role", "produce", "floorL", "chromaMult"]) if (req[k] === void 0) throw new Error(`role requirement missing "${k}"`);
|
|
3905
|
+
}
|
|
3906
|
+
return { req, seedRef: seed, mode };
|
|
3907
|
+
}
|
|
3908
|
+
function resolveDtcgRamp(group) {
|
|
3909
|
+
const seedHex = group.seed.$value.hex;
|
|
3910
|
+
const ons = group.$extensions?.[EXT_KEY]?.ons;
|
|
3911
|
+
if (!ons) throw new Error("missing group-level ons declaration");
|
|
3912
|
+
const stops = [];
|
|
3913
|
+
const roles = [];
|
|
3914
|
+
let mode;
|
|
3915
|
+
for (const [k, t] of Object.entries(group)) {
|
|
3916
|
+
if (k === "seed" || k === "$extensions") continue;
|
|
3917
|
+
const { req, mode: m } = parseToken(t);
|
|
3918
|
+
mode ?? (mode = m);
|
|
3919
|
+
"role" in req ? roles.push(req) : stops.push(req);
|
|
3920
|
+
}
|
|
3921
|
+
if (!mode) throw new Error("empty ramp group");
|
|
3922
|
+
stops.sort((a, b) => a.stop - b.stop);
|
|
3923
|
+
return resolveRamp(seedHex, mode, { stops, roles, ons });
|
|
3924
|
+
}
|