@vibes.diy/prompts 14.1.20 → 14.1.22

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/llms/p5.md ADDED
@@ -0,0 +1,714 @@
1
+ # p5.js API
2
+
3
+ _Instance mode, the pinned import, determinism, and the drawing surface for generative sketches_
4
+
5
+ Any request for generative art, a flow field, a particle field, a screensaver, a
6
+ visualiser, a doodle pad, a drawing toy, a kaleidoscope, a plotter-style pattern
7
+ or "a sketch" is a p5.js app: one canvas created by a p5 **instance**, driven by
8
+ a `setup`/`draw` pair, mounted from a single `React.useEffect(() => {…}, [])`
9
+ and torn down with `instance.remove()` on unmount. p5 gives you a canvas, a
10
+ frame loop, Perlin noise and a seeded random generator for free.
11
+
12
+ The whole model of p5 is two functions: `setup` runs once, `draw` runs about
13
+ sixty times a second, forever. A still picture is a loop that draws the same
14
+ thing every frame; an animation is a loop where something changed.
15
+
16
+ ## Complete starter — the shell every p5 vibe begins from
17
+
18
+ This is a full, runnable `App.jsx`. Start here and grow it: the sketch is a
19
+ factory function that receives the host element, the instance is built once in
20
+ one `useEffect([])`, the canvas is sized from the host, and the cleanup removes
21
+ the instance so remounts do not leak a second draw loop.
22
+
23
+ App.jsx
24
+
25
+ ```jsx
26
+ import React from "react";
27
+ import p5 from "https://esm.sh/p5@1.11.13";
28
+
29
+ const TAU = Math.PI * 2;
30
+
31
+ // Canvas pixels cannot follow a CSS variable, so resolve the injected theme
32
+ // tokens to concrete colors once per mount. Setting `color` on a throwaway
33
+ // element and reading it back returns a resolved `rgb(...)` string even when the
34
+ // token's value is a `color-mix()` or another `var()`, and the second argument
35
+ // is the fallback for an app with no theme injected.
36
+ function cssColor(host, token, fallback) {
37
+ const probe = document.createElement("span");
38
+ probe.style.display = "none";
39
+ probe.style.color = `var(${token}, ${fallback})`;
40
+ host.appendChild(probe);
41
+ const resolved = window.getComputedStyle(probe).color;
42
+ probe.remove();
43
+ return resolved || fallback;
44
+ }
45
+
46
+ // The sketch is a pure function of its parameters and its host element. Pass the
47
+ // host in explicitly — never read p5's private `_userNode` to find it.
48
+ function makeSketch({ params, host, stepsRef }) {
49
+ return (p) => {
50
+ let particles = [];
51
+ let palette = null;
52
+ let steps = 0;
53
+ let w = 0;
54
+ let h = 0;
55
+ let t = 0;
56
+
57
+ // Read the theme tokens into p5 colors. Called at setup AND whenever the
58
+ // page's CSS variables change under a live restyle.
59
+ const readPalette = () => {
60
+ const bg = p.color(cssColor(host, "--background", "#040a18"));
61
+ const ink = p.color(cssColor(host, "--accent", "#00e5ff"));
62
+ const fade = p.color(cssColor(host, "--background", "#040a18"));
63
+ fade.setAlpha(10);
64
+ ink.setAlpha(85);
65
+ palette = { bg, ink, fade };
66
+ };
67
+
68
+ // The step count lives inside this closure, and the Keep button lives in
69
+ // React — so publish it outward on every step. `p` exists ONLY here.
70
+ const publishSteps = () => {
71
+ if (stepsRef) stepsRef.current = steps;
72
+ };
73
+
74
+ const spawn = () => {
75
+ particles = [];
76
+ for (let i = 0; i < params.count; i++) {
77
+ const x = p.random(w);
78
+ const y = p.random(h);
79
+ particles.push({ x, y, px: x, py: y });
80
+ }
81
+ };
82
+
83
+ const advance = () => {
84
+ steps += 1;
85
+ t += params.speed * 0.0016;
86
+ for (const q of particles) {
87
+ const a = p.noise(q.x * params.grain, q.y * params.grain, t) * TAU * 2;
88
+ q.px = q.x;
89
+ q.py = q.y;
90
+ q.x += Math.cos(a) * params.speed;
91
+ q.y += Math.sin(a) * params.speed;
92
+ p.line(q.px, q.py, q.x, q.y);
93
+ if (q.x < -20 || q.x > w + 20 || q.y < -20 || q.y > h + 20) {
94
+ q.x = p.random(w);
95
+ q.y = p.random(h);
96
+ q.px = q.x;
97
+ q.py = q.y;
98
+ }
99
+ }
100
+ };
101
+
102
+ p.setup = () => {
103
+ w = Math.max(120, host.clientWidth || 320);
104
+ h = Math.max(120, host.clientHeight || 240);
105
+ const c = p.createCanvas(w, h);
106
+ if (c && c.elt) c.elt.style.display = "block";
107
+ p.pixelDensity(Math.min(window.devicePixelRatio || 1, 2));
108
+ p.randomSeed(params.seed);
109
+ p.noiseSeed(params.seed);
110
+
111
+ readPalette();
112
+ p.stroke(palette.ink);
113
+ p.strokeWeight(1);
114
+ p.background(palette.bg);
115
+ spawn();
116
+ publishSteps();
117
+ };
118
+
119
+ // ONE frame of the piece: the fade is part of the frame, not decoration
120
+ // around it, so the still render below replays this and not just advance().
121
+ const step = () => {
122
+ // Trails: dim what is already there instead of erasing it.
123
+ p.noStroke();
124
+ p.fill(palette.fade);
125
+ p.rect(0, 0, w, h);
126
+ p.stroke(palette.ink);
127
+ advance();
128
+ };
129
+
130
+ p.draw = () => {
131
+ step();
132
+ publishSteps();
133
+ };
134
+
135
+ // A live restyle swaps the page's CSS variables in place; the canvas is
136
+ // painted, so it has to be told. Called by the observer in App below.
137
+ p.retheme = () => {
138
+ if (palette === null) return;
139
+ readPalette();
140
+ p.stroke(palette.ink);
141
+ p.background(palette.bg);
142
+ };
143
+
144
+ p.mouseDragged = () => {
145
+ particles.push({ x: p.mouseX, y: p.mouseY, px: p.mouseX, py: p.mouseY });
146
+ };
147
+
148
+ p.touchMoved = () => {
149
+ particles.push({ x: p.mouseX, y: p.mouseY, px: p.mouseX, py: p.mouseY });
150
+ return false; // stop the browser scrolling the page under the finger
151
+ };
152
+
153
+ p.windowResized = () => {
154
+ w = Math.max(120, host.clientWidth);
155
+ h = Math.max(120, host.clientHeight);
156
+ p.resizeCanvas(w, h);
157
+ p.background(palette.bg);
158
+ spawn();
159
+ };
160
+ };
161
+ }
162
+
163
+ export default function App() {
164
+ const hostRef = React.useRef(null);
165
+ const stepsRef = React.useRef(0);
166
+ const [params, setParams] = React.useState({
167
+ count: 1200,
168
+ grain: 0.0022,
169
+ speed: 1.6,
170
+ seed: 1234,
171
+ });
172
+ const key = JSON.stringify(params);
173
+
174
+ React.useEffect(() => {
175
+ const node = hostRef.current;
176
+ if (!node) return undefined;
177
+ const instance = new p5(makeSketch({ params, host: node, stepsRef }), node);
178
+
179
+ // The Style tab recolors a running app by swapping the CSS variables with
180
+ // no remount, which every styled element follows and the canvas cannot.
181
+ // Watching the document's stylesheets is what makes the sketch follow too.
182
+ const observer = new MutationObserver(() => instance.retheme());
183
+ observer.observe(document.head, { childList: true, subtree: true, characterData: true });
184
+
185
+ return () => {
186
+ observer.disconnect();
187
+ instance.remove();
188
+ };
189
+ // eslint-disable-next-line react-hooks/exhaustive-deps
190
+ }, [key]);
191
+
192
+ return (
193
+ <div className="min-h-screen bg-[var(--background)] text-[var(--text-primary)] p-4">
194
+ <div
195
+ ref={hostRef}
196
+ className="w-full h-[70vh] overflow-hidden rounded-[var(--radius)] border border-[var(--border)]"
197
+ style={{ touchAction: "none" }}
198
+ />
199
+ <button
200
+ className="mt-3 rounded-full border border-[var(--border)] px-4 py-2 text-sm"
201
+ onClick={() => setParams((prev) => ({ ...prev, seed: Math.floor(Math.random() * 100000) }))}
202
+ >
203
+ Reseed
204
+ </button>
205
+ </div>
206
+ );
207
+ }
208
+ ```
209
+
210
+ Everything below grows that shell. The lifecycle above — one instance, one
211
+ `useEffect`, `remove()` on cleanup — stays the spine of every p5 vibe.
212
+
213
+ ## Instance mode is how p5 runs in a vibe
214
+
215
+ Every p5 tutorial on the internet opens with a global `function setup()`. That
216
+ works when the sketch owns the whole page. In a vibe React owns the page, and
217
+ the sketch is a component that mounts, unmounts, and mounts again every time an
218
+ input changes — so the sketch must be an **instance**.
219
+
220
+ **Create the instance inside one `React.useEffect` and return `() => instance.remove()` from it.**
221
+ Without the cleanup, every change to the sketch's inputs mounts a fresh instance
222
+ beside the old one and the old one keeps running: two draw loops, then four, a
223
+ leaked canvas per change, and a sketch that gets mysteriously slower and
224
+ blurrier every time a slider moves.
225
+
226
+ **Assign every lifecycle hook on the instance** — `p.setup`, `p.draw`,
227
+ `p.windowResized`, `p.mouseDragged` — rather than declaring them as globals.
228
+ The instance is the only handle the sketch has, so everything it draws with goes
229
+ through `p`: `p.noise`, `p.random`, `p.line`, `p.fill`.
230
+
231
+ **Pass the host element into the sketch factory explicitly.** `new p5(sketch, host)`
232
+ draws into that element instead of appending a canvas to the body, which is what
233
+ keeps the layout React's and the pixels p5's. p5 also stashes the node on the
234
+ instance, but that property is private — hand the node to your factory as an
235
+ argument instead of reading `_userNode` back off `p`.
236
+
237
+ ```jsx
238
+ const hostRef = React.useRef(null);
239
+
240
+ React.useEffect(() => {
241
+ const node = hostRef.current;
242
+ if (!node) return undefined;
243
+ const instance = new p5(makeSketch({ params, host: node }), node);
244
+ return () => instance.remove();
245
+ }, [key]);
246
+
247
+ return <div ref={hostRef} className="w-full h-full" style={{ touchAction: "none" }} />;
248
+ ```
249
+
250
+ Because the effect re-runs on a changed `key`, a parameter change is a clean
251
+ swap: the old instance is removed, a new one is built, and the sketch stays a
252
+ pure function of its parameters.
253
+
254
+ ## Importing p5
255
+
256
+ **Import p5 from a version-pinned esm.sh URL: `import p5 from "https://esm.sh/p5@1.11.13"`.**
257
+ A bare `"p5"` resolves to the 2.x line, which pulls a much heavier module graph
258
+ (acorn, escodegen) into the sandbox for an API this doc never uses.
259
+
260
+ Pinning is a correctness property here, not caution. For generative art a
261
+ library bump can change what a piece *looks like* — a tweak to `noise()`, a
262
+ different default `pixelDensity` — and a saved piece is a handful of numbers
263
+ that assume the renderer they were saved on. Pin the version the way you would
264
+ date a print.
265
+
266
+ ## Sizing the canvas
267
+
268
+ Size from the host element, never from `window.innerWidth`: the sketch lives in
269
+ a box React laid out, and the box is rarely the whole window.
270
+
271
+ ```js
272
+ p.setup = () => {
273
+ w = Math.max(120, host.clientWidth || 320);
274
+ h = Math.max(120, host.clientHeight || 240);
275
+ p.createCanvas(w, h);
276
+ };
277
+
278
+ p.windowResized = () => {
279
+ w = Math.max(120, host.clientWidth);
280
+ h = Math.max(120, host.clientHeight);
281
+ p.resizeCanvas(w, h);
282
+ };
283
+ ```
284
+
285
+ `p.resizeCanvas` clears the canvas, so a trails sketch repaints its background
286
+ and respawns its particles after a resize rather than resuming into an empty
287
+ frame.
288
+
289
+ **Cap pixel density at 2 for a live canvas and set `p.pixelDensity(1)` for thumbnails.**
290
+ On a retina screen the cost of a frame is fill rate, not JavaScript, and a
291
+ gallery of small cards at device density is the fastest way to make a page
292
+ stutter.
293
+
294
+ ## Colour comes from the injected theme tokens
295
+
296
+ The platform injects this app's colours as CSS variables — `--background`,
297
+ `--surface`, `--primary`, `--secondary`, `--accent`, `--text-primary`,
298
+ `--text-secondary`, `--border` — and the owner restyles the whole app by swapping
299
+ those values. Everything outside the canvas reads them through Tailwind bracket
300
+ notation (`bg-[var(--background)]`), but **canvas pixels cannot follow a CSS
301
+ variable**, so a sketch that inlines `"#00e5ff"` is the one part of a themed vibe
302
+ its owner can never restyle.
303
+
304
+ **Resolve the theme tokens to concrete colours at setup, and use a literal only
305
+ as the fallback when no theme is injected.** Reading the custom property
306
+ straight off the host with `getComputedStyle(host).getPropertyValue("--accent")`
307
+ is enough when the token holds a plain colour, but a token whose value is a
308
+ `color-mix()` or another `var()` comes back unresolved and p5 cannot parse it.
309
+ Letting the browser resolve it works for every value:
310
+
311
+ ```js
312
+ function cssColor(host, token, fallback) {
313
+ const probe = document.createElement("span");
314
+ probe.style.display = "none";
315
+ probe.style.color = `var(${token}, ${fallback})`;
316
+ host.appendChild(probe);
317
+ const resolved = window.getComputedStyle(probe).color;
318
+ probe.remove();
319
+ return resolved || fallback;
320
+ }
321
+
322
+ p.setup = () => {
323
+ p.createCanvas(w, h);
324
+ const ink = p.color(cssColor(host, "--accent", "#00e5ff"));
325
+ ink.setAlpha(85); // p5 owns the alpha; the token owns the hue
326
+ p.stroke(ink);
327
+ p.background(p.color(cssColor(host, "--background", "#040a18")));
328
+ };
329
+ ```
330
+
331
+ Read the tokens **inside `setup`**, not at module scope, so a rebuilt instance
332
+ picks up the current theme. Keep alpha in p5's hands with `setAlpha`: appending
333
+ `"55"` to a token works only if the token happened to be a six-digit hex, and a
334
+ resolved `rgb(...)` string it silently corrupts.
335
+
336
+ **Reading at setup is not enough on its own, because a restyle does not rebuild
337
+ the sketch.** The Style tab recolors a running app by swapping the page's CSS
338
+ variables in place — it replaces the contents of a `<style>` element in the
339
+ document head, with no remount — so every styled element re-renders through the
340
+ cascade and the canvas, which is painted rather than styled, keeps the colours it
341
+ resolved at setup until something unrelated happens to rebuild it. Watch the
342
+ document's stylesheets and repaint:
343
+
344
+ ```jsx
345
+ // in the sketch: re-read and repaint on demand
346
+ p.retheme = () => {
347
+ if (palette === null) return; // setup has not run yet
348
+ readPalette();
349
+ p.stroke(palette.ink);
350
+ p.background(palette.bg);
351
+ };
352
+
353
+ // in the component: same effect that built the instance, same cleanup
354
+ const instance = new p5(makeSketch({ params, host: node }), node);
355
+ const observer = new MutationObserver(() => instance.retheme());
356
+ observer.observe(document.head, { childList: true, subtree: true, characterData: true });
357
+ return () => {
358
+ observer.disconnect();
359
+ instance.remove();
360
+ };
361
+ ```
362
+
363
+ `characterData` and `subtree` are both needed: a first push APPENDS the style
364
+ element, and every push after it rewrites the text of the one already there. The
365
+ observer belongs to the effect that built the instance, so it is disconnected in
366
+ the same cleanup that removes it — an observer outliving its sketch is the same
367
+ leak as a draw loop outliving its canvas.
368
+
369
+ ## Determinism — a picture is a handful of numbers
370
+
371
+ **Call `p.randomSeed(seed)` and `p.noiseSeed(seed)` in `setup` so the sketch replays from its numbers instead of from its pixels.**
372
+ Seed both generators and the simulation becomes reproducible: the same seed
373
+ gives the same starting positions, the same wind, the same drift.
374
+
375
+ This is the single most useful p5 idea for a vibe with a database. A saved piece
376
+ is not a PNG — it is its parameters, its seed and **how far the simulation had
377
+ run**, a few dozen bytes, and a fresh instance cooks that recipe again as many
378
+ times as you like. So persist the numbers, not the pixels:
379
+
380
+ `steps` is as load-bearing as `seed` — an animated sketch looks different at step
381
+ 100 and step 10,000 — and it is the number that is hardest to get at, because
382
+ **`p` exists only inside the sketch factory and the Keep button lives in React**.
383
+ Reaching for `p.frameCount` from a click handler is a `ReferenceError`. The
384
+ sketch has to publish the count outward; a ref is the smallest way:
385
+
386
+ ```jsx
387
+ // in the component
388
+ const stepsRef = React.useRef(0);
389
+ const { database, useLiveQuery } = useFireproof("pieces");
390
+
391
+ React.useEffect(() => {
392
+ const instance = new p5(makeSketch({ params, host: node, stepsRef }), node);
393
+ return () => instance.remove();
394
+ }, [key]);
395
+
396
+ // in the sketch factory — `stepsRef` is an argument, so the closure can write it.
397
+ // ONE counter with ONE owner: `advance()` is the only thing that increments it,
398
+ // because it is the only thing that moves the simulation. Everything else reads.
399
+ let steps = 0;
400
+ const advance = () => {
401
+ steps += 1;
402
+ // …move every particle, draw its segment…
403
+ };
404
+
405
+ p.draw = () => {
406
+ step(); // calls advance() exactly once
407
+ if (stepsRef) stepsRef.current = steps;
408
+ };
409
+
410
+ // in the Keep handler — no `p` in sight
411
+ async function keep() {
412
+ await database.put({ ...params, type: "piece", steps: stepsRef.current, created: Date.now() });
413
+ }
414
+
415
+ const { docs } = useLiveQuery("created", { descending: true, limit: 12 });
416
+ ```
417
+
418
+ Keep the counter to one owner. It counts calls to `advance()`, so incrementing it
419
+ anywhere else — in `draw`, in `step`, beside the publish — counts the same frame
420
+ twice, and a card then replays twice the frames that were on screen when Keep was
421
+ pressed, which is the exact error persisting `steps` exists to prevent.
422
+
423
+ A card then renders exactly `doc.steps` steps (see the still-render pattern
424
+ below), so the wall shows the piece at the moment it was kept rather than at
425
+ whatever frame the card happened to reach. Loading somebody else's piece hands
426
+ the reader a live sketch with their parameters in it, so nudging one number is a
427
+ variation rather than a copy.
428
+
429
+ Two honest limits, and say them in the app's own copy rather than promising more
430
+ than it does:
431
+
432
+ - **Hand-drawn input is not in the numbers.** If a person can drag on the canvas
433
+ to push ink into it, what they drew is unbounded input that no seed replays. A
434
+ kept piece is a canonical render of the recipe, not a snapshot of the canvas at
435
+ the moment of keeping — the recipe is exact, the print is fresh. An app that
436
+ needs the literal pixels is asking for an image, so capture one
437
+ (`p.saveCanvas`, or the canvas's own `toBlob`) and store that instead.
438
+ - **The numbers fix the simulation, not the screen.** A canvas of a different
439
+ size or pixel density starts the particles in proportionally different places,
440
+ the way the same negative prints differently at different sizes.
441
+
442
+ `Math.random()` is NOT covered by `randomSeed` — use `p.random()` everywhere
443
+ inside a sketch that means to be reproducible, and `p.noise()` rather than any
444
+ hand-rolled noise.
445
+
446
+ ## Still renders — a thumbnail is a loop you ran to completion
447
+
448
+ To draw a gallery card, run the simulation inside `setup` for the stored number
449
+ of steps and then stop the loop. Same sketch factory, one flag — and `steps`
450
+ comes off the saved piece (`doc.steps`), which is what makes the card the piece
451
+ its author kept:
452
+
453
+ ```js
454
+ // ONE frame of the piece, called by both paths. The fade is part of a frame:
455
+ // a still loop that calls only `advance()` accumulates different stroke opacity
456
+ // than the live canvas did, so the card would not be the piece at identical
457
+ // seed, parameters, size and step count.
458
+ const step = () => {
459
+ p.noStroke();
460
+ p.fill(palette.fade);
461
+ p.rect(0, 0, w, h);
462
+ p.stroke(palette.ink);
463
+ advance();
464
+ };
465
+
466
+ p.setup = () => {
467
+ p.createCanvas(w, h);
468
+ p.pixelDensity(still ? 1 : Math.min(window.devicePixelRatio || 1, 2));
469
+ p.randomSeed(params.seed);
470
+ p.noiseSeed(params.seed);
471
+ readPalette();
472
+ p.background(palette.bg);
473
+ spawn();
474
+ if (still) {
475
+ for (let i = 0; i < steps; i++) step(); // the SAME step, not advance()
476
+ p.noLoop();
477
+ }
478
+ };
479
+
480
+ p.draw = () => {
481
+ if (still) return;
482
+ step();
483
+ };
484
+ ```
485
+
486
+ Whatever a live frame does — the fade, the stroke colour, a per-frame drift — the
487
+ still path has to do too, or the card is a different picture from the one its
488
+ author kept. `p.noLoop()` stops `draw` from being scheduled; `p.loop()` starts it again, and
489
+ `p.redraw()` runs one frame on demand. A wall of stills is cheap because none
490
+ of them are animating.
491
+
492
+ ## Performance
493
+
494
+ A flow field draws thousands of segments a frame, so the per-item work is what
495
+ decides whether it holds sixty frames a second.
496
+
497
+ **Group by colour so `stroke()` changes a handful of times per frame instead of
498
+ once per particle.** Bucket particles by palette band, sort by colour, or just
499
+ guard the call:
500
+
501
+ ```js
502
+ let cur = null;
503
+ for (const q of particles) {
504
+ if (q.c !== cur) {
505
+ cur = q.c;
506
+ p.stroke(cur);
507
+ }
508
+ p.line(q.px, q.py, q.x, q.y);
509
+ }
510
+ ```
511
+
512
+ **Rotate coordinates by hand instead of `push`/`translate`/`rotate`/`pop` per
513
+ item.** Precompute the sine and cosine of each symmetry arm once, then transform
514
+ the two endpoints with arithmetic — the difference between one transform matrix
515
+ and eleven thousand of them.
516
+
517
+ ```js
518
+ const arms = [];
519
+ for (let s = 0; s < symmetry; s++) {
520
+ const a = (TAU / symmetry) * s;
521
+ arms.push([Math.cos(a), Math.sin(a)]);
522
+ }
523
+ // per segment:
524
+ for (const [co, si] of arms) {
525
+ p.line(cx + ax * co - ay * si, cy + ax * si + ay * co, cx + bx * co - by * si, cy + bx * si + by * co);
526
+ }
527
+ ```
528
+
529
+ **Use a translucent rectangle for trails instead of `background()`.** Painting a
530
+ nearly-transparent rect over the canvas each frame dims the history a few
531
+ percent rather than erasing it, which is most of what people mean when a sketch
532
+ "looks generative". The alpha is the knob: lower is smokier, higher snaps back
533
+ to dots.
534
+
535
+ ```js
536
+ p.noStroke();
537
+ p.fill(20, 20, 20, 12);
538
+ p.rect(0, 0, width, height);
539
+ ```
540
+
541
+ Other levers: keep the particle array a fixed size and recycle dead particles
542
+ rather than allocating; keep React state out of the frame loop entirely (the
543
+ loop mutates plain objects the sketch closed over, and React only ever sets the
544
+ parameters); and for a field dense enough that 2D runs out of road, `WEBGL` mode
545
+ with `p.createShader(vert, frag)` evaluates it per pixel on the GPU.
546
+
547
+ ## Mouse and touch
548
+
549
+ p5 keeps `p.mouseX` / `p.mouseY` current and fills them from a touch too, so one
550
+ handler usually covers both surfaces.
551
+
552
+ ```js
553
+ p.mouseDragged = () => {
554
+ paintAt(p.mouseX, p.mouseY);
555
+ };
556
+
557
+ p.touchMoved = () => {
558
+ paintAt(p.mouseX, p.mouseY);
559
+ return false; // returning false prevents the browser's default scroll/zoom
560
+ };
561
+ ```
562
+
563
+ **Set `touchAction: "none"` on the host element** so a drag paints instead of
564
+ scrolling the page on a phone. Returning `false` from `touchMoved` handles the
565
+ gesture p5 sees; the CSS handles the gesture the browser would have taken first.
566
+
567
+ ## API surface
568
+
569
+ ### Canvas and lifecycle
570
+
571
+ ```js
572
+ p.createCanvas(w, h); // or p.createCanvas(w, h, p.WEBGL)
573
+ p.resizeCanvas(w, h);
574
+ p.pixelDensity(1);
575
+ p.noLoop();
576
+ p.loop();
577
+ p.redraw();
578
+ p.frameRate(30);
579
+ p.frameCount; // frames since setup
580
+ p.deltaTime; // ms since the previous frame
581
+ ```
582
+
583
+ ### Drawing
584
+
585
+ ```js
586
+ p.background("#040a18");
587
+ p.fill(255, 120, 0, 40); // r, g, b, alpha 0-255
588
+ p.noFill();
589
+ p.stroke("#00e5ff55"); // 8-digit hex carries alpha
590
+ p.noStroke();
591
+ p.strokeWeight(1.5);
592
+
593
+ p.line(x1, y1, x2, y2);
594
+ p.circle(x, y, d);
595
+ p.ellipse(x, y, w, h);
596
+ p.rect(x, y, w, h, radius);
597
+ p.triangle(x1, y1, x2, y2, x3, y3);
598
+ p.point(x, y);
599
+
600
+ p.beginShape();
601
+ p.vertex(x, y);
602
+ p.curveVertex(x, y);
603
+ p.endShape(p.CLOSE);
604
+
605
+ p.text("hello", x, y);
606
+ p.textSize(16);
607
+ p.textAlign(p.CENTER, p.CENTER);
608
+ ```
609
+
610
+ ### Colour
611
+
612
+ ```js
613
+ p.colorMode(p.HSB, 360, 100, 100, 1);
614
+ const c = p.color(200, 80, 90);
615
+ p.lerpColor(a, b, 0.3);
616
+ ```
617
+
618
+ ### Numbers, randomness, noise
619
+
620
+ ```js
621
+ p.random(max); // p.random(min, max), p.random(array)
622
+ p.randomSeed(1234);
623
+ p.noise(x, y, z); // 0..1 Perlin noise
624
+ p.noiseSeed(1234);
625
+ p.noiseDetail(octaves, falloff);
626
+ p.map(v, inMin, inMax, outMin, outMax);
627
+ p.constrain(v, min, max);
628
+ p.lerp(a, b, t);
629
+ p.dist(x1, y1, x2, y2);
630
+ p.TWO_PI;
631
+ p.sin(a);
632
+ p.cos(a);
633
+ ```
634
+
635
+ ### Transforms
636
+
637
+ ```js
638
+ p.push();
639
+ p.translate(x, y);
640
+ p.rotate(angle);
641
+ p.scale(s);
642
+ p.pop();
643
+ ```
644
+
645
+ ### Input
646
+
647
+ ```js
648
+ p.mouseX;
649
+ p.mouseY;
650
+ p.pmouseX;
651
+ p.pmouseY;
652
+ p.mouseIsPressed;
653
+ p.mousePressed = () => {};
654
+ p.mouseDragged = () => {};
655
+ p.mouseReleased = () => {};
656
+ p.mouseWheel = (event) => {};
657
+ p.touchStarted = () => {};
658
+ p.touchMoved = () => false;
659
+ p.touchEnded = () => {};
660
+ p.keyPressed = () => {};
661
+ p.key;
662
+ p.keyCode;
663
+ ```
664
+
665
+ ### Pixels and export
666
+
667
+ ```js
668
+ p.loadPixels();
669
+ p.pixels; // flat RGBA array
670
+ p.updatePixels();
671
+ p.get(x, y); // one pixel
672
+ p.createGraphics(w, h); // an offscreen buffer to draw into
673
+ p.saveCanvas("piece", "png");
674
+ ```
675
+
676
+ ### WEBGL
677
+
678
+ ```js
679
+ const shader = p.createShader(vertSource, fragSource);
680
+ p.shader(shader);
681
+ shader.setUniform("u_time", p.millis() / 1000);
682
+ shader.setUniform("u_resolution", [w, h]);
683
+ p.rect(-w / 2, -h / 2, w, h); // a full-canvas quad for the fragment shader
684
+ p.box(50);
685
+ p.sphere(40);
686
+ p.orbitControl();
687
+ ```
688
+
689
+ ## Common gotchas
690
+
691
+ **A global `function setup()` never runs.** In instance mode p5 only calls the
692
+ hooks it finds on the instance, so a sketch written the tutorial way mounts a
693
+ blank canvas and nothing reports a problem.
694
+
695
+ **A missing `instance.remove()` is invisible until it is loud.** The symptom is
696
+ a sketch that speeds up, brightens, or tears after a few parameter changes —
697
+ that is two or four draw loops painting the same canvas.
698
+
699
+ **`p.resizeCanvas` clears the drawing.** Repaint the background and reseed the
700
+ simulation from `windowResized` rather than assuming the previous frame survived.
701
+
702
+ **Do not drive the sketch from React state per frame.** A `setState` inside
703
+ `draw` re-renders the tree sixty times a second; let the loop mutate plain
704
+ objects and keep React for the controls.
705
+
706
+ **`p.random()` is seeded; `Math.random()` is not.** Mixing them makes a sketch
707
+ that looks reproducible until it isn't.
708
+
709
+ **Colour with alpha is how trails and depth are made.** An 8-digit hex string
710
+ (`"#00e5ff55"`) or a fourth argument to `fill`/`stroke` is usually the
711
+ difference between a diagram and a picture.
712
+
713
+ **One instance per host element.** Mounting a second p5 into the same node gives
714
+ two canvases stacked in the same box.