@dooph-software/fluid-gradients 1.0.0

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