• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

ROBOHASH - INTERACTIVE APP SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Robohash Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
  tailwind.config = {
    darkMode: 'class',
    theme: {
      extend: {
        colors: {
          primary: '#ffffff',
          'surface-container-low': '#1a1a1a',
        }
      }
    }
  }
</script>
<style>
  body {
    background-color: #000;
    color: #fff;
    font-family: system-ui, -apple-system, sans-serif;
  }
  .tile-container {
    position: relative;
    width: 100%;
    aspect-ratio: 1/1;
    background-color: #1a1a1a;
    border-radius: 1rem;
    overflow: hidden;
    border: 1px solid rgba(255,255,255,0.1);
    transition: border-color 0.2s;
  }
  .tile-container:hover {
    border-color: rgba(255,255,255,0.4);
  }
  .tile-iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: none;
    pointer-events: none;
    transition: opacity 0.3s;
    z-index: 0;
  }
  .tile-img {
    position: relative;
    z-index: 10;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.3s, transform 0.5s;
  }
  .tile-img:hover {
    transform: scale(1.1);
  }
</style>
</head>
<body class="p-8 md:p-12">
  <div class="max-w-6xl mx-auto w-full mt-0 animate-fade-in">
    <header class="mb-6 text-left">
      <p class="text-gray-400 text-base leading-relaxed max-w-3xl mb-6">
        Generate unique robot avatars instantly. Each robot is created from a hash of random data, ensuring no two are alike.
      </p>
    </header>

    <main>
      <div class="flex justify-start mb-8">
        <button onclick="generateImages()" class="group relative z-10 overflow-hidden inline-flex items-center gap-2.5 px-6 py-3 text-[11px] font-mono font-medium tracking-widest uppercase bg-transparent text-white border border-white/40 hover:border-white hover:text-black transition-all duration-300 shadow-[4px_4px_0px_0px_rgba(255,255,255,0.3)] hover:shadow-none hover:translate-x-[4px] hover:translate-y-[4px] before:absolute before:inset-0 before:w-full before:scale-x-0 before:origin-left before:bg-white before:transition-transform before:duration-300 before:ease-out hover:before:scale-x-100 before:-z-10">
          <span class="w-1.5 h-1.5 rounded-full bg-white/60 group-hover:bg-black transition-colors"></span>
          Generate New Robots
        </button>
      </div>

      <!-- UPDATED RESPONSIVE GRID with minmax(130px, 1fr) -->
      <div id="grid" class="grid grid-cols-[repeat(auto-fill,minmax(130px,1fr))] gap-6 mb-12">
        <!-- Tiles generated by JS -->
      </div>
    </main>
  </div>

<script>
  const loaderHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>RH — block loader</title>
<style>
  :root{
    color-scheme: light dark;
    --bg:        #e9ebf1;
    --bg-glow:   #ffffff;
    --block:     #3b4258;
    --accent:    #d9a012;
    --light-sky: #ffffff;
    --light-gnd: #b9bfd0;
  }
  @media (prefers-color-scheme: dark){
    :root:not([data-theme="light"]){
      --bg:        #0e1120;
      --bg-glow:   #1d2340;
      --block:     #aeb6cd;
      --accent:    #f2c230;
      --light-sky: #cdd6ff;
      --light-gnd: #2a3050;
    }
  }
  :root[data-theme="dark"]{
    --bg:        #0e1120;
    --bg-glow:   #1d2340;
    --block:     #aeb6cd;
    --accent:    #f2c230;
    --light-sky: #cdd6ff;
    --light-gnd: #2a3050;
  }
  *{box-sizing:border-box}
  html,body{height:100%}
  body{
    margin:0;
    background:var(--bg);
    background-image:radial-gradient(120% 90% at 50% 45%, var(--bg-glow) 0%, var(--bg) 62%);
    overflow:hidden;
  }
  #stage{position:fixed;inset:0;touch-action:none;cursor:grab}
  #stage:active{cursor:grabbing}
  canvas{display:block}
</style>
</head>
<body>
  <div id="stage"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"><\/script>
<script>
(function () {
  "use strict";
  const COLS = 9, ROWS = 7, SP = 1.0, SIZE = 0.93;
  const k = (r, c) => r * COLS + c;
  const R = new Set(), H = new Set();
  for (let r = 0; r < ROWS; r++) R.add(k(r, 0));
  for (let c = 1; c <= 4; c++) R.add(k(0, c));
  R.add(k(1, 4)); R.add(k(2, 4));
  for (let c = 1; c <= 4; c++) R.add(k(3, c));
  R.add(k(4, 2)); R.add(k(5, 3)); R.add(k(6, 4));
  for (let r = 0; r < ROWS; r++) { H.add(k(r, 4)); H.add(k(r, 8)); }
  for (let c = 5; c <= 7; c++) H.add(k(3, c));
  const cells = [];
  for (let r = 0; r < ROWS; r++) for (let c = 0; c < COLS; c++) {
    const key = k(r, c), inR = R.has(key), inH = H.has(key);
    if (inR || inH) cells.push({ r, c, inR, inH, shared: inR && inH });
  }
  cells.sort((a, b) => {
    const ga = a.shared ? 1 : (a.inR ? 0 : 2);
    const gb = b.shared ? 1 : (b.inR ? 0 : 2);
    return ga - gb || a.c - b.c || a.r - b.r;
  });
  const REDUCED = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const IN_STAGGER = 0.032, IN_DUR = 0.72;
  const HOLD = REDUCED ? 6.0 : 1.55;
  const OUT_STAGGER = 0.016, OUT_DUR = 0.5, GAP = 0.35;
  const N = cells.length;
  const IN_END  = (N - 1) * IN_STAGGER + IN_DUR;
  const HOLD_END = IN_END + HOLD;
  const CYCLE = HOLD_END + (N - 1) * OUT_STAGGER + OUT_DUR + GAP;
  const stage = document.getElementById("stage");
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 200);
  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.05;
  stage.appendChild(renderer.domElement);
  const root = new THREE.Group();
  scene.add(root);
  const key1 = new THREE.DirectionalLight(0xffffff, 1.15);
  key1.position.set(6, 9, 8);
  const fill1 = new THREE.DirectionalLight(0xffffff, 0.35);
  fill1.position.set(-8, -3, 4);
  const hemi = new THREE.HemisphereLight(0xffffff, 0x000000, 0.7);
  const rim = new THREE.PointLight(0xffffff, 1.1, 42);
  rim.position.set(-9, 4, -6);
  scene.add(key1, fill1, hemi, rim);
  const css = n => getComputedStyle(document.documentElement).getPropertyValue(n).trim();
  const matBlock = new THREE.MeshStandardMaterial({ roughness: 0.46, metalness: 0.16 });
  const matJoint = new THREE.MeshStandardMaterial({ roughness: 0.3, metalness: 0.4 });
  function paint() {
    const block = new THREE.Color(css("--block"));
    const accent = new THREE.Color(css("--accent"));
    matBlock.color.copy(block);
    matJoint.color.copy(accent);
    matJoint.emissive.copy(accent).multiplyScalar(0.22);
    hemi.color.set(css("--light-sky"));
    hemi.groundColor.set(css("--light-gnd"));
    rim.color.copy(accent);
  }
  paint();
  const geo = new THREE.BoxGeometry(SIZE, SIZE, SIZE);
  const blocks = cells.map((cell, i) => {
    const mesh = new THREE.Mesh(geo, cell.shared ? matJoint : matBlock);
    root.add(mesh);
    const x = (cell.c - (COLS - 1) / 2) * SP;
    const y = ((ROWS - 1) / 2 - cell.r) * SP;
    const a = Math.random() * Math.PI * 2;
    const rad = 8 + Math.random() * 7;
    const rnd = () => (Math.random() - 0.5) * 2;
    return {
      mesh,
      home: new THREE.Vector3(x, y, 0),
      from: new THREE.Vector3(x + Math.cos(a) * rad, y + Math.sin(a) * rad * 0.75, 8 + Math.random() * 12),
      away: new THREE.Vector3(x * 1.25 + rnd() * 3, y - 9 - Math.random() * 5, -5 - Math.random() * 9),
      spinIn: new THREE.Vector3(rnd() * 3, rnd() * 3, rnd() * 3),
      spinOut: new THREE.Vector3(rnd() * 2.4, rnd() * 2.4, rnd() * 2.4),
      tIn: i * IN_STAGGER,
      tOut: (N - 1 - i) * OUT_STAGGER,
      phase: (cell.c * 0.42 + cell.r * 0.3)
    };
  });
  const drag = { x: 0, y: 0, tx: 0, ty: 0, on: false, px: 0, py: 0 };
  stage.addEventListener("pointerdown", e => {
    drag.on = true; drag.px = e.clientX; drag.py = e.clientY;
    stage.setPointerCapture(e.pointerId);
  });
  stage.addEventListener("pointermove", e => {
    if (!drag.on) return;
    drag.tx += (e.clientX - drag.px) * 0.006;
    drag.ty += (e.clientY - drag.py) * 0.004;
    drag.ty = Math.max(-0.7, Math.min(0.7, drag.ty));
    drag.px = e.clientX; drag.py = e.clientY;
  });
  const release = () => { drag.on = false; };
  stage.addEventListener("pointerup", release);
  stage.addEventListener("pointercancel", release);
  function resize() {
    const w = window.innerWidth, h = window.innerHeight;
    renderer.setSize(w, h);
    camera.aspect = w / h;
    const vFov = camera.fov * Math.PI / 180;
    const needH = (ROWS * SP) / 2 / Math.tan(vFov / 2);
    const needW = (COLS * SP) / 2 / Math.tan(vFov / 2) / camera.aspect;
    const margin = w < 620 ? 1.72 : 1.42;
    camera.position.set(0, 0, Math.max(needH, needW) * margin);
    camera.updateProjectionMatrix();
  }
  window.addEventListener("resize", resize);
  resize();
  const clamp01 = v => v < 0 ? 0 : v > 1 ? 1 : v;
  const outCubic = t => 1 - Math.pow(1 - t, 3);
  const outBack  = t => { const c = 1.9; return 1 + (c + 1) * Math.pow(t - 1, 3) + c * Math.pow(t - 1, 2); };
  const inCubic  = t => t * t * t;
  const clock = new THREE.Clock();
  function frame() {
    const elapsed = clock.getElapsedTime();
    const t = elapsed % CYCLE;
    const assembling = t < HOLD_END;
    for (let i = 0; i < blocks.length; i++) {
      const b = blocks[i], m = b.mesh;
      if (assembling) {
        const p = clamp01((t - b.tIn) / IN_DUR);
        const e = outCubic(p);
        m.position.lerpVectors(b.from, b.home, e);
        m.rotation.set(b.spinIn.x * (1 - e), b.spinIn.y * (1 - e), b.spinIn.z * (1 - e));
        m.scale.setScalar(Math.max(0, outBack(clamp01(p * 1.15))));
        if (p >= 1 && !REDUCED) {
          const wave = Math.sin(elapsed * 2.2 - b.phase) * 0.07;
          m.position.y += wave;
          m.position.z += wave * 0.5;
        }
      } else {
        const q = clamp01((t - HOLD_END - b.tOut) / OUT_DUR);
        const e = inCubic(q);
        m.position.lerpVectors(b.home, b.away, e);
        m.rotation.set(b.spinOut.x * e, b.spinOut.y * e, b.spinOut.z * e);
        m.scale.setScalar(Math.max(0, 1 - e));
      }
    }
    drag.x += (drag.tx - drag.x) * 0.08;
    drag.y += (drag.ty - drag.y) * 0.08;
    const idle = REDUCED ? 0 : 1;
    root.rotation.y = Math.sin(elapsed * 0.42) * 0.4 * idle + drag.x;
    root.rotation.x = Math.sin(elapsed * 0.31) * 0.11 * idle + drag.y;
    renderer.render(scene, camera);
    requestAnimationFrame(frame);
  }
  frame();
})();
<\/script>
</body>
</html>`;

  function generateImages() {
    const grid = document.getElementById('grid');
    grid.innerHTML = '';
    
    for (let i = 0; i < 12; i++) {
      const num = Math.floor(Math.random() * 1000000);
      
      const div = document.createElement('div');
      div.className = 'tile-container';
      
      const iframe = document.createElement('iframe');
      iframe.className = 'tile-iframe';
      iframe.srcdoc = loaderHtml;
      div.appendChild(iframe);
      
      const img = document.createElement('img');
      img.className = 'tile-img';
      img.loading = 'lazy';
      img.src = \`https://robohash.org/set_set2/bgset_bg1/\${num}?size=300x300\`;
      
      img.onload = function(e) {
        e.target.style.opacity = '1';
        iframe.style.opacity = '0';
        setTimeout(() => {
          if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
        }, 300);
      };
      
      img.onerror = function(e) {
        e.target.style.display = 'none';
        div.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:red;font-family:monospace;font-size:12px">ERROR</div>';
      };
      
      div.appendChild(img);
      grid.appendChild(div);
    }
  }

  // Generate initial images
  generateImages();
</script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.