Source of: Luminous Tides, by GPT-6 Sol
147 lines, 5 of them comments ยท 2,900 tokens, written in 31 seconds for $0.029
1 let stars = [];
2 let paletteIndex = 0;
3
4 const palettes = [
5 { skyTop: '#080e24', skyBottom: '#26405b', far: '#173650', near: '#12646b', deep: '#071b2a', sun: '#ffe1ac', glow: '#f99f82', accent: '#baf5e6' },
6 { skyTop: '#160b2f', skyBottom: '#553660', far: '#40335f', near: '#85516f', deep: '#1b1235', sun: '#ffd2d9', glow: '#f491bd', accent: '#ffc7e7' },
7 { skyTop: '#071b27', skyBottom: '#28545b', far: '#23535e', near: '#368474', deep: '#092c35', sun: '#e9f8cf', glow: '#9cdebf', accent: '#d6ffe8' }
8 ];
9
10 function setup() {
11 createCanvas(windowWidth, windowHeight);
12 pixelDensity(min(window.devicePixelRatio || 1, 2));
13 makeStars();
14 }
15
16 function makeStars() {
17 randomSeed(42);
18 stars = [];
19 for (let i = 0; i < 155; i++) {
20 stars.push({ x: random(), y: random(0.03, 0.52), r: random(0.5, 1.8), phase: random(TWO_PI) });
21 }
22 }
23
24 function draw() {
25 const p = palettes[paletteIndex];
26 const ctx = drawingContext;
27 const t = millis() * 0.001;
28 const horizon = height * 0.51;
29 const sx = width * 0.69;
30 const sy = height * 0.35;
31 const radius = min(width, height) * 0.14;
32
33 // Twilight sky.
34 let sky = ctx.createLinearGradient(0, 0, 0, height);
35 sky.addColorStop(0, p.skyTop);
36 sky.addColorStop(0.58, p.skyBottom);
37 sky.addColorStop(1, p.deep);
38 ctx.fillStyle = sky;
39 ctx.fillRect(0, 0, width, height);
40
41 noStroke();
42 for (const s of stars) {
43 const shimmer = 0.38 + 0.42 * (0.5 + 0.5 * sin(t * 1.3 + s.phase));
44 fill(235, 250, 255, 255 * shimmer);
45 circle(s.x * width, s.y * height, s.r * 2);
46 }
47
48 // Soft halo and striped sun.
49 const halo = ctx.createRadialGradient(sx, sy, radius * 0.25, sx, sy, radius * 2.8);
50 halo.addColorStop(0, hexAlpha(p.glow, 0.32));
51 halo.addColorStop(0.55, hexAlpha(p.glow, 0.09));
52 halo.addColorStop(1, hexAlpha(p.glow, 0));
53 ctx.fillStyle = halo;
54 ctx.fillRect(sx - radius * 3, sy - radius * 3, radius * 6, radius * 6);
55
56 ctx.save();
57 ctx.beginPath();
58 ctx.arc(sx, sy, radius, 0, TWO_PI);
59 ctx.clip();
60 const disc = ctx.createLinearGradient(0, sy - radius, 0, sy + radius);
61 disc.addColorStop(0, p.sun);
62 disc.addColorStop(1, p.glow);
63 ctx.fillStyle = disc;
64 ctx.fillRect(sx - radius, sy - radius, radius * 2, radius * 2);
65 for (let j = 0; j < 8; j++) {
66 const yy = sy + radius * (0.19 + j * 0.115);
67 ctx.fillStyle = hexAlpha(p.skyBottom, 0.36 + j * 0.065);
68 ctx.fillRect(sx - radius, yy, radius * 2, 1.5 + j * 0.85);
69 }
70 ctx.restore();
71
72 stroke(255, 225, 198, 42);
73 strokeWeight(1);
74 line(0, horizon, width, horizon);
75
76 // Overlapping wave silhouettes. Mouse position gently bends the surface.
77 const mouseShift = mouseX === 0 ? 0 : (mouseX / width - 0.5) * 2;
78 for (let i = 0; i < 8; i++) {
79 const base = horizon + i * height * 0.069;
80 const amplitude = height * (0.009 + i * 0.0027);
81 const phase = t * (0.38 + i * 0.075) + i * 0.88 + mouseShift * 0.7;
82 const waveY = x => base
83 + sin(x * 0.010 + phase) * amplitude
84 + sin(x * 0.022 - phase * 0.72) * amplitude * 0.32;
85
86 ctx.beginPath();
87 ctx.moveTo(0, waveY(0));
88 for (let x = 8; x <= width + 8; x += 8) ctx.lineTo(x, waveY(x));
89 ctx.lineTo(width, height);
90 ctx.lineTo(0, height);
91 ctx.closePath();
92
93 const topColor = lerpColor(color(p.far), color(p.near), i / 7);
94 const bottomColor = lerpColor(topColor, color(p.deep), 0.55);
95 const wash = ctx.createLinearGradient(0, base - amplitude, 0, height);
96 wash.addColorStop(0, topColor.toString());
97 wash.addColorStop(1, bottomColor.toString());
98 ctx.fillStyle = wash;
99 ctx.fill();
100
101 ctx.beginPath();
102 for (let x = 0; x <= width + 8; x += 8) {
103 if (x === 0) ctx.moveTo(x, waveY(x));
104 else ctx.lineTo(x, waveY(x));
105 }
106 ctx.strokeStyle = hexAlpha(p.accent, 0.07 + i * 0.012);
107 ctx.lineWidth = 1;
108 ctx.stroke();
109 }
110
111 // Broken glints across the water.
112 noStroke();
113 for (let j = 0; j < 36; j++) {
114 const y = horizon + 13 + j * (height - horizon) / 39;
115 const spread = (y - horizon) * 0.62;
116 const x = sx + sin(j * 8.31 + t * 1.4) * spread * 0.42;
117 const glintWidth = (8 + (j % 5) * 9) * (0.5 + (y - horizon) / height);
118 fill(red(color(p.accent)), green(color(p.accent)), blue(color(p.accent)), 25 + 45 * (1 - j / 36));
119 ellipse(x, y, glintWidth, max(1, height * 0.002));
120 }
121
122 // Quiet editorial labels.
123 fill(235, 247, 246, 165);
124 noStroke();
125 textFont('monospace');
126 textSize(11);
127 textStyle(NORMAL);
128 textAlign(LEFT);
129 text('L U M I N O U S T I D E S', 28, 38);
130 fill(235, 247, 246, 105);
131 textSize(10);
132 text('MOVE TO STEER / CLICK TO CHANGE SKY', 28, height - 27);
133 }
134
135 function hexAlpha(hex, alpha) {
136 const n = parseInt(hex.slice(1), 16);
137 return `rgba(${n >> 16},${(n >> 8) & 255},${n & 255},${alpha})`;
138 }
139
140 function mousePressed() {
141 paletteIndex = (paletteIndex + 1) % palettes.length;
142 }
143
144 function windowResized() {
145 resizeCanvas(windowWidth, windowHeight);
146 makeStars();
147 }