112 lines, 2 of them comments ยท 1,313 tokens, written in 23 seconds for $0.067


  1  const palettes = [
  2    ['#42e8e0', '#60a5fa', '#c4b5fd', '#fef3c7'],
  3    ['#fb7185', '#ffb86b', '#fde68a', '#f472b6'],
  4    ['#a3e635', '#34d399', '#22d3ee', '#e0f2fe']
  5  ];
  6  
  7  let particles = [];
  8  let ink = [];
  9  let paletteIndex = 0;
 10  let time = 0;
 11  
 12  function setup() {
 13    createCanvas(windowWidth, windowHeight);
 14    pixelDensity(1);
 15    strokeCap(ROUND);
 16    resetScene();
 17  }
 18  
 19  function resetScene() {
 20    background(5, 9, 20);
 21    noiseSeed(floor(random(100000)));
 22    ink = palettes[paletteIndex].map(c => color(c));
 23    const count = floor(constrain(width * height / 850, 350, 1600));
 24    particles = Array.from({ length: count }, () => new Stream());
 25    time = 0;
 26  }
 27  
 28  function draw() {
 29    blendMode(BLEND);
 30    noStroke();
 31    fill(5, 9, 20, 16);
 32    rect(0, 0, width, height);
 33  
 34    blendMode(ADD);
 35    for (const p of particles) p.update();
 36    blendMode(BLEND);
 37    time += 0.002;
 38  }
 39  
 40  class Stream {
 41    constructor() {
 42      this.reset(true);
 43    }
 44  
 45    reset(scatter = false) {
 46      this.x = random(width);
 47      this.y = random(height);
 48      this.age = scatter ? random(220) : 0;
 49      this.life = random(220, 480);
 50      this.speed = random(0.8, 2.4);
 51      this.weight = random(0.5, 1.6);
 52      this.tone = floor(random(ink.length));
 53    }
 54  
 55    update() {
 56      const oldX = this.x;
 57      const oldY = this.y;
 58      const scale = 0.0018;
 59      const field = noise(this.x * scale, this.y * scale, time);
 60      const angle = field * TAU * 2.5;
 61  
 62      let vx = cos(angle) * this.speed;
 63      let vy = sin(angle) * this.speed;
 64  
 65      // A soft central eddy gives the currents an orbital structure.
 66      const cx = this.x - width * 0.5;
 67      const cy = this.y - height * 0.5;
 68      const radius = sqrt(cx * cx + cy * cy) + 1;
 69      const swirl = 0.7 * exp(-radius / max(width, height));
 70      vx -= cy / radius * swirl;
 71      vy += cx / radius * swirl;
 72  
 73      // The pointer acts like a small swirling obstacle.
 74      if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
 75        const dx = this.x - mouseX;
 76        const dy = this.y - mouseY;
 77        const d = sqrt(dx * dx + dy * dy) + 1;
 78        const force = 3.5 * exp(-d * d / 14000);
 79        vx += (dx - dy * 0.7) / d * force;
 80        vy += (dy + dx * 0.7) / d * force;
 81      }
 82  
 83      this.x += vx;
 84      this.y += vy;
 85      this.age++;
 86  
 87      if (this.x < -5 || this.x > width + 5 || this.y < -5 || this.y > height + 5 || this.age > this.life) {
 88        this.reset();
 89        return;
 90      }
 91  
 92      const fade = min(1, this.age / 30, (this.life - this.age) / 45);
 93      const c = ink[this.tone];
 94      stroke(red(c), green(c), blue(c), 65 * max(0, fade));
 95      strokeWeight(this.weight);
 96      line(oldX, oldY, this.x, this.y);
 97    }
 98  }
 99  
100  function mousePressed() {
101    paletteIndex = (paletteIndex + 1) % palettes.length;
102    ink = palettes[paletteIndex].map(c => color(c));
103  }
104  
105  function keyPressed() {
106    if (key === 'r' || key === 'R') resetScene();
107  }
108  
109  function windowResized() {
110    resizeCanvas(windowWidth, windowHeight);
111    resetScene();
112  }