@dooph-software/fluid-gradients 3.0.0 → 3.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/README.md +1 -0
- package/dist/cli.js +29 -12
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/skills/using-fluid-gradients/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -188,6 +188,7 @@ Output & info:
|
|
|
188
188
|
--out <path> Output file, .webp or .png. Default: output/<palette>-<seed>.webp
|
|
189
189
|
--random-palette Pick the palette at random instead of deriving it from --seed. The name is printed.
|
|
190
190
|
--chaos Ignore the palettes: use 3–5 random colors (the same --seed gives the same colors).
|
|
191
|
+
--colors <hex,...> Use your own 3–5 colors, light → dark, comma-separated: "#fdf6ee,#f2c4ce,#3f6fd8". The # is optional.
|
|
191
192
|
--json Print result metadata as JSON.
|
|
192
193
|
--list-palettes Print built-in palette names and exit.
|
|
193
194
|
--help Show this help (-h).
|
package/dist/cli.js
CHANGED
|
@@ -774,6 +774,11 @@ var cliOnlyFlags = [
|
|
|
774
774
|
placeholder: "",
|
|
775
775
|
describe: "Ignore the palettes: use 3\u20135 random colors (the same --seed gives the same colors)."
|
|
776
776
|
},
|
|
777
|
+
{
|
|
778
|
+
flag: "colors",
|
|
779
|
+
placeholder: "<hex,...>",
|
|
780
|
+
describe: 'Use your own 3\u20135 colors, light \u2192 dark, comma-separated: "#fdf6ee,#f2c4ce,#3f6fd8". The # is optional.'
|
|
781
|
+
},
|
|
777
782
|
{ flag: "json", placeholder: "", describe: "Print result metadata as JSON." },
|
|
778
783
|
{ flag: "list-palettes", placeholder: "", describe: "Print built-in palette names and exit." },
|
|
779
784
|
{ flag: "help", placeholder: "", describe: "Show this help (-h)." }
|
|
@@ -846,11 +851,10 @@ ${cliOnlyFlags.map(line).join("\n")}
|
|
|
846
851
|
})();
|
|
847
852
|
var parseOptions = {
|
|
848
853
|
...Object.fromEntries(allFlags.map((f) => [f.flag, { type: "string" }])),
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
chaos: { type: "boolean" },
|
|
854
|
+
// A CLI-only flag with a placeholder takes a value; one without is a switch.
|
|
855
|
+
...Object.fromEntries(
|
|
856
|
+
cliOnlyFlags.map((f) => [f.flag, { type: f.placeholder ? "string" : "boolean" }])
|
|
857
|
+
),
|
|
854
858
|
help: { type: "boolean", short: "h" }
|
|
855
859
|
};
|
|
856
860
|
function chaosPalette(seed) {
|
|
@@ -864,6 +868,16 @@ function chaosPalette(seed) {
|
|
|
864
868
|
});
|
|
865
869
|
return { name: "chaos", colors };
|
|
866
870
|
}
|
|
871
|
+
function customPalette(raw) {
|
|
872
|
+
const colors = raw.split(",").map((c) => c.trim()).filter((c) => c.length > 0).map((c) => (c.startsWith("#") ? c : `#${c}`).toLowerCase());
|
|
873
|
+
const palette = { name: "custom", colors };
|
|
874
|
+
try {
|
|
875
|
+
validatePalette(palette);
|
|
876
|
+
} catch (err) {
|
|
877
|
+
throw new Error(err.message.replace('Palette "custom"', "--colors"));
|
|
878
|
+
}
|
|
879
|
+
return palette;
|
|
880
|
+
}
|
|
867
881
|
async function runCli(argv) {
|
|
868
882
|
const values = parseArgs({ args: argv, options: parseOptions }).values;
|
|
869
883
|
const raw = (flag) => values[flag];
|
|
@@ -889,13 +903,14 @@ async function runCli(argv) {
|
|
|
889
903
|
}
|
|
890
904
|
const seed = read("seed") ?? randomBytes(4).toString("hex");
|
|
891
905
|
const paletteName = read("palette");
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
906
|
+
const customColors = raw("colors");
|
|
907
|
+
const sources = ["palette", "random-palette", "chaos", "colors"].filter(
|
|
908
|
+
(flag) => values[flag] !== void 0 && values[flag] !== false
|
|
909
|
+
);
|
|
910
|
+
if (sources.length > 1) {
|
|
911
|
+
throw new Error(`${sources.map((f) => `--${f}`).join(" and ")} cannot be combined.`);
|
|
897
912
|
}
|
|
898
|
-
const palette = values.chaos ? chaosPalette(seed) : values["random-palette"] ? colorPalettes[randomInt(colorPalettes.length)] : paletteName === void 0 ? pickPalette(seed) : findPalette(paletteName);
|
|
913
|
+
const palette = customColors !== void 0 ? customPalette(customColors) : values.chaos ? chaosPalette(seed) : values["random-palette"] ? colorPalettes[randomInt(colorPalettes.length)] : paletteName === void 0 ? pickPalette(seed) : findPalette(paletteName);
|
|
899
914
|
const out = raw("out") ?? join("output", `${palette.name}-${seed}.webp`);
|
|
900
915
|
const extension = extname(out).toLowerCase();
|
|
901
916
|
if (extension !== ".webp" && extension !== ".png") {
|
|
@@ -920,7 +935,9 @@ async function runCli(argv) {
|
|
|
920
935
|
} else {
|
|
921
936
|
console.log(`\u2713 ${out} (${(result.buffer.length / 1024).toFixed(2)} KB)`);
|
|
922
937
|
console.log(` seed: ${result.seed}`);
|
|
923
|
-
if (values.chaos
|
|
938
|
+
if (values.chaos || customColors !== void 0) {
|
|
939
|
+
console.log(` colors: ${result.palette.colors.join(" ")}`);
|
|
940
|
+
}
|
|
924
941
|
console.log(` palette: ${result.palette.name} (accent: ${result.accent}, complement: ${result.complement})`);
|
|
925
942
|
}
|
|
926
943
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/render.ts","../src/sweep.ts","../src/random.ts","../src/color.ts","../src/palettes.ts","../src/flags.ts","../src/create.ts","../src/index.ts"],"sourcesContent":["/*\n * cli — the `fluid-gradient` command: parse flags, render one image, write it out.\n *\n * ## behavior\n * - Parser config, help text and the options object are all derived from the\n * tables in `./flags`; this file contributes the wiring, not the flag list.\n * - With no `--seed`, a random one is generated and the palette is derived from\n * it exactly as the API would. Both are printed so any result can be\n * reproduced.\n * - `--random-palette` picks a built-in palette at random even when `--seed` is\n * given, so a fixed composition can be tried in many palettes; the picked name\n * is printed. `--chaos` replaces the palette with 3–5 random hex colors drawn\n * from the seed (`<seed>::chaos`), so the same seed gives the same colors.\n * Both conflict with `--palette` and with each other, and throw.\n * - Output format comes from the `--out` extension; anything but `.webp` or\n * `.png` throws.\n *\n * ## constraints\n * - No flag is defined here. Adding one to `parseOptions` or to the help string\n * gives it no `parse`, no validation and no README entry, and the typecheck\n * that pairs flags with render options never sees it — add it to `optionFlags`,\n * `lookFlags` or `cliOnlyFlags` in `./flags` and this file picks it up.\n * - `helpText` is documentation, not just terminal output:\n * `scripts/sync-docs.mjs` splices it into README.md between the\n * `docs:begin:cli-help` markers, and `npm test` runs that script with\n * `--check`. Changing the help layout without running `npm run docs` and\n * committing the README fails the test run.\n * - The `realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)` guard\n * stays. Importing this module must do nothing: the tests import `runCli` and\n * `scripts/sync-docs.mjs` imports `helpText`, so a bare top-level `runCli()`\n * would have both of them render an image and `process.exit` mid-run.\n */\nimport { randomBytes, randomInt } from 'node:crypto';\nimport { mkdirSync, realpathSync, writeFileSync } from 'node:fs';\nimport { dirname, extname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs, type ParseArgsConfig } from 'node:util';\nimport { cliOnlyFlags, lookFlags, optionFlags, type FlagSpec } from './flags';\nimport {\n colorPalettes,\n createSeededRandom,\n findPalette,\n generateMeshGradient,\n hashStringToInt,\n pickPalette,\n type Palette,\n type PaletteColors,\n} from './index';\nimport type { GradientLook } from './render';\n\nconst allFlags = [...Object.values(optionFlags), ...Object.values(lookFlags)];\n\n/** Help text, generated from the flag tables so it can't document a flag that doesn't exist. */\nexport const helpText = (() => {\n const width = Math.max(\n ...[...allFlags, ...cliOnlyFlags].map((f) => `--${f.flag} ${f.placeholder}`.trimEnd().length),\n );\n const line = (f: { flag: string; placeholder: string; describe: string }) =>\n ` ${`--${f.flag} ${f.placeholder}`.trimEnd().padEnd(width)} ${f.describe}`;\n return `Usage: fluid-gradient [options]\n\nEvery flag is optional. With none, writes one WebP with a random seed and a\npalette picked from that seed.\n\nOptions:\n${allFlags.map(line).join('\\n')}\n\nOutput & info:\n${cliOnlyFlags.map(line).join('\\n')}\n`;\n})();\n\nconst parseOptions: ParseArgsConfig['options'] = {\n ...Object.fromEntries(allFlags.map((f) => [f.flag, { type: 'string' as const }])),\n out: { type: 'string' },\n json: { type: 'boolean' },\n 'list-palettes': { type: 'boolean' },\n 'random-palette': { type: 'boolean' },\n chaos: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n};\n\n/** 3–5 uniformly random colors, a pure function of the seed. */\nfunction chaosPalette(seed: string): Palette {\n const random = createSeededRandom(hashStringToInt(`${seed}::chaos`));\n const count = 3 + Math.floor(random() * 3);\n const colors = Array.from({ length: count }, () => {\n const channels = [random(), random(), random()].map((c) =>\n Math.floor(c * 256).toString(16).padStart(2, '0'),\n );\n return `#${channels.join('')}` as const;\n }) as unknown as PaletteColors;\n return { name: 'chaos', colors };\n}\n\n/** What a flag's own `parse` returns, e.g. `width` → number. */\ntype Parsed<K extends keyof typeof optionFlags> = ReturnType<(typeof optionFlags)[K]['parse']>;\n\nexport async function runCli(argv: string[]): Promise<void> {\n // parseArgs' inference is lost through the generated options table, so the\n // flag tables provide the types instead.\n const values = parseArgs({ args: argv, options: parseOptions }).values as Record<\n string,\n string | boolean | undefined\n >;\n const raw = (flag: string) => values[flag] as string | undefined;\n\n if (values.help) {\n console.log(helpText);\n return;\n }\n if (values['list-palettes']) {\n for (const palette of colorPalettes) {\n console.log(`${palette.name} ${palette.colors.join(' ')}`);\n }\n return;\n }\n\n // Read each render option through its own spec, so parsing and validation live\n // with the flag definition rather than here.\n const read = <K extends keyof typeof optionFlags>(key: K): Parsed<K> | undefined => {\n const spec = optionFlags[key] as FlagSpec<Parsed<K>>;\n const value = raw(spec.flag);\n return value === undefined ? undefined : spec.parse(value);\n };\n\n const look: Partial<GradientLook> = {};\n for (const [key, spec] of Object.entries(lookFlags)) {\n const value = raw(spec.flag);\n if (value !== undefined) look[key as keyof GradientLook] = spec.parse(value);\n }\n\n // The seed is random when omitted; the palette then follows from it, exactly\n // as in the API. Both are printed, so any result can be reproduced.\n const seed = read('seed') ?? randomBytes(4).toString('hex');\n const paletteName = read('palette');\n if (paletteName !== undefined && (values['random-palette'] || values.chaos)) {\n throw new Error('--palette cannot be combined with --random-palette or --chaos.');\n }\n if (values['random-palette'] && values.chaos) {\n throw new Error('--random-palette and --chaos cannot be combined.');\n }\n const palette = values.chaos\n ? chaosPalette(seed)\n : values['random-palette']\n ? colorPalettes[randomInt(colorPalettes.length)]\n : paletteName === undefined\n ? pickPalette(seed)\n : findPalette(paletteName);\n const out = raw('out') ?? join('output', `${palette.name}-${seed}.webp`);\n\n const extension = extname(out).toLowerCase();\n if (extension !== '.webp' && extension !== '.png') {\n throw new Error(`--out must end in .webp or .png. Got: ${out}`);\n }\n\n const result = await generateMeshGradient({\n seed,\n palette,\n width: read('width'),\n height: read('height'),\n quality: read('quality'),\n format: extension === '.png' ? 'png' : 'webp',\n ...(Object.keys(look).length > 0 ? { look } : {}),\n });\n\n mkdirSync(dirname(out), { recursive: true });\n writeFileSync(out, result.buffer);\n\n if (values.json) {\n const { buffer, palette: used, ...meta } = result;\n console.log(\n JSON.stringify({ ...meta, palette: used.name, out, bytes: buffer.length }),\n );\n } else {\n console.log(`✓ ${out} (${(result.buffer.length / 1024).toFixed(2)} KB)`);\n console.log(` seed: ${result.seed}`);\n if (values.chaos) console.log(` colors: ${result.palette.colors.join(' ')}`);\n console.log(` palette: ${result.palette.name} (accent: ${result.accent}, complement: ${result.complement})`);\n }\n}\n\n// Only run when executed as a command; importing this module (tests, tooling) does nothing.\nconst invokedPath = process.argv[1];\nif (invokedPath && realpathSync(invokedPath) === fileURLToPath(import.meta.url)) {\n runCli(process.argv.slice(2)).catch((err: Error) => {\n console.error(`Error: ${err.message}`);\n process.exit(1);\n });\n}\n","/*\n * render — the noise pipeline that turns a seed and a palette into pixels.\n *\n * ## behavior\n * - `renderCanvas` seeds one stream from the seed, builds three simplex noise\n * fields from it, domain-warps the third by the first two, and maps the result\n * along the palette ramp; the film grain then draws from the same stream.\n * - `rampPositionAt` is the fluid itself: noise-space point → ramp position.\n * The pixel loop calls it once per pixel. `look.sweep` / `look.ground`\n * (0–1, default 0) also hand it to `applySweep` in `./sweep`, which samples it\n * at moved points (off the canvas too) and rewrites the pixels before grain.\n * At 0/0 that pass doesn't run.\n * - `generateImage` wraps that and encodes: PNG straight off the canvas, WebP\n * through sharp at `DEFAULT_WEBP_QUALITY`.\n * - `lookDescriptions` is the one copy of the per-knob prose; `src/flags.ts`\n * builds CLI help from it and `scripts/sync-docs.mjs` builds the README table.\n *\n * ## constraints\n * - The order values are drawn from `random` is fixed: three `createNoise2D`\n * calls, then one grain value per pixel. Inserting a draw, removing one, or\n * reordering them shifts every pixel of every image — the grain loop is not\n * incidental noise that can be swapped for `Math.random`; it consumes the\n * tail of the same stream. `test/fluid-gradients.test.mjs` compares seed `aspect` /\n * `jewel-peacock` byte-for-byte against artwork already shipped in Aspect.\n * - Sweep and ground must not disturb the default path. The sweep pass draws\n * from its own `<seed>::sweep` stream, never `random`, and grain still comes\n * after it from `random`. Keep `{ sweep: 0, ground: 0 }` identical to omitting them,\n * down to the bytes; the Aspect fixture test checks exactly that.\n * - `defaultLook` and the bare constants in `rampPositionAt` — the `5.2` / `1.3`\n * warp offsets, `amplitude` starting at 0.5 and halving, `frequency`\n * doubling, the `smoothStep` around the contrast term — are the prototype's\n * values, not tuning suggestions. Any of them changes every image rendered\n * without an explicit `look`, which is almost all of them.\n * - Noise is sampled in normalized coordinates (`pixelX / width`), with the\n * aspect ratio applied to X only. This is what makes a 700×800 render the same\n * composition as the 1200×1500 default, which `scripts/generate-examples.mjs`\n * relies on to ship small previews of the real output. Sampling in absolute\n * pixels would make every size a different picture.\n * - `DEFAULT_WEBP_QUALITY` and the plain `sharp(...).webp({ quality })` call are\n * part of the shipped bytes. Adding encoder options (effort, lossless,\n * chroma subsampling) changes the fixture comparison even though nothing about\n * the rendered pixels moved.\n */\nimport { createCanvas, ImageData, type Canvas, type SKRSContext2D } from '@napi-rs/canvas';\nimport sharp from 'sharp';\nimport { createNoise2D } from 'simplex-noise';\nimport { applySweep } from './sweep';\nimport { paletteAccent, paletteComplement, type Palette, type PaletteName } from './palettes';\nimport { createSeededRandom, hashStringToInt } from './random';\n\n/**\n * Knobs that define the gradient's look. The defaults are the exact values from\n * the original prototype; changing them changes every image for a given seed.\n */\nexport type GradientLook = {\n /** Zoom of the noise field. Lower = broader, softer color regions. */\n baseNoiseFrequency: number;\n /** How far the warp noise displaces the color field. */\n domainWarpStrength: number;\n /** Fractal noise octaves. More = finer detail. */\n fractalOctaveCount: number;\n /** Sharpness of transitions between palette stops. */\n colorTransitionContrast: number;\n /** Opacity of the film-grain overlay, 0–1. */\n grainOpacity: number;\n /** Carries color into soft, curving wisps, like dye drifting in water, 0–1. 0 is the plain fluid. */\n sweep: number;\n /** Share of the image opened to the palette's lightest stop as a pale ground, 0–1. */\n ground: number;\n};\n\n/** One-line description per knob. Shared by the CLI help and the generated README table. */\nexport const lookDescriptions: Readonly<Record<keyof GradientLook, string>> = {\n baseNoiseFrequency: 'Lower values give broader, softer color regions.',\n domainWarpStrength: 'How much the color field swirls.',\n fractalOctaveCount: 'More octaves add finer detail.',\n colorTransitionContrast: 'Sharpness of the edges between colors.',\n grainOpacity: 'Strength of the film-grain overlay.',\n sweep: 'Drifts the color into soft, curving wisps, like dye in water or folded silk, 0–1. 0 is the plain fluid.',\n ground: \"Roughly the share of the image, 0–1, left as a pale ground in the palette's lightest color.\",\n};\n\nexport const defaultLook: Readonly<GradientLook> = {\n baseNoiseFrequency: 0.3,\n domainWarpStrength: 1.0,\n fractalOctaveCount: 2,\n colorTransitionContrast: 1.75,\n grainOpacity: 0.09,\n sweep: 0,\n ground: 0,\n};\n\nexport type MeshGradientOptions<Name extends string = PaletteName> = {\n /** Any string. The same seed + palette + size + look always renders the same image. */\n seed: string;\n /** Palette object, palette name, or omitted to pick deterministically from the seed. */\n palette?: Palette | Name;\n /** Pixels. Defaults to 1200. */\n width?: number;\n /** Pixels. Defaults to 1500. */\n height?: number;\n look?: Partial<GradientLook>;\n};\n\nexport type MeshGradientFormatOptions = {\n /** Defaults to 'webp'. */\n format?: 'webp' | 'png';\n /** WebP quality, 1–100. Defaults to 82. */\n quality?: number;\n};\n\n/** Options after the palette has been resolved to an object. */\nexport type ResolvedMeshGradientOptions = Omit<MeshGradientOptions, 'palette'> & {\n palette: Palette;\n};\n\nexport type MeshGradientCanvas = {\n canvas: Canvas;\n palette: Palette;\n /** Mid-tone hex from the palette, handy as a UI accent next to the image. */\n accent: string;\n /** Opposite-hue color built to stand out on the image, for chips and geometry. */\n complement: string;\n};\n\nexport type MeshGradientImage = {\n buffer: Buffer;\n format: 'webp' | 'png';\n width: number;\n height: number;\n seed: string;\n palette: Palette;\n accent: string;\n complement: string;\n};\n\nexport const DEFAULT_WIDTH = 1200;\nexport const DEFAULT_HEIGHT = 1500;\nexport const DEFAULT_WEBP_QUALITY = 82;\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const packed = parseInt(hex.slice(1), 16);\n return [(packed >> 16) & 255, (packed >> 8) & 255, packed & 255];\n}\n\nfunction sampleColorRamp(\n rgbStops: [number, number, number][],\n position: number,\n): [number, number, number] {\n const clamped = Math.max(0, Math.min(1, position));\n const scaled = clamped * (rgbStops.length - 1);\n const lowerIndex = Math.min(rgbStops.length - 2, Math.floor(scaled));\n const blend = scaled - lowerIndex;\n const [lowR, lowG, lowB] = rgbStops[lowerIndex];\n const [highR, highG, highB] = rgbStops[lowerIndex + 1];\n return [\n lowR + (highR - lowR) * blend,\n lowG + (highG - lowG) * blend,\n lowB + (highB - lowB) * blend,\n ];\n}\n\nconst smoothStep = (edge: number) => edge * edge * (3 - 2 * edge);\n\n/**\n * Render a domain-warped noise gradient onto a canvas. The pixel pipeline and\n * the order random values are drawn in are verbatim from the prototype — both\n * must stay fixed for seeds to keep producing the same image.\n */\nexport async function renderCanvas(\n options: ResolvedMeshGradientOptions,\n): Promise<MeshGradientCanvas> {\n const { seed, palette, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT } = options;\n const look = { ...defaultLook, ...options.look };\n for (const knob of ['sweep', 'ground'] as const) {\n if (!(look[knob] >= 0 && look[knob] <= 1)) {\n throw new RangeError(`look.${knob} must be between 0 and 1. Got: ${look[knob]}`);\n }\n }\n\n const random = createSeededRandom(hashStringToInt(seed));\n const rgbStops = palette.colors.map(hexToRgb);\n\n const horizontalWarpNoise = createNoise2D(random);\n const verticalWarpNoise = createNoise2D(random);\n const colorFieldNoise = createNoise2D(random);\n\n const sampleFractalNoise = (\n noise: (x: number, y: number) => number,\n sampleX: number,\n sampleY: number,\n ) => {\n let total = 0;\n let amplitude = 0.5;\n let frequency = 1;\n for (let octave = 0; octave < look.fractalOctaveCount; octave++) {\n total += amplitude * noise(sampleX * frequency, sampleY * frequency);\n amplitude *= 0.5;\n frequency *= 2;\n }\n return total;\n };\n\n const canvas = createCanvas(width, height);\n const context = canvas.getContext('2d');\n const image = context.createImageData(width, height);\n const pixels = image.data;\n const aspectRatio = width / height;\n\n /** Position along the palette ramp (0–1) at a point in noise space. */\n const rampPositionAt = (sampleX: number, sampleY: number) => {\n const horizontalWarpOffset = sampleFractalNoise(horizontalWarpNoise, sampleX, sampleY);\n const verticalWarpOffset = sampleFractalNoise(verticalWarpNoise, sampleX + 5.2, sampleY + 1.3);\n\n const rawFieldValue = sampleFractalNoise(\n colorFieldNoise,\n sampleX + look.domainWarpStrength * horizontalWarpOffset,\n sampleY + look.domainWarpStrength * verticalWarpOffset,\n );\n const normalizedValue = (rawFieldValue + 1) / 2;\n\n return smoothStep(\n Math.max(0, Math.min(1, (normalizedValue - 0.5) * look.colorTransitionContrast + 0.5)),\n );\n };\n\n for (let pixelY = 0; pixelY < height; pixelY++) {\n for (let pixelX = 0; pixelX < width; pixelX++) {\n const sampleX = (pixelX / width) * look.baseNoiseFrequency * aspectRatio;\n const sampleY = (pixelY / height) * look.baseNoiseFrequency;\n const contrastedValue = rampPositionAt(sampleX, sampleY);\n\n const [red, green, blue] = sampleColorRamp(rgbStops, contrastedValue);\n const pixelIndex = (pixelY * width + pixelX) * 4;\n pixels[pixelIndex] = red;\n pixels[pixelIndex + 1] = green;\n pixels[pixelIndex + 2] = blue;\n pixels[pixelIndex + 3] = 255;\n }\n }\n\n if (look.sweep > 0 || look.ground > 0) {\n applySweep({\n seed,\n width,\n height,\n pixels,\n // Height units: the fluid's x runs 0…aspect, y runs 0…1.\n rampPositionAt: (u, v) => rampPositionAt(u * look.baseNoiseFrequency, v * look.baseNoiseFrequency),\n rgbStops,\n sampleRamp: (position) => sampleColorRamp(rgbStops, position),\n sweep: look.sweep,\n ground: look.ground,\n });\n }\n\n context.putImageData(image, 0, 0);\n\n // Film-grain overlay. Draws from the same random stream as the noise above.\n const grainPixels = new Uint8ClampedArray(width * height * 4);\n for (let i = 0; i < width * height; i++) {\n const brightness = Math.floor(random() * 256);\n grainPixels[i * 4] = brightness;\n grainPixels[i * 4 + 1] = brightness;\n grainPixels[i * 4 + 2] = brightness;\n grainPixels[i * 4 + 3] = 255;\n }\n const grainCanvas = createCanvas(width, height);\n grainCanvas.getContext('2d').putImageData(new ImageData(grainPixels, width, height), 0, 0);\n applyOverlay(context, grainCanvas, look.grainOpacity);\n\n return { canvas, palette, accent: paletteAccent(palette), complement: paletteComplement(palette) };\n}\n\nfunction applyOverlay(context: SKRSContext2D, overlay: Canvas, opacity: number) {\n context.save();\n context.globalAlpha = opacity;\n context.globalCompositeOperation = 'overlay';\n context.drawImage(overlay, 0, 0);\n context.restore();\n}\n\n/**\n * Render a gradient and encode it. WebP (the default) is encoded with sharp;\n * PNG comes straight from the canvas.\n */\nexport async function generateImage(\n options: ResolvedMeshGradientOptions & MeshGradientFormatOptions,\n): Promise<MeshGradientImage> {\n const { format = 'webp', quality = DEFAULT_WEBP_QUALITY } = options;\n const { canvas, palette, accent, complement } = await renderCanvas(options);\n const pngBuffer = canvas.encodeSync('png');\n\n let buffer: Buffer = pngBuffer;\n if (format === 'webp') {\n buffer = await sharp(pngBuffer).webp({ quality }).toBuffer();\n }\n\n return {\n buffer,\n format,\n width: canvas.width,\n height: canvas.height,\n seed: options.seed,\n palette,\n accent,\n complement,\n };\n}\n","/*\n * sweep — the opt-in second pass behind `look.sweep` and `look.ground`.\n *\n * \"Sweep\" is dye drifting in slow water, photographed soft: the fluid's color\n * is carried through a large, smooth current, so fast parts of the current\n * pull it into thin curving wisps and slow parts leave soft folds, like silk\n * or a long-exposure flower. \"Ground\" is the pale base the color floats over,\n * in the palette's lightest stop, opened where the fluid is already lightest\n * so it reads as space for text. Neither draws objects, adds fiber texture,\n * or twirls.\n *\n * ## behavior\n * - `applySweep` rewrites the RGB of the fluid pass's pixels in place, before\n * grain. It samples the fluid through `rampPositionAt` (the same function\n * the fluid loop uses, evaluable off the canvas) and maps back through the\n * same ramp, so every color is a ramp color or a blend toward the ground.\n * - Current: the curl of a low-frequency noise (divergence-free, so dye never\n * piles up or thins out), soft-capped below a seeded drift, plus that drift.\n * It slowly turns between two such fields over the trace, which folds the\n * wisps.\n * - Advection: each point is traced backward through the current for\n * `sweep × 1.6` image heights of drift and takes the fluid's color\n * from where it lands. At sweep 0 nothing moves.\n * - Softening: a slight defocus blur, then a line integral convolution along\n * the present current: each point blurs along its own path for one exposure\n * (typical travel `sweep × 0.5` heights, flat-topped window), so wisps blur\n * along their length more than across it, and still water barely blurs.\n * - Ground: coverage is the advected ramp position plus a domain-warped noise\n * sampled at the same advected points; the lowest `ground × 0.9` share of\n * the image becomes the lightest stop through a soft edge, which the\n * softening then carries along.\n * - Everything runs on a working grid at most 400 px tall and is upsampled;\n * about 1 s at 1200×1500.\n *\n * ## constraints\n * - Never draw from the fluid pass's `random`. Everything here comes from its\n * own `<seed>::sweep` stream. One extra draw from the main stream shifts the\n * grain of every image, sweep or not, and breaks the byte-for-byte Aspect\n * fixture. `render.ts` calls in only when `sweep > 0 || ground > 0`.\n * - Keep the eddies slower than the drift (`eddyCap × √2 < 1`). Where an eddy\n * outruns the drift its streamlines close, and the dye winds into bullseye\n * rings — the twirl look that was rejected. A much higher\n * `velocityFrequency` or longer `advectDistance` turns it into marbling.\n * - Alternatives already tried and rejected, so don't drift back to them:\n * painting along curl-noise flow lines (reads as Van Gogh brushwork), fiber\n * or strand feathering (watercolor, fur), and stretching along one camera\n * motion (too linear). The references are smooth, soft photographs.\n * - The velocity grid's margin must cover the farthest a trace can travel.\n * Traces that leave it read a clamped current and draw a hard-edged curve.\n * - The exposure follows the raw velocity, not its direction. Normalizing it\n * flips the blur's direction across every stagnation point, which shows as\n * sharp X-shaped creases.\n * - The constants below are part of the output for any seed rendered with\n * sweep or ground, as `defaultLook` is for the fluid. Retune them only as a\n * deliberate visual change, with before/after renders.\n */\nimport { createNoise2D } from 'simplex-noise';\nimport { createSeededRandom, hashStringToInt } from './random';\n\ntype Rgb = [number, number, number];\n\nexport type SweepInput = {\n seed: string;\n width: number;\n height: number;\n /** RGBA from the fluid pass, rewritten in place. Alpha is left alone. */\n pixels: Uint8ClampedArray;\n /** The fluid's ramp position (0–1) at any point, in height units; defined off the canvas too. */\n rampPositionAt: (u: number, v: number) => number;\n rgbStops: Rgb[];\n sampleRamp: (position: number) => Rgb;\n sweep: number;\n ground: number;\n};\n\n/** Lengths are in image heights, frequencies in cycles per image height. */\nconst tuning = {\n workHeight: 400,\n velocityFrequency: 0.4,\n /** Velocity grid cells per image height. */\n velocityResolution: 80,\n /** Travel at sweep 1, measured at drift speed. */\n advectDistance: 1.6,\n advectSteps: 32,\n /** Drift speed relative to the current's RMS speed. */\n drift: 1.0,\n /** Eddy speed cap, relative to the drift. Two fields mixed at most √2 ×\n * this, which must stay below 1 or eddies can close. */\n eddyCap: 0.65,\n /** Radians the current turns between its two fields over the trace. */\n timeSpin: 1.2,\n defocus: 0.008,\n smearLength: 0.5,\n smearSteps: 40,\n /** Fraction of the exposure at full weight before the window tapers. */\n smearFlat: 0.6,\n groundSoftness: 0.35,\n groundFieldWeight: 1.0,\n groundNoiseWeight: 0.6,\n groundBlur: 0.02,\n groundMaxCover: 0.9,\n groundFrequency: 0.9,\n} as const;\n\nconst smoothStep = (edge: number) => edge * edge * (3 - 2 * edge);\nconst clamp01 = (value: number) => Math.max(0, Math.min(1, value));\n\ntype Noise = (x: number, y: number) => number;\nconst fractal = (noise: Noise, x: number, y: number) =>\n 0.5 * noise(x, y) + 0.25 * noise(x * 2, y * 2);\n\n/** A scalar grid sampled bilinearly in its own pixel coordinates. */\nclass Grid {\n readonly values: Float32Array;\n constructor(\n readonly width: number,\n readonly height: number,\n ) {\n this.values = new Float32Array(width * height);\n }\n sample(x: number, y: number) {\n const gx = Math.max(0, Math.min(this.width - 1.0001, x));\n const gy = Math.max(0, Math.min(this.height - 1.0001, y));\n const x0 = Math.floor(gx);\n const y0 = Math.floor(gy);\n const fx = gx - x0;\n const fy = gy - y0;\n const v = this.values;\n const i = y0 * this.width + x0;\n const top = v[i] + (v[i + 1] - v[i]) * fx;\n const bottom = v[i + this.width] + (v[i + this.width + 1] - v[i + this.width]) * fx;\n return top + (bottom - top) * fy;\n }\n}\n\nexport function applySweep(input: SweepInput) {\n const { seed, width, height, pixels, rampPositionAt, rgbStops, sampleRamp, sweep, ground } = input;\n const t = tuning;\n const random = createSeededRandom(hashStringToInt(`${seed}::sweep`));\n const currentNoiseA = createNoise2D(random);\n const currentNoiseB = createNoise2D(random);\n const coverageNoise = createNoise2D(random);\n const coverageWarpNoise = createNoise2D(random);\n const driftAngle = random() * Math.PI * 2;\n\n // Working grid, in its own pixels; height units are y / workH.\n const scale = Math.min(1, t.workHeight / height);\n const workW = Math.max(2, Math.round(width * scale));\n const workH = Math.max(2, Math.round(height * scale));\n const aspect = width / height;\n\n // ── Current: two curl-noise fields at unit RMS speed, plus the drift. ──\n const driftX = Math.cos(driftAngle) * t.drift;\n const driftY = Math.sin(driftAngle) * t.drift;\n // Speed never exceeds the drift plus √2 × the eddy cap (< 2× drift), and\n // `advectDistance` is measured at drift speed, so 2× covers any trace.\n const margin = 2 * t.advectDistance * sweep + 0.1;\n const cell = 1 / t.velocityResolution;\n const velW = Math.ceil((aspect + 2 * margin) / cell) + 2;\n const velH = Math.ceil((1 + 2 * margin) / cell) + 2;\n const currents = [currentNoiseA, currentNoiseB].map((noise) => {\n const vx = new Grid(velW, velH);\n const vy = new Grid(velW, velH);\n const f = t.velocityFrequency;\n const e = 0.002;\n let total = 0;\n for (let gy = 0; gy < velH; gy++) {\n for (let gx = 0; gx < velW; gx++) {\n const u = (gx * cell - margin) * f;\n const v = (gy * cell - margin) * f;\n const i = gy * velW + gx;\n vx.values[i] = (fractal(noise, u, v + e) - fractal(noise, u, v - e)) / (2 * e);\n vy.values[i] = -(fractal(noise, u + e, v) - fractal(noise, u - e, v)) / (2 * e);\n total += vx.values[i] ** 2 + vy.values[i] ** 2;\n }\n }\n // Unit RMS speed, then a soft cap below the drift: the current then always\n // makes headway along the drift, so no streamline closes into an eddy and\n // nothing stalls.\n const rms = Math.sqrt(total / vx.values.length) || 1;\n const cap = t.eddyCap * t.drift;\n for (let i = 0; i < vx.values.length; i++) {\n const speed = Math.hypot(vx.values[i], vy.values[i]) / rms + 1e-9;\n const limited = (cap * Math.tanh(speed / cap)) / speed / rms;\n vx.values[i] *= limited;\n vy.values[i] *= limited;\n }\n return { vx, vy };\n });\n const velocity = (u: number, v: number, phase: number, out: number[]) => {\n const gx = (u + margin) / cell;\n const gy = (v + margin) / cell;\n const a = Math.cos(phase);\n const b = Math.sin(phase);\n out[0] = a * currents[0].vx.sample(gx, gy) + b * currents[1].vx.sample(gx, gy) + driftX;\n out[1] = a * currents[0].vy.sample(gx, gy) + b * currents[1].vy.sample(gx, gy) + driftY;\n };\n\n // ── Advection: trace each point back to where its dye came from. ──\n const flowX = new Grid(workW, workH);\n const flowY = new Grid(workW, workH);\n const fieldSource = new Grid(workW, workH);\n const sourceU = new Float32Array(workW * workH);\n const sourceV = new Float32Array(workW * workH);\n const steps = t.advectSteps;\n const dt = (sweep * t.advectDistance) / t.drift / steps;\n const k1 = [0, 0];\n const k2 = [0, 0];\n for (let y = 0; y < workH; y++) {\n for (let x = 0; x < workW; x++) {\n let u = x / workH;\n let v = y / workH;\n const i = y * workW + x;\n velocity(u, v, 0, k1);\n flowX.values[i] = k1[0];\n flowY.values[i] = k1[1];\n for (let s = 0; s < steps; s++) {\n const phase = (t.timeSpin * s) / steps;\n velocity(u, v, phase, k1);\n velocity(u - (k1[0] * dt) / 2, v - (k1[1] * dt) / 2, phase, k2);\n u -= k2[0] * dt;\n v -= k2[1] * dt;\n }\n sourceU[i] = u;\n sourceV[i] = v;\n fieldSource.values[i] = rampPositionAt(u, v);\n }\n }\n boxBlur(fieldSource.values, workW, workH, Math.max(1, Math.round(t.defocus * sweep * workH)), 3);\n\n // ── Ground: opens first where the fluid is already near the light end. ──\n const coverageSource = new Grid(workW, workH);\n if (ground > 0) {\n for (let y = 0; y < workH; y++) {\n for (let x = 0; x < workW; x++) {\n const i = y * workW + x;\n const su = sourceU[i] * t.groundFrequency;\n const sv = sourceV[i] * t.groundFrequency;\n const warpX = fractal(coverageWarpNoise, su, sv);\n const warpY = fractal(coverageWarpNoise, su + 5.2, sv + 1.3);\n coverageSource.values[i] =\n t.groundFieldWeight * fieldSource.values[i] + t.groundNoiseWeight * fractal(coverageNoise, su + warpX, sv + warpY);\n }\n }\n const sorted = Float32Array.from(coverageSource.values).sort();\n const threshold = sorted[Math.min(sorted.length - 1, Math.floor(ground * t.groundMaxCover * sorted.length))];\n for (let i = 0; i < coverageSource.values.length; i++) coverageSource.values[i] -= threshold;\n boxBlur(coverageSource.values, workW, workH, Math.max(1, Math.round(t.groundBlur * workH)), 3);\n }\n\n // ── Exposure: average along the present current (line integral convolution). ──\n const smearedField = new Grid(workW, workH);\n const smearedCoverage = new Grid(workW, workH);\n // Blur is speed × exposure time, so it fades to nothing where the current\n // stalls; a fixed length there would flip direction between neighbors and\n // draw a hard crease. Exposure time is set so typical travel is the length.\n const smearSteps = t.smearSteps;\n const step = (sweep * t.smearLength * workH) / 2 / Math.hypot(1, t.drift) / smearSteps;\n const weights = new Float32Array(smearSteps + 1);\n for (let s = 0; s <= smearSteps; s++) {\n const along = s / (smearSteps + 1);\n weights[s] = along <= t.smearFlat ? 1 : 0.5 + 0.5 * Math.cos((Math.PI * (along - t.smearFlat)) / (1 - t.smearFlat));\n }\n for (let y = 0; y < workH; y++) {\n for (let x = 0; x < workW; x++) {\n let fieldTotal = weights[0] * fieldSource.sample(x, y);\n let coverageTotal = ground > 0 ? weights[0] * coverageSource.sample(x, y) : 0;\n let weightTotal = weights[0];\n for (let direction = -1; direction <= 1; direction += 2) {\n let px = x;\n let py = y;\n for (let s = 1; s <= smearSteps; s++) {\n const mx = px + (direction * flowX.sample(px, py) * step) / 2;\n const my = py + (direction * flowY.sample(px, py) * step) / 2;\n px += direction * flowX.sample(mx, my) * step;\n py += direction * flowY.sample(mx, my) * step;\n if (px < 0 || py < 0 || px > workW - 1 || py > workH - 1) break;\n const w = weights[s];\n fieldTotal += w * fieldSource.sample(px, py);\n if (ground > 0) coverageTotal += w * coverageSource.sample(px, py);\n weightTotal += w;\n }\n }\n smearedField.values[y * workW + x] = fieldTotal / weightTotal;\n smearedCoverage.values[y * workW + x] = coverageTotal / weightTotal;\n }\n }\n\n // ── Compose at full resolution. ──\n const groundColor = rgbStops[0];\n for (let py = 0; py < height; py++) {\n for (let px = 0; px < width; px++) {\n const sx = (px + 0.5) * scale - 0.5;\n const sy = (py + 0.5) * scale - 0.5;\n let [r, g, b] = sampleRamp(smearedField.sample(sx, sy));\n if (ground > 0) {\n const m = smoothStep(clamp01(smearedCoverage.sample(sx, sy) / t.groundSoftness + 0.5));\n r = groundColor[0] + (r - groundColor[0]) * m;\n g = groundColor[1] + (g - groundColor[1]) * m;\n b = groundColor[2] + (b - groundColor[2]) * m;\n }\n const o = (py * width + px) * 4;\n pixels[o] = r;\n pixels[o + 1] = g;\n pixels[o + 2] = b;\n }\n }\n}\n\nfunction boxBlur(values: Float32Array, width: number, height: number, radius: number, passes: number) {\n const scratch = new Float32Array(values.length);\n for (let pass = 0; pass < passes; pass++) {\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n let total = 0;\n for (let k = -radius; k <= radius; k++) total += values[y * width + Math.max(0, Math.min(width - 1, x + k))];\n scratch[y * width + x] = total / (2 * radius + 1);\n }\n }\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n let total = 0;\n for (let k = -radius; k <= radius; k++) total += scratch[Math.max(0, Math.min(height - 1, y + k)) * width + x];\n values[y * width + x] = total / (2 * radius + 1);\n }\n }\n }\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 * 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 * 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 { name: 'ember-mono', colors: ['#ff9a6b', '#cf5604', '#632400', '#180501'] },\n\n // ── Monochromatic: one hue, lightness stepping down (OKLCH-spaced) ──\n { name: 'tangerine-mono', colors: ['#ffeee4', '#feb079', '#dc7100', '#824103'] },\n { name: 'electric-blue-mono', colors: ['#ebf2fe', '#a4c6fc', '#5d91e7', '#2a5397'] },\n { name: 'magenta-mono', colors: ['#feecf5', '#edaed1', '#cd6ca6', '#823564'] },\n { name: 'emerald-mono', colors: ['#e2f8ea', '#95d5af', '#2cab70', '#03663e'] },\n { name: 'violet-mono', colors: ['#f2f0fe', '#c8b7ff', '#9e71fd', '#5f38a7'] },\n { name: 'crimson-mono', colors: ['#ffedec', '#feaaa9', '#f44f5d', '#9e122b'] },\n { name: 'aqua-mono', colors: ['#dff7f8', '#8dd2d6', '#39a4a9', '#026266'] },\n\n { name: 'burgundy-mono', colors: ['#fbe0e4', '#dc9ea8', '#a7415a', '#560c24'] },\n { name: 'indigo-mono', colors: ['#eaeefe', '#a7b2f7', '#5a5ce2', '#261b81'] },\n { name: 'lime-mono', colors: ['#edfcd7', '#c2e87d', '#9fc93e', '#638119'] },\n\n // ── Monochromatic earth tones: same idea, chroma held up so they stay\n // clay / ochre / moss rather than collapsing to greige ──\n { name: 'terracotta-mono', colors: ['#fdeae4', '#f6b19b', '#c5694c', '#73311b'] },\n { name: 'ochre-mono', colors: ['#f9edd5', '#dec082', '#a87f09', '#5b4404'] },\n { name: 'olive-mono', colors: ['#edf2d7', '#c2cb91', '#848e38', '#474d04'] },\n { name: 'fern-mono', colors: ['#e3f5df', '#a9d1a0', '#5f9752', '#2a5320'] },\n { name: 'verdigris-mono', colors: ['#d6f7f1', '#96d2c9', '#38988d', '#07534c'] },\n { name: 'umber-mono', colors: ['#feeada', '#e1bc9d', '#ab7a4f', '#623f1e'] },\n { name: 'adobe-mono', colors: ['#fee9e8', '#eeb3b0', '#bb6d6b', '#6c3434'] },\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 { name: 'silver-steel', colors: ['#e2e5e8', '#c4c9cf', '#9aa2ac', '#646f7d'] },\n // ── Airy: a near-white base with soft color splashes, in the style of\n // blurred editorial gradients (light stops first, then the splash hues) ──\n { name: 'petal-bloom', colors: ['#e4eeff', '#bcd2ff', '#dfa6e9', '#fa787b', '#fba96a'] },\n { name: 'tulip-blur', colors: ['#e6f1f6', '#a8d4e8', '#f1c4d0', '#ee9a6e', '#f4b81c'] },\n { name: 'silk-rose', colors: ['#fcedf4', '#f2cfdd', '#f0c1b8', '#d68ba5', '#b07a9f'] },\n\n // ── Flags: sampled from heraldic flag art ──\n { name: 'sultans-eagle', colors: ['#f2da38', '#e3c42a', '#c9a21d', '#a1353a', '#6a1c26'] },\n { name: 'zhuge-nu', colors: ['#f0f3ee', '#dfc652', '#4fa79b', '#1f8283', '#0f5252'] },\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","/*\n * flags — the single source of truth for every CLI flag.\n *\n * ## behavior\n * - `optionFlags` and `lookFlags` each map one render option to one long flag,\n * with the parser/validator for that flag's value living beside it.\n * - `cliOnlyFlags` holds the flags with no API counterpart: file output,\n * terminal presentation, and the palette-source switches (`--random-palette`,\n * `--chaos`), which the CLI resolves into an ordinary `palette` before calling\n * the API.\n * - `src/cli.ts` builds its `parseArgs` config, its help text and its options\n * object from these tables. `format` is absent on purpose — it comes from the\n * `--out` extension.\n *\n * ## constraints\n * - The two `satisfies { [K in keyof ...]: FlagSpec<...> }` clauses are the\n * whole point of the file, not annotations. They make adding a render option\n * to `MeshGradientOptions` or a knob to `GradientLook` fail `npm run lint`\n * until it has a flag here, which is what keeps `fluid-gradient --foo` and\n * `generateMeshGradient({ foo })` from drifting apart. Deleting one to quiet\n * an error removes the check that was reporting it.\n * - Flag definitions live here and nowhere else. A flag added directly to the\n * parser or the help string in `src/cli.ts` gets no `parse`, no typecheck and\n * no README entry; add it to a table here instead and the CLI picks it up.\n * - `describe` strings are user-facing docs: they are rendered into the help\n * text and from there into README.md by `scripts/sync-docs.mjs`. Editing one\n * without running `npm run docs` fails `npm test`, which runs that script with\n * `--check`.\n */\nimport type { GradientLook, MeshGradientFormatOptions, MeshGradientOptions } from './render';\nimport { DEFAULT_HEIGHT, DEFAULT_WEBP_QUALITY, DEFAULT_WIDTH, defaultLook, lookDescriptions } from './render';\n\n/** One flag: how it is written, how it is described, and how its value parses. */\nexport type FlagSpec<Value> = {\n /** Long flag name, used as `--<flag>`. */\n flag: string;\n /** Shown in help, e.g. `<px>`. */\n placeholder: string;\n describe: string;\n parse: (raw: string) => Value;\n};\n\nconst positiveInteger = (flag: string) => (raw: string) => {\n const value = Number(raw);\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`--${flag} must be a positive integer. Got: ${raw}`);\n }\n return value;\n};\n\nconst finiteNumber = (flag: string) => (raw: string) => {\n const value = Number(raw);\n if (!Number.isFinite(value)) throw new Error(`--${flag} must be a number. Got: ${raw}`);\n return value;\n};\n\nconst unitInterval = (flag: string) => (raw: string) => {\n const value = Number(raw);\n if (raw.trim() === '' || !Number.isFinite(value) || value < 0 || value > 1) {\n throw new Error(`--${flag} must be a number from 0 to 1. Got: ${raw}`);\n }\n return value;\n};\n\n/**\n * Render options the CLI maps one-to-one. `format` is excluded because it comes\n * from the --out extension, and `look` has its own table below.\n */\ntype FlaggedOptions = Omit<\n Required<MeshGradientOptions & MeshGradientFormatOptions>,\n 'look' | 'format'\n>;\n\nexport const optionFlags = {\n seed: {\n flag: 'seed',\n placeholder: '<string>',\n describe: 'Seed string. Same seed → same image. Default: random.',\n parse: (raw: string) => raw,\n },\n palette: {\n flag: 'palette',\n placeholder: '<name>',\n describe: 'Built-in palette name. Default: a deterministic pick from the seed.',\n parse: (raw: string) => raw,\n },\n width: {\n flag: 'width',\n placeholder: '<px>',\n describe: `Output width. Default ${DEFAULT_WIDTH}.`,\n parse: positiveInteger('width'),\n },\n height: {\n flag: 'height',\n placeholder: '<px>',\n describe: `Output height. Default ${DEFAULT_HEIGHT}.`,\n parse: positiveInteger('height'),\n },\n quality: {\n flag: 'quality',\n placeholder: '<1-100>',\n describe: `WebP quality. Default ${DEFAULT_WEBP_QUALITY}.`,\n parse: positiveInteger('quality'),\n },\n} satisfies { [K in keyof FlaggedOptions]: FlagSpec<unknown> };\n\nexport const lookFlags = {\n baseNoiseFrequency: {\n flag: 'base-noise-frequency',\n placeholder: '<n>',\n describe: `${lookDescriptions.baseNoiseFrequency} Default ${defaultLook.baseNoiseFrequency}.`,\n parse: finiteNumber('base-noise-frequency'),\n },\n domainWarpStrength: {\n flag: 'domain-warp-strength',\n placeholder: '<n>',\n describe: `${lookDescriptions.domainWarpStrength} Default ${defaultLook.domainWarpStrength}.`,\n parse: finiteNumber('domain-warp-strength'),\n },\n fractalOctaveCount: {\n flag: 'fractal-octave-count',\n placeholder: '<n>',\n describe: `${lookDescriptions.fractalOctaveCount} Default ${defaultLook.fractalOctaveCount}.`,\n parse: positiveInteger('fractal-octave-count'),\n },\n colorTransitionContrast: {\n flag: 'color-transition-contrast',\n placeholder: '<n>',\n describe: `${lookDescriptions.colorTransitionContrast} Default ${defaultLook.colorTransitionContrast}.`,\n parse: finiteNumber('color-transition-contrast'),\n },\n grainOpacity: {\n flag: 'grain-opacity',\n placeholder: '<0-1>',\n describe: `${lookDescriptions.grainOpacity} Default ${defaultLook.grainOpacity}.`,\n parse: finiteNumber('grain-opacity'),\n },\n sweep: {\n flag: 'sweep',\n placeholder: '<0-1>',\n describe: `${lookDescriptions.sweep} Default ${defaultLook.sweep}.`,\n parse: unitInterval('sweep'),\n },\n ground: {\n flag: 'ground',\n placeholder: '<0-1>',\n describe: `${lookDescriptions.ground} Default ${defaultLook.ground}.`,\n parse: unitInterval('ground'),\n },\n} satisfies { [K in keyof GradientLook]: FlagSpec<number> };\n\n/** Flags that exist only in the CLI: file output and terminal presentation. */\nexport const cliOnlyFlags = [\n {\n flag: 'out',\n placeholder: '<path>',\n describe: 'Output file, .webp or .png. Default: output/<palette>-<seed>.webp',\n },\n {\n flag: 'random-palette',\n placeholder: '',\n describe: 'Pick the palette at random instead of deriving it from --seed. The name is printed.',\n },\n {\n flag: 'chaos',\n placeholder: '',\n describe: 'Ignore the palettes: use 3–5 random colors (the same --seed gives the same colors).',\n },\n { flag: 'json', placeholder: '', describe: 'Print result metadata as JSON.' },\n { flag: 'list-palettes', placeholder: '', describe: 'Print built-in palette names and exit.' },\n { flag: 'help', placeholder: '', describe: 'Show this help (-h).' },\n] as const;\n","import {\n findPalette,\n pickPalette,\n validatePalette,\n validatePalettes,\n type Palette,\n} from './palettes';\nimport {\n DEFAULT_HEIGHT,\n DEFAULT_WIDTH,\n generateImage,\n renderCanvas,\n type MeshGradientCanvas,\n type MeshGradientFormatOptions,\n type MeshGradientImage,\n type MeshGradientOptions,\n} from './render';\n\nexport type CreateMeshGradientsOptions<P extends readonly Palette[]> = {\n palettes: P;\n /** Default output width in pixels for this generator. Per-call `width` wins. Defaults to 1200. */\n width?: number;\n /** Default output height in pixels for this generator. Per-call `height` wins. Defaults to 1500. */\n height?: number;\n};\n\nexport type MeshGradients<Name extends string> = {\n palettes: readonly Palette[];\n /** Look up a palette in this set by name. Accepts any string and throws on a miss. */\n findPalette: (name: Name | (string & {})) => Palette;\n /** Deterministically pick a palette in this set from a seed. */\n pickPalette: (seed: string) => Palette;\n /** Render to an `@napi-rs/canvas` canvas, e.g. to draw on top before encoding. */\n render: (options: MeshGradientOptions<Name>) => Promise<MeshGradientCanvas>;\n /** Render and encode to WebP (default) or PNG. */\n generate: (\n options: MeshGradientOptions<Name> & MeshGradientFormatOptions,\n ) => Promise<MeshGradientImage>;\n};\n\n/**\n * Create a generator bound to a palette set and default output size. Palettes\n * and size are validated here, so a bad config fails when the module loads\n * instead of on first render.\n *\n * @example\n * export const gradients = createMeshGradients({\n * palettes: [...colorPalettes, { name: 'brand', colors: ['#f4f4f5', '#8fa579', '#1f2c44'] }],\n * width: 1920,\n * height: 1080,\n * });\n */\nexport function createMeshGradients<const P extends readonly Palette[]>(\n options: CreateMeshGradientsOptions<P>,\n): MeshGradients<P[number]['name']> {\n const { palettes, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT } = options;\n validatePalettes(palettes);\n validateDimension('width', width);\n validateDimension('height', height);\n\n const resolve = (seed: string, palette: Palette | string | undefined): Palette => {\n if (palette === undefined) return pickPalette(seed, palettes);\n if (typeof palette === 'string') return findPalette(palette, palettes);\n validatePalette(palette);\n return palette;\n };\n\n const resolveSize = (size: { width?: number; height?: number }) => {\n const resolved = { width: size.width ?? width, height: size.height ?? height };\n validateDimension('width', resolved.width);\n validateDimension('height', resolved.height);\n return resolved;\n };\n\n return {\n palettes,\n findPalette: (name) => findPalette(name, palettes),\n pickPalette: (seed) => pickPalette(seed, palettes),\n // async so an invalid palette rejects like every other render error.\n render: async (renderOptions) =>\n renderCanvas({\n ...renderOptions,\n ...resolveSize(renderOptions),\n palette: resolve(renderOptions.seed, renderOptions.palette),\n }),\n generate: async (generateOptions) =>\n generateImage({\n ...generateOptions,\n ...resolveSize(generateOptions),\n palette: resolve(generateOptions.seed, generateOptions.palette),\n }),\n };\n}\n\nfunction validateDimension(label: 'width' | 'height', value: number): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`${label} must be a positive integer. Got ${value}.`);\n }\n}\n","import { createMeshGradients } from './create';\nimport { colorPalettes } from './palettes';\n\nexport {\n createMeshGradients,\n type CreateMeshGradientsOptions,\n type MeshGradients,\n} from './create';\nexport {\n colorPalettes,\n complementColor,\n findPalette,\n MAX_PALETTE_COLORS,\n MIN_PALETTE_COLORS,\n paletteAccent,\n paletteComplement,\n pickPalette,\n validatePalette,\n validatePalettes,\n type HexColor,\n type Palette,\n type PaletteColors,\n type PaletteName,\n} from './palettes';\nexport {\n DEFAULT_HEIGHT,\n DEFAULT_WEBP_QUALITY,\n DEFAULT_WIDTH,\n defaultLook,\n lookDescriptions,\n type GradientLook,\n type MeshGradientCanvas,\n type MeshGradientFormatOptions,\n type MeshGradientImage,\n type MeshGradientOptions,\n} from './render';\nexport { createSeededRandom, hashStringToInt } from './random';\n\n/** Generator bound to the built-in palettes. */\nconst builtIn = createMeshGradients({ palettes: colorPalettes });\n\n/** Render and encode a gradient using the built-in palettes. */\nexport const generateMeshGradient = builtIn.generate;\n/** Render a gradient to a canvas using the built-in palettes. */\nexport const renderMeshGradient = builtIn.render;\n"],"mappings":"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","names":["createNoise2D","hexToRgb","smoothStep","createNoise2D","palette"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/render.ts","../src/sweep.ts","../src/random.ts","../src/color.ts","../src/palettes.ts","../src/flags.ts","../src/create.ts","../src/index.ts"],"sourcesContent":["/*\n * cli — the `fluid-gradient` command: parse flags, render one image, write it out.\n *\n * ## behavior\n * - Parser config, help text and the options object are all derived from the\n * tables in `./flags`; this file contributes the wiring, not the flag list.\n * - With no `--seed`, a random one is generated and the palette is derived from\n * it exactly as the API would. Both are printed so any result can be\n * reproduced.\n * - `--random-palette` picks a built-in palette at random even when `--seed` is\n * given, so a fixed composition can be tried in many palettes; the picked name\n * is printed. `--chaos` replaces the palette with 3–5 random hex colors drawn\n * from the seed (`<seed>::chaos`), so the same seed gives the same colors.\n * `--colors` takes the caller's own 3–5 comma-separated hex colors (the `#` is\n * optional) as a palette named `custom`. All four palette sources (`--palette`,\n * `--random-palette`, `--chaos`, `--colors`) are mutually exclusive and throw\n * when combined.\n * - Output format comes from the `--out` extension; anything but `.webp` or\n * `.png` throws.\n *\n * ## constraints\n * - No flag is defined here. Adding one to `parseOptions` or to the help string\n * gives it no `parse`, no validation and no README entry, and the typecheck\n * that pairs flags with render options never sees it — add it to `optionFlags`,\n * `lookFlags` or `cliOnlyFlags` in `./flags` and this file picks it up.\n * - `helpText` is documentation, not just terminal output:\n * `scripts/sync-docs.mjs` splices it into README.md between the\n * `docs:begin:cli-help` markers, and `npm test` runs that script with\n * `--check`. Changing the help layout without running `npm run docs` and\n * committing the README fails the test run.\n * - The `realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)` guard\n * stays. Importing this module must do nothing: the tests import `runCli` and\n * `scripts/sync-docs.mjs` imports `helpText`, so a bare top-level `runCli()`\n * would have both of them render an image and `process.exit` mid-run.\n */\nimport { randomBytes, randomInt } from 'node:crypto';\nimport { mkdirSync, realpathSync, writeFileSync } from 'node:fs';\nimport { dirname, extname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs, type ParseArgsConfig } from 'node:util';\nimport { cliOnlyFlags, lookFlags, optionFlags, type FlagSpec } from './flags';\nimport {\n colorPalettes,\n createSeededRandom,\n findPalette,\n generateMeshGradient,\n hashStringToInt,\n pickPalette,\n validatePalette,\n type Palette,\n type PaletteColors,\n} from './index';\nimport type { GradientLook } from './render';\n\nconst allFlags = [...Object.values(optionFlags), ...Object.values(lookFlags)];\n\n/** Help text, generated from the flag tables so it can't document a flag that doesn't exist. */\nexport const helpText = (() => {\n const width = Math.max(\n ...[...allFlags, ...cliOnlyFlags].map((f) => `--${f.flag} ${f.placeholder}`.trimEnd().length),\n );\n const line = (f: { flag: string; placeholder: string; describe: string }) =>\n ` ${`--${f.flag} ${f.placeholder}`.trimEnd().padEnd(width)} ${f.describe}`;\n return `Usage: fluid-gradient [options]\n\nEvery flag is optional. With none, writes one WebP with a random seed and a\npalette picked from that seed.\n\nOptions:\n${allFlags.map(line).join('\\n')}\n\nOutput & info:\n${cliOnlyFlags.map(line).join('\\n')}\n`;\n})();\n\nconst parseOptions: ParseArgsConfig['options'] = {\n ...Object.fromEntries(allFlags.map((f) => [f.flag, { type: 'string' as const }])),\n // A CLI-only flag with a placeholder takes a value; one without is a switch.\n ...Object.fromEntries(\n cliOnlyFlags.map((f) => [f.flag, { type: f.placeholder ? ('string' as const) : ('boolean' as const) }]),\n ),\n help: { type: 'boolean', short: 'h' },\n};\n\n/** 3–5 uniformly random colors, a pure function of the seed. */\nfunction chaosPalette(seed: string): Palette {\n const random = createSeededRandom(hashStringToInt(`${seed}::chaos`));\n const count = 3 + Math.floor(random() * 3);\n const colors = Array.from({ length: count }, () => {\n const channels = [random(), random(), random()].map((c) =>\n Math.floor(c * 256).toString(16).padStart(2, '0'),\n );\n return `#${channels.join('')}` as const;\n }) as unknown as PaletteColors;\n return { name: 'chaos', colors };\n}\n\n/** `--colors \"#aabbcc, ddeeff, ...\"` → a palette named `custom`, validated with `--colors` errors. */\nfunction customPalette(raw: string): Palette {\n const colors = raw\n .split(',')\n .map((c) => c.trim())\n .filter((c) => c.length > 0)\n .map((c) => (c.startsWith('#') ? c : `#${c}`).toLowerCase()) as unknown as PaletteColors;\n const palette: Palette = { name: 'custom', colors };\n try {\n validatePalette(palette);\n } catch (err) {\n throw new Error((err as Error).message.replace('Palette \"custom\"', '--colors'));\n }\n return palette;\n}\n\n/** What a flag's own `parse` returns, e.g. `width` → number. */\ntype Parsed<K extends keyof typeof optionFlags> = ReturnType<(typeof optionFlags)[K]['parse']>;\n\nexport async function runCli(argv: string[]): Promise<void> {\n // parseArgs' inference is lost through the generated options table, so the\n // flag tables provide the types instead.\n const values = parseArgs({ args: argv, options: parseOptions }).values as Record<\n string,\n string | boolean | undefined\n >;\n const raw = (flag: string) => values[flag] as string | undefined;\n\n if (values.help) {\n console.log(helpText);\n return;\n }\n if (values['list-palettes']) {\n for (const palette of colorPalettes) {\n console.log(`${palette.name} ${palette.colors.join(' ')}`);\n }\n return;\n }\n\n // Read each render option through its own spec, so parsing and validation live\n // with the flag definition rather than here.\n const read = <K extends keyof typeof optionFlags>(key: K): Parsed<K> | undefined => {\n const spec = optionFlags[key] as FlagSpec<Parsed<K>>;\n const value = raw(spec.flag);\n return value === undefined ? undefined : spec.parse(value);\n };\n\n const look: Partial<GradientLook> = {};\n for (const [key, spec] of Object.entries(lookFlags)) {\n const value = raw(spec.flag);\n if (value !== undefined) look[key as keyof GradientLook] = spec.parse(value);\n }\n\n // The seed is random when omitted; the palette then follows from it, exactly\n // as in the API. Both are printed, so any result can be reproduced.\n const seed = read('seed') ?? randomBytes(4).toString('hex');\n const paletteName = read('palette');\n const customColors = raw('colors');\n const sources = (['palette', 'random-palette', 'chaos', 'colors'] as const).filter(\n (flag) => values[flag] !== undefined && values[flag] !== false,\n );\n if (sources.length > 1) {\n throw new Error(`${sources.map((f) => `--${f}`).join(' and ')} cannot be combined.`);\n }\n const palette =\n customColors !== undefined\n ? customPalette(customColors)\n : values.chaos\n ? chaosPalette(seed)\n : values['random-palette']\n ? colorPalettes[randomInt(colorPalettes.length)]\n : paletteName === undefined\n ? pickPalette(seed)\n : findPalette(paletteName);\n const out = raw('out') ?? join('output', `${palette.name}-${seed}.webp`);\n\n const extension = extname(out).toLowerCase();\n if (extension !== '.webp' && extension !== '.png') {\n throw new Error(`--out must end in .webp or .png. Got: ${out}`);\n }\n\n const result = await generateMeshGradient({\n seed,\n palette,\n width: read('width'),\n height: read('height'),\n quality: read('quality'),\n format: extension === '.png' ? 'png' : 'webp',\n ...(Object.keys(look).length > 0 ? { look } : {}),\n });\n\n mkdirSync(dirname(out), { recursive: true });\n writeFileSync(out, result.buffer);\n\n if (values.json) {\n const { buffer, palette: used, ...meta } = result;\n console.log(\n JSON.stringify({ ...meta, palette: used.name, out, bytes: buffer.length }),\n );\n } else {\n console.log(`✓ ${out} (${(result.buffer.length / 1024).toFixed(2)} KB)`);\n console.log(` seed: ${result.seed}`);\n if (values.chaos || customColors !== undefined) {\n console.log(` colors: ${result.palette.colors.join(' ')}`);\n }\n console.log(` palette: ${result.palette.name} (accent: ${result.accent}, complement: ${result.complement})`);\n }\n}\n\n// Only run when executed as a command; importing this module (tests, tooling) does nothing.\nconst invokedPath = process.argv[1];\nif (invokedPath && realpathSync(invokedPath) === fileURLToPath(import.meta.url)) {\n runCli(process.argv.slice(2)).catch((err: Error) => {\n console.error(`Error: ${err.message}`);\n process.exit(1);\n });\n}\n","/*\n * render — the noise pipeline that turns a seed and a palette into pixels.\n *\n * ## behavior\n * - `renderCanvas` seeds one stream from the seed, builds three simplex noise\n * fields from it, domain-warps the third by the first two, and maps the result\n * along the palette ramp; the film grain then draws from the same stream.\n * - `rampPositionAt` is the fluid itself: noise-space point → ramp position.\n * The pixel loop calls it once per pixel. `look.sweep` / `look.ground`\n * (0–1, default 0) also hand it to `applySweep` in `./sweep`, which samples it\n * at moved points (off the canvas too) and rewrites the pixels before grain.\n * At 0/0 that pass doesn't run.\n * - `generateImage` wraps that and encodes: PNG straight off the canvas, WebP\n * through sharp at `DEFAULT_WEBP_QUALITY`.\n * - `lookDescriptions` is the one copy of the per-knob prose; `src/flags.ts`\n * builds CLI help from it and `scripts/sync-docs.mjs` builds the README table.\n *\n * ## constraints\n * - The order values are drawn from `random` is fixed: three `createNoise2D`\n * calls, then one grain value per pixel. Inserting a draw, removing one, or\n * reordering them shifts every pixel of every image — the grain loop is not\n * incidental noise that can be swapped for `Math.random`; it consumes the\n * tail of the same stream. `test/fluid-gradients.test.mjs` compares seed `aspect` /\n * `jewel-peacock` byte-for-byte against artwork already shipped in Aspect.\n * - Sweep and ground must not disturb the default path. The sweep pass draws\n * from its own `<seed>::sweep` stream, never `random`, and grain still comes\n * after it from `random`. Keep `{ sweep: 0, ground: 0 }` identical to omitting them,\n * down to the bytes; the Aspect fixture test checks exactly that.\n * - `defaultLook` and the bare constants in `rampPositionAt` — the `5.2` / `1.3`\n * warp offsets, `amplitude` starting at 0.5 and halving, `frequency`\n * doubling, the `smoothStep` around the contrast term — are the prototype's\n * values, not tuning suggestions. Any of them changes every image rendered\n * without an explicit `look`, which is almost all of them.\n * - Noise is sampled in normalized coordinates (`pixelX / width`), with the\n * aspect ratio applied to X only. This is what makes a 700×800 render the same\n * composition as the 1200×1500 default, which `scripts/generate-examples.mjs`\n * relies on to ship small previews of the real output. Sampling in absolute\n * pixels would make every size a different picture.\n * - `DEFAULT_WEBP_QUALITY` and the plain `sharp(...).webp({ quality })` call are\n * part of the shipped bytes. Adding encoder options (effort, lossless,\n * chroma subsampling) changes the fixture comparison even though nothing about\n * the rendered pixels moved.\n */\nimport { createCanvas, ImageData, type Canvas, type SKRSContext2D } from '@napi-rs/canvas';\nimport sharp from 'sharp';\nimport { createNoise2D } from 'simplex-noise';\nimport { applySweep } from './sweep';\nimport { paletteAccent, paletteComplement, type Palette, type PaletteName } from './palettes';\nimport { createSeededRandom, hashStringToInt } from './random';\n\n/**\n * Knobs that define the gradient's look. The defaults are the exact values from\n * the original prototype; changing them changes every image for a given seed.\n */\nexport type GradientLook = {\n /** Zoom of the noise field. Lower = broader, softer color regions. */\n baseNoiseFrequency: number;\n /** How far the warp noise displaces the color field. */\n domainWarpStrength: number;\n /** Fractal noise octaves. More = finer detail. */\n fractalOctaveCount: number;\n /** Sharpness of transitions between palette stops. */\n colorTransitionContrast: number;\n /** Opacity of the film-grain overlay, 0–1. */\n grainOpacity: number;\n /** Carries color into soft, curving wisps, like dye drifting in water, 0–1. 0 is the plain fluid. */\n sweep: number;\n /** Share of the image opened to the palette's lightest stop as a pale ground, 0–1. */\n ground: number;\n};\n\n/** One-line description per knob. Shared by the CLI help and the generated README table. */\nexport const lookDescriptions: Readonly<Record<keyof GradientLook, string>> = {\n baseNoiseFrequency: 'Lower values give broader, softer color regions.',\n domainWarpStrength: 'How much the color field swirls.',\n fractalOctaveCount: 'More octaves add finer detail.',\n colorTransitionContrast: 'Sharpness of the edges between colors.',\n grainOpacity: 'Strength of the film-grain overlay.',\n sweep: 'Drifts the color into soft, curving wisps, like dye in water or folded silk, 0–1. 0 is the plain fluid.',\n ground: \"Roughly the share of the image, 0–1, left as a pale ground in the palette's lightest color.\",\n};\n\nexport const defaultLook: Readonly<GradientLook> = {\n baseNoiseFrequency: 0.3,\n domainWarpStrength: 1.0,\n fractalOctaveCount: 2,\n colorTransitionContrast: 1.75,\n grainOpacity: 0.09,\n sweep: 0,\n ground: 0,\n};\n\nexport type MeshGradientOptions<Name extends string = PaletteName> = {\n /** Any string. The same seed + palette + size + look always renders the same image. */\n seed: string;\n /** Palette object, palette name, or omitted to pick deterministically from the seed. */\n palette?: Palette | Name;\n /** Pixels. Defaults to 1200. */\n width?: number;\n /** Pixels. Defaults to 1500. */\n height?: number;\n look?: Partial<GradientLook>;\n};\n\nexport type MeshGradientFormatOptions = {\n /** Defaults to 'webp'. */\n format?: 'webp' | 'png';\n /** WebP quality, 1–100. Defaults to 82. */\n quality?: number;\n};\n\n/** Options after the palette has been resolved to an object. */\nexport type ResolvedMeshGradientOptions = Omit<MeshGradientOptions, 'palette'> & {\n palette: Palette;\n};\n\nexport type MeshGradientCanvas = {\n canvas: Canvas;\n palette: Palette;\n /** Mid-tone hex from the palette, handy as a UI accent next to the image. */\n accent: string;\n /** Opposite-hue color built to stand out on the image, for chips and geometry. */\n complement: string;\n};\n\nexport type MeshGradientImage = {\n buffer: Buffer;\n format: 'webp' | 'png';\n width: number;\n height: number;\n seed: string;\n palette: Palette;\n accent: string;\n complement: string;\n};\n\nexport const DEFAULT_WIDTH = 1200;\nexport const DEFAULT_HEIGHT = 1500;\nexport const DEFAULT_WEBP_QUALITY = 82;\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const packed = parseInt(hex.slice(1), 16);\n return [(packed >> 16) & 255, (packed >> 8) & 255, packed & 255];\n}\n\nfunction sampleColorRamp(\n rgbStops: [number, number, number][],\n position: number,\n): [number, number, number] {\n const clamped = Math.max(0, Math.min(1, position));\n const scaled = clamped * (rgbStops.length - 1);\n const lowerIndex = Math.min(rgbStops.length - 2, Math.floor(scaled));\n const blend = scaled - lowerIndex;\n const [lowR, lowG, lowB] = rgbStops[lowerIndex];\n const [highR, highG, highB] = rgbStops[lowerIndex + 1];\n return [\n lowR + (highR - lowR) * blend,\n lowG + (highG - lowG) * blend,\n lowB + (highB - lowB) * blend,\n ];\n}\n\nconst smoothStep = (edge: number) => edge * edge * (3 - 2 * edge);\n\n/**\n * Render a domain-warped noise gradient onto a canvas. The pixel pipeline and\n * the order random values are drawn in are verbatim from the prototype — both\n * must stay fixed for seeds to keep producing the same image.\n */\nexport async function renderCanvas(\n options: ResolvedMeshGradientOptions,\n): Promise<MeshGradientCanvas> {\n const { seed, palette, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT } = options;\n const look = { ...defaultLook, ...options.look };\n for (const knob of ['sweep', 'ground'] as const) {\n if (!(look[knob] >= 0 && look[knob] <= 1)) {\n throw new RangeError(`look.${knob} must be between 0 and 1. Got: ${look[knob]}`);\n }\n }\n\n const random = createSeededRandom(hashStringToInt(seed));\n const rgbStops = palette.colors.map(hexToRgb);\n\n const horizontalWarpNoise = createNoise2D(random);\n const verticalWarpNoise = createNoise2D(random);\n const colorFieldNoise = createNoise2D(random);\n\n const sampleFractalNoise = (\n noise: (x: number, y: number) => number,\n sampleX: number,\n sampleY: number,\n ) => {\n let total = 0;\n let amplitude = 0.5;\n let frequency = 1;\n for (let octave = 0; octave < look.fractalOctaveCount; octave++) {\n total += amplitude * noise(sampleX * frequency, sampleY * frequency);\n amplitude *= 0.5;\n frequency *= 2;\n }\n return total;\n };\n\n const canvas = createCanvas(width, height);\n const context = canvas.getContext('2d');\n const image = context.createImageData(width, height);\n const pixels = image.data;\n const aspectRatio = width / height;\n\n /** Position along the palette ramp (0–1) at a point in noise space. */\n const rampPositionAt = (sampleX: number, sampleY: number) => {\n const horizontalWarpOffset = sampleFractalNoise(horizontalWarpNoise, sampleX, sampleY);\n const verticalWarpOffset = sampleFractalNoise(verticalWarpNoise, sampleX + 5.2, sampleY + 1.3);\n\n const rawFieldValue = sampleFractalNoise(\n colorFieldNoise,\n sampleX + look.domainWarpStrength * horizontalWarpOffset,\n sampleY + look.domainWarpStrength * verticalWarpOffset,\n );\n const normalizedValue = (rawFieldValue + 1) / 2;\n\n return smoothStep(\n Math.max(0, Math.min(1, (normalizedValue - 0.5) * look.colorTransitionContrast + 0.5)),\n );\n };\n\n for (let pixelY = 0; pixelY < height; pixelY++) {\n for (let pixelX = 0; pixelX < width; pixelX++) {\n const sampleX = (pixelX / width) * look.baseNoiseFrequency * aspectRatio;\n const sampleY = (pixelY / height) * look.baseNoiseFrequency;\n const contrastedValue = rampPositionAt(sampleX, sampleY);\n\n const [red, green, blue] = sampleColorRamp(rgbStops, contrastedValue);\n const pixelIndex = (pixelY * width + pixelX) * 4;\n pixels[pixelIndex] = red;\n pixels[pixelIndex + 1] = green;\n pixels[pixelIndex + 2] = blue;\n pixels[pixelIndex + 3] = 255;\n }\n }\n\n if (look.sweep > 0 || look.ground > 0) {\n applySweep({\n seed,\n width,\n height,\n pixels,\n // Height units: the fluid's x runs 0…aspect, y runs 0…1.\n rampPositionAt: (u, v) => rampPositionAt(u * look.baseNoiseFrequency, v * look.baseNoiseFrequency),\n rgbStops,\n sampleRamp: (position) => sampleColorRamp(rgbStops, position),\n sweep: look.sweep,\n ground: look.ground,\n });\n }\n\n context.putImageData(image, 0, 0);\n\n // Film-grain overlay. Draws from the same random stream as the noise above.\n const grainPixels = new Uint8ClampedArray(width * height * 4);\n for (let i = 0; i < width * height; i++) {\n const brightness = Math.floor(random() * 256);\n grainPixels[i * 4] = brightness;\n grainPixels[i * 4 + 1] = brightness;\n grainPixels[i * 4 + 2] = brightness;\n grainPixels[i * 4 + 3] = 255;\n }\n const grainCanvas = createCanvas(width, height);\n grainCanvas.getContext('2d').putImageData(new ImageData(grainPixels, width, height), 0, 0);\n applyOverlay(context, grainCanvas, look.grainOpacity);\n\n return { canvas, palette, accent: paletteAccent(palette), complement: paletteComplement(palette) };\n}\n\nfunction applyOverlay(context: SKRSContext2D, overlay: Canvas, opacity: number) {\n context.save();\n context.globalAlpha = opacity;\n context.globalCompositeOperation = 'overlay';\n context.drawImage(overlay, 0, 0);\n context.restore();\n}\n\n/**\n * Render a gradient and encode it. WebP (the default) is encoded with sharp;\n * PNG comes straight from the canvas.\n */\nexport async function generateImage(\n options: ResolvedMeshGradientOptions & MeshGradientFormatOptions,\n): Promise<MeshGradientImage> {\n const { format = 'webp', quality = DEFAULT_WEBP_QUALITY } = options;\n const { canvas, palette, accent, complement } = await renderCanvas(options);\n const pngBuffer = canvas.encodeSync('png');\n\n let buffer: Buffer = pngBuffer;\n if (format === 'webp') {\n buffer = await sharp(pngBuffer).webp({ quality }).toBuffer();\n }\n\n return {\n buffer,\n format,\n width: canvas.width,\n height: canvas.height,\n seed: options.seed,\n palette,\n accent,\n complement,\n };\n}\n","/*\n * sweep — the opt-in second pass behind `look.sweep` and `look.ground`.\n *\n * \"Sweep\" is dye drifting in slow water, photographed soft: the fluid's color\n * is carried through a large, smooth current, so fast parts of the current\n * pull it into thin curving wisps and slow parts leave soft folds, like silk\n * or a long-exposure flower. \"Ground\" is the pale base the color floats over,\n * in the palette's lightest stop, opened where the fluid is already lightest\n * so it reads as space for text. Neither draws objects, adds fiber texture,\n * or twirls.\n *\n * ## behavior\n * - `applySweep` rewrites the RGB of the fluid pass's pixels in place, before\n * grain. It samples the fluid through `rampPositionAt` (the same function\n * the fluid loop uses, evaluable off the canvas) and maps back through the\n * same ramp, so every color is a ramp color or a blend toward the ground.\n * - Current: the curl of a low-frequency noise (divergence-free, so dye never\n * piles up or thins out), soft-capped below a seeded drift, plus that drift.\n * It slowly turns between two such fields over the trace, which folds the\n * wisps.\n * - Advection: each point is traced backward through the current for\n * `sweep × 1.6` image heights of drift and takes the fluid's color\n * from where it lands. At sweep 0 nothing moves.\n * - Softening: a slight defocus blur, then a line integral convolution along\n * the present current: each point blurs along its own path for one exposure\n * (typical travel `sweep × 0.5` heights, flat-topped window), so wisps blur\n * along their length more than across it, and still water barely blurs.\n * - Ground: coverage is the advected ramp position plus a domain-warped noise\n * sampled at the same advected points; the lowest `ground × 0.9` share of\n * the image becomes the lightest stop through a soft edge, which the\n * softening then carries along.\n * - Everything runs on a working grid at most 400 px tall and is upsampled;\n * about 1 s at 1200×1500.\n *\n * ## constraints\n * - Never draw from the fluid pass's `random`. Everything here comes from its\n * own `<seed>::sweep` stream. One extra draw from the main stream shifts the\n * grain of every image, sweep or not, and breaks the byte-for-byte Aspect\n * fixture. `render.ts` calls in only when `sweep > 0 || ground > 0`.\n * - Keep the eddies slower than the drift (`eddyCap × √2 < 1`). Where an eddy\n * outruns the drift its streamlines close, and the dye winds into bullseye\n * rings — the twirl look that was rejected. A much higher\n * `velocityFrequency` or longer `advectDistance` turns it into marbling.\n * - Alternatives already tried and rejected, so don't drift back to them:\n * painting along curl-noise flow lines (reads as Van Gogh brushwork), fiber\n * or strand feathering (watercolor, fur), and stretching along one camera\n * motion (too linear). The references are smooth, soft photographs.\n * - The velocity grid's margin must cover the farthest a trace can travel.\n * Traces that leave it read a clamped current and draw a hard-edged curve.\n * - The exposure follows the raw velocity, not its direction. Normalizing it\n * flips the blur's direction across every stagnation point, which shows as\n * sharp X-shaped creases.\n * - The constants below are part of the output for any seed rendered with\n * sweep or ground, as `defaultLook` is for the fluid. Retune them only as a\n * deliberate visual change, with before/after renders.\n */\nimport { createNoise2D } from 'simplex-noise';\nimport { createSeededRandom, hashStringToInt } from './random';\n\ntype Rgb = [number, number, number];\n\nexport type SweepInput = {\n seed: string;\n width: number;\n height: number;\n /** RGBA from the fluid pass, rewritten in place. Alpha is left alone. */\n pixels: Uint8ClampedArray;\n /** The fluid's ramp position (0–1) at any point, in height units; defined off the canvas too. */\n rampPositionAt: (u: number, v: number) => number;\n rgbStops: Rgb[];\n sampleRamp: (position: number) => Rgb;\n sweep: number;\n ground: number;\n};\n\n/** Lengths are in image heights, frequencies in cycles per image height. */\nconst tuning = {\n workHeight: 400,\n velocityFrequency: 0.4,\n /** Velocity grid cells per image height. */\n velocityResolution: 80,\n /** Travel at sweep 1, measured at drift speed. */\n advectDistance: 1.6,\n advectSteps: 32,\n /** Drift speed relative to the current's RMS speed. */\n drift: 1.0,\n /** Eddy speed cap, relative to the drift. Two fields mixed at most √2 ×\n * this, which must stay below 1 or eddies can close. */\n eddyCap: 0.65,\n /** Radians the current turns between its two fields over the trace. */\n timeSpin: 1.2,\n defocus: 0.008,\n smearLength: 0.5,\n smearSteps: 40,\n /** Fraction of the exposure at full weight before the window tapers. */\n smearFlat: 0.6,\n groundSoftness: 0.35,\n groundFieldWeight: 1.0,\n groundNoiseWeight: 0.6,\n groundBlur: 0.02,\n groundMaxCover: 0.9,\n groundFrequency: 0.9,\n} as const;\n\nconst smoothStep = (edge: number) => edge * edge * (3 - 2 * edge);\nconst clamp01 = (value: number) => Math.max(0, Math.min(1, value));\n\ntype Noise = (x: number, y: number) => number;\nconst fractal = (noise: Noise, x: number, y: number) =>\n 0.5 * noise(x, y) + 0.25 * noise(x * 2, y * 2);\n\n/** A scalar grid sampled bilinearly in its own pixel coordinates. */\nclass Grid {\n readonly values: Float32Array;\n constructor(\n readonly width: number,\n readonly height: number,\n ) {\n this.values = new Float32Array(width * height);\n }\n sample(x: number, y: number) {\n const gx = Math.max(0, Math.min(this.width - 1.0001, x));\n const gy = Math.max(0, Math.min(this.height - 1.0001, y));\n const x0 = Math.floor(gx);\n const y0 = Math.floor(gy);\n const fx = gx - x0;\n const fy = gy - y0;\n const v = this.values;\n const i = y0 * this.width + x0;\n const top = v[i] + (v[i + 1] - v[i]) * fx;\n const bottom = v[i + this.width] + (v[i + this.width + 1] - v[i + this.width]) * fx;\n return top + (bottom - top) * fy;\n }\n}\n\nexport function applySweep(input: SweepInput) {\n const { seed, width, height, pixels, rampPositionAt, rgbStops, sampleRamp, sweep, ground } = input;\n const t = tuning;\n const random = createSeededRandom(hashStringToInt(`${seed}::sweep`));\n const currentNoiseA = createNoise2D(random);\n const currentNoiseB = createNoise2D(random);\n const coverageNoise = createNoise2D(random);\n const coverageWarpNoise = createNoise2D(random);\n const driftAngle = random() * Math.PI * 2;\n\n // Working grid, in its own pixels; height units are y / workH.\n const scale = Math.min(1, t.workHeight / height);\n const workW = Math.max(2, Math.round(width * scale));\n const workH = Math.max(2, Math.round(height * scale));\n const aspect = width / height;\n\n // ── Current: two curl-noise fields at unit RMS speed, plus the drift. ──\n const driftX = Math.cos(driftAngle) * t.drift;\n const driftY = Math.sin(driftAngle) * t.drift;\n // Speed never exceeds the drift plus √2 × the eddy cap (< 2× drift), and\n // `advectDistance` is measured at drift speed, so 2× covers any trace.\n const margin = 2 * t.advectDistance * sweep + 0.1;\n const cell = 1 / t.velocityResolution;\n const velW = Math.ceil((aspect + 2 * margin) / cell) + 2;\n const velH = Math.ceil((1 + 2 * margin) / cell) + 2;\n const currents = [currentNoiseA, currentNoiseB].map((noise) => {\n const vx = new Grid(velW, velH);\n const vy = new Grid(velW, velH);\n const f = t.velocityFrequency;\n const e = 0.002;\n let total = 0;\n for (let gy = 0; gy < velH; gy++) {\n for (let gx = 0; gx < velW; gx++) {\n const u = (gx * cell - margin) * f;\n const v = (gy * cell - margin) * f;\n const i = gy * velW + gx;\n vx.values[i] = (fractal(noise, u, v + e) - fractal(noise, u, v - e)) / (2 * e);\n vy.values[i] = -(fractal(noise, u + e, v) - fractal(noise, u - e, v)) / (2 * e);\n total += vx.values[i] ** 2 + vy.values[i] ** 2;\n }\n }\n // Unit RMS speed, then a soft cap below the drift: the current then always\n // makes headway along the drift, so no streamline closes into an eddy and\n // nothing stalls.\n const rms = Math.sqrt(total / vx.values.length) || 1;\n const cap = t.eddyCap * t.drift;\n for (let i = 0; i < vx.values.length; i++) {\n const speed = Math.hypot(vx.values[i], vy.values[i]) / rms + 1e-9;\n const limited = (cap * Math.tanh(speed / cap)) / speed / rms;\n vx.values[i] *= limited;\n vy.values[i] *= limited;\n }\n return { vx, vy };\n });\n const velocity = (u: number, v: number, phase: number, out: number[]) => {\n const gx = (u + margin) / cell;\n const gy = (v + margin) / cell;\n const a = Math.cos(phase);\n const b = Math.sin(phase);\n out[0] = a * currents[0].vx.sample(gx, gy) + b * currents[1].vx.sample(gx, gy) + driftX;\n out[1] = a * currents[0].vy.sample(gx, gy) + b * currents[1].vy.sample(gx, gy) + driftY;\n };\n\n // ── Advection: trace each point back to where its dye came from. ──\n const flowX = new Grid(workW, workH);\n const flowY = new Grid(workW, workH);\n const fieldSource = new Grid(workW, workH);\n const sourceU = new Float32Array(workW * workH);\n const sourceV = new Float32Array(workW * workH);\n const steps = t.advectSteps;\n const dt = (sweep * t.advectDistance) / t.drift / steps;\n const k1 = [0, 0];\n const k2 = [0, 0];\n for (let y = 0; y < workH; y++) {\n for (let x = 0; x < workW; x++) {\n let u = x / workH;\n let v = y / workH;\n const i = y * workW + x;\n velocity(u, v, 0, k1);\n flowX.values[i] = k1[0];\n flowY.values[i] = k1[1];\n for (let s = 0; s < steps; s++) {\n const phase = (t.timeSpin * s) / steps;\n velocity(u, v, phase, k1);\n velocity(u - (k1[0] * dt) / 2, v - (k1[1] * dt) / 2, phase, k2);\n u -= k2[0] * dt;\n v -= k2[1] * dt;\n }\n sourceU[i] = u;\n sourceV[i] = v;\n fieldSource.values[i] = rampPositionAt(u, v);\n }\n }\n boxBlur(fieldSource.values, workW, workH, Math.max(1, Math.round(t.defocus * sweep * workH)), 3);\n\n // ── Ground: opens first where the fluid is already near the light end. ──\n const coverageSource = new Grid(workW, workH);\n if (ground > 0) {\n for (let y = 0; y < workH; y++) {\n for (let x = 0; x < workW; x++) {\n const i = y * workW + x;\n const su = sourceU[i] * t.groundFrequency;\n const sv = sourceV[i] * t.groundFrequency;\n const warpX = fractal(coverageWarpNoise, su, sv);\n const warpY = fractal(coverageWarpNoise, su + 5.2, sv + 1.3);\n coverageSource.values[i] =\n t.groundFieldWeight * fieldSource.values[i] + t.groundNoiseWeight * fractal(coverageNoise, su + warpX, sv + warpY);\n }\n }\n const sorted = Float32Array.from(coverageSource.values).sort();\n const threshold = sorted[Math.min(sorted.length - 1, Math.floor(ground * t.groundMaxCover * sorted.length))];\n for (let i = 0; i < coverageSource.values.length; i++) coverageSource.values[i] -= threshold;\n boxBlur(coverageSource.values, workW, workH, Math.max(1, Math.round(t.groundBlur * workH)), 3);\n }\n\n // ── Exposure: average along the present current (line integral convolution). ──\n const smearedField = new Grid(workW, workH);\n const smearedCoverage = new Grid(workW, workH);\n // Blur is speed × exposure time, so it fades to nothing where the current\n // stalls; a fixed length there would flip direction between neighbors and\n // draw a hard crease. Exposure time is set so typical travel is the length.\n const smearSteps = t.smearSteps;\n const step = (sweep * t.smearLength * workH) / 2 / Math.hypot(1, t.drift) / smearSteps;\n const weights = new Float32Array(smearSteps + 1);\n for (let s = 0; s <= smearSteps; s++) {\n const along = s / (smearSteps + 1);\n weights[s] = along <= t.smearFlat ? 1 : 0.5 + 0.5 * Math.cos((Math.PI * (along - t.smearFlat)) / (1 - t.smearFlat));\n }\n for (let y = 0; y < workH; y++) {\n for (let x = 0; x < workW; x++) {\n let fieldTotal = weights[0] * fieldSource.sample(x, y);\n let coverageTotal = ground > 0 ? weights[0] * coverageSource.sample(x, y) : 0;\n let weightTotal = weights[0];\n for (let direction = -1; direction <= 1; direction += 2) {\n let px = x;\n let py = y;\n for (let s = 1; s <= smearSteps; s++) {\n const mx = px + (direction * flowX.sample(px, py) * step) / 2;\n const my = py + (direction * flowY.sample(px, py) * step) / 2;\n px += direction * flowX.sample(mx, my) * step;\n py += direction * flowY.sample(mx, my) * step;\n if (px < 0 || py < 0 || px > workW - 1 || py > workH - 1) break;\n const w = weights[s];\n fieldTotal += w * fieldSource.sample(px, py);\n if (ground > 0) coverageTotal += w * coverageSource.sample(px, py);\n weightTotal += w;\n }\n }\n smearedField.values[y * workW + x] = fieldTotal / weightTotal;\n smearedCoverage.values[y * workW + x] = coverageTotal / weightTotal;\n }\n }\n\n // ── Compose at full resolution. ──\n const groundColor = rgbStops[0];\n for (let py = 0; py < height; py++) {\n for (let px = 0; px < width; px++) {\n const sx = (px + 0.5) * scale - 0.5;\n const sy = (py + 0.5) * scale - 0.5;\n let [r, g, b] = sampleRamp(smearedField.sample(sx, sy));\n if (ground > 0) {\n const m = smoothStep(clamp01(smearedCoverage.sample(sx, sy) / t.groundSoftness + 0.5));\n r = groundColor[0] + (r - groundColor[0]) * m;\n g = groundColor[1] + (g - groundColor[1]) * m;\n b = groundColor[2] + (b - groundColor[2]) * m;\n }\n const o = (py * width + px) * 4;\n pixels[o] = r;\n pixels[o + 1] = g;\n pixels[o + 2] = b;\n }\n }\n}\n\nfunction boxBlur(values: Float32Array, width: number, height: number, radius: number, passes: number) {\n const scratch = new Float32Array(values.length);\n for (let pass = 0; pass < passes; pass++) {\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n let total = 0;\n for (let k = -radius; k <= radius; k++) total += values[y * width + Math.max(0, Math.min(width - 1, x + k))];\n scratch[y * width + x] = total / (2 * radius + 1);\n }\n }\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n let total = 0;\n for (let k = -radius; k <= radius; k++) total += scratch[Math.max(0, Math.min(height - 1, y + k)) * width + x];\n values[y * width + x] = total / (2 * radius + 1);\n }\n }\n }\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 * 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 * 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 { name: 'ember-mono', colors: ['#ff9a6b', '#cf5604', '#632400', '#180501'] },\n\n // ── Monochromatic: one hue, lightness stepping down (OKLCH-spaced) ──\n { name: 'tangerine-mono', colors: ['#ffeee4', '#feb079', '#dc7100', '#824103'] },\n { name: 'electric-blue-mono', colors: ['#ebf2fe', '#a4c6fc', '#5d91e7', '#2a5397'] },\n { name: 'magenta-mono', colors: ['#feecf5', '#edaed1', '#cd6ca6', '#823564'] },\n { name: 'emerald-mono', colors: ['#e2f8ea', '#95d5af', '#2cab70', '#03663e'] },\n { name: 'violet-mono', colors: ['#f2f0fe', '#c8b7ff', '#9e71fd', '#5f38a7'] },\n { name: 'crimson-mono', colors: ['#ffedec', '#feaaa9', '#f44f5d', '#9e122b'] },\n { name: 'aqua-mono', colors: ['#dff7f8', '#8dd2d6', '#39a4a9', '#026266'] },\n\n { name: 'burgundy-mono', colors: ['#fbe0e4', '#dc9ea8', '#a7415a', '#560c24'] },\n { name: 'indigo-mono', colors: ['#eaeefe', '#a7b2f7', '#5a5ce2', '#261b81'] },\n { name: 'lime-mono', colors: ['#edfcd7', '#c2e87d', '#9fc93e', '#638119'] },\n\n // ── Monochromatic earth tones: same idea, chroma held up so they stay\n // clay / ochre / moss rather than collapsing to greige ──\n { name: 'terracotta-mono', colors: ['#fdeae4', '#f6b19b', '#c5694c', '#73311b'] },\n { name: 'ochre-mono', colors: ['#f9edd5', '#dec082', '#a87f09', '#5b4404'] },\n { name: 'olive-mono', colors: ['#edf2d7', '#c2cb91', '#848e38', '#474d04'] },\n { name: 'fern-mono', colors: ['#e3f5df', '#a9d1a0', '#5f9752', '#2a5320'] },\n { name: 'verdigris-mono', colors: ['#d6f7f1', '#96d2c9', '#38988d', '#07534c'] },\n { name: 'umber-mono', colors: ['#feeada', '#e1bc9d', '#ab7a4f', '#623f1e'] },\n { name: 'adobe-mono', colors: ['#fee9e8', '#eeb3b0', '#bb6d6b', '#6c3434'] },\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 { name: 'silver-steel', colors: ['#e2e5e8', '#c4c9cf', '#9aa2ac', '#646f7d'] },\n // ── Airy: a near-white base with soft color splashes, in the style of\n // blurred editorial gradients (light stops first, then the splash hues) ──\n { name: 'petal-bloom', colors: ['#e4eeff', '#bcd2ff', '#dfa6e9', '#fa787b', '#fba96a'] },\n { name: 'tulip-blur', colors: ['#e6f1f6', '#a8d4e8', '#f1c4d0', '#ee9a6e', '#f4b81c'] },\n { name: 'silk-rose', colors: ['#fcedf4', '#f2cfdd', '#f0c1b8', '#d68ba5', '#b07a9f'] },\n\n // ── Flags: sampled from heraldic flag art ──\n { name: 'sultans-eagle', colors: ['#f2da38', '#e3c42a', '#c9a21d', '#a1353a', '#6a1c26'] },\n { name: 'zhuge-nu', colors: ['#f0f3ee', '#dfc652', '#4fa79b', '#1f8283', '#0f5252'] },\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","/*\n * flags — the single source of truth for every CLI flag.\n *\n * ## behavior\n * - `optionFlags` and `lookFlags` each map one render option to one long flag,\n * with the parser/validator for that flag's value living beside it.\n * - `cliOnlyFlags` holds the flags with no API counterpart: file output,\n * terminal presentation, and the palette-source switches (`--random-palette`,\n * `--chaos`, `--colors`), which the CLI resolves into an ordinary `palette`\n * before calling the API. An entry with an empty `placeholder` is a boolean\n * switch; one with a placeholder takes a string value.\n * - `src/cli.ts` builds its `parseArgs` config, its help text and its options\n * object from these tables. `format` is absent on purpose — it comes from the\n * `--out` extension.\n *\n * ## constraints\n * - The two `satisfies { [K in keyof ...]: FlagSpec<...> }` clauses are the\n * whole point of the file, not annotations. They make adding a render option\n * to `MeshGradientOptions` or a knob to `GradientLook` fail `npm run lint`\n * until it has a flag here, which is what keeps `fluid-gradient --foo` and\n * `generateMeshGradient({ foo })` from drifting apart. Deleting one to quiet\n * an error removes the check that was reporting it.\n * - Flag definitions live here and nowhere else. A flag added directly to the\n * parser or the help string in `src/cli.ts` gets no `parse`, no typecheck and\n * no README entry; add it to a table here instead and the CLI picks it up.\n * - `describe` strings are user-facing docs: they are rendered into the help\n * text and from there into README.md by `scripts/sync-docs.mjs`. Editing one\n * without running `npm run docs` fails `npm test`, which runs that script with\n * `--check`.\n */\nimport type { GradientLook, MeshGradientFormatOptions, MeshGradientOptions } from './render';\nimport { DEFAULT_HEIGHT, DEFAULT_WEBP_QUALITY, DEFAULT_WIDTH, defaultLook, lookDescriptions } from './render';\n\n/** One flag: how it is written, how it is described, and how its value parses. */\nexport type FlagSpec<Value> = {\n /** Long flag name, used as `--<flag>`. */\n flag: string;\n /** Shown in help, e.g. `<px>`. */\n placeholder: string;\n describe: string;\n parse: (raw: string) => Value;\n};\n\nconst positiveInteger = (flag: string) => (raw: string) => {\n const value = Number(raw);\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`--${flag} must be a positive integer. Got: ${raw}`);\n }\n return value;\n};\n\nconst finiteNumber = (flag: string) => (raw: string) => {\n const value = Number(raw);\n if (!Number.isFinite(value)) throw new Error(`--${flag} must be a number. Got: ${raw}`);\n return value;\n};\n\nconst unitInterval = (flag: string) => (raw: string) => {\n const value = Number(raw);\n if (raw.trim() === '' || !Number.isFinite(value) || value < 0 || value > 1) {\n throw new Error(`--${flag} must be a number from 0 to 1. Got: ${raw}`);\n }\n return value;\n};\n\n/**\n * Render options the CLI maps one-to-one. `format` is excluded because it comes\n * from the --out extension, and `look` has its own table below.\n */\ntype FlaggedOptions = Omit<\n Required<MeshGradientOptions & MeshGradientFormatOptions>,\n 'look' | 'format'\n>;\n\nexport const optionFlags = {\n seed: {\n flag: 'seed',\n placeholder: '<string>',\n describe: 'Seed string. Same seed → same image. Default: random.',\n parse: (raw: string) => raw,\n },\n palette: {\n flag: 'palette',\n placeholder: '<name>',\n describe: 'Built-in palette name. Default: a deterministic pick from the seed.',\n parse: (raw: string) => raw,\n },\n width: {\n flag: 'width',\n placeholder: '<px>',\n describe: `Output width. Default ${DEFAULT_WIDTH}.`,\n parse: positiveInteger('width'),\n },\n height: {\n flag: 'height',\n placeholder: '<px>',\n describe: `Output height. Default ${DEFAULT_HEIGHT}.`,\n parse: positiveInteger('height'),\n },\n quality: {\n flag: 'quality',\n placeholder: '<1-100>',\n describe: `WebP quality. Default ${DEFAULT_WEBP_QUALITY}.`,\n parse: positiveInteger('quality'),\n },\n} satisfies { [K in keyof FlaggedOptions]: FlagSpec<unknown> };\n\nexport const lookFlags = {\n baseNoiseFrequency: {\n flag: 'base-noise-frequency',\n placeholder: '<n>',\n describe: `${lookDescriptions.baseNoiseFrequency} Default ${defaultLook.baseNoiseFrequency}.`,\n parse: finiteNumber('base-noise-frequency'),\n },\n domainWarpStrength: {\n flag: 'domain-warp-strength',\n placeholder: '<n>',\n describe: `${lookDescriptions.domainWarpStrength} Default ${defaultLook.domainWarpStrength}.`,\n parse: finiteNumber('domain-warp-strength'),\n },\n fractalOctaveCount: {\n flag: 'fractal-octave-count',\n placeholder: '<n>',\n describe: `${lookDescriptions.fractalOctaveCount} Default ${defaultLook.fractalOctaveCount}.`,\n parse: positiveInteger('fractal-octave-count'),\n },\n colorTransitionContrast: {\n flag: 'color-transition-contrast',\n placeholder: '<n>',\n describe: `${lookDescriptions.colorTransitionContrast} Default ${defaultLook.colorTransitionContrast}.`,\n parse: finiteNumber('color-transition-contrast'),\n },\n grainOpacity: {\n flag: 'grain-opacity',\n placeholder: '<0-1>',\n describe: `${lookDescriptions.grainOpacity} Default ${defaultLook.grainOpacity}.`,\n parse: finiteNumber('grain-opacity'),\n },\n sweep: {\n flag: 'sweep',\n placeholder: '<0-1>',\n describe: `${lookDescriptions.sweep} Default ${defaultLook.sweep}.`,\n parse: unitInterval('sweep'),\n },\n ground: {\n flag: 'ground',\n placeholder: '<0-1>',\n describe: `${lookDescriptions.ground} Default ${defaultLook.ground}.`,\n parse: unitInterval('ground'),\n },\n} satisfies { [K in keyof GradientLook]: FlagSpec<number> };\n\n/** Flags that exist only in the CLI: file output and terminal presentation. */\nexport const cliOnlyFlags = [\n {\n flag: 'out',\n placeholder: '<path>',\n describe: 'Output file, .webp or .png. Default: output/<palette>-<seed>.webp',\n },\n {\n flag: 'random-palette',\n placeholder: '',\n describe: 'Pick the palette at random instead of deriving it from --seed. The name is printed.',\n },\n {\n flag: 'chaos',\n placeholder: '',\n describe: 'Ignore the palettes: use 3–5 random colors (the same --seed gives the same colors).',\n },\n {\n flag: 'colors',\n placeholder: '<hex,...>',\n describe: 'Use your own 3–5 colors, light → dark, comma-separated: \"#fdf6ee,#f2c4ce,#3f6fd8\". The # is optional.',\n },\n { flag: 'json', placeholder: '', describe: 'Print result metadata as JSON.' },\n { flag: 'list-palettes', placeholder: '', describe: 'Print built-in palette names and exit.' },\n { flag: 'help', placeholder: '', describe: 'Show this help (-h).' },\n] as const;\n","import {\n findPalette,\n pickPalette,\n validatePalette,\n validatePalettes,\n type Palette,\n} from './palettes';\nimport {\n DEFAULT_HEIGHT,\n DEFAULT_WIDTH,\n generateImage,\n renderCanvas,\n type MeshGradientCanvas,\n type MeshGradientFormatOptions,\n type MeshGradientImage,\n type MeshGradientOptions,\n} from './render';\n\nexport type CreateMeshGradientsOptions<P extends readonly Palette[]> = {\n palettes: P;\n /** Default output width in pixels for this generator. Per-call `width` wins. Defaults to 1200. */\n width?: number;\n /** Default output height in pixels for this generator. Per-call `height` wins. Defaults to 1500. */\n height?: number;\n};\n\nexport type MeshGradients<Name extends string> = {\n palettes: readonly Palette[];\n /** Look up a palette in this set by name. Accepts any string and throws on a miss. */\n findPalette: (name: Name | (string & {})) => Palette;\n /** Deterministically pick a palette in this set from a seed. */\n pickPalette: (seed: string) => Palette;\n /** Render to an `@napi-rs/canvas` canvas, e.g. to draw on top before encoding. */\n render: (options: MeshGradientOptions<Name>) => Promise<MeshGradientCanvas>;\n /** Render and encode to WebP (default) or PNG. */\n generate: (\n options: MeshGradientOptions<Name> & MeshGradientFormatOptions,\n ) => Promise<MeshGradientImage>;\n};\n\n/**\n * Create a generator bound to a palette set and default output size. Palettes\n * and size are validated here, so a bad config fails when the module loads\n * instead of on first render.\n *\n * @example\n * export const gradients = createMeshGradients({\n * palettes: [...colorPalettes, { name: 'brand', colors: ['#f4f4f5', '#8fa579', '#1f2c44'] }],\n * width: 1920,\n * height: 1080,\n * });\n */\nexport function createMeshGradients<const P extends readonly Palette[]>(\n options: CreateMeshGradientsOptions<P>,\n): MeshGradients<P[number]['name']> {\n const { palettes, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT } = options;\n validatePalettes(palettes);\n validateDimension('width', width);\n validateDimension('height', height);\n\n const resolve = (seed: string, palette: Palette | string | undefined): Palette => {\n if (palette === undefined) return pickPalette(seed, palettes);\n if (typeof palette === 'string') return findPalette(palette, palettes);\n validatePalette(palette);\n return palette;\n };\n\n const resolveSize = (size: { width?: number; height?: number }) => {\n const resolved = { width: size.width ?? width, height: size.height ?? height };\n validateDimension('width', resolved.width);\n validateDimension('height', resolved.height);\n return resolved;\n };\n\n return {\n palettes,\n findPalette: (name) => findPalette(name, palettes),\n pickPalette: (seed) => pickPalette(seed, palettes),\n // async so an invalid palette rejects like every other render error.\n render: async (renderOptions) =>\n renderCanvas({\n ...renderOptions,\n ...resolveSize(renderOptions),\n palette: resolve(renderOptions.seed, renderOptions.palette),\n }),\n generate: async (generateOptions) =>\n generateImage({\n ...generateOptions,\n ...resolveSize(generateOptions),\n palette: resolve(generateOptions.seed, generateOptions.palette),\n }),\n };\n}\n\nfunction validateDimension(label: 'width' | 'height', value: number): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`${label} must be a positive integer. Got ${value}.`);\n }\n}\n","import { createMeshGradients } from './create';\nimport { colorPalettes } from './palettes';\n\nexport {\n createMeshGradients,\n type CreateMeshGradientsOptions,\n type MeshGradients,\n} from './create';\nexport {\n colorPalettes,\n complementColor,\n findPalette,\n MAX_PALETTE_COLORS,\n MIN_PALETTE_COLORS,\n paletteAccent,\n paletteComplement,\n pickPalette,\n validatePalette,\n validatePalettes,\n type HexColor,\n type Palette,\n type PaletteColors,\n type PaletteName,\n} from './palettes';\nexport {\n DEFAULT_HEIGHT,\n DEFAULT_WEBP_QUALITY,\n DEFAULT_WIDTH,\n defaultLook,\n lookDescriptions,\n type GradientLook,\n type MeshGradientCanvas,\n type MeshGradientFormatOptions,\n type MeshGradientImage,\n type MeshGradientOptions,\n} from './render';\nexport { createSeededRandom, hashStringToInt } from './random';\n\n/** Generator bound to the built-in palettes. */\nconst builtIn = createMeshGradients({ palettes: colorPalettes });\n\n/** Render and encode a gradient using the built-in palettes. */\nexport const generateMeshGradient = builtIn.generate;\n/** Render a gradient to a canvas using the built-in palettes. */\nexport const renderMeshGradient = builtIn.render;\n"],"mappings":"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","names":["createNoise2D","hexToRgb","smoothStep","createNoise2D","palette"]}
|
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@ const contrastChip = paletteComplement(palette); // opposite hue, vivid — stan
|
|
|
40
40
|
## Quick reference
|
|
41
41
|
|
|
42
42
|
- **Custom palettes or a project-wide default size:** `createMeshGradients({ palettes, width, height })` in one module, exported and reused.
|
|
43
|
-
- **Browse palettes:** `npx fluid-gradient --list-palettes`; one-off images: `npx fluid-gradient --seed x --palette y`. `--random-palette` keeps `--seed` but picks the palette at random; `--chaos` uses random colors (deterministic per seed).
|
|
43
|
+
- **Browse palettes:** `npx fluid-gradient --list-palettes`; one-off images: `npx fluid-gradient --seed x --palette y`. `--random-palette` keeps `--seed` but picks the palette at random; `--chaos` uses random colors (deterministic per seed); `--colors "#fdf6ee,#f2c4ce,#3f6fd8"` uses your own 3–5 colors, light → dark.
|
|
44
44
|
- **Draw on top before encoding:** `renderMeshGradient` returns the canvas.
|
|
45
45
|
|
|
46
46
|
## Common mistakes
|