 
 
 
 https://www.flickr.com/photos/frumbert/with/3049339762
https://www.flickr.com/photos/frumbert/with/3049339762
 
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Lake Ripple Enhanced</title>
  <style>
    body { margin: 0; overflow: hidden; background: #000; }
    canvas { display: block; width: 100vw; height: 100vh; }
  </style>
</head>
<body>
<canvas id="lakeCanvas"></canvas>
<script>
const img = new Image();
const reflectionStart = 0.68;
const buffer = document.createElement('canvas');
const bctx = buffer.getContext('2d');
img.src = 'pexels-eberhardgross-449461.jpg';
img.onload = () => {
  const canvas = document.getElementById('lakeCanvas');
  const ctx = canvas.getContext('2d');
  canvas.width = img.width;
  canvas.height = img.height;
  buffer.width = img.width;
  buffer.height = img.height;
  bctx.drawImage(img, 0, 0); // single time
  // ✨ Control how far down the reflection begins (0.0 to 1.0)
  const midY = img.height * reflectionStart;
  const rippleHeight = img.height - midY;
  const sliceHeight = 2;
  const subtleGradientColor = '50,200,255';
  // Glints
  const glints = [];
  const maxGlints = 60;
  function createGlint() {
    const x = Math.random() * canvas.width;
    const y = midY + Math.random() * rippleHeight;
    const relY = (y - midY) / rippleHeight;
    const sampleY = Math.floor((img.height * reflectionStart) * relY);
    let r = 255, g = 255, b = 255;
    try {
      const sample = bctx.getImageData(x, sampleY, 1, 1).data;
      r = sample[0]; g = sample[1]; b = sample[2];
    } catch (e) {
      // fallback color if sampling fails
    }
    return {
      x, y,
      color: `rgba(${r},${g},${b},`,
      opacity: Math.random() * 0.8 + 0.2,
      lifetime: Math.random() * 2000 + 500,
      born: performance.now(),
      radius: 2 + Math.random() * 3
    };
  }
  let hue = 0;
  function draw(time) {
    ctx.globalCompositeOperation = "source-over";
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(img, 0, 0);
    for (let y = 0; y < rippleHeight; y += sliceHeight) {
      const distFactor = 1 - (y / rippleHeight);
      const dx = Math.sin((time / 400) + y / 12) * (1-distFactor) * 4;
      const dy = Math.cos((time / 100) + y / 8) * (1-distFactor) * 0.75;
      ctx.drawImage(
        img,
        0, midY + y, img.width, sliceHeight,
        dx, midY + y + dy, img.width, sliceHeight
      );
    }
    // ✨ Reflection glare gradient adjusted to match new reflection start
    hue+=0.5; if (hue>360) hue = 0;
    const gradient = ctx.createLinearGradient(0, midY, 0, img.height);
    gradient.addColorStop(0, `HSL(${hue}, 100%, 50%, 0)`);
    gradient.addColorStop(0.01, `HSL(${hue}, 100%, 50%, 0.12)`);
    gradient.addColorStop(0.15, `HSL(${hue}, 100%, 50%, 0.05)`);
    gradient.addColorStop(1, `HSL(${hue}, 100%, 50%, 0)`);
    ctx.globalCompositeOperation = 'difference';
    ctx.fillStyle = gradient;
    ctx.fillRect(0, midY, canvas.width, rippleHeight);
    // ✨ Glints (unchanged)
    while (glints.length < maxGlints) glints.push(createGlint());
    const now = performance.now();
    for (let i = glints.length - 1; i >= 0; i--) {
      const g = glints[i];
      const age = now - g.born;
      if (age > g.lifetime) {
        glints.splice(i, 1);
        continue;
      }
      const fade = 1 - (age / g.lifetime);
      const sparkle = Math.sin(age / 400 * Math.PI);
      ctx.beginPath();
      ctx.globalCompositeOperation = 'screen';
      ctx.fillStyle = `${g.color}${g.opacity * fade * sparkle})`;
      ctx.arc(g.x, g.y, g.radius, 0, Math.PI * 2);
      ctx.fill();
    }
    requestAnimationFrame(draw);
  }
  requestAnimationFrame(draw);
};
</script>
</body>
</html> 
Some Rise interactions I've been tinkering with:
 
 
And nor is my website! It is in a constant state of flux. I'm always trying one thing or another. In 1995 it was a perl script. In 2001 it was a blog running on classic asp. In 2011 it was a Wordpress thing, which got hacked/spammed pretty much every day. In 2021 it was the default apache file listing without a site in front. And now, it's a sprawl.
Use it like you would a map, albeit a map without cardinality, meaningful coordinates, or a legend, or topology .. ok now that I think of it, it's not at all like a map. But use it as if it was. Well, I'm glad that's sorted out.
Use the clipboard icon (top-left) to share your position (copies a link to your clipboard). Use the wierd square icon next to it to reset the zoom. Click on the animated logo (a lissajous warping rotozoomer with colour morphing, in canvas/vanilla js) if it's getting annoying. The bottom-right has a kind of overview thing with a draggable rectangle to power-navigate and show you an overview. The website is supposed to work on mobile, but if it doesn't I'll attempt to leave a contact form lying about on here somewhere. There's also some spatial audio - whatever that means.
Also somewhere out there are any of the following:
 
 
 
 
 https://play.frumbert.org
https://play.frumbert.org
