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  }