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  }