You make short animated pieces for the web.
Build me a single HTML page: a bird, breakdancing.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.Two sentences: what the bird is, and the look you went for.
The single fenced html block.
Three bullets.
It is a municipal pigeon named DJ Crumb, b-boying on a manhole cover like it owns the block. The look is a misregistered risograph/zine cutout: receipt-feather crest, newspaper belly, traffic-cone beak and feet, and loud ink stamps.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BIRD BREAK UNIT</title>
<style>
*{box-sizing:border-box}
:root{
--paper:#eadfc8;
--ui:#17151a;
--line:#17151a;
--panel:rgba(239,226,201,.92);
--hot:#ff4c7a;
--cyan:#00aec1;
--yellow:#f6cf58;
}
html,body{height:100%;margin:0;overflow:hidden;background:var(--paper);color:var(--ui)}
body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Courier New",monospace}
#stage{position:fixed;inset:0;width:100%;height:100%;display:block;touch-action:none;cursor:crosshair}
.hud{
position:fixed;z-index:4;top:18px;left:18px;width:min(350px,calc(100vw - 36px));
padding:14px 14px 12px;border:3px solid var(--line);background:var(--panel);
box-shadow:7px 7px 0 var(--line);transform:rotate(-.7deg);pointer-events:auto;
}
.hud:before,.hud:after{
content:"";position:absolute;width:84px;height:20px;background:var(--yellow);
border:2px solid var(--line);opacity:.85;pointer-events:none;
}
.hud:before{left:-24px;top:18px;transform:rotate(-24deg)}
.hud:after{right:-25px;bottom:-9px;transform:rotate(17deg)}
.kicker{font-size:10px;letter-spacing:.18em;font-weight:900;opacity:.75}
h1{font-size:34px;line-height:.88;margin:7px 0 8px;letter-spacing:-.07em;font-weight:1000}
h1 span{display:inline-block;color:var(--hot);transform:skew(-9deg)}
.sub{margin:0 0 11px;font-size:11px;line-height:1.35;opacity:.82}
.meters{display:grid;grid-template-columns:1fr;gap:6px;border-top:2px dashed var(--line);border-bottom:2px dashed var(--line);padding:9px 0;margin-bottom:10px}
label{display:flex;justify-content:space-between;align-items:center;font-size:10px;letter-spacing:.11em;font-weight:900}
output{font-variant-numeric:tabular-nums;color:var(--hot)}
input[type=range]{width:100%;height:14px;accent-color:var(--hot);cursor:ew-resize;background:transparent}
input[type=range]::-webkit-slider-thumb{border:2px solid var(--line)}
input[type=range]::-moz-range-thumb{border:2px solid var(--line)}
.buttons{display:grid;grid-template-columns:1fr 1fr;gap:6px}
button{
font:inherit;font-size:10px;font-weight:900;letter-spacing:.08em;color:var(--ui);
background:transparent;border:2px solid var(--line);padding:8px 5px;cursor:pointer;
box-shadow:2px 2px 0 var(--line);transition:transform .08s,background .08s,color .08s;
}
button:hover{background:var(--cyan);color:#08080c}
button:active,.fired{transform:translate(2px,2px);box-shadow:none;background:var(--hot);color:#08080c}
.looks{display:flex;align-items:center;gap:5px;margin-top:9px;font-size:10px;font-weight:900}
.looks .lab{margin-right:auto;letter-spacing:.12em}
.look{padding:5px 7px;box-shadow:none;border-width:2px}
.look.selected{background:var(--hot);color:#08080c}
.status{display:flex;justify-content:space-between;gap:8px;margin-top:10px;font-size:9px;font-weight:900;letter-spacing:.05em;text-transform:uppercase}
.status span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.floorhint{
position:fixed;z-index:3;right:16px;bottom:14px;padding:6px 8px;border:2px solid var(--line);
background:var(--panel);font-size:10px;font-weight:900;letter-spacing:.08em;pointer-events:none;
transform:rotate(1.2deg);box-shadow:3px 3px 0 var(--line)
}
@media(max-width:600px){
.hud{top:9px;left:9px;width:calc(100vw - 18px);padding:10px;box-shadow:4px 4px 0 var(--line)}
.hud:before,.hud:after{display:none}
h1{font-size:25px;margin:4px 0 5px}
.sub{display:none}
.meters{padding:6px 0;margin-bottom:7px}
button{padding:6px 3px}
.floorhint{font-size:8px;right:8px;bottom:8px}
}
@media(max-height:540px){
.hud{transform:scale(.82) rotate(-.7deg);transform-origin:top left}
.sub{display:none}
}
</style>
</head>
<body>
<canvas id="stage" aria-label="A pigeon breakdancing on a manhole cover">A pigeon is breakdancing.</canvas>
<section class="hud" aria-label="Controls">
<div class="kicker">MUNICIPAL CYPHER // NO. 04</div>
<h1>BIRD BREAK <span>UNIT</span></h1>
<p class="sub">One pigeon. One manhole. Poses are nailed down: coil, strike, hold, recover. The floor takes the hit.</p>
<div class="meters">
<label>FLOOR WEIGHT <output id="weightOut">1.00 G</output></label>
<input id="weight" type="range" min="55" max="180" value="100" aria-label="Floor weight">
<label>BPM <output id="bpmOut">104</output></label>
<input id="bpm" type="range" min="70" max="150" value="104" aria-label="Tempo">
</div>
<div class="buttons">
<button type="button" data-call="footwork">1 FOOTWORK</button>
<button type="button" data-call="windmill">2 WIND</button>
<button type="button" data-call="cut">3 CUT / FREEZE</button>
<button type="button" data-call="scratch">4 SCRATCH</button>
</div>
<div class="looks">
<span class="lab">PRINT</span>
<button type="button" class="look selected" data-pal="riso">RISO</button>
<button type="button" class="look" data-pal="night">NIGHT</button>
<button type="button" class="look" data-pal="news">NEWS</button>
</div>
<div class="status"><span id="moveRead">NOW · TOP ROCK</span><span id="nextRead">NEXT · COIL</span></div>
</section>
<div class="floorhint">CLICK/TAP FLOOR → THROW A CRUMB / FOOTWORK FOLLOWS</div>
<script>
(() => {
'use strict';
const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const bg = document.createElement('canvas');
const bctx = bg.getContext('2d');
const TAU = Math.PI * 2;
const clamp = (v,a,b) => Math.max(a, Math.min(b, v));
const lerp = (a,b,t) => a + (b-a)*t;
const PALS = {
riso: {
paper:'#eadfc8', line:'#17151a', text:'#17151a', panel:'rgba(239,226,201,.92)',
ink:'#17151a', floor:'#24212b', floor2:'#3b3340', hot:'#ff4c7a', cyan:'#00aec1',
yellow:'#f6cf58', body:'#b8b4a8', belly:'#f3e7cc', wing:'#737b82', orange:'#f2a13b',
light:'#fff4dc', shadow:'#100d14', grain:'#8e806a', glow:false
},
night: {
paper:'#101325', line:'#eef3ff', text:'#f5f2ff', panel:'rgba(14,16,31,.89)',
ink:'#eaf0ff', floor:'#080910', floor2:'#191d3a', hot:'#ff4bab', cyan:'#43ecff',
yellow:'#ffe76a', body:'#455273', belly:'#d9ddff', wing:'#693c86', orange:'#ff9c3c',
light:'#eef3ff', shadow:'#000000', grain:'#33436a', glow:true
},
news: {
paper:'#ded8c7', line:'#262421', text:'#262421', panel:'rgba(224,217,199,.93)',
ink:'#262421', floor:'#302e2a', floor2:'#4b473f', hot:'#a74331', cyan:'#55736d',
yellow:'#c5a65b', body:'#aaa69a', belly:'#eae5d5', wing:'#7c786e', orange:'#b86a35',
light:'#f8f4e8', shadow:'#171512', grain:'#9b9587', glow:false
}
};
let W=0,H=0,DPR=1,S=1,cx=0,floorY=0;
let palKey='riso';
let pal=PALS[palKey];
const controls={bpm:104,weight:1};
function mulberry32(seed){
let t=seed>>>0;
return function(){
t += 0x6D2B79F5;
let r=Math.imul(t ^ (t>>>15), 1 | t);
r ^= r + Math.imul(r ^ (r>>>7), 61 | r);
return ((r ^ (r>>>14))>>>0) / 4294967296;
};
}
function roundRectPath(c,x,y,w,h,r){
r=Math.min(r,w/2,h/2);
c.beginPath(); c.moveTo(x+r,y); c.lineTo(x+w-r,y);
c.quadraticCurveTo(x+w,y,x+w,y+r); c.lineTo(x+w,y+h-r);
c.quadraticCurveTo(x+w,y+h,x+w-r,y+h); c.lineTo(x+r,y+h);
c.quadraticCurveTo(x,y+h,x,y+h-r); c.lineTo(x,y+r);
c.quadraticCurveTo(x,y,x+r,y); c.closePath();
}
function setCSSPalette(){
const r=document.documentElement.style;
r.setProperty('--paper',pal.paper);
r.setProperty('--ui',pal.text);
r.setProperty('--line',pal.line);
r.setProperty('--panel',pal.panel);
r.setProperty('--hot',pal.hot);
r.setProperty('--cyan',pal.cyan);
r.setProperty('--yellow',pal.yellow);
document.querySelectorAll('[data-pal]').forEach(b=>b.classList.toggle('selected',b.dataset.pal===palKey));
}
function applyPalette(key){
if(key) palKey=key;
pal=PALS[palKey];
setCSSPalette();
if(W) makeBackground();
}
function resize(){
W=innerWidth; H=innerHeight; DPR=Math.min(devicePixelRatio||1,2);
canvas.width=Math.round(W*DPR); canvas.height=Math.round(H*DPR);
canvas.style.width=W+'px'; canvas.style.height=H+'px';
cx=W>820?W*.59:W*.5;
floorY=H*.73;
S=clamp(Math.min(W/980,H/650),.52,1.25);
makeBackground();
}
function makeBackground(){
bg.width=Math.max(1,Math.floor(W)); bg.height=Math.max(1,Math.floor(H));
const c=bctx; c.setTransform(1,0,0,1,0,0); c.globalAlpha=1;
c.clearRect(0,0,W,H);
c.fillStyle=pal.paper; c.fillRect(0,0,W,H);
const rr=mulberry32(palKey==='night'?31:palKey==='news'?53:17);
const dots=Math.floor(W*H/1900);
c.globalAlpha=.15; c.fillStyle=pal.grain;
for(let i=0;i<dots;i++){
c.beginPath(); c.arc(rr()*W,rr()*H,.45+rr()*1.25,0,TAU); c.fill();
}
c.save();
c.globalAlpha=.09; c.strokeStyle=pal.hot; c.lineWidth=2*S;
for(let x=-H;x<W+H;x+=18*S){ c.beginPath(); c.moveTo(x,0); c.lineTo(x+H,H*.58); c.stroke(); }
c.restore();
c.save();
c.globalAlpha=.075; c.fillStyle=pal.hot;
c.font=`900 ${Math.max(28,Math.min(W,H)*.20)}px ui-monospace, Menlo, Consolas, monospace`;
c.textAlign='center'; c.translate(cx,floorY-165*S); c.rotate(-.08); c.fillText('B-BIRD',0,0);
c.restore();
c.fillStyle=pal.floor; c.fillRect(0,floorY,W,H-floorY);
c.save(); c.beginPath(); c.rect(0,floorY,W,H-floorY); c.clip();
c.globalAlpha=.13; c.strokeStyle=pal.cyan; c.lineWidth=2*S;
for(let x=-H;x<W+H;x+=22*S){ c.beginPath(); c.moveTo(x,floorY); c.lineTo(x+H*.55,H); c.stroke(); }
c.globalAlpha=.12; c.fillStyle=pal.light;
for(let i=0;i<28;i++){
const x=rr()*W, y=floorY+rr()*(H-floorY);
c.fillRect(x,y,(12+rr()*50)*S,1.2*S);
}
c.restore();
c.save(); c.translate(cx,floorY+44*S); c.scale(1,.28);
c.fillStyle=pal.floor2; c.strokeStyle=pal.ink; c.lineWidth=5*S;
c.beginPath(); c.arc(0,0,195*S,0,TAU); c.fill(); c.stroke();
c.globalAlpha=.55; c.strokeStyle=pal.cyan; c.lineWidth=2*S;
[150,110,72].forEach(r=>{ c.beginPath(); c.arc(0,0,r*S,0,TAU); c.stroke(); });
c.strokeStyle=pal.ink; c.lineWidth=2*S;
for(let i=0;i<16;i++){
const a=i/16*TAU;
c.beginPath(); c.moveTo(Math.cos(a)*72*S,Math.sin(a)*72*S);
c.lineTo(Math.cos(a)*188*S,Math.sin(a)*188*S); c.stroke();
}
c.restore();
c.save();
c.lineWidth=6*S; c.strokeStyle=pal.ink; c.lineCap='round';
c.beginPath();
const step=Math.max(10,18*S);
for(let x=0;x<=W+step;x+=step){
const y=floorY+Math.sin(x/(45*S))*2.2*S;
if(x===0)c.moveTo(x,y); else c.lineTo(x,y);
}
c.stroke();
c.globalAlpha=.35; c.strokeStyle=pal.hot; c.lineWidth=3*S;
c.beginPath();
for(let x=0;x<=W+step;x+=step){
const y=floorY-5*S+Math.sin(x/(45*S)+.7)*2.2*S;
if(x===0)c.moveTo(x,y); else c.lineTo(x,y);
}
c.stroke(); c.restore();
c.save(); c.globalAlpha=.72; c.fillStyle=pal.text;
c.font=`900 ${Math.max(8,10*S)}px ui-monospace, Menlo, Consolas, monospace`;
c.fillText('MUNICIPAL DANCE FLOOR / WET INK',18,floorY-11*S);
c.textAlign='right'; c.fillText('DO NOT FEED ... EXCEPT CRUMBS',W-18,H-38);
c.restore();
for(const [x,y] of [[22,22],[W-22,22],[22,H-22],[W-22,H-22]]){
c.save(); c.translate(x,y); c.strokeStyle=pal.line; c.globalAlpha=.35; c.lineWidth=1;
c.beginPath(); c.moveTo(-8,0); c.lineTo(8,0); c.moveTo(0,-8); c.lineTo(0,8); c.stroke(); c.restore();
}
}
/* pose system */
const BASE={
x:0,y:-92,rot:0,sx:1,sy:1,
wingL:[-28,-18,-62,6,-74,44], wingR:[28,-18,62,6,74,44],
legL:[-18,34,-28,64,-36,92], legR:[18,34,28,64,36,92],
footL:0,footR:0, tail:0,tailSpread:1,
head:[0,-72,0],beak:.15,eye:0,crest:0,
blur:0,shadow:1,contact:1
};
function clonePose(p){
const q={};
for(const k in BASE){
const v=p[k]===undefined?BASE[k]:p[k];
q[k]=Array.isArray(v)?v.slice():v;
}
return q;
}
function amend(p,o){ const q=clonePose(p); for(const k in o) q[k]=Array.isArray(o[k])?o[k].slice():o[k]; return q; }
function P(o){ return amend(BASE,o||{}); }
function mixPose(a,b,t){
const q={};
for(const k in BASE){
const va=a[k], vb=b[k]===undefined?va:b[k];
if(Array.isArray(va)) q[k]=va.map((v,i)=>lerp(v,vb[i]===undefined?v:vb[i],t));
else q[k]=lerp(va,vb,t);
}
return q;
}
const E={
linear:t=>t,
smooth:t=>t*t*(3-2*t),
in:t=>t*t,
in3:t=>t*t*t,
out:t=>1-Math.pow(1-t,3),
snap:t=>1-Math.pow(1-t,6),
hold:()=>0,
shag:t=>t<.20?0:1-Math.pow(1-(t-.20)/.80,4)
};
function K(at,p,ease='smooth'){ return {at,p:clonePose(p),ease}; }
function sampleKeys(keys,u){
if(u<=keys[0].at) return clonePose(keys[0].p);
for(let i=0;i<keys.length-1;i++){
const a=keys[i], b=keys[i+1];
if(u<=b.at){
const t=clamp((u-a.at)/Math.max(.0001,b.at-a.at),0,1);
const f=E[b.ease]||E.smooth;
return mixPose(a.p,b.p,f(t));
}
}
return clonePose(keys[keys.length-1].p);
}
function ratchet(t,n,hold){
const q=Math.min(n-1,Math.floor(t*n));
let f=t*n-q;
const h=clamp(hold,0,.6);
const e=f<h?0:E.snap(clamp((f-h)/(1-h),0,1));
return (q+e)/n;
}
/* hand-authored poses */
const STAND=P({});
const STAND_L=P({
x:-14,y:-90,rot:-.06,sx:1.02,sy:.97,
wingL:[-28,-18,-66,0,-82,35],wingR:[28,-18,60,14,84,52],
legL:[-18,34,-28,62,-43,90],legR:[18,34,36,66,48,92],
footL:-.10,footR:.12,tail:.20,head:[-5,-72,-.08],beak:.30,eye:-.30,crest:.20,contact:1.1
});
const STAND_R=P({
x:14,y:-90,rot:.06,sx:1.02,sy:.97,
wingL:[-28,-18,-60,14,-84,52],wingR:[28,-18,66,0,82,35],
legL:[-18,34,-36,66,-48,92],legR:[18,34,28,62,43,90],
footL:.12,footR:-.10,tail:-.20,head:[5,-72,.08],beak:.30,eye:.30,crest:.20,contact:1.1
});
const TOP_DIP=P({
x:0,y:-82,rot:0,sx:1.12,sy:.86,
wingL:[-28,-16,-54,18,-60,52],wingR:[28,-16,56,18,68,52],
legL:[-18,34,-26,58,-35,82],legR:[18,34,30,58,38,82],
footL:-.20,footR:.20,tail:-.30,head:[0,-62,.05],beak:.80,eye:.70,crest:-.20,shadow:1.15,contact:1.2
});
const TOP_KICK=P({
x:10,y:-88,rot:.10,sx:.96,sy:1.04,
wingL:[-28,-18,-75,-4,-96,