131 lines, 1 of them comments · 1,833 tokens, written in 37 seconds for $0.093


  1  let ink;
  2  let particles = [];
  3  let clock = 0;
  4  let paletteIndex = 0;
  5  const palettes = [
  6    [165, 195, 225, 275],
  7    [325, 350, 20, 45],
  8    [235, 265, 295, 320],
  9    [110, 150, 175, 200]
 10  ];
 11  
 12  function setup() {
 13    createCanvas(windowWidth, windowHeight);
 14    pixelDensity(1);
 15    colorMode(HSB, 360, 100, 100, 100);
 16    textFont('monospace');
 17    resetCurrent();
 18  }
 19  
 20  function resetCurrent() {
 21    if (ink) ink.remove();
 22    ink = createGraphics(width, height);
 23    ink.pixelDensity(1);
 24    ink.colorMode(HSB, 360, 100, 100, 100);
 25    ink.background(230, 55, 5);
 26    noiseSeed(floor(random(100000)));
 27    clock = 0;
 28    const count = floor(constrain(width * height / 650, 500, 1900));
 29    particles = Array.from({ length: count }, () => makeParticle());
 30    for (let i = 0; i < 45; i++) advance(false);
 31  }
 32  
 33  function makeParticle() {
 34    return {
 35      x: random(width),
 36      y: random(height),
 37      life: random(100, 420),
 38      speed: random(0.8, 2.2),
 39      tint: random(),
 40      weight: random(0.45, 1.45)
 41    };
 42  }
 43  
 44  function advance(interactive) {
 45    ink.noStroke();
 46    ink.fill(230, 55, 5, 4.2);
 47    ink.rect(0, 0, width, height);
 48    clock += 0.0025;
 49    const scale = 0.0018;
 50    const colors = palettes[paletteIndex];
 51    const radius = min(width, height) * 0.3;
 52    const active = interactive && mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height;
 53  
 54    for (const p of particles) {
 55      const oldX = p.x;
 56      const oldY = p.y;
 57      const n = noise(p.x * scale, p.y * scale, clock * 0.28);
 58      const angle = n * TWO_PI * 2.7 + sin(p.y * 0.002 + clock) * 0.55;
 59      let vx = cos(angle) * p.speed;
 60      let vy = sin(angle) * p.speed;
 61  
 62      if (active) {
 63        const dx = mouseX - p.x;
 64        const dy = mouseY - p.y;
 65        const d = sqrt(dx * dx + dy * dy);
 66        if (d < radius && d > 1) {
 67          const influence = pow(1 - d / radius, 2) * 4.5;
 68          vx += (dx * 0.25 - dy) / d * influence;
 69          vy += (dy * 0.25 + dx) / d * influence;
 70        }
 71      }
 72  
 73      p.x += vx;
 74      p.y += vy;
 75      p.life--;
 76      if (p.life < 0 || p.x < -5 || p.x > width + 5 || p.y < -5 || p.y > height + 5) {
 77        Object.assign(p, makeParticle());
 78        continue;
 79      }
 80  
 81      const position = constrain(n * 0.75 + p.tint * 0.25, 0, 0.999) * (colors.length - 1);
 82      const index = floor(position);
 83      const h1 = colors[index];
 84      const h2 = colors[index + 1];
 85      const delta = ((h2 - h1 + 540) % 360) - 180;
 86      const hue = (h1 + delta * (position - index) + 360) % 360;
 87      const brightness = 65 + 35 * sin(PI * min(p.life / 55, 1) / 2);
 88  
 89      ink.stroke(hue, 66, brightness, 44);
 90      ink.strokeWeight(p.weight);
 91      ink.line(oldX, oldY, p.x, p.y);
 92    }
 93  }
 94  
 95  function draw() {
 96    advance(true);
 97    image(ink, 0, 0);
 98  
 99    // A quiet editorial label, separate from the persistent drawing layer.
100    noStroke();
101    fill(230, 45, 5, 72);
102    rect(22, 22, 190, 54, 8);
103    fill(190, 12, 96, 92);
104    textSize(13);
105    text('S I L K  C U R R E N T S', 35, 44);
106    fill(190, 15, 80, 65);
107    textSize(9);
108    text('A STUDY IN CONTINUOUS MOTION', 35, 62);
109  
110    const hint = width < 480 ? 'MOVE · CLICK · R TO RESET' : 'MOVE TO BEND  /  CLICK FOR COLOR  /  R TO RESET';
111    textSize(10);
112    const labelWidth = textWidth(hint) + 26;
113    fill(230, 45, 5, 72);
114    rect(22, height - 49, labelWidth, 28, 7);
115    fill(190, 12, 87, 75);
116    text(hint, 35, height - 31);
117  }
118  
119  function mousePressed() {
120    paletteIndex = (paletteIndex + 1) % palettes.length;
121  }
122  
123  function keyPressed() {
124    if (key === 'r' || key === 'R') resetCurrent();
125  }
126  
127  function windowResized() {
128    resizeCanvas(windowWidth, windowHeight);
129    resetCurrent();
130  }
131