202 lines, no comments · 3,637 tokens, written in 38 seconds for $0.037


  1  let trails, particles = [], stars = [];
  2  let paletteIndex = 0;
  3  let pointerActive = false;
  4  const palettes = [
  5    { name: 'BLUE HOUR', colors: [[77, 221, 235], [120, 150, 255], [255, 187, 137]] },
  6    { name: 'SOLAR WIND', colors: [[255, 174, 105], [255, 101, 146], [255, 225, 166]] },
  7    { name: 'NIGHT GARDEN', colors: [[108, 239, 177], [111, 193, 248], [213, 171, 255]] }
  8  ];
  9  
 10  function setup() {
 11    createCanvas(windowWidth, windowHeight);
 12    pixelDensity(min(2, window.devicePixelRatio || 1));
 13    frameRate(60);
 14    initialize();
 15  }
 16  
 17  function initialize() {
 18    trails = createGraphics(width, height);
 19    trails.pixelDensity(1);
 20    particles = [];
 21    stars = [];
 22    for (let i = 0; i < 150; i++) {
 23      stars.push({ x: random(), y: random(), size: random(0.5, 1.8), phase: random(TWO_PI) });
 24    }
 25    for (let i = 0; i < 440; i++) {
 26      const p = {
 27        angle: random(TWO_PI),
 28        radius: random(0.15, 0.46),
 29        speed: random(0.0015, 0.0045) * (random() < 0.14 ? -1 : 1),
 30        wobble: random(4, 19),
 31        phase: random(TWO_PI),
 32        color: floor(random(3)),
 33        weight: random(0.45, 1.65)
 34      };
 35      p.previous = particlePosition(p);
 36      particles.push(p);
 37    }
 38  }
 39  
 40  function particlePosition(p) {
 41    const size = min(width, height);
 42    const centerX = width * 0.5;
 43    const centerY = height * 0.51;
 44    const r = p.radius * size + sin(p.angle * 3 + p.phase) * p.wobble
 45            + sin(p.angle * 7 - p.phase + frameCount * 0.008) * 3;
 46    let x = centerX + cos(p.angle) * r * 1.07;
 47    let y = centerY + sin(p.angle) * r * 0.87;
 48  
 49    if (pointerActive) {
 50      const dx = x - mouseX;
 51      const dy = y - mouseY;
 52      const d = sqrt(dx * dx + dy * dy);
 53      const reach = min(190, size * 0.34);
 54      if (d > 1 && d < reach) {
 55        const force = pow(1 - d / reach, 2) * 45;
 56        x -= dy / d * force;
 57        y += dx / d * force;
 58      }
 59    }
 60    return { x, y };
 61  }
 62  
 63  function draw() {
 64    const ctx = drawingContext;
 65    const bg = ctx.createLinearGradient(0, 0, width, height);
 66    bg.addColorStop(0, '#071426');
 67    bg.addColorStop(0.55, '#101b32');
 68    bg.addColorStop(1, '#050d1c');
 69    ctx.fillStyle = bg;
 70    ctx.fillRect(0, 0, width, height);
 71  
 72    drawStars();
 73    updateTrails();
 74    image(trails, 0, 0);
 75    drawOrbits();
 76    drawCore();
 77    drawLabels();
 78  }
 79  
 80  function drawStars() {
 81    noStroke();
 82    for (const s of stars) {
 83      const alpha = 65 + 75 * (0.5 + 0.5 * sin(frameCount * 0.018 + s.phase));
 84      fill(190, 218, 245, alpha);
 85      circle(s.x * width, s.y * height, s.size);
 86    }
 87  }
 88  
 89  function updateTrails() {
 90    trails.noStroke();
 91    trails.fill(5, 13, 28, 17);
 92    trails.rect(0, 0, width, height);
 93    trails.blendMode(ADD);
 94    for (const p of particles) {
 95      p.angle += p.speed;
 96      const next = particlePosition(p);
 97      const c = palettes[paletteIndex].colors[p.color];
 98      if (dist(next.x, next.y, p.previous.x, p.previous.y) < 35) {
 99        trails.stroke(c[0], c[1], c[2], 48);
100        trails.strokeWeight(p.weight);
101        trails.line(p.previous.x, p.previous.y, next.x, next.y);
102      }
103      if (frameCount % 3 === 0 && p.weight > 1.1) {
104        trails.noStroke();
105        trails.fill(c[0], c[1], c[2], 95);
106        trails.circle(next.x, next.y, p.weight * 1.5);
107      }
108      p.previous = next;
109    }
110    trails.blendMode(BLEND);
111  }
112  
113  function drawOrbits() {
114    const size = min(width, height);
115    const cx = width * 0.5;
116    const cy = height * 0.51;
117    const c = palettes[paletteIndex].colors[0];
118    noFill();
119  
120    for (let ring = 0; ring < 5; ring++) {
121      const radius = size * (0.17 + ring * 0.067);
122      stroke(c[0], c[1], c[2], ring % 2 === 0 ? 32 : 17);
123      strokeWeight(1);
124      beginShape();
125      for (let a = 0; a <= TWO_PI + 0.025; a += 0.025) {
126        const r = radius + sin(a * 5 + ring * 1.8 + frameCount * 0.005) * (2 + ring);
127        vertex(cx + cos(a) * r * 1.07, cy + sin(a) * r * 0.87);
128      }
129      endShape();
130    }
131  
132    push();
133    translate(cx, cy);
134    rotate(frameCount * 0.0009);
135    stroke(c[0], c[1], c[2], 115);
136    strokeWeight(1.3);
137    const r = size * 0.39;
138    arc(0, 0, r * 2.14, r * 1.74, -0.35, 0.48);
139    arc(0, 0, r * 2.14, r * 1.74, PI - 0.35, PI + 0.48);
140    pop();
141  }
142  
143  function drawCore() {
144    const ctx = drawingContext;
145    const x = width * 0.5;
146    const y = height * 0.51;
147    const r = min(width, height) * 0.092;
148    const c = palettes[paletteIndex].colors[0];
149  
150    const glow = ctx.createRadialGradient(x, y, r * 0.65, x, y, r * 2.3);
151    glow.addColorStop(0, `rgba(${c[0]},${c[1]},${c[2]},0.20)`);
152    glow.addColorStop(1, `rgba(${c[0]},${c[1]},${c[2]},0)`);
153    ctx.fillStyle = glow;
154    ctx.beginPath();
155    ctx.arc(x, y, r * 2.3, 0, TWO_PI);
156    ctx.fill();
157  
158    noStroke();
159    fill(c[0], c[1], c[2], 35);
160    circle(x, y, r * 2.24);
161    fill(5, 13, 29);
162    circle(x, y, r * 2.02);
163    noFill();
164    stroke(c[0], c[1], c[2], 175);
165    strokeWeight(1);
166    circle(x, y, r * 2.02);
167    stroke(230, 245, 255, 40);
168    circle(x, y, r * 1.7);
169  
170    noStroke();
171    fill(225, 241, 248, 180);
172    circle(x, y, 3);
173    stroke(c[0], c[1], c[2], 90);
174    line(x - r * 0.35, y, x - r * 0.13, y);
175    line(x + r * 0.13, y, x + r * 0.35, y);
176  }
177  
178  function drawLabels() {
179    const pad = max(24, min(width, height) * 0.055);
180    noStroke();
181    textFont('monospace');
182    textAlign(LEFT, TOP);
183    fill(214, 232, 244, 195);
184    textSize(12);
185    text('T I D A L   A T L A S', pad, pad);
186    fill(170, 204, 222, 105);
187    textSize(10);
188    text(palettes[paletteIndex].name + '  /  0' + (paletteIndex + 1), pad, pad + 23);
189    textAlign(RIGHT, BOTTOM);
190    text('MOVE TO BEND  ·  CLICK TO SHIFT', width - pad, height - pad);
191  }
192  
193  function mouseMoved() { pointerActive = true; }
194  function touchMoved() { pointerActive = true; return false; }
195  function mousePressed() {
196    paletteIndex = (paletteIndex + 1) % palettes.length;
197    trails.clear();
198  }
199  function windowResized() {
200    resizeCanvas(windowWidth, windowHeight);
201    initialize();
202  }