27:18Gemini 4 Hydrogen: Cheaper Tokens, More Expensive Answers?
Nov 13, 2026 · YouTube
18:40ERNIE 5.5 Flash Preview vs GLM 5.3 Flash: Baidu's new model is very impressive!
Nov 11, 2026 · YouTube
36:22Fable 5.2 robbing me blind, plus: Nex AGI's N2.6 vs GLM 5.3 Flash
Nov 11, 2026 · YouTube
14:03Gemini 4 Carbon: Can it handle my challenges? (First look with one-shot coding tests)
Nov 10, 2026 · YouTube
47:51Ranking major U.S. Open-Weights Models. (Where does Ripple 65B fit in?)
Nov 6, 2026 · YouTube
10:57"Crouton Alpha": the latest super-efficient coding model by Meta?
Nov 3, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
22:14GPT-6.1 Terra Pro vs Terra: How Much Better Is Pro?
Oct 27, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
31:28Is this company reselling GPT Sol 6.1 at a loss (for non-agentic/text-only)?🤔 Is this sustainable?
Oct 20, 2026 · YouTube
17:58Nemotron 3.5 Super VL vs Opus 5.5: Testing NVIDIA’s new coding juggernaut
Oct 15, 2026 · YouTube
16:04Testing a *massive* new open-weights model 🤯: Muse Spark Open (vs. Nemotron, Opus 5.5, and more)
Oct 15, 2026 · YouTube
16:43Gemini 4 Argon: Great 🐈 Animation, but a broken escalator. (Coding challenge vs Opus 5.5, Sol 6.1)
Oct 13, 2026 · YouTube
16:37Meta's new "Muse Wonder" AI is impressive... but can it compete with Anthropic's models?
Oct 12, 2026 · YouTube
11:12WOW... Haiku 5.5 is very impressive. RIP OpenAI?
Oct 7, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
22:02Is Pareto 26.10 Preview Just Routing to GPT Sol? I Tested It Against Sol 6.1 Pro
Oct 1, 2026 · YouTube
11:22I Tested Sonnet 5.5 Against Opus 5.5. Is It Actually Cheaper?
Sep 28, 2026 · YouTube
30:00Did Elon Musk's team "train the gay away"... and make Grok 4.7 worse?
Sep 26, 2026 · YouTube
19:21I Tested the Stealth "Space Bunny Alpha" Model Before It Went Offline
Sep 23, 2026 · YouTube
10:58So... how much better is Claude Opus 5.5 vs. GPT-6 Sol, really?
Sep 23, 2026 · YouTube
9:38Claude Opus 5.5... the BEST new model for coding!! 🤩
Sep 22, 2026 · YouTube
29:00Grok 4.7: improved, but GLM 5.3 FlashX beat it anyway (benchmark against Gemini, Sol, etc.)
Sep 22, 2026 · YouTube
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.
A chunky, ink-and-coral riso-print crow in a snapback, built from a small joint rig (hip, head, two wings, two legs) so it can genuinely tip, plant, spin and recover. The look is a two-ink screenprint on cream paper — misregistered coral ghost behind a near-black bird, grain over everything, a coral spotlight disc and a bobbing silhouette crowd.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>BIRD / BREAKS</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:#0b0b14;overflow:hidden;
font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#f4ead8}
body{display:flex;flex-direction:column}
#stage{flex:1;display:grid;place-items:center;min-height:0;padding:10px}
canvas{display:block;border-radius:3px;box-shadow:0 0 0 1px #000,0 18px 50px rgba(0,0,0,.7)}
#ui{flex:0 0 auto;display:flex;gap:16px;align-items:center;justify-content:center;
padding:8px 14px 14px;flex-wrap:wrap;background:#0b0b14}
.grp{display:flex;align-items:center;gap:8px;font-size:10px;letter-spacing:.14em;
text-transform:uppercase;color:#7c7c9c}
input[type=range]{width:104px;accent-color:#ff4b5c;height:16px}
.val{color:#ff4b5c;min-width:36px;display:inline-block;text-align:right;font-size:11px}
button{font:inherit;font-size:10px;letter-spacing:.12em;text-transform:uppercase;
background:#16162a;color:#f4ead8;border:1px solid #2e2e4a;border-radius:3px;
padding:6px 9px;cursor:pointer;transition:background .1s,color .1s,border-color .1s}
button:hover{background:#ff4b5c;color:#0b0b14;border-color:#ff4b5c}
button:active{transform:translateY(1px)}
#moves{display:flex;gap:5px;flex-wrap:wrap;justify-content:center}
</style>
</head>
<body>
<div id="stage"><canvas id="cv"></canvas></div>
<div id="ui">
<div class="grp">BPM <input id="bpm" type="range" min="70" max="160" value="96"><span class="val" id="bpmv">96</span></div>
<div class="grp">WEIGHT <input id="wt" type="range" min="0.3" max="2" step="0.05" value="1"><span class="val" id="wtv">1.00</span></div>
<div id="moves"></div>
</div>
<script>
(function(){
'use strict';
/* ---------- constants ---------- */
const W=960, H=600;
const BASE={x:480, y:410};
const GROUND=470;
const INK = '#141428';
const CORAL = '#ff4b5c';
const CREAM = '#f4ead8';
const YELLOW= '#ffc93c';
const cv = document.getElementById('cv');
const ctx = cv.getContext('2d');
let dpr = 1;
function fit(){
dpr = Math.min(2, window.devicePixelRatio || 1);
cv.width = Math.round(W*dpr);
cv.height = Math.round(H*dpr);
const st = document.getElementById('stage');
const s = Math.min((st.clientWidth-20)/W, (st.clientHeight-20)/H);
cv.style.width = Math.round(W*s)+'px';
cv.style.height = Math.round(H*s)+'px';
}
window.addEventListener('resize', fit);
/* ---------- paper grain ---------- */
const grainCv = document.createElement('canvas');
grainCv.width = grainCv.height = 140;
(function(){
const g = grainCv.getContext('2d');
const id = g.createImageData(140,140);
for(let i=0;i<id.data.length;i+=4){
const v = Math.random()*255;
id.data[i]=id.data[i+1]=id.data[i+2]=v;
id.data[i+3]=Math.random()*24;
}
g.putImageData(id,0,0);
})();
let grainPat = null;
/* ---------- pose rig ---------- */
const NEUTRAL = {
x:0, y:0, rot:0, sq:1, pvx:0, pvy:0,
head:0, wl:-0.35, wr:0.35,
tl:0.12, sl:-0.22, tr:-0.12, sr:0.22,
tail:0
};
const PKEYS = Object.keys(NEUTRAL);
function P(o){ const p={}; for(const k of PKEYS) p[k]=NEUTRAL[k]; if(o) for(const k in o) p[k]=o[k]; return p; }
function mix(a,b,t){ const o={}; for(const k of PKEYS) o[k]=a[k]+(b[k]-a[k])*t; return o; }
const E = {
lin : u=>u,
io : u=>u<.5 ? 2*u*u : 1-Math.pow(-2*u+2,2)/2,
out : u=>1-Math.pow(1-u,2),
out3: u=>1-Math.pow(1-u,3),
out5: u=>1-Math.pow(1-u,5),
in3 : u=>u*u*u,
hold: u=>0
};
function kf(u, keys){
if(u <= keys[0].t) return keys[0].p;
for(let i=1;i<keys.length;i++){
if(u <= keys[i].t){
const a=keys[i-1], b=keys[i];
const t=(u-a.t)/Math.max(1e-6,(b.t-a.t));
return mix(a.p, b.p, (b.e||E.io)(t));
}
}
return keys[keys.length-1].p;
}
function withEnds(u, fn, inT, outT, endRot){
if(u < inT) return mix(NEUTRAL, fn(0), E.out5(u/inT));
if(u > 1-outT){
const t = E.io((u-(1-outT))/outT);
const end = P({rot: endRot===undefined ? 0 : endRot});
return mix(fn(1), end, t);
}
return fn((u-inT)/(1-inT-outT));
}
/* ---------- the moves ---------- */
const MOVES = {};
MOVES.toprock = { label:'TOPROCK', beats:4, fn:function(u){
const A =P({x:-32,y:-8,rot:-0.13,head:-0.16,wl:-1.35,wr:0.55,tl:0.50,sl:-0.75,tr:-0.40,sr:0.05,tail:0.28});
const Ad=P({x:-32,y: 6,rot:-0.10,head: 0.12,wl:-1.10,wr:0.50,tl:0.44,sl:-0.62,tr:-0.34,sr:0.05,tail:0.22});
const M =P({x: 0,y:10,rot: 0.00,head: 0.14,wl:-0.55,wr:0.55,tl:0.22,sl:-0.42,tr:-0.22,sr:0.42,tail:0.00});
const B =P({x: 32,y:-8,rot: 0.13,head: 0.16,wl:-0.55,wr:1.35,tl:0.40,sl:-0.05,tr:-0.50,sr:0.75,tail:-0.28});
const Bd=P({x: 32,y: 6,rot: 0.10,head:-0.12,wl:-0.50,wr:1.10,tl:0.34,sl:-0.05,tr:-0.44,sr:0.62,tail:-0.22});
return kf(u,[
{t:0.00,p:P()},
{t:0.10,p:A ,e:E.out5},
{t:0.24,p:Ad,e:E.io},
{t:0.36,p:M ,e:E.out3},
{t:0.50,p:B ,e:E.out5},
{t:0.64,p:Bd,e:E.io},
{t:0.76,p:M ,e:E.out3},
{t:0.88,p:A ,e:E.out3},
{t:1.00,p:P(),e:E.io}
]);
}};
MOVES.sixstep = { label:'SIX-STEP', beats:4, fn:function(u){
const L = {y:54,rot:0.30,head:-0.30,wl:0.10,wr:0.20,tail:0.45};
const base=P(L);
const s1=P(Object.assign({},L,{x:-14,tl: 1.30,sl:-0.55,tr:-1.10,sr: 1.10}));
const s2=P(Object.assign({},L,{x: 6,tl: 0.55,sl:-1.35,tr:-0.45,sr: 0.45}));
const s3=P(Object.assign({},L,{x: 14,tl:-0.55,sl:-1.20,tr: 0.55,sr:-0.45}));
const s4=P(Object.assign({},L,{x: -6,tl:-1.10,sl:-0.45,tr: 1.30,sr:-0.55}));
return kf(u,[
{t:0.00,p:P()},
{t:0.14,p:base,e:E.out5},
{t:0.30,p:s1,e:E.io},
{t:0.46,p:s2,e:E.out3},
{t:0.62,p:s3,e:E.io},
{t:0.78,p:s4,e:E.out3},
{t:0.90,p:base,e:E.io},
{t:1.00,p:P(),e:E.io}
]);
}};
MOVES.windmill = { label:'WINDMILL', beats:4, heavy:true, fn:function(u){
return withEnds(u, function(v){
const lp = v*Math.PI*4;
return P({
x:0, y:62, rot:-Math.PI*2*E.io(v), pvx:0, pvy:-34,
head:-0.30,
wl:1.10+0.50*Math.sin(lp),
wr:1.10+0.50*Math.sin(lp+Math.PI),
tl:1.30+0.90*Math.sin(lp),
sl:-0.50,
tr:1.30+0.90*Math.sin(lp+Math.PI),
sr:-0.50,
tail:0.30
});
}, 0.14, 0.14, -Math.PI*2);
}};
MOVES.headspin = { label:'HEADSPIN', beats:4, heavy:true, fn:function(u){
let rot, y;
if(u < 0.15){ const t=E.out3(u/0.15); rot=Math.PI*t; y=115*t; }
else if(u < 0.85){ const t=(u-0.15)/0.70; rot=Math.PI+Math.PI*4*t; y=115; }
else { const t=E.io((u-0.85)/0.15); rot=Math.PI*5+Math.PI*t; y=115*(1-t); }
const b = Math.min(1, Math.min(u,1-u)/0.18);
const bb = E.io(b);
return P({
x:0, y:y, rot:rot, pvx:30, pvy:-76,
head:-0.20*bb,
wl:-0.35+2.35*bb,
wr: 0.35+2.05*bb,
tl: 0.12+0.43*bb,
sl:-0.22-0.18*bb,
tr:-0.12-0.43*bb,
sr: 0.22+0.18*bb,
tail:0.30*bb
});
}};
MOVES.freeze = { label:'FREEZE', beats:3, heavy:true, fn:function(u,t){
const b = Math.min(1, Math.min(u,1-u)/0.22);
const bb = E.out5(b);
const trem = Math.sin(t*36)*0.016*bb;
return P({
x:0, y:26*bb, rot:1.15*bb+trem,
head:-0.35*bb,
wl:-0.35+1.50*bb,
wr: 0.35-2.35*bb,
tl: 0.12+0.23*bb,
sl:-0.22-0.08*bb,
tr:-0.12+1.97*bb,
sr: 0.22+0.08*bb,
tail:0.40*bb
});
}};
MOVES.backspin = { label:'BACKSPIN', beats:3, heavy:true, fn:function(u){
return withEnds(u, function(v){
return P({
x:0, y:66, rot:-Math.PI*4*E.io(v), pvx:0, pvy:-34,
head:0.20,
wl:1.60, wr:2.00,
tl:0.70, sl:-0.60, tr:-0.70, sr:0.60,
tail:0.20
});
}, 0.15, 0.15, -Math.PI*4);
}};
MOVES.hop = { label:'HOP', beats:2, fn:function(u){
return kf(u,[
{t:0.00,p:P()},
{t:0.16,p:P({y:18,sq:0.80,tl:0.55,sl:-0.95,tr:-0.55,sr:0.95,wl:-0.95,wr:0.95,head:0.15}),e:E.out3},
{t:0.34,p:P({y:-72,sq:1.14,tl:-0.30,sl:-0.10,tr:0.30,sr:0.10,wl:-2.20,wr:2.20,head:-0.20}),e:E.out5},
{t:0.52,p:P({y:-84,sq:1.06,tl:1.20,sl:-0.20,tr:-1.20,sr:0.20,wl:-2.60,wr:2.60,head:0.10}),e:E.io},
{t:0.74,p:P({y:12,sq:0.76,tl:0.60,sl:-1.00,tr:-0.60,sr:1.00,wl:-0.40,wr:0.40,head:0.22}),e:E.in3},
{t:0.88,p:P({y:-4,sq:1.05}),e:E.out3},
{t:1.00,p:P(),e:E.io}
]);
}};
MOVES.recover = { label:'RECOVER', beats:1, fn:function(u){
const br = Math.sin(u*Math.PI*2)*0.5+0.5;
return P({
y:5*br, sq:1-0.035*br,
head:0.10*Math.sin(u*Math.PI*2+1.2),
wl:-0.35-0.12*br,
wr: 0.35+0.12*br,
tl: 0.12+0.06*br,
tr:-0.12-0.06*br,
sl:-0.22, sr:0.22
});
}};
/* ---------- scheduler ---------- */
let bpm = 96;
let weight = 1;
let musicT = 0;
let plan = [];
let cur = null;
let curBeat = 0;
let lastT = performance.now();
function shuffle(a){
for(let i=a.length-1;i>0;i--){ const j=(Math.random()*(i+1))|0; const t=a[i]; a[i]=a[j]; a[j]=t; }
return a;
}
function refill(){
plan.push({name:'toprock', beats:4});
const set = shuffle(['sixstep','windmill','freeze','headspin','backspin','hop']).slice(0,3);
for(const n of set){
plan.push({name:n, beats:MOVES[n].beats});
plan.push({name:'recover', beats:0.5+Math.random()*0.7});
}
}
function nextSeg(){
if(plan.length===0) refill();
const s = plan.shift();
cur = { name:s.name, fn:MOVES[s.name].fn, beats:s.beats };
curBeat = 0;
}
/* ---------- drawing helpers ---------- */
function rr(g,x,y,w,h,r){
g.beginPath();
g.moveTo(x+r,y);
g.arcTo(x+w,y,x+w,y+h,r);
g.arcTo(x+w,y+h,x,y+h,r);
g.arcTo(x,y+h,x,y,r);
g.arcTo(x,y,x+w,y,r);
g.closePath();
}
function drawLeg(ta,sa,fill,K,sc){
const L1=30*sc, L2=30*sc;
const kx=Math.sin(ta)*L1, ky=Math.cos(ta)*L1;
const a2=ta+sa;
const fx=kx+Math.sin(a2)*L2, fy=ky+Math.cos(a2)*L2;
ctx.lineCap='round';
ctx.strokeStyle=K; ctx.lineWidth=15*sc;
ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(kx,ky); ctx.stroke();
ctx.strokeStyle=fill; ctx.lineWidth= 9*sc;
ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(kx,ky); ctx.stroke();
ctx.strokeStyle=K; ctx.lineWidth=11*sc;
ctx.beginPath(); ctx.moveTo(kx,ky); ctx.lineTo(fx,fy); ctx.stroke();
ctx.strokeStyle=fill; ctx.lineWidth= 6*sc;
ctx.beginPath(); ctx.moveTo(kx,ky); ctx.lineTo(fx,fy); ctx.stroke();
ctx.strokeStyle=K; ctx.lineWidth= 9*sc;
ctx.beginPath(); ctx.moveTo(fx,fy); ctx.lineTo(fx+11*sc,fy+2); ctx.stroke();
ctx.strokeStyle=fill; ctx.lineWidth= 5*sc;
ctx.beginPath(); ctx.moveTo(fx,fy); ctx.lineTo(fx+11*sc,fy+2); ctx.stroke();
}
function drawWing(a,fill,K,sc){
const L=46*sc, sx=4, sy=-48;
const ex=sx+Math.sin(a)*L, ey=sy+Math.cos(a)*L;
ctx.lineCap='round';
ctx.strokeStyle=K; ctx.lineWidth=24*sc;
ctx.beginPath(); ctx.moveTo(sx,sy); ctx.lineTo(ex,ey); ctx.stroke();
ctx.strokeStyle=fill; ctx.lineWidth=15*sc;
ctx.beginPath(); ctx.moveTo(sx,sy); ctx.lineTo(ex,ey); ctx.stroke();
ctx.beginPath(); ctx.arc(ex,ey,9*sc,0,Math.PI*2);
ctx.fillStyle=fill; ctx.fill();
ctx.lineWidth=4; ctx.strokeStyle=K; ctx.stroke();
}
function drawTail(a,K){
ctx.save();
ctx.translate(-30,-42);
ctx.rotate(a);
for(let i=-1;i<=1;i++){
ctx.save();
ctx.rotate(i*0.30);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-44,-10);
ctx.lineTo(-52,0);
ctx.lineTo(-44,10);
ctx.closePath();
ctx.fillStyle=K; ctx.fill();
ctx.lineWidth=4; ctx.strokeStyle=K; ctx.stroke();
ctx.restore();
}
ctx.restore();
}
function drawBody(C,K){
ctx.save();
ctx.translate(0,-34);
ctx.rotate(-0.10);
ctx.beginPath();
ctx.ellipse(0,0,38,32,0,0,Math.PI*2);
ctx.fillStyle=C('#1b1b36'); ctx.fill();
ctx.lineWidth=5; ctx.strokeStyle=K; ctx.stroke();
ctx.beginPath();
ctx.ellipse(13,7,19,17,0.15,0,Math.PI*2);
ctx.fillStyle=C('#2c2c54'); ctx.fill();
ctx.restore();
}
function drawHead(a,C,K){
ctx.save();
ctx.translate(30,-76);
ctx.rotate(a);
// neck
ctx.beginPath();
ctx.moveTo(-16,10); ctx.lineTo(-6,26); ctx.lineTo(10,22); ctx.lineTo(6,4);
ctx.closePath();
ctx.fillStyle=C('#1b1b36'); ctx.fill();
// skull
ctx.beginPath(); ctx.arc(0,0,22,0,Math.PI*2);
ctx.fillStyle=C('#1b1b36'); ctx.fill();
ctx.lineWidth=5; ctx.strokeStyle=K; ctx.stroke();
// beak
ctx.beginPath();
ctx.moveTo(17,4); ctx.lineTo(46,9); ctx.lineTo(17,15); ctx.closePath();
ctx.fillStyle=C(YELLOW); ctx.fill();
ctx.lineWidth=4; ctx.strokeStyle=K; ctx.stroke();
// eye
ctx.beginPath(); ctx.arc(11,-3,7,0,Math.PI*2);
ctx.fillStyle=C('#f4ead8'); ctx.fill();
ctx.lineWidth=3.5; ctx.strokeStyle=K; ctx.stroke();
ctx.beginPath(); ctx.arc(13,-3,3.2,0,Math.PI*2);
ctx.fillStyle=K; ctx.fill();
// cap dome
ctx.beginPath();
ctx.arc(0,-7,22,Math.PI,Math.PI*2);
ctx.closePath();
ctx.fillStyle=C(CORAL); ctx.fill();
ctx.lineWidth=4.5; ctx.strokeStyle=K; ctx.stroke();
// brim
ctx.beginPath();
ctx.moveTo(12,-9);
ctx.quadraticCurveTo(40,-11,44,-2);
ctx.quadraticCurveTo(30,-5,12,-3);
ctx.closePath();
ctx.fillStyle=C(CORAL); ctx.fill();
ctx.lineWidth=4; ctx.strokeStyle=K; ctx.stroke();
ctx.restore();
}
function drawBird(p, ghost){
const C = c => ghost ? CORAL : c;
const K = ghost ? CORAL : INK;
ctx.save();
ctx.translate(BASE.x+p.x, BASE.y+p.y);
ctx.translate(p.pvx, p.pvy);
ctx.rotate(p.rot);
ctx.translate(-p.pvx, -p.pvy);
const sq = 1 + (p.sq-1)*weight;
ctx.scale(1/Math.sqrt(sq), sq);
drawLeg(p.tl, p.sl, C('#d99a2b'), K, 0.92);
drawWing(p.wl, C('#20203c'), K, 0.90);
drawTail(p.tail, K);
drawBody(C, K);
drawHead(p.head, C, K);
drawLeg(p.tr, p.sr, C(YELLOW), K, 1.0);
drawWing(p.wr, C('#2a2a4e'), K, 1.0);
ctx.restore();
}
function drawBirdFull(p){
const spinning = cur && (cur.name==='windmill' || cur.name==='headspin' || cur.name==='backspin');
if(spinning){
for(let i=3;i>=1;i--){
const q = Object.assign({}, p, {rot: p.rot - i*0.5});
ctx.globalAlpha = 0.10*i;
drawBird(q, true);
}
ctx.globalAlpha = 1;
}
// misregistered coral plate
ctx.save();
ctx.globalCompositeOperation = 'multiply';
ctx.translate(5,4);
drawBird(p, true);
ctx.restore();
// black plate
ctx.save();
ctx.globalCompositeOperation = 'multiply';
drawBird(p, false);
ctx.restore();
}
function drawShadow(p){
const h = Math.max(0, -p.y);
const s = 1/(1+h/140);
ctx.save();
ctx.globalAlpha = 0.30*s;
ctx.fillStyle = INK;
ctx.beginPath();
ctx.ellipse(BASE.x + p.x*0.7, GROUND+4, 66*s, 15*s, 0, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
function drawCrowd(){
const beat = musicT/(60/bpm);
ctx.fillStyle = INK;
for(let i=0;i<7;i++){
const x = 300 + i*100;
const ph = i*0.83;
const bob= Math.sin((beat+ph)*Math.PI*2)*8;
const r = 24 + ((i*7)%3)*5;
ctx.beginPath(); ctx.arc(x, 596+bob, r, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.ellipse(x, 640+bob, r*1.7, r*1.4, 0, 0, Math.PI*2); ctx.fill();
}
}
function drawBoombox(){
const beat = (musicT/(60/bpm))%1;
const pulse = Math.exp(-beat*7);
ctx.save();
ctx.translate(56,486);
ctx.fillStyle = INK;
rr(ctx,0,0,158,86,10); ctx.fill();
ctx.strokeStyle = CREAM; ctx.lineWidth = 3;
rr(ctx,0,0,158,86,10); ctx.stroke();
for(const sx of [40,118]){
ctx.fillStyle = CREAM;
ctx.beginPath(); ctx.arc(sx,50,26,0,Math.PI*2); ctx.fill();
ctx.fillStyle = CORAL;
ctx.beginPath(); ctx.arc(sx,50,26*(0.50+0.42*pulse),0,Math.PI*2); ctx.fill();
ctx.fillStyle = INK;
ctx.beginPath(); ctx.arc(sx,50,8,0,Math.PI*2); ctx.fill();
}
ctx.strokeStyle = INK; ctx.lineWidth = 7; ctx.lineCap='round';
ctx.beginPath();
ctx.moveTo(34,-4); ctx.lineTo(34,-20); ctx.lineTo(124,-20); ctx.lineTo(124,-4);
ctx.stroke();
ctx.restore();
}
/* ---------- render ---------- */
function render(p){
ctx.setTransform(dpr,0,0,dpr,0,0);
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1;
ctx.fillStyle = CREAM;
ctx.fillRect(0,0,W,H);
ctx.fillStyle = CORAL;
ctx.beginPath(); ctx.arc(480,352,186,0,Math.PI*2); ctx.fill();
ctx.fillStyle = INK;
ctx.fillRect(0,GROUND,W,H-GROUND);
ctx.fillStyle = 'rgba(255,75,92,0.30)';
ctx.beginPath(); ctx.ellipse(480,GROUND+34,250,34,0,0,Math.PI*2); ctx.fill();
drawCrowd();
drawBoombox();
drawShadow(p);
drawBirdFull(p);
if(!grainPat) grainPat = ctx.createPattern(grainCv,'repeat');
ctx.globalAlpha = 0.55;
ctx.fillStyle = grainPat;
ctx.fillRect(0,0,W,H);
ctx.globalAlpha = 1;
}
/* ---------- loop ---------- */
function loop(now){
const dt = Math.min(0.06, (now-lastT)/1000);
lastT = now;
musicT += dt;
const beatDur = 60/bpm;
if(!cur) nextSeg();
curBeat += dt/beatDur;
if(curBeat >= cur.beats) nextSeg();
const u = Math.min(1, curBeat/cur.beats);
const pose = cur.fn(u, curBeat*beatDur);
render(pose);
requestAnimationFrame(loop);
}
/* ---------- controls ---------- */
const bpmEl = document.getElementById('bpm');
const bpmV = document.getElementById('bpmv');
bpmEl.addEventListener('input', function(){
bpm = +bpmEl.value;
bpmV.textContent = bpm;
});
const wtEl = document.getElementById('wt');
const wtV = document.getElementById('wtv');
wtEl.addEventListener('input', function(){
weight = +wtEl.value;
wtV.textContent = weight.toFixed(2);
});
const movesEl = document.getElementById('moves');
['toprock','sixstep','windmill','freeze','headspin','backspin','hop'].forEach(function(k){
const b = document.createElement('button');
b.textContent = MOVES[k].label;
b.addEventListener('click', function(){
plan.unshift({name:k, beats:MOVES[k].beats});
});
movesEl.appendChild(b);
});
/* ---------- go ---------- */
fit();
nextSeg();
lastT = performance.now();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
recover segment. Every move starts and ends on the same neutral crouch, so transitions are clean, but the inside of each move is keyframed with asymmetric easings: out5 for the snap into a hit, io for the settle, in3 for the drop into a landing squash. The freeze holds a 36 Hz micro-tremble so the balance looks muscular rather than frozen, and the windmill/backspin use a pivot at the body centre so the mass stays planted while the limbs whip around it.