Custom Code Experiment •
Insect Loader
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Glitch bee loader</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@500&display=swap" rel="stylesheet">
<style>
:root {
box-sizing: border-box;
padding-top: env(safe-area-inset-top, 0px);
padding-bottom: env(safe-area-inset-bottom, 0px);
--page: #EDEDED;
--ink: #111111;
--ring-track: rgba(0, 0, 0, 0.12);
--pollen: #FFC21A;
--comb: #0A0A0A;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--page: #141414;
--ink: #F2F2F2;
--ring-track: rgba(255, 255, 255, 0.12);
}
}
:root[data-theme="dark"] {
--page: #141414;
--ink: #F2F2F2;
--ring-track: rgba(255, 255, 255, 0.12);
}
*, *::before, *::after { box-sizing: inherit; }
html { height: 100%; scroll-padding-top: env(safe-area-inset-top, 0px); }
body {
margin: 0;
height: 100%;
background: var(--page);
color: var(--ink);
font-family: "Chakra Petch", "Segoe UI", system-ui, sans-serif;
display: grid;
place-items: center;
}
.loader {
display: grid;
place-items: center;
}
.orb {
position: relative;
width: min(260px, 70vw);
aspect-ratio: 1;
}
/* Progress ring around the circle */
.ring {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.ring circle { fill: none; stroke-width: 3; }
.ring .track { stroke: var(--ring-track); }
.ring .arc {
stroke: var(--pollen);
stroke-linecap: round;
stroke-dasharray: 90 400;
animation: sweep 1.6s cubic-bezier(.6,.1,.3,.9) infinite;
transform-origin: 50% 50%;
}
@keyframes sweep {
0% { stroke-dasharray: 20 400; transform: rotate(0deg); }
50% { stroke-dasharray: 160 400; }
100% { stroke-dasharray: 20 400; transform: rotate(360deg); }
}
/* The circular viewport */
.window {
position: absolute;
inset: 12px;
border-radius: 50%;
overflow: hidden;
background:
radial-gradient(circle at 50% 45%, #262626 0%, var(--comb) 70%);
box-shadow: inset 0 0 40px rgba(0,0,0,.8), inset 0 0 0 1px rgba(255,255,255,.2);
}
.window canvas { display: block; width: 100%; height: 100%; }
/* scanlines + honeycomb texture */
.window::before {
content: "";
position: absolute;
inset: 0;
opacity: .09;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='48.5' viewBox='0 0 28 48.5'%3E%3Cpath d='M14 0 L28 8.08 V24.25 L14 32.33 L0 24.25 V8.08 Z M14 32.33 V48.5' fill='none' stroke='%23FFFFFF' stroke-width='1'/%3E%3C/svg%3E");
pointer-events: none;
}
.window::after {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(to bottom, rgba(0,0,0,0) 0 2px, rgba(0,0,0,.28) 2px 3px);
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.ring .arc { animation-duration: 4s; }
}
</style>
</head>
<body>
<div class="loader" role="status" aria-label="Loading">
<div class="orb">
<svg class="ring" viewBox="0 0 100 100" aria-hidden="true">
<circle class="track" cx="50" cy="50" r="48"/>
<circle class="arc" cx="50" cy="50" r="48"/>
</svg>
<div class="window">
<canvas id="bee" aria-hidden="true"></canvas>
</div>
</div>
</div>
<script>
(() => {
const canvas = document.getElementById('bee');
const ctx = canvas.getContext('2d');
const buf = document.createElement('canvas');
const bctx = buf.getContext('2d');
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const YELLOW = '#FFFFFF';
const AMBER = '#5E5E5E';
const TAIL = '#BDBDBD';
const WING = '#D6D6D6';
const MAG = '#8C8C8C', CYAN = '#E0E0E0';
let W = 0, H = 0, dpr = 1;
function resize() {
dpr = Math.min(window.devicePixelRatio || 1, 2);
const r = canvas.getBoundingClientRect();
W = Math.round(r.width * dpr); H = Math.round(r.height * dpr);
canvas.width = buf.width = W;
canvas.height = buf.height = H;
}
new ResizeObserver(resize).observe(canvas);
resize();
// ---------- Geometry (bee points along +x = head) ----------
const statics = []; // {pts:[[x,y,z]...], color, w}
function ellipsoid(cx, rx, ry, rz, lat, lon, colorFn, cy = 0) {
const seg = 28;
for (let i = 1; i < lat; i++) {
const t = -Math.PI / 2 + Math.PI * i / lat;
const x = cx + rx * Math.sin(t), f = Math.cos(t);
const pts = [];
for (let k = 0; k <= seg; k++) {
const a = 2 * Math.PI * k / seg;
pts.push([x, cy + ry * f * Math.cos(a), rz * f * Math.sin(a)]);
}
statics.push({ pts, color: colorFn(x), w: 1.8 });
}
for (let j = 0; j < lon; j++) {
const a = 2 * Math.PI * j / lon;
const pts = [];
for (let k = 0; k <= seg; k++) {
const t = -Math.PI / 2 + Math.PI * k / seg;
const f = Math.cos(t);
pts.push([cx + rx * Math.sin(t), cy + ry * f * Math.cos(a), rz * f * Math.sin(a)]);
}
statics.push({ pts, color: YELLOW, w: 1.0, dim: true });
}
}
// Abdomen with bumblebee stripes
ellipsoid(-0.62, 0.78, 0.56, 0.56, 14, 12, x => {
if (x < -1.12) return TAIL; // white bumblebee tail
const band = Math.floor((x + 1.12) / 0.3);
return band % 2 === 0 ? AMBER : YELLOW;
}, -0.05);
// Thorax (fuzzy, full yellow)
ellipsoid(0.38, 0.40, 0.44, 0.44, 7, 10, () => YELLOW, 0.02);
// Head (darker)
ellipsoid(0.98, 0.26, 0.30, 0.30, 6, 8, () => AMBER, 0.0);
// Eyes
for (const s of [-1, 1]) {
const pts = [];
for (let k = 0; k <= 16; k++) {
const a = 2 * Math.PI * k / 16;
pts.push([1.08 + 0.09 * Math.cos(a), 0.06 + 0.13 * Math.sin(a), s * 0.25]);
}
statics.push({ pts, color: YELLOW, w: 1.4 });
}
// Fuzz: short radial hairs on thorax and abdomen
(() => {
let seed = 7; const rnd = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
const hair = (cx, cy, rx, ry, rz, n, len, color) => {
for (let i = 0; i < n; i++) {
const t = (rnd() - .5) * Math.PI * 0.9, a = rnd() * Math.PI * 2;
const f = Math.cos(t);
const nx = Math.sin(t), ny = f * Math.cos(a), nz = f * Math.sin(a);
const p = [cx + rx * nx, cy + ry * ny, rz * nz];
statics.push({ pts: [p, [p[0] + nx*len, p[1] + ny*len, p[2] + nz*len]], color, w: 0.8, dim: true });
}
};
hair(0.38, 0.02, 0.40, 0.44, 0.44, 90, 0.09, YELLOW);
hair(-0.62, -0.05, 0.78, 0.56, 0.56, 110, 0.06, YELLOW);
})();
// Stinger
statics.push({ pts: [[-1.38, -0.05, 0], [-1.62, -0.12, 0]], color: YELLOW, w: 1.6 });
// Antennae
for (const s of [-1, 1]) {
statics.push({ pts: [[1.12, 0.22, s*0.10], [1.35, 0.55, s*0.22], [1.55, 0.62, s*0.34]], color: YELLOW, w: 1.3 });
}
// Legs (three pairs, jointed)
for (const s of [-1, 1]) {
for (let i = 0; i < 3; i++) {
const x = 0.55 - i * 0.22;
statics.push({ pts: [
[x, -0.30, s*0.18],
[x - 0.05, -0.52, s*0.42],
[x - 0.22 - i*0.05, -0.78, s*0.46]
], color: YELLOW, w: 1.1 });
}
}
// Wings: built each frame so they flap
function wing(side, fore, flap) {
const root = [0.40, 0.40, side * 0.10];
const len = fore ? 0.95 : 0.65, wid = fore ? 0.32 : 0.24;
const sweep = fore ? 0.35 : 0.75; // angle back from +z
const outline = [], vein = [];
const seg = 22;
for (let k = 0; k <= seg; k++) {
const a = 2 * Math.PI * k / seg;
outline.push([len/2 + (len/2) * Math.cos(a), (wid) * Math.sin(a)]);
}
vein.push([0,0],[len*0.9, wid*0.1]);
vein.push(null, [len*0.35, 0], [len*0.6, wid*0.7]);
vein.push(null, [len*0.35, 0], [len*0.65, -wid*0.6]);
const map = ([u, v]) => {
// u along wing span, v across chord; lay in x-z plane then lift by flap
const dz = u * Math.cos(sweep), dx = -u * Math.sin(sweep) + v * 0.4;
// rotate the span around the body axis to flap
const liftY = dz * Math.sin(flap);
const liftZ = dz * Math.cos(flap);
return [root[0] + dx, root[1] + liftY, root[2] + side * liftZ];
};
const polys = [];
polys.push({ pts: outline.map(map), color: WING, w: 1.1, wing: true });
let cur = [];
for (const p of vein) {
if (p === null) { if (cur.length) polys.push({ pts: cur, color: WING, w: 0.8, wing: true, dim: true }); cur = []; continue; }
cur.push(map(p));
}
if (cur.length) polys.push({ pts: cur, color: WING, w: 0.8, wing: true, dim: true });
return polys;
}
// ---------- Projection ----------
function project(p, rotY, tilt, jitter) {
let [x, y, z] = p;
if (jitter) { x += (Math.random()-.5)*jitter; y += (Math.random()-.5)*jitter; }
// rotate Y
const cy = Math.cos(rotY), sy = Math.sin(rotY);
let x1 = x * cy + z * sy, z1 = -x * sy + z * cy;
// tilt X
const cx = Math.cos(tilt), sx = Math.sin(tilt);
let y1 = y * cx - z1 * sx, z2 = y * sx + z1 * cx;
const d = 4.2, persp = d / (d - z2);
const scale = W * 0.25;
return [W/2 + x1 * scale * persp, H/2 - (y1 - 0.02) * scale * persp, z2];
}
function strokeAll(polys, rotY, tilt, color, alpha, ox, oy, jitter, hover) {
for (const poly of polys) {
bctx.beginPath();
let depthSum = 0;
poly.pts.forEach((p, i) => {
const [sx, sy, sz] = project([p[0], p[1] + hover, p[2]], rotY, tilt, jitter);
depthSum += sz;
if (i === 0) bctx.moveTo(sx + ox, sy + oy); else bctx.lineTo(sx + ox, sy + oy);
});
const depth = depthSum / poly.pts.length; // -1.5..1.5
const fade = 0.5 + 0.5 * Math.min(1, Math.max(0, (depth + 1.2) / 2.4));
bctx.globalAlpha = alpha * fade * (poly.dim ? 0.55 : 1) * (poly.wing ? 0.8 : 1);
bctx.strokeStyle = color || poly.color;
bctx.lineWidth = poly.w * dpr;
bctx.stroke();
}
bctx.globalAlpha = 1;
}
// ---------- Glitch state ----------
let glitch = 0, glitchStrength = 0;
let rot = 0, last = performance.now();
let frozenRot = null;
function frame(now) {
// Skip drawing while the canvas has no size (hidden, not yet laid out, etc.)
if (W === 0 || H === 0) {
resize();
if (W === 0 || H === 0) { last = now; requestAnimationFrame(frame); return; }
}
const dt = Math.min(50, now - last) / 1000; last = now;
const speed = reduced ? 0.5 : 1.6;
rot += dt * speed;
if (!reduced) {
if (glitch <= 0 && Math.random() < 0.025) {
glitch = 4 + Math.floor(Math.random() * 10);
glitchStrength = 0.4 + Math.random() * 0.6;
if (Math.random() < 0.4) frozenRot = rot + (Math.random() - .5) * 1.2;
}
}
const g = glitch > 0;
if (g) glitch--; else frozenRot = null;
const t = now / 1000;
const flap = Math.sin(t * (reduced ? 6 : 38)) * 0.55 + 0.25;
const hover = Math.sin(t * 2.2) * 0.06;
const rotY = frozenRot ?? rot;
const tilt = 0.32 + Math.sin(t * 0.9) * 0.08;
const polys = statics.concat(
wing(1, true, flap), wing(1, false, flap * 0.9),
wing(-1, true, flap), wing(-1, false, flap * 0.9)
);
// --- render bee to buffer ---
bctx.clearRect(0, 0, W, H);
bctx.lineJoin = 'round'; bctx.lineCap = 'round';
bctx.globalCompositeOperation = 'lighter';
const baseJit = reduced ? 0 : 0.006;
if (g) {
const off = (4 + Math.random() * 8) * dpr * glitchStrength;
strokeAll(polys, rotY, tilt, MAG, 0.75, -off, 0, 0.03 * glitchStrength, hover);
strokeAll(polys, rotY, tilt, CYAN, 0.75, off, (Math.random()-.5)*2*dpr, 0.03 * glitchStrength, hover);
}
strokeAll(polys, rotY, tilt, null, g ? 0.85 : 1, 0, 0, g ? 0.02 : baseJit, hover);
bctx.globalCompositeOperation = 'source-over';
// --- composite to screen, slicing when glitching ---
ctx.clearRect(0, 0, W, H);
if (g) {
let y = 0;
while (y < H) {
const h = Math.min(H - y, Math.max(2, Math.floor((4 + Math.random() * 30) * dpr)));
const shift = Math.random() < 0.35 ? (Math.random() - .5) * 40 * dpr * glitchStrength : 0;
ctx.drawImage(buf, 0, y, W, h, shift, y, W, h);
y += h;
}
// occasional block noise
const blocks = Math.floor(Math.random() * 4);
for (let i = 0; i < blocks; i++) {
ctx.fillStyle = [YELLOW, MAG, CYAN][i % 3];
ctx.globalAlpha = 0.18 + Math.random() * 0.3;
ctx.fillRect(Math.random() * W, Math.random() * H, (10 + Math.random() * 60) * dpr, (1 + Math.random() * 3) * dpr);
}
ctx.globalAlpha = 1;
} else {
ctx.drawImage(buf, 0, 0);
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>