Source of: Luminous Tide, by GPT-5.6 Sol
134 lines, no comments ยท 1,341 tokens, written in 13 seconds for $0.027
1 const particles = [];
2 const PARTICLE_COUNT = 900;
3 let fieldScale = 0.0028;
4 let timeOffset = 0;
5
6 function setup() {
7 createCanvas(windowWidth, windowHeight);
8 pixelDensity(1);
9 colorMode(HSB, 360, 100, 100, 100);
10 background(225, 45, 7);
11 blendMode(ADD);
12
13 for (let i = 0; i < PARTICLE_COUNT; i++) {
14 particles.push(new Particle(random(width), random(height)));
15 }
16 }
17
18 function draw() {
19 blendMode(BLEND);
20 noStroke();
21 fill(225, 45, 7, 7);
22 rect(0, 0, width, height);
23 blendMode(ADD);
24
25 const pointerActive = mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height;
26
27 for (const p of particles) {
28 p.followFlow();
29 if (pointerActive) p.reactToPointer();
30 p.update();
31 p.display();
32 p.wrap();
33 }
34
35 timeOffset += 0.0025;
36 }
37
38 class Particle {
39 constructor(x, y) {
40 this.pos = createVector(x, y);
41 this.prev = this.pos.copy();
42 this.vel = p5.Vector.random2D().mult(random(0.2, 1.2));
43 this.acc = createVector();
44 this.maxSpeed = random(1.2, 3.4);
45 this.hueShift = random(-28, 28);
46 this.weight = random(0.35, 1.45);
47 }
48
49 followFlow() {
50 const n = noise(
51 this.pos.x * fieldScale,
52 this.pos.y * fieldScale,
53 timeOffset
54 );
55 const angle = n * TAU * 3.2 + sin(timeOffset * 3) * 0.3;
56 const force = p5.Vector.fromAngle(angle).mult(0.055);
57 this.acc.add(force);
58 }
59
60 reactToPointer() {
61 const pointer = createVector(mouseX, mouseY);
62 const delta = p5.Vector.sub(pointer, this.pos);
63 const d = delta.mag();
64
65 if (d < 180 && d > 1) {
66 delta.normalize();
67 const strength = map(d, 0, 180, 0.16, 0);
68 const tangent = createVector(-delta.y, delta.x).mult(strength * 1.8);
69 delta.mult(mouseIsPressed ? -strength * 2.2 : strength * 0.55);
70 this.acc.add(delta).add(tangent);
71 }
72 }
73
74 update() {
75 this.prev.set(this.pos);
76 this.vel.add(this.acc);
77 this.vel.limit(this.maxSpeed);
78 this.pos.add(this.vel);
79 this.acc.mult(0);
80 }
81
82 display() {
83 const speed = this.vel.mag();
84 const baseHue = map(this.pos.x, 0, width, 175, 315);
85 const hue = (baseHue + this.hueShift + frameCount * 0.08) % 360;
86 const alpha = map(speed, 0, this.maxSpeed, 15, 55);
87
88 stroke(hue, 65, 100, alpha);
89 strokeWeight(this.weight);
90 line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
91
92 if (random() < 0.008) {
93 noStroke();
94 fill(hue, 35, 100, 30);
95 circle(this.pos.x, this.pos.y, random(2, 6));
96 }
97 }
98
99 wrap() {
100 let wrapped = false;
101 if (this.pos.x < 0) { this.pos.x = width; wrapped = true; }
102 if (this.pos.x > width) { this.pos.x = 0; wrapped = true; }
103 if (this.pos.y < 0) { this.pos.y = height; wrapped = true; }
104 if (this.pos.y > height) { this.pos.y = 0; wrapped = true; }
105 if (wrapped) this.prev.set(this.pos);
106 }
107 }
108
109 function mousePressed() {
110 for (const p of particles) {
111 const burst = p5.Vector.sub(p.pos, createVector(mouseX, mouseY));
112 const d = burst.mag();
113 if (d < 240 && d > 1) {
114 burst.normalize().mult(map(d, 0, 240, 5, 0));
115 p.vel.add(burst);
116 }
117 }
118 }
119
120 function keyPressed() {
121 if (key === 'r' || key === 'R') {
122 background(225, 45, 7);
123 for (const p of particles) {
124 p.pos.set(random(width), random(height));
125 p.prev.set(p.pos);
126 p.vel = p5.Vector.random2D();
127 }
128 }
129 }
130
131 function windowResized() {
132 resizeCanvas(windowWidth, windowHeight);
133 background(225, 45, 7);
134 }