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>