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:
This is a test markdown file to demonstrate the spatial scanner's ability to process .md files.
Use the minimap and zoom controls to explore the spatial layout of all discovered content.
This file was automatically processed by the spatial scanner.
https://play.frumbert.org
