picasso-skill 2.4.0 → 2.6.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/agents/picasso.md +26 -542
- package/commands/godmode.md +3 -13
- package/commands/roast.md +3 -14
- package/commands/score.md +3 -18
- package/commands/steal.md +3 -31
- package/package.json +1 -1
- package/references/accessibility-wcag.md +3 -0
- package/references/code-typography.md +36 -166
- package/references/color-and-contrast.md +78 -345
- package/references/generative-art.md +49 -561
- package/references/modern-css-performance.md +46 -258
- package/references/motion-and-animation.md +225 -88
- package/references/navigation-patterns.md +29 -186
- package/references/performance-optimization.md +42 -678
- package/references/react-patterns.md +56 -216
- package/references/responsive-design.md +77 -379
- package/references/sensory-design.md +62 -263
- package/references/ux-writing.md +64 -354
- package/references/animation-performance.md +0 -244
- package/references/interaction-design.md +0 -162
|
@@ -1,338 +1,47 @@
|
|
|
1
1
|
# Generative Art Reference
|
|
2
2
|
|
|
3
|
-
## Table of Contents
|
|
4
|
-
1. Philosophy
|
|
5
|
-
2. p5.js Patterns
|
|
6
|
-
3. SVG Generative Art
|
|
7
|
-
4. Canvas 2D API
|
|
8
|
-
5. Noise Functions
|
|
9
|
-
6. Color in Generative Art
|
|
10
|
-
7. Seeded Randomness
|
|
11
|
-
8. Animation vs Static
|
|
12
|
-
9. Performance
|
|
13
|
-
10. Common Mistakes
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
3
|
## 1. Philosophy
|
|
18
4
|
|
|
19
5
|
Generative art is intentional design expressed through algorithms. Randomness is a tool, not the goal. The artist defines the system, its constraints, and its parameter space. The algorithm explores that space. Every output should feel curated, as if the artist chose it from a thousand variations.
|
|
20
6
|
|
|
21
7
|
The quality bar: a viewer should not think "a computer made this." They should think "someone designed this." The difference is in constraint. Unconstrained randomness produces noise. Constrained randomness produces beauty.
|
|
22
8
|
|
|
23
|
-
Three principles
|
|
9
|
+
Three principles:
|
|
24
10
|
- **Parameterize everything.** Every magic number becomes a parameter. This lets you explore the design space systematically.
|
|
25
11
|
- **Seed everything.** Reproducibility is non-negotiable. A good output must be recoverable.
|
|
26
12
|
- **Curate ruthlessly.** Generate hundreds of variations. Ship the best five. The algorithm is a collaborator, not the artist.
|
|
27
13
|
|
|
28
14
|
---
|
|
29
15
|
|
|
30
|
-
## 2.
|
|
31
|
-
|
|
32
|
-
### Canvas Setup
|
|
33
|
-
Always size the canvas to the container or a specific aspect ratio. Never hardcode pixel dimensions without a reason.
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
function setup() {
|
|
37
|
-
const canvas = createCanvas(windowWidth, windowHeight);
|
|
38
|
-
canvas.parent('canvas-container');
|
|
39
|
-
pixelDensity(2); // retina support
|
|
40
|
-
randomSeed(params.seed);
|
|
41
|
-
noiseSeed(params.seed);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function windowResized() {
|
|
45
|
-
resizeCanvas(windowWidth, windowHeight);
|
|
46
|
-
}
|
|
47
|
-
```
|
|
16
|
+
## 2. Core Patterns
|
|
48
17
|
|
|
49
18
|
### Flow Field
|
|
50
|
-
|
|
51
|
-
A complete flow field with particle trails. Particles follow angles derived from Perlin noise, accumulating into organic density maps.
|
|
52
|
-
|
|
19
|
+
Particles follow angles derived from Perlin noise, accumulating into organic density maps.
|
|
53
20
|
```javascript
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
noiseScale: 0.003,
|
|
58
|
-
speed: 2,
|
|
59
|
-
trailAlpha: 10,
|
|
60
|
-
fieldRotation: 0
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
let particles = [];
|
|
64
|
-
|
|
65
|
-
function setup() {
|
|
66
|
-
createCanvas(800, 800);
|
|
67
|
-
randomSeed(params.seed);
|
|
68
|
-
noiseSeed(params.seed);
|
|
69
|
-
background(15);
|
|
70
|
-
|
|
71
|
-
for (let i = 0; i < params.particleCount; i++) {
|
|
72
|
-
particles.push({
|
|
73
|
-
x: random(width),
|
|
74
|
-
y: random(height),
|
|
75
|
-
prevX: 0,
|
|
76
|
-
prevY: 0
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function draw() {
|
|
82
|
-
for (const p of particles) {
|
|
83
|
-
const angle = noise(p.x * params.noiseScale, p.y * params.noiseScale) *
|
|
84
|
-
TAU * 2 + params.fieldRotation;
|
|
85
|
-
p.prevX = p.x;
|
|
86
|
-
p.prevY = p.y;
|
|
87
|
-
p.x += cos(angle) * params.speed;
|
|
88
|
-
p.y += sin(angle) * params.speed;
|
|
89
|
-
|
|
90
|
-
// Wrap around edges
|
|
91
|
-
if (p.x < 0) p.x = width;
|
|
92
|
-
if (p.x > width) p.x = 0;
|
|
93
|
-
if (p.y < 0) p.y = height;
|
|
94
|
-
if (p.y > height) p.y = 0;
|
|
95
|
-
|
|
96
|
-
stroke(255, params.trailAlpha);
|
|
97
|
-
strokeWeight(0.5);
|
|
98
|
-
line(p.prevX, p.prevY, p.x, p.y);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
21
|
+
const angle = noise(p.x * noiseScale, p.y * noiseScale) * TAU * 2;
|
|
22
|
+
p.x += cos(angle) * speed;
|
|
23
|
+
p.y += sin(angle) * speed;
|
|
101
24
|
```
|
|
102
25
|
|
|
103
26
|
### Particle System
|
|
104
|
-
|
|
105
|
-
Self-contained particle class with lifecycle management.
|
|
106
|
-
|
|
107
|
-
```javascript
|
|
108
|
-
class Particle {
|
|
109
|
-
constructor(x, y, hue) {
|
|
110
|
-
this.pos = createVector(x, y);
|
|
111
|
-
this.vel = p5.Vector.random2D().mult(random(0.5, 2));
|
|
112
|
-
this.acc = createVector(0, 0);
|
|
113
|
-
this.lifespan = 255;
|
|
114
|
-
this.hue = hue;
|
|
115
|
-
this.size = random(2, 6);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
applyForce(force) {
|
|
119
|
-
this.acc.add(force);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
update() {
|
|
123
|
-
this.vel.add(this.acc);
|
|
124
|
-
this.vel.limit(4);
|
|
125
|
-
this.pos.add(this.vel);
|
|
126
|
-
this.acc.mult(0);
|
|
127
|
-
this.lifespan -= 2;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
draw() {
|
|
131
|
-
noStroke();
|
|
132
|
-
fill(`oklch(0.75 0.15 ${this.hue} / ${this.lifespan / 255})`);
|
|
133
|
-
circle(this.pos.x, this.pos.y, this.size);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
isDead() {
|
|
137
|
-
return this.lifespan <= 0;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
```
|
|
27
|
+
Class-based particles with position, velocity, acceleration, lifespan, and lifecycle management. Use `p5.Vector` or plain `{x, y}` objects. Always wrap coordinates at edges.
|
|
141
28
|
|
|
142
29
|
### Single-File HTML Scaffold
|
|
143
|
-
|
|
144
|
-
All generative art ships as a single HTML file with p5.js from CDN.
|
|
145
|
-
|
|
146
|
-
```html
|
|
147
|
-
<!DOCTYPE html>
|
|
148
|
-
<html lang="en">
|
|
149
|
-
<head>
|
|
150
|
-
<meta charset="UTF-8">
|
|
151
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
152
|
-
<title>Generative Piece</title>
|
|
153
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js"></script>
|
|
154
|
-
<style>
|
|
155
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
156
|
-
body { background: #0f0f0f; overflow: hidden; }
|
|
157
|
-
#controls {
|
|
158
|
-
position: fixed; bottom: 16px; left: 16px;
|
|
159
|
-
display: flex; gap: 8px; z-index: 10;
|
|
160
|
-
}
|
|
161
|
-
#controls button {
|
|
162
|
-
padding: 6px 14px; border: 1px solid rgba(255,255,255,0.2);
|
|
163
|
-
background: rgba(0,0,0,0.6); color: #fff; border-radius: 6px;
|
|
164
|
-
cursor: pointer; font-size: 13px;
|
|
165
|
-
}
|
|
166
|
-
#seed-display {
|
|
167
|
-
position: fixed; top: 16px; left: 16px;
|
|
168
|
-
color: rgba(255,255,255,0.4); font: 13px/1 monospace;
|
|
169
|
-
}
|
|
170
|
-
</style>
|
|
171
|
-
</head>
|
|
172
|
-
<body>
|
|
173
|
-
<div id="seed-display"></div>
|
|
174
|
-
<div id="controls">
|
|
175
|
-
<button onclick="prevSeed()">Prev</button>
|
|
176
|
-
<button onclick="nextSeed()">Next</button>
|
|
177
|
-
<button onclick="randomizeSeed()">Random</button>
|
|
178
|
-
<button onclick="saveCanvas('output', 'png')">Export PNG</button>
|
|
179
|
-
</div>
|
|
180
|
-
<script>
|
|
181
|
-
/* params, setup, draw, seed controls here */
|
|
182
|
-
</script>
|
|
183
|
-
</body>
|
|
184
|
-
</html>
|
|
185
|
-
```
|
|
30
|
+
All generative art ships as a single HTML file with p5.js from CDN. Include: seed display, prev/next/random buttons, export PNG button.
|
|
186
31
|
|
|
187
32
|
---
|
|
188
33
|
|
|
189
34
|
## 3. SVG Generative Art
|
|
190
35
|
|
|
191
|
-
SVG output is resolution-independent and ideal for print, plotters, and crisp digital display.
|
|
192
|
-
|
|
193
|
-
### Programmatic SVG Construction
|
|
194
|
-
|
|
195
|
-
```javascript
|
|
196
|
-
function generateSVG(width, height, seed) {
|
|
197
|
-
const rng = createRNG(seed);
|
|
198
|
-
const paths = [];
|
|
199
|
-
|
|
200
|
-
for (let i = 0; i < 50; i++) {
|
|
201
|
-
const cx = rng() * width;
|
|
202
|
-
const cy = rng() * height;
|
|
203
|
-
const points = [];
|
|
204
|
-
|
|
205
|
-
for (let a = 0; a < Math.PI * 2; a += 0.1) {
|
|
206
|
-
const r = 40 + rng() * 30;
|
|
207
|
-
const x = cx + Math.cos(a) * r;
|
|
208
|
-
const y = cy + Math.sin(a) * r;
|
|
209
|
-
points.push(`${x.toFixed(2)},${y.toFixed(2)}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const hue = (i * 7.2) % 360;
|
|
213
|
-
paths.push(
|
|
214
|
-
`<polygon points="${points.join(' ')}" ` +
|
|
215
|
-
`fill="oklch(0.7 0.12 ${hue})" fill-opacity="0.3" ` +
|
|
216
|
-
`stroke="oklch(0.5 0.15 ${hue})" stroke-width="0.5" />`
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}">
|
|
221
|
-
<rect width="${width}" height="${height}" fill="oklch(0.12 0.01 260)" />
|
|
222
|
-
${paths.join('\n ')}
|
|
223
|
-
</svg>`;
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Path Generation with Cubic Beziers
|
|
228
|
-
|
|
229
|
-
For smooth organic curves, use cubic bezier path commands. Generate control points with noise-influenced offsets.
|
|
230
|
-
|
|
231
|
-
```javascript
|
|
232
|
-
function noisyPath(startX, startY, steps, rng, noiseScale = 0.02) {
|
|
233
|
-
let d = `M ${startX} ${startY}`;
|
|
234
|
-
let x = startX, y = startY;
|
|
235
|
-
|
|
236
|
-
for (let i = 0; i < steps; i++) {
|
|
237
|
-
const angle = simplexNoise2D(x * noiseScale, y * noiseScale) * Math.PI * 2;
|
|
238
|
-
const length = 20 + rng() * 40;
|
|
239
|
-
const cp1x = x + Math.cos(angle) * length * 0.5;
|
|
240
|
-
const cp1y = y + Math.sin(angle) * length * 0.5;
|
|
241
|
-
x += Math.cos(angle) * length;
|
|
242
|
-
y += Math.sin(angle) * length;
|
|
243
|
-
const cp2x = x - Math.cos(angle) * length * 0.3;
|
|
244
|
-
const cp2y = y - Math.sin(angle) * length * 0.3;
|
|
245
|
-
d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x} ${y}`;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return d;
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### Exporting SVG
|
|
253
|
-
|
|
254
|
-
```javascript
|
|
255
|
-
function downloadSVG(svgString, filename = 'generative.svg') {
|
|
256
|
-
const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
|
257
|
-
const url = URL.createObjectURL(blob);
|
|
258
|
-
const a = document.createElement('a');
|
|
259
|
-
a.href = url;
|
|
260
|
-
a.download = filename;
|
|
261
|
-
a.click();
|
|
262
|
-
URL.revokeObjectURL(url);
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
## 4. Canvas 2D API
|
|
269
|
-
|
|
270
|
-
When you need raw performance without a library, use the native Canvas 2D context.
|
|
271
|
-
|
|
272
|
-
```javascript
|
|
273
|
-
function initCanvas(width, height) {
|
|
274
|
-
const canvas = document.createElement('canvas');
|
|
275
|
-
canvas.width = width * devicePixelRatio;
|
|
276
|
-
canvas.height = height * devicePixelRatio;
|
|
277
|
-
canvas.style.width = `${width}px`;
|
|
278
|
-
canvas.style.height = `${height}px`;
|
|
279
|
-
const ctx = canvas.getContext('2d');
|
|
280
|
-
ctx.scale(devicePixelRatio, devicePixelRatio);
|
|
281
|
-
return { canvas, ctx };
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function drawFlowField(ctx, w, h, seed) {
|
|
285
|
-
const rng = createRNG(seed);
|
|
286
|
-
ctx.fillStyle = 'oklch(0.08 0.01 260)';
|
|
287
|
-
ctx.fillRect(0, 0, w, h);
|
|
288
|
-
|
|
289
|
-
ctx.strokeStyle = 'oklch(0.85 0.04 200 / 0.04)';
|
|
290
|
-
ctx.lineWidth = 0.5;
|
|
291
|
-
|
|
292
|
-
for (let i = 0; i < 3000; i++) {
|
|
293
|
-
let x = rng() * w;
|
|
294
|
-
let y = rng() * h;
|
|
295
|
-
ctx.beginPath();
|
|
296
|
-
ctx.moveTo(x, y);
|
|
297
|
-
|
|
298
|
-
for (let step = 0; step < 80; step++) {
|
|
299
|
-
const angle = simplexNoise2D(x * 0.003, y * 0.003) * Math.PI * 4;
|
|
300
|
-
x += Math.cos(angle) * 1.5;
|
|
301
|
-
y += Math.sin(angle) * 1.5;
|
|
302
|
-
ctx.lineTo(x, y);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
ctx.stroke();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Pixel-Level Manipulation
|
|
36
|
+
SVG output is resolution-independent and ideal for print, plotters, and crisp digital display.
|
|
311
37
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
function pixelEffect(ctx, w, h) {
|
|
316
|
-
const imageData = ctx.getImageData(0, 0, w, h);
|
|
317
|
-
const data = imageData.data;
|
|
318
|
-
|
|
319
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
320
|
-
const px = (i / 4) % w;
|
|
321
|
-
const py = Math.floor(i / 4 / w);
|
|
322
|
-
const n = simplexNoise2D(px * 0.01, py * 0.01);
|
|
323
|
-
data[i] = Math.floor(n * 128 + 128); // R
|
|
324
|
-
data[i + 1] = Math.floor(n * 64 + 128); // G
|
|
325
|
-
data[i + 2] = Math.floor(n * 180 + 75); // B
|
|
326
|
-
data[i + 3] = 255; // A
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
ctx.putImageData(imageData, 0, 0);
|
|
330
|
-
}
|
|
331
|
-
```
|
|
38
|
+
- Build SVG strings programmatically; use `<polygon>`, `<path>`, `<circle>` elements
|
|
39
|
+
- For smooth organic curves, use cubic bezier path commands (`C`) with noise-influenced control points
|
|
40
|
+
- Export via `Blob` + `URL.createObjectURL` + click-triggered download
|
|
332
41
|
|
|
333
42
|
---
|
|
334
43
|
|
|
335
|
-
##
|
|
44
|
+
## 4. Noise Functions
|
|
336
45
|
|
|
337
46
|
### Perlin vs Simplex
|
|
338
47
|
|
|
@@ -343,30 +52,6 @@ function pixelEffect(ctx, w, h) {
|
|
|
343
52
|
| Performance | Moderate | Faster in higher dimensions |
|
|
344
53
|
| Use case | p5.js `noise()` default | Preferred for custom implementations |
|
|
345
54
|
|
|
346
|
-
### Practical Usage
|
|
347
|
-
|
|
348
|
-
```javascript
|
|
349
|
-
// Single-octave noise: smooth, blobby
|
|
350
|
-
const value = noise(x * scale, y * scale);
|
|
351
|
-
|
|
352
|
-
// Multi-octave (fractal) noise: organic detail
|
|
353
|
-
function fractalNoise(x, y, octaves = 4, lacunarity = 2, persistence = 0.5) {
|
|
354
|
-
let total = 0;
|
|
355
|
-
let frequency = 1;
|
|
356
|
-
let amplitude = 1;
|
|
357
|
-
let maxValue = 0;
|
|
358
|
-
|
|
359
|
-
for (let i = 0; i < octaves; i++) {
|
|
360
|
-
total += simplexNoise2D(x * frequency, y * frequency) * amplitude;
|
|
361
|
-
maxValue += amplitude;
|
|
362
|
-
amplitude *= persistence;
|
|
363
|
-
frequency *= lacunarity;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return total / maxValue; // normalized to -1..1
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
55
|
### Noise Scale Guide
|
|
371
56
|
|
|
372
57
|
| Scale | Effect | Use Case |
|
|
@@ -376,141 +61,40 @@ function fractalNoise(x, y, octaves = 4, lacunarity = 2, persistence = 0.5) {
|
|
|
376
61
|
| 0.02-0.1 | Visible texture | Surface detail, displacement |
|
|
377
62
|
| 0.1-0.5 | High frequency, gritty | Texture overlay, grain |
|
|
378
63
|
|
|
379
|
-
### Layering
|
|
380
|
-
|
|
381
|
-
Combine noise at different scales for complex organic results.
|
|
382
|
-
|
|
383
|
-
```javascript
|
|
384
|
-
function layeredNoise(x, y) {
|
|
385
|
-
const base = fractalNoise(x, y, 4, 2, 0.5); // large form
|
|
386
|
-
const detail = simplexNoise2D(x * 0.08, y * 0.08); // medium texture
|
|
387
|
-
const grain = simplexNoise2D(x * 0.5, y * 0.5); // fine grain
|
|
388
|
-
return base * 0.7 + detail * 0.2 + grain * 0.1;
|
|
389
|
-
}
|
|
390
|
-
```
|
|
64
|
+
### Layering
|
|
65
|
+
Combine noise at different scales: base form (4 octaves) + medium texture + fine grain, weighted (e.g., 0.7 / 0.2 / 0.1). Use fractal noise (multi-octave with lacunarity and persistence) for organic detail.
|
|
391
66
|
|
|
392
67
|
---
|
|
393
68
|
|
|
394
|
-
##
|
|
69
|
+
## 5. Color in Generative Art
|
|
395
70
|
|
|
396
71
|
Use OKLCH for all generative color work. Its perceptual uniformity means programmatic palette generation produces visually coherent results, unlike HSL where "equal" lightness values look uneven.
|
|
397
72
|
|
|
398
|
-
### Palette
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
function analogousPalette(baseHue, count = 5, spread = 30) {
|
|
403
|
-
return Array.from({ length: count }, (_, i) => {
|
|
404
|
-
const hue = (baseHue - spread / 2 + (spread / (count - 1)) * i) % 360;
|
|
405
|
-
return `oklch(0.7 0.15 ${hue})`;
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Complementary with variation
|
|
410
|
-
function complementaryPalette(baseHue) {
|
|
411
|
-
return [
|
|
412
|
-
`oklch(0.65 0.2 ${baseHue})`,
|
|
413
|
-
`oklch(0.75 0.1 ${baseHue})`,
|
|
414
|
-
`oklch(0.65 0.2 ${(baseHue + 180) % 360})`,
|
|
415
|
-
`oklch(0.80 0.08 ${(baseHue + 180) % 360})`,
|
|
416
|
-
];
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Triadic palette
|
|
420
|
-
function triadicPalette(baseHue, lightness = 0.65, chroma = 0.18) {
|
|
421
|
-
return [0, 120, 240].map(offset =>
|
|
422
|
-
`oklch(${lightness} ${chroma} ${(baseHue + offset) % 360})`
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
```
|
|
73
|
+
### Palette Strategies
|
|
74
|
+
- **Analogous**: hues clustered within a 30-degree spread
|
|
75
|
+
- **Complementary**: base hue + base+180, with lightness/chroma variations
|
|
76
|
+
- **Triadic**: base + base+120 + base+240
|
|
426
77
|
|
|
427
78
|
### Color from Noise
|
|
428
|
-
|
|
429
|
-
Map noise values to hue ranges for smooth, organic color transitions.
|
|
430
|
-
|
|
431
|
-
```javascript
|
|
432
|
-
function noiseColor(x, y, noiseScale = 0.005) {
|
|
433
|
-
const n = noise(x * noiseScale, y * noiseScale); // 0..1
|
|
434
|
-
const hue = lerp(200, 320, n); // blue to magenta
|
|
435
|
-
const lightness = lerp(0.5, 0.8, n);
|
|
436
|
-
const chroma = lerp(0.08, 0.2, n);
|
|
437
|
-
return `oklch(${lightness} ${chroma} ${hue})`;
|
|
438
|
-
}
|
|
439
|
-
```
|
|
79
|
+
Map noise values to hue ranges for smooth organic transitions: `lerp(hueMin, hueMax, noiseValue)`.
|
|
440
80
|
|
|
441
81
|
### Background and Contrast
|
|
442
|
-
|
|
443
|
-
Dark backgrounds with luminous strokes produce the best generative art contrast. Use near-black with a slight hue tint, never pure `#000000`.
|
|
444
|
-
|
|
445
|
-
```javascript
|
|
446
|
-
const bg = 'oklch(0.08 0.015 260)'; // dark blue-tinted black
|
|
447
|
-
const stroke = 'oklch(0.85 0.12 200 / 0.06)'; // faint luminous trail
|
|
448
|
-
```
|
|
82
|
+
Dark backgrounds with luminous strokes produce the best generative art contrast. Use near-black with a slight hue tint (`oklch(0.08 0.015 260)`), never pure `#000000`.
|
|
449
83
|
|
|
450
84
|
---
|
|
451
85
|
|
|
452
|
-
##
|
|
86
|
+
## 6. Seeded Randomness
|
|
453
87
|
|
|
454
88
|
Every generative piece must be reproducible. Same seed, same output. Always.
|
|
455
89
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
```javascript
|
|
461
|
-
function createRNG(seed) {
|
|
462
|
-
let s = seed | 0;
|
|
463
|
-
return function () {
|
|
464
|
-
s = (s + 0x6d2b79f5) | 0;
|
|
465
|
-
let t = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
466
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
467
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const rng = createRNG(42);
|
|
472
|
-
rng(); // 0.0..1.0, deterministic
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### Seed Management in p5.js
|
|
476
|
-
|
|
477
|
-
```javascript
|
|
478
|
-
let currentSeed = 42;
|
|
479
|
-
|
|
480
|
-
function regenerate(newSeed) {
|
|
481
|
-
currentSeed = newSeed;
|
|
482
|
-
randomSeed(currentSeed);
|
|
483
|
-
noiseSeed(currentSeed);
|
|
484
|
-
clear();
|
|
485
|
-
background(15);
|
|
486
|
-
draw(); // or loop() for animated pieces
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function nextSeed() { regenerate(currentSeed + 1); }
|
|
490
|
-
function prevSeed() { regenerate(currentSeed - 1); }
|
|
491
|
-
function randomizeSeed() { regenerate(Math.floor(Math.random() * 999999)); }
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### Seed from URL
|
|
495
|
-
|
|
496
|
-
Let users share specific outputs via URL.
|
|
497
|
-
|
|
498
|
-
```javascript
|
|
499
|
-
function seedFromURL() {
|
|
500
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
501
|
-
return urlParams.has('seed') ? parseInt(urlParams.get('seed'), 10) : 42;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function updateURL(seed) {
|
|
505
|
-
const url = new URL(window.location);
|
|
506
|
-
url.searchParams.set('seed', seed);
|
|
507
|
-
window.history.replaceState({}, '', url);
|
|
508
|
-
}
|
|
509
|
-
```
|
|
90
|
+
- Use Mulberry32 or similar fast 32-bit PRNG for custom seeds
|
|
91
|
+
- In p5.js, call `randomSeed(seed)` and `noiseSeed(seed)` in `setup()`
|
|
92
|
+
- Provide seed navigation UI: prev, next, random buttons
|
|
93
|
+
- Store seed in URL params for sharing specific outputs
|
|
510
94
|
|
|
511
95
|
---
|
|
512
96
|
|
|
513
|
-
##
|
|
97
|
+
## 7. Animation vs Static
|
|
514
98
|
|
|
515
99
|
### When to Animate
|
|
516
100
|
- The piece explores temporal evolution (particles finding equilibrium, growth systems)
|
|
@@ -522,127 +106,31 @@ function updateURL(seed) {
|
|
|
522
106
|
- The algorithm is computationally expensive (reaction-diffusion, deep recursion)
|
|
523
107
|
- The piece will be exported as PNG/SVG
|
|
524
108
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
```javascript
|
|
528
|
-
let isAnimating = true;
|
|
529
|
-
|
|
530
|
-
function draw() {
|
|
531
|
-
if (!isAnimating) return;
|
|
532
|
-
// drawing logic here
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// For non-p5 contexts
|
|
536
|
-
function animate(ctx, state) {
|
|
537
|
-
function frame(timestamp) {
|
|
538
|
-
if (!state.running) return;
|
|
539
|
-
update(state, timestamp);
|
|
540
|
-
render(ctx, state);
|
|
541
|
-
state.frameId = requestAnimationFrame(frame);
|
|
542
|
-
}
|
|
543
|
-
state.frameId = requestAnimationFrame(frame);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function stop(state) {
|
|
547
|
-
state.running = false;
|
|
548
|
-
cancelAnimationFrame(state.frameId);
|
|
549
|
-
}
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### Export Workflow for Animated Pieces
|
|
553
|
-
|
|
554
|
-
Render a fixed number of frames, then stop and allow PNG export.
|
|
555
|
-
|
|
556
|
-
```javascript
|
|
557
|
-
const TOTAL_FRAMES = 300;
|
|
558
|
-
let frameCount = 0;
|
|
559
|
-
|
|
560
|
-
function draw() {
|
|
561
|
-
if (frameCount >= TOTAL_FRAMES) {
|
|
562
|
-
noLoop();
|
|
563
|
-
document.getElementById('export-btn').disabled = false;
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
// render frame
|
|
567
|
-
frameCount++;
|
|
568
|
-
}
|
|
569
|
-
```
|
|
109
|
+
For animated pieces, render a fixed frame count then stop and enable export.
|
|
570
110
|
|
|
571
111
|
---
|
|
572
112
|
|
|
573
|
-
##
|
|
574
|
-
|
|
575
|
-
### Offscreen Canvas
|
|
576
|
-
|
|
577
|
-
Pre-render static or expensive layers to an offscreen canvas, then composite.
|
|
578
|
-
|
|
579
|
-
```javascript
|
|
580
|
-
function createOffscreenLayer(width, height, drawFn) {
|
|
581
|
-
const offscreen = document.createElement('canvas');
|
|
582
|
-
offscreen.width = width;
|
|
583
|
-
offscreen.height = height;
|
|
584
|
-
const offCtx = offscreen.getContext('2d');
|
|
585
|
-
drawFn(offCtx, width, height);
|
|
586
|
-
return offscreen;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Usage: render background noise once
|
|
590
|
-
const bgLayer = createOffscreenLayer(800, 800, (ctx, w, h) => {
|
|
591
|
-
// expensive noise texture
|
|
592
|
-
pixelEffect(ctx, w, h);
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
// In the main draw loop, just composite
|
|
596
|
-
mainCtx.drawImage(bgLayer, 0, 0);
|
|
597
|
-
```
|
|
113
|
+
## 8. Performance
|
|
598
114
|
|
|
599
|
-
###
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
// Fast: single path for all particles of the same color
|
|
605
|
-
ctx.beginPath();
|
|
606
|
-
ctx.strokeStyle = 'oklch(0.8 0.1 220 / 0.05)';
|
|
607
|
-
for (const p of particles) {
|
|
608
|
-
ctx.moveTo(p.prevX, p.prevY);
|
|
609
|
-
ctx.lineTo(p.x, p.y);
|
|
610
|
-
}
|
|
611
|
-
ctx.stroke(); // one draw call
|
|
612
|
-
|
|
613
|
-
// Slow: individual stroke per particle (avoid this)
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
### requestAnimationFrame Discipline
|
|
617
|
-
|
|
618
|
-
Never use `setInterval` or `setTimeout` for animation. Always use `requestAnimationFrame`. It syncs to the display refresh, pauses in background tabs, and produces smooth frame pacing.
|
|
619
|
-
|
|
620
|
-
### Pixel Density
|
|
621
|
-
|
|
622
|
-
On retina screens, match the device pixel ratio but keep the logical coordinate space the same.
|
|
623
|
-
|
|
624
|
-
```javascript
|
|
625
|
-
const dpr = window.devicePixelRatio || 1;
|
|
626
|
-
canvas.width = logicalWidth * dpr;
|
|
627
|
-
canvas.height = logicalHeight * dpr;
|
|
628
|
-
canvas.style.width = `${logicalWidth}px`;
|
|
629
|
-
canvas.style.height = `${logicalHeight}px`;
|
|
630
|
-
ctx.scale(dpr, dpr);
|
|
631
|
-
```
|
|
115
|
+
### Rules
|
|
116
|
+
- Pre-render static/expensive layers to an offscreen canvas, then composite with `drawImage`
|
|
117
|
+
- Batch draw calls: one `beginPath()` with many `lineTo()` calls, one `stroke()` (not per-particle)
|
|
118
|
+
- Always use `requestAnimationFrame`, never `setInterval`/`setTimeout`
|
|
119
|
+
- On retina screens, set canvas dimensions to `logicalSize * devicePixelRatio` and `ctx.scale(dpr, dpr)`
|
|
632
120
|
|
|
633
121
|
---
|
|
634
122
|
|
|
635
|
-
##
|
|
636
|
-
|
|
637
|
-
- Using `Math.random()` without seeding (
|
|
638
|
-
- Hardcoding canvas dimensions instead of deriving from container
|
|
639
|
-
- Forgetting to wrap particle coordinates at edges
|
|
640
|
-
- Calling `background()` every frame in
|
|
641
|
-
- Using HSL for programmatic color generation (perceptually uneven
|
|
642
|
-
- Animating when the piece should be static (wasting battery
|
|
643
|
-
- Not providing seed navigation UI
|
|
644
|
-
- Rendering at 1x on retina displays (
|
|
645
|
-
-
|
|
646
|
-
-
|
|
647
|
-
-
|
|
648
|
-
- Shipping without an export button
|
|
123
|
+
## 9. Common Mistakes
|
|
124
|
+
|
|
125
|
+
- Using `Math.random()` without seeding (unreproducible outputs)
|
|
126
|
+
- Hardcoding canvas dimensions instead of deriving from container/aspect ratio
|
|
127
|
+
- Forgetting to wrap particle coordinates at edges
|
|
128
|
+
- Calling `background()` every frame in trail-accumulation pieces
|
|
129
|
+
- Using HSL for programmatic color generation (perceptually uneven)
|
|
130
|
+
- Animating when the piece should be static (wasting battery/CPU)
|
|
131
|
+
- Not providing seed navigation UI
|
|
132
|
+
- Rendering at 1x on retina displays (blurry output)
|
|
133
|
+
- One `beginPath/stroke` per particle instead of batching (kills frame rate)
|
|
134
|
+
- Noise scale of 1.0 (produces white noise; useful scales are 0.001-0.1)
|
|
135
|
+
- Random RGB values for palettes (muddy, clashing; use OKLCH)
|
|
136
|
+
- Shipping without an export button
|