Source of: Nacre Tide, by GPT-6 Luna
91 lines, 3 of them comments ยท 2,109 tokens, written in 33 seconds for $0.0011
1 let particles = [];
2 const PARTICLE_COUNT = 950;
3
4 function setup() {
5 createCanvas(windowWidth, windowHeight);
6 pixelDensity(1);
7 colorMode(HSB, 360, 100, 100, 100);
8 background(228, 55, 8);
9 for (let i = 0; i < PARTICLE_COUNT; i++) particles.push(makeParticle());
10 }
11
12 function makeParticle() {
13 const x = random(width);
14 const y = random(height);
15 return { x, y, px: x, py: y, vx: 0, vy: 0, seed: random(1000) };
16 }
17
18 function draw() {
19 // A transparent veil keeps old strokes lingering as soft trails.
20 blendMode(BLEND);
21 noStroke();
22 fill(228, 55, 8, 11);
23 rect(0, 0, width, height);
24
25 const cx = width * 0.5;
26 const cy = height * 0.5;
27 const t = frameCount * 0.008;
28
29 blendMode(ADD);
30 for (const p of particles) {
31 p.px = p.x;
32 p.py = p.y;
33
34 const dx = p.x - cx;
35 const dy = p.y - cy;
36 const radius = sqrt(dx * dx + dy * dy);
37 const angle = atan2(dy, dx);
38
39 // A rotating field, gently rippled into nested tidal bands.
40 const ripple = 0.85 * sin(radius * 0.012 - t + 3 * angle + p.seed);
41 const flowAngle = angle + HALF_PI + ripple + 0.22 * cos(5 * angle - t * 1.4);
42 let ax = cos(flowAngle);
43 let ay = sin(flowAngle);
44
45 if (mouseIsPressed) {
46 const mx = mouseX - p.x;
47 const my = mouseY - p.y;
48 const md = sqrt(mx * mx + my * my) + 1;
49 const influence = 180 / (180 + md);
50 ax += (mx / md) * influence * 1.8;
51 ay += (my / md) * influence * 1.8;
52 }
53
54 const speed = 0.8 + 0.75 * (0.5 + 0.5 * sin(radius * 0.009 + p.seed));
55 const targetVx = ax * speed;
56 const targetVy = ay * speed;
57 p.vx = lerp(p.vx, targetVx, 0.12);
58 p.vy = lerp(p.vy, targetVy, 0.12);
59 p.x += p.vx;
60 p.y += p.vy;
61
62 const hue = (190 + 95 * (0.5 + 0.5 * sin(angle * 2 + radius * 0.003 - t * 2)) + 360) % 360;
63 const alpha = 22 + 38 * (0.5 + 0.5 * sin(p.seed + t));
64 stroke(hue, 72, 100, alpha);
65 strokeWeight(0.7 + 0.65 * (0.5 + 0.5 * sin(p.seed * 2)));
66 line(p.px, p.py, p.x, p.y);
67
68 if (p.x < -10 || p.x > width + 10 || p.y < -10 || p.y > height + 10) {
69 Object.assign(p, makeParticle());
70 }
71 }
72
73 // A small, breathing pearl of light at the heart of the current.
74 const pulse = 1 + 0.12 * sin(frameCount * 0.035);
75 noStroke();
76 for (let i = 7; i >= 1; i--) {
77 fill(190 + i * 5, 65, 100, 2.5);
78 circle(cx, cy, i * 11 * pulse);
79 }
80 noFill();
81 stroke(188, 35, 100, 32);
82 strokeWeight(0.8);
83 circle(cx, cy, 28 * pulse);
84 blendMode(BLEND);
85 }
86
87 function windowResized() {
88 resizeCanvas(windowWidth, windowHeight);
89 background(228, 55, 8);
90 for (const p of particles) Object.assign(p, makeParticle());
91 }