Source of: Tidal Atlas, by GPT-6 Sol
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 }