@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,261 @@
1
+ /**
2
+ * The complement of a `#rrggbb` color, built to stand out: the opposite OKLCH
3
+ * hue at its most saturated readable lightness. Grays get the opposite-lightness gray.
4
+ */
5
+ declare function complementColor(hex: string): string;
6
+
7
+ declare const MIN_PALETTE_COLORS = 3;
8
+ declare const MAX_PALETTE_COLORS = 5;
9
+ /** `#rrggbb`. The type only checks the `#`; the 6 hex digits are checked at runtime. */
10
+ type HexColor = `#${string}`;
11
+ /** A color ramp of 3–5 stops, light → dark. */
12
+ type PaletteColors = readonly [HexColor, HexColor, HexColor] | readonly [HexColor, HexColor, HexColor, HexColor] | readonly [HexColor, HexColor, HexColor, HexColor, HexColor];
13
+ type Palette = {
14
+ /** Unique within a palette set. Seed-based picks are keyed on it, so renaming reshuffles. */
15
+ name: string;
16
+ colors: PaletteColors;
17
+ };
18
+ declare const colorPalettes: readonly [{
19
+ readonly name: "amber-latte";
20
+ readonly colors: readonly ["#faf4ea", "#f2d29b", "#e0a45c", "#b9722f"];
21
+ }, {
22
+ readonly name: "terracotta-sand";
23
+ readonly colors: readonly ["#f6ede1", "#e8c9a0", "#cf8f63", "#9c4e33"];
24
+ }, {
25
+ readonly name: "peach-blush";
26
+ readonly colors: readonly ["#fdf1ec", "#f8cbb6", "#ef9a8a", "#d76e73"];
27
+ }, {
28
+ readonly name: "coral-cream";
29
+ readonly colors: readonly ["#fbe9df", "#f5b79a", "#e8735a", "#c33f36"];
30
+ }, {
31
+ readonly name: "honey-espresso";
32
+ readonly colors: readonly ["#f7ecd9", "#e5b877", "#b9793f", "#5f3418"];
33
+ }, {
34
+ readonly name: "apricot-plum";
35
+ readonly colors: readonly ["#fdf0e6", "#f6c79a", "#e58b6f", "#7d4a6b"];
36
+ }, {
37
+ readonly name: "golden-hour";
38
+ readonly colors: readonly ["#fdf3e1", "#f7cf8a", "#e89a5a", "#9a4a3a"];
39
+ }, {
40
+ readonly name: "vermilion-blush";
41
+ readonly colors: readonly ["#fbe8e2", "#f7c6a8", "#e4562f", "#a82a1c"];
42
+ }, {
43
+ readonly name: "ivory-gilt";
44
+ readonly colors: readonly ["#fffdf7", "#f7ecd0", "#e0c47e", "#b08c3a"];
45
+ }, {
46
+ readonly name: "rose-gold";
47
+ readonly colors: readonly ["#fceee8", "#f2cdbd", "#dba18c", "#b3705c"];
48
+ }, {
49
+ readonly name: "peony-glow";
50
+ readonly colors: readonly ["#fdeef3", "#f9b3c8", "#f78a5c", "#f2a93f"];
51
+ }, {
52
+ readonly name: "lavender-rose";
53
+ readonly colors: readonly ["#f4eefb", "#d9c2ef", "#c98fb8", "#8a5fd0"];
54
+ }, {
55
+ readonly name: "violet-mist";
56
+ readonly colors: readonly ["#f1eefb", "#c9b8ef", "#8f7fe0", "#4a3d9c"];
57
+ }, {
58
+ readonly name: "mauve-plum";
59
+ readonly colors: readonly ["#f6f0f2", "#d8b8c6", "#a5738c", "#5f3a55"];
60
+ }, {
61
+ readonly name: "periwinkle-blush";
62
+ readonly colors: readonly ["#f0f0fb", "#c3c6f0", "#cf9fc6", "#6a5fd0"];
63
+ }, {
64
+ readonly name: "orchid-frost";
65
+ readonly colors: readonly ["#f6eff6", "#e0c2e0", "#b98fc4", "#7a5aa6", "#3f2f5c"];
66
+ }, {
67
+ readonly name: "quartz-rose";
68
+ readonly colors: readonly ["#faf1f1", "#ecc9cc", "#c99aa3", "#8a5f6a"];
69
+ }, {
70
+ readonly name: "wisteria-sky";
71
+ readonly colors: readonly ["#f2f0fb", "#cfc6ef", "#9fb2e6", "#4f6aa6"];
72
+ }, {
73
+ readonly name: "zephyr-dawn";
74
+ readonly colors: readonly ["#fdf3f5", "#f2d6e2", "#c6d6f2", "#7f9fd8"];
75
+ }, {
76
+ readonly name: "candy-sky";
77
+ readonly colors: readonly ["#f6e8ff", "#f7a3e8", "#9aa8f5", "#3fa0f5"];
78
+ }, {
79
+ readonly name: "sky-slate";
80
+ readonly colors: readonly ["#eef4fb", "#bcd2ef", "#6f8fc4", "#2f3f66"];
81
+ }, {
82
+ readonly name: "teal-fog";
83
+ readonly colors: readonly ["#eef7f6", "#b6dcd8", "#5fa39c", "#234a49"];
84
+ }, {
85
+ readonly name: "arctic-ink";
86
+ readonly colors: readonly ["#f2f6f8", "#cddbe2", "#7f96a3", "#1c2630"];
87
+ }, {
88
+ readonly name: "cloud-navy";
89
+ readonly colors: readonly ["#f0f3f7", "#c6d1de", "#7488a3", "#1f2c44"];
90
+ }, {
91
+ readonly name: "denim-clay";
92
+ readonly colors: readonly ["#eef2f6", "#aebfd0", "#5f7794", "#b06a4e"];
93
+ }, {
94
+ readonly name: "cobalt-ice";
95
+ readonly colors: readonly ["#e8eeff", "#b9cdfb", "#2f4fd8", "#1836b8"];
96
+ }, {
97
+ readonly name: "sage-mist";
98
+ readonly colors: readonly ["#f2f5ee", "#cdd8bd", "#8fa579", "#4a5c3a"];
99
+ }, {
100
+ readonly name: "mint-sea";
101
+ readonly colors: readonly ["#eef8f4", "#b6e2d2", "#5fb39c", "#1f5c4d"];
102
+ }, {
103
+ readonly name: "olive-cream";
104
+ readonly colors: readonly ["#f6f3e6", "#dbd09b", "#a39a5c", "#5f5a2f"];
105
+ }, {
106
+ readonly name: "eucalyptus-fog";
107
+ readonly colors: readonly ["#eff4f1", "#cbdcd2", "#94b3a6", "#4f6b60"];
108
+ }, {
109
+ readonly name: "kelp-forest";
110
+ readonly colors: readonly ["#eef3ea", "#a9c49a", "#4f7a5a", "#1f3a2f"];
111
+ }, {
112
+ readonly name: "yuzu-mint";
113
+ readonly colors: readonly ["#fbfbe6", "#eef29a", "#b3d98a", "#3f8f7a"];
114
+ }, {
115
+ readonly name: "lime-lagoon";
116
+ readonly colors: readonly ["#f8ffe8", "#c8f56a", "#6fe3d8", "#2fc6d8"];
117
+ }, {
118
+ readonly name: "bliss-meadow";
119
+ readonly colors: readonly ["#f4fbff", "#8ed0f2", "#6cb03a", "#2d6b22"];
120
+ }, {
121
+ readonly name: "periwinkle-gold";
122
+ readonly colors: readonly ["#dfe6ff", "#a9b6f0", "#5b6bd6", "#e6c25a"];
123
+ }, {
124
+ readonly name: "lilac-lemon";
125
+ readonly colors: readonly ["#f3eefb", "#d3c2ef", "#9f8fd0", "#e6d25a"];
126
+ }, {
127
+ readonly name: "blush-teal";
128
+ readonly colors: readonly ["#fbeee9", "#f2c2b0", "#d78a8f", "#3f9c94"];
129
+ }, {
130
+ readonly name: "sage-coral";
131
+ readonly colors: readonly ["#f2f5ee", "#cdd8bd", "#8fa579", "#e0755a"];
132
+ }, {
133
+ readonly name: "slate-amber";
134
+ readonly colors: readonly ["#eef2f6", "#bcc9d8", "#5f7794", "#e0a24e"];
135
+ }, {
136
+ readonly name: "ultramarine-sand";
137
+ readonly colors: readonly ["#dfe8ff", "#e6c27a", "#4757dd", "#232a9c"];
138
+ }, {
139
+ readonly name: "amber-tide";
140
+ readonly colors: readonly ["#eef4f8", "#a9c8ef", "#f2b96b", "#df8a33"];
141
+ }, {
142
+ readonly name: "sunset-rose";
143
+ readonly colors: readonly ["#fce9d6", "#f6a95c", "#e8617a", "#8a3d7a"];
144
+ }, {
145
+ readonly name: "berry-bloom";
146
+ readonly colors: readonly ["#fbe9f2", "#e88fb8", "#c94f8f", "#7a2f6a"];
147
+ }, {
148
+ readonly name: "jewel-peacock";
149
+ readonly colors: readonly ["#e6f5f0", "#4fb3a6", "#1f7a99", "#243f8a"];
150
+ }, {
151
+ readonly name: "iris-meadow";
152
+ readonly colors: readonly ["#eaf5ec", "#7cc48f", "#3f8fb3", "#5f4fb0"];
153
+ }, {
154
+ readonly name: "autumn-spice";
155
+ readonly colors: readonly ["#f7ecd6", "#dba24f", "#b5522f", "#6e2f4a"];
156
+ }, {
157
+ readonly name: "ultraviolet-sea";
158
+ readonly colors: readonly ["#e9f0fb", "#6f9fe0", "#4a5fc4", "#6a3da6"];
159
+ }, {
160
+ readonly name: "purple-rain";
161
+ readonly colors: readonly ["#f1ebf7", "#b69ad6", "#6f3fa8", "#2a1740"];
162
+ }, {
163
+ readonly name: "nectarine-fizz";
164
+ readonly colors: readonly ["#fff1e6", "#ffc59a", "#ff8a5c", "#d9485f"];
165
+ }, {
166
+ readonly name: "raspberry-cream";
167
+ readonly colors: readonly ["#fdeef1", "#f4a9bd", "#d9537a", "#8a1f45"];
168
+ }, {
169
+ readonly name: "pastel-confetti";
170
+ readonly colors: readonly ["#ffffff", "#f9f3cf", "#cbbcf2", "#e8798f"];
171
+ }, {
172
+ readonly name: "burgundy-velvet";
173
+ readonly colors: readonly ["#f2dfe0", "#c98a91", "#8a2740", "#4a0f22"];
174
+ }, {
175
+ readonly name: "indigo-beam";
176
+ readonly colors: readonly ["#ffffff", "#a9b6f7", "#4a3fe0", "#2a1fb0"];
177
+ }, {
178
+ readonly name: "crimson-ice";
179
+ readonly colors: readonly ["#f2f7fc", "#f8d7e0", "#e0517f", "#a81050"];
180
+ }, {
181
+ readonly name: "fog-plum";
182
+ readonly colors: readonly ["#f2f0f3", "#cfc6d2", "#9a8a9c", "#5f4a5c"];
183
+ }, {
184
+ readonly name: "sand-sky";
185
+ readonly colors: readonly ["#f4efe4", "#e0d3b8", "#a9bcc4", "#5f7f8c"];
186
+ }, {
187
+ readonly name: "xanadu-sage";
188
+ readonly colors: readonly ["#f1f4f1", "#c8d3c8", "#738678", "#36443a"];
189
+ }, {
190
+ readonly name: "walnut-grain";
191
+ readonly colors: readonly ["#e8d5b7", "#c49a6c", "#8a5a34", "#4a2c17"];
192
+ }, {
193
+ readonly name: "onyx-gold";
194
+ readonly colors: readonly ["#e8c877", "#b8862f", "#3a2f14", "#0b0b0d"];
195
+ }, {
196
+ readonly name: "onyx-silver";
197
+ readonly colors: readonly ["#d7dade", "#8b9199", "#3a3f45", "#0c0d0f"];
198
+ }, {
199
+ readonly name: "graphite-royal";
200
+ readonly colors: readonly ["#9fb0e8", "#3f56c4", "#2a2e38", "#101218"];
201
+ }, {
202
+ readonly name: "onyx-ultraviolet";
203
+ readonly colors: readonly ["#c9a6ff", "#7b3ff2", "#2a1450", "#08070d"];
204
+ }, {
205
+ readonly name: "onyx-snow";
206
+ readonly colors: readonly ["#ffffff", "#b0b0b0", "#4a4a4a", "#000000"];
207
+ }, {
208
+ readonly name: "midnight-ember";
209
+ readonly colors: readonly ["#f0a468", "#c23f1f", "#2a1410", "#0a0a0c"];
210
+ }, {
211
+ readonly name: "slate-harbor";
212
+ readonly colors: readonly ["#aeb7bd", "#6f7b84", "#3c454c", "#1a1f24"];
213
+ }, {
214
+ readonly name: "ivory-ink";
215
+ readonly colors: readonly ["#f7f4ef", "#d8d2c8", "#8a8378", "#20201d"];
216
+ }, {
217
+ readonly name: "paper-graphite";
218
+ readonly colors: readonly ["#f4f4f5", "#cfcfd4", "#83838f", "#1c1c22"];
219
+ }, {
220
+ readonly name: "bone-coffee";
221
+ readonly colors: readonly ["#f5efe6", "#d9c9b3", "#9c8468", "#3a2c1e"];
222
+ }, {
223
+ readonly name: "porcelain-greige";
224
+ readonly colors: readonly ["#f6f3ef", "#ddd4c9", "#b0a596", "#6a5f52"];
225
+ }, {
226
+ readonly name: "battleship-steel";
227
+ readonly colors: readonly ["#e6e9e8", "#b9c0be", "#7d8683", "#3f4745"];
228
+ }, {
229
+ readonly name: "gunmetal-fog";
230
+ readonly colors: readonly ["#eceef1", "#c2c7cd", "#7b838d", "#343a42"];
231
+ }];
232
+ /** Union of every built-in palette name, e.g. `"mint-sea"`. */
233
+ type PaletteName = (typeof colorPalettes)[number]['name'];
234
+ /** Primary accent for UI chips: the palette ramp's mid-tone. */
235
+ declare function paletteAccent(palette: Palette): string;
236
+ /**
237
+ * A color built to stand out on the palette's images: the hue opposite the
238
+ * accent (or the whole ramp, when the accent is near-gray) at its most saturated
239
+ * readable lightness. For chips, geometry and highlights.
240
+ */
241
+ declare function paletteComplement(palette: Palette): string;
242
+ /**
243
+ * Runtime check for palettes that didn't come through the type system
244
+ * (JSON, CLI input, `as` casts). Throws with the offending palette's name.
245
+ */
246
+ declare function validatePalette(palette: Palette): void;
247
+ /** Validates every palette and that names are unique. */
248
+ declare function validatePalettes(palettes: readonly Palette[]): void;
249
+ /** Look up a palette by name. Throws if not found. */
250
+ declare function findPalette(name: PaletteName | (string & {}), palettes?: readonly Palette[]): Palette;
251
+ /**
252
+ * Deterministically pick a palette from a seed.
253
+ *
254
+ * Uses rendezvous (highest-random-weight) hashing: every palette gets a
255
+ * pseudo-random score from `seed + name`, and the highest score wins. The list's
256
+ * order doesn't matter, adding a palette only moves the seeds the new palette
257
+ * wins (~1 in N), and removing one only moves the seeds that had picked it.
258
+ */
259
+ declare function pickPalette(seed: string, palettes?: readonly Palette[]): Palette;
260
+
261
+ export { type HexColor, MAX_PALETTE_COLORS, MIN_PALETTE_COLORS, type Palette, type PaletteColors, type PaletteName, colorPalettes, complementColor, findPalette, paletteAccent, paletteComplement, pickPalette, validatePalette, validatePalettes };
@@ -0,0 +1,25 @@
1
+ import {
2
+ MAX_PALETTE_COLORS,
3
+ MIN_PALETTE_COLORS,
4
+ colorPalettes,
5
+ complementColor,
6
+ findPalette,
7
+ paletteAccent,
8
+ paletteComplement,
9
+ pickPalette,
10
+ validatePalette,
11
+ validatePalettes
12
+ } from "./chunk-KYCZHKFE.js";
13
+ export {
14
+ MAX_PALETTE_COLORS,
15
+ MIN_PALETTE_COLORS,
16
+ colorPalettes,
17
+ complementColor,
18
+ findPalette,
19
+ paletteAccent,
20
+ paletteComplement,
21
+ pickPalette,
22
+ validatePalette,
23
+ validatePalettes
24
+ };
25
+ //# sourceMappingURL=palettes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@dooph-software/fluid-gradients",
3
+ "version": "1.0.0",
4
+ "description": "Deterministic, seeded fluid-gradient images with a curated set of color palettes.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/dooph-software/fluid-gradients.git"
11
+ },
12
+ "publishConfig": {
13
+ "registry": "https://registry.npmjs.org/",
14
+ "access": "public"
15
+ },
16
+ "bin": {
17
+ "fluid-gradient": "./dist/cli.js",
18
+ "fluid-gradients-init-skills": "./bin/init-skills.mjs"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "require": "./dist/index.cjs"
25
+ },
26
+ "./palettes": {
27
+ "types": "./dist/palettes.d.ts",
28
+ "import": "./dist/palettes.js",
29
+ "require": "./dist/palettes.cjs"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "main": "./dist/index.cjs",
34
+ "module": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "sideEffects": false,
37
+ "files": [
38
+ "dist",
39
+ "skills",
40
+ "bin"
41
+ ],
42
+ "engines": {
43
+ "node": ">=20"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "build:watch": "tsup --watch",
48
+ "gradient": "npm run build && node dist/cli.js",
49
+ "lint": "tsc --noEmit",
50
+ "test": "npm run build && node scripts/sync-docs.mjs --check && node scripts/check-readme-types.mjs && node --test test/*.test.mjs",
51
+ "examples": "npm run build && node scripts/generate-examples.mjs",
52
+ "docs": "npm run build && node scripts/sync-docs.mjs",
53
+ "docs:check": "npm run build && node scripts/sync-docs.mjs --check && node scripts/check-readme-types.mjs",
54
+ "version": "npm run build && node scripts/sync-docs.mjs && node scripts/check-readme-types.mjs && git add README.md package.json",
55
+ "prep-release:patch": "npm version patch && npm run build && git push --follow-tags",
56
+ "prep-release:minor": "npm version minor && npm run build && git push --follow-tags",
57
+ "prep-release:major": "npm version major && npm run build && git push --follow-tags",
58
+ "prepublishOnly": "npm run build"
59
+ },
60
+ "dependencies": {
61
+ "@napi-rs/canvas": "^1.0.5",
62
+ "sharp": "^0.35.3",
63
+ "simplex-noise": "^4.0.3"
64
+ },
65
+ "devDependencies": {
66
+ "@types/node": "^22.19.0",
67
+ "tsup": "^8.5.1",
68
+ "typescript": "~5.9.3"
69
+ }
70
+ }
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: using-fluid-gradients
3
+ description: Use when generating, storing, serving, or displaying gradient images or palette colors in a project that depends on @dooph-software/fluid-gradients — cover art, hero backgrounds, avatars, placeholders, OG images, or accent chips that should match a gradient.
4
+ ---
5
+
6
+ # Using @dooph-software/fluid-gradients
7
+
8
+ An image is a pure function of **seed + palette + aspect ratio + look**. Store those, not just the seed, and the image is reproducible forever.
9
+
10
+ ## What to persist per image
11
+
12
+ | Store | Why |
13
+ | --- | --- |
14
+ | `seed` | Drives the composition. |
15
+ | `palette.name` (from the result) | Omitting `palette` picks one from the seed, but that pick shifts for ~1/N seeds whenever a package upgrade adds palettes. Store the resolved name and pass it back. |
16
+ | `width` × `height` | The field is scaled to the image's height and anchored at its left edge. Same aspect ratio → same composition at any resolution. Wider or narrower → the right side extends or trims, so a square render is exactly the left square of a wide one (only the grain differs — it's seeded but drawn per pixel, so it depends on size). Taller or shorter relative to width → rescaled, a different-looking image. |
17
+ | `accent`, `complement` (optional) | Mid-tone hex, and a vivid opposite-hue color built to stand out on the image, for chips and geometry. Both are derivable later from the palette name. |
18
+
19
+ If "must never change" is a hard requirement, also store the rendered bytes. The renderer is frozen by design, but an encoder (sharp/libwebp) upgrade can still shift WebP bytes; stored bytes can't drift at all.
20
+
21
+ ## Core usage
22
+
23
+ ```ts
24
+ // server-only module — rendering needs Node (native canvas + sharp)
25
+ import { generateMeshGradient } from '@dooph-software/fluid-gradients';
26
+
27
+ const { buffer, palette, accent } = await generateMeshGradient({ seed: post.slug, width: 1200, height: 630 });
28
+ // persist: seed, palette.name, 1200×630, accent — and buffer, if immutability is required
29
+ ```
30
+
31
+ ```ts
32
+ // client / edge-safe — zero dependencies, no rendering
33
+ import { findPalette, paletteAccent, paletteComplement } from '@dooph-software/fluid-gradients/palettes';
34
+ const palette = findPalette(post.gradientPalette);
35
+ const chip = paletteAccent(palette);
36
+ const contrastChip = paletteComplement(palette); // opposite hue, vivid — stands out on the image
37
+ ```
38
+
39
+ ## Quick reference
40
+
41
+ - **Custom palettes or a project-wide default size:** `createMeshGradients({ palettes, width, height })` in one module, exported and reused.
42
+ - **Browse palettes:** `npx fluid-gradient --list-palettes`; one-off images: `npx fluid-gradient --seed x --palette y`.
43
+ - **Draw on top before encoding:** `renderMeshGradient` returns the canvas.
44
+
45
+ ## Common mistakes
46
+
47
+ - **Importing the main entry in client or edge code.** It pulls in native binaries; use `/palettes` for colors.
48
+ - **Re-deriving the palette from the seed on every read.** Works until an upgrade adds palettes; then some existing images change color.
49
+ - **Assuming a new crop needs new art.** For a narrower variant (e.g. a square from a 1200×630 cover), render the same seed + palette at the new size: it matches the cover's left edge. Only a relatively taller variant changes the composition.
50
+ - **Assuming text is readable on the image.** Ramps run light to dark, so no single text color is safe everywhere — put text on a scrim or a solid surface.