Source of: Luminous Current, by GPT-5.6 Sol
155 lines, 3 of them comments ยท 1,656 tokens, written in 16 seconds for $0.034
1 let particles = [];
2 let paletteIndex = 0;
3 let t = 0;
4
5 const palettes = [
6 ['#61E8E1', '#7C83FD', '#F96EAD', '#FFF3B0'],
7 ['#FF6B35', '#F7C59F', '#EFEFD0', '#3D5A80'],
8 ['#9EF01A', '#38B000', '#00B4D8', '#CAF0F8'],
9 ['#E0AAFF', '#C77DFF', '#7B2CBF', '#FFAFCC']
10 ];
11
12 function setup() {
13 createCanvas(windowWidth, windowHeight);
14 pixelDensity(min(window.devicePixelRatio || 1, 2));
15 colorMode(RGB, 255, 255, 255, 255);
16 background(4, 7, 18);
17 initializeParticles();
18 }
19
20 function initializeParticles() {
21 particles = [];
22 const count = constrain(floor((width * height) / 3800), 160, 520);
23 for (let i = 0; i < count; i++) {
24 particles.push(new Particle(random(width), random(height)));
25 }
26 }
27
28 function draw() {
29 // A translucent veil creates persistent light trails.
30 noStroke();
31 fill(4, 7, 18, 18);
32 rect(0, 0, width, height);
33
34 blendMode(ADD);
35 for (const particle of particles) {
36 particle.followFlow();
37 particle.reactToPointer();
38 particle.update();
39 particle.display();
40 particle.wrap();
41 }
42 blendMode(BLEND);
43
44 drawPointerGlow();
45 t += 0.003;
46 }
47
48 function drawPointerGlow() {
49 if (!mouseIsPressed || mouseX < 0 || mouseX > width || mouseY < 0 || mouseY > height) return;
50
51 const pulse = 55 + sin(frameCount * 0.12) * 12;
52 noFill();
53 strokeWeight(1.2);
54 for (let r = pulse; r > 8; r -= 9) {
55 const c = color(palettes[paletteIndex][r % palettes[paletteIndex].length]);
56 c.setAlpha(map(r, 8, pulse, 85, 5));
57 stroke(c);
58 circle(mouseX, mouseY, r * 2);
59 }
60 }
61
62 class Particle {
63 constructor(x, y) {
64 this.pos = createVector(x, y);
65 this.prev = this.pos.copy();
66 this.vel = p5.Vector.random2D().mult(random(0.2, 1));
67 this.acc = createVector();
68 this.speed = random(0.8, 2.8);
69 this.weight = random(0.4, 1.7);
70 this.life = random(TWO_PI);
71 this.colorSlot = floor(random(palettes[0].length));
72 }
73
74 followFlow() {
75 const scale = 0.0022;
76 const n = noise(this.pos.x * scale, this.pos.y * scale, t);
77 const angle = n * TAU * 3.2 + sin(t * 4 + this.pos.y * 0.002) * 0.5;
78 this.acc.add(p5.Vector.fromAngle(angle).mult(0.085));
79 }
80
81 reactToPointer() {
82 if (mouseX < 0 || mouseX > width || mouseY < 0 || mouseY > height) return;
83
84 const pointer = createVector(mouseX, mouseY);
85 const offset = p5.Vector.sub(pointer, this.pos);
86 const distance = offset.mag();
87 const radius = mouseIsPressed ? 240 : 130;
88
89 if (distance > 2 && distance < radius) {
90 const strength = pow(1 - distance / radius, 2);
91 offset.normalize();
92
93 if (mouseIsPressed) {
94 // Tangential force produces a vortex with a gentle inward pull.
95 const tangent = createVector(-offset.y, offset.x);
96 this.acc.add(tangent.mult(strength * 0.55));
97 this.acc.add(offset.mult(strength * 0.12));
98 } else {
99 // The resting pointer softly parts the stream.
100 this.acc.sub(offset.mult(strength * 0.16));
101 }
102 }
103 }
104
105 update() {
106 this.prev.set(this.pos);
107 this.vel.add(this.acc);
108 this.vel.limit(this.speed);
109 this.pos.add(this.vel);
110 this.acc.mult(0);
111 this.life += 0.025;
112 }
113
114 display() {
115 const base = color(palettes[paletteIndex][this.colorSlot]);
116 const shimmer = map(sin(this.life), -1, 1, 35, 150);
117 const velocityGlow = map(this.vel.mag(), 0, this.speed, 0.5, 1);
118
119 base.setAlpha(shimmer * velocityGlow);
120 stroke(base);
121 strokeWeight(this.weight);
122 line(this.prev.x, this.prev.y, this.pos.x, this.pos.y);
123
124 if (this.weight > 1.25) {
125 base.setAlpha(25);
126 stroke(base);
127 strokeWeight(this.weight * 4);
128 point(this.pos.x, this.pos.y);
129 }
130 }
131
132 wrap() {
133 let wrapped = false;
134 if (this.pos.x < -5) { this.pos.x = width + 5; wrapped = true; }
135 if (this.pos.x > width + 5) { this.pos.x = -5; wrapped = true; }
136 if (this.pos.y < -5) { this.pos.y = height + 5; wrapped = true; }
137 if (this.pos.y > height + 5) { this.pos.y = -5; wrapped = true; }
138 if (wrapped) this.prev.set(this.pos);
139 }
140 }
141
142 function mousePressed() {
143 paletteIndex = (paletteIndex + 1) % palettes.length;
144 }
145
146 function keyPressed() {
147 if (key === 'c' || key === 'C') background(4, 7, 18);
148 if (key === 's' || key === 'S') saveCanvas('luminous-current', 'png');
149 }
150
151 function windowResized() {
152 resizeCanvas(windowWidth, windowHeight);
153 background(4, 7, 18);
154 initializeParticles();
155 }