@dooph-software/fluid-gradients 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +200 -0
- package/bin/init-skills.mjs +91 -0
- package/dist/chunk-CRRF2Q64.cjs +260 -0
- package/dist/chunk-CRRF2Q64.cjs.map +1 -0
- package/dist/chunk-KYCZHKFE.js +260 -0
- package/dist/chunk-KYCZHKFE.js.map +1 -0
- package/dist/cli.js +604 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +210 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +210 -0
- package/dist/index.js.map +1 -0
- package/dist/palettes.cjs +25 -0
- package/dist/palettes.cjs.map +1 -0
- package/dist/palettes.d.cts +261 -0
- package/dist/palettes.d.ts +261 -0
- package/dist/palettes.js +25 -0
- package/dist/palettes.js.map +1 -0
- package/package.json +70 -0
- package/skills/using-fluid-gradients/SKILL.md +50 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// src/color.ts
|
|
2
|
+
var ACHROMATIC_CHROMA = 0.01;
|
|
3
|
+
var HUE_TRUST_CHROMA = 0.03;
|
|
4
|
+
var VIVID_L_MIN = 50;
|
|
5
|
+
var VIVID_L_MAX = 80;
|
|
6
|
+
var VIVID_MAX_CHROMA = 0.2;
|
|
7
|
+
var CHROMA_SEARCH_MAX = 0.4;
|
|
8
|
+
var GAMUT_STEPS = 24;
|
|
9
|
+
var toLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
10
|
+
var toGamma = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
11
|
+
function hexToRgb(hex) {
|
|
12
|
+
return [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);
|
|
13
|
+
}
|
|
14
|
+
function rgbToHex(rgb) {
|
|
15
|
+
return "#" + rgb.map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255).toString(16).padStart(2, "0")).join("");
|
|
16
|
+
}
|
|
17
|
+
function hexToOklab(hex) {
|
|
18
|
+
const [r, g, b] = hexToRgb(hex).map(toLinear);
|
|
19
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
20
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
21
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
22
|
+
return [
|
|
23
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
24
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
25
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
function oklabToSrgb([L, a, b]) {
|
|
29
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
30
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
31
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
32
|
+
return [
|
|
33
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
34
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
35
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
36
|
+
].map(toGamma);
|
|
37
|
+
}
|
|
38
|
+
var inGamut = (rgb) => rgb.every((c) => c >= 0 && c <= 1);
|
|
39
|
+
var atLch = (L, C, hue) => oklabToSrgb([L, C * Math.cos(hue), C * Math.sin(hue)]);
|
|
40
|
+
function maxChroma(L, hue) {
|
|
41
|
+
let low = 0;
|
|
42
|
+
let high = CHROMA_SEARCH_MAX;
|
|
43
|
+
for (let step = 0; step < GAMUT_STEPS; step++) {
|
|
44
|
+
const mid = (low + high) / 2;
|
|
45
|
+
if (inGamut(atLch(L, mid, hue))) low = mid;
|
|
46
|
+
else high = mid;
|
|
47
|
+
}
|
|
48
|
+
return low;
|
|
49
|
+
}
|
|
50
|
+
function vividOpposite(a, b) {
|
|
51
|
+
const hue = Math.atan2(b, a) + Math.PI;
|
|
52
|
+
let bestL = VIVID_L_MIN / 100;
|
|
53
|
+
let bestC = -1;
|
|
54
|
+
for (let l = VIVID_L_MIN; l <= VIVID_L_MAX; l++) {
|
|
55
|
+
const chroma = maxChroma(l / 100, hue);
|
|
56
|
+
if (chroma > bestC) {
|
|
57
|
+
bestC = chroma;
|
|
58
|
+
bestL = l / 100;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return rgbToHex(atLch(bestL, Math.min(bestC, VIVID_MAX_CHROMA), hue));
|
|
62
|
+
}
|
|
63
|
+
function oppositeGray(L) {
|
|
64
|
+
return rgbToHex(atLch(L > 0.5 ? 0.25 : 0.85, 0, 0));
|
|
65
|
+
}
|
|
66
|
+
function complementColor(hex) {
|
|
67
|
+
const [L, a, b] = hexToOklab(hex);
|
|
68
|
+
return Math.hypot(a, b) < ACHROMATIC_CHROMA ? oppositeGray(L) : vividOpposite(a, b);
|
|
69
|
+
}
|
|
70
|
+
function paletteComplementColor(accent, ramp) {
|
|
71
|
+
const [L, a, b] = hexToOklab(accent);
|
|
72
|
+
if (Math.hypot(a, b) >= HUE_TRUST_CHROMA) return vividOpposite(a, b);
|
|
73
|
+
let sumA = 0;
|
|
74
|
+
let sumB = 0;
|
|
75
|
+
for (const color of ramp) {
|
|
76
|
+
const [, ca, cb] = hexToOklab(color);
|
|
77
|
+
sumA += ca;
|
|
78
|
+
sumB += cb;
|
|
79
|
+
}
|
|
80
|
+
const meanA = sumA / ramp.length;
|
|
81
|
+
const meanB = sumB / ramp.length;
|
|
82
|
+
return Math.hypot(meanA, meanB) < ACHROMATIC_CHROMA / 2 ? oppositeGray(L) : vividOpposite(meanA, meanB);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/random.ts
|
|
86
|
+
function hashStringToInt(seed) {
|
|
87
|
+
let hash = 2166136261 >>> 0;
|
|
88
|
+
for (let i = 0; i < seed.length; i++) {
|
|
89
|
+
hash ^= seed.charCodeAt(i);
|
|
90
|
+
hash = Math.imul(hash, 16777619);
|
|
91
|
+
}
|
|
92
|
+
return hash >>> 0;
|
|
93
|
+
}
|
|
94
|
+
function createSeededRandom(seedInt) {
|
|
95
|
+
let state = seedInt;
|
|
96
|
+
return function nextRandom() {
|
|
97
|
+
state |= 0;
|
|
98
|
+
state = state + 1831565813 | 0;
|
|
99
|
+
let mixed = Math.imul(state ^ state >>> 15, 1 | state);
|
|
100
|
+
mixed = mixed + Math.imul(mixed ^ mixed >>> 7, 61 | mixed) ^ mixed;
|
|
101
|
+
return ((mixed ^ mixed >>> 14) >>> 0) / 4294967296;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/palettes.ts
|
|
106
|
+
var MIN_PALETTE_COLORS = 3;
|
|
107
|
+
var MAX_PALETTE_COLORS = 5;
|
|
108
|
+
var colorPalettes = [
|
|
109
|
+
// ── Warm: amber / cream / clay / terracotta (analogous) ──
|
|
110
|
+
{ name: "amber-latte", colors: ["#faf4ea", "#f2d29b", "#e0a45c", "#b9722f"] },
|
|
111
|
+
{ name: "terracotta-sand", colors: ["#f6ede1", "#e8c9a0", "#cf8f63", "#9c4e33"] },
|
|
112
|
+
{ name: "peach-blush", colors: ["#fdf1ec", "#f8cbb6", "#ef9a8a", "#d76e73"] },
|
|
113
|
+
{ name: "coral-cream", colors: ["#fbe9df", "#f5b79a", "#e8735a", "#c33f36"] },
|
|
114
|
+
{ name: "honey-espresso", colors: ["#f7ecd9", "#e5b877", "#b9793f", "#5f3418"] },
|
|
115
|
+
{ name: "apricot-plum", colors: ["#fdf0e6", "#f6c79a", "#e58b6f", "#7d4a6b"] },
|
|
116
|
+
{ name: "golden-hour", colors: ["#fdf3e1", "#f7cf8a", "#e89a5a", "#9a4a3a"] },
|
|
117
|
+
{ name: "vermilion-blush", colors: ["#fbe8e2", "#f7c6a8", "#e4562f", "#a82a1c"] },
|
|
118
|
+
{ name: "ivory-gilt", colors: ["#fffdf7", "#f7ecd0", "#e0c47e", "#b08c3a"] },
|
|
119
|
+
{ name: "rose-gold", colors: ["#fceee8", "#f2cdbd", "#dba18c", "#b3705c"] },
|
|
120
|
+
{ name: "peony-glow", colors: ["#fdeef3", "#f9b3c8", "#f78a5c", "#f2a93f"] },
|
|
121
|
+
// ── Pink / violet / mauve ──
|
|
122
|
+
{ name: "lavender-rose", colors: ["#f4eefb", "#d9c2ef", "#c98fb8", "#8a5fd0"] },
|
|
123
|
+
{ name: "violet-mist", colors: ["#f1eefb", "#c9b8ef", "#8f7fe0", "#4a3d9c"] },
|
|
124
|
+
{ name: "mauve-plum", colors: ["#f6f0f2", "#d8b8c6", "#a5738c", "#5f3a55"] },
|
|
125
|
+
{ name: "periwinkle-blush", colors: ["#f0f0fb", "#c3c6f0", "#cf9fc6", "#6a5fd0"] },
|
|
126
|
+
{ name: "orchid-frost", colors: ["#f6eff6", "#e0c2e0", "#b98fc4", "#7a5aa6", "#3f2f5c"] },
|
|
127
|
+
{ name: "quartz-rose", colors: ["#faf1f1", "#ecc9cc", "#c99aa3", "#8a5f6a"] },
|
|
128
|
+
{ name: "wisteria-sky", colors: ["#f2f0fb", "#cfc6ef", "#9fb2e6", "#4f6aa6"] },
|
|
129
|
+
{ name: "zephyr-dawn", colors: ["#fdf3f5", "#f2d6e2", "#c6d6f2", "#7f9fd8"] },
|
|
130
|
+
{ name: "candy-sky", colors: ["#f6e8ff", "#f7a3e8", "#9aa8f5", "#3fa0f5"] },
|
|
131
|
+
// ── Cool: blue / slate / sky (analogous) ──
|
|
132
|
+
{ name: "sky-slate", colors: ["#eef4fb", "#bcd2ef", "#6f8fc4", "#2f3f66"] },
|
|
133
|
+
{ name: "teal-fog", colors: ["#eef7f6", "#b6dcd8", "#5fa39c", "#234a49"] },
|
|
134
|
+
{ name: "arctic-ink", colors: ["#f2f6f8", "#cddbe2", "#7f96a3", "#1c2630"] },
|
|
135
|
+
{ name: "cloud-navy", colors: ["#f0f3f7", "#c6d1de", "#7488a3", "#1f2c44"] },
|
|
136
|
+
{ name: "denim-clay", colors: ["#eef2f6", "#aebfd0", "#5f7794", "#b06a4e"] },
|
|
137
|
+
{ name: "cobalt-ice", colors: ["#e8eeff", "#b9cdfb", "#2f4fd8", "#1836b8"] },
|
|
138
|
+
// ── Green: sage / moss / mint / olive (analogous) ──
|
|
139
|
+
{ name: "sage-mist", colors: ["#f2f5ee", "#cdd8bd", "#8fa579", "#4a5c3a"] },
|
|
140
|
+
{ name: "mint-sea", colors: ["#eef8f4", "#b6e2d2", "#5fb39c", "#1f5c4d"] },
|
|
141
|
+
{ name: "olive-cream", colors: ["#f6f3e6", "#dbd09b", "#a39a5c", "#5f5a2f"] },
|
|
142
|
+
{ name: "eucalyptus-fog", colors: ["#eff4f1", "#cbdcd2", "#94b3a6", "#4f6b60"] },
|
|
143
|
+
{ name: "kelp-forest", colors: ["#eef3ea", "#a9c49a", "#4f7a5a", "#1f3a2f"] },
|
|
144
|
+
{ name: "yuzu-mint", colors: ["#fbfbe6", "#eef29a", "#b3d98a", "#3f8f7a"] },
|
|
145
|
+
{ name: "lime-lagoon", colors: ["#f8ffe8", "#c8f56a", "#6fe3d8", "#2fc6d8"] },
|
|
146
|
+
{ name: "bliss-meadow", colors: ["#f4fbff", "#8ed0f2", "#6cb03a", "#2d6b22"] },
|
|
147
|
+
// ── Analogous + a single contrasting splash ──
|
|
148
|
+
{ name: "periwinkle-gold", colors: ["#dfe6ff", "#a9b6f0", "#5b6bd6", "#e6c25a"] },
|
|
149
|
+
{ name: "lilac-lemon", colors: ["#f3eefb", "#d3c2ef", "#9f8fd0", "#e6d25a"] },
|
|
150
|
+
{ name: "blush-teal", colors: ["#fbeee9", "#f2c2b0", "#d78a8f", "#3f9c94"] },
|
|
151
|
+
{ name: "sage-coral", colors: ["#f2f5ee", "#cdd8bd", "#8fa579", "#e0755a"] },
|
|
152
|
+
{ name: "slate-amber", colors: ["#eef2f6", "#bcc9d8", "#5f7794", "#e0a24e"] },
|
|
153
|
+
{ name: "ultramarine-sand", colors: ["#dfe8ff", "#e6c27a", "#4757dd", "#232a9c"] },
|
|
154
|
+
{ name: "amber-tide", colors: ["#eef4f8", "#a9c8ef", "#f2b96b", "#df8a33"] },
|
|
155
|
+
// ── Colorful / multi-hue (rich & saturated, jewel tones) ──
|
|
156
|
+
{ name: "sunset-rose", colors: ["#fce9d6", "#f6a95c", "#e8617a", "#8a3d7a"] },
|
|
157
|
+
{ name: "berry-bloom", colors: ["#fbe9f2", "#e88fb8", "#c94f8f", "#7a2f6a"] },
|
|
158
|
+
{ name: "jewel-peacock", colors: ["#e6f5f0", "#4fb3a6", "#1f7a99", "#243f8a"] },
|
|
159
|
+
{ name: "iris-meadow", colors: ["#eaf5ec", "#7cc48f", "#3f8fb3", "#5f4fb0"] },
|
|
160
|
+
{ name: "autumn-spice", colors: ["#f7ecd6", "#dba24f", "#b5522f", "#6e2f4a"] },
|
|
161
|
+
{ name: "ultraviolet-sea", colors: ["#e9f0fb", "#6f9fe0", "#4a5fc4", "#6a3da6"] },
|
|
162
|
+
{ name: "purple-rain", colors: ["#f1ebf7", "#b69ad6", "#6f3fa8", "#2a1740"] },
|
|
163
|
+
{ name: "nectarine-fizz", colors: ["#fff1e6", "#ffc59a", "#ff8a5c", "#d9485f"] },
|
|
164
|
+
{ name: "raspberry-cream", colors: ["#fdeef1", "#f4a9bd", "#d9537a", "#8a1f45"] },
|
|
165
|
+
{ name: "pastel-confetti", colors: ["#ffffff", "#f9f3cf", "#cbbcf2", "#e8798f"] },
|
|
166
|
+
{ name: "burgundy-velvet", colors: ["#f2dfe0", "#c98a91", "#8a2740", "#4a0f22"] },
|
|
167
|
+
{ name: "indigo-beam", colors: ["#ffffff", "#a9b6f7", "#4a3fe0", "#2a1fb0"] },
|
|
168
|
+
{ name: "crimson-ice", colors: ["#f2f7fc", "#f8d7e0", "#e0517f", "#a81050"] },
|
|
169
|
+
// ── Muted / dusty / earthy ──
|
|
170
|
+
{ name: "fog-plum", colors: ["#f2f0f3", "#cfc6d2", "#9a8a9c", "#5f4a5c"] },
|
|
171
|
+
{ name: "sand-sky", colors: ["#f4efe4", "#e0d3b8", "#a9bcc4", "#5f7f8c"] },
|
|
172
|
+
{ name: "xanadu-sage", colors: ["#f1f4f1", "#c8d3c8", "#738678", "#36443a"] },
|
|
173
|
+
{ name: "walnut-grain", colors: ["#e8d5b7", "#c49a6c", "#8a5a34", "#4a2c17"] },
|
|
174
|
+
// ── Dark-focused: ramps that start mid-tone instead of near-white, so the
|
|
175
|
+
// image reads dark overall with the lighter stop as a glow ──
|
|
176
|
+
{ name: "onyx-gold", colors: ["#e8c877", "#b8862f", "#3a2f14", "#0b0b0d"] },
|
|
177
|
+
{ name: "onyx-silver", colors: ["#d7dade", "#8b9199", "#3a3f45", "#0c0d0f"] },
|
|
178
|
+
{ name: "graphite-royal", colors: ["#9fb0e8", "#3f56c4", "#2a2e38", "#101218"] },
|
|
179
|
+
{ name: "onyx-ultraviolet", colors: ["#c9a6ff", "#7b3ff2", "#2a1450", "#08070d"] },
|
|
180
|
+
{ name: "onyx-snow", colors: ["#ffffff", "#b0b0b0", "#4a4a4a", "#000000"] },
|
|
181
|
+
{ name: "midnight-ember", colors: ["#f0a468", "#c23f1f", "#2a1410", "#0a0a0c"] },
|
|
182
|
+
{ name: "slate-harbor", colors: ["#aeb7bd", "#6f7b84", "#3c454c", "#1a1f24"] },
|
|
183
|
+
// ── Neutral / mono ──
|
|
184
|
+
{ name: "ivory-ink", colors: ["#f7f4ef", "#d8d2c8", "#8a8378", "#20201d"] },
|
|
185
|
+
{ name: "paper-graphite", colors: ["#f4f4f5", "#cfcfd4", "#83838f", "#1c1c22"] },
|
|
186
|
+
{ name: "bone-coffee", colors: ["#f5efe6", "#d9c9b3", "#9c8468", "#3a2c1e"] },
|
|
187
|
+
{ name: "porcelain-greige", colors: ["#f6f3ef", "#ddd4c9", "#b0a596", "#6a5f52"] },
|
|
188
|
+
{ name: "battleship-steel", colors: ["#e6e9e8", "#b9c0be", "#7d8683", "#3f4745"] },
|
|
189
|
+
{ name: "gunmetal-fog", colors: ["#eceef1", "#c2c7cd", "#7b838d", "#343a42"] }
|
|
190
|
+
];
|
|
191
|
+
function paletteAccent(palette) {
|
|
192
|
+
const colors = palette.colors;
|
|
193
|
+
return colors[Math.floor(colors.length / 2)];
|
|
194
|
+
}
|
|
195
|
+
function paletteComplement(palette) {
|
|
196
|
+
return paletteComplementColor(paletteAccent(palette), palette.colors);
|
|
197
|
+
}
|
|
198
|
+
var HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
|
|
199
|
+
function validatePalette(palette) {
|
|
200
|
+
const label = `Palette "${palette?.name}"`;
|
|
201
|
+
if (typeof palette?.name !== "string" || palette.name.length === 0) {
|
|
202
|
+
throw new Error("Palette needs a non-empty name.");
|
|
203
|
+
}
|
|
204
|
+
const { colors } = palette;
|
|
205
|
+
if (!Array.isArray(colors) || colors.length < MIN_PALETTE_COLORS || colors.length > MAX_PALETTE_COLORS) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`${label} needs ${MIN_PALETTE_COLORS}\u2013${MAX_PALETTE_COLORS} colors. Got ${Array.isArray(colors) ? colors.length : typeof colors}.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
for (const color of colors) {
|
|
211
|
+
if (typeof color !== "string" || !HEX_COLOR.test(color)) {
|
|
212
|
+
throw new Error(`${label} has an invalid color "${color}". Use #rrggbb.`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function validatePalettes(palettes) {
|
|
217
|
+
if (palettes.length === 0) throw new Error("At least one palette is required.");
|
|
218
|
+
const seen = /* @__PURE__ */ new Set();
|
|
219
|
+
for (const palette of palettes) {
|
|
220
|
+
validatePalette(palette);
|
|
221
|
+
if (seen.has(palette.name)) throw new Error(`Duplicate palette name "${palette.name}".`);
|
|
222
|
+
seen.add(palette.name);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function findPalette(name, palettes = colorPalettes) {
|
|
226
|
+
const palette = palettes.find((p) => p.name === name);
|
|
227
|
+
if (!palette) {
|
|
228
|
+
throw new Error(`Unknown palette "${name}". Valid: ${palettes.map((p) => p.name).join(", ")}`);
|
|
229
|
+
}
|
|
230
|
+
return palette;
|
|
231
|
+
}
|
|
232
|
+
function pickPalette(seed, palettes = colorPalettes) {
|
|
233
|
+
let best;
|
|
234
|
+
let bestScore = -1;
|
|
235
|
+
for (const palette of palettes) {
|
|
236
|
+
const score = createSeededRandom(hashStringToInt(`${seed}::palette::${palette.name}`))();
|
|
237
|
+
if (score > bestScore || score === bestScore && palette.name < best.name) {
|
|
238
|
+
best = palette;
|
|
239
|
+
bestScore = score;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (!best) throw new Error("At least one palette is required.");
|
|
243
|
+
return best;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export {
|
|
247
|
+
complementColor,
|
|
248
|
+
hashStringToInt,
|
|
249
|
+
createSeededRandom,
|
|
250
|
+
MIN_PALETTE_COLORS,
|
|
251
|
+
MAX_PALETTE_COLORS,
|
|
252
|
+
colorPalettes,
|
|
253
|
+
paletteAccent,
|
|
254
|
+
paletteComplement,
|
|
255
|
+
validatePalette,
|
|
256
|
+
validatePalettes,
|
|
257
|
+
findPalette,
|
|
258
|
+
pickPalette
|
|
259
|
+
};
|
|
260
|
+
//# sourceMappingURL=chunk-KYCZHKFE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/color.ts","../src/random.ts","../src/palettes.ts"],"sourcesContent":["/*\n * color — perceptual color math for colors derived from a palette.\n *\n * ## behavior\n * - The complement is the design-sense complement: the hue opposite the source\n * (OKLCH hue + 180°), at the most saturated color sRGB can show for that hue\n * within a readable lightness band (VIVID_L_MIN–VIVID_L_MAX), chroma capped at\n * VIVID_MAX_CHROMA. It is built to stand out on the gradient as a chip or\n * shape, so it deliberately does NOT keep the source's lightness or chroma —\n * keeping them made muted palettes produce muted, invisible complements.\n * - `paletteComplementColor` takes the hue from the accent, unless the accent is\n * too gray to have a reliable hue (chroma < HUE_TRUST_CHROMA); then it uses the\n * chroma-weighted hue of the whole ramp, whose faint tint is still real.\n * - A source with no usable hue at all (pure grays) has no opposite hue; it gets\n * a gray at the opposite lightness instead.\n *\n * ## constraints\n * - Output is part of what consumers store. Complements are rendered as UI\n * chips and geometry next to images generated earlier; changing the color\n * space, the lightness band, the chroma cap, the search steps or the rounding\n * shifts every complement. Treat it like the render pipeline: a deliberate,\n * announced change, not a refactor.\n * - Zero dependencies. This module is reachable from the `/palettes` entry,\n * which promises to be safe for browser and edge bundles.\n */\n\ntype Rgb = [number, number, number];\n\n/** Below this OKLCH chroma a color has no meaningful hue. */\nconst ACHROMATIC_CHROMA = 0.01;\n/** Below this, an accent's hue is too faint to trust; use the whole ramp's instead. */\nconst HUE_TRUST_CHROMA = 0.03;\n/** Lightness band searched for the most saturated opposite color (OKLab L, in hundredths). */\nconst VIVID_L_MIN = 50;\nconst VIVID_L_MAX = 80;\n/** Cap so the most saturated hues (e.g. blue, magenta) stay vivid rather than neon. */\nconst VIVID_MAX_CHROMA = 0.2;\n/** Upper bound of the chroma search; no sRGB color exceeds it. */\nconst CHROMA_SEARCH_MAX = 0.4;\n/** Bisection steps; 2^-24 of the search range is far below 1/255 per channel. */\nconst GAMUT_STEPS = 24;\n\nconst toLinear = (c: number) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);\nconst toGamma = (c: number) => (c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055);\n\nfunction hexToRgb(hex: string): Rgb {\n return [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255) as Rgb;\n}\n\nfunction rgbToHex(rgb: Rgb): string {\n return (\n '#' +\n rgb\n .map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255).toString(16).padStart(2, '0'))\n .join('')\n );\n}\n\nfunction hexToOklab(hex: string): Rgb {\n const [r, g, b] = hexToRgb(hex).map(toLinear);\n const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n return [\n 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,\n 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,\n 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,\n ];\n}\n\n/** Returns sRGB in gamma space, possibly outside [0, 1] when out of gamut. */\nfunction oklabToSrgb([L, a, b]: Rgb): Rgb {\n const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;\n const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;\n const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;\n return [\n 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,\n -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,\n -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,\n ].map(toGamma) as Rgb;\n}\n\nconst inGamut = (rgb: Rgb) => rgb.every((c) => c >= 0 && c <= 1);\nconst atLch = (L: number, C: number, hue: number) =>\n oklabToSrgb([L, C * Math.cos(hue), C * Math.sin(hue)]);\n\n/** Largest chroma sRGB can show at this lightness and hue. */\nfunction maxChroma(L: number, hue: number): number {\n let low = 0;\n let high = CHROMA_SEARCH_MAX;\n for (let step = 0; step < GAMUT_STEPS; step++) {\n const mid = (low + high) / 2;\n if (inGamut(atLch(L, mid, hue))) low = mid;\n else high = mid;\n }\n return low;\n}\n\n/** The most saturated in-band color at the hue opposite (a, b). */\nfunction vividOpposite(a: number, b: number): string {\n const hue = Math.atan2(b, a) + Math.PI;\n let bestL = VIVID_L_MIN / 100;\n let bestC = -1;\n for (let l = VIVID_L_MIN; l <= VIVID_L_MAX; l++) {\n const chroma = maxChroma(l / 100, hue);\n if (chroma > bestC) {\n bestC = chroma;\n bestL = l / 100;\n }\n }\n return rgbToHex(atLch(bestL, Math.min(bestC, VIVID_MAX_CHROMA), hue));\n}\n\n/** A hueless color's only opposite: the same gray on the other side of mid lightness. */\nfunction oppositeGray(L: number): string {\n return rgbToHex(atLch(L > 0.5 ? 0.25 : 0.85, 0, 0));\n}\n\n/**\n * The complement of a `#rrggbb` color, built to stand out: the opposite OKLCH\n * hue at its most saturated readable lightness. Grays get the opposite-lightness gray.\n */\nexport function complementColor(hex: string): string {\n const [L, a, b] = hexToOklab(hex);\n return Math.hypot(a, b) < ACHROMATIC_CHROMA ? oppositeGray(L) : vividOpposite(a, b);\n}\n\n/**\n * Complement for a palette: opposite the accent's hue, or — when the accent is\n * near-gray — opposite the chroma-weighted hue of the whole ramp.\n */\nexport function paletteComplementColor(accent: string, ramp: readonly string[]): string {\n const [L, a, b] = hexToOklab(accent);\n if (Math.hypot(a, b) >= HUE_TRUST_CHROMA) return vividOpposite(a, b);\n\n // Summing OKLab a/b weights each stop's hue by its chroma.\n let sumA = 0;\n let sumB = 0;\n for (const color of ramp) {\n const [, ca, cb] = hexToOklab(color);\n sumA += ca;\n sumB += cb;\n }\n const meanA = sumA / ramp.length;\n const meanB = sumB / ramp.length;\n return Math.hypot(meanA, meanB) < ACHROMATIC_CHROMA / 2 ? oppositeGray(L) : vividOpposite(meanA, meanB);\n}\n","/*\n * random — the deterministic RNG behind every seeded image and every palette pick.\n *\n * ## behavior\n * - `hashStringToInt` is FNV-1a/32; `createSeededRandom` is Mulberry32 returning\n * floats in [0, 1). Both are re-exported from the package root, so their\n * signatures are public API.\n * - Two consumers: `render.ts` seeds one stream per image and draws from it for\n * the whole pipeline, and `pickPalette` in `palettes.ts` scores each palette\n * with a fresh one-shot stream.\n *\n * ## constraints\n * - Both algorithms are frozen. These are the exact functions Aspect's released\n * 0.3.0 artwork was rendered with, and `test/fluid-gradients.test.mjs` compares\n * a render byte-for-byte against that shipped image. Swapping in a \"better\"\n * hash or a stronger PRNG — or reordering the mixing steps — changes every\n * image every seed has ever produced and reshuffles which palette each seed\n * picks. There is no migration for artwork already published.\n * - `hashStringToInt` hashes the raw string, code unit by code unit. Do not\n * normalize the input here (trim, lowercase, NFC): seeds are arbitrary user\n * strings, and folding two of them together remaps the images they render.\n * A caller that wants normalized seeds normalizes before calling.\n */\n\n/** FNV-1a hash of a string to an unsigned 32-bit integer. */\nexport function hashStringToInt(seed: string): number {\n let hash = 2166136261 >>> 0;\n for (let i = 0; i < seed.length; i++) {\n hash ^= seed.charCodeAt(i);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n}\n\n/** Mulberry32 PRNG. Returns floats in [0, 1). */\nexport function createSeededRandom(seedInt: number): () => number {\n let state = seedInt;\n return function nextRandom() {\n state |= 0;\n state = (state + 0x6d2b79f5) | 0;\n let mixed = Math.imul(state ^ (state >>> 15), 1 | state);\n mixed = (mixed + Math.imul(mixed ^ (mixed >>> 7), 61 | mixed)) ^ mixed;\n return ((mixed ^ (mixed >>> 14)) >>> 0) / 4294967296;\n };\n}\n","/*\n * palettes — the curated palette set, its runtime validation, and seed → palette\n * selection.\n *\n * Design intent for new entries: analogous families anchored by neutrals, muted\n * and earthy options included. Ramps run light → dark, the order the renderer\n * maps noise values along; the \"dark-focused\" group starts mid-tone on purpose\n * so those images read dark with the light stop as a glow.\n *\n * ## behavior\n * - `colorPalettes` is the built-in set. `PaletteName` is derived from it, so\n * the literal names are part of the package's public types.\n * - `pickPalette` uses rendezvous (highest-random-weight) hashing: each palette\n * scores `seed::palette::<name>` through the RNG and the highest wins, with\n * the name as the tiebreak.\n * - `validatePalette` / `validatePalettes` are the runtime gate for palettes\n * that bypassed the type system (JSON, CLI input, casts).\n * - `paletteAccent` is the ramp's mid stop by position; `paletteComplement` is\n * a vivid opposite-hue color for chips, computed by `./color`.\n *\n * ## constraints\n * - A palette's name is its identity, not a label. It is the rendezvous hash\n * key, so renaming one re-picks every seed that had landed on it; it is also\n * the filename in `examples/` and the key in `examples/featured.json`.\n * Renaming a palette is a breaking change to callers' existing artwork, not a\n * copy edit.\n * - Selection stays rendezvous-hashed. An index- or modulo-based pick over the\n * array is smaller code and re-picks nearly every seed the moment a palette is\n * added or removed; `test/fluid-gradients.test.mjs` asserts that adding one\n * moves only the seeds that land on the new palette. The `::palette::`\n * separator and the run through `createSeededRandom` are part of the key —\n * hashing `seed + name` directly mixes shared prefixes poorly and is a\n * different assignment for every seed.\n * - `colorPalettes` keeps `as const satisfies readonly Palette[]`. Dropping the\n * `as const` widens `PaletteName` to `string`, which silently removes name\n * checking for every consumer; the `@ts-expect-error` lines in\n * `test/types.check.ts` then stop erroring and `npm run lint` fails.\n * - Every ramp is 3–5 stops and the runtime check enforces it. The width of\n * `PaletteColors` and `MIN_PALETTE_COLORS`/`MAX_PALETTE_COLORS` must move\n * together — a 6-stop tuple added to the type alone would pass typecheck and\n * then throw at render time for the caller.\n */\nimport { complementColor, paletteComplementColor } from './color';\nimport { createSeededRandom, hashStringToInt } from './random';\n\nexport { complementColor };\n\nexport const MIN_PALETTE_COLORS = 3;\nexport const MAX_PALETTE_COLORS = 5;\n\n/** `#rrggbb`. The type only checks the `#`; the 6 hex digits are checked at runtime. */\nexport type HexColor = `#${string}`;\n\n/** A color ramp of 3–5 stops, light → dark. */\nexport type PaletteColors =\n | readonly [HexColor, HexColor, HexColor]\n | readonly [HexColor, HexColor, HexColor, HexColor]\n | readonly [HexColor, HexColor, HexColor, HexColor, HexColor];\n\nexport type Palette = {\n /** Unique within a palette set. Seed-based picks are keyed on it, so renaming reshuffles. */\n name: string;\n colors: PaletteColors;\n};\n\nexport const colorPalettes = [\n // ── Warm: amber / cream / clay / terracotta (analogous) ──\n { name: 'amber-latte', colors: ['#faf4ea', '#f2d29b', '#e0a45c', '#b9722f'] },\n { name: 'terracotta-sand', colors: ['#f6ede1', '#e8c9a0', '#cf8f63', '#9c4e33'] },\n { name: 'peach-blush', colors: ['#fdf1ec', '#f8cbb6', '#ef9a8a', '#d76e73'] },\n { name: 'coral-cream', colors: ['#fbe9df', '#f5b79a', '#e8735a', '#c33f36'] },\n { name: 'honey-espresso', colors: ['#f7ecd9', '#e5b877', '#b9793f', '#5f3418'] },\n { name: 'apricot-plum', colors: ['#fdf0e6', '#f6c79a', '#e58b6f', '#7d4a6b'] },\n { name: 'golden-hour', colors: ['#fdf3e1', '#f7cf8a', '#e89a5a', '#9a4a3a'] },\n { name: 'vermilion-blush', colors: ['#fbe8e2', '#f7c6a8', '#e4562f', '#a82a1c'] },\n { name: 'ivory-gilt', colors: ['#fffdf7', '#f7ecd0', '#e0c47e', '#b08c3a'] },\n { name: 'rose-gold', colors: ['#fceee8', '#f2cdbd', '#dba18c', '#b3705c'] },\n { name: 'peony-glow', colors: ['#fdeef3', '#f9b3c8', '#f78a5c', '#f2a93f'] },\n\n // ── Pink / violet / mauve ──\n { name: 'lavender-rose', colors: ['#f4eefb', '#d9c2ef', '#c98fb8', '#8a5fd0'] },\n { name: 'violet-mist', colors: ['#f1eefb', '#c9b8ef', '#8f7fe0', '#4a3d9c'] },\n { name: 'mauve-plum', colors: ['#f6f0f2', '#d8b8c6', '#a5738c', '#5f3a55'] },\n { name: 'periwinkle-blush', colors: ['#f0f0fb', '#c3c6f0', '#cf9fc6', '#6a5fd0'] },\n { name: 'orchid-frost', colors: ['#f6eff6', '#e0c2e0', '#b98fc4', '#7a5aa6', '#3f2f5c'] },\n { name: 'quartz-rose', colors: ['#faf1f1', '#ecc9cc', '#c99aa3', '#8a5f6a'] },\n { name: 'wisteria-sky', colors: ['#f2f0fb', '#cfc6ef', '#9fb2e6', '#4f6aa6'] },\n { name: 'zephyr-dawn', colors: ['#fdf3f5', '#f2d6e2', '#c6d6f2', '#7f9fd8'] },\n { name: 'candy-sky', colors: ['#f6e8ff', '#f7a3e8', '#9aa8f5', '#3fa0f5'] },\n\n // ── Cool: blue / slate / sky (analogous) ──\n { name: 'sky-slate', colors: ['#eef4fb', '#bcd2ef', '#6f8fc4', '#2f3f66'] },\n { name: 'teal-fog', colors: ['#eef7f6', '#b6dcd8', '#5fa39c', '#234a49'] },\n { name: 'arctic-ink', colors: ['#f2f6f8', '#cddbe2', '#7f96a3', '#1c2630'] },\n { name: 'cloud-navy', colors: ['#f0f3f7', '#c6d1de', '#7488a3', '#1f2c44'] },\n { name: 'denim-clay', colors: ['#eef2f6', '#aebfd0', '#5f7794', '#b06a4e'] },\n { name: 'cobalt-ice', colors: ['#e8eeff', '#b9cdfb', '#2f4fd8', '#1836b8'] },\n\n // ── Green: sage / moss / mint / olive (analogous) ──\n { name: 'sage-mist', colors: ['#f2f5ee', '#cdd8bd', '#8fa579', '#4a5c3a'] },\n { name: 'mint-sea', colors: ['#eef8f4', '#b6e2d2', '#5fb39c', '#1f5c4d'] },\n { name: 'olive-cream', colors: ['#f6f3e6', '#dbd09b', '#a39a5c', '#5f5a2f'] },\n { name: 'eucalyptus-fog', colors: ['#eff4f1', '#cbdcd2', '#94b3a6', '#4f6b60'] },\n { name: 'kelp-forest', colors: ['#eef3ea', '#a9c49a', '#4f7a5a', '#1f3a2f'] },\n { name: 'yuzu-mint', colors: ['#fbfbe6', '#eef29a', '#b3d98a', '#3f8f7a'] },\n { name: 'lime-lagoon', colors: ['#f8ffe8', '#c8f56a', '#6fe3d8', '#2fc6d8'] },\n { name: 'bliss-meadow', colors: ['#f4fbff', '#8ed0f2', '#6cb03a', '#2d6b22'] },\n\n // ── Analogous + a single contrasting splash ──\n { name: 'periwinkle-gold', colors: ['#dfe6ff', '#a9b6f0', '#5b6bd6', '#e6c25a'] },\n { name: 'lilac-lemon', colors: ['#f3eefb', '#d3c2ef', '#9f8fd0', '#e6d25a'] },\n { name: 'blush-teal', colors: ['#fbeee9', '#f2c2b0', '#d78a8f', '#3f9c94'] },\n { name: 'sage-coral', colors: ['#f2f5ee', '#cdd8bd', '#8fa579', '#e0755a'] },\n { name: 'slate-amber', colors: ['#eef2f6', '#bcc9d8', '#5f7794', '#e0a24e'] },\n { name: 'ultramarine-sand', colors: ['#dfe8ff', '#e6c27a', '#4757dd', '#232a9c'] },\n { name: 'amber-tide', colors: ['#eef4f8', '#a9c8ef', '#f2b96b', '#df8a33'] },\n\n // ── Colorful / multi-hue (rich & saturated, jewel tones) ──\n { name: 'sunset-rose', colors: ['#fce9d6', '#f6a95c', '#e8617a', '#8a3d7a'] },\n { name: 'berry-bloom', colors: ['#fbe9f2', '#e88fb8', '#c94f8f', '#7a2f6a'] },\n { name: 'jewel-peacock', colors: ['#e6f5f0', '#4fb3a6', '#1f7a99', '#243f8a'] },\n { name: 'iris-meadow', colors: ['#eaf5ec', '#7cc48f', '#3f8fb3', '#5f4fb0'] },\n { name: 'autumn-spice', colors: ['#f7ecd6', '#dba24f', '#b5522f', '#6e2f4a'] },\n { name: 'ultraviolet-sea', colors: ['#e9f0fb', '#6f9fe0', '#4a5fc4', '#6a3da6'] },\n { name: 'purple-rain', colors: ['#f1ebf7', '#b69ad6', '#6f3fa8', '#2a1740'] },\n { name: 'nectarine-fizz', colors: ['#fff1e6', '#ffc59a', '#ff8a5c', '#d9485f'] },\n { name: 'raspberry-cream', colors: ['#fdeef1', '#f4a9bd', '#d9537a', '#8a1f45'] },\n { name: 'pastel-confetti', colors: ['#ffffff', '#f9f3cf', '#cbbcf2', '#e8798f'] },\n { name: 'burgundy-velvet', colors: ['#f2dfe0', '#c98a91', '#8a2740', '#4a0f22'] },\n { name: 'indigo-beam', colors: ['#ffffff', '#a9b6f7', '#4a3fe0', '#2a1fb0'] },\n { name: 'crimson-ice', colors: ['#f2f7fc', '#f8d7e0', '#e0517f', '#a81050'] },\n\n // ── Muted / dusty / earthy ──\n { name: 'fog-plum', colors: ['#f2f0f3', '#cfc6d2', '#9a8a9c', '#5f4a5c'] },\n { name: 'sand-sky', colors: ['#f4efe4', '#e0d3b8', '#a9bcc4', '#5f7f8c'] },\n { name: 'xanadu-sage', colors: ['#f1f4f1', '#c8d3c8', '#738678', '#36443a'] },\n { name: 'walnut-grain', colors: ['#e8d5b7', '#c49a6c', '#8a5a34', '#4a2c17'] },\n\n // ── Dark-focused: ramps that start mid-tone instead of near-white, so the\n // image reads dark overall with the lighter stop as a glow ──\n { name: 'onyx-gold', colors: ['#e8c877', '#b8862f', '#3a2f14', '#0b0b0d'] },\n { name: 'onyx-silver', colors: ['#d7dade', '#8b9199', '#3a3f45', '#0c0d0f'] },\n { name: 'graphite-royal', colors: ['#9fb0e8', '#3f56c4', '#2a2e38', '#101218'] },\n { name: 'onyx-ultraviolet', colors: ['#c9a6ff', '#7b3ff2', '#2a1450', '#08070d'] },\n { name: 'onyx-snow', colors: ['#ffffff', '#b0b0b0', '#4a4a4a', '#000000'] },\n { name: 'midnight-ember', colors: ['#f0a468', '#c23f1f', '#2a1410', '#0a0a0c'] },\n { name: 'slate-harbor', colors: ['#aeb7bd', '#6f7b84', '#3c454c', '#1a1f24'] },\n\n // ── Neutral / mono ──\n { name: 'ivory-ink', colors: ['#f7f4ef', '#d8d2c8', '#8a8378', '#20201d'] },\n { name: 'paper-graphite', colors: ['#f4f4f5', '#cfcfd4', '#83838f', '#1c1c22'] },\n { name: 'bone-coffee', colors: ['#f5efe6', '#d9c9b3', '#9c8468', '#3a2c1e'] },\n { name: 'porcelain-greige', colors: ['#f6f3ef', '#ddd4c9', '#b0a596', '#6a5f52'] },\n { name: 'battleship-steel', colors: ['#e6e9e8', '#b9c0be', '#7d8683', '#3f4745'] },\n { name: 'gunmetal-fog', colors: ['#eceef1', '#c2c7cd', '#7b838d', '#343a42'] },\n] as const satisfies readonly Palette[];\n\n/** Union of every built-in palette name, e.g. `\"mint-sea\"`. */\nexport type PaletteName = (typeof colorPalettes)[number]['name'];\n\n/** Primary accent for UI chips: the palette ramp's mid-tone. */\nexport function paletteAccent(palette: Palette): string {\n const colors = palette.colors;\n return colors[Math.floor(colors.length / 2)];\n}\n\n/**\n * A color built to stand out on the palette's images: the hue opposite the\n * accent (or the whole ramp, when the accent is near-gray) at its most saturated\n * readable lightness. For chips, geometry and highlights.\n */\nexport function paletteComplement(palette: Palette): string {\n return paletteComplementColor(paletteAccent(palette), palette.colors);\n}\n\nconst HEX_COLOR = /^#[0-9a-fA-F]{6}$/;\n\n/**\n * Runtime check for palettes that didn't come through the type system\n * (JSON, CLI input, `as` casts). Throws with the offending palette's name.\n */\nexport function validatePalette(palette: Palette): void {\n const label = `Palette \"${palette?.name}\"`;\n if (typeof palette?.name !== 'string' || palette.name.length === 0) {\n throw new Error('Palette needs a non-empty name.');\n }\n const { colors } = palette;\n if (\n !Array.isArray(colors) ||\n colors.length < MIN_PALETTE_COLORS ||\n colors.length > MAX_PALETTE_COLORS\n ) {\n throw new Error(\n `${label} needs ${MIN_PALETTE_COLORS}–${MAX_PALETTE_COLORS} colors. Got ${Array.isArray(colors) ? colors.length : typeof colors}.`,\n );\n }\n for (const color of colors) {\n if (typeof color !== 'string' || !HEX_COLOR.test(color)) {\n throw new Error(`${label} has an invalid color \"${color}\". Use #rrggbb.`);\n }\n }\n}\n\n/** Validates every palette and that names are unique. */\nexport function validatePalettes(palettes: readonly Palette[]): void {\n if (palettes.length === 0) throw new Error('At least one palette is required.');\n const seen = new Set<string>();\n for (const palette of palettes) {\n validatePalette(palette);\n if (seen.has(palette.name)) throw new Error(`Duplicate palette name \"${palette.name}\".`);\n seen.add(palette.name);\n }\n}\n\n/** Look up a palette by name. Throws if not found. */\nexport function findPalette(\n name: PaletteName | (string & {}),\n palettes: readonly Palette[] = colorPalettes,\n): Palette {\n const palette = palettes.find((p) => p.name === name);\n if (!palette) {\n throw new Error(`Unknown palette \"${name}\". Valid: ${palettes.map((p) => p.name).join(', ')}`);\n }\n return palette;\n}\n\n/**\n * Deterministically pick a palette from a seed.\n *\n * Uses rendezvous (highest-random-weight) hashing: every palette gets a\n * pseudo-random score from `seed + name`, and the highest score wins. The list's\n * order doesn't matter, adding a palette only moves the seeds the new palette\n * wins (~1 in N), and removing one only moves the seeds that had picked it.\n */\nexport function pickPalette(seed: string, palettes: readonly Palette[] = colorPalettes): Palette {\n let best: Palette | undefined;\n let bestScore = -1;\n for (const palette of palettes) {\n // Run the hash through the PRNG once: FNV-1a alone mixes trailing\n // characters poorly, and names often share prefixes.\n const score = createSeededRandom(hashStringToInt(`${seed}::palette::${palette.name}`))();\n if (score > bestScore || (score === bestScore && palette.name < best!.name)) {\n best = palette;\n bestScore = score;\n }\n }\n if (!best) throw new Error('At least one palette is required.');\n return best;\n}\n"],"mappings":"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","names":[]}
|