i have big plans for a sort of choose-your-own-adventure type thing with areas you can come across .. sounds that lead you on .. these things take time.
This area is unfinished

found archives, circa late 1990's to early naughties

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>
outer space?
any music you find playing around the place is something i've made or recorded .. and it will be down in this list somewhere, probably ...
spooky noises

frumbert is not normal.

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:

  1. Tools for elearning courses
  2. Elearning course examples
  3. Old versions of my website
  4. Bits of music, poetry and writing I did half a lifetime ago that I've given up being embarassed about.
  5. Other random nonsense I've forgotten.
  6. Photos n stuff
actually it's all unfinished.

photos n stuff

if it's worth doing, the do the least to begin with, and see how that goes.

- frumbert 2025
https://play.frumbert.org

SPAAAAACEEEEEE!

e-learning guff

To Navigate: Grab, pull, zoom

right now there's crap everywhere, on top of other things, its hard to make out. it's just like how I file everything IRL. I'd show you my desk, its under here somewhere.