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.
The bird is a chunky urban pigeon in tiny sneakers and a gold chain, built to look like a neighbourhood regular who has rehearsed this routine for years. The look is hand-drawn paper-cutout line art on canvas — slate feathers, an iridescent neck, orange feet, and exaggerated squash-and-stretch for weight.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Pigeon Breaks — Crouton Alpha</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%;overflow:hidden;background:#0e0b1e;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#fff}
canvas{display:block;width:100vw;height:100vh}
#ui{position:fixed;left:50%;transform:translateX(-50%);bottom:14px;width:min(960px,94vw);background:rgba(12,10,24,.72);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.16);border-radius:18px;padding:12px 14px;display:flex;flex-wrap:wrap;gap:12px;align-items:center;justify-content:center;box-shadow:0 10px 40px rgba(0,0,0,.45)}
.grp{display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.06);padding:8px 10px;border-radius:12px}
label{font-size:11px;letter-spacing:.12em;text-transform:uppercase;opacity:.85;white-space:nowrap}
input[type=range]{width:110px;accent-color:#ffcc33}
button{appearance:none;border:0;border-radius:999px;padding:9px 13px;font-weight:800;letter-spacing:.04em;cursor:pointer;background:#ffcc33;color:#17120a;box-shadow:0 4px 0 #b8911f}
button:active{transform:translateY(2px);box-shadow:0 2px 0 #b8911f}
button.alt{background:#2de2e6;color:#062a2c;box-shadow:0 4px 0 #149a9e}
button.ghost{background:rgba(255,255,255,.12);color:#fff;box-shadow:none;border:1px solid rgba(255,255,255,.2)}
button.on{outline:2px solid #fff}
#title{position:fixed;top:14px;left:16px;pointer-events:none;text-shadow:0 2px 12px rgba(0,0,0,.5)}
#title h1{margin:0;font-size:clamp(22px,4vw,42px);line-height:.9;letter-spacing:-.02em}
#title p{margin:6px 0 0;font-size:12px;letter-spacing:.18em;text-transform:uppercase;opacity:.85}
#move{position:fixed;top:16px;right:16px;background:rgba(0,0,0,.45);border:1px solid rgba(255,255,255,.18);padding:8px 12px;border-radius:999px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;font-size:12px}
@media(max-width:640px){input[type=range]{width:80px} #ui{gap:8px;padding:10px}}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="title"><h1>PIGEON<br>BREAKS</h1><p>toprock · freeze · windmill</p></div>
<div id="move">TOPROCK</div>
<div id="ui">
<div class="grp"><label for="tempo">Tempo <span id="tempoV">112</span></label><input id="tempo" type="range" min="70" max="165" value="112"></div>
<div class="grp"><label for="weight">Weight <span id="weightV">1.2</span></label><input id="weight" type="range" min="0.5" max="2" step="0.1" value="1.2"></div>
<button id="freezeBtn">HIT FREEZE!</button>
<button id="windBtn" class="alt">WINDMILL!</button>
<button id="slowBtn" class="ghost">SLOW-MO</button>
<button id="lightBtn" class="ghost">DAY / CLUB</button>
<button id="camBtn" class="ghost on">CAM FOLLOW</button>
<button id="soundBtn" class="ghost">SOUND OFF</button>
</div>
<script>
const cv=document.getElementById('c'), ctx=cv.getContext('2d');
let W=0,H=0,DPR=1;
function resize(){DPR=Math.min(2,devicePixelRatio||1);W=innerWidth;H=innerHeight;cv.width=W*DPR;cv.height=H*DPR;ctx.setTransform(DPR,0,0,DPR,0,0)}
addEventListener('resize',resize);resize();
let tempo=112, weight=1.2, mode='club', slowmo=false, follow=true, soundOn=false, actx=null;
let particles=[], feathers=[], notes=[], shake=0, beat=0, lastBeat=-1, tGlobal=0;
const $=id=>document.getElementById(id);
$('tempo').oninput=e=>{tempo=+e.target.value;$('tempoV').textContent=tempo};
$('weight').oninput=e=>{weight=+e.target.value;$('weightV').textContent=weight.toFixed(1)};
$('slowBtn').onclick=e=>{slowmo=!slowmo;e.target.classList.toggle('on',slowmo)};
$('lightBtn').onclick=()=>{mode=mode==='club'?'day':'club'};
$('camBtn').onclick=e=>{follow=!follow;e.target.classList.toggle('on',follow)};
$('freezeBtn').onclick=()=>queue('freeze');
$('windBtn').onclick=()=>queue('windmill');
$('soundBtn').onclick=e=>{soundOn=!soundOn;if(soundOn&&!actx)actx=new (window.AudioContext||window.webkitAudioContext)();if(actx&&actx.state==='suspended')actx.resume();e.target.textContent=soundOn?'SOUND ON':'SOUND OFF';e.target.classList.toggle('on',soundOn)};
addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();queue('freeze')} if(e.key==='w')queue('windmill')});
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)), lerp=(a,b,t)=>a+(b-a)*t;
const s=Math.sin, PI=Math.PI;
function base(){return{y:0,rot:0,squash:0,head:0,wL:.5,wR:-.5,lL:.18,lR:-.18,tail:.35,beak:0,sway:0}}
function easeIO(t){return t<.5?2*t*t:1-Math.pow(-2*t+2,2)/2}
function easeOut(t){return 1-Math.pow(1-t,3)}
const MOVES={
toprock:{label:'TOPROCK',beats:4,fn(t){let p=base(),ph=t*4*PI; p.y=-Math.abs(s(ph))*9*weight; p.rot=s(ph)*.07; p.squash=s(ph)*.08*weight; p.sway=s(ph)*14; p.head=s(ph*2)*.15; p.wL=.5+s(ph)*.6; p.wR=-.5-s(ph)*.6; p.lL=.2+Math.max(0,s(ph))*.7; p.lR=-.2+Math.max(0,-s(ph))*.7; p.tail=.35+s(ph)*.2; return p}},
downrock:{label:'DOWNROCK',beats:4,fn(t){let p=base(); p.y=-26*weight+s(t*PI*8)*3; p.rot=s(t*PI*4)*.12; p.squash=.18*weight; p.sway=s(t*PI*8)*10; p.head=.3; p.wL=.4; p.wR=1.25; p.lL=s(t*PI*8)*.8; p.lR=-s(t*PI*8)*.8; p.tail=.6; return p}},
freeze:{label:'FREEZE',beats:4,fn(t){let p=base();
if(t<.22){let k=t/.22; p.y=-30*weight*easeIO(k); p.squash=.35*weight*easeIO(k); p.rot=0; p.wL=.8; p.wR=-.8; p.lL=.3; p.lR=-.3}
else if(t<.42){let k=(t-.22)/.2; p.y=lerp(-30*weight,62*weight,easeOut(k)); p.rot=lerp(0,PI,easeIO(k)); p.squash=-.15*k; p.wL=lerp(.8,2.6,k); p.wR=lerp(-.8,-2.6,k); p.lL=lerp(.3,.9,k); p.lR=lerp(-.3,-.9,k); p.head=.4}
else if(t<.82){let k=(t-.42)/.4; p.y=62*weight+s(k*30)*2*weight; p.rot=PI+s(k*22)*.045*weight; p.squash=.06*s(k*22)*weight; p.wL=2.6+s(k*22)*.06; p.wR=-2.6-s(k*22)*.06; p.lL=.9+s(k*18)*.12*weight; p.lR=-.9-s(k*18)*.12*weight; p.head=.5+s(k*22)*.1; p.tail=.2}
else{let k=(t-.82)/.18; p.y=lerp(62*weight,0,easeIO(k)); p.rot=lerp(PI,0,easeIO(k)); p.squash=.3*weight*k; p.wL=lerp(2.6,.5,k); p.wR=lerp(-2.6,-.5,k); p.lL=lerp(.9,.2,k); p.lR=lerp(-.9,-.2,k)}
return p}},
windmill:{label:'WINDMILL',beats:4,fn(t){let p=base(); let spin=easeIO(t)*PI*4; p.rot=spin; p.y=-34*weight+s(t*PI*8)*4; p.squash=.22*weight; p.wL=.9; p.wR=-.9; p.lL=s(t*PI*8)*1.0; p.lR=-s(t*PI*8)*1.0; p.head=.6; p.tail=.7; p.sway=s(t*PI*4)*8; return p}},
flare:{label:'AIR FLARE',beats:3,fn(t){let p=base(); p.y=s(t*PI)*78*weight; p.rot=t*PI*2; p.squash=(t>.85?.35*weight:0); p.wL=1.4; p.wR=-1.4; p.lL=1.1; p.lR=-1.1; p.head=.3; p.tail=.5; return p}},
recovery:{label:'RECOVERY',beats:2,fn(t){let p=base(); let amp=(1-t)*.28; p.rot=s(t*22)*amp; p.y=-s(t*PI)*12*weight; p.squash=.2*weight*s(t*PI); p.wL=.5+s(t*28)*(1-t)*.9; p.wR=-.5-s(t*28)*(1-t)*.9; p.lL=.3+s(t*18)*.3*(1-t); p.lR=-.3-s(t*18)*.3*(1-t); p.head=s(t*20)*.3*(1-t); p.tail=.35+s(t*24)*.3*(1-t); return p}}
};
let seq=['toprock','downrock','toprock','freeze','recovery','windmill','recovery','flare','toprock'];
let idx=0, mt=0, queued=null, prevPose=base(), landed=false;
function queue(name){queued=name}
function curMove(){return MOVES[seq[idx]]}
function advance(dt){
let dur=curMove().beats*60/tempo; mt+=dt/dur;
if(mt>=1){prevPose=MOVES[seq[idx]].fn(1); mt=0; landed=false;
if(queued){seq[idx]=queued;queued=null}else idx=(idx+1)%seq.length;
// keep sequence sensible after a queued move
if(seq[idx]==='freeze'||seq[idx]==='windmill'){ /* next in seq will be recovery naturally if we insert */ }
}
}
// insert recovery after queued power moves by patching seq on queue execution
const origAdvance=advance;
// simpler: when queued executes, force following move to recovery via flag
let forceRecovery=false;
function queue2(n){queued=n;forceRecovery=true}
// rebind buttons to queue2 logic by monkey patching advance check: we handle in loop below
function spawnDust(x,y,n){for(let i=0;i<n;i++)particles.push({x,y,vx:(Math.random()-.5)*3,vy:-Math.random()*2-0.5,life:1,r:4+Math.random()*10})}
function spawnFeathers(x,y){for(let i=0;i<10;i++)feathers.push({x,y,vx:(Math.random()-.5)*6,vy:-Math.random()*5-1,rot:Math.random()*6,vr:(Math.random()-.5)*.4,life:1})}
function kick(){if(!soundOn||!actx)return;let o=actx.createOscillator(),g=actx.createGain();o.frequency.setValueAtTime(150,actx.currentTime);o.frequency.exponentialRampToValueAtTime(40,actx.currentTime+.12);g.gain.setValueAtTime(.25,actx.currentTime);g.gain.exponentialRampToValueAtTime(.001,actx.currentTime+.15);o.connect(g).connect(actx.destination);o.start();o.stop(actx.currentTime+.16)}
function drawBird(p,cx,floorY,pal){
let scale=Math.min(W,H)/520; if(follow) scale*=1+clamp(p.y/300,0,.18);
ctx.save(); ctx.translate(cx+p.sway, floorY-58*scale+p.y); ctx.scale(scale,scale); ctx.rotate(p.rot);
// squash
ctx.scale(1+p.squash*.6,1-p.squash*.5);
// tail
ctx.save(); ctx.translate(-34,-6); ctx.rotate(-.4+p.tail*.4); ctx.fillStyle=pal.tail;
for(let i=-1;i<=1;i++){ctx.save();ctx.rotate(i*.28);ctx.beginPath();ctx.ellipse(-22,i*6,26,7,0,0,PI*2);ctx.fill();ctx.restore()} ctx.restore();
// far wing
drawWing(p.wR,pal,true);
// legs
drawLeg(p.lL,10,pal); drawLeg(p.lR,-10,pal);
// body
ctx.fillStyle=pal.body; ctx.beginPath(); ctx.ellipse(0,0,46,36,0,0,PI*2); ctx.fill();
ctx.fillStyle=pal.belly; ctx.beginPath(); ctx.ellipse(8,10,30,22,0,0,PI*2); ctx.fill();
// iridescent neck patch
let grad=ctx.createLinearGradient(20,-30,45,10); grad.addColorStop(0,pal.neck1); grad.addColorStop(1,pal.neck2);
ctx.fillStyle=grad; ctx.beginPath(); ctx.ellipse(28,-14,20,18,.4,0,PI*2); ctx.fill();
// head
ctx.save(); ctx.translate(38,-28); ctx.rotate(p.head);
ctx.fillStyle=pal.body; ctx.beginPath(); ctx.arc(0,0,20,0,PI*2); ctx.fill();
// crest
ctx.strokeStyle=pal.body; ctx.lineWidth=4; ctx.lineCap='round';
ctx.beginPath(); ctx.moveTo(-6,-16); ctx.quadraticCurveTo(-14,-28,-22,-24); ctx.stroke();
ctx.beginPath(); ctx.moveTo(-2,-18); ctx.quadraticCurveTo(-6,-32,-14,-30); ctx.stroke();
// beak
ctx.fillStyle=pal.beak; ctx.beginPath(); ctx.moveTo(14,-4); ctx.lineTo(34,2+p.beak*4); ctx.lineTo(14,8); ctx.closePath(); ctx.fill();
// eye - grumpy
ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(6,-6,6,0,PI*2); ctx.fill();
ctx.fillStyle='#111'; ctx.beginPath(); ctx.arc(8,-6,3,0,PI*2); ctx.fill();
ctx.strokeStyle='#111'; ctx.lineWidth=3; ctx.beginPath(); ctx.moveTo(0,-14); ctx.lineTo(14,-11); ctx.stroke();
ctx.restore();
// gold chain
ctx.strokeStyle='#ffcc33'; ctx.lineWidth=3; ctx.setLineDash([5,4]); ctx.beginPath(); ctx.arc(10,6,24,.3,PI-.3); ctx.stroke(); ctx.setLineDash([]);
ctx.fillStyle='#ffcc33'; ctx.beginPath(); ctx.arc(10,30+s(p.rot*3)*2,5,0,PI*2); ctx.fill();
// near wing
drawWing(p.wL,pal,false);
ctx.restore();
}
function drawWing(a,pal,far){
ctx.save(); ctx.translate(6,-8); ctx.rotate(a);
ctx.fillStyle=far?pal.tail:pal.wing; ctx.beginPath(); ctx.ellipse(0,22,13,26,0,0,PI*2); ctx.fill();
ctx.strokeStyle='rgba(0,0,0,.25)'; ctx.lineWidth=2;
for(let i=0;i<3;i++){ctx.beginPath();ctx.moveTo(-6,18+i*8);ctx.lineTo(8,22+i*8);ctx.stroke()}
// feather fingers
ctx.beginPath(); ctx.moveTo(-4,44); ctx.lineTo(-10,54); ctx.moveTo(2,46); ctx.lineTo(0,58); ctx.moveTo(8,44); ctx.lineTo(12,54); ctx.stroke();
ctx.restore();
}
function drawLeg(a,hipX,pal){
ctx.save(); ctx.translate(hipX,26); ctx.rotate(a);
ctx.strokeStyle=pal.leg; ctx.lineWidth=6; ctx.lineCap='round';
ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(0,20); ctx.stroke();
ctx.translate(0,20); ctx.rotate(-a*.6);
ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(0,18); ctx.stroke();
// sneaker
ctx.translate(0,20); ctx.fillStyle='#fff'; ctx.beginPath(); ctx.roundRect(-6,-4,24,11,5); ctx.fill();
ctx.fillStyle='#e63946'; ctx.fillRect(2,-4,5,11); ctx.fillStyle='#222'; ctx.fillRect(-6,5,24,3);
ctx.restore();
}
function palettes(){
if(mode==='club') return{bg:'#0e0b1e',floor:'#241640',spot:'rgba(255,210,80,.20)',accent:'#ff4d8d',accent2:'#2de2e6',body:'#8a94a6',belly:'#d7dde8',neck1:'#2de2e6',neck2:'#9b5de5',wing:'#6b768c',tail:'#4a5468',beak:'#f4a261',leg:'#f4a261',crowd:'#05030a'};
return{bg:'#f2e8d5',floor:'#d9c7a5',spot:'rgba(255,255,255,.35)',accent:'#e63946',accent2:'#2a9d8f',body:'#8a94a6',belly:'#eef2f7',neck1:'#2a9d8f',neck2:'#7b2cbf',wing:'#6b768c',tail:'#4a5468',beak:'#f4a261',leg:'#f4a261',crowd:'#2b2118'};
}
let last=performance.now();
function loop(now){
let dt=Math.min(.05,(now-last)/1000); last=now; if(slowmo)dt*=.22; tGlobal+=dt;
beat=tGlobal*tempo/60; if(Math.floor(beat)!==lastBeat){lastBeat=Math.floor(beat);kick(); if(Math.random()<.5)notes.push({x:W*.5+(Math.random()-.5)*W*.6,y:H*.35,vy:-.6,life:1,rot:Math.random()*6})}
// advance with queued handling
let dur=curMove().beats*60/tempo; mt+=dt/dur;
if(mt>=1){prevPose=curMove().fn(1); mt=0; landed=false;
if(queued){seq[idx]=queued;queued=null; // insert recovery next
seq.splice(idx+1,0,'recovery');
} else idx=(idx+1)%seq.length;
}
let name=seq[idx], mv=MOVES[name]; $('move').textContent=mv.label;
let pose=mv.fn(mt);
// blend from prev at start
if(mt<.15){let k=easeIO(mt/.15); for(let key in pose) pose[key]=lerp(prevPose[key]||0,pose[key],k)}
// landing effects
if(!landed && ((name==='freeze'&&mt>.42)||(name==='flare'&&mt>.9)||(name==='windmill'&&mt>.05))){landed=true; spawnDust(W/2,H*.78,14*weight); shake=6*weight; if(name==='windmill')spawnFeathers(W/2,H*.7)}
shake*=Math.pow(.001,dt); // decay
let pal=palettes(), floorY=H*.78, cx=W/2+(shake?(Math.random()-.5)*shake:0), cy=(shake?(Math.random()-.5)*shake:0);
ctx.save(); ctx.translate(cx,cy);
// bg
ctx.fillStyle=pal.bg; ctx.fillRect(-20,-20,W+40,H+40);
// wall shapes
ctx.fillStyle=pal.accent; ctx.globalAlpha=.18; ctx.beginPath(); ctx.arc(W*.18,H*.28,90,0,PI*2); ctx.fill();
ctx.fillStyle=pal.accent2; ctx.globalAlpha=.15; ctx.beginPath(); ctx.arc(W*.82,H*.22,120,0,PI*2); ctx.fill(); ctx.globalAlpha=1;
// speakers
ctx.fillStyle=mode==='club'?'#120d24':'#3a2e22'; ctx.fillRect(W*.06,H*.42,54,110); ctx.fillRect(W*.06,H*.42+120,54,70); ctx.fillRect(W- W*.06-54,H*.42,54,110); ctx.fillRect(W- W*.06-54,H*.42+120,54,70);
ctx.fillStyle=pal.accent2; ctx.beginPath(); ctx.arc(W*.06+27,H*.42+55,18+Math.sin(beat*PI)*3,0,PI*2); ctx.fill(); ctx.beginPath(); ctx.arc(W-W*.06-27,H*.42+55,18+Math.sin(beat*PI)*3,0,PI*2); ctx.fill();
// spotlight
let g=ctx.createLinearGradient(W/2,0,W/2,floorY); g.addColorStop(0,pal.spot); g.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle=g; ctx.beginPath(); ctx.moveTo(W/2-40,0); ctx.lineTo(W/2+40,0); ctx.lineTo(W/2+180,floorY); ctx.lineTo(W/2-180,floorY); ctx.closePath(); ctx.fill();
// floor
ctx.fillStyle=pal.floor; ctx.beginPath(); ctx.ellipse(W/2,floorY+18,W*.42,42,0,0,PI*2); ctx.fill();
ctx.strokeStyle=pal.accent; ctx.lineWidth=3; ctx.beginPath(); ctx.ellipse(W/2,floorY+18,W*.42,42,0,0,PI*2); ctx.stroke();
// shadow
let shScale=clamp(1-pose.y/180,.4,1); ctx.fillStyle='rgba(0,0,0,.28)'; ctx.beginPath(); ctx.ellipse(W/2+pose.sway,floorY+22,70*shScale,12*shScale,0,0,PI*2); ctx.fill();
// notes
notes=notes.filter(n=>n.life>0); notes.forEach(n=>{n.y+=n.vy;n.life-=dt*.5;ctx.save();ctx.translate(n.x,n.y);ctx.rotate(n.rot);ctx.fillStyle=pal.accent;ctx.globalAlpha=n.life;ctx.font='28px serif';ctx.fillText('♪',0,0);ctx.restore()}); ctx.globalAlpha=1;
// bird
drawBird(pose,W/2,floorY,pal);
// dust
particles=particles.filter(p=>p.life>0); particles.forEach(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=.05;p.life-=dt*1.2;ctx.fillStyle=`rgba(200,190,170,${p.life*.6})`;ctx.beginPath();ctx.arc(p.x,p.y,p.r*(2-p.life),0,PI*2);ctx.fill()});
feathers=feathers.filter(f=>f.life>0); feathers.forEach(f=>{f.x+=f.vx;f.y+=f.vy;f.vy+=.08;f.rot+=f.vr;f.life-=dt*.7;ctx.save();ctx.translate(f.x,f.y);ctx.rotate(f.rot);ctx.fillStyle=pal.wing;ctx.globalAlpha=f.life;ctx.beginPath();ctx.ellipse(0,0,10,4,0,0,PI*2);ctx.fill();ctx.restore()}); ctx.globalAlpha=1;
// crowd
ctx.fillStyle=pal.crowd; for(let i=0;i<14;i++){let x=(i/13)*W, bob=Math.abs(s(beat*PI+i))*(6+ (i%3)*3); ctx.beginPath(); ctx.arc(x,H+18-bob,26+(i%4)*4,PI,0); ctx.fill(); ctx.beginPath(); ctx.arc(x,H+38-bob,34,PI,0); ctx.fill()}
ctx.restore();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>